Skip to content

Commit f15a34a

Browse files
committed
fix(gate): delete unconsumed exports instead of allowlisting them
The allowlist now names only the agent-package contract. Exports with no production importer are removed, and vendor plus path-loaded eval fixtures stay outside the knip scan.
1 parent 8ad80ac commit f15a34a

24 files changed

Lines changed: 53 additions & 221 deletions

File tree

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export const notes: string[] = [];
22

3-
// ts-prune-ignore-next
43
export function addNote(note: string): void {
54
notes.push(note);
65
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
// ts-prune-ignore-next
21
export const data = { seed: 7 };

‎evals/completion/tasks/sum-fix/fixture/sum.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// ts-prune-ignore-next
21
export function total(values: number[]): number {
32
let sum = 0;
43
for (const value of values) {

‎evals/completion/tasks/version-endpoint/fixture/service.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ export interface Reply {
33
body: unknown;
44
}
55

6-
// ts-prune-ignore-next
76
export function handleRequest(method: string, path: string): Reply {
87
if (method === "GET" && path === "/health") {
98
return { status: 200, body: { ok: true } };

‎fixtures/plugins/implement-feature/src/index.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ import { implementFeature } from "./workflows/implement-feature.js";
22
import type { CommandPlugin } from "../../../../src/tui/commands/registry.js";
33
import type { WorkflowPlugin } from "../../../../src/workflows/definition.js";
44

5-
// ts-prune-ignore-next
65
export const workflowPlugin: WorkflowPlugin = {
76
workflows: [implementFeature],
87
};
98

10-
// ts-prune-ignore-next
119
export const commandPlugin: CommandPlugin = {
1210
commands: [
1311
{

‎knip.agents.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
"e2e/**/*.ts",
1515
"evals/**/*.ts",
1616
"scripts/**/*.ts",
17-
"agents/**/*.ts",
18-
"vendor/**/*.ts"
17+
"agents/**/*.ts"
1918
],
19+
"ignore": ["vendor/**", "evals/completion/tasks/**/fixture/**"],
2020
"workspaces": {
2121
"agents/*": {
2222
"entry": ["src/index.ts!"],

‎knip.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
"e2e/**/*.ts",
1515
"evals/**/*.ts",
1616
"scripts/**/*.ts",
17-
"agents/**/*.ts",
18-
"vendor/**/*.ts"
17+
"agents/**/*.ts"
1918
],
19+
"ignore": ["vendor/**", "evals/completion/tasks/**/fixture/**"],
2020
"includeEntryExports": true,
2121
"rules": {
2222
"exports": "error",

‎packages/prompt-variance/src/index.ts‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
export {
22
claudeRow,
3-
defaultRow,
4-
FAMILY_IDS,
5-
FAMILY_ROWS,
63
gptRow,
74
grokRow,
85
grokToolBudgetResidual,
96
museRow,
10-
type PromptVarianceFamily,
11-
type PromptVarianceRow,
127
} from "./rows.js";
138

149
// Astra tool-evasion residual: the gpt-6-astra leaf re-issues the same

‎scripts/check-dead-exports.test.ts‎

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,19 @@ function liveScannedFileCount(): number {
4747

4848
// Scoped exemptions cover only the named flags; a sibling probe still fails.
4949
describe("scoped exemptions", () => {
50-
test("the real allowlist covers the named flags but not a sibling probe", () => {
50+
test("the real allowlist covers the named public exports but not a sibling", () => {
5151
const rules = loadAllowlist();
52+
expect(isAllowlisted(rules, "agents/coder/src/index.ts", "tools")).toBe(
53+
true,
54+
);
55+
expect(isAllowlisted(rules, "agents/coder/src/index.ts", "config")).toBe(
56+
true,
57+
);
58+
expect(isAllowlisted(rules, "agents/coder/src/index.ts", "director")).toBe(
59+
false,
60+
);
5261
expect(
53-
isAllowlisted(
54-
rules,
55-
"src/auth/codex/constants.ts",
56-
"CODEX_REFRESH_SKEW_MS",
57-
),
58-
).toBe(true);
59-
expect(
60-
isAllowlisted(
61-
rules,
62-
"src/auth/codex/constants.ts",
63-
"CODEX_HEADLESS_REFRESH_INTERVAL_MS",
64-
),
65-
).toBe(true);
66-
expect(
67-
isAllowlisted(rules, "src/auth/codex/usage.ts", "someNewDeadExport"),
62+
isAllowlisted(rules, "agents/coder/src/index.ts", "someNewDeadExport"),
6863
).toBe(false);
6964
});
7065

@@ -237,7 +232,7 @@ describe("live knip scan semantics", () => {
237232
expect(reported("agents/coder/src/index.ts", "director")).toBe(false);
238233
// knip counts the package contract test's import as consumption.
239234
expect(reported("agents/coder/src/index.ts", "tools")).toBe(false);
240-
// The ts-prune parser false positives are gone.
235+
// A `satisfies` token inside a live export is not itself an export.
241236
expect(reported("src/agent/directors/identity.ts", "satisfies")).toBe(
242237
false,
243238
);
@@ -459,7 +454,7 @@ describe("pinned scan invocation", () => {
459454
"agents/coder/src/index.ts: agent\n" +
460455
"agents/coder/src/index.ts: agent\n" +
461456
"agents/dispatch/src/index.ts: agent\n" +
462-
"src/auth/codex/constants.ts: CODEX_REFRESH_SKEW_MS\n",
457+
"src/auth/codex/constants.ts: CODEX_BASE_URL\n",
463458
);
464459
// Parse dedupes nothing at rule level; the derivation dedupes ids.
465460
expect(agentWorkspaceIds(rules)).toEqual([
@@ -475,7 +470,7 @@ describe("pinned scan invocation", () => {
475470
expect(() => parseGuardConfig({ ...valid, minScannedFiles: 0 })).toThrow();
476471
expect(() => parseGuardConfig({ ...valid, tsconfig: "" })).toThrow();
477472
expect(() =>
478-
parseGuardConfig({ ...valid, tsPruneArgs: ["-p", "x"] }),
473+
parseGuardConfig({ ...valid, scanArgs: ["-p", "x"] }),
479474
).toThrow();
480475
expect(() => parseGuardConfig(null)).toThrow();
481476
expect(() => parseGuardConfig([])).toThrow();

‎scripts/check-dead-exports.ts‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import { dirname, join, sep } from "node:path";
44
import { fileURLToPath } from "node:url";
55

66
// Dead-export guard: runs knip and fails when a consumer-less export or an
7-
// unused file falls outside scripts/dead-export-allowlist.txt. knip replaces
8-
// the retired ts-prune scanner (CL-10655); it resolves the module graph, so an
9-
// export consumed through a re-export barrel or the closed director fan-in is
10-
// not reported, and exports used only inside their own module are skipped
11-
// (ignoreExportsUsedInFile in knip.json knits both semantics). knip.json pins
12-
// the scan (entry points per tree plus the workspace packages); this guard
13-
// owns only the allowlist and the fail-closed floor. New dead exports must be
14-
// deleted, not allowlisted — the allowlist covers entry-point API surfaces and
15-
// deferred-deletion lanes only.
7+
// unused file falls outside scripts/dead-export-allowlist.txt. knip resolves
8+
// the module graph, so an export consumed through a re-export barrel or the
9+
// closed director fan-in is not reported, and exports used only inside their
10+
// own module are skipped (ignoreExportsUsedInFile in knip.json). knip.json
11+
// pins the scan; this guard owns only the allowlist and the fail-closed floor.
12+
// New dead exports must be deleted, not allowlisted — the allowlist names
13+
// entry-point API surfaces only. vendor/ and path-loaded eval fixtures are
14+
// knip ignore entries, not allowlist blankets.
1615
//
1716
// Hardening: stale entries fail instead of warning, every entry must pass
1817
// shape validation and sit under a reason comment (the gate enforces the
@@ -33,10 +32,10 @@ const guardConfigPath = join(here, "dead-export-guard.json");
3332
const knipBin = join(repoRoot, "node_modules", ".bin", "knip");
3433
const tscBin = join(repoRoot, "node_modules", "typescript", "bin", "tsc");
3534

36-
// The integrated-monorepo knip config that drives the production agent scan;
37-
// a separate file so the default scan keeps knip.json's ts-prune-parity
38-
// project scope (the `workspaces` key switches knip into integrated mode,
39-
// which also widens its default file detection beyond that scope).
35+
// The integrated-monorepo knip config that drives the production agent scan.
36+
// A separate file so the default scan keeps knip.json's project scope (the
37+
// `workspaces` key switches knip into integrated mode, which also widens its
38+
// default file detection beyond that scope).
4039
export function agentsConfigPath(): string {
4140
return join(repoRoot, "knip.agents.json");
4241
}
@@ -366,8 +365,7 @@ export function knipDefaultSpawn(knipBinPath: string): {
366365
// `workspaces` key makes cross-workspace consumers visible in production
367366
// mode), so the registry fan-in still clears `director` while the laundered
368367
// worker names surface. Outside that surface the default scan owns
369-
// enforcement at ts-prune parity; the union of both scans is the guard's
370-
// finding set.
368+
// enforcement; the union of both scans is the guard's finding set.
371369
export function knipAgentProductionSpawn(
372370
knipBinPath: string,
373371
workspaces: readonly string[],
@@ -490,11 +488,11 @@ function fail(message: string): never {
490488

491489
// The guard's finding set is the union of two knip reports:
492490
//
493-
// 1. the default scan over knip.json's project scope (ts-prune parity), and
491+
// 1. the default scan over knip.json's project scope, and
494492
// 2. the `--production` scan filtered to the agent workspaces, where knip
495493
// does not count test-file consumers.
496494
//
497-
// Both run under the pinned pinned spawn contract; a failing or malformed
495+
// Both run under the pinned spawn contract; a failing or malformed
498496
// report fails the gate rather than narrowing it silently.
499497
export function runKnipScans(rules: readonly AllowRule[]): DeadExport[] {
500498
requireKnipConfigs();

0 commit comments

Comments
 (0)