You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This implements cairn Minimum Viable Gate Set gate #6 (Dead code detection — catches AI002, AI003), from https://github.com/krokoko/cairn (plugins/software-verification/.../detection-patterns-gates.md, "Minimum Viable Gate Set"). Mapping the gate set to the open CA issues, gate #6 is the one with no corresponding issue:
Add a dead-code detection gate covering two surfaces the existing gates miss:
Unused imports / locals / params — lint-level, per file.
Unreferenced exports / files / dead barrels — project-graph level (an exported symbol no module imports; a barrel index.ts with zero importers).
across the TypeScript packages (cdk, cli) and the Python package (agent).
Use case — the detection asymmetry this closes
The repo enforces unused-symbol detection on one language and not the other, so dead code accumulates only on the TypeScript side:
Python (agent/) — already enforced.ruffselect includes "F" (agent/pyproject.toml), so pyflakes F401 fails the build on an unused import. A dead import in agent/src cannot reach main.
TypeScript is split.cli/tsconfig.json:21-22 sets "noUnusedLocals": true / "noUnusedParameters": true, so the CLI already blocks unused locals and imports. But cdk/tsconfig.json:21-22 sets both to false, and neither flat-config eslint (cdk/eslint.config.mjs, cli/eslint.config.mjs) carries a no-unused-vars error rule. So unused TS symbols pass silently in cdk/ only — which is exactly where every known instance lives. Note: this repo does NOT use projen; the eslint configs are hand-maintained flat-config files.
This is not hypothetical — three live instances exist at HEAD bb7876a (all in cdk/):
Orphaned export:LINEAR_SECRET_PREFIX in cdk/src/handlers/shared/linear-verify.ts is exported but has no importer anywhere in cdk/cli/agent.
cairn's CI-integration model lists "dead code" as a fast-CI gate and tracks the metric "dead code count (should not increase)" — i.e. a ratchet, not a one-time cleanup.
Proposed solution (grounded against current tooling, mid-2026)
1. TS lint-level — @typescript-eslint/no-unused-vars (typescript-eslint v8; base no-unused-vars must be off). This is the direct parity match to the Python F401 already enforced — the same pattern CA-10 (#258) used for magic values (no-magic-numbers ↔ PLR2004).
Apply by editing the hand-maintained flat-config files directly: cdk/eslint.config.mjs and cli/eslint.config.mjs (this repo has no projen, so there is no generated config to regenerate).
2. TS project-graph level — knip (v6.x).ts-prune is dead (archived read-only 2025-09-19; its README points new projects to knip). knip natively understands Yarn workspaces and per-workspace entry points. Minimal knip.json (the ! suffix marks production entry so test/config files aren't counted as dead):
The explicit cli entry is what stops cli/src/index.ts (the declared package main/types source) from false-positiving as an unused export.
3. Python module-level — vulture (v2.16). ruff F401 covers unused imports but has no rule for unused module-level functions/classes. vulture fills that gap:
4. Rollout — advisory first, then ratchet. Land all three non-blocking; fix or baseline the existing instances (the three above plus whatever knip/vulture surface); then make blocking in the required build check. For knip, gate on the JSON issue count so the build fails only on increases (the cairn "should not increase" metric); knip has no native baseline file, so snapshot the count in CI. For vulture, ship the reviewed allowlist so the current tree passes, then flip --min-confidence 80 to a blocking exit.
Acceptance criteria
@typescript-eslint/no-unused-vars enabled as an error in the cdk and cli flat-config eslint files (cdk/eslint.config.mjs, cli/eslint.config.mjs), with ^_ ignore patterns. (cdk also flips noUnusedLocals/noUnusedParameters to true to match cli, closing the tsconfig half of the gap.)
knip added with a per-workspace knip.json covering cdk/cli/docs; cli/src/index.ts is not flagged (entry-point caveat handled).
vulture added for agent/ with a reviewed --make-whitelist allowlist and --min-confidence 80.
The three known instances are resolved: dead DEFAULT_MAX_TURNS import dropped, cdk/src/bootstrap/index.ts barrel removed (or given a real importer), LINEAR_SECRET_PREFIX removed or consumed.
Gate runs in fast CI (static, seconds; no build needed), advisory first.
Gate becomes blocking in the required build check once the baseline is clean, on a "dead-code count must not increase" basis.
Per ADR-003 this issue needs the approved label before work begins.
(Drafted by Bonk during the ABCA review; tool currency — knip 6.x, ts-prune archived, vulture 2.16, typescript-eslint v8 — verified against current docs. Filed via Laith's account.)
Component
Tooling / CI
Describe the feature
Add a dead-code detection gate covering two surfaces the existing gates miss:
index.tswith zero importers).across the TypeScript packages (
cdk,cli) and the Python package (agent).Use case — the detection asymmetry this closes
The repo enforces unused-symbol detection on one language and not the other, so dead code accumulates only on the TypeScript side:
agent/) — already enforced.ruffselectincludes"F"(agent/pyproject.toml), so pyflakes F401 fails the build on an unused import. A dead import inagent/srccannot reachmain.cli/tsconfig.json:21-22sets"noUnusedLocals": true/"noUnusedParameters": true, so the CLI already blocks unused locals and imports. Butcdk/tsconfig.json:21-22sets both tofalse, and neither flat-config eslint (cdk/eslint.config.mjs,cli/eslint.config.mjs) carries ano-unused-varserror rule. So unused TS symbols pass silently incdk/only — which is exactly where every known instance lives. Note: this repo does NOT use projen; the eslint configs are hand-maintained flat-config files.This is not hypothetical — three live instances exist at HEAD
bb7876a(all incdk/):cdk/src/handlers/shared/create-task-core.tsimportsDEFAULT_MAX_TURNSfrom./validationand never uses it. (This file is also the subject of refactor(cdk): trim createTaskCore import graph so webhook processors don't load attachment/Cedar deps #232; an unused-import gate would have surfaced it.)cdk/src/bootstrap/index.ts(added in feat(bootstrap): policies as typed TypeScript with version and hash #158) re-exports the bootstrap policy/version symbols, but nothing imports the barrel — every consumer imports the submodules (./policies,./version) directly.LINEAR_SECRET_PREFIXincdk/src/handlers/shared/linear-verify.tsis exported but has no importer anywhere incdk/cli/agent.cairn's CI-integration model lists "dead code" as a fast-CI gate and tracks the metric "dead code count (should not increase)" — i.e. a ratchet, not a one-time cleanup.
Proposed solution (grounded against current tooling, mid-2026)
1. TS lint-level —
@typescript-eslint/no-unused-vars(typescript-eslint v8; baseno-unused-varsmust beoff). This is the direct parity match to the PythonF401already enforced — the same pattern CA-10 (#258) used for magic values (no-magic-numbers↔PLR2004).Apply by editing the hand-maintained flat-config files directly:
cdk/eslint.config.mjsandcli/eslint.config.mjs(this repo has no projen, so there is no generated config to regenerate).2. TS project-graph level —
knip(v6.x).ts-pruneis dead (archived read-only 2025-09-19; its README points new projects to knip). knip natively understands Yarn workspaces and per-workspace entry points. Minimalknip.json(the!suffix marks production entry so test/config files aren't counted as dead):{ "$schema": "https://unpkg.com/knip@6/schema.json", "workspaces": { "cdk": { "entry": ["src/main.ts!"], "project": ["src/**/*.ts!"] }, "cli": { "entry": ["src/index.ts!"], "project": ["src/**/*.ts!"] }, "docs": { "entry": ["astro.config.{js,mjs,ts}", "src/pages/**/*.{astro,ts}"], "project": ["src/**/*.{astro,ts,tsx}"] } } }3. Python module-level —
vulture(v2.16). ruffF401covers unused imports but has no rule for unused module-level functions/classes. vulture fills that gap:4. Rollout — advisory first, then ratchet. Land all three non-blocking; fix or baseline the existing instances (the three above plus whatever knip/vulture surface); then make blocking in the required
buildcheck. For knip, gate on the JSON issue count so the build fails only on increases (the cairn "should not increase" metric); knip has no native baseline file, so snapshot the count in CI. For vulture, ship the reviewed allowlist so the current tree passes, then flip--min-confidence 80to a blocking exit.Acceptance criteria
@typescript-eslint/no-unused-varsenabled as an error in thecdkandcliflat-config eslint files (cdk/eslint.config.mjs,cli/eslint.config.mjs), with^_ignore patterns. (cdk also flipsnoUnusedLocals/noUnusedParametersto true to match cli, closing the tsconfig half of the gap.)knipadded with a per-workspaceknip.jsoncoveringcdk/cli/docs;cli/src/index.tsis not flagged (entry-point caveat handled).vultureadded foragent/with a reviewed--make-whitelistallowlist and--min-confidence 80.DEFAULT_MAX_TURNSimport dropped,cdk/src/bootstrap/index.tsbarrel removed (or given a real importer),LINEAR_SECRET_PREFIXremoved or consumed.buildcheck once the baseline is clean, on a "dead-code count must not increase" basis.Other information
krokoko/cairn). Effort: S–M.create-task-core.ts, where the deadDEFAULT_MAX_TURNSimport lives.dependency-cruiser/madge) — boundaries constrain which modules may import which; this finds symbols/files imported by nobody.approvedlabel before work begins.(Drafted by Bonk during the ABCA review; tool currency — knip 6.x, ts-prune archived, vulture 2.16, typescript-eslint v8 — verified against current docs. Filed via Laith's account.)