Skip to content

Config (aburi.config.v1)

Complete specification of aburi.json, Aburi's per-project configuration file. The JSON Schema aburi.config.v1.json is the single source of truth.

References:


1. File Format and Placement

  • File name: aburi.json or aburi.jsonc
  • Placement: workspace root (same level as pnpm-workspace.yaml / package.json, etc.)
  • Format: JSONC (comments allowed) or JSON
  • Encoding: UTF-8, LF
  • Indentation: 2 spaces

Why JSONC is allowed: this is a human-authored config, so comments on each section carry value. Generated JSON must be passable to a parser by consumers as-is, so pure JSON is supported too.

aburi.config.ts / aburi.config.yaml are not adopted (on the premise that AI interprets the config, everything is unified as static JSON).

2. Overall Structure

jsonc
{
  "$schema": "https://aburi.dev/schema/aburi.config.v1.json",

  // File exclusion (drop-list Category A)
  "ignore": ["docs/**", "scripts/legacy/**"],
  "respectGitignore": true,

  // Plugin enablement (extension-vocab §4)
  "languages": ["lang-typescript"],
  "frameworks": ["framework-nestjs"],
  "effects": ["effects-prisma", "effects-pino"],

  // Per-plugin options (optional)
  "pluginOptions": {
    "effects-prisma": { "treatExtendsAsTx": true }
  },

  // Component override (autodetect is the default)
  "components": [
    { "id": "billing", "roots": ["apps/billing"] }
  ],

  // drop-list extension (Category D)
  "suppress": ["myLogger", "metrics"],
  "keep": ["myLogger.audit"],

  // Framework hints (Tier 3 plugin, extension-vocab §11.3)
  "frameworkHints": [
    {
      "name": "acme-framework",
      "decorators": {
        "AcmeController": { "boundary": true, "extKind": "framework:acme:controller" }
      }
    }
  ],

  // Output destination
  "output": {
    "dir": "out"
  },

  // Execution mode
  "strict": true,

  // Resource limits for parsing/extraction
  "maxFileSizeBytes": 2097152,        // 2 MB (default); files exceeding this are skipped
  "parseTimeoutMs": 5000              // per-file parse timeout (default 5s)
}

Every field is optional. Even an empty { "$schema": "..." } works (autodetect does everything).

3. ignore

Array of glob patterns appended to drop-list Category A.

jsonc
{
  "ignore": ["docs/**", "scripts/legacy/**", "**/*.fixture.ts"]
}
  • POSIX globs (forward slashes)
  • Relative to the workspace root
  • micromatch-compatible

These are appended to the core standard patterns of drop-list §3.1 (existing patterns are not replaced).

4. respectGitignore

jsonc
{ "respectGitignore": true }
  • Default: true
  • Incorporates .gitignore patterns into drop-list Category A

Setting it to false makes git-ignored files (build artifacts, etc.) extraction targets as well. Intended for special cases such as deliberately inspecting built code in CI.

5. Plugin Enablement

5.1 languages / frameworks / effects

jsonc
{
  "languages": ["lang-typescript"],
  "frameworks": ["framework-nestjs", "framework-nextjs"],
  "effects": ["effects-prisma", "effects-pino", "effects-fetch"]
}

Each array element is a plugin manifest name (the name field).

5.2 Resolution Order

For a string <id>, Aburi resolves in the following order:

  1. <id> is a path (starts with ./ or ../) → relative-path resolution
  2. Resolve <id> as an npm package name
  3. Resolve @aburi/<id> as an npm package name (fallback resolution for official plugins)

Examples:

  • "effects-prisma" → try effects-prisma first, then @aburi/effects-prisma
  • "./aburi-plugins/internal-framework.mjs" → direct relative path

5.3 Meaning of Array Order

ArrayMeaning of order
languagesOrder is meaningless (unless extensions collide)
frameworksOrder is meaningless (unless decorator names etc. collide; on collision, config order wins)
effectsOrder is priority (effect-plugin §5.1 first-match-wins)

Earlier entries in effects have higher priority. Placing a project-specific plugin first gives it priority over standard plugins.

5.4 pluginOptions

Opaque per-plugin configuration.

jsonc
{
  "pluginOptions": {
    "effects-prisma": { "treatExtendsAsTx": true },
    "framework-nestjs": { "considerHttpExceptionAsThrow": false }
  }
}
  • Keys are plugin manifest names
  • Values are arbitrary objects interpreted by the plugin (the Aburi core does not inspect their contents)
  • Each plugin is responsible for documenting the schema of its options

6. components

Overrides the logical component boundaries of a monorepo. When not specified explicitly, the core Component autodetect infers them from pnpm-workspace.yaml / turbo.json / go.work / Cargo.toml / pyproject.toml, etc.

