mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 22:45:02 +01:00
* 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>
53 lines
2.8 KiB
TypeScript
53 lines
2.8 KiB
TypeScript
/**
|
|
* `$D compare` image list input: --images-file and JSON arrays carry any path
|
|
* losslessly; comma and glob input keep working.
|
|
*/
|
|
|
|
import { afterEach, beforeEach, expect, test } from "bun:test";
|
|
import { spawnSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { resolveImagePaths } from "../src/image-args";
|
|
|
|
let dir: string;
|
|
beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), "design-image-args-")); });
|
|
afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); });
|
|
|
|
// Value: protects=a path containing a comma and a space reaches compare intact via --images-file; fails_when=the file is split on commas;
|
|
// why_new=Lossless path handoff is new; seam=none
|
|
test("--images-file keeps a comma+space path and builds the board", () => {
|
|
const odd = path.join(dir, "round 2, final");
|
|
fs.mkdirSync(odd);
|
|
const images = [path.join(odd, "variant-A-2.png"), path.join(odd, "variant-B.png")];
|
|
for (const img of images) fs.writeFileSync(img, Buffer.from("iVBORw0KGgo=", "base64"));
|
|
const list = path.join(odd, "board-images.json");
|
|
fs.writeFileSync(list, JSON.stringify(images));
|
|
const board = path.join(odd, "board.html");
|
|
const r = spawnSync(process.execPath, ["--no-env-file", path.join(import.meta.dir, "..", "src", "cli.ts"),
|
|
"compare", "--images-file", list, "--output", board], { encoding: "utf8", timeout: 30_000, env: { PATH: process.env.PATH!, HOME: dir } });
|
|
expect(r.status, r.stderr).toBe(0);
|
|
const html = fs.readFileSync(board, "utf8");
|
|
expect(html).toContain('data-variant="A"');
|
|
expect(html).toContain('data-variant="B"');
|
|
expect(html).not.toContain('data-variant="C"');
|
|
});
|
|
|
|
// Value: protects=JSON-array --images input and the unchanged comma form; fails_when=a `[`-prefixed literal path is misparsed or comma input changes;
|
|
// why_new=new input form; seam=none
|
|
test("--images: JSON array only when it parses; otherwise comma and literal behavior is unchanged", async () => {
|
|
expect(await resolveImagePaths('["/a, b/x.png","/c.png"]')).toEqual(["/a, b/x.png", "/c.png"]);
|
|
expect(await resolveImagePaths("[draft]/x.png")).toEqual(["[draft]/x.png"]);
|
|
expect(await resolveImagePaths("/a.png, /b.png")).toEqual(["/a.png", "/b.png"]);
|
|
expect(await resolveImagePaths("[1,2]")).toEqual(["[1", "2]"]);
|
|
});
|
|
|
|
// Value: protects=a malformed --images-file fails loudly instead of building an empty board; fails_when=parse errors are swallowed;
|
|
// why_new=new flag; seam=none
|
|
test("--images-file must hold a JSON array of strings", async () => {
|
|
const bad = path.join(dir, "bad.json");
|
|
fs.writeFileSync(bad, '{"paths": []}');
|
|
await expect(resolveImagePaths(undefined, bad)).rejects.toThrow("JSON array");
|
|
await expect(resolveImagePaths(undefined, path.join(dir, "none.json"))).rejects.toThrow("cannot read --images-file");
|
|
});
|