Skip to content

Drop List Standard Set

The core convention behind Aburi's differentiating extraction strategy of "stripping decoration". Defines which files, symbols, and nodes are removed from the IR, and when the fact of removal is retained as dropped: true.

See: ir-schema.md §5.6 (dropped convention) / §8.2 (Rule extraction convention) Extension mechanism: extension-vocab.md (for plugins that add drop rules)


1. Purpose

Identify and remove decorative, ceremonial, and self-evident structure so that the extraction pipeline keeps only meaningful logic. A low-noise IR means:

  • diff reports show only the semantic changes, increasing review focus
  • fewer tokens when an AI consumer reads the IR
  • stable fingerprints (adding decoration does not flood the diff)

drop means "removed from view", not "discarded". For transparency, symbol-level drops remain in the IR as dropped: true.

2. The four drop tiers

TierGranularityTreatment in the IR
A. File-level skipEntire fileNever appears in symbols/dependencies (also excluded from stats.totalFiles)
B. Symbol-level dropSingle SymbolRetained with dropped: true + dropReason; fingerprint is all zeros
C. Node-level filtercall / return / decorator inside a SymbolIndividually excluded from call/return/effect/rule; the Symbol itself remains
D. Config/Plugin extensionLayered on top of all of A/B/CFollows the evaluation order in §7

3. Category A: File-level skip

Excludes an entire file from extraction. The file is not even AST-parsed.

3.1 Core standard patterns

PatternReason
**/node_modules/**Dependency libraries
**/dist/** **/build/** **/out/** **/target/**Build artifacts
**/.next/** **/.nuxt/** **/.svelte-kit/** **/.output/**Framework build caches
**/coverage/**Coverage reports
**/__snapshots__/** **/*.snapTest snapshots
**/*.d.ts **/*.d.mts **/*.d.ctsTS type declaration files (no implementation)
**/*.generated.* **/*.gen.* **/*.g.tsAuto-generated
**/*.min.js **/*.bundle.jsminified/bundled
**/__pycache__/** **/*.pycPython caches
**/.venv/** **/venv/** **/site-packages/**Python virtual environments
**/target/** **/Cargo.lockRust
**/vendor/** **/go.sumGo

3.2 Additions by language plugins

Each language plugin may add skip patterns specific to its own language. Examples:

  • lang-typescript: **/*.d.ts, **/*.config.{js,ts,mjs,cjs} (optional)
  • lang-python: **/__pycache__/**, **/*.pyi
  • lang-rust: **/target/**, **/Cargo.lock

3.3 .gitignore integration

Patterns from .gitignore are respected by default (can be disabled with --no-respect-gitignore).

Files ignored by git are therefore skipped automatically without listing dist/ etc. explicitly.

3.4 Config additions

Glob patterns from config.ignore[] are added to Category A.

4. Category B: Symbol-level drop

The Symbol itself remains in the IR but is marked dropped: true + dropReason, and its fingerprint is all zeros. Excluded from the Markdown projection; shown only via aburi explain <symbol>.

4.1 Core standard patterns

PatterndropReason
class with only field declarations, no methods, no boundary decorator"pure DTO"
interface declaration"interface (data model)"
type alias declaration"type alias"
empty function/method (body is {})"empty body"
re-export only (export { X } from './y')"re-export"
single-literal class (only static / readonly constant fields)"pure constants"

4.2 Determining "pure DTO"

