mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 02:30:39 +01:00
* feat: retire /investigate after a no-gain eval (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(skill-start): core routing offer drops every non-core rule line (GST-1717)
The retired /investigate rule became 'Bugs/errors → fix them directly', which no
longer matches '→ invoke /', so proactive=core leaked it into the offered section.
Match any '- … → ' rule line.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* revert(review): keep review/checklist.md at its no-loss-pinned bytes (GST-1717)
The pin names replay GSTA-224 H1; a reviewer checklist edit merges only after a
passing no-loss replay (crew rule 15). The /investigate flake line stays until a
replayed repin.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: repair free-suite fallout from retiring /investigate (GST-1717)
CI run 42340956269 at 83a8a9a: drop fixtures, ledger rows, allowlist entries and
counts for the retired skill and its deleted paid files; record the harness
change as non-behavioral; the test-of-test detector accepts path.join(ROOT, 'bin');
uncovered-prompt fixtures use a tracked prompt, since an untracked path now
reads as a deleted one.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(relink): back up a retired skill's real SKILL.md before cleaning it (GST-1717)
With the skill's source gone, the customized-file check never ran, so a
banner-carrying local copy of investigate/SKILL.md was deleted instead of moved
to the backup root (red-team review, pass 1).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(qm): restore the review checklist's flake-line rewrite (GST-1717)
review/checklist.md keeps its pinned /investigate line, so the QM edition needs
its replaceText rule back; qm/ stays fresh against the generator.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): name the setup flag break, the relink backup and the pinned checklist line (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(setup,relink): back up a retired skill's real SKILL.md instead of deleting it (GST-1717)
Scoped fix round under grant a-360dd62bd46a (outside-voice blocking finding and
adversarial #1, #4). setup's prune compares a copied host entry with the render
before removing that render, and backs up a copy that differs or whose render is
gone; relink no longer takes the marker's rm -rf for a skill whose source is
gone, so a real SKILL.md is backed up and the user's files stay. setup reports a
failed remove-source of the retired gate hooks on stderr.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pr-profile): a changed prompt counts as deleted only when it is gone from disk (GST-1717)
An untracked prompt file on disk still needs prompt validation (targeted review
finding); harness change recorded as non-behavioral.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): exact backup wording, the hook-removal warning and GST-1744 (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(setup): one backup folder per host for a retired skill's copies (GST-1717)
Under grant a-c95576b8b1f9 (fix-check#1): every copy host moved its
gstack-investigate/SKILL.md to the same backup path, so a later host's copy
overwrote an earlier host's edits. A sandboxed ./setup --host factory run showed
gen-skill-docs removes the retired render before the prune, so every real copy
is backed up; each host now gets its own folder.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): say exactly what setup backs up for a retired skill (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): end the two colon sentences prose-lint flagged (GST-1717)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(retired-skills): name plan-eng-review once behind a pregate-literal waiver (GST-1717)
pregate's literals check read the GSTACK_RETIRED_SKILLS change as an old
value still in the tests. plan-eng-review is still retired; investigate was
appended. The fixture names it once, with the waiver.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
80 lines
4.2 KiB
TypeScript
80 lines
4.2 KiB
TypeScript
/**
|
|
* Router failure routing (#3157, #3158). The gstack router checks a reported
|
|
* failure's cause before it fixes it: a user-supplied cause that one read-only
|
|
* check confirms is fixed directly, and an unknown cause gets one bounded
|
|
* triage pass. Anything still unverified gets a focused failing test first.
|
|
*
|
|
* Two fixtures, one per issue (verified cause, triage that resolves it): each
|
|
* must fix the bug directly, without routing to a skill, and leave the suite
|
|
* green after one to four read-only calls. The two /investigate fallback cases
|
|
* retired with that skill (GST-1717). The agent sees the rendered router section, sliced from
|
|
* SKILL.md; GSTACK_ROUTER_ARM=before slices the base branch's router instead.
|
|
* GSTACK_ROUTER_ARM=none is the descriptive no-gstack arm: the request alone,
|
|
* no installed skills, scored on plain metrics (suite green, edits before the
|
|
* first read, read-only calls before the first edit) recorded as observations.
|
|
* Fixtures and scoring live in test/helpers/failure-routing-fixture.ts.
|
|
*/
|
|
import { afterAll, expect } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { runSkillTest } from './helpers/session-runner';
|
|
import {
|
|
runId, describeIfSelected, testConcurrentIfSelected, logCost, recordE2E,
|
|
createEvalCollector, finalizeEvalCollector,
|
|
} from './helpers/e2e-helpers';
|
|
import { CAPTURE_MS } from './helpers/eval-budgets';
|
|
import { bunIsolationArgs } from '../lib/bun-isolation';
|
|
import { FIXTURES, makeRepo, plainMetrics, routerArm, routerPrompt, routing } from './helpers/failure-routing-fixture';
|
|
import { recordObservation } from './helpers/none-arm';
|
|
|
|
const collector = createEvalCollector('e2e-failure-routing');
|
|
const ARM = routerArm();
|
|
const SCORE_GRACE_MS = 10_000;
|
|
|
|
describeIfSelected('Router failure routing (#3157, #3158)', FIXTURES.map(f => f.name), () => {
|
|
const dirs: string[] = [];
|
|
afterAll(() => {
|
|
for (const dir of dirs) fs.rmSync(dir, { recursive: true, force: true });
|
|
finalizeEvalCollector(collector);
|
|
});
|
|
|
|
for (const fixture of FIXTURES) {
|
|
testConcurrentIfSelected(fixture.name, async () => {
|
|
const repo = makeRepo(fixture, ARM);
|
|
dirs.push(repo);
|
|
const result = await runSkillTest({
|
|
prompt: routerPrompt(fixture, ARM),
|
|
workingDirectory: repo,
|
|
maxTurns: 6,
|
|
tools: ['Skill', 'Read', 'Grep', 'Glob', 'Edit', 'Bash'],
|
|
allowedTools: ['Skill', 'Read', 'Grep', 'Glob', 'Edit', 'Bash'],
|
|
timeout: CAPTURE_MS - SCORE_GRACE_MS,
|
|
testName: fixture.name,
|
|
runId,
|
|
});
|
|
logCost(`router ${fixture.name} (${ARM})`, result);
|
|
const suite = spawnSync(process.execPath, [...bunIsolationArgs(), 'test'], { cwd: repo, encoding: 'utf8', timeout: 60_000 });
|
|
const green = suite.status === 0;
|
|
if (ARM === 'none') {
|
|
const p = plainMetrics(result.toolCalls);
|
|
const plain = `arm=none reads_before_edit=${p.readsBeforeEdit} edited_before_read=${p.editedBeforeRead} green=${green} tools: ${p.sequence}`;
|
|
recordE2E(collector, fixture.name, 'Router failure routing', result, { passed: true, output: plain });
|
|
recordObservation(fixture.name, green, `suite green after the run: ${plain}`);
|
|
recordObservation(fixture.name, !p.editedBeforeRead, `no edit before the first read-only call: ${plain}`);
|
|
recordObservation(fixture.name, p.readsBeforeEdit >= 1, `at least one read-only call before the first edit: ${plain}`);
|
|
return;
|
|
}
|
|
const r = routing(result);
|
|
const summary = `arm=${ARM} route=${r.skillRouted ? 'skill' : 'direct'} reads=${r.readsBeforeRoute} green=${green} tools: ${r.sequence}`;
|
|
const bounded = r.readsBeforeRoute >= 1 && r.readsBeforeRoute <= 4;
|
|
const passed = bounded && !r.skillRouted && green;
|
|
recordE2E(collector, fixture.name, 'Router failure routing', result, { passed, output: summary });
|
|
|
|
expect(r.skillRouted, summary).toBe(false);
|
|
expect(green, `suite still red after the direct fix: ${summary}\n${suite.stdout}${suite.stderr}`).toBe(true);
|
|
expect(r.readsBeforeRoute, `no read-only check before routing: ${summary}`).toBeGreaterThanOrEqual(1);
|
|
expect(r.readsBeforeRoute, `triage was not bounded: ${summary}`).toBeLessThanOrEqual(4);
|
|
}, CAPTURE_MS);
|
|
}
|
|
});
|