jsonc
{
  "components": [
    {
      "id": "billing",
      "name": "Billing",
      "roots": ["apps/billing", "packages/billing-domain"],
      "publicApi": [
        "apps/billing/src/routes/**",
        "ts:packages/billing-domain/src/index.ts#Invoice"
      ],
      "frameworks": ["nestjs"]
    }
  ]
}

Each field has the same shape as ir-schema.md §4. However, languages may be omitted on the config side (when omitted, autodetect fills it in). name may also be omitted on the config side, in which case the autodetect result (package.json#name, etc.) is used. In the schema, both are required on the IR Component; the config Component is lenient.

6.1 Merge Rules

Components explicitly declared in the config replace the autodetect results (no merging):

  • The config contains component A's id → the autodetect result with the same id is discarded and the config is adopted
  • The config does not contain component A's id → the autodetect result is adopted as-is

No ad-hoc merge is provided for "I only want to tweak part of the autodetect result". A component you want to modify must be written out explicitly.

7. suppress / keep (drop-list Extension)

jsonc
{
  "suppress": ["myLogger", "metrics", "tracer"],
  "keep": ["myLogger.audit", "@Transaction"]
}
  • suppress[]: identifier prefixes. Excludes all of myLogger.* from effects/calls
  • keep[]: exceptional retention. The @<name> form denotes a decorator; anything else a callee

Follows the evaluation order of drop-list §6.1 / §6.2 / §7 (keep > suppress > plugin > core).

8. frameworkHints (Tier 3 Plugin)

A mechanism to declaratively add framework Boundary recognition and extKind mapping with no code at all. The concrete form of the Tier 3 plugin of extension-vocab §11.3.

jsonc
{
  "frameworkHints": [
    {
      "name": "acme-framework",
      "decorators": {
        "AcmeController": {
          "boundary": true,
          "extKind": "framework:acme:controller",
          "derivedBy": "framework-hint:acme:controller"
        },
        "AcmeInternal": {
          "boundary": false,
          "drop": true
        }
      },
      "classNamePatterns": {
        "*Handler": {
          "extKind": "framework:acme:handler"
        }
      }
    }
  ]
}

8.1 decorators

Keys are decorator names (the AcmeController part of the decorator @AcmeController). Fields of each value:

FieldEffect
boundaryOverrides Decorator.boundary with this value
extKindSets Symbol.extKind for Symbols carrying this decorator
derivedByAppended to Symbol.derivedBy[]
dropCategory-B drop of Symbols carrying this decorator

All are optional.

8.2 classNamePatterns

Keys are class-name globs (*Handler, *Service, Abstract*). Values take the same fields as decorators (extKind/derivedBy/drop; boundary is decorator-only).

8.3 Automatic Ad-hoc Plugin Conversion

Each frameworkHints entry is internally registered as a single ad-hoc plugin:

name: hint-<name>
type: framework
provides.extKindPrefixes:  each extKind value's namespace with "hint:" prepended
provides.derivedByPrefixes: likewise
provides.frameworks: [<name>]

8.3.1 Namespace Isolation via Automatic hint: Prefixing

The user-written extKind: "framework:acme:controller" is internally transformed into framework:hint:acme:controller. This avoids:

  • Collision accidents on the framework:acme prefix when an @aburi/framework-acme plugin is already installed
  • frameworkHints later breaking an existing plugin

The hint: prefixing is done transparently by the core, so users keep writing extKind: "framework:acme:controller". It appears in the generated IR and Markdown as framework:hint:acme:controller.

Example: extKind: "framework:acme:controller" → internally framework:hint:acme:controllerextKindPrefixes: ["framework:hint:acme"] is inferred automatically.

Users never need to think about prefix declarations.

8.4 Collisions

  • Multiple entries with the same name → config validation error
  • Another entry claims the same extKindPrefixes (auto-inferred result) → the registry errors at startup (extension-vocab §6.1)
  • An existing plugin owns the same namespace → startup error

9. output

jsonc
{
  "output": {
    "dir": "out"
  }
}
  • dir (default "out"): output destination for IR/Markdown (relative to the workspace root)

Future addition candidates:

  • format: "json" / "md" / "both" (default "both")
  • compact: true for single-line JSON

Currently only dir is supported.

10. strict

jsonc
{ "strict": true }
  • Default: true
  • When true, a plugin emitting vocab not declared in its manifest is an extraction-time error
  • When false (= making the equivalent of aburi scan --discover permanent), it is a warning only and execution continues

The CI default is true; use false or the CLI's --discover when you want discovery in local development.

When both are specified, the CLI flag overrides the config.

11. Overrides from the CLI

Config values can be overridden by CLI flags:

CLI flagConfig overridden
--ignore <glob>Appended to ignore[]
--no-respect-gitignorerespectGitignore: false
--strict / --no-strictstrict
--discoverEquivalent to strict: false
--output-dir <path>output.dir

See cli-spec.md for details.

12. autodetect (Behavior Without a Config)

When no config exists at all (aburi.json is absent), aburi scan attempts the following autodetect:

Determining the workspace root:
  - Location of the .git directory (or the nearest one when inside a subdirectory)
  - pnpm-workspace.yaml / package.json workspaces / turbo.json / go.work / Cargo.toml workspace

Monorepo detection:
  - Infer components[].roots from the manifests above

Language detection:
  - Determine languages per component from extension frequency

Framework detection:
  - Look for nestjs / next / react / express etc. in package.json dependencies
  - Suggest auto-enabling the `framework-<name>` plugin matching each detected framework (without actually enabling it)

Autodetect alone is enough to run, but for stability it is recommended to write the results to aburi.json via aburi init.

13. Output of aburi init

aburi init writes the autodetect results out as aburi.json. Example:

jsonc
{
  "$schema": "https://aburi.dev/schema/aburi.config.v1.json",
  "languages": ["lang-typescript"],
  "frameworks": [],
  "effects": [],
  "components": [
    { "id": "billing", "name": "Billing", "roots": ["apps/billing"], "languages": ["typescript"] },
    { "id": "shared",  "name": "Shared",  "roots": ["packages/shared"], "languages": ["typescript"] }
  ]
}

Users are expected to add framework / effects plugins afterwards.

14. Verifiable Properties

IDInputExpectation
C1A config that is only {}Runs via autodetect
C2effects is an empty arrayNo effect classification; everything remains in calls
C3effects: ["effects-prisma", "effects-stripe"]Classification is evaluated in order
C4Two entries with the same component idConfig validation error
C5The same callee in both keep and suppresskeep wins (drop-list §7)
C6Two frameworkHints entries with the same nameConfig validation error
C7Prefixes auto-inferred from frameworkHints extKind valuesRegistered in the registry
C8Extracting undeclared vocab with strict: falseWarning only; recorded in out/aburi-vocab-discovered.json
C9Passing CLI --strictOverrides config strict: false
C10Specifying an unregistered plugin in pluginOptionsWarning (ignored because the plugin is disabled)

14.1 Config Schema Compatibility Policy

The compatibility of aburi.config.v1.json follows the same policy as the IR schema (ir-schema.md §15). Particularly important:

  • The contents of pluginOptions values are managed by each plugin's own schema and are outside the compatibility scope of the Aburi core
  • Adding fields to frameworkHints is non-breaking
  • Changing the default values of output.dir / strict is treated as breaking (behavior changes in CI, so it goes to v2)

15. Design Decisions

15.1 Why Explicit Plugin Enablement Is Adopted

There is an "implicit" alternative — scanning node_modules and auto-loading anything with an aburi-plugin.json — but it is rejected to avoid:

  • A single rogue package influencing Aburi's output
  • Developers struggling to trace "why is this effect appearing"
  • Unpredictable vocab collisions

Explicit enablement is used uniformly. aburi init proposes and writes candidates from the package.json dependencies, so the manual burden is effectively zero.

15.2 Why Plugin Names Are Manifest Names

Using npm package names (@aburi/effects-prisma) alongside manifest names (effects-prisma) makes naming redundant. The config is more readable written with short manifest names. Aburi trying @aburi/<name> during npm resolution preserves the DX of official plugins.

15.3 Why pluginOptions Is an Opaque Object

Controlling each plugin's behavior is the plugin's responsibility. If the Aburi core enforced a schema, plugin evolution would slow down.

Instead, each plugin documents its own pluginOptions schema, and users consult the plugin docs.

15.4 Why components Merging Is Not Supported

Ad-hoc merging causes the accident where "I wrote a config, but a change in autodetect behavior silently changed the values". By standardizing on replacement, the behavior of a written config is predictable independently of autodetect changes.

15.5 Automatic Prefix Inference for frameworkHints

Making users hand-write extKindPrefixes: ["framework:acme"] is redundant and error-prone. Auto-inferring from extKind values lets a Tier 3 plugin come together with minimal code.

15.6 Why aburi.config.ts Is Not Adopted

  • On the premise that AI consumers read the config, static JSON is overwhelmingly easier to handle
  • TypeScript completion is fully achievable via JSON Schema ($schema) — the biome.json / tsconfig.json worldview
  • The need for dynamic logic (e.g. deciding suppress via a function) is low; when needed, writing a plugin has higher reusability

Released under the MIT License.