A class is a pure DTO when all of the following hold:

  • The body has no methods (including constructors; a shorthand-property constructor such as class { constructor(public x: number) {} is allowed)
  • It has no boundary decorator (one that a framework plugin judged boundary: true)
  • The body consists solely of field declarations

The core performs this determination, but each language plugin may add auxiliary rules for pure-DTO detection (e.g. class-validator decorators such as @IsString count as decoration; anything else is an ordinary determinant).

4.3 Why "interface (data model)" is dropped

An interface is a declaration expressing a data shape; it has no control flow and no effects. For Aburi's primary use case (logic diff review), its details need not be kept in the IR.

However, relationships such as "some symbol implements the Invoice interface" are retained in dependencies[] as a Dependency (via=implement). The existence of the interface itself remains discoverable.

4.4 Plugin additions

Language/framework plugins may add to Category B:

  • lang-typescript: enum (?) — whether enum is treated as a data model depends on usage; make it configurable
  • framework-nestjs: a class with only @Module and no body is NOT dropped (it carries a boundary, so it is outside Category B)

5. Category C: Node-level filter

The Symbol itself remains; specific nodes inside it are excluded from effects/calls/rules.

5.1 Dropping calls (call_expression)

callee patternReason
console.{log,info,warn,error,debug,trace,table,dir,group,groupEnd}Logging
process.stdout.write / process.stderr.writeStandard output
print / println / eprintln (per language)Language-standard logging
panic (Rust's panic is handled by a plugin)(not included in this set)

These appear in neither effects nor calls.

5.2 Plugin additions

An effect plugin may add loggers that it recognizes to Category C. Examples:

  • effects-pino: pino.*, child.* (return value of logger.child)
  • effects-winston: winston.*, createLogger().*
  • effects-otel: tracer.startSpan, span.setAttribute, metrics.counter

If these plugins are enabled in the config, the corresponding callees are excluded from extraction. If not enabled, they remain as ordinary calls (i.e. an in-house logger keeps appearing in calls[] unless something is done).

5.3 Dropping trivial returns

A return statement is not turned into a rule when its returned expression is any of the following:

AST shapeExample
string/number/boolean/null/undefined literalreturn 1 / return 'x' / return true
identifierreturn x
this or a member chain (arbitrary depth)return this.value / return obj.a.b.c
unary operator + trivial expressionreturn !x / return -count
void expressionreturn void 0

Non-trivial returns (included as rules):

  • binary/logical operations (return a + b, return x > 0, return a && b)
  • ternary operations (return x ? a : b)
  • contains a function call combined with other expressions (return foo() + 1, return [...foo(), 1])
  • object/array literals containing spread/dynamic computation (return { ...x, status: 'ok' })
  • template literals with interpolation (return `hello ${name}`)
  • new expressions composed with something else (return new Foo() ?? bar)

5.4 When the return is a single call_expression only

When the returned expression is a single call, as in return foo() / return this.bar():

  • it is not included as a return rule (avoids duplication)
  • the call itself is recorded normally in calls[] (or as an effect in effects[])

Thus a forward method containing return foo() becomes "rules: empty, calls: foo", expressing the essence of forwarding.

5.5 Recursive definition of triviality

isTrivialExpr(node):
  literal             → true
  identifier          → true
  this                → true
  member_expression   → isTrivialExpr(node.object)   # arbitrary depth
  unary_expression    → isTrivialExpr(node.argument)
  parenthesized       → isTrivialExpr(node.expression)
  otherwise           → false

isTrivialReturn(returnStatement):
  arg = returnStatement.argument
  if arg is null            → true   # return without a value
  if isTrivialExpr(arg)     → true
  if arg is call_expression → "call-only" (no Rule; the call is recorded normally)
  otherwise                 → false  # non-trivial return Rule

Whether a call_expression's arguments are trivial is not part of the determination (calls are recorded independently as calls, so it is unnecessary for evaluating return wrapping).

5.6 Dropping decorators

Aburi core does not drop decorators. Framework plugins only judge boundary: true/false. Non-boundary decorators (@deprecated, @experimental, etc.) also remain in the IR (they affect the api fingerprint).

6. Category D: Config/Plugin extension

6.1 config.suppress[]

Identifier prefixes added to Category C (call drop):

jsonc
{
  "suppress": ["myLogger", "metrics", "telemetry"]
}

This excludes myLogger.* / metrics.* / telemetry.* calls from effects/calls.

6.2 config.keep[]

Patterns exceptionally retained from Category C drops:

jsonc
{
  "keep": ["@Transaction", "myCriticalLogger.audit"]
}
  • @<name> form: a decorator name (the core never drops decorators, so this has weak meaning, but it is allowed for explicitness)
  • <callee> form: exempts a call from dropping (e.g. myCriticalLogger.audit is the core of monitoring, so keep it)

6.3 Drop additions via Framework hints (Tier 3 plugin)

From the Framework hints in extension-vocab.md §11.3:

jsonc
{
  "frameworkHints": [
    {
      "name": "acme-framework",
      "decorators": {
        "AcmeInternal": { "boundary": false, "drop": true }
      }
    }
  ]
}

Symbols decorated with @AcmeInternal are added to Category B.

7. Evaluation order

Precedence when multiple drop rules / keep rules conflict. Higher entries win:

1. config.keep                          (forced keep)
2. config.suppress                       (forced drop, but loses to keep)
3. plugin drop rules (including manifest.dropCallees)  (added to Category C/B)
4. core drop rules                       (Category A/B/C standard set)
5. default                               (no drop)

When an effect plugin such as @aburi/effects-pino declares dropCallees: ["pino"], it is treated as a level 3 plugin drop rule. Individual entries can be kept via config.keep: ["pino.audit"] (level 1).

Concrete examples:

  • console.log is a core drop target (level 4) and config.keep: ["console.log"] is present → keep wins; it is retained
  • myLogger.info is a drop candidate via config.suppress: ["myLogger"] (level 2) and config.keep: ["myLogger.audit"] is present → myLogger.info is dropped, myLogger.audit is retained
  • Some plugin adds panic! as a drop target (level 3) → can be exempted via config.keep; the core does not touch it

8. Verifiable properties (test criteria)

Properties the extraction pipeline must satisfy.

8.1 File-level (Category A)

IDInputExpected
A1scan including node_modules/foo/bar.tsDoes not appear in symbols; excluded from stats.totalFiles
A2scan including *.d.tsSame as above
A3Files listed in .gitignoreSame as above (unless --no-respect-gitignore)
A4Adding config.ignore: ["docs/**"]Everything under docs/ is skipped

8.2 Symbol-level (Category B)

IDInputExpected
B1pure DTO (class Foo { x: number; y: string })dropped: true, dropReason: "pure DTO"
B2interface Foo { x: number }dropped: true, dropReason: "interface (data model)"
B3type Bar = stringdropped: true, dropReason: "type alias"
B4export { x } from './y'dropped: true, dropReason: "re-export"
B5class with a boundary decorator (@Controller), even without methodsdropped: false

8.3 Node-level (Category C)

IDInputExpected
C1method containing console.log(x)Does not appear in effects/calls
C2method containing only return 1No return appears in rules
C3method containing only return this.foo()No return in rules; this.foo in calls
C4method with return a + brules contain a return (expr: "a + b")
C5method with return { ...x, status: 'ok' }rules contain a return

8.4 Config/Plugin (Category D)

IDInputExpected
D1myLogger.info(x) with config.suppress: ["myLogger"]Excluded from extraction
D2console.log(x) with config.keep: ["console.log"]Remains in calls
D3The same callee in both config.keep and config.suppresskeep wins

8.5 Unit tests for the triviality determination

IDInput expressionTrivial?
T11yes
T2'x'yes
T3xyes
T4this.x.y.zyes
T5!xyes
T6-this.countyes
T7foo()call-only (no return rule, but recorded in calls)
T8this.bar()call-only
T9foo() + 1no
T10x ? a : bno
T11{ ...x }no
T12`hello ${name}`no

9. Configuration examples

Minimal configuration (defaults suffice):

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

Mid-size project with custom drops:

jsonc
{
  "$schema": "https://aburi.dev/schema/aburi.config.v1.json",
  "ignore": ["docs/**", "scripts/legacy/**"],
  "suppress": ["myLogger", "metrics", "datadog"],
  "keep": ["myLogger.audit"]
}

10. Design decisions

10.1 Why retain dropped: true instead of discarding

  • Transparency: why something was dropped can be queried from the IR (aburi explain X)
  • Reproducibility: when comparing a past IR against the current IR, dropped symbols can still be matched up
  • Debugging: when drop rules change, it is easier to predict "this will enter/leave next run"

stats.droppedSymbols exposes the drop count, making false positives (dropping something that should have been kept) easier to discover.

10.2 Why file-level skips (Category A) are not dropped: true

Marking every symbol of a file as dropped: true would bloat the IR (including all of node_modules would mean hundreds of thousands of entries). Excluding them from stats as well makes it clear they are "outside Aburi's concern".

If a per-file drop history ever becomes necessary, a separate mechanism (stats.skippedFiles[] etc.) will be considered.

10.3 Why "pure DTO" is dropped

A DTO is a data shape and carries no business logic. During review, "did the type change?" matters, but that is a separate concern (type-change review), distinct from Aburi's primary use case.

The DTO's existence itself remains as dropped: true, so it does not disappear. Type-change detection will be handled by a separate future feature.

10.4 Why trivial returns are call-only and excluded from rules

If a return rule were emitted for a forward method like return foo(), the output would look redundant (foo already appears in calls). Recording it exactly once as a call keeps rules focused on meaningful branches, throws, and composite returns.

However, compositions such as return foo() + 1 carry a distinct meaning and stay in rules.

10.5 The keep > suppress > plugin > core precedence

User intent always takes precedence over core/plugins. "The core drops loggers, but our in-house auditLog.write must stay as an audit log" is a common requirement, so keep has the highest priority.

Choices like "keep console.log via config.keep" are also technically allowed (e.g. projects that want to keep it for debugging).

10.6 Why plugin drop rules sit at the third level

Plugins hold more detailed knowledge than the core (pino.child(...).info), so they may decide drops ahead of the core. User configuration (config) still takes precedence over them.

Released under the MIT License.