Skip to content

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 by id
    • symbols[]: ascending by id
    • dependencies[]: lexicographic by (from, to, via)
    • decorators[] / rules[] / effects[] / calls[] within a Symbol: ascending by line (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)

jsonc
{
  "$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 validation
  • generatedAt: operational metadata of the producer. Excluded from fingerprint computation. When committing the IR and diffing it, omit it with --no-timestamp
  • workspace.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

Symbolqualified name
top-level function / const / varcreateInvoice
classInvoiceService
instance methodInvoiceService.createInvoice
static methodInvoiceService::fromJson
nested namespace / classBilling.Invoice.create
interface / type aliasInvoice
default export (including anonymous functions/classes)<default>
function/class expression assigned to a variablethe 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 the moved status via git rename + fingerprint matching

4. Component

A logical boundary of the monorepo. Independent of physical packages.

jsonc
{
  "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
}
  • id is fixed to ASCII kebab-case so it can be used in URLs and CLI arguments
  • Each element of publicApi is either a glob or a symbol id
  • Physical Component boundary inference automatically reads package manager configuration (pnpm-workspace.yaml, turbo.json, go.work, Cargo.toml workspace, pyproject.toml/uv workspaces, etc.); see the separate document component-detect.md for details

5. Symbol

The core entity of the review unit.

jsonc
{
  "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:

namespaceexamplesowning plugin
fp:*fp:match, fp:adt, fp:effectfunctional-language plugins
oop:*oop:abstract, oop:traitOOP extension plugins
meta:*meta:macro, meta:proc-macromacro-language plugins
framework:*framework:nestjs:guard, framework:react:hookframework 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 modifier
  • private / protected: in-class visibility as-is
  • internal: visible within the workspace but not exposed externally
  • package: visible within the monorepo package but not exposed outside the component

5.4 confidence (enum)

"high" | "medium" | "low"

Criteria:

ValueCriterion
highExplicit in the AST (export modifier, throw statement, if statement), or explicitly declared by a framework/effect plugin
mediumIdentifier match (inferring db.write from prisma.invoice.create), or determination from naming conventions (*Service, *Controller)
lowHeuristic (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 output
  • true: 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

jsonc
{
  "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

jsonc
{
  "inputs": [
    { "name": "createInvoiceDto", "type": "CreateInvoiceDto" }
  ],
  "outputs": ["Promise<Invoice>"],
  "throws": ["CreditLimitExceeded"],
  "async": true,
  "generator": false,
  "typeParameters": []
}
  • type is the string representation as read from the AST. No type resolution is performed (LSP enrichment may optionally normalize it)
  • throws combines explicit throw statements and JSDoc @throws
  • A Symbol's entire signature may be null (class bodies, whole interfaces)

8. Rule

Semantically meaningful branches, exceptions, loops, and compound returns in the control flow.

jsonc
{
  "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: an if statement containing an early return / throw / continue
  • throw: a throw statement
  • return: any non-trivial return (trivial determination per drop-list.md)
  • loop: for / while / do
  • try: try-catch (rules inside the catch body are not expanded into the same Symbol's rules)
  • switch: a switch statement
  • match: pattern matching (used only in symbols with extKind: "fp:match")

8.2 Extraction conventions

  • The same AST node must not produce multiple Rules
  • condition/what/expr are whitespace-normalized (consecutive whitespace collapsed to one, newlines removed, trailing ... when over 120 characters)
  • Simple returns such as return x / return true do not become Rules (inclusion follows the trivial determination in drop-list.md)

9. Effect

The result of side-effect detection.

jsonc
{
  "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.

CategoryValues
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
randomrandom
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:charge
  • x-s3:upload
  • x-nest:lifecycle.on-module-init
  • x-auth:permission-check
  • x-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.

jsonc
{
  "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.

jsonc
{
  "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

jsonc
{
  "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

jsonc
{
  "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:

AxisChanges whenInvariant under
apisignature / visibility / boundary decorator changesbody implementation changes
logicchanges to the set of rules / effectslocal variable renames / method reordering / comments / added decoration
syntaxany AST structural changeformatting-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:

  1. symbols[].id is unique within the Document
  2. components[].id is unique within the Document
  3. If symbols[].component is non-null, it exists in components[].id
  4. If dependencies[].from / to is in symbol-id form, it exists in symbols[].id
  5. If dropped: true, dropReason is non-null
  6. confidence ∈ §5.4 enum
  7. effects[].id is in the §9.1 core vocabulary or carries an x-<plugin>: prefix
  8. kind is in the §5.1 enum
  9. extKind is null or of the form <namespace>(:<segment>)+ (at least 2 segments, arbitrarily deep)
  10. All paths are POSIX (forward slash), relative to the workspace root
  11. 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 $schema to aburi.ir.v2.json

15.2 Compatibility policy

ChangeCompatibility
Adding a required fieldbreaking
Adding an optional fieldnon-breaking
Adding an enum value (only where consumers tolerate unknowns)non-breaking
Removing an enum valuebreaking
Required → optionalnon-breaking
Optional → requiredbreaking
Renaming a fieldbreaking
Changing array-ordering rulesbreaking

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 targetLocationExtension form
Special concepts of a new languageextKind<namespace>:<kind>
Runtime/library-specific effectseffects[].idx-<plugin>:<action>
Additional extraction evidencederivedByfree-form string (by convention <plugin>:<reason>)
Logical Component boundariescomponents[] (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.

Released under the MIT License.