train S3: merge #3909 at cc9b9b9a29 v1.94.18.0

This commit is contained in:
garrytan committed 2000-01-01 00:00:00 +00:00
commit c5f47ef481
19 files changed
+521 -39

No files matched your search

+14
View File
@@ -1,5 +1,19 @@
# Changelog
## [1.94.18.0] - 2026-11-03
**/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.
## [1.94.17.0] - 2026-11-03
- A pregate guard whose exact command already passed at this tree no longer runs again (GST-1186). When the base branch's `ship_lanes_from` maps the command, it is carried from the verified CI run through the same `gstack-evidence record-ci` path /ship lanes use (`GUARD: <id> carried runs=…`); otherwise a counted local ledger pass of that command at the same working tree stands (CI rows count only through a live `record-ci` check) (`GUARD: <id> reused …`, and the new read-only `gstack-evidence passed -- <command>`). Repos whose suites are guards, like founderstar, stop rerunning about six minutes of suites per /ship. See docs/pregate.md#guard-carry.
+1 -1
View File
@@ -1 +1 @@
1.94.17.0
1.94.18.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.94.17.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.94.18.0 — regenerate/re-copy after upgrading gstack
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
+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 you test the site, API or CLI and fix bugs.
- [/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 you review this PR's diff.
- [/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
+29 -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,13 @@ 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 (`none: <why>` where the skill's own stop rule leaves one empty).
- `check-after-last-edit`: a test or repro command run after your last edit.
## 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
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.94.17",
"version": "1.94.18",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
+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 you test the site, API or CLI and fix bugs. (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 you test the site, API or CLI and fix bugs.
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),
+25 -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 you review this PR's diff. (gstack)
allowed-tools:
- Bash
- Read
@@ -15,13 +15,37 @@ allowed-tools:
- WebSearch
triggers:
- review this pr
- look over my diff
- code review
- check my diff
- review my changes
- 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 (none when a small diff skips them).
- `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 (`none: <why>` where the skill's own stop rule leaves one empty).
## 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.
+23 -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 you review this PR's diff.
Checks it for SQL safety, LLM trust boundaries, conditional side effects and
bugs that pass CI and break production. Fits "look over my diff", "review my
changes", "code review", "check my diff" or "pre-landing review".
Proactively suggest before a merge. (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,29 @@ allowed-tools:
- WebSearch
triggers:
- review this pr
- look over my diff
- code review
- check my diff
- review my changes
- 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}}
+142
View File
@@ -0,0 +1,142 @@
/**
* {{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';
import { getHostConfig } from '../../hosts/index';
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'],
};
// The resolver and section that render the step each kind checks today. A host that suppresses the resolver, or a loadout that excludes the section, never shows that step, so its items are left out of the list there.
const BACKED_BY: Partial<Record<ContractItem['kind'], { resolver: string; section: string }>> = {
selected_specialists_dispatched: { resolver: 'REVIEW_ARMY', section: 'review-army' },
agent_dispatch: { resolver: 'ADVERSARIAL_STEP', section: 'adversarial' },
};
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 (' + code('none: <why>') + ' where the skill\'s own stop rule leaves one empty)';
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 (none when a small diff skips them)';
case 'agent_dispatch': return 'an Agent dispatch for the ' + item.id.replace(/-/g, ' ') + ', its prompt including "' + item.match + '"';
}
}
/** Whether bin/gstack-skill-contract ships. The PR that adds it flips this, regenerates and re-pins; test/skill-descriptions.test.ts fails while the two disagree. */
export const CONTRACT_CHECKER_SHIPPED = false;
/** The list; checker adds the check and waive commands, which only a tree that ships the checker may mention. */
export function renderDoneMeans(contract: SkillContract, checker = CONTRACT_CHECKER_SHIPPED): string {
if (contract.items.length === 0) return '';
return [
'## Done means',
'',
'Finish only when each of these exists (contract v1):',
'',
...contract.items.map(item => '- ' + code(item.id) + ': ' + plainWords(item) + '.'),
...(checker ? ['', 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('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 })!;
const suppressed = new Set(getHostConfig(ctx.host).suppressedResolvers ?? []);
const shown = (item: ContractItem) => {
const backing = BACKED_BY[item.kind];
return !backing || (!suppressed.has(backing.resolver) && !ctx.modules?.excluded.has('section:' + ctx.skillName + '/' + backing.section));
};
return renderDoneMeans({ ...contract, items: contract.items.filter(shown) });
}
+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": 104488,
"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": 104011,
"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": 104069,
"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": 122541,
"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": 126314,
"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": 103891,
"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": 122365,
"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": 123163,
"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": 122453
}
}
+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_774, // 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,774. 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.3261, // + 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 143,907 / 108,523 = 1.3261. 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',
+228
View File
@@ -0,0 +1,228 @@
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 { CONTRACT_CHECKER_SHIPPED, contractFromTemplate, generateContractDoneMeans, parseSkillContract, renderDoneMeans } from '../scripts/resolvers/contract-done-means';
import { HOST_PATHS } from '../scripts/resolvers/types';
/**
* 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)), 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 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, true);
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 (' + BT + 'none: <why>' + BT + " where the skill's own stop rule leaves one empty).");
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 (none when a small diff skips them).');
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 + '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('check and waive appear only once bin/gstack-skill-contract ships', () => {
const shipped = existsSync(join(ROOT, 'bin', 'gstack-skill-contract'));
expect(CONTRACT_CHECKER_SHIPPED, 'CONTRACT_CHECKER_SHIPPED in scripts/resolvers/contract-done-means.ts must be ' + shipped + ' while bin/gstack-skill-contract ' + (shipped ? 'exists' : 'is missing') + '. Fix: set it, then bun run gen:skill-docs').toBe(shipped);
for (const skill of ['investigate', 'review']) {
expect(read(skill + '/SKILL.md').includes('gstack-skill-contract'), skill + '/SKILL.md names gstack-skill-contract while bin/gstack-skill-contract ' + (shipped ? 'exists' : 'is missing') + '. Fix: bun run gen:skill-docs').toBe(shipped);
}
if (shipped) expect(existsSync(join(ROOT, 'docs', 'skill-routing-and-contracts.md')), 'docs/skill-routing-and-contracts.md is missing').toBe(true);
expect(renderDoneMeans(all, false)).not.toContain('gstack-skill-contract');
});
test('a host that suppresses, or a loadout that excludes, the step an item checks leaves that item out', () => {
const render = (host: string, excluded: string[] = []) => generateContractDoneMeans({ skillName: 'review', tmplPath: join(ROOT, 'review', 'SKILL.md.tmpl'), host, paths: HOST_PATHS[host], modules: excluded.length ? { name: 'test', excluded: new Set(excluded) } : undefined } as unknown as Parameters<typeof generateContractDoneMeans>[0]);
const ids = (host: string, excluded: string[] = []) => [...render(host, excluded).matchAll(/^- \u0060([a-z-]+)\u0060:/gm)].map(m => m[1]);
expect(ids('claude', ['section:review/review-army', 'section:review/adversarial'])).toEqual(['review-report']);
expect(ids('claude')).toEqual(['specialists-dispatched', 'adversarial-pass', 'review-report']);
expect(ids('codex')).toEqual(['adversarial-pass', 'review-report']);
expect(ids('hermes')).toEqual(['review-report']);
expect(renderDoneMeans({ version: 1, items: [] })).toBe('');
});
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