fix(#2494): capture stderr and guard empty output on the claude and gemini reviewer legs (#2592)

* fix(#2494): capture stderr and guard empty output on the claude and gemini reviewer legs

The gemini and claude blocks in gsd-core/workflows/review.md were the only
two of the ten prompt-fed reviewer legs with both `2>/dev/null` and no
empty-output guard. Any failure that wrote no stdout — CLI missing,
unauthenticated, rate-limited, crashed — left a zero-byte review file with
the only diagnostic evidence already discarded.

write_reviews substitutes each file's raw content into a `## <Reviewer>
Review` section with no marker separating "empty/failed" from "ran cleanly,
nothing to report", so a failed lane silently degraded the advertised
N-reviewer consensus to N-1 while present_results reported success. Both
/gsd:review and /gsd:plan-review-convergence share the invoke_reviewers
step, so both were affected.

Both legs now redirect stderr to a `.err` sidecar and write a diagnostic
stub with the captured stderr appended when the review file comes back
empty — the same shape the codex and cursor legs already use.

Scope limit, noted in the block comment: the guard only runs if the block
itself completes. A host Bash-tool timeout that kills the whole block skips
it, the same hard bound the OpenCode block already documents. The existing
timeout guidance (#2194) covers that case and is unchanged.

Regression test extracts the two dispatch blocks verbatim from the workflow
and runs them under bash against a failing CLI stub, asserting the review
file is non-empty and carries a diagnosable message plus the captured
stderr. It fails against pre-fix review.md (4 of 5 cases).

Golden install-parity fixtures and the workflow size baseline are
regenerated: one review.md hash per fixture, one size entry.

Fixes #2494

* fix(#2494): stamp the changeset fragment with the filed PR number

The fragment was authored with the documented `pr: 0` placeholder because
the PR number does not exist until `gh pr create` returns. Now that this
PR is #2592, stamp it — scripts/changeset/parse.cjs requires pr > 0, so
the placeholder would fail the Changeset Required check.
This commit is contained in:
0xdhx
2026-07-24 11:50:34 -05:00
committed by GitHub
parent bf9fe4630d
commit bd18a593a9
23 changed files with 253 additions and 24 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2592
---
**The Gemini and Claude reviewer legs now fail loudly instead of silently dropping out of the cross-AI review** — both blocks capture stderr to a `.err` sidecar instead of discarding it to `/dev/null`, and write a diagnostic stub with the captured error when the lane produces no output. Previously they were the only two of the ten prompt-fed reviewer legs with neither guard, so any failure that wrote no stdout (CLI missing, unauthenticated, rate-limited, crashed) left a zero-byte review file that `write_reviews` rendered as a reviewer that had run cleanly with nothing to report — quietly degrading an N-reviewer consensus to N-1 while `present_results` reported success. The guard matches the shape the Codex and Cursor legs already use. (#2494)

View File

@@ -277,19 +277,39 @@ For each selected CLI, invoke in sequence (not parallel — avoid rate limits):
**Gemini:**
```bash
# #2494: capture stderr to a .err sidecar (not /dev/null) and stub an empty
# result, mirroring the Codex and Cursor blocks. Without the guard a failed
# lane — CLI missing, unauthenticated, rate-limited, crashed, or any exit that
# writes no stdout — leaves a zero-byte file that write_reviews renders as a
# reviewer that ran cleanly with nothing to report, silently dropping a lane
# from the cross-AI consensus while present_results reports success.
# Scope limit: the guard only runs if this block completes. A host Bash-tool
# timeout that kills the whole block skips it, the same hard bound the
# OpenCode block documents below — per the timeout guidance above, treat an
# empty result on a slow lane as a dropped lane rather than a crash.
if [ -n "$GEMINI_MODEL" ] && [ "$GEMINI_MODEL" != "null" ]; then
cat {run_dir}/gsd-review-prompt.md | gemini -m "$GEMINI_MODEL" -p - 2>/dev/null > {run_dir}/gsd-review-gemini.md
cat {run_dir}/gsd-review-prompt.md | gemini -m "$GEMINI_MODEL" -p - 2>{run_dir}/gsd-review-gemini.err > {run_dir}/gsd-review-gemini.md
else
cat {run_dir}/gsd-review-prompt.md | gemini -p - 2>/dev/null > {run_dir}/gsd-review-gemini.md
cat {run_dir}/gsd-review-prompt.md | gemini -p - 2>{run_dir}/gsd-review-gemini.err > {run_dir}/gsd-review-gemini.md
fi
if [ ! -s {run_dir}/gsd-review-gemini.md ]; then
echo "Gemini review failed or returned empty output. stderr:" > {run_dir}/gsd-review-gemini.md
cat {run_dir}/gsd-review-gemini.err >> {run_dir}/gsd-review-gemini.md
fi
```
**Claude (separate session):**
```bash
# #2494: same guard as the Gemini block above — stderr to a .err sidecar
# instead of /dev/null, and a diagnostic stub when the lane produces nothing.
if [ -n "$CLAUDE_MODEL" ] && [ "$CLAUDE_MODEL" != "null" ]; then
cat {run_dir}/gsd-review-prompt.md | claude --model "$CLAUDE_MODEL" $CLAUDE_EFFORT_ARGS -p - 2>/dev/null > {run_dir}/gsd-review-claude.md
cat {run_dir}/gsd-review-prompt.md | claude --model "$CLAUDE_MODEL" $CLAUDE_EFFORT_ARGS -p - 2>{run_dir}/gsd-review-claude.err > {run_dir}/gsd-review-claude.md
else
cat {run_dir}/gsd-review-prompt.md | claude $CLAUDE_EFFORT_ARGS -p - 2>/dev/null > {run_dir}/gsd-review-claude.md
cat {run_dir}/gsd-review-prompt.md | claude $CLAUDE_EFFORT_ARGS -p - 2>{run_dir}/gsd-review-claude.err > {run_dir}/gsd-review-claude.md
fi
if [ ! -s {run_dir}/gsd-review-claude.md ]; then
echo "Claude review failed or returned empty output. stderr:" > {run_dir}/gsd-review-claude.md
cat {run_dir}/gsd-review-claude.err >> {run_dir}/gsd-review-claude.md
fi
```

View File

@@ -0,0 +1,204 @@
// allow-test-rule: source-text-is-the-product (see #2494)
// The Gemini and Claude reviewer dispatch blocks in gsd-core/workflows/review.md
// ARE the runtime contract — the workflow's text is what the reviewing agent
// executes. This suite extracts those two shell blocks verbatim from the
// workflow and runs them under a real bash against a failing CLI stub, so the
// shipped guard is what gets exercised rather than a reimplementation of it.
// The assertions on the produced review file are assertions on that guard's
// documented output contract (the stub line the consensus step must be able to
// tell apart from a clean empty review), not incidental string matching.
/**
* Regression tests for #2494 — the claude and gemini reviewer legs discarded
* stderr with no empty-output guard.
*
* Before the fix both blocks were `... 2>/dev/null > {run_dir}/gsd-review-<leg>.md`
* with nothing after: a non-zero exit that wrote no stdout (CLI missing,
* unauthenticated, rate-limited, timeout-killed, crashed) left a ZERO-BYTE
* review file with the only diagnostic evidence — stderr — already discarded.
* The write_reviews step then substituted that empty file into a
* `## <Reviewer> Review` section indistinguishable from "ran cleanly, nothing
* to report", silently degrading the advertised N-reviewer consensus to N-1.
*
* These tests fail against pre-fix review.md: the produced file is zero-byte,
* so both the non-empty and the diagnosable-message assertions trip.
*/
'use strict';
const { describe, test, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
const { createTempDir, cleanup } = require('./helpers.cjs');
const ROOT = path.join(__dirname, '..');
const REVIEW_PATH = path.join(ROOT, 'gsd-core', 'workflows', 'review.md');
// Normalize CRLF: on a Windows git-autocrlf checkout every line carries a
// trailing \r, which would leave the extracted block's shebang/redirect tokens
// mangled and defeat the fence regexes below.
const WORKFLOW = fs.readFileSync(REVIEW_PATH, 'utf-8').replace(/\r\n/g, '\n');
// This suite executes the extracted blocks with a real bash and a POSIX CLI
// stub on PATH. Gate to non-Windows, mirroring the opencode reconstruction
// suite's win32 skip — the guard logic is platform-independent and is asserted
// in full on every macOS/Linux CI leg.
const skipReason = process.platform === 'win32'
? 'extracted block is POSIX shell; guard logic is platform-independent and asserted on macOS/Linux'
: false;
/**
* Extract a reviewer dispatch block verbatim from the workflow. If review.md
* changes the block's shape these throw and the test fails loudly — intended
* coupling, the same contract the opencode suite pins for its jq programs.
*/
function extractBlock(headingRe, label) {
const re = new RegExp(`${headingRe}\\n\`\`\`bash\\n([\\s\\S]*?)\\n\`\`\``);
const m = WORKFLOW.match(re);
assert.ok(m, `review.md must define the ${label} reviewer dispatch as a bash block (#2494)`);
return m[1];
}
const GEMINI_BLOCK = extractBlock('\\*\\*Gemini:\\*\\*', 'Gemini');
const CLAUDE_BLOCK = extractBlock('\\*\\*Claude \\(separate session\\):\\*\\*', 'Claude');
const STUB_STDERR = 'gsd-2494-stub: command not found / not authenticated';
let sandbox;
before(() => {
sandbox = createTempDir('gsd-2494-');
});
after(() => {
cleanup(sandbox);
});
/**
* Run one extracted block with `{run_dir}` pointed at a fresh run directory and
* `stubBody` installed on PATH under the reviewer's binary name.
*
* Single exec site for the whole suite so the Windows guard lives in one place:
* Git Bash (msys2) ignores Node's chmod exec bit for PATH-executed
* extension-less scripts (DEFECT.WINDOWS-TEST-PORTABILITY), and every suite
* below is skipped on win32 — this early return keeps the exec unreachable
* there rather than relying on the skip alone.
*/
function runBlockWithStub({ binName, block, stubBody, env = {} }) {
if (process.platform === 'win32') return null;
const caseDir = fs.mkdtempSync(path.join(sandbox, 'run-'));
const runDir = path.join(caseDir, 'run');
const binDir = path.join(caseDir, 'bin');
fs.mkdirSync(runDir);
fs.mkdirSync(binDir);
const stub = path.join(binDir, binName);
fs.writeFileSync(stub, stubBody);
fs.chmodSync(stub, 0o755);
fs.writeFileSync(path.join(runDir, 'gsd-review-prompt.md'), '# review prompt\n');
const script = block.split('{run_dir}').join(runDir);
const result = spawnSync('bash', ['-c', script], {
encoding: 'utf8',
timeout: 30000,
killSignal: 'SIGKILL',
env: { ...process.env, PATH: `${binDir}${path.delimiter}${process.env.PATH}`, ...env },
});
const reviewPath = path.join(runDir, `gsd-review-${binName}.md`);
return {
result,
reviewPath,
errPath: path.join(runDir, `gsd-review-${binName}.err`),
review: fs.existsSync(reviewPath) ? fs.readFileSync(reviewPath, 'utf-8') : null,
};
}
/**
* The issue's own repro harness: a CLI that writes STUB_STDERR to stderr,
* nothing to stdout, and exits non-zero.
*/
function runLeg({ binName, block, env = {} }) {
return runBlockWithStub({
binName,
block,
stubBody: `#!/bin/sh\necho "${STUB_STDERR}" >&2\nexit 127\n`,
env,
});
}
/**
* The guard's contract: the review file exists, is NOT empty, names the leg as
* failed-or-empty, and carries the captured stderr. The last assertion is what
* separates this fix from a bare `[ ! -s ]` stub — the evidence has to survive.
*/
function assertDiagnosable({ review, reviewPath, errPath }, legLabel) {
assert.ok(review !== null, `${legLabel}: review file must exist after a failed lane (#2494)`);
assert.notStrictEqual(review.trim(), '', `${legLabel}: review file must not be empty after a failed lane (#2494)`);
assert.match(
review,
new RegExp(`${legLabel} review failed or returned empty output`, 'i'),
`${legLabel}: review file must carry a diagnosable failure line, distinguishable at consensus synthesis from "ran cleanly, nothing to report" (#2494)`,
);
assert.ok(
review.includes(STUB_STDERR),
`${legLabel}: captured stderr must be appended to the review file, not discarded to /dev/null (#2494)`,
);
assert.ok(fs.existsSync(errPath), `${legLabel}: stderr must be captured to a .err sidecar (#2494)`);
assert.ok(reviewPath.endsWith('.md'), `${legLabel}: review output path unchanged`);
}
describe('#2494 — gemini reviewer leg fails loudly', { skip: skipReason }, () => {
test('a failing gemini CLI produces a diagnosable stub, not an empty file (model configured)', () => {
const out = runLeg({ binName: 'gemini', block: GEMINI_BLOCK, env: { GEMINI_MODEL: 'gemini-test-model' } });
assertDiagnosable(out, 'Gemini');
});
test('a failing gemini CLI produces a diagnosable stub, not an empty file (no model configured)', () => {
const out = runLeg({ binName: 'gemini', block: GEMINI_BLOCK, env: { GEMINI_MODEL: '' } });
assertDiagnosable(out, 'Gemini');
});
});
describe('#2494 — claude reviewer leg fails loudly', { skip: skipReason }, () => {
test('a failing claude CLI produces a diagnosable stub, not an empty file (model configured)', () => {
const out = runLeg({
binName: 'claude',
block: CLAUDE_BLOCK,
env: { CLAUDE_MODEL: 'claude-test-model', CLAUDE_EFFORT_ARGS: '' },
});
assertDiagnosable(out, 'Claude');
});
test('a failing claude CLI produces a diagnosable stub, not an empty file (no model configured)', () => {
const out = runLeg({
binName: 'claude',
block: CLAUDE_BLOCK,
env: { CLAUDE_MODEL: '', CLAUDE_EFFORT_ARGS: '' },
});
assertDiagnosable(out, 'Claude');
});
});
describe('#2494 — the guard does not swallow a successful review', { skip: skipReason }, () => {
test('gemini stdout is preserved verbatim when the CLI succeeds', () => {
const out = runBlockWithStub({
binName: 'gemini',
block: GEMINI_BLOCK,
stubBody: '#!/bin/sh\necho "## Real Review"\necho "Looks good."\nexit 0\n',
env: { GEMINI_MODEL: '' },
});
const { review } = out;
assert.ok(review !== null, 'a successful review must produce a review file (#2494)');
assert.ok(review.includes('Looks good.'), 'a successful review must pass through untouched (#2494)');
assert.ok(
!/failed or returned empty output/i.test(review),
'the stub must not fire on a non-empty review (#2494)',
);
});
});

View File

@@ -299,7 +299,7 @@
"gsd-core/workflows/remove-phase.md": "23b9eb0858a2535e",
"gsd-core/workflows/remove-workspace.md": "1058d1d3160eb120",
"gsd-core/workflows/resume-project.md": "98e2cf8908e73a52",
"gsd-core/workflows/review.md": "cb4ed7b2b77bba35",
"gsd-core/workflows/review.md": "73ea346aebf61b77",
"gsd-core/workflows/scan.md": "46c5a73f6f682023",
"gsd-core/workflows/secure-phase.md": "9564c529052d1d36",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -370,7 +370,7 @@
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "f67860c795fb4bfe",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "d4f26b05b3eff7f6",
"gsd-core/workflows/review.md": "ed04746925ea035f",
"gsd-core/workflows/scan.md": "e1c12d542e61720d",
"gsd-core/workflows/secure-phase.md": "fda2361739e511ee",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -369,7 +369,7 @@
"gsd-core/workflows/remove-phase.md": "8effc8742d58a11a",
"gsd-core/workflows/remove-workspace.md": "64b73daff58b9aec",
"gsd-core/workflows/resume-project.md": "af9761bcec0f6fe9",
"gsd-core/workflows/review.md": "9eb81c7c61cfe50e",
"gsd-core/workflows/review.md": "7838e12644bf808a",
"gsd-core/workflows/scan.md": "686e787d3704db90",
"gsd-core/workflows/secure-phase.md": "a7272ff8163a61ac",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -298,7 +298,7 @@
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
"gsd-core/workflows/remove-workspace.md": "015cde237c75be39",
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
"gsd-core/workflows/review.md": "bba666ebc5e13130",
"gsd-core/workflows/review.md": "fa72c8f343102dba",
"gsd-core/workflows/scan.md": "a71e3009998cfc57",
"gsd-core/workflows/secure-phase.md": "ba807b4862d7f3c9",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -302,7 +302,7 @@
"gsd-core/workflows/remove-phase.md": "e336350f8113a328",
"gsd-core/workflows/remove-workspace.md": "79d3669cc9eb0b10",
"gsd-core/workflows/resume-project.md": "e23981178fa37b3d",
"gsd-core/workflows/review.md": "b1c5ebc05398ca45",
"gsd-core/workflows/review.md": "2a95ced731e6d14c",
"gsd-core/workflows/scan.md": "f0bd2f64f5530598",
"gsd-core/workflows/secure-phase.md": "1a2e389991fc6263",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -370,7 +370,7 @@
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "f67860c795fb4bfe",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "d4f26b05b3eff7f6",
"gsd-core/workflows/review.md": "ed04746925ea035f",
"gsd-core/workflows/scan.md": "e1c12d542e61720d",
"gsd-core/workflows/secure-phase.md": "fda2361739e511ee",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -405,7 +405,7 @@
"gsd-core/workflows/remove-phase.md": "9ee0fddd11a0d9d4",
"gsd-core/workflows/remove-workspace.md": "d0160c5d05bcb2bb",
"gsd-core/workflows/resume-project.md": "9965f87eb278f7f8",
"gsd-core/workflows/review.md": "6c74f7151f41df8b",
"gsd-core/workflows/review.md": "9987e9fd8f7e5adb",
"gsd-core/workflows/scan.md": "dcd6aac25ef39251",
"gsd-core/workflows/secure-phase.md": "3ba89719288dd3f3",
"gsd-core/workflows/session-report.md": "dd8fa011c9394075",

View File

@@ -300,7 +300,7 @@
"gsd-core/workflows/remove-phase.md": "e262654e319d1bc4",
"gsd-core/workflows/remove-workspace.md": "e7e5b5b1e0cc9d81",
"gsd-core/workflows/resume-project.md": "40db7f350f5866d8",
"gsd-core/workflows/review.md": "31d6ec81b8c8dddc",
"gsd-core/workflows/review.md": "6aad3c5254932206",
"gsd-core/workflows/scan.md": "76aad4e70281c364",
"gsd-core/workflows/secure-phase.md": "d60aa4053154e52f",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -370,7 +370,7 @@
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
"gsd-core/workflows/remove-workspace.md": "0572a83d71650937",
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
"gsd-core/workflows/review.md": "28a82741e3e8616d",
"gsd-core/workflows/review.md": "f5972bbe92dc5f0c",
"gsd-core/workflows/scan.md": "a71e3009998cfc57",
"gsd-core/workflows/secure-phase.md": "b008216148d2afac",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -299,7 +299,7 @@
"gsd-core/workflows/remove-phase.md": "fce799aae3ab2715",
"gsd-core/workflows/remove-workspace.md": "42029a7559f1d8fb",
"gsd-core/workflows/resume-project.md": "a0443839f1f83c2d",
"gsd-core/workflows/review.md": "9a7fd3c1c2bd617d",
"gsd-core/workflows/review.md": "b7aa706def0b93d6",
"gsd-core/workflows/scan.md": "5c2370d6a8118b6c",
"gsd-core/workflows/secure-phase.md": "c087131f1dd12901",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -370,7 +370,7 @@
"gsd-core/workflows/remove-phase.md": "ada8a0546c686483",
"gsd-core/workflows/remove-workspace.md": "f5dc82bb63e2efa4",
"gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96",
"gsd-core/workflows/review.md": "9299d96d44252a8a",
"gsd-core/workflows/review.md": "c5c59f2bb70396ab",
"gsd-core/workflows/scan.md": "c039d3e40d26b606",
"gsd-core/workflows/secure-phase.md": "5a8fbf603d218100",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -327,7 +327,7 @@
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "f67860c795fb4bfe",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "d4f26b05b3eff7f6",
"gsd-core/workflows/review.md": "ed04746925ea035f",
"gsd-core/workflows/scan.md": "e1c12d542e61720d",
"gsd-core/workflows/secure-phase.md": "fda2361739e511ee",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -363,7 +363,7 @@
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "f67860c795fb4bfe",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "d4f26b05b3eff7f6",
"gsd-core/workflows/review.md": "ed04746925ea035f",
"gsd-core/workflows/scan.md": "e1c12d542e61720d",
"gsd-core/workflows/secure-phase.md": "fda2361739e511ee",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -370,7 +370,7 @@
"gsd-core/workflows/remove-phase.md": "dea4661e8f89596f",
"gsd-core/workflows/remove-workspace.md": "21a8581add5f31ae",
"gsd-core/workflows/resume-project.md": "ad9f06a10bab8cc0",
"gsd-core/workflows/review.md": "a2c5d053c8ef6e27",
"gsd-core/workflows/review.md": "486bc0f9ab4c24f4",
"gsd-core/workflows/scan.md": "cbfb79df855e5e61",
"gsd-core/workflows/secure-phase.md": "ef09f40dfd4d2424",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -266,7 +266,7 @@
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "f67860c795fb4bfe",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "d4f26b05b3eff7f6",
"gsd-core/workflows/review.md": "ed04746925ea035f",
"gsd-core/workflows/scan.md": "e1c12d542e61720d",
"gsd-core/workflows/secure-phase.md": "fda2361739e511ee",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -299,7 +299,7 @@
"gsd-core/workflows/remove-phase.md": "e8ae4fbbfac700f0",
"gsd-core/workflows/remove-workspace.md": "ae520235f4d1f4a5",
"gsd-core/workflows/resume-project.md": "7f20769f302e5427",
"gsd-core/workflows/review.md": "5b59389282a276fc",
"gsd-core/workflows/review.md": "3db704f47d7a5bde",
"gsd-core/workflows/scan.md": "b7efd0d381a3b8a5",
"gsd-core/workflows/secure-phase.md": "f7bfa7175102af31",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -299,7 +299,7 @@
"gsd-core/workflows/remove-phase.md": "a46c2fe853bf4e86",
"gsd-core/workflows/remove-workspace.md": "8ddc5f04f7c48d6c",
"gsd-core/workflows/resume-project.md": "f242e4c8aba18ea2",
"gsd-core/workflows/review.md": "2e10545b307c867c",
"gsd-core/workflows/review.md": "b02dae5c743b1797",
"gsd-core/workflows/scan.md": "3b14bcb51d3a4de8",
"gsd-core/workflows/secure-phase.md": "267393d5b02b5334",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -299,7 +299,7 @@
"gsd-core/workflows/remove-phase.md": "e7a6af429b36e77b",
"gsd-core/workflows/remove-workspace.md": "30ca05fe4a3efc25",
"gsd-core/workflows/resume-project.md": "82cfe1b8cb17c085",
"gsd-core/workflows/review.md": "ac0c02f11e2ae1dc",
"gsd-core/workflows/review.md": "f3fdf577dbc08f5f",
"gsd-core/workflows/scan.md": "4a910da5e34f2685",
"gsd-core/workflows/secure-phase.md": "d5d811bc468ce7f2",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -370,7 +370,7 @@
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "f67860c795fb4bfe",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "d4f26b05b3eff7f6",
"gsd-core/workflows/review.md": "ed04746925ea035f",
"gsd-core/workflows/scan.md": "e1c12d542e61720d",
"gsd-core/workflows/secure-phase.md": "fda2361739e511ee",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",

View File

@@ -64,7 +64,7 @@
"remove-phase.md": 8513,
"remove-workspace.md": 7916,
"resume-project.md": 17270,
"review.md": 51210,
"review.md": 52646,
"scan.md": 8314,
"secure-phase.md": 14627,
"session-report.md": 4044,