@opengsd/gsd-core 1.7.0-rc.6 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-executor.md +2 -1
- package/agents/gsd-security-auditor.md +13 -15
- package/bin/install.js +5 -4
- package/commands/gsd/mempalace-capture.md +27 -1
- package/commands/gsd/surface.md +6 -6
- package/gsd-core/bin/gsd-tools.cjs +63 -2
- package/gsd-core/bin/lib/api-coverage.cjs +3 -4
- package/gsd-core/bin/lib/audit.cjs +7 -6
- package/gsd-core/bin/lib/capability-registry.cjs +57 -57
- package/gsd-core/bin/lib/check-command-router.cjs +1 -1
- package/gsd-core/bin/lib/clock.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +48 -9
- package/gsd-core/bin/lib/config-loader.cjs +6 -2
- package/gsd-core/bin/lib/config.cjs +12 -0
- package/gsd-core/bin/lib/core-utils.cjs +8 -2
- package/gsd-core/bin/lib/drift.cjs +4 -4
- package/gsd-core/bin/lib/frontmatter.cjs +22 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
- package/gsd-core/bin/lib/init.cjs +27 -13
- package/gsd-core/bin/lib/install-engine.cjs +3 -2
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +342 -0
- package/gsd-core/bin/lib/markdown-table.cjs +698 -0
- package/gsd-core/bin/lib/milestone.cjs +217 -31
- package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
- package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
- package/gsd-core/bin/lib/phase-locator.cjs +23 -2
- package/gsd-core/bin/lib/phase.cjs +436 -61
- package/gsd-core/bin/lib/plan-scan.cjs +3 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +218 -13
- package/gsd-core/bin/lib/roadmap.cjs +100 -49
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +7 -6
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +2 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +25 -17
- package/gsd-core/bin/lib/schema-detect.cjs +2 -1
- package/gsd-core/bin/lib/security.cjs +1 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +61 -25
- package/gsd-core/bin/lib/smart-entry.cjs +4 -3
- package/gsd-core/bin/lib/state-transition.cjs +100 -45
- package/gsd-core/bin/lib/state.cjs +391 -126
- package/gsd-core/bin/lib/surface.cjs +2 -2
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +54 -8
- package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
- package/gsd-core/bin/lib/verify.cjs +4 -3
- package/gsd-core/bin/lib/workstream.cjs +3 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
- package/gsd-core/bin/lib/write-set.cjs +38 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/checkpoints.md +12 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/debug.md +2 -0
- package/gsd-core/workflows/execute-phase.md +5 -3
- package/gsd-core/workflows/fast.md +8 -22
- package/gsd-core/workflows/progress.md +2 -2
- package/gsd-core/workflows/quick.md +2 -0
- package/gsd-core/workflows/review.md +42 -3
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/ship.md +8 -2
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/hooks/dist/gsd-statusline.js +164 -14
- package/hooks/gsd-statusline.js +164 -14
- package/package.json +4 -2
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -45
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-table-schema-drift.cjs +157 -0
- package/skills/gsd-mempalace-capture/SKILL.md +27 -1
- package/skills/gsd-surface/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -305,8 +305,8 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
305
305
|
// surface-path agents lack path-prefix rewrites and Co-Authored-By trailers,
|
|
306
306
|
// diverging from a fresh install.
|
|
307
307
|
const _homedirFn = opts?.homedir ?? (() => node_os_1.default.homedir());
|
|
308
|
-
const _resolvedTarget = node_path_1.default.resolve(layout.configDir)
|
|
309
|
-
const _homeDir =
|
|
308
|
+
const _resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(layout.configDir));
|
|
309
|
+
const _homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(_homedirFn());
|
|
310
310
|
const _isGlobal = (layout.scope ?? 'global') === 'global';
|
|
311
311
|
const _isOpencode = layout.runtime === 'opencode';
|
|
312
312
|
const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
|
|
@@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
};
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
14
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
16
|
const ioMod = require("./io.cjs");
|
|
16
17
|
const { output, error } = ioMod;
|
|
@@ -85,7 +86,7 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
|
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
const padded = normalizePhaseName(options.phase);
|
|
88
|
-
const today =
|
|
89
|
+
const today = clock_cjs_1.realClock.localToday();
|
|
89
90
|
const phaseName = options.name || phaseInfo.phase_name || 'Unnamed';
|
|
90
91
|
const phaseSlug = phaseInfo.phase_slug || generateSlugInternal(phaseName);
|
|
91
92
|
const phaseId = `${padded}-${phaseSlug}`;
|
package/gsd-core/bin/lib/uat.cjs
CHANGED
|
@@ -21,6 +21,9 @@ const { output, error } = io;
|
|
|
21
21
|
const markdownSectionizer = require("./markdown-sectionizer.cjs");
|
|
22
22
|
const { collectSection, tokenizeHeadings } = markdownSectionizer;
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
|
+
const markdownTable = require("./markdown-table.cjs");
|
|
25
|
+
const { splitTableRow } = markdownTable;
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
27
|
const roadmapParser = require("./roadmap-parser.cjs");
|
|
25
28
|
const { getMilestonePhaseFilter } = roadmapParser;
|
|
26
29
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -292,24 +295,67 @@ function parseVerificationItems(content, status) {
|
|
|
292
295
|
// Use the seam to locate the ## Human Verification section (ADR-1372 T5).
|
|
293
296
|
const hvSection = collectSection(content, (h) => /^human\s+verification/i.test(h.text) && h.level === 2, { levelBounded: true });
|
|
294
297
|
if (hvSection) {
|
|
298
|
+
// #2245 review Fix 3: reverted to the pre-Phase-4 (HEAD 2cbf18642)
|
|
299
|
+
// implementation. The live Human Verification section is NOT a strict
|
|
300
|
+
// GFM table — the planner/verifier templates mix table rows, numbered
|
|
301
|
+
// items, and bullet items in the same section (and a `### N.` heading
|
|
302
|
+
// format is common too), so a table-XOR-list read (parse a table, and
|
|
303
|
+
// if it parses, suppress numbered/bullet items entirely) silently
|
|
304
|
+
// dropped items on any mixed or malformed section: a malformed
|
|
305
|
+
// `| N | … |` table with no valid header/delimiter yielded ZERO items
|
|
306
|
+
// instead of reading the rows positionally. This per-line scan reads
|
|
307
|
+
// table rows AND numbered items AND bullet items as a UNION (whichever
|
|
308
|
+
// pattern a given line matches), exactly like OLD, and reads
|
|
309
|
+
// `| N | desc |` rows even without a valid table header/delimiter.
|
|
310
|
+
//
|
|
311
|
+
// #2245 audit: the table-row branch's CELL SPLIT is name/position-
|
|
312
|
+
// addressed via `splitTableRow` (escape-aware, canonical) instead of a
|
|
313
|
+
// hand-rolled pipe regex — candidacy itself is decided WITHOUT a table
|
|
314
|
+
// regex (a leading `|` plus a purely-numeric first cell), so this no
|
|
315
|
+
// longer needs an allow-adhoc-markdown suppression at all.
|
|
295
316
|
const lines = hvSection.body.split('\n');
|
|
296
317
|
for (const line of lines) {
|
|
297
|
-
|
|
298
|
-
|
|
318
|
+
const trimmedLine = line.trim();
|
|
319
|
+
// Match table rows: | N | description | ... — candidacy requires a
|
|
320
|
+
// leading pipe and a purely-numeric first cell (mirrors what the old
|
|
321
|
+
// regex effectively required: a "|digit|" cell immediately followed
|
|
322
|
+
// by more content), with at least 2 physical cells so a bare "| N |"
|
|
323
|
+
// with nothing after it is NOT treated as a row.
|
|
324
|
+
//
|
|
325
|
+
// #2245 review Fix 9: this is NOT the same as OLD for a row whose
|
|
326
|
+
// ONLY content past the digit cell is trailing whitespace (e.g.
|
|
327
|
+
// "| N | ", no second delimiting `|`). OLD's `([^|]+)` regex ran
|
|
328
|
+
// against the RAW (untrimmed) line and its `\s*` would backtrack to
|
|
329
|
+
// let `[^|]+` swallow that trailing whitespace, so OLD matched and
|
|
330
|
+
// pushed an item with an EMPTY (`.trim()`-collapsed) name. Here,
|
|
331
|
+
// `trimmedLine = line.trim()` strips that trailing whitespace BEFORE
|
|
332
|
+
// `splitTableRow` ever sees it, collapsing the line to a single cell
|
|
333
|
+
// (`candidateCells.length === 1`), which fails the `>= 2` check —
|
|
334
|
+
// the item is silently dropped instead. A real, acceptable behaviour
|
|
335
|
+
// change (an empty-named UAT item is not useful either way), but the
|
|
336
|
+
// two implementations are NOT equivalent on this input.
|
|
337
|
+
let tableCells = null;
|
|
338
|
+
if (trimmedLine.startsWith('|')) {
|
|
339
|
+
const candidateCells = splitTableRow(trimmedLine);
|
|
340
|
+
if (candidateCells.length >= 2 && /^\d+$/.test(candidateCells[0])) {
|
|
341
|
+
tableCells = candidateCells;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
299
344
|
// Match bullet items: - description
|
|
300
345
|
const bulletMatch = line.match(/^[-*]\s+(.+)/);
|
|
301
346
|
// Match numbered items: 1. description
|
|
302
347
|
const numberedMatch = line.match(/^(\d+)\.\s+(.+)/);
|
|
303
|
-
if (
|
|
348
|
+
if (tableCells) {
|
|
304
349
|
// Skip rows that already have a passing result (PASS, pass, resolved, etc.)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
350
|
+
// — checked over every cell AFTER the description column, mirroring
|
|
351
|
+
// OLD's rowRemainder scan (which only ever saw cells past the
|
|
352
|
+
// description, the description itself having already been consumed).
|
|
353
|
+
const hasPassResult = tableCells.slice(2).some(c => /^pass$/i.test(c) || /^resolved$/i.test(c));
|
|
308
354
|
if (hasPassResult)
|
|
309
355
|
continue;
|
|
310
356
|
items.push({
|
|
311
|
-
test: parseInt(
|
|
312
|
-
name:
|
|
357
|
+
test: parseInt(tableCells[0], 10),
|
|
358
|
+
name: tableCells[1] ?? '',
|
|
313
359
|
result: 'human_needed',
|
|
314
360
|
category: 'human_uat',
|
|
315
361
|
});
|
|
@@ -67,14 +67,36 @@ function checkUiPresence(text) {
|
|
|
67
67
|
}
|
|
68
68
|
// Normalise CRLF so the pattern sees consistent line boundaries.
|
|
69
69
|
const normalised = text.replace(/\r\n/g, '\n');
|
|
70
|
+
// #2150: an explicit `**UI hint**: yes|no` metadata line is the author's
|
|
71
|
+
// authoritative declaration of whether the phase has a UI surface — progress.md
|
|
72
|
+
// and new-project.md already parse this line (`UI hint.*yes`). The bare token
|
|
73
|
+
// `UI` in the line itself must not count as a UI indicator, and the declaration
|
|
74
|
+
// overrides token-sniffing. Line-anchored (`m`) so a mid-line prose mention is
|
|
75
|
+
// not treated as the metadata line; word-boundary on the value so `nope`/`not`
|
|
76
|
+
// do not match `no`.
|
|
77
|
+
const hintMatch = normalised.match(/^\s*\*\*UI hint\*\*\s*:\s*(yes|no)\b/im);
|
|
78
|
+
const hint = hintMatch ? hintMatch[1].toLowerCase() : null;
|
|
79
|
+
// Strip ANY `**UI hint**:` line before token-sniffing so a hint without a
|
|
80
|
+
// recognised yes/no (or one we did not short-circuit on) cannot false-positive
|
|
81
|
+
// on the bare `UI` token.
|
|
82
|
+
const sniffable = normalised
|
|
83
|
+
.split('\n')
|
|
84
|
+
.filter((line) => !/^\s*\*\*UI hint\*\*\s*:/i.test(line))
|
|
85
|
+
.join('\n');
|
|
70
86
|
const found = new Set();
|
|
71
|
-
for (const line of
|
|
87
|
+
for (const line of sniffable.split('\n')) {
|
|
72
88
|
// Reset lastIndex before each line so the global pattern restarts from 0.
|
|
73
89
|
UI_GATE_PATTERN_GLOBAL.lastIndex = 0;
|
|
74
90
|
for (const m of line.matchAll(UI_GATE_PATTERN_GLOBAL)) {
|
|
75
91
|
found.add(m[2].toLowerCase());
|
|
76
92
|
}
|
|
77
93
|
}
|
|
94
|
+
if (hint === 'no') {
|
|
95
|
+
return { hasUI: false, tokens: [] };
|
|
96
|
+
}
|
|
97
|
+
if (hint === 'yes') {
|
|
98
|
+
return { hasUI: true, tokens: [...found] };
|
|
99
|
+
}
|
|
78
100
|
return { hasUI: found.size > 0, tokens: [...found] };
|
|
79
101
|
}
|
|
80
102
|
// ── CLI entry point ─────────────────────────────────────────────────────────
|
|
@@ -13,6 +13,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const node_os_1 = __importDefault(require("node:os"));
|
|
15
15
|
const validate_cjs_1 = require("./validate.cjs");
|
|
16
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
16
17
|
const validate_cjs_2 = require("./validate.cjs");
|
|
17
18
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
18
19
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -1049,7 +1050,7 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
1049
1050
|
.sort();
|
|
1050
1051
|
for (const dir of dirs) {
|
|
1051
1052
|
const phasePath = node_path_1.default.join(phaseRoot, dir);
|
|
1052
|
-
const phaseLabel = node_path_1.default.relative(planBase, phasePath)
|
|
1053
|
+
const phaseLabel = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.relative(planBase, phasePath));
|
|
1053
1054
|
const phaseFiles = node_fs_1.default.readdirSync(phasePath);
|
|
1054
1055
|
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md')).sort();
|
|
1055
1056
|
const planNums = plans
|
|
@@ -1591,7 +1592,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1591
1592
|
stateContent += `**Current phase:** (determining...)\n`;
|
|
1592
1593
|
stateContent += `**Status:** Resuming\n\n`;
|
|
1593
1594
|
stateContent += `## Session Log\n\n`;
|
|
1594
|
-
stateContent += `- ${
|
|
1595
|
+
stateContent += `- ${clock_cjs_1.realClock.localToday()}: STATE.md regenerated by ${slash('health')} --repair\n`;
|
|
1595
1596
|
writeStateMd(statePath, stateContent, cwd);
|
|
1596
1597
|
repairActions.push({ action: repair, success: true, path: 'STATE.md' });
|
|
1597
1598
|
break;
|
|
@@ -1647,7 +1648,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1647
1648
|
case 'backfillMilestones': {
|
|
1648
1649
|
if (!options['backfill'] && !options['repair'])
|
|
1649
1650
|
break;
|
|
1650
|
-
const today =
|
|
1651
|
+
const today = clock_cjs_1.realClock.localToday();
|
|
1651
1652
|
let backfilled = 0;
|
|
1652
1653
|
for (const ver of missingFromRegistry) {
|
|
1653
1654
|
try {
|
|
@@ -17,6 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
};
|
|
18
18
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
19
|
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
22
|
const io = require("./io.cjs");
|
|
22
23
|
const { output, error } = io;
|
|
@@ -154,7 +155,7 @@ function cmdWorkstreamCreate(cwd, name, options, raw) {
|
|
|
154
155
|
}
|
|
155
156
|
(0, shell_command_projection_cjs_1.platformEnsureDir)(wsDir);
|
|
156
157
|
(0, shell_command_projection_cjs_1.platformEnsureDir)(node_path_1.default.join(wsDir, 'phases'));
|
|
157
|
-
const today =
|
|
158
|
+
const today = clock_cjs_1.realClock.localToday();
|
|
158
159
|
const stateContent = [
|
|
159
160
|
'---',
|
|
160
161
|
`workstream: ${slug}`,
|
|
@@ -264,7 +265,7 @@ function cmdWorkstreamComplete(cwd, name, options, raw) {
|
|
|
264
265
|
if (active === name)
|
|
265
266
|
setActiveWorkstream(cwd, null);
|
|
266
267
|
const archiveDir = node_path_1.default.join(root, 'milestones');
|
|
267
|
-
const today =
|
|
268
|
+
const today = clock_cjs_1.realClock.localToday();
|
|
268
269
|
let archivePath = node_path_1.default.join(archiveDir, `ws-${name}-${today}`);
|
|
269
270
|
let suffix = 1;
|
|
270
271
|
while (node_fs_1.default.existsSync(archivePath)) {
|
|
@@ -440,7 +440,7 @@ function rescueSummaryArtifacts(worktreePath, repoRoot, deps) {
|
|
|
440
440
|
// relPath is the path relative to the worktree root (e.g. ".planning/q1-SUMMARY.md")
|
|
441
441
|
// Normalize to forward slashes so the Set comparison against `git status --porcelain`
|
|
442
442
|
// output works on Windows too (git always emits forward slashes in porcelain output).
|
|
443
|
-
const relPath = absPath.slice(worktreePath.length).replace(/^[/\\]/, '')
|
|
443
|
+
const relPath = (0, shell_command_projection_cjs_1.posixNormalize)(absPath.slice(worktreePath.length).replace(/^[/\\]/, ''));
|
|
444
444
|
// #706: skip rescue when the SUMMARY is already committed on the branch.
|
|
445
445
|
// Use `git cat-file -e HEAD:<relPath>` (not `ls-files --error-unmatch`) so
|
|
446
446
|
// the check is against the committed tree, not the index. ls-files also
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Write-Set — shared fail-loud parse `Result` and per-surface write-set
|
|
4
|
+
* contracts (ADR-2143, epic #2143). Pure, Node built-ins only, no I/O.
|
|
5
|
+
* Compiled by tsc to gsd-core/bin/lib/write-set.cjs.
|
|
6
|
+
*
|
|
7
|
+
* ADR-2143 §5 (fail-loud parsing, no null-swallow): seam parse operations
|
|
8
|
+
* and document-model accessors return a typed `Result<T>` — never a bare
|
|
9
|
+
* `null` a caller can mistake for "empty but fine." This is the same
|
|
10
|
+
* `{ ok: true; value: T } | { ok: false; reason: string }` shape
|
|
11
|
+
* `markdown-table.cts` already defined for `parseMarkdownTable` /
|
|
12
|
+
* `appendQuickTaskRow`; this module is now the single source of truth for
|
|
13
|
+
* it and `markdown-table.cjs` re-exports the type so existing importers of
|
|
14
|
+
* `Result` from that module keep working unchanged.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: deliberately distinct from command-routing-hub's dispatch `Result`
|
|
17
|
+
* (`{ok,data}|{ok:false,kind}`) — the two never mix (different modules,
|
|
18
|
+
* different shapes, different purposes).
|
|
19
|
+
*
|
|
20
|
+
* ADR-2143 §6 (write-set results for multi-surface commands, no
|
|
21
|
+
* OR-into-one-flag): a command that mutates more than one surface returns
|
|
22
|
+
* an explicit per-surface write-set — `{ surface, applied }` outcomes — and
|
|
23
|
+
* its top-level "did this fully succeed" signal is true only if EVERY
|
|
24
|
+
* surface in the set applied. ORing independent surfaces into a single
|
|
25
|
+
* boolean is the direct anti-pattern that let a checkbox-only partial
|
|
26
|
+
* write (#2140) report full success.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.writeSetComplete = writeSetComplete;
|
|
30
|
+
/**
|
|
31
|
+
* True only if the write-set is non-empty AND every surface in it applied.
|
|
32
|
+
* An empty write-set is never "complete" — there is nothing to be complete
|
|
33
|
+
* about, so treating it as vacuously true would let a no-op masquerade as
|
|
34
|
+
* a full success (the same OR-into-one-flag class ADR-2143 §6 prohibits).
|
|
35
|
+
*/
|
|
36
|
+
function writeSetComplete(ws) {
|
|
37
|
+
return ws.length > 0 && ws.every((o) => o.applied);
|
|
38
|
+
}
|
|
@@ -9,6 +9,18 @@ Plans execute autonomously. Checkpoints formalize interaction points where human
|
|
|
9
9
|
3. **User only does what requires human judgment** - Visual checks, UX evaluation, "does this feel right?"
|
|
10
10
|
4. **Secrets come from user, automation comes from Claude** - Ask for API keys, then Claude uses them via CLI
|
|
11
11
|
5. **Auto-mode bypasses verification/decision checkpoints** — When `workflow._auto_chain_active` or `workflow.auto_advance` is true in config: human-verify auto-approves, decision auto-selects first option, human-action still stops (auth gates cannot be automated)
|
|
12
|
+
6. **`gate="blocking-human"` is never auto-approved** — a checkpoint carrying this gate stops for a human in *every* mode, including auto-mode, regardless of its type. Rule 5 does not apply to it.
|
|
13
|
+
|
|
14
|
+
**The `gate` attribute:**
|
|
15
|
+
|
|
16
|
+
| Value | Auto-mode behavior | Use for |
|
|
17
|
+
|-------|--------------------|---------|
|
|
18
|
+
| `gate="blocking"` | Bypassed per rule 5 (human-verify auto-approves, decision auto-selects) | The default. Post-hoc verification and implementation choices that are safe to take the recommended path on when unattended. |
|
|
19
|
+
| `gate="blocking-human"` | **Never bypassed.** Stops for a human in auto-mode too. | Irreversible or trust-establishing steps a human must actually see: package-legitimacy verification before install, and any decision whose default answer would be wrong to assume. |
|
|
20
|
+
|
|
21
|
+
Reach for `gate="blocking-human"` whenever auto-approving the checkpoint would defeat its purpose. If the checkpoint exists because a human must *decide* something, `blocking` is the wrong gate — auto-mode will decide it for them.
|
|
22
|
+
|
|
23
|
+
The gate spans two layers, and both must honor it. `gsd-executor` refuses to auto-approve a `gate="blocking-human"` checkpoint and escalates it via `checkpoint_return_format` precisely so a human sees it; `execute-phase`'s `checkpoint_handling` step then decides what the user is actually shown. An orchestrator that dispatches on checkpoint *type* alone would auto-approve the very checkpoint the executor just refused to auto-approve, nullifying that refusal one layer up and letting an unattended `--auto` / `--chain` run install a package no human ever vetted.
|
|
12
24
|
</overview>
|
|
13
25
|
|
|
14
26
|
<checkpoint_types>
|
|
@@ -308,7 +308,7 @@ If there are passing tests to commit:
|
|
|
308
308
|
|
|
309
309
|
```bash
|
|
310
310
|
git add {test files}
|
|
311
|
-
git commit -m "test(phase-${phase_number}): add unit and E2E tests from add-tests command"
|
|
311
|
+
git commit -m "test(phase-${phase_number}): add unit and E2E tests from add-tests command" -- {test files}
|
|
312
312
|
```
|
|
313
313
|
|
|
314
314
|
Present next steps:
|
|
@@ -190,6 +190,8 @@ Create `.planning/debug/{slug}.md` with initial state using the Write tool (neve
|
|
|
190
190
|
|
|
191
191
|
After initial context setup, spawn the session manager to handle the full checkpoint/continuation loop. The session manager handles specialist_hint dispatch internally: when gsd-debugger returns ROOT CAUSE FOUND it extracts the specialist_hint field and invokes the matching skill (e.g. typescript-expert, swift-concurrency) before offering fix options.
|
|
192
192
|
|
|
193
|
+
> **Foreground, blocking spawn — #2196.** The `Agent(subagent_type="gsd-debug-session-manager", …)` call below is FOREGROUND and BLOCKING — it returns the compact session summary directly. Wait for it; do not background it, and do not poll for it. Never pass an agent or session identifier to `TaskOutput` — an agent ID is NOT a task ID, so `TaskOutput <agent-id>` always returns `No task found with ID`. If the spawn returns no usable result (the handoff is lost), do NOT claim the session is still running: preserve the checkpoint at `.planning/debug/{slug}.md`, report the failed handoff plainly, and resume by re-spawning the session manager or via `/gsd:debug continue {slug}`.
|
|
194
|
+
|
|
193
195
|
Print before spawning (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
|
|
194
196
|
```
|
|
195
197
|
[debug] Delegating loop to session manager...
|
|
@@ -1056,11 +1056,13 @@ AUTO_MODE=$(gsd_run query check auto-mode --pick active 2>/dev/null || echo "fal
|
|
|
1056
1056
|
```
|
|
1057
1057
|
|
|
1058
1058
|
When executor returns a checkpoint AND `AUTO_MODE` is `true`:
|
|
1059
|
-
- **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`.
|
|
1060
|
-
- **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`.
|
|
1059
|
+
- **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`. **Except `blocking-human`.**
|
|
1060
|
+
- **decision** → Auto-spawn continuation agent with `{user_response}` = first option from checkpoint details. Log `⚡ Auto-selected: [option]`. **Except `blocking-human`.**
|
|
1061
1061
|
- **human-action** → Present to user (existing behavior below). Auth gates cannot be automated.
|
|
1062
1062
|
|
|
1063
|
-
**
|
|
1063
|
+
**Carve-out — overrides all branches above.** If the returned `Gate:` is `blocking-human`, or its `<what-built>` mentions `Package verification required before install` or `Package install failed — human verification required`, never auto-approve or auto-select, regardless of type. Present to user (standard flow below). Log `⛔ blocking-human gate — auto-mode suspended`.
|
|
1064
|
+
|
|
1065
|
+
**Standard flow (not auto-mode, human-action, or blocking-human):**
|
|
1064
1066
|
|
|
1065
1067
|
1. Spawn agent for checkpoint plan
|
|
1066
1068
|
2. Agent runs until checkpoint task or auth gate → returns structured state
|
|
@@ -64,32 +64,18 @@ Use conventional commit format: `fix:`, `feat:`, `docs:`, `chore:`, `refactor:`
|
|
|
64
64
|
|
|
65
65
|
<step name="log_to_state">
|
|
66
66
|
If `.planning/STATE.md` exists and has a "Quick Tasks Completed" table, append a row
|
|
67
|
-
that matches the existing table's schema
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
that matches the existing table's schema via the schema-backed `gsd-tools
|
|
68
|
+
quick-tasks-append` helper (`markdown-table.cjs`'s `appendQuickTaskRow`; #2133,
|
|
69
|
+
ADR-2143 §3/§7). If no table exists, skip silently. If the table's schema is
|
|
70
|
+
unrecognized, the helper fails loud (non-zero exit) instead of silently guessing
|
|
71
|
+
a column count — this replaces the prior inline `awk NF-2` arithmetic that was
|
|
72
|
+
the root cause of #2133.
|
|
70
73
|
|
|
71
74
|
```bash
|
|
75
|
+
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
72
76
|
# Detect whether STATE.md has a Quick Tasks Completed table
|
|
73
77
|
if grep -q "Quick Tasks Completed" .planning/STATE.md 2>/dev/null; then
|
|
74
|
-
|
|
75
|
-
# quick.md Step 7 creates a 5-column table:
|
|
76
|
-
# | # | Description | Date | Commit | Directory |
|
|
77
|
-
# Count pipe characters in the header to determine column count.
|
|
78
|
-
HEADER_LINE=$(grep -A2 "Quick Tasks Completed" .planning/STATE.md 2>/dev/null | grep "^|" | head -1)
|
|
79
|
-
# Count columns: number of | separators minus 1 gives column count
|
|
80
|
-
COL_COUNT=$(echo "$HEADER_LINE" | awk -F'|' '{print NF-1}')
|
|
81
|
-
|
|
82
|
-
if [ "$COL_COUNT" -eq 5 ] && echo "$HEADER_LINE" | grep -qi "Description" && echo "$HEADER_LINE" | grep -qi "Commit" && echo "$HEADER_LINE" | grep -qi "Directory"; then
|
|
83
|
-
# 5-column schema from quick.md Step 7: | # | Description | Date | Commit | Directory |
|
|
84
|
-
# Determine the next row number by counting existing data rows (non-separator, non-header).
|
|
85
|
-
NEXT_NUM=$(awk '/Quick Tasks Completed/{found=1} found && /^\|/ && !/^[|][-: |]*[|]$/ && !/Description/{count++} END{print count+1}' .planning/STATE.md 2>/dev/null || echo "1")
|
|
86
|
-
# Get the latest commit hash (short)
|
|
87
|
-
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "—")
|
|
88
|
-
echo "| $NEXT_NUM | $TASK | $(date +%Y-%m-%d) | $COMMIT_HASH | — |" >> .planning/STATE.md
|
|
89
|
-
else
|
|
90
|
-
# Unrecognized table schema — skip to avoid appending a malformed row.
|
|
91
|
-
echo "⚠ fast.md log_to_state: Quick Tasks Completed table has unrecognized schema (${COL_COUNT} columns); skipping STATE.md update."
|
|
92
|
-
fi
|
|
78
|
+
gsd_run quick-tasks-append --task "$TASK" || echo "⚠ fast.md log_to_state: could not append Quick Tasks row (see message above); continuing."
|
|
93
79
|
fi
|
|
94
80
|
```
|
|
95
81
|
</step>
|
|
@@ -536,7 +536,7 @@ State: "Current phase is {X}. Milestone has {N} phases (highest: {Y})."
|
|
|
536
536
|
| Condition | Meaning | Action |
|
|
537
537
|
|-----------|---------|--------|
|
|
538
538
|
| current phase < highest phase | More phases remain | Go to **Route C** |
|
|
539
|
-
| current phase = highest phase |
|
|
539
|
+
| current phase = highest phase | All phases complete | Go to **Route D** |
|
|
540
540
|
|
|
541
541
|
---
|
|
542
542
|
|
|
@@ -602,7 +602,7 @@ NEXT_HAS_UI=$(echo "$NEXT_PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true
|
|
|
602
602
|
|
|
603
603
|
---
|
|
604
604
|
|
|
605
|
-
**Route D:
|
|
605
|
+
**Route D: All phases complete (milestone ready to close)**
|
|
606
606
|
|
|
607
607
|
```
|
|
608
608
|
---
|
|
@@ -971,6 +971,8 @@ Use `date` from init:
|
|
|
971
971
|
| ${quick_id} | ${DESCRIPTION} | ${date} | ${commit_hash} | [${quick_id}-${slug}](./quick/${quick_id}-${slug}/) |
|
|
972
972
|
```
|
|
973
973
|
|
|
974
|
+
For a schema-safe append outside this workflow (e.g. from fast.md), `gsd-tools quick-tasks-append --task <text>` performs the equivalent write via the shared, schema-backed `appendQuickTaskRow` helper (#2133, ADR-2143 §3/§7).
|
|
975
|
+
|
|
974
976
|
**7d. Update "Last activity" line:**
|
|
975
977
|
|
|
976
978
|
Use `date` from init:
|
|
@@ -255,6 +255,8 @@ shell-interpolated). Exact invocation in `gsd-core/references/reviewer-instances
|
|
|
255
255
|
|
|
256
256
|
For each selected CLI, invoke in sequence (not parallel — avoid rate limits):
|
|
257
257
|
|
|
258
|
+
**Timeout guidance (#2194):** prompt-fed source-grounded reviews are slow — measured ~570s for Codex at `xhigh` effort and ~525s for headless Claude on a large plan set. Each of the Gemini / Claude / Codex blocks below MUST be invoked with a high Bash `timeout:` — at least `900000` (15 min), and `1200000` (20 min) for Codex `xhigh` or headless Claude — so a lane is not killed mid-review. On Claude Code, raise the host cap via `BASH_MAX_TIMEOUT_MS` if a review can exceed it. A silent empty output after a long run is a **timeout kill, not a crash** — the Codex `0xc0000142` misdiagnosis persisted because the empty-output branches below cannot distinguish the two; treat an empty result on a slow lane as a dropped lane and re-run with more time rather than diagnosing a CLI/sandbox failure. A cross-AI review that silently drops a lane is blind in one eye.
|
|
259
|
+
|
|
258
260
|
**Gemini:**
|
|
259
261
|
```bash
|
|
260
262
|
if [ -n "$GEMINI_MODEL" ] && [ "$GEMINI_MODEL" != "null" ]; then
|
|
@@ -364,7 +366,12 @@ fi
|
|
|
364
366
|
# prompt as an ARGUMENT, not stdin. A full review prompt can exceed the OS argument limit, so
|
|
365
367
|
# reference the prompt file by path rather than inlining it. Capture stderr so a failure is
|
|
366
368
|
# diagnosable instead of a silent empty result.
|
|
367
|
-
|
|
369
|
+
# #2176: same absolute-root anchor as the Antigravity block — cursor-agent runs
|
|
370
|
+
# in the repo cwd, but repo-relative references in the assembled prompt still
|
|
371
|
+
# need an explicit root to resolve against. rev-parse (not bare pwd) so the
|
|
372
|
+
# anchor is correct even when /gsd:review is invoked from a repo subdirectory.
|
|
373
|
+
_CURSOR_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
374
|
+
CURSOR_PROMPT_ARG="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. The repository under review is at $_CURSOR_ROOT — resolve every relative file path in the review request against that absolute root. Output only the resulting markdown review. Do not edit any files."
|
|
368
375
|
cursor-agent -p --mode ask --trust --output-format text "$CURSOR_PROMPT_ARG" 2>/tmp/gsd-review-cursor-{phase}.err > /tmp/gsd-review-cursor-{phase}.md
|
|
369
376
|
if [ ! -s /tmp/gsd-review-cursor-{phase}.md ]; then
|
|
370
377
|
echo "Cursor review failed or returned empty output. stderr:" > /tmp/gsd-review-cursor-{phase}.md
|
|
@@ -456,7 +463,19 @@ if [ -n "$AGY_MODEL" ] && [ "$AGY_MODEL" != "null" ]; then
|
|
|
456
463
|
else
|
|
457
464
|
set --
|
|
458
465
|
fi
|
|
459
|
-
|
|
466
|
+
# #2176: grant the reviewer the repo under review. Without --add-dir, agy's
|
|
467
|
+
# permission context never receives the cwd repo — the agent anchors on its own
|
|
468
|
+
# ~/.gemini/antigravity-cli/scratch dir and reviews the plan text in isolation
|
|
469
|
+
# (the exact failure the Review Instructions forbid). Capability-probed like the
|
|
470
|
+
# Codex bypass flag so an older agy without --add-dir still runs; the prompt
|
|
471
|
+
# anchor below keeps absolute-path reads possible on that fallback.
|
|
472
|
+
if agy --help 2>/dev/null | grep -q -- '--add-dir'; then
|
|
473
|
+
set -- "$@" --add-dir "$_AGY_WS"
|
|
474
|
+
fi
|
|
475
|
+
# #2176: anchor the prompt to the absolute repo root so repo-relative references
|
|
476
|
+
# in the assembled review prompt resolve even on the no---add-dir fallback, and
|
|
477
|
+
# require an explicit self-report if the reviewer still cannot read the repo.
|
|
478
|
+
_AGY_PROMPT="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. The repository under review is at $_AGY_WS — resolve every relative file path in the review request against that absolute root and verify claims against those files. If you cannot read files under $_AGY_WS, begin your output with the exact line REVIEWED-WITHOUT-REPO-ACCESS before the review. Output only the resulting markdown review. Do not edit any files."
|
|
460
479
|
# Capability-probe an external wall-clock killer (GNU coreutils `timeout` or the
|
|
461
480
|
# macOS Homebrew `gtimeout`). Stock macOS ships NEITHER — a bare `timeout …` would
|
|
462
481
|
# fail with rc 127 ("command not found") and silently lose the reviewer, so fall
|
|
@@ -523,6 +542,26 @@ if [ ! -s /tmp/gsd-review-antigravity-{phase}.md ]; then
|
|
|
523
542
|
echo "If no agy run started, that is the pre-session-stall case: check whether a new ~/.gemini/antigravity-cli/brain/<conv-id>/ dir appeared within ~30s of launch."
|
|
524
543
|
} > /tmp/gsd-review-antigravity-{phase}.md
|
|
525
544
|
fi
|
|
545
|
+
|
|
546
|
+
# #2176: blind-review marker. Two tells that the reviewer ran without repo
|
|
547
|
+
# access: the prompt's mandated REVIEWED-WITHOUT-REPO-ACCESS self-report in the
|
|
548
|
+
# first lines of output, or the agent DECLARING the scratch dir as its
|
|
549
|
+
# workspace. Both patterns are anchored — the self-report to the head of the
|
|
550
|
+
# file, the scratch tell to a workspace-declaration phrasing — so a grounded
|
|
551
|
+
# review that merely QUOTES these strings (e.g. reviewing this very file) is
|
|
552
|
+
# never mis-stamped. Stamp a machine-readable marker so the Consensus Summary
|
|
553
|
+
# down-weights the review instead of counting an ungrounded verdict at full
|
|
554
|
+
# weight. (Temp file + mv, no in-place sed — BSD/GNU safe.)
|
|
555
|
+
if [ -s /tmp/gsd-review-antigravity-{phase}.md ] && \
|
|
556
|
+
{ head -5 /tmp/gsd-review-antigravity-{phase}.md | grep -q 'REVIEWED-WITHOUT-REPO-ACCESS' || \
|
|
557
|
+
grep -qiE '(workspace|working) (directory|dir).{0,40}antigravity-cli/scratch' /tmp/gsd-review-antigravity-{phase}.md; }; then
|
|
558
|
+
{
|
|
559
|
+
echo "> [reviewed-without-repo-access] This reviewer ran without visibility into the repo under review — down-weight its verdict in the Consensus Summary."
|
|
560
|
+
echo ""
|
|
561
|
+
cat /tmp/gsd-review-antigravity-{phase}.md
|
|
562
|
+
} > /tmp/gsd-review-antigravity-{phase}.md.tmp && \
|
|
563
|
+
mv /tmp/gsd-review-antigravity-{phase}.md.tmp /tmp/gsd-review-antigravity-{phase}.md
|
|
564
|
+
fi
|
|
526
565
|
```
|
|
527
566
|
|
|
528
567
|
**Ollama (local, OpenAI-compatible):**
|
|
@@ -835,7 +874,7 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
|
|
|
835
874
|
|
|
836
875
|
## Consensus Summary
|
|
837
876
|
|
|
838
|
-
{synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers.}
|
|
877
|
+
{synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers. A reviewer output carrying the `[reviewed-without-repo-access]` marker (or beginning with `REVIEWED-WITHOUT-REPO-ACCESS`) ran without repo access (#2176) — treat it the same way: note its concerns, but do not count its verdict at full consensus weight.}
|
|
839
878
|
|
|
840
879
|
### Agreed Strengths
|
|
841
880
|
{strengths mentioned by 2+ reviewers}
|
|
@@ -108,7 +108,7 @@ Agent(
|
|
|
108
108
|
"<files_to_read>{PLAN, SUMMARY, impl files, SECURITY.md}</files_to_read>" +
|
|
109
109
|
"<threat_register>{threat register}</threat_register>" +
|
|
110
110
|
"<config>asvs_level: {SECURITY_ASVS}, block_on: {SECURITY_BLOCK_ON}</config>" +
|
|
111
|
-
"<constraints>Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps.</constraints>" +
|
|
111
|
+
"<constraints>Never modify implementation files. Verify mitigations exist — do not scan for new threats. Escalate implementation gaps. Return a structured verdict only — do NOT write SECURITY.md (the orchestrator owns the file write).</constraints>" +
|
|
112
112
|
"${AGENT_SKILLS_AUDITOR}",
|
|
113
113
|
subagent_type="gsd-security-auditor",
|
|
114
114
|
model="{AUDITOR_MODEL}",
|
|
@@ -394,9 +394,15 @@ gsd_run query state.update "Last Activity" "$(date +%Y-%m-%d)"
|
|
|
394
394
|
gsd_run query state.update "Status" "Phase ${PHASE_NUMBER} shipped — PR #${PR_NUMBER}"
|
|
395
395
|
```
|
|
396
396
|
|
|
397
|
-
If `commit_docs` is true
|
|
397
|
+
If `commit_docs` is true, commit the ship-note AND push it onto the PR branch so
|
|
398
|
+
it reaches the default branch when the PR merges. Without this push the ship-note
|
|
399
|
+
commit stays local-only and is silently discarded when the branch is deleted on
|
|
400
|
+
merge (#2138). The `[ci skip]` trailer suppresses the redundant pipeline the push
|
|
401
|
+
would otherwise trigger (GitHub honors `[ci skip]` / `[skip ci]`):
|
|
402
|
+
|
|
398
403
|
```bash
|
|
399
|
-
gsd_run query commit "docs(${padded_phase}): ship phase ${PHASE_NUMBER} — PR #${PR_NUMBER}" --files .planning/STATE.md
|
|
404
|
+
gsd_run query commit "docs(${padded_phase}): ship phase ${PHASE_NUMBER} — PR #${PR_NUMBER} [ci skip]" --files .planning/STATE.md
|
|
405
|
+
git push origin ${CURRENT_BRANCH} 2>&1 || echo "⚠ track_shipping: ship-note push failed — it is local-only; rerun: git push origin ${CURRENT_BRANCH}"
|
|
400
406
|
```
|
|
401
407
|
</step>
|
|
402
408
|
|
|
@@ -456,7 +456,7 @@ Write to: `{phase_dir}/{padded_phase}-SPEC.md`
|
|
|
456
456
|
|
|
457
457
|
```bash
|
|
458
458
|
git add "${phase_dir}/${padded_phase}-SPEC.md"
|
|
459
|
-
git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)"
|
|
459
|
+
git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)" -- "${phase_dir}/${padded_phase}-SPEC.md"
|
|
460
460
|
```
|
|
461
461
|
|
|
462
462
|
If `commit_docs` is false: Skip commit. Note that SPEC.md was written but not committed.
|
|
@@ -597,7 +597,7 @@ Do NOT auto-invoke any further slash commands.
|
|
|
597
597
|
|
|
598
598
|
---
|
|
599
599
|
|
|
600
|
-
**Route B:
|
|
600
|
+
**Route B: All phases complete (milestone ready to close)**
|
|
601
601
|
|
|
602
602
|
**This route is only reached when:**
|
|
603
603
|
- `is_last_phase: true` AND no other active workstreams exist (or flat mode)
|