Files
gstack/test/ship-budget-section.test.ts
T
Garry Tanandcapy-ai[bot] c85dd0a799 v1.91.316.0 ship: token cap with real meters and fresh-context delegation (GSTA-152 PR A) (#3593)
* ship: token cap with real meters and fresh-context delegation (GSTA-152 PR A)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (stopped or lost capped run no longer blocks uncapped review starts; short child return reconciles incomplete)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: carry estimate spend across a meter epoch change; calibration docs match their evidence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: load the committed calibration file by its repo path so pregate maps it to a lane

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ship: fix fence placeholders and register claude-usage fixture (GSTA-152 PR A)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: run ship-budget and review-log bins through their interpreters so Windows can launch them

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>
2026-10-29 10:29:23 +00:00

42 lines
2.4 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);
});
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');
});
});