C8: a render given disabled skills drops them from the router (#3018)

The root router named every skill and ends 'When in doubt, invoke the skill',
so it routed to skills the user disabled. gen-skill-docs takes
--disabled-skills (runGeneration disabledSkills): the router drops their
routing rules, keeps the other side of an 'A or B' rule, and names them as
disabled. The router and gstack-upgrade cannot be disabled. With no disabled
skills the render is byte-identical.
This commit is contained in:
garrytan committed 2026-10-03 21:42:40 +00:00
1 parent 3f3655b925
commit dc7a95a4b4
2 files changed
+79

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
/**
* C8 (#3018): the root router listed every skill and ends "When in doubt,
* invoke the skill", so it routed to skills the user disabled. A render given
* the install's disabled skills drops their routing rules and names them as
* off; the canonical (committed) render is unchanged.
*/
import { afterAll, describe, expect, test } from 'bun:test';
import { spawnSync } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { runGeneration } from '../scripts/gen-skill-docs';
const ROOT = path.resolve(import.meta.dir, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-router-disabled-'));
afterAll(() => fs.rmSync(tmp, { recursive: true, force: true }));
async function router(host: 'claude' | 'codex', disabledSkills?: string[]) {
const out = fs.mkdtempSync(path.join(tmp, `${host}-`));
const r = await runGeneration({ host, outputRoot: out, disabledSkills });
expect(r.exitCode).toBe(0);
return fs.readFileSync(host === 'claude' ? path.join(out, 'SKILL.md') : path.join(out, '.agents/skills/gstack/SKILL.md'), 'utf8');
}
describe('C8: disabled skills leave the rendered router', () => {
for (const host of ['claude', 'codex'] as const) {
test(`${host}: routing rules drop disabled skills and keep the other side of an "A or B" rule`, async () => {
const text = await router(host, ['retro', 'gstack-cso', '/careful', 'gstack-upgrade']);
expect(text).toContain('Disabled on this install (never invoke or suggest them): `/retro`, `/cso`, `/careful`.');
expect(text).not.toMatch(/→ invoke `\/retro`/);
expect(text).not.toMatch(/→ invoke `\/cso`/);
expect(text).toContain('careful mode → invoke `/guard`');
expect(text).toContain('→ invoke `/gstack-upgrade`');
expect(text).toContain('→ invoke `/ship`');
});
test(`${host}: no disabled skills renders the canonical router byte for byte`, async () => {
expect(await router(host, [])).toBe(await router(host));
});
}
test('the CLI flag rejects anything but skill names', () => {
const r = spawnSync(process.execPath, ['scripts/gen-skill-docs.ts', '--host', 'claude', '--dry-run', '--disabled-skills', 'retro,$(touch x)'],
{ cwd: ROOT, encoding: 'utf8', timeout: 60_000 });
expect(r.status).toBe(1);
expect(r.stderr).toContain('--disabled-skills takes skill names');
});
});