feat(skills): when-to-use descriptions, wider triggers and contract blocks for /investigate, /review, /qa, /verify (GST-1088 lane B)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-11-03 02:14:38 +00:00
1 parent 54606c525c
commit b772dc6208
16 files changed
+487 -36

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
**/investigate, /review, /qa and /verify now say when to use them, and /investigate and /review declare what "done" means (GST-1088 lane B).** Untold, Opus 5.5 never opened /investigate in 252 trials (Run D, gstack-evals#140). A when-to-use description raised GPT-6.1 Sol's open rate from 9.5% to 27.0% in the same run. Claude Code's catalog shows only a description's first sentence, so each of the four now opens with a "Use when ..." sentence. The routing hook and the contract check that read the new frontmatter ship separately (lane A), both off by default.
### Changed
- **Descriptions**: the first sentence of each is now a when-clause, for example /investigate's "Use when a test fails, code errors or output is wrong: find the root cause, then fix it." Hosts that show the full description also get the longer list of situations. The catalog stays within its existing budget.
- **Triggers**: /investigate, /review and /verify list the prompts people actually type, such as "test fails", "look over my diff" and "make it green". /qa keeps its three triggers because its size cap had 9 bytes of room.
### Added
- **`contract:` frontmatter** (frozen v1 schema) on /investigate (the DEBUG REPORT labels, and a test or repro run after the last edit) and /review (one Agent dispatch per selected specialist, the adversarial pass, the Pre-Landing Review report). `{{CONTRACT_DONE_MEANS}}` (`scripts/resolvers/contract-done-means.ts`) renders each block as a short "Done means" list at the top of the skill, so the list and the block can't drift.
### Tests
- `test/skill-descriptions.test.ts`: each of the four opens with a when-clause within the catalog, lead and host caps; triggers are short and don't overlap; ten sample prompts match exactly the intended skill; every template's contract block passes the v1 schema and names markers the skill text actually prints; the renderer covers each item kind; and malformed blocks fail with the file and the fix.
+4 -4
View File
@@ -37,7 +37,7 @@ Conventions:
- [/gstack-upgrade](gstack-upgrade/SKILL.md): Upgrade gstack to the latest version.
- [/guard](guard/SKILL.md): Full safety mode: destructive command warnings + directory-scoped edits.
- [/health](health/SKILL.md): Code quality dashboard.
- [/investigate](investigate/SKILL.md): Systematic debugging with root cause investigation.
- [/investigate](investigate/SKILL.md): Use when a test fails, code errors or output is wrong: find the root cause, then fix it.
- [/ios-clean](ios-clean/SKILL.md): Remove the DebugBridge SPM package and all #if DEBUG wiring from an iOS app.
- [/ios-design-review](ios-design-review/SKILL.md): Visual design audit for iOS apps on real hardware.
- [/ios-fix](ios-fix/SKILL.md): Autonomous iOS bug fixer.
@@ -56,10 +56,10 @@ Conventions:
- [/plan-devex-review](plan-devex-review/SKILL.md): Interactive developer experience plan review.
- [/plan-eng-review](plan-eng-review/SKILL.md): Eng manager-mode plan review.
- [/plan-tune](plan-tune/SKILL.md): Self-tuning question sensitivity + developer psychographic for gstack (v1: observational).
- [/qa](qa/SKILL.md): Test the site, API or CLI and fix the bugs it finds.
- [/qa](qa/SKILL.md): Use when a site, API or CLI needs testing and fixing.
- [/qa-only](qa-only/SKILL.md): Report browser/API/CLI/job/worker/webhook bugs.
- [/retro](retro/SKILL.md): Weekly engineering retrospective.
- [/review](review/SKILL.md): Review this PR's diff.
- [/review](review/SKILL.md): Use when a PR or diff needs review.
- [/scrape](scrape/SKILL.md): Pull data from a web page through the Aside browser — your real, already signed-in sessions.
- [/setup-browser-cookies](setup-browser-cookies/SKILL.md): Import cookies from your real Chromium browser into the headless browse session.
- [/setup-deploy](setup-deploy/SKILL.md): Configure deployment settings for /land-and-deploy.
@@ -71,7 +71,7 @@ Conventions:
- [/test-audit](test-audit/SKILL.md): Find low-value or duplicate tests and the test-only code they keep alive.
- [/ubicloud](ubicloud/SKILL.md): Run a heavy suite, build or eval on an ephemeral 16-vCPU Ubicloud VM: TTL-named VMs, a reaper, quota preflight and a hang watchdog.
- [/unfreeze](unfreeze/SKILL.md): Clear the freeze boundary set by /freeze, allowing edits to all directories again.
- [/verify](verify/SKILL.md): Build loop while coding: run the repo's checks and the tests /ship would pick, fix, repeat until green.
- [/verify](verify/SKILL.md): Use when coding: run the checks and tests /ship picks until green.
- [/write-skill](write-skill/SKILL.md): Write a new agent skill or improve one without bloat: the edit ladder, levels, drift and size checks, full diets.
## Browse Commands
+31 -2
View File
@@ -2,7 +2,7 @@
name: investigate
preamble-tier: 2
version: 1.0.0
description: Systematic debugging with root cause investigation. (gstack)
description: "Use when a test fails, code errors or output is wrong: find the root cause, then fix it. (gstack)"
allowed-tools:
- Bash
- Read
@@ -15,9 +15,29 @@ allowed-tools:
triggers:
- debug this
- fix this bug
- test fails
- failing test
- why is this broken
- root cause analysis
- this doesn't work
- build is broken
- stack trace
- investigate this error
- root cause analysis
contract:
version: 1
items:
- id: debug-report
kind: report_labels
heading: DEBUG REPORT
labels:
- Symptom
- Root cause
- Fix
- Evidence
- Regression test
- Status
- id: check-after-last-edit
kind: check_after_last_edit
hooks:
PreToolUse:
- matcher: "Edit"
@@ -61,6 +81,15 @@ gbrain:
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
<!-- Regenerate: bun run gen:skill-docs -->
## Done means
Finish only when each of these exists (contract v1):
- `debug-report`: a `DEBUG REPORT` with Symptom, Root cause, Fix, Evidence, Regression test and Status filled in.
- `check-after-last-edit`: a test or repro command run after your last edit.
`gstack-skill-contract check` reads them from the transcript (docs/skill-routing-and-contracts.md). For an item that truly does not apply, run `~/.claude/skills/gstack/bin/gstack-skill-contract waive <item> --reason "<why>"` (this session only; it counts as a miss).
## Shared preamble (load first)
This skill follows gstack shared preamble `4e82fa4c839f` (AskUserQuestion format, voice, writing, completeness and completion rules). If your Read of that file, headed by this id, is still in context, do not read it again; otherwise Read `~/.claude/skills/gstack/investigate/sections/preamble.md` in full before the command below. A section or subsection here with a shared heading replaces the shared one.
+27 -5
View File
@@ -3,10 +3,10 @@ name: investigate
preamble-tier: 2
version: 1.0.0
description: |
Systematic debugging with root cause investigation. Iron Law: no fixes
without root cause.
Use when asked to "debug this", "fix this bug", "why is this broken",
"investigate this error", or "root cause analysis". (gstack)
Use when a test fails, code errors or output is wrong: find the root cause, then fix it.
Systematic debugging; Iron Law: no fixes without root cause. Fits a failing
build, test, script, service or config, an error message or stack trace,
wrong or missing output, or broken data, before any edit that fixes it. (gstack)
allowed-tools:
- Bash
- Read
@@ -19,9 +19,29 @@ allowed-tools:
triggers:
- debug this
- fix this bug
- test fails
- failing test
- why is this broken
- root cause analysis
- this doesn't work
- build is broken
- stack trace
- investigate this error
- root cause analysis
contract:
version: 1
items:
- id: debug-report
kind: report_labels
heading: DEBUG REPORT
labels:
- Symptom
- Root cause
- Fix
- Evidence
- Regression test
- Status
- id: check-after-last-edit
kind: check_after_last_edit
hooks:
PreToolUse:
- matcher: "Edit"
@@ -63,6 +83,8 @@ gbrain:
render_as: "## Recent eureka moments (cross-project)"
---
{{CONTRACT_DONE_MEANS}}
{{PREAMBLE}}
# Systematic Debugging
+1 -1
View File
@@ -2,7 +2,7 @@
name: qa
preamble-tier: 4
version: 2.0.0
description: Test the site, API or CLI and fix the bugs it finds. (gstack)
description: Use when a site, API or CLI needs testing and fixing. (gstack)
allowed-tools:
- Bash
- Read
+2 -2
View File
@@ -3,9 +3,9 @@ name: qa
preamble-tier: 4
version: 2.0.0
description: |
Test the site, API or CLI and fix the bugs it finds.
Use when a site, API or CLI needs testing and fixing.
Covers browser/API/CLI/job/worker/webhook bugs and commits verified fixes atomically.
Use when asked to "qa", "QA", "qa test this", "test this site", "find bugs", "find bugs on site",
Fits "qa", "QA", "qa test this", "test this site", "find bugs", "find bugs on site",
"test and fix", or "fix what's broken".
Proactively suggest when the user says a feature is ready for testing
or asks "does this work?". Three tiers: Quick (critical/high only),
+29 -1
View File
@@ -2,7 +2,7 @@
name: review
preamble-tier: 4
version: 1.0.0
description: Review this PR's diff. (gstack)
description: Use when a PR or diff needs review. (gstack)
allowed-tools:
- Bash
- Read
@@ -15,13 +15,41 @@ allowed-tools:
- WebSearch
triggers:
- review this pr
- look over my diff
- code review
- check my diff
- review my changes
- review this diff
- review the branch
- pre-landing review
contract:
version: 1
items:
- id: specialists-dispatched
kind: selected_specialists_dispatched
marker: "Dispatching N specialists:"
- id: adversarial-pass
kind: agent_dispatch
match: Think like an attacker and a chaos engineer
- id: review-report
kind: report_labels
heading: Pre-Landing Review
labels:
- Gate edits
---
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
<!-- Regenerate: bun run gen:skill-docs -->
## Done means
Finish only when each of these exists (contract v1):
- `specialists-dispatched`: the specialist selection printed (`Dispatching N specialists:` ...), then one Agent dispatch per specialist it names.
- `adversarial-pass`: an Agent dispatch for the adversarial pass, its prompt including "Think like an attacker and a chaos engineer".
- `review-report`: a `Pre-Landing Review` with Gate edits filled in.
`gstack-skill-contract check` reads them from the transcript (docs/skill-routing-and-contracts.md). For an item that truly does not apply, run `~/.claude/skills/gstack/bin/gstack-skill-contract waive <item> --reason "<why>"` (this session only; it counts as a miss).
## Shared preamble (load first)
This skill follows gstack shared preamble `4e82fa4c839f` (AskUserQuestion format, voice, writing, completeness and completion rules). If your Read of that file, headed by this id, is still in context, do not read it again; otherwise Read `~/.claude/skills/gstack/review/sections/preamble.md` in full before the command below. A section or subsection here with a shared heading replaces the shared one.
+25 -4
View File
@@ -3,10 +3,11 @@ name: review
preamble-tier: 4
version: 1.0.0
description: |
Review this PR's diff. Checks it against the base branch for SQL safety, LLM trust
boundary violations, conditional side effects, and other structural issues. Use when
asked to "review this PR", "code review", "pre-landing review", or "check my diff".
Proactively suggest when the user is about to merge or land code changes. (gstack)
Use when a PR or diff needs review.
Checks this PR's diff against the base branch for SQL safety, LLM trust boundary
violations, conditional side effects and other structural issues that pass CI
and break production. Proactively suggest when the user is about to merge or
land code changes. (gstack)
codex_default_prompt: "Use $gstack-review to review this diff against its base before it lands: find the bugs that pass CI and break in production, fix the clear ones, and list what remains."
allowed-tools:
- Bash
@@ -20,11 +21,31 @@ allowed-tools:
- WebSearch
triggers:
- review this pr
- look over my diff
- code review
- check my diff
- review my changes
- review this diff
- review the branch
- pre-landing review
contract:
version: 1
items:
- id: specialists-dispatched
kind: selected_specialists_dispatched
marker: "Dispatching N specialists:"
- id: adversarial-pass
kind: agent_dispatch
match: Think like an attacker and a chaos engineer
- id: review-report
kind: report_labels
heading: Pre-Landing Review
labels:
- Gate edits
---
{{CONTRACT_DONE_MEANS}}
{{PREAMBLE}}
{{BASE_BRANCH_DETECT}}
+126
View File
@@ -0,0 +1,126 @@
/**
* {{CONTRACT_DONE_MEANS}}: a skill's `contract:` frontmatter block (GST-1088, frozen v1
* schema) rendered as a short "Done means" list at the top of the body, so the
* outputs the model reads as required and the block the Stop check reads cannot
* drift. parseSkillContract is the schema; test/skill-descriptions.test.ts runs it
* over every template, so a malformed block fails CI instead of failing open.
*/
import * as fs from 'fs';
import * as path from 'path';
import type { TemplateContext } from './types';
const ROOT = path.resolve(import.meta.dir, '..', '..');
const code = (s: string) => '\u0060' + s + '\u0060';
export type ContractItem =
| { id: string; kind: 'report_labels'; heading: string; labels: string[] }
| { id: string; kind: 'check_after_last_edit' }
| { id: string; kind: 'selected_specialists_dispatched'; marker: string }
| { id: string; kind: 'agent_dispatch'; match: string };
export interface SkillContract {
version: 1;
items: ContractItem[];
}
const KIND_FIELDS: Record<ContractItem['kind'], string[]> = {
report_labels: ['heading', 'labels'],
check_after_last_edit: [],
selected_specialists_dispatched: ['marker'],
agent_dispatch: ['match'],
};
const PLACEHOLDER = /^\[.*\]$|^<.*>$|^\.\.\.$|^(todo|tbd|n\/a)$/i;
function fail(where: string, problem: string): never {
throw new Error('CONTRACT_INVALID ' + where + ': ' + problem + '. Fix: edit ' + where + ', then bun run gen:skill-docs');
}
const isMap = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
const isText = (v: unknown): v is string => typeof v === 'string' && v.trim() !== '';
/** Validates a parsed `contract:` value against the frozen v1 schema; where names the template in errors. */
export function parseSkillContract(value: unknown, where: string): SkillContract {
if (!isMap(value)) fail(where, 'contract must be a mapping { version: 1, items: [...] }');
for (const key of Object.keys(value)) {
if (key !== 'version' && key !== 'items') fail(where, 'contract has unknown key "' + key + '"');
}
if (value.version !== 1) fail(where, 'contract version must be 1, got ' + JSON.stringify(value.version));
if (!Array.isArray(value.items) || value.items.length === 0) fail(where, 'contract items must be a non-empty list');
const seen = new Set<string>();
const items = value.items.map((raw: unknown, i: number) => {
const at = 'items[' + i + ']';
if (!isMap(raw)) fail(where, at + ' must be a mapping');
const kind = raw.kind as ContractItem['kind'];
if (typeof raw.kind !== 'string' || !Object.hasOwn(KIND_FIELDS, kind)) {
fail(where, at + ' kind ' + JSON.stringify(raw.kind) + ' is not one of ' + Object.keys(KIND_FIELDS).join(', '));
}
if (typeof raw.id !== 'string' || !/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(raw.id)) {
fail(where, at + ' id must be lowercase kebab-case, got ' + JSON.stringify(raw.id));
}
if (seen.has(raw.id)) fail(where, at + ' duplicate id "' + raw.id + '"');
seen.add(raw.id);
for (const key of Object.keys(raw)) {
if (key !== 'id' && key !== 'kind' && !KIND_FIELDS[kind].includes(key)) fail(where, at + ' (' + kind + ') has unknown key "' + key + '"');
}
for (const field of KIND_FIELDS[kind]) {
if (field !== 'labels' && !isText(raw[field])) fail(where, at + ' ' + field + ' must be a non-empty string');
}
if (kind === 'report_labels') {
if (!Array.isArray(raw.labels) || raw.labels.length === 0) fail(where, at + ' labels must be a non-empty list');
for (const label of raw.labels as unknown[]) {
if (!isText(label)) fail(where, at + ' labels must be non-empty strings');
if (PLACEHOLDER.test(label.trim())) fail(where, at + ' label ' + JSON.stringify(label) + ' is a placeholder');
}
}
return raw as ContractItem;
});
return { version: 1, items };
}
/** The template's contract, or null without one; required makes a missing block an error. */
export function contractFromTemplate(tmplContent: string, where: string, opts: { required?: boolean } = {}): SkillContract | null {
const block = tmplContent.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
if (!block || !/^contract:/m.test(block[1])) {
if (opts.required) fail(where, '{{CONTRACT_DONE_MEANS}} needs a contract block in the frontmatter');
return null;
}
let frontmatter: unknown;
try {
frontmatter = Bun.YAML.parse(block[1]);
} catch (error) {
fail(where, 'frontmatter is not valid YAML (' + (error instanceof Error ? error.message : String(error)) + ')');
}
return parseSkillContract(isMap(frontmatter) ? frontmatter.contract : undefined, where);
}
const list = (xs: string[]) => xs.length < 2 ? xs.join('') : xs.slice(0, -1).join(', ') + ' and ' + xs[xs.length - 1];
function plainWords(item: ContractItem): string {
switch (item.kind) {
case 'report_labels': return 'a ' + code(item.heading) + ' with ' + list(item.labels) + ' filled in';
case 'check_after_last_edit': return 'a test or repro command run after your last edit';
case 'selected_specialists_dispatched': return 'the specialist selection printed (' + code(item.marker) + ' ...), then one Agent dispatch per specialist it names';
case 'agent_dispatch': return 'an Agent dispatch for the ' + item.id.replace(/-/g, ' ') + ', its prompt including "' + item.match + '"';
}
}
export function renderDoneMeans(contract: SkillContract, binDir: string): string {
return [
'## Done means',
'',
'Finish only when each of these exists (contract v1):',
'',
...contract.items.map(item => '- ' + code(item.id) + ': ' + plainWords(item) + '.'),
'',
code('gstack-skill-contract check') + ' reads them from the transcript (docs/skill-routing-and-contracts.md). ' +
'For an item that truly does not apply, run ' + code(binDir + '/gstack-skill-contract waive <item> --reason "<why>"') + ' (this session only; it counts as a miss).',
].join('\n');
}
export function generateContractDoneMeans(ctx: TemplateContext): string {
const where = path.relative(ROOT, ctx.tmplPath).split(path.sep).join('/');
const contract = contractFromTemplate(fs.readFileSync(ctx.tmplPath, 'utf-8'), where, { required: true });
return renderDoneMeans(contract!, ctx.paths.binDir);
}
+2
View File
@@ -58,6 +58,7 @@ import { generateShipDriverStops } from './ship-driver';
import { generateQAScope, generateQAExploratory, generateQAFunctional, generateQAResource, generateQAReview, generateQAReviewPreflight, generateQAMethodReads } from './qa';
import { generateVoiceTriggerRouting } from './voice-triggers';
import { generateTranscriptDiscovery, generateTranscriptSources } from './transcripts';
import { generateContractDoneMeans } from './contract-done-means';
export const RESOLVERS: Record<string, ResolverFn> = {
AUTOPLAN_PUBLICATION_HOOK: generateAutoplanPublicationHook,
@@ -197,4 +198,5 @@ export const RESOLVERS: Record<string, ResolverFn> = {
SECTION_INDEX,
TRANSCRIPT_SOURCES: generateTranscriptSources,
TRANSCRIPT_DISCOVERY: generateTranscriptDiscovery,
CONTRACT_DONE_MEANS: generateContractDoneMeans,
};
+9 -9
View File
@@ -6,52 +6,52 @@
"agy/gstack-plan-design-review": 109107,
"agy/gstack-plan-devex-review": 114948,
"agy/gstack-plan-eng-review": 143581,
"agy/gstack-review": 104182,
"agy/gstack-review": 104820,
"codex/gstack-autoplan": 108400,
"codex/gstack-plan-design-review": 110464,
"codex/gstack-plan-devex-review": 116456,
"codex/gstack-plan-eng-review": 144746,
"codex/gstack-review": 103705,
"codex/gstack-review": 104343,
"copilot/gstack-autoplan": 109100,
"copilot/gstack-plan-design-review": 109373,
"copilot/gstack-plan-devex-review": 114410,
"copilot/gstack-plan-eng-review": 143746,
"copilot/gstack-review": 103763,
"copilot/gstack-review": 104401,
"cursor/gstack-autoplan": 108332,
"cursor/gstack-plan-design-review": 107915,
"cursor/gstack-plan-devex-review": 113680,
"cursor/gstack-plan-eng-review": 142336,
"cursor/gstack-review": 122053,
"cursor/gstack-review": 122691,
"factory/gstack-autoplan": 110610,
"factory/gstack-plan-design-review": 111013,
"factory/gstack-plan-devex-review": 117043,
"factory/gstack-plan-eng-review": 145444,
"factory/gstack-review": 125826,
"factory/gstack-review": 126464,
"gbrain/gstack-autoplan": 108739,
"gbrain/gstack-plan-eng-review": 124963,
"gemini/gstack-autoplan": 108890,
"gemini/gstack-plan-design-review": 109201,
"gemini/gstack-plan-devex-review": 114238,
"gemini/gstack-plan-eng-review": 143579,
"gemini/gstack-review": 103585,
"gemini/gstack-review": 104223,
"hermes/gstack-autoplan": 108742,
"hermes/gstack-plan-eng-review": 124999,
"kiro/gstack-autoplan": 108106,
"kiro/gstack-plan-design-review": 107761,
"kiro/gstack-plan-devex-review": 113532,
"kiro/gstack-plan-eng-review": 142198,
"kiro/gstack-review": 121877,
"kiro/gstack-review": 122515,
"openclaw/gstack-autoplan": 108562,
"openclaw/gstack-plan-eng-review": 123478,
"opencode/gstack-autoplan": 109081,
"opencode/gstack-plan-design-review": 109229,
"opencode/gstack-plan-devex-review": 114288,
"opencode/gstack-plan-eng-review": 143605,
"opencode/gstack-review": 122675,
"opencode/gstack-review": 123313,
"slate/gstack-autoplan": 108219,
"slate/gstack-plan-design-review": 107838,
"slate/gstack-plan-devex-review": 113606,
"slate/gstack-plan-eng-review": 142267,
"slate/gstack-review": 121965
"slate/gstack-review": 122603
}
}
+1 -1
View File
@@ -248,7 +248,7 @@ describe('GSTA-135: registered skills get used', () => {
}
const oc = register(opts(d, 'openclaw', { mode: 'all', skillsDir: path.join(d, 'oc') }));
const review = fs.readFileSync(path.join(oc.dest, 'gstack-review', 'SKILL.md'), 'utf8');
expect(descriptionOf(review)[0]).not.toMatch(/^Use /);
expect(descriptionOf(review)[0]).not.toMatch(/^Use for: /);
}, 120_000);
test('OpenClaw hybrid is the default: the task skills listed directly, the router for the rest; mode switches refresh and uninstall cleanly', () => {
+2 -2
View File
@@ -490,10 +490,10 @@ do not launch the downstream skill or open a browser.`,
gateAfterStop: undefined, // operational multi-STOP skill, like ship
},
behavioral: 'plan',
maxSkeletonBytes: 59_073, // GSTA-96 hoist (2026-10-26): lowered from 80_900 by the shared preamble that moved to sections/preamble.md, keeping the pre-hoist headroom of 319 bytes; measured 58,754. Before: Shared-code identity/skip/action rules + critical-severity validation; measured 74,493 (2026-09-17). + v1.91.12.0 merge of #2999 (review clarity repairs: await reads, research alongside dispatch, /review deadline and setup authority, findings sources) with #3002 (guarded state-root lines, plan-check checkpoints); each fit alone; measured 74,881 (2026-10-01). + Oct 8 wave: shared voice directive bans "load-bearing" and adds the reply-language rule, offset by tightening the bounded closer (net +17 bytes per tier-2+ skill); measured 74,884 (2026-10-08). + gate integrity (HONEST_WORK_GATE_INTEGRITY W2): Step 3.5 Diff scans with the shared GATE_SCAN_BLOCK, the Step 4 carry, the four-option `review-gate-disposition` question in 5c, gate record fields in 5.8 and the Gate edits report line; measured 80,054 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): Step 4's enum-only paragraph becomes the sideways sweep with the forced-CRITICAL list, plus the flake-fix number sentence in the Gate Integrity paragraph; measured skeleton 80,812 (2026-10-10).
maxSkeletonBytes: 59_984, // GST-1088 lane B (raised from 59,073 to measured, no headroom): the contract: frontmatter block and the F1 "Done means" list rendered from it at the top of the body, plus widened triggers; F1 is a gate-rev-2 recommendation on the plan Garry approved with "do it" on gate rev 1 (2026-11-03), contingent on his gate-rev-2 answer, and lane C's diet is expected to bring /review back under 59,073; measured 59,984. Before: GSTA-96 hoist (2026-10-26): lowered from 80_900 by the shared preamble that moved to sections/preamble.md, keeping the pre-hoist headroom of 319 bytes; measured 58,754. Before: Shared-code identity/skip/action rules + critical-severity validation; measured 74,493 (2026-09-17). + v1.91.12.0 merge of #2999 (review clarity repairs: await reads, research alongside dispatch, /review deadline and setup authority, findings sources) with #3002 (guarded state-root lines, plan-check checkpoints); each fit alone; measured 74,881 (2026-10-01). + Oct 8 wave: shared voice directive bans "load-bearing" and adds the reply-language rule, offset by tightening the bounded closer (net +17 bytes per tier-2+ skill); measured 74,884 (2026-10-08). + gate integrity (HONEST_WORK_GATE_INTEGRITY W2): Step 3.5 Diff scans with the shared GATE_SCAN_BLOCK, the Step 4 carry, the four-option `review-gate-disposition` question in 5c, gate record fields in 5.8 and the Gate edits report line; measured 80,054 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): Step 4's enum-only paragraph becomes the sideways sweep with the forced-CRITICAL list, plus the flake-fix number sentence in the Gate Integrity paragraph; measured skeleton 80,812 (2026-10-10).
minUnionBytes: 89_000, // Phase 4 wave 1; measured union 93,357
mustContain: ['confidence', 'P1', 'P2', 'Review Army', 'adversarial'],
maxSizeRatio: 1.3180, // + GSTA-167 variant G part 1 (plan approved by Garry 2026-10-30, autoplan-20261030161754 "Do it", which names about +0.3k prompt tokens per voice): the native adversarial and outside-voice prompts carry the blocking floor and the Severity and Blocks lines, +537 bytes; measured 1.3180 (2026-10-30). Before: 1.3136. GSTA-96 hoist merged with GSTA-92 phase 2 (2026-10-26): both owner-approved raises together, measured 142,550 / 108,523 = 1.3135. Before: Shared-code feature + critical-severity validation: 128,042 union bytes / 108,523 baseline = 1.1799; preserves content floors. + v1.91.12.0 merge of #2999 (above, plus plan-completion fallback intent and specialist checklist-by-path) with #3002; measured 1.1843 (2026-10-01). + v1.91.19.0 wave: verdict-form Codex callers with sandbox preflight (INV-1/B1), P0/P1 structured gate (B1b), bound-plan discovery and data-not-instructions guard (F1/D3), visible LEARNINGS failures (B5); measured 129,941 / 108,523 = 1.1974 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 130,025 / 108,523 = 1.1981 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051); measured 130,359 / 108,523 = 1.2012 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 131,314 / 108,523 = 1.2100 (2026-10-06). Combined Oct 6 wave head (A and B): measured 131,966 / 108,523 = 1.2160 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 132,199 / 108,523 = 1.2182 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 132,258 / 108,523 = 1.2187 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 132,233 / 108,523 = 1.2185 (2026-10-07). Merged with the guard area: measured 132,522 / 108,523 = 1.2211 (2026-10-07). + Oct 8 wave: the defensive-security framing and fixture summary mode reach the security specialist and the Red Team (stated once in the dispatch, referenced by the Red Team); measured 133,902 / 108,523 = 1.2339 (2026-10-08). + gate integrity W2 (above); measured 1.282 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): sideways sweep and flake-fix number in Step 4; `Diff read` and `Undeclared behavior changes` in the review-mode Scope Check (plan-completion section); measured 140,497 / 108,523 = 1.2946 (2026-10-10). + harness lane E (2026-10-26): the lead-judgment stage in the finding merge and the `cause` anchor in the specialist prompts, shared with /ship through scripts/resolvers/review-army.ts; measured 141,949 / 108,523 = 1.3080.
maxSizeRatio: 1.3280, // + GST-1088 lane B (raised from 1.3180 to measured, no headroom): the contract: frontmatter block and the F1 "Done means" list, contingent on Garry's gate-rev-2 answer; lane C's diet is expected to bring /review back under 1.3180; measured 144,117 / 108,523 = 1.3280. Before: + GSTA-167 variant G part 1 (plan approved by Garry 2026-10-30, autoplan-20261030161754 "Do it", which names about +0.3k prompt tokens per voice): the native adversarial and outside-voice prompts carry the blocking floor and the Severity and Blocks lines, +537 bytes; measured 1.3180 (2026-10-30). Before: 1.3136. GSTA-96 hoist merged with GSTA-92 phase 2 (2026-10-26): both owner-approved raises together, measured 142,550 / 108,523 = 1.3135. Before: Shared-code feature + critical-severity validation: 128,042 union bytes / 108,523 baseline = 1.1799; preserves content floors. + v1.91.12.0 merge of #2999 (above, plus plan-completion fallback intent and specialist checklist-by-path) with #3002; measured 1.1843 (2026-10-01). + v1.91.19.0 wave: verdict-form Codex callers with sandbox preflight (INV-1/B1), P0/P1 structured gate (B1b), bound-plan discovery and data-not-instructions guard (F1/D3), visible LEARNINGS failures (B5); measured 129,941 / 108,523 = 1.1974 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 130,025 / 108,523 = 1.1981 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051); measured 130,359 / 108,523 = 1.2012 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 131,314 / 108,523 = 1.2100 (2026-10-06). Combined Oct 6 wave head (A and B): measured 131,966 / 108,523 = 1.2160 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 132,199 / 108,523 = 1.2182 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 132,258 / 108,523 = 1.2187 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 132,233 / 108,523 = 1.2185 (2026-10-07). Merged with the guard area: measured 132,522 / 108,523 = 1.2211 (2026-10-07). + Oct 8 wave: the defensive-security framing and fixture summary mode reach the security specialist and the Red Team (stated once in the dispatch, referenced by the Red Team); measured 133,902 / 108,523 = 1.2339 (2026-10-08). + gate integrity W2 (above); measured 1.282 (2026-10-10). + Oct 10 multi-agent wave PR D2 (sideways review): sideways sweep and flake-fix number in Step 4; `Diff read` and `Undeclared behavior changes` in the review-mode Scope Check (plan-completion section); measured 140,497 / 108,523 = 1.2946 (2026-10-10). + harness lane E (2026-10-26): the lead-judgment stage in the finding merge and the `cause` anchor in the specialist prompts, shared with /ship through scripts/resolvers/review-army.ts; measured 141,949 / 108,523 = 1.3080.
},
codex: {
skill: 'codex',
+208
View File
@@ -0,0 +1,208 @@
import { describe, expect, test } from 'bun:test';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { sentences } from '../lib/skill-triggers';
import { contractFromTemplate, parseSkillContract, renderDoneMeans } from '../scripts/resolvers/contract-done-means';
/**
* GST-1088 discovery targets: each says when to use it in the part of its
* description every host shows, and every contract block matches the frozen
* v1 schema, so a malformed block fails here instead of failing open in the
* Stop check.
*/
const ROOT = join(import.meta.dir, '..');
const SKILLS = ['investigate', 'review', 'qa', 'verify'];
// splitCatalogDescription cuts a longer Claude lead with "...", which drops the end of the when-clause.
const LEAD_CAP = 200;
// test/catalog-budget.test.ts PER_SKILL_BYTE_CAP: name + Claude description bytes.
const CATALOG_ENTRY_CAP = 260;
// Codex, Gemini, Copilot and agy reject a longer description (hosts/*.ts descriptionLimit).
const DESCRIPTION_CAP = 1024;
const TRIGGER_CAP = 32;
const BT = '\u0060';
const fix = (skill: string) => 'Fix: edit ' + skill + '/SKILL.md.tmpl, then bun run gen:skill-docs';
const read = (rel: string) => readFileSync(join(ROOT, rel), 'utf8').replace(/\r\n/g, '\n');
function frontmatter(rel: string): Record<string, unknown> {
const m = read(rel).match(/^---\n([\s\S]*?)\n---\n/);
if (!m) throw new Error(rel + ': no frontmatter');
return Bun.YAML.parse(m[1]) as Record<string, unknown>;
}
const body = (rel: string) => read(rel).replace(/^---\n[\s\S]*?\n---\n/, '');
const triggersOf = (skill: string) => (frontmatter(skill + '/SKILL.md.tmpl').triggers ?? []) as string[];
describe('discovery-target descriptions (GST-1088)', () => {
for (const skill of SKILLS) {
const tmpl = skill + '/SKILL.md.tmpl';
test(skill + ': the template description opens with a "Use when ..." sentence', () => {
const first = sentences(String(frontmatter(tmpl).description ?? ''))[0] ?? '';
expect(/^Use when\b/.test(first), tmpl + ': the first sentence is "' + first.slice(0, 80) + '", not a "Use when ..." clause. ' + fix(skill)).toBe(true);
});
test(skill + ': the Claude catalog entry keeps the when-clause within its caps', () => {
const rel = skill + '/SKILL.md';
const fm = frontmatter(rel);
const description = String(fm.description ?? '');
const lead = description.replace(/\s*\(gstack\)\s*$/, '');
expect(/^Use when\b/.test(lead), rel + ': the catalog description "' + lead.slice(0, 80) + '" does not start with "Use when". ' + fix(skill)).toBe(true);
expect(lead.length, rel + ': the catalog lead is ' + lead.length + ' chars against the ' + LEAD_CAP + '-char cap. ' + fix(skill)).toBeLessThanOrEqual(LEAD_CAP);
const entry = Buffer.byteLength(String(fm.name)) + Buffer.byteLength(description);
expect(entry, rel + ': name + description is ' + entry + ' bytes against the ' + CATALOG_ENTRY_CAP + '-byte cap. ' + fix(skill)).toBeLessThanOrEqual(CATALOG_ENTRY_CAP);
});
test(skill + ': the full description fits every host limit', () => {
const full = String(frontmatter(tmpl).description ?? '').trim();
expect(full.length, tmpl + ': the description is ' + full.length + ' chars against the ' + DESCRIPTION_CAP + '-char cap. ' + fix(skill)).toBeLessThanOrEqual(DESCRIPTION_CAP);
});
test(skill + ': triggers are short and distinct', () => {
const own = triggersOf(skill);
expect(own.length, tmpl + ': no triggers. ' + fix(skill)).toBeGreaterThan(0);
expect(new Set(own).size, tmpl + ': duplicate triggers. ' + fix(skill)).toBe(own.length);
for (const t of own) {
expect(t.length, tmpl + ': trigger "' + t + '" is ' + t.length + ' chars against the ' + TRIGGER_CAP + '-char cap. ' + fix(skill)).toBeLessThanOrEqual(TRIGGER_CAP);
expect(t, tmpl + ': trigger "' + t + '" must be lowercase. ' + fix(skill)).toBe(t.toLowerCase());
for (const other of SKILLS.filter(s => s !== skill)) {
const clash = triggersOf(other).find(o => o.includes(t) || t.includes(o));
expect(clash, tmpl + ': trigger "' + t + '" overlaps ' + other + "'s \"" + clash + '". ' + fix(skill)).toBeUndefined();
}
}
});
}
const PROMPTS: Array<[string, string]> = [
['this test fails, fix it', 'investigate'],
['why is this broken?', 'investigate'],
['the build is broken, here is the stack trace', 'investigate'],
['review this PR', 'review'],
['can you look over my diff', 'review'],
['review my changes before I merge', 'review'],
['test the site and fix what you find', 'qa'],
['can you qa test this page', 'qa'],
['run the checks and make it green', 'verify'],
['verify my change', 'verify'],
];
for (const [prompt, want] of PROMPTS) {
test('untold prompt "' + prompt + '" carries a ' + want + ' trigger and no other target trigger', () => {
const p = prompt.toLowerCase();
const hits = SKILLS.filter(s => triggersOf(s).some(t => p.includes(t)));
expect(hits, '"' + prompt + '" matches [' + hits.join(', ') + '], want [' + want + ']. ' + fix(want)).toEqual([want]);
});
}
});
describe('contract blocks (frozen v1 schema)', () => {
const templates = readdirSync(ROOT, { withFileTypes: true })
.filter(d => d.isDirectory() && existsSync(join(ROOT, d.name, 'SKILL.md.tmpl')))
.map(d => d.name)
.sort();
test('investigate and review declare contracts', () => {
for (const skill of ['investigate', 'review']) {
const tmpl = skill + '/SKILL.md.tmpl';
expect(contractFromTemplate(read(tmpl), tmpl), tmpl + ': no contract block. ' + fix(skill)).not.toBeNull();
}
});
for (const skill of templates) {
const tmpl = skill + '/SKILL.md.tmpl';
const contract = contractFromTemplate(read(tmpl), tmpl);
if (!contract) {
test(skill + ': no contract, no Done-means placeholder', () => {
expect(read(tmpl).includes('{{CONTRACT_DONE_MEANS}}'), tmpl + ': {{CONTRACT_DONE_MEANS}} without a contract block. ' + fix(skill)).toBe(false);
});
continue;
}
test(skill + ': the body carries the Done-means placeholder and the render carries the list', () => {
expect(body(tmpl).includes('{{CONTRACT_DONE_MEANS}}'), tmpl + ': a contract block needs {{CONTRACT_DONE_MEANS}} at the top of the body. ' + fix(skill)).toBe(true);
expect(read(skill + '/SKILL.md').includes(renderDoneMeans(contract, '~/.claude/skills/gstack/bin')), skill + '/SKILL.md: the Done-means list is stale. ' + fix(skill)).toBe(true);
});
test(skill + ': every contract marker appears in the skill text', () => {
const sectionDir = join(ROOT, skill, 'sections');
const sections = existsSync(sectionDir)
? readdirSync(sectionDir).filter(f => f.endsWith('.md')).map(f => read(skill + '/sections/' + f))
: [];
const text = [body(tmpl), ...sections].join('\n');
for (const item of contract.items) {
const markers = item.kind === 'report_labels' ? [item.heading, ...item.labels.map(l => l + ':')]
: item.kind === 'selected_specialists_dispatched' ? [item.marker]
: item.kind === 'agent_dispatch' ? [item.match]
: [];
for (const marker of markers) {
expect(text.includes(marker), tmpl + ': contract item ' + item.id + ' needs "' + marker + '", which the skill text never prints. ' + fix(skill)).toBe(true);
}
}
});
}
});
describe('contract-done-means renderer', () => {
const BIN = '~/.claude/skills/gstack/bin';
const all = parseSkillContract({
version: 1,
items: [
{ id: 'debug-report', kind: 'report_labels', heading: 'DEBUG REPORT', labels: ['Symptom', 'Root cause', 'Status'] },
{ id: 'check-after-last-edit', kind: 'check_after_last_edit' },
{ id: 'specialists-dispatched', kind: 'selected_specialists_dispatched', marker: 'Dispatching N specialists:' },
{ id: 'adversarial-pass', kind: 'agent_dispatch', match: 'chaos engineer' },
],
}, 'fixture/SKILL.md.tmpl');
const out = renderDoneMeans(all, BIN);
test('names each item kind in plain words', () => {
expect(out).toContain('## Done means');
expect(out).toContain('- ' + BT + 'debug-report' + BT + ': a ' + BT + 'DEBUG REPORT' + BT + ' with Symptom, Root cause and Status filled in.');
expect(out).toContain('- ' + BT + 'check-after-last-edit' + BT + ': a test or repro command run after your last edit.');
expect(out).toContain('- ' + BT + 'specialists-dispatched' + BT + ': the specialist selection printed (' + BT + 'Dispatching N specialists:' + BT + ' ...), then one Agent dispatch per specialist it names.');
expect(out).toContain('- ' + BT + 'adversarial-pass' + BT + ': an Agent dispatch for the adversarial pass, its prompt including "chaos engineer".');
});
test('points to check and waive in the routing-and-contracts doc, never the off switch', () => {
expect(out).toContain(BT + 'gstack-skill-contract check' + BT);
expect(out).toContain(BT + BIN + '/gstack-skill-contract waive <item> --reason "<why>"' + BT);
expect(out).toContain('docs/skill-routing-and-contracts.md');
expect(out).not.toContain('skill_contracts');
});
const one = (item: Record<string, unknown>) => ({ version: 1, items: [item] });
const MALFORMED: Array<[string, unknown, RegExp]> = [
['not a mapping', 'yes', /must be a mapping/],
['wrong version', { version: 2, items: [{ id: 'a', kind: 'check_after_last_edit' }] }, /version must be 1/],
['no items', { version: 1, items: [] }, /items must be a non-empty list/],
['unknown top-level key', { version: 1, items: [{ id: 'a', kind: 'check_after_last_edit' }], extra: 1 }, /unknown key "extra"/],
['unknown kind', one({ id: 'a', kind: 'vibes' }), /kind "vibes" is not one of/],
['bad id', one({ id: 'Debug Report', kind: 'check_after_last_edit' }), /id must be lowercase kebab-case/],
['duplicate id', { version: 1, items: [{ id: 'a', kind: 'check_after_last_edit' }, { id: 'a', kind: 'agent_dispatch', match: 'x' }] }, /duplicate id "a"/],
['labels missing', one({ id: 'a', kind: 'report_labels', heading: 'R' }), /labels must be a non-empty list/],
['placeholder label', one({ id: 'a', kind: 'report_labels', heading: 'R', labels: ['[what the user observed]'] }), /placeholder/],
['heading missing', one({ id: 'a', kind: 'report_labels', labels: ['Symptom'] }), /heading must be a non-empty string/],
['marker missing', one({ id: 'a', kind: 'selected_specialists_dispatched' }), /marker must be a non-empty string/],
['match missing', one({ id: 'a', kind: 'agent_dispatch' }), /match must be a non-empty string/],
['field from another kind', one({ id: 'a', kind: 'check_after_last_edit', match: 'x' }), /unknown key "match"/],
];
for (const [name, value, want] of MALFORMED) {
test('a malformed block fails loudly: ' + name, () => {
let message = '';
try { parseSkillContract(value, 'demo/SKILL.md.tmpl'); } catch (e) { message = (e as Error).message; }
expect(message).toMatch(want);
expect(message).toContain('CONTRACT_INVALID demo/SKILL.md.tmpl');
expect(message).toContain('Fix: edit demo/SKILL.md.tmpl, then bun run gen:skill-docs');
});
}
test('a placeholder with no contract block fails the render', () => {
expect(() => contractFromTemplate('---\nname: demo\n---\n{{CONTRACT_DONE_MEANS}}\n', 'demo/SKILL.md.tmpl', { required: true }))
.toThrow(/CONTRACT_INVALID demo\/SKILL.md.tmpl: \{\{CONTRACT_DONE_MEANS\}\} needs a contract block/);
});
test('invalid frontmatter YAML fails the render', () => {
expect(() => contractFromTemplate('---\nname: demo\ncontract: [unclosed\n---\n', 'demo/SKILL.md.tmpl'))
.toThrow(/CONTRACT_INVALID demo\/SKILL.md.tmpl: frontmatter is not valid YAML/);
});
});
+3 -1
View File
@@ -2,11 +2,13 @@
name: verify
preamble-tier: 1
version: 0.1.0
description: "Build loop while coding: run the repo's checks and the tests /ship would pick, fix, repeat until green. (gstack)"
description: "Use when coding: run the checks and tests /ship picks until green. (gstack)"
triggers:
- verify my change
- run the checks
- make the tests pass
- make it green
- is it green
- check my work before ship
allowed-tools:
- Bash
+6 -4
View File
@@ -3,14 +3,16 @@ name: verify
preamble-tier: 1
version: 0.1.0
description: |
Build loop while coding: run the repo's checks and the tests /ship would
pick, fix, repeat until green. Never green when no test covers a change.
Use when asked to "verify", "check my work", "run the checks", "make it
green", or after each change before /ship. (gstack)
Use when coding: run the checks and tests /ship picks until green.
Build loop: run the repo's checks and the tests /ship would pick, fix, repeat.
Never green when no test covers a change. Fits "verify", "check my work", "run
the checks", "make it green", or after each change before /ship. (gstack)
triggers:
- verify my change
- run the checks
- make the tests pass
- make it green
- is it green
- check my work before ship
allowed-tools:
- Bash