fix(gen): resolver registry describes the template language again

Seven registered {{PLACEHOLDER}}s had zero uses in any .tmpl (checked in both
bare and :arg forms): REDACT_TAXONOMY_TABLE, TEST_COVERAGE_AUDIT_REVIEW,
MODEL_OVERLAY, QUESTION_PREFERENCE_CHECK, QUESTION_LOG, INLINE_TUNE_FEEDBACK,
MAKE_PDF_SETUP. The last two of those families are invoked programmatically by
preamble.ts (functions kept, registry entries dropped); the question-tuning
trio and the review coverage-audit wrapper were documented by their own module
as existing 'for unit testing' that no test performed — deleted, along with
generateRedactTaxonomyTable + its EXAMPLE/TIER_BLURB constants (its '/cso
renders the full table' comment was itself stale) and its test describe.

Also deletes the gated-resolver mechanism (ResolverEntry/appliesTo/
unwrapResolver + test/resolver-entry.test.ts): fully built, fully tested,
used by zero of the 65 registry entries — the generator loop simplifies to a
direct function call. CLAUDE.md's redact-doc line stops advertising the dead
token.

Proof: zero-diff regen (0 SKILL.md changed); gen-skill-docs + skill-validation
737 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 21:12:20 -07:00
co-authored by Claude Fable 5
parent 5c806a4bd7
commit 7933a38aa8
10 changed files with 19 additions and 406 deletions
+2 -13
View File
@@ -3,15 +3,12 @@
* lib/redact-patterns.ts (single source of truth). /spec and /cso both reference
* it by pointer rather than inlining the full catalog (size discipline). This
* test guards that the recognizable HIGH-tier prefixes stay present in /cso's
* archaeology prose and that the resolver-generated table stays derived from the
* lib (no drift between the generator and the pattern source).
* archaeology prose. (A fourth test covered the resolver-generated taxonomy
* table; that generator was deleted as dead code — no template ever used it.)
*/
import { describe, test, expect } from "bun:test";
import * as fs from "fs";
import * as path from "path";
import { generateRedactTaxonomyTable } from "../scripts/resolvers/redact-doc";
import { HOST_PATHS } from "../scripts/resolvers/types";
import { PATTERNS } from "../lib/redact-patterns";
const ROOT = path.resolve(import.meta.dir, "..");
// cso is carved (skeleton + sections/audit-phases.md). The Secrets Archaeology
@@ -28,7 +25,6 @@ function unionSkill(skill: string): string {
return t;
}
const CSO = unionSkill("cso");
const ctx = { skillName: "cso", tmplPath: "", host: "claude" as const, paths: HOST_PATHS["claude"] };
describe("cso/spec taxonomy alignment", () => {
test("cso archaeology names the recognizable HIGH-tier prefixes", () => {
@@ -41,13 +37,6 @@ describe("cso/spec taxonomy alignment", () => {
expect(CSO).toContain("lib/redact-patterns.ts");
});
test("the generated taxonomy table is derived from lib (every pattern id present)", () => {
const table = generateRedactTaxonomyTable(ctx);
for (const p of PATTERNS) {
expect(table).toContain(`\`${p.id}\``);
}
});
test("cso keeps its git-history archaeology (different use case, not replaced)", () => {
expect(CSO).toContain("git log -p --all");
expect(CSO).toContain("Secrets Archaeology");
-27
View File
@@ -7,7 +7,6 @@
*/
import { describe, test, expect } from "bun:test";
import {
generateRedactTaxonomyTable,
generateRedactInvocationBlock,
} from "../scripts/resolvers/redact-doc";
import { HOST_PATHS } from "../scripts/resolvers/types";
@@ -20,32 +19,6 @@ const ctx = {
paths: HOST_PATHS["claude"],
};
describe("REDACT_TAXONOMY_TABLE", () => {
const table = generateRedactTaxonomyTable(ctx);
test("lists every pattern id from the engine (no drift)", () => {
for (const p of PATTERNS) {
expect(table).toContain(`\`${p.id}\``);
}
});
test("contains the recognizable credential prefixes", () => {
for (const s of ["AKIA", "ghp_", "sk-ant-", "sk-", "BEGIN"]) {
expect(table).toContain(s);
}
});
test("has all three tier sections", () => {
expect(table).toContain("HIGH — genuinely-secret");
expect(table).toContain("MEDIUM — PII");
expect(table).toContain("LOW — surfaced");
});
test("documents the calibration rationale (publishable/AIza/JWT are MEDIUM)", () => {
expect(table).toMatch(/cries wolf/);
expect(table).toContain("pk_live_");
});
});
describe("REDACT_INVOCATION_BLOCK", () => {
test("scan-at-sink: temp file → scan that file → exact bytes", () => {
-186
View File
@@ -1,186 +0,0 @@
/**
* Unit tests for the ResolverEntry / unwrapResolver mechanism.
*
* Verifies the conditional-injection plumbing added in T2 (v1.45.0.0).
* Plain functions still work; gated entries skip when appliesTo returns false.
*/
import { describe, test, expect } from 'bun:test';
import { unwrapResolver, type ResolverFn, type ResolverEntry, type TemplateContext } from '../scripts/resolvers/types';
function makeCtx(overrides: Partial<TemplateContext> = {}): TemplateContext {
return {
skillName: 'test-skill',
tmplPath: '/tmp/test/SKILL.md.tmpl',
host: 'claude',
paths: {
skillRoot: '~/.claude/skills/gstack',
localSkillRoot: '.claude/skills',
binDir: '~/.claude/skills/gstack/bin',
browseDir: '~/.claude/skills/gstack/browse/dist',
designDir: '~/.claude/skills/gstack/design/dist',
makePdfDir: '~/.claude/skills/gstack/make-pdf/dist',
},
...overrides,
};
}
describe('unwrapResolver — plain function pass-through', () => {
test('returns the function as-is, no gate', () => {
const fn: ResolverFn = (ctx) => `hello-${ctx.skillName}`;
const { resolve, appliesTo } = unwrapResolver(fn);
expect(resolve(makeCtx())).toBe('hello-test-skill');
expect(appliesTo).toBeUndefined();
});
});
describe('unwrapResolver — gated entry', () => {
test('returns resolve + gate', () => {
const entry: ResolverEntry = {
resolve: (ctx) => `gated-${ctx.skillName}`,
appliesTo: (ctx) => ['ship', 'review'].includes(ctx.skillName),
};
const { resolve, appliesTo } = unwrapResolver(entry);
expect(resolve(makeCtx({ skillName: 'ship' }))).toBe('gated-ship');
expect(appliesTo!(makeCtx({ skillName: 'ship' }))).toBe(true);
expect(appliesTo!(makeCtx({ skillName: 'qa' }))).toBe(false);
});
test('gate returning false should signal skip — gen-skill-docs substitutes empty string', () => {
// This mirrors the gen-skill-docs.ts contract:
// if (appliesTo && !appliesTo(ctx)) return '';
const entry: ResolverEntry = {
resolve: () => 'CONTENT',
appliesTo: () => false,
};
const { resolve, appliesTo } = unwrapResolver(entry);
const result = appliesTo && !appliesTo(makeCtx()) ? '' : resolve(makeCtx());
expect(result).toBe('');
});
test('gate returning true allows resolve to fire', () => {
const entry: ResolverEntry = {
resolve: () => 'CONTENT',
appliesTo: () => true,
};
const { resolve, appliesTo } = unwrapResolver(entry);
const result = appliesTo && !appliesTo(makeCtx()) ? '' : resolve(makeCtx());
expect(result).toBe('CONTENT');
});
test('entry without appliesTo behaves like ungated', () => {
const entry: ResolverEntry = { resolve: () => 'ALWAYS' };
const { resolve, appliesTo } = unwrapResolver(entry);
expect(appliesTo).toBeUndefined();
expect(resolve(makeCtx())).toBe('ALWAYS');
});
});
describe('RESOLVERS registry still loads with mixed shapes', () => {
test('importing the live registry produces a record with expected resolvers', async () => {
const { RESOLVERS } = await import('../scripts/resolvers/index');
// Spot-check that core resolvers are present.
expect(RESOLVERS.PREAMBLE).toBeDefined();
expect(RESOLVERS.REVIEW_DASHBOARD).toBeDefined();
expect(RESOLVERS.SLUG_EVAL).toBeDefined();
// Each entry should unwrap cleanly.
for (const [name, entry] of Object.entries(RESOLVERS)) {
const { resolve } = unwrapResolver(entry);
expect(typeof resolve).toBe('function');
expect(name.length).toBeGreaterThan(0);
}
});
});
/**
* Gap D (v1.46.0.0): live appliesTo gate end-to-end integration.
*
* The ResolverEntry / unwrapResolver machinery has unit coverage above. The
* remaining gap: does the gen-skill-docs.ts:444 substitution loop actually
* USE the gate? A refactor that drops the `if (appliesTo && !appliesTo(ctx))`
* check would silently break every future gated resolver.
*
* This test simulates the exact 4-line shape the live pipeline uses against
* a synthetic registry. If gen-skill-docs.ts is refactored and someone
* forgets to keep the gate check in sync, this assertion fails.
*/
describe('gen-skill-docs substitution loop respects the appliesTo gate', () => {
function simulateGenSubstitution(
template: string,
registry: Record<string, import('../scripts/resolvers/types').ResolverValue>,
ctx: TemplateContext,
): string {
// Mirrors scripts/gen-skill-docs.ts:457-467 (the {{NAME}} substitution
// loop). Keep this in sync with the real loop. Drift here is what the
// test is designed to catch.
return template.replace(/\{\{(\w+(?::[^}]+)?)\}\}/g, (_match, fullKey) => {
const parts = fullKey.split(':');
const resolverName = parts[0];
const args = parts.slice(1);
const entry = registry[resolverName];
if (!entry) throw new Error(`Unknown placeholder {{${resolverName}}}`);
const { resolve, appliesTo } = unwrapResolver(entry);
if (appliesTo && !appliesTo(ctx)) return '';
return args.length > 0 ? resolve(ctx, args) : resolve(ctx);
});
}
test('plain-function resolver fires unconditionally', () => {
const tpl = '{{ALWAYS}}';
const out = simulateGenSubstitution(tpl, {
ALWAYS: () => 'fired',
}, makeCtx({ skillName: 'whatever' }));
expect(out).toBe('fired');
});
test('gated resolver fires only when appliesTo returns true', () => {
const tpl = 'before-{{GATED}}-after';
const out = simulateGenSubstitution(tpl, {
GATED: {
resolve: () => 'CONTENT',
appliesTo: (ctx) => ctx.skillName === 'allowed',
},
}, makeCtx({ skillName: 'allowed' }));
expect(out).toBe('before-CONTENT-after');
});
test('gated resolver is substituted with empty string when appliesTo returns false', () => {
const tpl = 'before-{{GATED}}-after';
const out = simulateGenSubstitution(tpl, {
GATED: {
resolve: () => 'CONTENT',
appliesTo: (ctx) => ctx.skillName === 'allowed',
},
}, makeCtx({ skillName: 'something-else' }));
expect(out).toBe('before--after');
});
test('mixed registry: gated + plain resolvers in the same template', () => {
const tpl = '{{PLAIN}} / {{GATED_ON}} / {{GATED_OFF}}';
const ctx = makeCtx({ skillName: 'ship' });
const out = simulateGenSubstitution(tpl, {
PLAIN: () => 'plain',
GATED_ON: { resolve: () => 'on', appliesTo: () => true },
GATED_OFF: { resolve: () => 'off', appliesTo: () => false },
}, ctx);
expect(out).toBe('plain / on / ');
});
test('parameterized resolver still respects gate', () => {
const tpl = '{{GATED:arg1:arg2}}';
const ctx = makeCtx({ skillName: 'no' });
const out = simulateGenSubstitution(tpl, {
GATED: {
resolve: (_c, args) => `fired-with-${(args ?? []).join('-')}`,
appliesTo: (c) => c.skillName === 'yes',
},
}, ctx);
expect(out).toBe(''); // gated off, args ignored
});
test('unknown resolver throws (matches real gen-skill-docs error contract)', () => {
expect(() =>
simulateGenSubstitution('{{NEVER_DEFINED}}', {}, makeCtx()),
).toThrow(/Unknown placeholder/);
});
});