diff --git a/.changeset/clever-voles-swim.md b/.changeset/clever-voles-swim.md new file mode 100644 index 000000000..d5adcdf15 --- /dev/null +++ b/.changeset/clever-voles-swim.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2318 +--- +**Deferred out-of-scope findings logged to `deferred-items.md` are now surfaced** — the executor's SCOPE BOUNDARY convention writes discoveries to a phase directory's `deferred-items.md`, but nothing read it back, so those items were permanently invisible. `/gsd-progress`'s forensic audit and `audit-uat` now glob `.planning/phases/*/deferred-items.md` and surface unresolved entries. (#2287) diff --git a/gsd-core/workflows/progress.md b/gsd-core/workflows/progress.md index 49ca1dbd1..9bedfe0e3 100644 --- a/gsd-core/workflows/progress.md +++ b/gsd-core/workflows/progress.md @@ -264,6 +264,7 @@ Track: `outstanding_debt` — `summary.total_items` from the audit. |-------|------|-------| | {phase} | {filename} | {pending_count} pending, {skipped_count} skipped, {blocked_count} blocked | | {phase} | {filename} | human_needed — {count} items | +| {phase} | {filename} | {unresolved_count} deferred items | Review: `/gsd:audit-uat ${GSD_WS}` — full cross-phase audit Resume testing: `/gsd:verify-work {phase} ${GSD_WS}` — retest specific phase @@ -675,7 +676,7 @@ If `--forensic` IS present: after the standard report and routing suggestion hav ## Forensic Integrity Audit -Running 6 deep checks against project state... +Running 7 deep checks against project state... Run each check in order. For each check, emit ✓ (pass) or ⚠ (warning) with concrete evidence when a problem is found. @@ -748,11 +749,24 @@ Emit: - ✓ `Working tree clean` — if no modified files outside `.planning/` - ⚠ `Uncommitted changes in source files` — list up to 10 file paths +**Check 7 — Unresolved deferred items** + +Glob every phase directory's SCOPE BOUNDARY log (executor writes out-of-scope discoveries here per `agents/gsd-executor.md`): +```bash +ls .planning/phases/*/deferred-items.md 2>/dev/null || true +``` + +For each `deferred-items.md` found, read its entries (bullet list, one entry per top-level `- ` line, continuation lines indented beneath it). An entry is RESOLVED only if it carries an explicit `status: resolved` field (case-insensitive) on one of its lines; every other entry — including one with no `status:` field at all — is UNRESOLVED and must be surfaced (fail-safe: never silently drop a possibly-open item). + +Emit: +- ✓ `No unresolved deferred items` — if no `deferred-items.md` files exist, or every entry in every file is `status: resolved` +- ⚠ `Unresolved deferred items found` — list each file's phase directory and its unresolved entry text (max 5 per file, truncated at 80 chars) + --- -After all 6 checks, display the verdict: +After all 7 checks, display the verdict: -**If all 6 checks passed:** +**If all 7 checks passed:** ``` ### Verdict: CLEAN @@ -773,6 +787,7 @@ Then for each failed check, add a concrete next action: - Check 4 (memory pending): `Review the flagged memory entries and resolve or clear them` - Check 5 (blocking todos): `Complete the operational steps in .planning/todos/pending/ before continuing` - Check 6 (uncommitted code): `Commit or stash the uncommitted changes before advancing` +- Check 7 (unresolved deferred items): `Address each deferred item and mark it status: resolved in its deferred-items.md, or fold it into the roadmap` - Check 1 (STATE inconsistency): `Run /gsd:verify-work ${PHASE} ${GSD_WS} to reconcile state` diff --git a/src/uat.cts b/src/uat.cts index 5d3b00ec8..c8c6f6e24 100644 --- a/src/uat.cts +++ b/src/uat.cts @@ -40,7 +40,7 @@ import { requireSafePath, sanitizeForDisplay } from './security.cjs'; // ─── Types ──────────────────────────────────────────────────────────────────── type UatResult = string; -type UatCategory = 'server_blocked' | 'device_needed' | 'build_needed' | 'third_party' | 'blocked' | 'skipped_unresolved' | 'pending' | 'human_uat' | 'unknown'; +type UatCategory = 'server_blocked' | 'device_needed' | 'build_needed' | 'third_party' | 'blocked' | 'skipped_unresolved' | 'pending' | 'human_uat' | 'unknown' | 'deferred'; interface UatItem { test?: number; @@ -57,7 +57,7 @@ interface UatFileResult { phase_dir: string; file: string; file_path: string; - type: 'uat' | 'verification'; + type: 'uat' | 'verification' | 'deferred'; status: string; items: UatItem[]; } @@ -129,6 +129,30 @@ function cmdAuditUat(cwd: string, raw: boolean): void { } } } + + // Process deferred-items.md (#2287) — the SCOPE BOUNDARY convention + // (agents/gsd-executor.md) has the executor log out-of-scope discoveries + // to this file; nothing previously read it back. Surface every + // UNRESOLVED entry (see parseDeferredItems for the resolved/unresolved + // parsing rule) as a 'deferred'-typed result, keeping deferred-items.md + // itself the single source of truth — no duplicate pending-todo entry + // required. + const deferredFile = 'deferred-items.md'; + if (files.includes(deferredFile)) { + const content = fs.readFileSync(path.join(phaseDir, deferredFile), 'utf-8'); + const items = parseDeferredItems(content); + if (items.length > 0) { + results.push({ + phase: phaseNum, + phase_dir: dir, + file: deferredFile, + file_path: toPosixPath(path.relative(cwd, path.join(phaseDir, deferredFile))), + type: 'deferred', + status: 'unresolved', + items, + }); + } + } } // Compute summary @@ -445,6 +469,62 @@ function parseGapsItems(content: string): UatItem[] { return items; } +// ─── parseDeferredItems ──────────────────────────────────────────────────────── + +/** + * Extract unresolved entries from a phase directory's `deferred-items.md` + * (#2287) — the SCOPE BOUNDARY convention `agents/gsd-executor.md` instructs + * the executor to follow: "Log out-of-scope discoveries to `deferred-items.md` + * in the phase directory". Nothing previously read this file back, so a + * deferred entry was permanently invisible outside the phase directory. + * + * The writer convention (unchanged by this fix, per the issue's stated + * out-of-scope) emits a plain bullet list, typically under a `## Deferred + * Items` heading (see the issue's own reproduction fixture), one entry per + * top-level `- ` line with optional indented continuation lines. There is no + * mandated heading text, so if no `## Deferred Items`-shaped level-2 heading + * is found, the WHOLE file is scanned as the entry list — fail-safe, so an + * agent writing a differently-headed (or headless) deferred-items.md still + * has its entries surfaced rather than silently skipped. + * + * Reuses the same per-line field/entry-splitting seams as `parseGapsItems` + * (`splitGapsEntries`, `extractGapEntryFields`, `rawGapEntryText`) — an entry + * is RESOLVED only when it carries an explicit `status: resolved` field + * (case-insensitive), mirroring the established Gaps convention so a human or + * follow-up agent can mark a deferred item done in place, keeping + * `deferred-items.md` the single source of truth (no duplicate + * `.planning/todos/pending/*.md` entry required). Every other entry — + * including one with no `status:` field at all — is UNRESOLVED and is + * surfaced. + */ +function parseDeferredItems(content: string): UatItem[] { + const deferredSection = collectSection( + content, + (h) => /^deferred\s+items$/i.test(h.text) && h.level === 2, + { levelBounded: true }, + ); + const sectionBody = deferredSection ? deferredSection.body : content; + + const items: UatItem[] = []; + + for (const entryLines of splitGapsEntries(sectionBody)) { + const fields = extractGapEntryFields(entryLines); + const rawStatus = fields.status; + if (rawStatus && rawStatus.toLowerCase() === 'resolved') continue; + + const text = rawGapEntryText(entryLines); + if (!text) continue; + + items.push({ + name: text, + result: 'unresolved', + category: 'deferred', + }); + } + + return items; +} + /** * Split a `## Gaps` section body into per-entry line groups on TOP-LEVEL * `- ` bullet openers. @@ -747,4 +827,5 @@ export = { cmdRenderCheckpoint, parseCurrentTest, buildCheckpoint, + parseDeferredItems, }; diff --git a/tests/fix-2287-deferred-items-reader.test.cjs b/tests/fix-2287-deferred-items-reader.test.cjs new file mode 100644 index 000000000..57f0aebb0 --- /dev/null +++ b/tests/fix-2287-deferred-items-reader.test.cjs @@ -0,0 +1,341 @@ +/** + * #2287 — deferred-items.md has no reader anywhere in gsd-core. + * + * The SCOPE BOUNDARY convention (`agents/gsd-executor.md`) instructs the + * executor to log out-of-scope discoveries to `deferred-items.md` inside the + * phase directory. Nothing read that file back: `cmdAuditUat` (src/uat.cts) + * filtered phase-directory files down to `*-UAT.md` / `*-VERIFICATION.md` + * only, and the `forensic_audit` workflow step (gsd-core/workflows/ + * progress.md) ran 6 checks, none of which globbed the phase-directory + * `deferred-items.md` path. An entry written there was permanently invisible. + * + * This fix: + * - `cmdAuditUat` gains a `deferred-items.md` scan per phase directory, + * surfacing every UNRESOLVED entry as a `type: 'deferred'` result. An + * entry is resolved only when it carries an explicit `status: resolved` + * field (mirroring the established `## Gaps` convention from #2286) — a + * missing/garbled status fails safe and is surfaced. + * - `forensic_audit` gains a 7th check that globs the same path and reports + * unresolved entries with the same ✓/⚠ semantics as the other 6 checks. + * + * `deferred-items.md` remains the single source of truth — no duplicate + * `.planning/todos/pending/*.md` entry is required. + */ + +'use strict'; + +const { test, describe, beforeEach, afterEach } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const fc = require('./helpers/fast-check-setup.cjs'); +const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); +const { parseDeferredItems } = require('../gsd-core/bin/lib/uat.cjs'); + +// ─── cmdAuditUat behavioral coverage ─────────────────────────────────────── + +describe('#2287 cmdAuditUat: deferred-items.md awareness', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject(); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('no deferred-items.md present (0 entries) → no results, no false positive', () => { + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation'); + fs.mkdirSync(phaseDir, { recursive: true }); + fs.writeFileSync(path.join(phaseDir, '.gitkeep'), ''); + + const result = runGsdTools('audit-uat --raw', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.deepStrictEqual(output.results, []); + assert.strictEqual(output.summary.total_items, 0); + assert.strictEqual(output.summary.total_files, 0); + }); + + test('deferred-items.md with only a resolved entry (0 unresolved) → no result surfaced', () => { + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation'); + fs.mkdirSync(phaseDir, { recursive: true }); + + fs.writeFileSync(path.join(phaseDir, 'deferred-items.md'), [ + '## Deferred Items', + '', + '- Already handled unrelated lint warning.', + ' status: resolved', + ].join('\n')); + + const result = runGsdTools('audit-uat --raw', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.deepStrictEqual(output.results, [], + 'a fully-resolved deferred-items.md must not surface any result'); + assert.strictEqual(output.summary.total_items, 0); + }); + + test('deferred-items.md with 1 unresolved entry → surfaced in structured JSON output', () => { + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation'); + fs.mkdirSync(phaseDir, { recursive: true }); + + fs.writeFileSync(path.join(phaseDir, 'deferred-items.md'), [ + '## Deferred Items', + '', + '- Found an unrelated pre-existing test failure in `some-other-module` while working on', + ' this phase\'s task. Out of scope for this task — logged here per SCOPE BOUNDARY.', + ].join('\n')); + + const result = runGsdTools('audit-uat --raw', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.summary.total_items, 1); + assert.strictEqual(output.summary.total_files, 1); + assert.strictEqual(output.summary.by_category.deferred, 1); + assert.strictEqual(output.summary.by_phase['01'], 1); + + const deferredResult = output.results.find(r => r.type === 'deferred'); + assert.ok(deferredResult, 'a deferred-typed result must be present'); + assert.strictEqual(deferredResult.phase, '01'); + assert.strictEqual(deferredResult.file, 'deferred-items.md'); + assert.strictEqual( + deferredResult.file_path, + '.planning/phases/01-foundation/deferred-items.md', + ); + assert.strictEqual(deferredResult.items.length, 1); + assert.match(deferredResult.items[0].name, /unrelated pre-existing test failure/); + assert.strictEqual(deferredResult.items[0].result, 'unresolved'); + assert.strictEqual(deferredResult.items[0].category, 'deferred'); + }); + + test('deferred-items.md with 2+ entries (mixed resolved/unresolved) → only unresolved surfaced', () => { + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation'); + fs.mkdirSync(phaseDir, { recursive: true }); + + fs.writeFileSync(path.join(phaseDir, 'deferred-items.md'), [ + '## Deferred Items', + '', + '- First unrelated finding, still open.', + '- Second unrelated finding, also still open.', + '- Third finding, already fixed separately.', + ' status: resolved', + ].join('\n')); + + const result = runGsdTools('audit-uat --raw', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + const deferredResult = output.results.find(r => r.type === 'deferred'); + assert.ok(deferredResult); + assert.strictEqual(deferredResult.items.length, 2, + 'exactly the 2 unresolved entries must surface; the resolved 3rd must not'); + const names = deferredResult.items.map(i => i.name); + assert.ok(names.some(n => n.includes('First unrelated finding'))); + assert.ok(names.some(n => n.includes('Second unrelated finding'))); + assert.ok(!names.some(n => n.includes('Third finding'))); + }); + + test('deferred entries surface across multiple phase directories', () => { + const phase1 = path.join(tmpDir, '.planning', 'phases', '01-foundation'); + const phase2 = path.join(tmpDir, '.planning', 'phases', '02-auth'); + fs.mkdirSync(phase1, { recursive: true }); + fs.mkdirSync(phase2, { recursive: true }); + + fs.writeFileSync(path.join(phase1, 'deferred-items.md'), [ + '## Deferred Items', + '', + '- Phase 1 unrelated finding.', + ].join('\n')); + fs.writeFileSync(path.join(phase2, 'deferred-items.md'), [ + '## Deferred Items', + '', + '- Phase 2 unrelated finding.', + ].join('\n')); + + const result = runGsdTools('audit-uat --raw', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + const deferredResults = output.results.filter(r => r.type === 'deferred'); + assert.strictEqual(deferredResults.length, 2); + assert.strictEqual(output.summary.total_items, 2); + assert.strictEqual(output.summary.by_phase['01'], 1); + assert.strictEqual(output.summary.by_phase['02'], 1); + }); + + test('an entry with a garbled/missing status fails safe and is surfaced (not silently dropped)', () => { + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation'); + fs.mkdirSync(phaseDir, { recursive: true }); + + fs.writeFileSync(path.join(phaseDir, 'deferred-items.md'), [ + '## Deferred Items', + '', + '- An entry with no status field at all.', + ].join('\n')); + + const result = runGsdTools('audit-uat --raw', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.summary.total_items, 1, + 'missing status must SURFACE the entry, not silently drop it'); + }); + + test('existing UAT/VERIFICATION scanning is unchanged when a deferred-items.md is also present', () => { + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation'); + fs.mkdirSync(phaseDir, { recursive: true }); + + fs.writeFileSync(path.join(phaseDir, '01-UAT.md'), [ + '---', + 'status: testing', + 'phase: 01-foundation', + 'started: 2025-01-01T00:00:00Z', + 'updated: 2025-01-01T00:00:00Z', + '---', + '', + '## Tests', + '', + '### 1. Login Form', + 'expected: Form displays with email and password fields', + 'result: pending', + ].join('\n')); + + fs.writeFileSync(path.join(phaseDir, 'deferred-items.md'), [ + '## Deferred Items', + '', + '- An unrelated out-of-scope finding.', + ].join('\n')); + + const result = runGsdTools('audit-uat --raw', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.results.length, 2, 'both the UAT file and deferred-items.md must surface as separate results'); + const uatResult = output.results.find(r => r.type === 'uat'); + const deferredResult = output.results.find(r => r.type === 'deferred'); + assert.ok(uatResult, 'existing uat-type result must still be present'); + assert.strictEqual(uatResult.items.length, 1); + assert.strictEqual(uatResult.items[0].result, 'pending'); + assert.ok(deferredResult, 'new deferred-type result must be present'); + assert.strictEqual(deferredResult.items.length, 1); + }); +}); + +// ─── forensic_audit workflow-prose source-contract guard ────────────────── + +const PROGRESS_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'progress.md'); + +describe('#2287 progress.md forensic_audit: deferred-items.md contract', () => { + const content = fs.readFileSync(PROGRESS_MD, 'utf-8'); + const stepStart = content.indexOf(''); + const stepEnd = content.indexOf('', stepStart); + const section = stepStart !== -1 && stepEnd !== -1 ? content.slice(stepStart, stepEnd) : ''; + + test('forensic_audit step exists', () => { + assert.notEqual(stepStart, -1, 'progress.md must contain the forensic_audit step'); + }); + + test('forensic_audit now runs 7 checks (was 6) and globs deferred-items.md', () => { + assert.ok(/running 7 deep checks/i.test(section), + 'forensic_audit must advertise 7 deep checks (was 6) now that deferred-items.md is read'); + assert.ok(/\.planning\/phases\/\*\/deferred-items\.md/.test(section), + 'forensic_audit must glob .planning/phases/*/deferred-items.md'); + }); + + test('the new check reports unresolved deferred items with the same ✓/⚠ semantics as the other checks', () => { + assert.ok(/check\s*7/i.test(section), + 'a 7th check must be present'); + assert.ok(/unresolved deferred items/i.test(section), + 'the check must be framed around unresolved deferred items'); + assert.ok(/✓[^\n]*no unresolved deferred items/i.test(section), + 'the check must emit a ✓ pass line when no unresolved deferred items exist'); + assert.ok(/⚠[^\n]*unresolved deferred items found/i.test(section), + 'the check must emit a ⚠ warning line when unresolved deferred items exist'); + }); + + test('an entry is resolved only via an explicit status: resolved field (fail-safe otherwise)', () => { + assert.ok(/status:\s*resolved/i.test(section), + 'the resolved/unresolved parsing rule must be documented in the step prose'); + }); + + test('the verdict summary now gates on 7 checks (was 6)', () => { + assert.ok(/after all 7 checks/i.test(section), + 'the verdict section must say "after all 7 checks"'); + assert.ok(/if all 7 checks passed/i.test(section), + 'the verdict section must say "if all 7 checks passed"'); + assert.ok(!/after all 6 checks/i.test(section) && !/if all 6 checks passed/i.test(section), + 'stale "6 checks" phrasing must not remain in the step'); + }); +}); + +// ─── parseDeferredItems property test ────────────────────────────────────── + +describe('#2287 parseDeferredItems: property (status: resolved fail-safe)', () => { + // Single-line entry text: no newlines (would break bullet-entry splitting), + // non-empty after trim, and never itself SHAPED like a `status:` field line + // (that would be indistinguishable from a real field regardless of intent). + const plainText = fc.string({ minLength: 1, maxLength: 40 }) + .map((s) => s.replace(/[\r\n]/g, ' ').trim()) + .filter((s) => s.length > 0 && !/^status:/i.test(s)); + + // Decoy: entry text that CONTAINS a `status: resolved`-shaped substring + // mid-line (not at line start) — must never be misread as a resolved + // marker, since extractGapEntryFields only recognises a field anchored to + // the START of its own trimmed line (see parseDeferredItems' doc comment). + const decoyText = plainText.map((s) => `${s} status: resolved trailing note`); + + const textArb = fc.oneof(plainText, decoyText); + const entryArb = fc.record({ text: textArb, resolved: fc.boolean() }); + + test('property: an entry is surfaced iff it is NOT marked status: resolved; surfaced count == non-resolved count', () => { + fc.assert( + fc.property( + fc.array(entryArb, { maxLength: 20 }), + (rawEntries) => { + // Index-prefix for uniqueness so surfaced items can be mapped back + // to their source entry unambiguously even with colliding random text. + const entries = rawEntries.map((e, i) => ({ text: `E${i}_${e.text}`, resolved: e.resolved })); + + const lines = ['## Deferred Items', '']; + for (const e of entries) { + lines.push(`- ${e.text}`); + if (e.resolved) lines.push(' status: resolved'); + } + const content = lines.join('\n'); + + const items = parseDeferredItems(content); + const surfacedNames = new Set(items.map((it) => it.name)); + + const expectedUnresolved = entries.filter((e) => !e.resolved); + const expectedResolved = entries.filter((e) => e.resolved); + + // Total surfaced count equals the count of non-resolved entries. + assert.strictEqual(items.length, expectedUnresolved.length); + + // Every non-resolved entry IS surfaced (including status:-shaped + // decoy substrings embedded mid-line — those must not flip the + // outcome). + for (const e of expectedUnresolved) { + assert.ok(surfacedNames.has(e.text), `expected unresolved entry to surface: ${e.text}`); + } + + // No status:-resolved entry is EVER surfaced. + for (const e of expectedResolved) { + assert.ok(!surfacedNames.has(e.text), `status: resolved entry must never surface: ${e.text}`); + } + + // Every returned item carries the fixed deferred category/result shape. + for (const item of items) { + assert.strictEqual(item.result, 'unresolved'); + assert.strictEqual(item.category, 'deferred'); + } + } + ) + ); + }); +}); diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 7fabff4fa..fafc8f068 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -279,7 +279,7 @@ "gsd-core/workflows/plant-seed.md": "6cf726be6427a35a", "gsd-core/workflows/pr-branch.md": "dc5598ae8accdecd", "gsd-core/workflows/profile-user.md": "355af92ac285567f", - "gsd-core/workflows/progress.md": "4f0c285ce35553e3", + "gsd-core/workflows/progress.md": "0e41db02a270a2b2", "gsd-core/workflows/quick.md": "424c0eb61769e747", "gsd-core/workflows/reapply-patches.md": "4dcd6117d0a507ca", "gsd-core/workflows/remove-phase.md": "23b9eb0858a2535e", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index f5e1840af..63f11430e 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -350,7 +350,7 @@ "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "14263db831230142", - "gsd-core/workflows/progress.md": "281a77e800d11614", + "gsd-core/workflows/progress.md": "9457fb10437c9402", "gsd-core/workflows/quick.md": "25f0cc40bc22ed3e", "gsd-core/workflows/reapply-patches.md": "39050f72601aec89", "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index d3893f1ae..2c4ed5d44 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -349,7 +349,7 @@ "gsd-core/workflows/plant-seed.md": "fbe964fcdb244802", "gsd-core/workflows/pr-branch.md": "513f6cff722eff2d", "gsd-core/workflows/profile-user.md": "3b34dcb337d50f4b", - "gsd-core/workflows/progress.md": "eb0885959b4ac66e", + "gsd-core/workflows/progress.md": "f6739fc007de6f66", "gsd-core/workflows/quick.md": "f1b474b46327034f", "gsd-core/workflows/reapply-patches.md": "44a96b52b975e9bb", "gsd-core/workflows/remove-phase.md": "8effc8742d58a11a", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index e9e87b37c..2cdb2a6e6 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -278,7 +278,7 @@ "gsd-core/workflows/plant-seed.md": "af50e9f10d3cc6e1", "gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd", "gsd-core/workflows/profile-user.md": "ff3820a27731ceb8", - "gsd-core/workflows/progress.md": "5ce5a11b468bd419", + "gsd-core/workflows/progress.md": "8aa2fcebb8b4876e", "gsd-core/workflows/quick.md": "b4237633eaf5a246", "gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041", "gsd-core/workflows/remove-phase.md": "ada8a0546c686483", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 4d0ac889d..4e49a8454 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -282,7 +282,7 @@ "gsd-core/workflows/plant-seed.md": "a2cdd513663226f1", "gsd-core/workflows/pr-branch.md": "9923878a4f6a2d91", "gsd-core/workflows/profile-user.md": "26f74db0a7fcd268", - "gsd-core/workflows/progress.md": "c5ce768042ca9b8b", + "gsd-core/workflows/progress.md": "a2d948b084a48500", "gsd-core/workflows/quick.md": "797b1c8d62c3d33d", "gsd-core/workflows/reapply-patches.md": "eb4272145a117904", "gsd-core/workflows/remove-phase.md": "e336350f8113a328", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index 1a5dd5dc1..7caf4df82 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -350,7 +350,7 @@ "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "4fa910d15dea5695", - "gsd-core/workflows/progress.md": "281a77e800d11614", + "gsd-core/workflows/progress.md": "9457fb10437c9402", "gsd-core/workflows/quick.md": "0bd83bd88c5e63d1", "gsd-core/workflows/reapply-patches.md": "39050f72601aec89", "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 973e0f618..5aabd3233 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -385,7 +385,7 @@ "gsd-core/workflows/plant-seed.md": "5b07de07e4593281", "gsd-core/workflows/pr-branch.md": "d13e1cc81de40896", "gsd-core/workflows/profile-user.md": "05828c8cc61ef384", - "gsd-core/workflows/progress.md": "7ec6495e273904cf", + "gsd-core/workflows/progress.md": "03f12b223eb3a96e", "gsd-core/workflows/quick.md": "bf70b7beb314aa50", "gsd-core/workflows/reapply-patches.md": "26297b84736e66a4", "gsd-core/workflows/remove-phase.md": "9ee0fddd11a0d9d4", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 1e4133696..05c422e16 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -280,7 +280,7 @@ "gsd-core/workflows/plant-seed.md": "21e461cd39e5181b", "gsd-core/workflows/pr-branch.md": "2833905f119b5722", "gsd-core/workflows/profile-user.md": "5cc032206c99ef71", - "gsd-core/workflows/progress.md": "9bd7472d2efe14dd", + "gsd-core/workflows/progress.md": "449b7f4de1bef7ae", "gsd-core/workflows/quick.md": "4a53899b69b8c801", "gsd-core/workflows/reapply-patches.md": "8fd59e24b486f180", "gsd-core/workflows/remove-phase.md": "e262654e319d1bc4", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index 1ee36be06..58ef6f79a 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -350,7 +350,7 @@ "gsd-core/workflows/plant-seed.md": "7bccd151ce7b69f2", "gsd-core/workflows/pr-branch.md": "c67d90c65da47168", "gsd-core/workflows/profile-user.md": "8c943983241260b5", - "gsd-core/workflows/progress.md": "a980339715a431f0", + "gsd-core/workflows/progress.md": "0f03aaf96f5abf7e", "gsd-core/workflows/quick.md": "3b37350964cfe391", "gsd-core/workflows/reapply-patches.md": "ba9406b60f2c4041", "gsd-core/workflows/remove-phase.md": "ada8a0546c686483", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 8261b0186..dad4594b3 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -279,7 +279,7 @@ "gsd-core/workflows/plant-seed.md": "f862f77fca983749", "gsd-core/workflows/pr-branch.md": "ecabd55e4eabf229", "gsd-core/workflows/profile-user.md": "de5030437226cf2c", - "gsd-core/workflows/progress.md": "2641aa5457ab784f", + "gsd-core/workflows/progress.md": "debe980226d9260d", "gsd-core/workflows/quick.md": "01560a250a6040ea", "gsd-core/workflows/reapply-patches.md": "158083a310859594", "gsd-core/workflows/remove-phase.md": "fce799aae3ab2715", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index d93ba9396..f8a27bff4 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -350,7 +350,7 @@ "gsd-core/workflows/plant-seed.md": "ffa5774304243649", "gsd-core/workflows/pr-branch.md": "ab157cd8e49621dd", "gsd-core/workflows/profile-user.md": "203ebe3f8f3876a8", - "gsd-core/workflows/progress.md": "fb1ea7476a839fd8", + "gsd-core/workflows/progress.md": "c2a8a79af6603e70", "gsd-core/workflows/quick.md": "4ddd6d3214f57f57", "gsd-core/workflows/reapply-patches.md": "becf9728cdb124c4", "gsd-core/workflows/remove-phase.md": "ada8a0546c686483", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 5d690f133..04735c9de 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -343,7 +343,7 @@ "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "5abfae83739fa978", - "gsd-core/workflows/progress.md": "281a77e800d11614", + "gsd-core/workflows/progress.md": "9457fb10437c9402", "gsd-core/workflows/quick.md": "8dcda37c46954ebe", "gsd-core/workflows/reapply-patches.md": "39050f72601aec89", "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 0d473e07b..b36c2461a 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -350,7 +350,7 @@ "gsd-core/workflows/plant-seed.md": "b8dad652e31c2318", "gsd-core/workflows/pr-branch.md": "929b7cb0c99c7b9e", "gsd-core/workflows/profile-user.md": "248d59a31948e0ed", - "gsd-core/workflows/progress.md": "8e01238a8ac6b73d", + "gsd-core/workflows/progress.md": "352920a61ed4f4db", "gsd-core/workflows/quick.md": "774d646560a74c93", "gsd-core/workflows/reapply-patches.md": "a0e9b53f90abceb2", "gsd-core/workflows/remove-phase.md": "dea4661e8f89596f", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index 594bd91fa..34f153694 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -246,7 +246,7 @@ "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "1bac7f69142801ef", - "gsd-core/workflows/progress.md": "281a77e800d11614", + "gsd-core/workflows/progress.md": "9457fb10437c9402", "gsd-core/workflows/quick.md": "31787c1e8a867e3f", "gsd-core/workflows/reapply-patches.md": "39050f72601aec89", "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index b79261ff2..9eeb5cbd2 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -279,7 +279,7 @@ "gsd-core/workflows/plant-seed.md": "0a92ba12993ac261", "gsd-core/workflows/pr-branch.md": "cef0f65b16d500b4", "gsd-core/workflows/profile-user.md": "263c0693563d98da", - "gsd-core/workflows/progress.md": "b1b3f88614d6b9cc", + "gsd-core/workflows/progress.md": "4ceb0f6f5e34c4a4", "gsd-core/workflows/quick.md": "d0fffe516abaf47a", "gsd-core/workflows/reapply-patches.md": "de0ee8acfe7245b2", "gsd-core/workflows/remove-phase.md": "e8ae4fbbfac700f0", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 98ff03b1d..ed2a4e300 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -279,7 +279,7 @@ "gsd-core/workflows/plant-seed.md": "856ad565b2eb0c47", "gsd-core/workflows/pr-branch.md": "79fd55b88ea2db9c", "gsd-core/workflows/profile-user.md": "672821e6b1266645", - "gsd-core/workflows/progress.md": "e7d05715aff75881", + "gsd-core/workflows/progress.md": "00de1afbe907efa5", "gsd-core/workflows/quick.md": "0529abcf9913ec04", "gsd-core/workflows/reapply-patches.md": "21b38c374f19fd78", "gsd-core/workflows/remove-phase.md": "a46c2fe853bf4e86", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 9a8ddd406..bef04f6b5 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -279,7 +279,7 @@ "gsd-core/workflows/plant-seed.md": "6cf61f540cdbd8b9", "gsd-core/workflows/pr-branch.md": "acd59f915d018ad4", "gsd-core/workflows/profile-user.md": "c4313672b81b5bcd", - "gsd-core/workflows/progress.md": "d4a4f96975419d09", + "gsd-core/workflows/progress.md": "7b6b9564b34fcabb", "gsd-core/workflows/quick.md": "71fbcf38e2305de0", "gsd-core/workflows/reapply-patches.md": "d449a23d3acf6379", "gsd-core/workflows/remove-phase.md": "e7a6af429b36e77b", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 59b3baa42..87a95f075 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -350,7 +350,7 @@ "gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede", "gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b", "gsd-core/workflows/profile-user.md": "e23bea0a69c0bb4b", - "gsd-core/workflows/progress.md": "281a77e800d11614", + "gsd-core/workflows/progress.md": "9457fb10437c9402", "gsd-core/workflows/quick.md": "7ad8d250c536c4dd", "gsd-core/workflows/reapply-patches.md": "39050f72601aec89", "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 270be8aed..9f3d4697d 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -58,7 +58,7 @@ "plant-seed.md": 11785, "pr-branch.md": 15963, "profile-user.md": 21246, - "progress.md": 30628, + "progress.md": 31789, "quick.md": 50699, "reapply-patches.md": 20312, "remove-phase.md": 8513,