Files
gstack/test/context-remaining-work-provenance.test.ts
Garry TanandMatt Van Horn fe6d1ae62a v1.91.17.0 fix: mvanhorn wave (checkpoint provenance, never-overwrite design images) (#3022)
* fix: mark Remaining Work provenance so restore verifies guessed steps first (#3004)

/context-save now starts every Remaining Work item with `Open.` and ends it
with how this session knows it: `(path run)` with the observed outcome,
`(path read)` or `(path assumed)` for concrete paths, and
`(target state checked)`, `(code read)` or `(path assumed)` for writing
steps. /context-restore keeps saved order and original text but splits the
list into Next steps and Verify first (assumed, code-read, failed runs and
unmarked concrete or writing items), prints a banner for pre-marker
checkpoints, and option A walks saved order so an unverified first item is
verified before any later step runs.

Adds a prompt-byte contract test for the marker wire format and a periodic
behavior E2E (context-restore-provenance-order) that seeds an assumed item
ahead of a run item plus a code-read write.

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>

* fix(design): never overwrite a paid design image (#1529)

Every image the API returns now goes through one persistence path
(design/src/persist.ts): mkdir, exclusive `wx` claim that bumps
x.png -> x-2.png ... x-999.png, write, fsync, close. A failure after the
bytes arrived removes the claimed file and keeps a 0600 recovery copy in
the temp dir; empty image data is a failure, never a 0-byte PNG. The API
call is the only thing inside each retry loop, so a local save failure
never buys a second image (variants, iterate fallback, generate --retry).

generate, iterate, evolve and variants (both branches) print the same run
fields (requested, saved, selected, failures, recovered) on every exit,
with exit 0 / 2 (nothing saved) / 3 (stopped after saving). Each
generate --retry attempt is its own saved image listed in `attempts`.
compare accepts --images-file and JSON-array --images. The gallery lists
bumped names, orders history by mtime then board-images.json, and marks
approvals by approved_path. New bin/gstack-design-claim publishes staged
images without clobbering; bin/gstack-design-approved resolves an
approval record (legacy letter-only records still resolve; a missing
approved_path is an error, never a substitute).

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>

* fix(design skills): read the paths $D printed, never fixed variant names (#1529)

Every design flow now captures each generating $D command's JSON and exit
code without set -e, does round accounting before the first board, check
or inline view (stopping when nothing was saved), and carries the printed
paths forward. Boards are built from board-images.json via
compare --images-file after archiving any earlier feedback.json, and
approvals map the confirmed letter through that file and record
approved_path. design-shotgun subagents generate into a per-run mktemp
staging directory and publish with bin/gstack-design-claim instead of a
shared /tmp/variant-X.png plus cp. design-html and taste memory resolve
approvals with gstack-design-approved; design-review verifies against the
printed target path.

test/design-printed-paths.test.ts bans fixed variant names, variant globs,
variant-{letter} reads, cp/mv of $D output and literal check paths in the
generated skills and resolvers (one content-anchored allowlist entry: the
design-html discovery probe), and pins the accounting/board marker order.
test/design-approval-flow.test.ts runs the generated board and approval
blocks through a stubbed two-round flow.

* chore: release v1.91.17.0 (mvanhorn fix wave: checkpoint provenance, never-overwrite design images)

* fix(design): keep iterate sessions in the OS temp dir and normalize tripwire paths on Windows

* fix(setup-deploy): report failing status commands, specify merge-method, health and workflow checks

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-10-03 16:13:45 -07:00

64 lines
3.4 KiB
TypeScript

/**
* Remaining Work provenance (#3004): prompt-byte contract for the shipped
* context-save and context-restore SKILL.md files.
*
* The markers are a wire format between two skills: /context-save writes them
* into a checkpoint and a later /context-restore sorts on them. Only that
* contract is pinned (marker tokens, group headings, banner, option-A order),
* not the surrounding prose. Behavior is covered by the periodic E2E case
* context-restore-provenance-order.
*
* Value: protects=save writes the five provenance markers and restore sorts them into Next steps and Verify first; fails_when=a marker, a group or the saved-order option A is dropped; why_new=no test reads Remaining Work; seam=none
*/
import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..');
const MARKERS = ['(path run)', '(path read)', '(path assumed)', '(code read)', '(target state checked)'];
function read(rel: string): string {
return fs.readFileSync(path.join(ROOT, rel), 'utf-8');
}
function between(text: string, start: string, end: string): string {
const from = text.indexOf(start);
expect(from, `missing ${start}`).toBeGreaterThanOrEqual(0);
const to = text.indexOf(end, from + start.length);
expect(to, `missing ${end} after ${start}`).toBeGreaterThan(from);
return text.slice(from, to);
}
describe('Remaining Work provenance contract', () => {
test('context-save marks every item Open. and defines all five markers, writing steps first', () => {
const save = read('context-save/SKILL.md');
const table = between(save, '**Remaining Work provenance.**', 'If the user provided a title');
for (const marker of MARKERS) expect(table).toContain(`\`${marker}\``);
expect(table).toContain('`Open.`');
expect(table).toContain('`[done]`');
expect(table.indexOf('a writing step')).toBeLessThan(table.indexOf('names a concrete path'));
const format = between(save, '### Remaining Work', '### Notes');
expect(format).toContain('`Open.`');
});
test('context-restore sorts into Next steps and Verify first and option A walks saved order', () => {
const restore = read('context-restore/SKILL.md');
expect(restore).not.toContain('{remaining work items}');
const rules = between(restore, '**Sort Remaining Work by provenance.**', 'Read the chosen file');
const verifyFirst = between(rules, 'Put an item under **Verify first**', 'Every other item');
for (const marker of ['(path assumed)', '(code read)']) expect(verifyFirst).toContain(`\`${marker}\``);
expect(verifyFirst).toContain('reports a failure');
expect(verifyFirst).toContain('has no marker');
const nextSteps = between(rules, 'Every other item goes under **Next steps**', 'Verifying means');
for (const marker of ['(path run)', '(path read)', '(target state checked)']) expect(nextSteps).toContain(`\`${marker}\``);
expect(rules).toContain(
'`This checkpoint predates provenance markers; items naming commands, paths or writes are listed under Verify first.`',
);
const summary = between(restore, '### Remaining Work', '### Notes');
expect(summary.indexOf('Next steps')).toBeLessThan(summary.indexOf('Verify first'));
const optionA = between(restore, 'If A,', '## If no saved contexts exist');
expect(optionA).toContain('first Remaining Work item in saved order');
expect(optionA).toContain('under Verify first, suggest verifying it');
});
});