mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 15:57:33 +01:00
* perf(gen-skill-docs): stop emitting the "When to invoke" body section
The Claude catalog trim moved each skill's routing prose ("Use when...",
"Proactively invoke...", voice triggers) out of the frontmatter description
into a "## When to invoke this skill" section at the top of the body. Claude
Code reads only name + description to choose a skill and loads the body after
the skill is invoked, so that text cannot route anything and costs tokens on
every invocation. Keep the one-line trimmed description; drop the section.
The routing prose stays in each template's description, which
--catalog-mode=full and the other hosts still render. Tests that pinned the
section now assert its absence, or check the template instead.
(cherry picked from commit 707ccd6f38)
* feat(gen-skill-docs): keep voice triggers in the root routing block
The Claude catalog trim drops each skill's "Voice triggers (speech-to-text
aliases)" line from its description, and the body section that used to hold
it is gone. {{VOICE_TRIGGER_ROUTING}} renders them in the root router's
routing block instead: one "Voice trigger ... → invoke /<skill>" line per
skill, read from the templates' voice-triggers lists, so disabled-skill
filtering applies to them like any other routing line. Other hosts keep the
line in each description and render nothing here.
extractVoiceTriggers moves to the new resolver so the generator and the
resolver share one parser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 645e75b1e8)
* chore: regenerate SKILL.md files without the "When to invoke" section
Output of `bun run gen:skill-docs --host all` after the previous commits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ff4c813337)
* test: refresh context-budget ceilings for the smaller skill bodies
`bun test/helpers/capture-context-budget.ts`. Against a recapture of the base
commit, 55 skills that lost the section get a lower ceiling and the root
gstack router rises by 532 tokens for the voice-trigger routing lines (5,369
tokens lower in total). devex-review and eval-plan differ from the committed
fixture only because it already lagged main. alwaysOnTotal is unchanged: the
catalog descriptions did not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d886fcecc1)
* test: refresh the claude ship golden without the "When to invoke" section
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f1789dca5c)
* test: lower the claude design-review size pin
The size guard (#3199) asks for it: claude/design-review shrank by 437 chars
without its "When to invoke" section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3a446fcc62)
* test: skip alias symlinks in the voice-routing check, keep unrelated budget ceilings, add #3129 CHANGELOG entry
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: rebuild the historical cookie approval with the dropped section, record the skill-fixture change as non-behavioral
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.157.0 perf(gen-skill-docs): drop the "When to invoke" body section from Claude SKILL.md files (port of #3129)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: thanhnguyentung95 <17350411+thanhnguyentung95@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
108 lines
6.5 KiB
TypeScript
108 lines
6.5 KiB
TypeScript
/** Security-critical CSO routing/proof policy must remain in the always-loaded controller. */
|
|
import { describe, test, expect } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { expectMentions, expectTokens } from './helpers/prompt-structure';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const controller = () => fs.readFileSync(path.join(ROOT, 'cso/SKILL.md'), 'utf8');
|
|
const template = () => fs.readFileSync(path.join(ROOT, 'cso/SKILL.md.tmpl'), 'utf8');
|
|
const domain = () => fs.readFileSync(path.join(ROOT, 'cso/sections/audit-phases.md'), 'utf8');
|
|
|
|
describe('CSO v3 always-loaded policy', () => {
|
|
test('dispatch and execution boundaries are available before a host section read', () => {
|
|
const text = controller();
|
|
const stop = text.indexOf('> **STOP.**');
|
|
for (const directive of ['## Arguments', '## Mode Resolution', '**Private startup.**', 'untrusted evidence']) {
|
|
expect(text.indexOf(directive)).toBeGreaterThan(-1);
|
|
if (stop >= 0) expect(text.indexOf(directive)).toBeLessThan(stop);
|
|
}
|
|
for (const phase of [0, 1, 12, 13, 14]) expect(text).toContain(`### Phase ${phase}:`);
|
|
});
|
|
|
|
test('every scope and lifecycle entrypoint survives host generation', () => {
|
|
const text = controller();
|
|
for (const flag of ['--infra', '--code', '--skills', '--supply-chain', '--owasp', '--scope', '--diff', '--base', '--budget', '--offline', '--comprehensive', '--doctor', '--resume', '--replay', '--recheck']) {
|
|
expect(text).toContain(flag);
|
|
}
|
|
expect(text).toContain('mutually exclusive');
|
|
for (const command of ['start', 'doctor', 'resume', 'replay', 'recheck', 'inspect', 'read', 'history', 'scan', 'import-sarif', 'submit', 'verify', 'finish', 'import-v2', 'inspect-v2', 'schema']) {
|
|
expect(text).toContain(`gstack-cso ${command}`);
|
|
}
|
|
});
|
|
|
|
test('CSO cannot acquire shared preamble execution or content-export hooks', () => {
|
|
for (const forbidden of ['{{PREAMBLE}}', '{{GBRAIN_CONTEXT_LOAD}}', '{{GBRAIN_SAVE_RESULTS}}', '{{LEARNINGS_SEARCH}}', '{{LEARNINGS_LOG}}', '{{CONFIDENCE_CALIBRATION}}']) {
|
|
expect(template()).not.toContain(forbidden);
|
|
}
|
|
const text = controller();
|
|
expect(text).not.toContain('gstack-skill-start');
|
|
expect(text).not.toContain('gstack-telemetry-log');
|
|
expect(text).not.toContain('gstack-review-log');
|
|
expectMentions(text, [['do not', 'harnesses', 'findings']], 'text');
|
|
expect(text).toContain('trusted installed gstack distribution');
|
|
});
|
|
|
|
test('static review cannot acquire tested or current-source closure labels', () => {
|
|
const text = controller();
|
|
for (const proof of ['CSO evidence rubric', 'identical security assertion', 'legitimate control passes', 'pristine second copy', 'before/after configuration and dependency closures', 'boundary-replacing mocks', 'legacy review evidence', 'new evidence covering the same root cause']) {
|
|
expect(text).toContain(proof);
|
|
}
|
|
expectMentions(text, [['cannot', 'application', 'establish']], 'text');
|
|
});
|
|
|
|
test('runtime and scanner execution claims require qualified catalog profiles', () => {
|
|
const text = controller();
|
|
expectMentions(text, [['without', 'assessment', 'available']], 'text');
|
|
expect(text).toContain('matching qualified runtime catalog profile');
|
|
expect(text).toContain('Project-test completion remains `self_reported`');
|
|
expectTokens(text, ['`tested`'], 'text');
|
|
expectMentions(text, [['until', 'target-independent', 'completion']], 'text');
|
|
expect(domain()).toContain('matching qualified scanner catalog profile');
|
|
});
|
|
|
|
test('sensitive helper control files stay out of the audited working tree', () => {
|
|
const text = controller();
|
|
for (const proof of ['Private control files', 'umask 077', 'mode-`0700`', 'mode-`0600`', 'outside the audited repository', 'remove each control file immediately']) expect(text).toContain(proof);
|
|
for (const proof of ['Audited-source access invariant', "only with that run's `inspect`, `read`, and `history`", 'exact `path` from `inspect`', '`displayPath` is only a redacted label', 'Never use host `Read`/`Glob`/`Grep`', 'direct reads bypass']) expect(text).toContain(proof);
|
|
for (const proof of ["copy every record's `domain` and `scope` exactly", 'a new scope leaves the planned scope unassessed', 'Only helper commands may update helper-owned records']) expect(text).toContain(proof);
|
|
});
|
|
|
|
test('supported findings are persisted and surfaced before the final report', () => {
|
|
const text = controller();
|
|
expect(text).toContain('Invoke `start` exactly once');
|
|
expect(text).toContain('never call `start` again');
|
|
expect(text).toContain('same ID');
|
|
expect(text).toContain('submit it to the helper **and surface it to the user immediately**');
|
|
expectMentions(text, [['do not', 'report', 'final']], 'text');
|
|
expect(text).toContain('if the run is interrupted');
|
|
});
|
|
|
|
test('empty, partial, redaction failure, persistence failure and expiry stay truthful', () => {
|
|
const text = controller();
|
|
for (const contract of ['**complete**, **partial**, or **not assessed**', 'No supported findings in the assessed scope.', 'PERSISTENCE_FAILED', 'MISSING_INPUT', 'withhold the payload entirely', 'one bounded correction attempt', 'seven days', 'thirty days']) {
|
|
expect(text).toContain(contract);
|
|
}
|
|
expect(text).toContain('outside synchronization allowlists');
|
|
});
|
|
|
|
test('obsolete blanket exclusions and certainty scores are absent', () => {
|
|
const text = controller() + domain();
|
|
for (const obsolete of ['8/10 confidence gate', '2/10 confidence gate', 'devDependency CVEs are MEDIUM max', "gstack's own skills are trusted", 'User content in the user-message position of an AI conversation is NOT prompt injection', 'pull_request_target` without PR ref checkout is safe', 'Hard exclusions — automatically discard']) {
|
|
expect(text).not.toContain(obsolete);
|
|
}
|
|
expect(text).toContain('Unknown reachability remains');
|
|
expect(text).toContain('sequential challenge; independent agent unavailable');
|
|
});
|
|
|
|
test('controller stays compact without moving proof policy behind a section read', () => {
|
|
expect(Buffer.byteLength(template())).toBeLessThan(18_000);
|
|
expect(Buffer.byteLength(controller())).toBeLessThan(20_000);
|
|
expect(Buffer.byteLength(controller() + domain())).toBeLessThan(36_000);
|
|
const frontmatter = controller().match(/^---\n([\s\S]*?)\n---/)![1];
|
|
const description = frontmatter.match(/^description:\s+(.+)$/m)![1].trim();
|
|
expect(description.length).toBeLessThanOrEqual(200);
|
|
expect(description).toContain('(gstack)');
|
|
});
|
|
});
|