IR Schema (aburi.ir.v1)
Schema definition of the Intermediate Representation (IR) emitted by Aburi. The JSON Schema at schema/aburi.ir.v1.json is the single source of truth; this document states the rationale behind the design decisions.
1. File Format and Ordering
- Format: JSON (UTF-8, LF)
- Indentation: 2 spaces (default), single line with
--compact - Top-level keys: ascending alphabetical order
- Array ordering (fixed for diff stability):
components[]: ascending byidsymbols[]: ascending byiddependencies[]: lexicographic by (from,to,via)decorators[]/rules[]/effects[]/calls[]within a Symbol: ascending byline(source order within the same line)
The ordering convention is a precondition for diff stability. Spurious diffs caused by array order must never occur.
2. Top-Level Structure (Document)
{
"$schema": "https://aburi.dev/schema/aburi.ir.v1.json", // required
"generator": { // required
"name": "aburi",
"version": "1.0.0",
"plugins": [ // required; records ALL plugins (lang/framework/effects)
{ "name": "lang-typescript", "type": "lang", "version": "1.0.0", "grammarRevision": "tree-sitter-typescript@0.23.2" },
{ "name": "framework-nestjs", "type": "framework", "version": "1.0.0", "grammarRevision": null },
{ "name": "effects-prisma", "type": "effects", "version": "1.0.0", "grammarRevision": null }
]
},
"generatedAt": "2026-06-19T15:30:00Z", // optional (may be omitted with --no-timestamp)
"workspace": { // required
"root": ".",
"managers": [ // required (empty array allowed)
{ "tool": "pnpm", "roots": ["packages/*", "apps/*"] }
],
"languages": ["ts"] // required (short-form ids declared by lang plugins, e.g. ts/tsx/py/go/rs)
},
"components": [ /* Component[] */ ], // required
"symbols": [ /* Symbol[] */ ], // required
"dependencies": [ /* Dependency[] */ ], // required
"stats": { // required
"totalFiles": 18,
"parsedFiles": 18,
"keptSymbols": 27,
"droppedSymbols": 7
}
}$schema: canonical URL. Used for IDE JSON Schema resolution and integrated validationgeneratedAt: operational metadata of the producer. Excluded from fingerprint computation. When committing the IR and diffing it, omit it with--no-timestampworkspace.root: always".". Never write absolute paths (IR portability)workspace.managers[].tool: runtime-independent string. Representative values:pnpm/npm/yarn/bun/uv/poetry/pip/cargo/go/mvn/gradle/hatch/pixi. Unknown values are not rejected (so that adding a new tool never requires a schema revision)stats: for human/CI logs. Excluded from fingerprint
3. Symbol ID Convention
3.1 Format
<language>:<file-path>#<qualified-name><language>: identifier declared by the language plugin (ts,tsx,js,py,go,rs, ...)<file-path>: POSIX path relative to the workspace root (forward slashes enforced)<qualified-name>: name unique within the file
3.2 Building the qualified name
| Symbol | qualified name |
|---|---|
| top-level function / const / var | createInvoice |
| class | InvoiceService |
| instance method | InvoiceService.createInvoice |
| static method | InvoiceService::fromJson |
| nested namespace / class | Billing.Invoice.create |
| interface / type alias | Invoice |
| default export (including anonymous functions/classes) | <default> |
| function/class expression assigned to a variable | the variable name becomes the qname (e.g. const handler = () => ... → handler) |
3.3 Handling anonymous symbols
Anonymous symbols do not become independent Symbol entries. Callbacks, immediately-invoked function expressions, and anonymous function arguments are absorbed into the parent Symbol's calls / effects / rules. Position-dependent IDs (of the <anon@L42> kind) must not be used (they break diff stability).
The only exceptions are <default> and "function expressions assigned to a variable" from §3.2. These are named entry points, so they get a Symbol.
3.4 ID stability
- IDs generated by the same Aburi version for the same input match exactly
- Renaming parameters or local variables does not change the ID
- Moving a file changes the ID (the
<file-path>part changes) → the Diff algorithm assigns themovedstatus via git rename + fingerprint matching
4. Component
A logical boundary of the monorepo. Independent of physical packages.
{
"id": "billing", // required, unique, ASCII kebab-case
"name": "Billing", // required, human-facing label
"roots": ["apps/billing", "packages/billing-domain"], // required, POSIX relative
"publicApi": [ // optional
"apps/billing/src/routes/**",
"ts:packages/billing-domain/src/index.ts#Invoice"
],
"languages": ["ts"], // required, short-form lang ids
"frameworks": ["nestjs"], // optional
"description": null // optional
}idis fixed to ASCII kebab-case so it can be used in URLs and CLI arguments- Each element of
publicApiis either a glob or a symbol id - Physical Component boundary inference automatically reads package manager configuration (
pnpm-workspace.yaml,turbo.json,go.work,Cargo.tomlworkspace,pyproject.toml/uv workspaces, etc.); see the separate documentcomponent-detect.mdfor details
5. Symbol
The core entity of the review unit.
{
"id": "ts:apps/billing/src/InvoiceService.ts#InvoiceService.createInvoice", // required
"kind": "method", // required, enum §5.1
"extKind": null, // optional, language extension §5.2
"name": "InvoiceService.createInvoice", // required, qualified name
"language": "ts", // required, short-form lang id (e.g. ts / tsx / py / go / rs)
"component": "billing", // optional (null = outside any component)
"visibility": "public", // required, enum §5.3
"decorators": [ /* Decorator[] */ ], // required
"signature": { /* Signature */ }, // optional (null = no signature)
"rules": [ /* Rule[] */ ], // required
"effects": [ /* Effect[] */ ], // required
"calls": [ /* Call[] */ ], // required
"source": { /* SourceRange */ }, // required
"fingerprint": { /* Fingerprint */ }, // required
"confidence": "high", // required, enum §5.4
"derivedBy": ["framework:nestjs:controller", "branch-condition"], // required
"dropped": false, // required
"dropReason": null // required, non-null when dropped=true
}5.1 kind (core enum)
"function" | "method" | "class" | "interface" | "type" | "const" | "module" | "namespace" | "variable" | "enum" | "constructor"
Anything outside the core enum is expressed via extKind. A consumer may treat an unknown kind as an error (= strict enum).
5.2 extKind (language extension)
Either null or a string of the form <namespace>(:<segment>)+. At least 2 segments, arbitrarily deep. The namespace is a language/paradigm identifier:
| namespace | examples | owning plugin |
|---|---|---|
fp:* | fp:match, fp:adt, fp:effect | functional-language plugins |
oop:* | oop:abstract, oop:trait | OOP extension plugins |
meta:* | meta:macro, meta:proc-macro | macro-language plugins |
framework:* | framework:nestjs:guard, framework:react:hook | framework plugins |
When extKind is non-null, kind must hold the closest core kind (kind: "function" when extKind: "fp:match"). Consumers that only read the core vocabulary can ignore extKind.
5.3 visibility (enum)
"public" | "private" | "protected" | "internal" | "package"
public: explicit export or public modifierprivate/protected: in-class visibility as-isinternal: visible within the workspace but not exposed externallypackage: visible within the monorepo package but not exposed outside the component
5.4 confidence (enum)
"high" | "medium" | "low"
Criteria:
| Value | Criterion |
|---|---|
high | Explicit in the AST (export modifier, throw statement, if statement), or explicitly declared by a framework/effect plugin |
medium | Identifier match (inferring db.write from prisma.invoice.create), or determination from naming conventions (*Service, *Controller) |
low | Heuristic (symbol connectivity or file location is the only evidence) |
Symbols with low confidence get a badge in the Markdown projection so reviewers clearly know the machine is not confident.
5.5 derivedBy (evidence)
A string array indicating why this symbol was extracted in this form. Each entry takes one of the following forms:
<rule>— a core extraction rule (branch-condition,throw-statement,export-keyword)framework:<name>:<role>— determined by a framework plugin (framework:nestjs:controller)effects-plugin:<name>:<action>— determined by an effect plugin (effects-plugin:prisma:write)convention:<name>— determined by a naming/structural convention (convention:service-suffix)
An empty array means "picked up automatically by the core extraction path".
5.6 dropped
false(default): included in the IR outputtrue: judged to be decoration, but kept for transparency about what was dropped
Symbols with dropped: true have rules/effects/calls/fingerprint set to their prescribed values (empty arrays / all fingerprints = "0"*12). The Markdown projection aggregates them in a collapsed ## Dropped section (see markdown-projection.md §3.6). aburi explain outputs the full details. dropReason is a short human-readable phrase (e.g. "pure DTO", "logger boilerplate", "generated file").
6. Decorator
{
"name": "Post", // required
"raw": "Post('/invoices')", // required, verbatim source
"arguments": ["'/invoices'"], // required, string representations of the arguments
"boundary": true, // required
"line": 14 // required
}The boundary: true determination is made by the framework plugin. The Aburi core does not hardcode it.
7. Signature
{
"inputs": [
{ "name": "createInvoiceDto", "type": "CreateInvoiceDto" }
],
"outputs": ["Promise<Invoice>"],
"throws": ["CreditLimitExceeded"],
"async": true,
"generator": false,
"typeParameters": []
}typeis the string representation as read from the AST. No type resolution is performed (LSP enrichment may optionally normalize it)throwscombines explicit throw statements and JSDoc@throws- A Symbol's entire
signaturemay benull(class bodies, whole interfaces)
8. Rule
Semantically meaningful branches, exceptions, loops, and compound returns in the control flow.
{
"type": "guard", // required, enum §8.1
"line": 58, // required
"condition": "customer.creditLimit < invoice.total", // type=guard/switch/match only
"what": null, // type=throw only
"expr": null, // type=return only
"loopKind": null // type=loop only ("for"|"while"|"do")
}8.1 type (enum)
"guard" | "throw" | "return" | "loop" | "try" | "switch" | "match"
guard: anifstatement containing an early return / throw / continuethrow: a throw statementreturn: any non-trivial return (trivial determination perdrop-list.md)loop: for / while / dotry: try-catch (rules inside the catch body are not expanded into the same Symbol's rules)switch: a switch statementmatch: pattern matching (used only in symbols withextKind: "fp:match")
8.2 Extraction conventions
- The same AST node must not produce multiple Rules
condition/what/exprare whitespace-normalized (consecutive whitespace collapsed to one, newlines removed, trailing...when over 120 characters)- Simple returns such as
return x/return truedo not become Rules (inclusion follows the trivial determination indrop-list.md)
9. Effect
The result of side-effect detection.
{
"id": "db.write", // required, effect tag §9.1
"target": "prisma.invoice.create", // required, callee string
"line": 75, // required
"plugin": "effects-prisma", // required, detection source
"confidence": "high" // required, same enum as Symbol
}9.1 Core effect vocabulary
A fixed set in namespace:action form. Only concepts that hold universally across any runtime/language.
| Category | Values |
|---|---|
db.* | db.read, db.write, db.transaction, db.migration |
network.* | network.http, network.ws, network.rpc |
queue.* | queue.publish, queue.consume |
event.* | event.publish, event.subscribe |
fs.* | fs.read, fs.write |
state.* | state.mutate, collection.mutate |
time.* | time.now, time.timer |
random | random |
env.* | env.read, env.write |
process.* | process.exit, process.signal |
Within schema version aburi.ir.v1, this set is additive-only; deletion and semantic change are forbidden.
9.2 Plugin-extended effects
Effects specific to a particular runtime/library/domain are declared by the plugin with the x-<plugin>:<action> prefix.
Examples:
x-stripe:chargex-s3:uploadx-nest:lifecycle.on-module-initx-auth:permission-checkx-react:state-update
Consumers must tolerate unknown x- effects; the Markdown projection sections them by prefix.
9.3 Separation of Effect and Call
If a call_expression is recognized by an effect plugin, it is recorded in effects[] only, not in calls[]. No duplicate output.
10. Call
A call that does not qualify as an effect.
{
"target": "pricing.calculateTotal", // required
"line": 70, // required
"resolved": null // optional, resolved symbol id
}resolved is filled in by the call-resolution feature (separate document). null while unresolved.
11. Dependency
An edge between symbols or between components.
{
"from": "billing", // required, symbol id or component id
"to": "pricing",
"via": "import", // required, enum
"direction": "outbound", // required, enum
"effect": null // optional, related effect tag
}11.1 via (enum)
"import" | "call" | "inherit" | "implement" | "compose" | "http" | "event" | "sql"
11.2 direction (enum)
"outbound" | "inbound" | "bidirectional"
The direction as seen from from. bidirectional is limited to cases such as bidirectional RPC.
12. SourceRange
{
"file": "apps/billing/src/InvoiceService.ts", // required, POSIX relative
"startLine": 42, // required, 1-based
"endLine": 91, // required
"startColumn": null, // optional, 1-based
"endColumn": null // optional
}startColumn / endColumn are filled in during LSP enrichment.
13. Fingerprint
{
"api": "9ee77913af43", // required, 12 hex
"logic": "7ecf8c1cebe7", // required, 12 hex
"syntax": "a3f2e1d0c9b8" // required, 12 hex
}See the separate document fingerprint.md for the exact computation. This document only stipulates that "there are three 12-hex strings" and that "the fingerprint computation includes no noise other than generatedAt / stats / array order".
Roles of the three axes:
| Axis | Changes when | Invariant under |
|---|---|---|
api | signature / visibility / boundary decorator changes | body implementation changes |
logic | changes to the set of rules / effects | local variable renames / method reordering / comments / added decoration |
syntax | any AST structural change | formatting-only changes |
For symbols with dropped: true, all fingerprints are fixed to the 12-hex string "000000000000".
14. Invariants
Guaranteed by the schema validator plus Aburi internals:
symbols[].idis unique within the Documentcomponents[].idis unique within the Document- If
symbols[].componentis non-null, it exists incomponents[].id - If
dependencies[].from/tois in symbol-id form, it exists insymbols[].id - If
dropped: true,dropReasonis non-null confidence∈ §5.4 enumeffects[].idis in the §9.1 core vocabulary or carries anx-<plugin>:prefixkindis in the §5.1 enumextKindisnullor of the form<namespace>(:<segment>)+(at least 2 segments, arbitrarily deep)- All paths are POSIX (forward slash), relative to the workspace root
- The array-ordering conventions (§1) are satisfied
An invariant violation is a fatal error, not a warning.
15. Versioning
15.1 $schema URL
- Fixed to
https://aburi.dev/schema/aburi.ir.v1.json - Backward-compatible field additions: allowed within v1
- Field removal / type change / semantic change: goes to v2. Change
$schematoaburi.ir.v2.json
15.2 Compatibility policy
| Change | Compatibility |
|---|---|
| Adding a required field | breaking |
| Adding an optional field | non-breaking |
| Adding an enum value (only where consumers tolerate unknowns) | non-breaking |
| Removing an enum value | breaking |
| Required → optional | non-breaking |
| Optional → required | breaking |
| Renaming a field | breaking |
| Changing array-ordering rules | breaking |
Because consumers may treat unknown kind values as errors, additions to the kind enum are also treated as breaking. extKind / derivedBy / effects[].id (x- extensions) / via are close to free-form strings, so additions are unrestricted.
15.3 Freezing the core effect vocabulary
The core effect vocabulary of §9.1 is additive-only within version v1. Deletion and semantic change are forbidden. Plugin extensions are fully separated by the x- prefix, so freezing the core does not hinder plugin evolution.
16. Extension Points
Places where Aburi can be extended without forking the core:
| Extension target | Location | Extension form |
|---|---|---|
| Special concepts of a new language | extKind | <namespace>:<kind> |
| Runtime/library-specific effects | effects[].id | x-<plugin>:<action> |
| Additional extraction evidence | derivedBy | free-form string (by convention <plugin>:<reason>) |
| Logical Component boundaries | components[] (via config) | arbitrary |
To avoid breaking compatibility, the design is deliberately asymmetric: fields consumed by the Aburi core are strict enums, while fields extended by plugins are free-form strings.