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
| Tier | Granularity | Treatment in the IR |
|---|---|---|
| A. File-level skip | Entire file | Never appears in symbols/dependencies (also excluded from stats.totalFiles) |
| B. Symbol-level drop | Single Symbol | Retained with dropped: true + dropReason; fingerprint is all zeros |
| C. Node-level filter | call / return / decorator inside a Symbol | Individually excluded from call/return/effect/rule; the Symbol itself remains |
| D. Config/Plugin extension | Layered on top of all of A/B/C | Follows 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
| Pattern | Reason |
|---|---|
**/node_modules/** | Dependency libraries |
**/dist/** **/build/** **/out/** **/target/** | Build artifacts |
**/.next/** **/.nuxt/** **/.svelte-kit/** **/.output/** | Framework build caches |
**/coverage/** | Coverage reports |
**/__snapshots__/** **/*.snap | Test snapshots |
**/*.d.ts **/*.d.mts **/*.d.cts | TS type declaration files (no implementation) |
**/*.generated.* **/*.gen.* **/*.g.ts | Auto-generated |
**/*.min.js **/*.bundle.js | minified/bundled |
**/__pycache__/** **/*.pyc | Python caches |
**/.venv/** **/venv/** **/site-packages/** | Python virtual environments |
**/target/** **/Cargo.lock | Rust |
**/vendor/** **/go.sum | Go |
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__/**,**/*.pyilang-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
| Pattern | dropReason |
|---|---|
| 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 configurableframework-nestjs: a class with only@Moduleand 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 pattern | Reason |
|---|---|
console.{log,info,warn,error,debug,trace,table,dir,group,groupEnd} | Logging |
process.stdout.write / process.stderr.write | Standard 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 shape | Example |
|---|---|
| string/number/boolean/null/undefined literal | return 1 / return 'x' / return true |
| identifier | return x |
this or a member chain (arbitrary depth) | return this.value / return obj.a.b.c |
| unary operator + trivial expression | return !x / return -count |
void expression | return 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 ineffects[])
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 RuleWhether 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):
{
"suppress": ["myLogger", "metrics", "telemetry"]
}This excludes myLogger.* / metrics.* / telemetry.* calls from effects/calls.
6.2 config.keep[]
Patterns exceptionally retained from Category C drops:
{
"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.auditis 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:
{
"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.logis a core drop target (level 4) andconfig.keep: ["console.log"]is present → keep wins; it is retainedmyLogger.infois a drop candidate viaconfig.suppress: ["myLogger"](level 2) andconfig.keep: ["myLogger.audit"]is present →myLogger.infois dropped,myLogger.auditis retained- Some plugin adds
panic!as a drop target (level 3) → can be exempted viaconfig.keep; the core does not touch it
8. Verifiable properties (test criteria)
Properties the extraction pipeline must satisfy.
8.1 File-level (Category A)
| ID | Input | Expected |
|---|---|---|
| A1 | scan including node_modules/foo/bar.ts | Does not appear in symbols; excluded from stats.totalFiles |
| A2 | scan including *.d.ts | Same as above |
| A3 | Files listed in .gitignore | Same as above (unless --no-respect-gitignore) |
| A4 | Adding config.ignore: ["docs/**"] | Everything under docs/ is skipped |
8.2 Symbol-level (Category B)
| ID | Input | Expected |
|---|---|---|
| B1 | pure DTO (class Foo { x: number; y: string }) | dropped: true, dropReason: "pure DTO" |
| B2 | interface Foo { x: number } | dropped: true, dropReason: "interface (data model)" |
| B3 | type Bar = string | dropped: true, dropReason: "type alias" |
| B4 | export { x } from './y' | dropped: true, dropReason: "re-export" |
| B5 | class with a boundary decorator (@Controller), even without methods | dropped: false |
8.3 Node-level (Category C)
| ID | Input | Expected |
|---|---|---|
| C1 | method containing console.log(x) | Does not appear in effects/calls |
| C2 | method containing only return 1 | No return appears in rules |
| C3 | method containing only return this.foo() | No return in rules; this.foo in calls |
| C4 | method with return a + b | rules contain a return (expr: "a + b") |
| C5 | method with return { ...x, status: 'ok' } | rules contain a return |
8.4 Config/Plugin (Category D)
| ID | Input | Expected |
|---|---|---|
| D1 | myLogger.info(x) with config.suppress: ["myLogger"] | Excluded from extraction |
| D2 | console.log(x) with config.keep: ["console.log"] | Remains in calls |
| D3 | The same callee in both config.keep and config.suppress | keep wins |
8.5 Unit tests for the triviality determination
| ID | Input expression | Trivial? |
|---|---|---|
| T1 | 1 | yes |
| T2 | 'x' | yes |
| T3 | x | yes |
| T4 | this.x.y.z | yes |
| T5 | !x | yes |
| T6 | -this.count | yes |
| T7 | foo() | call-only (no return rule, but recorded in calls) |
| T8 | this.bar() | call-only |
| T9 | foo() + 1 | no |
| T10 | x ? a : b | no |
| T11 | { ...x } | no |
| T12 | `hello ${name}` | no |
9. Configuration examples
Minimal configuration (defaults suffice):
{ "$schema": "https://aburi.dev/schema/aburi.config.v1.json" }Mid-size project with custom drops:
{
"$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.