mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
* feat(ship): GSTA-167 turn levers on the restored reviewers: ship preflight/release bins, own-jam meter, dispatch-pair metering, ship/DRIVER.md
Ports the non-reviewer parts of #3633 (gsta-167-pr34) and #3637 (gsta-167-pr5 @2914400)
onto main ae3d58c (v1.91.331.0). Reviewer briefs, checklists, the fix-check prompt and
the depth rules are byte-identical to main and now pinned in
test/review-packets-removed.test.ts.
Left out: gstack-review-packet and its libs, parallel/batched fanout, --like, collect,
the audit prompt split, the docs-audit packet, review-army/adversarial/outside-voice
edits, the gate-scan summary-only block and the review-depth scope-error change.
Packet-bytes reader pricing is dropped from gstack-ship-budget; handoff READ ranges
send each section whole; the driver runs the audit, review, adversarial and
documentation sections as written.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(context-budget): raise the capy/skill/gstack eager ceiling to its captured value after the main merge (own-jam meter, driver and reviewer-batch prose, GSTA-167)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(ship-driver): name ship/DRIVER.md.tmpl so the pregate maps the template to its test lane (GSTA-167)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-driver): keep the skill generator off lib/ship-budget so a minimal setup checkout still renders (GSTA-167)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(windows): keep test/ship-release.test.ts off the Windows subset; it launches the shebang bin and sh stubs directly (GSTA-167)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(ship): variant E orchestration: one program per review pass and Step 14.5 audit, the review child starts at Step 9, a metered dispatch:docs role, and the driver's record headings (GSTA-167)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(ship): variant G orchestration: one call per driver phase (9 for a clean pass, at most 12), gstack-ship-preflight --audit for single reads and deterministic audits, an observe-mode ledger with note and credits, 240 s wait pings, and small-model audit readers (GSTA-167)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-budget): a Capy handoff child needs no jam id: verify-handoff with no reading makes it parent-metered (METER: parent), it never delegates on an unknown charge, and reconcile charges the parent's counter delta (GSTA-167 replay 7e)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship): a poll settles a reader only at done, failed or a final summary, never a fresh idle; return settles the child's open step reservation (GSTA-167 replay 7e)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-budget): return releases an open step reservation under the estimate meter instead of booking its projection; measured meters still settle it (GSTA-167 replay 7e)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(review): a clean fix-check after below-floor fixes is done; Step 11 and fix-check below-floor findings are notes at pass 1
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(ship): verbatim reviewer briefs and loss-free collection (gstack-review-dispatch render/sent/collect/judge/verify)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pre-landing review fixes
- verify requires each output's brief rendered and sent verbatim, reruns judge
against the recorded judged.jsonl hash, and flags outputs collected after judge
- judge drops self-folds, fold cycles and chains
- prose findings parse a leading label or a label heading, not any severity word
- releasePaths: manifests count as release metadata only for version-only edits,
lockfiles never; TODOS.md stays out of lane-evidence exemptions
- push stops when the sealed-output guard did not run
- ship-audit reads the test tree once instead of one git grep per file
- drop the uncalled --delegate measured mode; doc/help step-number fixes
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pre-landing review fixes (pass 2)
- collect keeps a finding wherever its label stands (headings, table rows,
mid-line, Informational and upper-case labels); only top-level list items
inherit a label heading, and any heading or bold-only line ends it
- judge treats a merged file missing any collected id as a drop; verify flags
merged_ids_missing and a sent reader never collected (NOT_COLLECTED)
- finish computes release paths after --build, so a build that rewrites
package.json is a build-input change
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(review-dispatch): verify per render, voided superseded mismatches
- verify treats each render as one dispatch of its reader, so a two-pass
ledger compares pass-1 sends with pass-1 briefs (later renders print as
<reader>@2); outputs bind to the latest send of their own render
- a mismatched send re-sent byte-exact before anything was collected is void
and counted (voided=N on the verdict line); one that produced an output
stays MISMATCH
- an indented bold sub-line no longer ends a label section (fix-check)
- docs: a failed reader is collected from its failure text
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(review-dispatch): only the newest render of a path checks its file; absolute ledger paths
- a later render into the same briefs directory overwrites the earlier brief by
design, so verify checks a brief file only for the newest render of that path
- collect and judge record absolute output, merged and judged paths, so verify
reads them from any directory
- docs: a failed reader's failure text is parsed like any output
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: changelog and TODOS
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-release): the changelog_fragments fragment is release metadata
Fix-check 4 (CRITICAL) after the main merge: releasePaths omitted
<changelog_fragments.dir>/<branch-slug>.md, so finish stopped on the
fragment Step 13 writes under gstack's policy, or routed it to re-review.
version now names the fragment in its Step 13 NEXT line instead of
[Unreleased], DRIVER.md Step 13 writes and lints where NEXT says, and the
preflight POLICY line says Steps 12-16.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: changelog and TODOS
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>
77 lines
5.7 KiB
TypeScript
77 lines
5.7 KiB
TypeScript
/**
|
|
* Skill structure for the /ship budget (GSTA-152 plan items 5 and 9): the
|
|
* budget-handoff section is registered, the skeleton points at it and names the
|
|
* start/check/close calls, every step id gstack-ship-budget accepts is a ship
|
|
* step, and the dispatch sections pass a reservation into gstack-review-log.
|
|
*/
|
|
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { DISPATCH_ROLES, SHIP_STEPS } from '../lib/ship-budget';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const read = (rel: string) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
const templates = ['ship/SKILL.md.tmpl', ...fs.readdirSync(path.join(ROOT, 'ship/sections')).filter(f => f.endsWith('.md.tmpl')).map(f => `ship/sections/${f}`)];
|
|
|
|
describe('ship budget skill structure', () => {
|
|
test('budget-handoff is registered in the manifest and read from the skeleton', () => {
|
|
const manifest = JSON.parse(read('ship/sections/manifest.json'));
|
|
const entry = manifest.sections.find((s: any) => s.id === 'budget-handoff');
|
|
expect(entry).toMatchObject({ file: 'budget-handoff.md' });
|
|
expect(fs.existsSync(path.join(ROOT, 'ship/sections/budget-handoff.md.tmpl'))).toBe(true);
|
|
const skeleton = read('ship/SKILL.md.tmpl');
|
|
expect(skeleton).toContain('sections/budget-handoff.md');
|
|
for (const call of ['gstack-ship-budget start', 'check', 'close', '--token-cap', '--resume', '--continue', 'verdict=']) expect(skeleton).toContain(call);
|
|
});
|
|
|
|
test('every step id the budget accepts is a step the ship templates define', () => {
|
|
const text = templates.map(read).join('\n');
|
|
for (const step of SHIP_STEPS) expect(text, `Step ${step}`).toMatch(new RegExp(`Step ${step.replace('.', '\\.')}(?![\\d.])`));
|
|
});
|
|
|
|
test('the budget section names only valid steps and roles in its commands', () => {
|
|
const text = read('ship/sections/budget-handoff.md.tmpl');
|
|
for (const m of text.matchAll(/--(?:step|from|to) ([\d.]+)/g)) expect(SHIP_STEPS as readonly string[]).toContain(m[1]!);
|
|
for (const m of text.matchAll(/--next dispatch:([a-z,-]+)/g)) for (const role of m[1]!.split(',')) expect(DISPATCH_ROLES as readonly string[]).toContain(role);
|
|
});
|
|
|
|
// Value: protects=the budget section's dispatch program starts the review log once per logged row on the batch's reservation, and a later batch of the same pass (the Red Team, an ADMIT remainder) takes no start, so no start token is left unfinished; fails_when=the program starts once per role or tells every batch to start;
|
|
// why_new=main's tests only check that each review file mentions --reservation; seam=none; no_claim=the agent follows the order
|
|
test('the dispatch program starts once per logged row on its reservation, and a later batch of the pass takes no start', () => {
|
|
const handoff = read('ship/sections/budget-handoff.md.tmpl');
|
|
expect(handoff).toContain('`gstack-review-log --start <skill> --reservation <id>` once per\nrow the step logs');
|
|
expect(handoff).not.toContain('once per role on the printed reservation');
|
|
expect(handoff).toMatch(/later batch of the same pass \(the Red Team or an ADMIT remainder\) takes no start/);
|
|
});
|
|
|
|
// Value: protects=every concrete check command in the ship sections passes the counter, since on the Capy meter a check without --reading exits 2 and the dispatch it gates never starts (#3633 pass 2); fails_when=a section spells `gstack-ship-budget check --run` without `--reading`;
|
|
// why_new=no test reads the check commands; seam=none; no_claim=the agent fills the reading
|
|
test('every gstack-ship-budget check --run command in the ship sections passes --reading', () => {
|
|
for (const rel of fs.readdirSync(path.join(ROOT, 'ship/sections')).filter(n => n.endsWith('.md.tmpl'))) {
|
|
for (const cmd of read(`ship/sections/${rel}`).match(/gstack-ship-budget check --run [^`]*/g) ?? []) {
|
|
expect(cmd, rel).toContain('--reading');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Value: protects=the prose that takes a reservation id reads the RESERVATION_ID: line, never a field cut from RESERVATION:; fails_when=the budget section or the Capy skill names the id without that line;
|
|
// why_new=the line is new; seam=none; no_claim=the agent copies it
|
|
test('the budget section and the Capy skill read the id from the RESERVATION_ID: line', () => {
|
|
for (const rel of ['ship/sections/budget-handoff.md.tmpl', 'capy/skill/gstack/SKILL.md']) {
|
|
expect(read(rel), rel).toContain('`RESERVATION_ID:`');
|
|
}
|
|
});
|
|
|
|
// Value: protects=the budget section's Capy meter reads tokens and credits in the program that runs the command, never spends a call only to read the meter, and points a driver run at observe mode; fails_when=the section asks for a per-call meter read again (budget-handoff.md:12-13 at b617445: 11 meter-only calls at 286k each on #34's child), drops --credits, or loses the observe pointer;
|
|
// why_new=variant G; seam=none; no_claim=the agent's adherence
|
|
test('the budget section reads credits beside tokens, never spends a call on the meter, and points driver runs at observe mode', () => {
|
|
const text = read('ship/sections/budget-handoff.md.tmpl').replace(/\s+/g, ' ');
|
|
for (const s of ['--reading <cost.tokens> --credits <cost.credits>', 'never spend a call only to read the meter', 'A `ship/DRIVER.md` run starts `--mode observe` instead: no check, reservation or handoff; each program ends with `note`']) expect(text).toContain(s);
|
|
expect(read('docs/ship-budget.md')).toContain('## Observe mode (ship/DRIVER.md)');
|
|
});
|
|
|
|
test('every review-log --start in the dispatch sections takes a reservation when a cap is set', () => {
|
|
for (const rel of ['ship/sections/review-army.md.tmpl', 'ship/sections/adversarial.md.tmpl']) expect(read(rel)).toContain('--reservation');
|
|
});
|
|
});
|