@opengsd/gsd-core 1.3.0 → 1.4.0-rc.1
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/agents/gsd-advisor-researcher.md +1 -20
- package/agents/gsd-ai-researcher.md +1 -20
- package/agents/gsd-domain-researcher.md +1 -20
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-phase-researcher.md +92 -166
- package/agents/gsd-planner.md +9 -36
- package/agents/gsd-project-researcher.md +62 -141
- package/agents/gsd-ui-researcher.md +2 -21
- package/agents/gsd-verifier.md +8 -2
- package/bin/install.js +85 -4
- package/commands/gsd/graphify.md +11 -6
- package/commands/gsd/import.md +6 -2
- package/commands/gsd/plan-phase.md +2 -2
- package/gsd-core/bin/check-latest-version.cjs +3 -2
- package/gsd-core/bin/gsd-tools.cjs +238 -32
- package/gsd-core/bin/lib/check-command-router.cjs +1 -0
- package/gsd-core/bin/lib/cli-exit.cjs +42 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +1 -1
- package/gsd-core/bin/lib/commands.cjs +5 -4
- package/gsd-core/bin/lib/config.cjs +28 -4
- package/gsd-core/bin/lib/core.cjs +72 -28
- package/gsd-core/bin/lib/graphify.cjs +2 -2
- package/gsd-core/bin/lib/init-command-router.cjs +2 -2
- package/gsd-core/bin/lib/init.cjs +19 -3
- package/gsd-core/bin/lib/installer-migrations.cjs +61 -22
- package/gsd-core/bin/lib/intel.cjs +3 -20
- package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
- package/gsd-core/bin/lib/phase.cjs +3 -3
- package/gsd-core/bin/lib/research-provider.cjs +137 -0
- package/gsd-core/bin/lib/research-store.cjs +167 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +4 -19
- package/gsd-core/bin/lib/security.cjs +73 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +3 -0
- package/gsd-core/bin/lib/validate.cjs +2 -2
- package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
- package/gsd-core/bin/lib/verification.cjs +193 -0
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -1
- package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +31 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +8 -11
- package/gsd-core/references/planner-load-graph-context.md +36 -0
- package/gsd-core/references/planning-config.md +3 -1
- package/gsd-core/references/research-documentation-lookup.md +29 -0
- package/gsd-core/references/research-philosophy.md +29 -0
- package/gsd-core/references/research-verification-protocol.md +27 -0
- package/gsd-core/workflows/execute-phase.md +19 -8
- package/gsd-core/workflows/help/modes/full.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -2
- package/gsd-core/workflows/plan-phase.md +14 -10
- package/gsd-core/workflows/plan-review-convergence.md +3 -3
- package/gsd-core/workflows/review.md +22 -5
- package/gsd-core/workflows/ship.md +5 -8
- package/gsd-core/workflows/spec-phase.md +2 -1
- package/gsd-core/workflows/update.md +2 -1
- package/hooks/dist/gsd-context-monitor.js +1 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -0
- package/hooks/dist/gsd-worktree-path-guard.js +1 -1
- package/hooks/gsd-context-monitor.js +1 -1
- package/hooks/gsd-workflow-guard.js +1 -0
- package/hooks/gsd-worktree-path-guard.js +1 -1
- package/package.json +4 -1
- package/scripts/affected-tests-lib.cjs +3 -2
- package/scripts/changeset/cli.cjs +183 -28
- package/scripts/changeset/lint.cjs +5 -4
- package/scripts/changeset/new.cjs +4 -4
- package/scripts/check-alias-drift.cjs +77 -71
- package/scripts/check-env.cjs +185 -179
- package/scripts/check-npm-integrity.cjs +115 -109
- package/scripts/ci-guard-runner.cjs +11 -5
- package/scripts/ci-prepare-test-scope.cjs +27 -22
- package/scripts/ci-rebase-check.cjs +46 -45
- package/scripts/ci-test-scope.cjs +6 -4
- package/scripts/diff-touches-shipped-paths.cjs +52 -44
- package/scripts/gen-inventory-manifest.cjs +38 -32
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-command-contract.cjs +28 -22
- package/scripts/lint-descriptions.cjs +32 -28
- package/scripts/lint-docs-required.cjs +4 -4
- package/scripts/lint-legacy-dir-name.cjs +56 -52
- package/scripts/lint-pr-check-project-dir.cjs +3 -1
- package/scripts/lint-shell-command-projection-drift.cjs +27 -22
- package/scripts/lint-skill-deps.cjs +31 -26
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/lint-test-file-count.cjs +5 -4
- package/scripts/mutation-matrix.cjs +6 -3
- package/scripts/prompt-injection-scan.sh +1 -1
- package/scripts/release-notes/format-github-release-notes.cjs +8 -3
- package/scripts/release-tarball-smoke.cjs +6 -4
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +2 -1
- package/scripts/run-cross-platform-tests.cjs +11 -7
- package/scripts/run-tests.cjs +8 -7
- package/scripts/strip-prose-atrefs.cjs +1 -1
- package/scripts/sync-runtime-launcher.cjs +0 -3
- package/scripts/verify-npm-publish.cjs +14 -26
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Worktree base-ref detection and degradation logic (issue #683).
|
|
4
|
+
*
|
|
5
|
+
* Determines whether a worktree's HEAD has drifted from the fork base that the
|
|
6
|
+
* Claude Code harness would use to create a 'fresh' parallel worktree. When
|
|
7
|
+
* drift is detected the caller should fall back to sequential execution on the
|
|
8
|
+
* main working tree to avoid a base mismatch.
|
|
9
|
+
*
|
|
10
|
+
* Pure/testable module: all I/O is injectable via the `deps` argument so unit
|
|
11
|
+
* tests can run without touching the real filesystem or spawning real git.
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.shortSha = shortSha;
|
|
18
|
+
exports.readBaseRefFromSettings = readBaseRefFromSettings;
|
|
19
|
+
exports.applyWorktreeBaseRef = applyWorktreeBaseRef;
|
|
20
|
+
exports.resolveEffectiveBaseRef = resolveEffectiveBaseRef;
|
|
21
|
+
exports.cmdWorktreeBaseCheck = cmdWorktreeBaseCheck;
|
|
22
|
+
exports.cmdWorktreeSetBaseRef = cmdWorktreeSetBaseRef;
|
|
23
|
+
exports.evaluateWorktreeBaseDegrade = evaluateWorktreeBaseDegrade;
|
|
24
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
25
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
26
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
27
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Strip JSONC comments (line and block forms) from a string to produce valid JSON.
|
|
30
|
+
* Handles comments inside strings correctly (does not strip them).
|
|
31
|
+
* Mirrors the same logic in bin/install.js:stripJsonComments.
|
|
32
|
+
*/
|
|
33
|
+
function stripJsonComments(text) {
|
|
34
|
+
let result = '';
|
|
35
|
+
let i = 0;
|
|
36
|
+
let inString = false;
|
|
37
|
+
let stringChar = '';
|
|
38
|
+
while (i < text.length) {
|
|
39
|
+
// Handle string literals — don't strip comments inside strings
|
|
40
|
+
if (inString) {
|
|
41
|
+
if (text[i] === '\\') {
|
|
42
|
+
result += text[i] + (text[i + 1] || '');
|
|
43
|
+
i += 2;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (text[i] === stringChar) {
|
|
47
|
+
inString = false;
|
|
48
|
+
}
|
|
49
|
+
result += text[i];
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Start of string
|
|
54
|
+
if (text[i] === '"' || text[i] === "'") {
|
|
55
|
+
inString = true;
|
|
56
|
+
stringChar = text[i];
|
|
57
|
+
result += text[i];
|
|
58
|
+
i++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Line comment
|
|
62
|
+
if (text[i] === '/' && text[i + 1] === '/') {
|
|
63
|
+
// Skip to end of line
|
|
64
|
+
while (i < text.length && text[i] !== '\n')
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Block comment
|
|
69
|
+
if (text[i] === '/' && text[i + 1] === '*') {
|
|
70
|
+
i += 2;
|
|
71
|
+
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/'))
|
|
72
|
+
i++;
|
|
73
|
+
i += 2; // skip closing */
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
result += text[i];
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
// Remove trailing commas before } or ] (common in JSONC)
|
|
80
|
+
return result.replace(/,\s*([}\]])/g, '$1');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Parse a string as JSONC (JSON with comments). Returns the parsed value or
|
|
84
|
+
* throws a SyntaxError if the content is genuinely malformed.
|
|
85
|
+
*/
|
|
86
|
+
function parseJsonc(text) {
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(text);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return JSON.parse(stripJsonComments(text));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ─── Message constants (verbatim — downstream docs/tests depend on these) ─────
|
|
95
|
+
function buildMsgDiverged(headSha, forkRef, forkSha) {
|
|
96
|
+
return `⚠ Worktree base mismatch: HEAD (${shortSha(headSha)}) differs from ${forkRef} (${shortSha(forkSha)}). Running this phase sequentially on the main working tree. To keep parallel worktrees, set worktree.baseRef:"head" in .claude/settings.local.json (or run: gsd-tools worktree set-baseref). See #683.`;
|
|
97
|
+
}
|
|
98
|
+
const MSG_UNKNOWN = `⚠ Cannot determine the worktree fork base (origin/HEAD unresolved). Running this phase sequentially on the main working tree to avoid a base mismatch. To keep parallel worktrees, set worktree.baseRef:"head" in .claude/settings.local.json (or run: gsd-tools worktree set-baseref). See #683.`;
|
|
99
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Returns the first 8 characters of a SHA, or '' if null/empty.
|
|
102
|
+
*/
|
|
103
|
+
function shortSha(sha) {
|
|
104
|
+
if (!sha)
|
|
105
|
+
return '';
|
|
106
|
+
return sha.slice(0, 8);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Extracts settings.worktree.baseRef if it is a string; otherwise null.
|
|
110
|
+
* Defensive: settings may be null/undefined, worktree may be missing or
|
|
111
|
+
* not an object.
|
|
112
|
+
*/
|
|
113
|
+
function readBaseRefFromSettings(settings) {
|
|
114
|
+
if (settings == null || typeof settings !== 'object')
|
|
115
|
+
return null;
|
|
116
|
+
const s = settings;
|
|
117
|
+
if (s.worktree == null || typeof s.worktree !== 'object' || Array.isArray(s.worktree))
|
|
118
|
+
return null;
|
|
119
|
+
const worktree = s.worktree;
|
|
120
|
+
if (typeof worktree.baseRef !== 'string')
|
|
121
|
+
return null;
|
|
122
|
+
return worktree.baseRef;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* No-clobber application of worktree.baseRef = 'head'.
|
|
126
|
+
*
|
|
127
|
+
* - If baseRef is absent/null/undefined → set to 'head', return changed:true.
|
|
128
|
+
* - If already 'head' → skip, return skipped:'already-head'.
|
|
129
|
+
* - If any other string → skip without overwriting, return skipped:'explicit-other'.
|
|
130
|
+
*
|
|
131
|
+
* Mutates `settings` in place and also returns it.
|
|
132
|
+
*/
|
|
133
|
+
function applyWorktreeBaseRef(settings) {
|
|
134
|
+
// Defensive: caller must pass a plain object — reject null, arrays, and primitives.
|
|
135
|
+
if (settings === null || Array.isArray(settings) || typeof settings !== 'object') {
|
|
136
|
+
throw new TypeError(`applyWorktreeBaseRef: expected a plain object, got ${settings === null ? 'null' : Array.isArray(settings) ? 'array' : typeof settings}`);
|
|
137
|
+
}
|
|
138
|
+
// Ensure worktree object exists, preserving any existing keys
|
|
139
|
+
if (settings.worktree == null || typeof settings.worktree !== 'object' || Array.isArray(settings.worktree)) {
|
|
140
|
+
settings.worktree = {};
|
|
141
|
+
}
|
|
142
|
+
const worktree = settings.worktree;
|
|
143
|
+
const current = typeof worktree.baseRef === 'string' ? worktree.baseRef : null;
|
|
144
|
+
if (current === 'head') {
|
|
145
|
+
return { changed: false, settings, skipped: 'already-head', previous: 'head' };
|
|
146
|
+
}
|
|
147
|
+
if (current !== null) {
|
|
148
|
+
// Some other explicit string value — don't overwrite
|
|
149
|
+
return { changed: false, settings, skipped: 'explicit-other', previous: current };
|
|
150
|
+
}
|
|
151
|
+
// Absent/null/undefined → set to 'head'
|
|
152
|
+
worktree.baseRef = 'head';
|
|
153
|
+
return { changed: true, settings, skipped: null, previous: null };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Reads settings.local.json then settings.json under claudeDir, extracts
|
|
157
|
+
* worktree.baseRef from the first file that provides a non-null string value.
|
|
158
|
+
*
|
|
159
|
+
* deps.readFile(path) must return the file contents or null on any error.
|
|
160
|
+
*/
|
|
161
|
+
function resolveEffectiveBaseRef(claudeDir, deps) {
|
|
162
|
+
const readFile = deps?.readFile ?? ((p) => {
|
|
163
|
+
try {
|
|
164
|
+
return node_fs_1.default.readFileSync(p, 'utf8');
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const localPath = node_path_1.default.join(claudeDir, 'settings.local.json');
|
|
171
|
+
const sharedPath = node_path_1.default.join(claudeDir, 'settings.json');
|
|
172
|
+
function parseBaseRef(filePath) {
|
|
173
|
+
const contents = readFile(filePath);
|
|
174
|
+
if (contents == null)
|
|
175
|
+
return null;
|
|
176
|
+
try {
|
|
177
|
+
const parsed = parseJsonc(contents);
|
|
178
|
+
return readBaseRefFromSettings(parsed);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const localRef = parseBaseRef(localPath);
|
|
185
|
+
if (localRef !== null)
|
|
186
|
+
return localRef;
|
|
187
|
+
return parseBaseRef(sharedPath);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* CLI command: check current worktree base-ref degradation status.
|
|
191
|
+
*
|
|
192
|
+
* Reads effective baseRef from <cwd>/.claude settings, runs degradation
|
|
193
|
+
* evaluation, writes JSON result to stdout (or injected write), and returns
|
|
194
|
+
* the result object.
|
|
195
|
+
*/
|
|
196
|
+
function cmdWorktreeBaseCheck(cwd, _args, deps) {
|
|
197
|
+
const claudeDir = node_path_1.default.join(cwd, '.claude');
|
|
198
|
+
const effectiveBaseRef = resolveEffectiveBaseRef(claudeDir, deps?.readFile ? { readFile: deps.readFile } : undefined);
|
|
199
|
+
const result = evaluateWorktreeBaseDegrade({
|
|
200
|
+
cwd,
|
|
201
|
+
effectiveBaseRef,
|
|
202
|
+
execGit: deps?.execGit,
|
|
203
|
+
});
|
|
204
|
+
const write = deps?.write ?? ((s) => process.stdout.write(s));
|
|
205
|
+
write(JSON.stringify(result, null, 2) + '\n');
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* CLI command: write worktree.baseRef = 'head' into <cwd>/.claude/settings.local.json.
|
|
210
|
+
*
|
|
211
|
+
* No-clobber: if the file already has an explicit baseRef that is not 'head',
|
|
212
|
+
* the existing value is preserved and output reflects skipped:'explicit-other'.
|
|
213
|
+
* If the file contains malformed JSON, throws a clear error rather than
|
|
214
|
+
* silently clobbering the user's file.
|
|
215
|
+
*/
|
|
216
|
+
function cmdWorktreeSetBaseRef(cwd, _args, deps) {
|
|
217
|
+
const file = node_path_1.default.join(cwd, '.claude', 'settings.local.json');
|
|
218
|
+
const readFile = deps?.readFile ??
|
|
219
|
+
((p) => { try {
|
|
220
|
+
return node_fs_1.default.readFileSync(p, 'utf8');
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return null;
|
|
224
|
+
} });
|
|
225
|
+
const raw = readFile(file);
|
|
226
|
+
let settings = {};
|
|
227
|
+
if (raw != null) {
|
|
228
|
+
let parsed;
|
|
229
|
+
try {
|
|
230
|
+
parsed = parseJsonc(raw);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
throw new Error(`Refusing to modify ${file}: existing JSON is malformed`);
|
|
234
|
+
}
|
|
235
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
236
|
+
throw new Error(`Refusing to modify ${file}: expected a JSON object at the top level`);
|
|
237
|
+
}
|
|
238
|
+
settings = parsed;
|
|
239
|
+
}
|
|
240
|
+
const apply = applyWorktreeBaseRef(settings);
|
|
241
|
+
if (apply.changed) {
|
|
242
|
+
const dir = node_path_1.default.dirname(file);
|
|
243
|
+
const existsSync = deps?.existsSync ?? node_fs_1.default.existsSync;
|
|
244
|
+
const mkdirFn = deps?.mkdir ??
|
|
245
|
+
((p, opts) => { node_fs_1.default.mkdirSync(p, opts); });
|
|
246
|
+
if (!existsSync(dir)) {
|
|
247
|
+
mkdirFn(dir, { recursive: true });
|
|
248
|
+
}
|
|
249
|
+
const writeFile = deps?.writeFile ??
|
|
250
|
+
((p, content) => { node_fs_1.default.writeFileSync(p, content, 'utf8'); });
|
|
251
|
+
writeFile(file, JSON.stringify(settings, null, 2) + '\n');
|
|
252
|
+
}
|
|
253
|
+
const output = {
|
|
254
|
+
changed: apply.changed,
|
|
255
|
+
skipped: apply.skipped,
|
|
256
|
+
previous: apply.previous,
|
|
257
|
+
baseRef: 'head',
|
|
258
|
+
file,
|
|
259
|
+
};
|
|
260
|
+
const write = deps?.write ?? ((s) => process.stdout.write(s));
|
|
261
|
+
write(JSON.stringify(output, null, 2) + '\n');
|
|
262
|
+
return output;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Evaluates whether the current worktree HEAD has diverged from the fork base
|
|
266
|
+
* (origin/HEAD) that the Claude Code harness would use when creating a 'fresh'
|
|
267
|
+
* parallel worktree.
|
|
268
|
+
*
|
|
269
|
+
* Returns a structured result with shouldDegrade, reason, and a user-visible
|
|
270
|
+
* message when degradation is warranted.
|
|
271
|
+
*/
|
|
272
|
+
function evaluateWorktreeBaseDegrade(deps) {
|
|
273
|
+
const execGit = deps?.execGit ?? shell_command_projection_cjs_1.execGit;
|
|
274
|
+
const cwd = deps?.cwd;
|
|
275
|
+
const cwdOpts = cwd ? { cwd } : {};
|
|
276
|
+
// a. If baseRef is explicitly 'head' the harness forks from HEAD — no mismatch possible.
|
|
277
|
+
// Claude Code's worktree.baseRef accepts only "fresh" (= origin/HEAD, the default) or "head".
|
|
278
|
+
// Therefore special-casing "head" here and otherwise comparing HEAD against origin/HEAD is
|
|
279
|
+
// complete: any non-"head" value (including "fresh" and absent/null) has fresh/origin-HEAD
|
|
280
|
+
// semantics and must be evaluated against origin/HEAD. (Reference: Claude Code worktrees docs, #683.)
|
|
281
|
+
if (deps?.effectiveBaseRef === 'head') {
|
|
282
|
+
return { shouldDegrade: false, reason: 'baseref-head', message: null, headSha: null, forkRef: null, forkSha: null };
|
|
283
|
+
}
|
|
284
|
+
// b. Resolve HEAD sha.
|
|
285
|
+
const headResult = execGit(['rev-parse', 'HEAD'], cwdOpts);
|
|
286
|
+
const headStdout = headResult.stdout ? headResult.stdout.trim() : '';
|
|
287
|
+
if (headResult.exitCode !== 0 || !headStdout) {
|
|
288
|
+
return { shouldDegrade: false, reason: 'no-head', message: null, headSha: null, forkRef: null, forkSha: null };
|
|
289
|
+
}
|
|
290
|
+
const headSha = headStdout;
|
|
291
|
+
// c. Resolve fork base (what the harness forks 'fresh' worktrees from = origin/HEAD).
|
|
292
|
+
let forkRef = null;
|
|
293
|
+
let forkSha = null;
|
|
294
|
+
// Try direct origin/HEAD rev-parse first.
|
|
295
|
+
const directResult = execGit(['rev-parse', '--verify', '--quiet', 'origin/HEAD'], cwdOpts);
|
|
296
|
+
const directStdout = directResult.stdout ? directResult.stdout.trim() : '';
|
|
297
|
+
if (directResult.exitCode === 0 && directStdout) {
|
|
298
|
+
forkRef = 'origin/HEAD';
|
|
299
|
+
forkSha = directStdout;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
// Fall back via symbolic-ref → refs/remotes/origin/HEAD
|
|
303
|
+
const symResult = execGit(['symbolic-ref', '--quiet', 'refs/remotes/origin/HEAD'], cwdOpts);
|
|
304
|
+
const symStdout = symResult.stdout ? symResult.stdout.trim() : '';
|
|
305
|
+
if (symResult.exitCode === 0 && symStdout) {
|
|
306
|
+
const ref = symStdout;
|
|
307
|
+
const symShaResult = execGit(['rev-parse', '--verify', '--quiet', ref], cwdOpts);
|
|
308
|
+
const symShaStdout = symShaResult.stdout ? symShaResult.stdout.trim() : '';
|
|
309
|
+
if (symShaResult.exitCode === 0 && symShaStdout) {
|
|
310
|
+
// Strip leading 'refs/remotes/' to get e.g. 'origin/next'
|
|
311
|
+
forkRef = ref.replace(/^refs\/remotes\//, '');
|
|
312
|
+
forkSha = symShaStdout;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// d. Evaluate.
|
|
317
|
+
if (forkSha === null) {
|
|
318
|
+
return { shouldDegrade: true, reason: 'fork-ref-unknown', message: MSG_UNKNOWN, headSha, forkRef: null, forkSha: null };
|
|
319
|
+
}
|
|
320
|
+
if (forkSha === headSha) {
|
|
321
|
+
return { shouldDegrade: false, reason: 'head-matches-fork', message: null, headSha, forkRef, forkSha };
|
|
322
|
+
}
|
|
323
|
+
const message = buildMsgDiverged(headSha, forkRef, forkSha);
|
|
324
|
+
return { shouldDegrade: true, reason: 'head-diverged-from-fork', message, headSha, forkRef, forkSha };
|
|
325
|
+
}
|
|
@@ -406,6 +406,12 @@ function defaultFindSummaryFiles(worktreePath) {
|
|
|
406
406
|
*
|
|
407
407
|
* For each *SUMMARY.md found under <worktreePath>/.planning/:
|
|
408
408
|
* - compute relative path from worktree root → .planning/<id>-SUMMARY.md
|
|
409
|
+
* - if the file is ALREADY COMMITTED on the worktree branch
|
|
410
|
+
* (`git cat-file -e HEAD:<relPath>` returns exit 0), skip the copy entirely:
|
|
411
|
+
* the merge will carry it naturally and copying it as an untracked file would
|
|
412
|
+
* cause a "untracked working tree files would be overwritten by merge" collision.
|
|
413
|
+
* On timeout or fatal exit (128) the rescue is also skipped (fail-closed).
|
|
414
|
+
* (#706 — execute-phase committed-SUMMARY contract)
|
|
409
415
|
* - destination = <repoRoot>/<relPath>
|
|
410
416
|
* - copy when dest is absent or content differs
|
|
411
417
|
*
|
|
@@ -419,6 +425,7 @@ function defaultFindSummaryFiles(worktreePath) {
|
|
|
419
425
|
* failure — it sets needsCopy=true and the copy is attempted normally.
|
|
420
426
|
*/
|
|
421
427
|
function rescueSummaryArtifacts(worktreePath, repoRoot, deps) {
|
|
428
|
+
const execGit = deps.execGit || execGitDefault;
|
|
422
429
|
const findSummaryFiles = deps.findSummaryFiles || defaultFindSummaryFiles;
|
|
423
430
|
const existsSync = deps.existsSync || node_fs_1.default.existsSync;
|
|
424
431
|
const readFileSync = deps.readFileSync || ((p) => node_fs_1.default.readFileSync(p, 'utf8'));
|
|
@@ -432,6 +439,30 @@ function rescueSummaryArtifacts(worktreePath, repoRoot, deps) {
|
|
|
432
439
|
// Normalize to forward slashes so the Set comparison against `git status --porcelain`
|
|
433
440
|
// output works on Windows too (git always emits forward slashes in porcelain output).
|
|
434
441
|
const relPath = absPath.slice(worktreePath.length).replace(/^[/\\]/, '').replace(/\\/g, '/');
|
|
442
|
+
// #706: skip rescue when the SUMMARY is already committed on the branch.
|
|
443
|
+
// Use `git cat-file -e HEAD:<relPath>` (not `ls-files --error-unmatch`) so
|
|
444
|
+
// the check is against the committed tree, not the index. ls-files also
|
|
445
|
+
// matches staged-but-uncommitted files, which would skip rescue when the
|
|
446
|
+
// file is staged but not yet committed — the merge wouldn't carry it, and
|
|
447
|
+
// the executor's content could be lost. cat-file -e HEAD:<path> returns
|
|
448
|
+
// exit 0 only when the object exists in the committed HEAD tree.
|
|
449
|
+
//
|
|
450
|
+
// Fail-closed on timeout/fatal git errors: if we cannot determine whether
|
|
451
|
+
// the file is committed, do NOT rescue it (rescuing an actually-committed
|
|
452
|
+
// file would re-create the untracked collision; the merge will surface the
|
|
453
|
+
// issue). The cleanup will be blocked by merge_failed in the worst case,
|
|
454
|
+
// which is the observable behaviour before this fix and is recoverable.
|
|
455
|
+
const catFileResult = execGit(['-C', worktreePath, 'cat-file', '-e', `HEAD:${relPath}`], { cwd: repoRoot });
|
|
456
|
+
if (catFileResult.exitCode !== 1) {
|
|
457
|
+
// Rescue only when cat-file definitively reports the object is absent (exit 1).
|
|
458
|
+
// exit 0 → object exists (committed on HEAD) — merge will carry it, skip.
|
|
459
|
+
// exit 128 → fatal git error (corrupt store, unborn HEAD, etc.) — uncertain,
|
|
460
|
+
// fail-closed: do NOT rescue to avoid recreating the #706 collision.
|
|
461
|
+
// timedOut / null / other → unreliable result — same fail-closed policy.
|
|
462
|
+
// In all non-1 cases the merge will either succeed naturally (0) or surface
|
|
463
|
+
// the problem safely (128/timeout), which is the recoverable pre-fix behaviour.
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
435
466
|
const dest = node_path_1.default.join(repoRoot, relPath);
|
|
436
467
|
let needsCopy = !existsSync(dest);
|
|
437
468
|
if (!needsCopy) {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
const fs = require('node:fs');
|
|
33
33
|
const path = require('node:path');
|
|
34
34
|
const crypto = require('node:crypto');
|
|
35
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
35
36
|
|
|
36
37
|
const SIGNIFICANT_MIN_CHARS = 12;
|
|
37
38
|
const GSD_HOOK_VERSION_LINE_RE = /^(?:\/\/|#)\s*gsd-hook-version:\s*\S+\s*$/i;
|
|
@@ -48,10 +49,9 @@ function parseArgs(argv) {
|
|
|
48
49
|
process.stdout.write(
|
|
49
50
|
'usage: verify-reapply-patches.cjs --patches-dir <path> --config-dir <path> [--pristine-dir <path>] [--json]\n',
|
|
50
51
|
);
|
|
51
|
-
|
|
52
|
+
throw new ExitError(0);
|
|
52
53
|
} else {
|
|
53
|
-
|
|
54
|
-
process.exit(2);
|
|
54
|
+
throw new ExitError(2, `unknown argument: ${arg}`);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
return opts;
|
|
@@ -281,16 +281,13 @@ function verifyFile({ relPath, patchesDir, configDir, pristineDir, pristineHashe
|
|
|
281
281
|
function main() {
|
|
282
282
|
const opts = parseArgs(process.argv.slice(2));
|
|
283
283
|
if (!opts.patchesDir || !opts.configDir) {
|
|
284
|
-
|
|
285
|
-
process.exit(2);
|
|
284
|
+
throw new ExitError(2, '--patches-dir and --config-dir are required');
|
|
286
285
|
}
|
|
287
286
|
if (!fs.existsSync(opts.patchesDir)) {
|
|
288
|
-
|
|
289
|
-
process.exit(2);
|
|
287
|
+
throw new ExitError(2, `patches dir not found: ${opts.patchesDir}`);
|
|
290
288
|
}
|
|
291
289
|
if (!fs.existsSync(opts.configDir)) {
|
|
292
|
-
|
|
293
|
-
process.exit(2);
|
|
290
|
+
throw new ExitError(2, `config dir not found: ${opts.configDir}`);
|
|
294
291
|
}
|
|
295
292
|
|
|
296
293
|
const files = walk(opts.patchesDir).filter((f) => !f.endsWith('backup-meta.json'));
|
|
@@ -342,11 +339,11 @@ function main() {
|
|
|
342
339
|
}
|
|
343
340
|
}
|
|
344
341
|
|
|
345
|
-
|
|
342
|
+
return failures.length > 0 ? 1 : 0;
|
|
346
343
|
}
|
|
347
344
|
|
|
348
345
|
if (require.main === module) {
|
|
349
|
-
main
|
|
346
|
+
runMain(main);
|
|
350
347
|
}
|
|
351
348
|
|
|
352
349
|
module.exports = { computeUserAddedLines, isSignificantLine, verifyFile, walk, REASON, readPristineHashes, sha256 };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Planner — Load Graph Context
|
|
2
|
+
|
|
3
|
+
> Loaded by `gsd-planner` at the `load_graph_context` step.
|
|
4
|
+
|
|
5
|
+
Check for knowledge graph:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
ls .planning/graphs/graph.json 2>/dev/null
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
If graph.json exists, check freshness:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
_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 command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/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
|
|
15
|
+
gsd_run graphify status
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If the status response has `stale: true`, note for later: "Graph is {age_hours}h old -- treat semantic relationships as approximate." Include this annotation inline with any graph context injected below.
|
|
19
|
+
|
|
20
|
+
Query the graph for phase-relevant dependency context (single query per D-06):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
gsd_run graphify query "<phase-goal-keyword>" --budget 2000
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use the keyword that best captures the phase goal. Examples:
|
|
27
|
+
- Phase "User Authentication" -> query term "auth"
|
|
28
|
+
- Phase "Payment Integration" -> query term "payment"
|
|
29
|
+
- Phase "Database Migration" -> query term "migration"
|
|
30
|
+
|
|
31
|
+
If the query returns nodes and edges, incorporate as dependency context for planning:
|
|
32
|
+
- Which modules/files are semantically related to this phase's domain
|
|
33
|
+
- Which subsystems may be affected by changes in this phase
|
|
34
|
+
- Cross-document relationships that inform task ordering and wave structure
|
|
35
|
+
|
|
36
|
+
If no results or graph.json absent, continue without graph context.
|
|
@@ -34,7 +34,7 @@ Configuration options for `.planning/` directory behavior.
|
|
|
34
34
|
| `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy |
|
|
35
35
|
| `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy |
|
|
36
36
|
| `git.quick_branch_template` | `null` | Optional branch template for quick-task runs |
|
|
37
|
-
| `workflow.use_worktrees` | `true` | Whether executor agents run in isolated git worktrees. Set to `false` to disable worktrees — agents execute sequentially on the main working tree instead. Recommended for solo developers or when worktree merges cause issues. |
|
|
37
|
+
| `workflow.use_worktrees` | `true` | Whether executor agents run in isolated git worktrees. Set to `false` to disable worktrees — agents execute sequentially on the main working tree instead. Recommended for solo developers or when worktree merges cause issues. Note: if your branch is ahead of `origin/HEAD` (a diverged milestone or feature branch), GSD auto-degrades to sequential and prints a warning; set `worktree.baseRef:"head"` in `.claude/settings.local.json` to restore parallel execution. See the branch-divergence note below. |
|
|
38
38
|
| `workflow.subagent_timeout` | `300000` | Timeout in milliseconds for parallel subagent tasks (e.g. codebase mapping). Increase for large codebases or slower models. Default: 300000 (5 minutes). |
|
|
39
39
|
| `workflow.inline_plan_threshold` | `2` | Plans with this many tasks or fewer execute inline (Pattern C) instead of spawning a subagent. Avoids ~14K token spawn overhead for small plans. Set to `0` to always spawn subagents. |
|
|
40
40
|
| `manager.flags.discuss` | `""` | Flags passed to `/gsd:discuss-phase` when dispatched from manager (e.g. `"--auto --analyze"`) |
|
|
@@ -385,6 +385,8 @@ Several config fields affect each other or trigger special behavior:
|
|
|
385
385
|
|
|
386
386
|
8. **`sub_repos` auto-sync** -- On every config load, GSD scans for child directories with `.git` and updates the `sub_repos` array if the filesystem has changed. Legacy `multiRepo: true` is automatically migrated to a detected `sub_repos` array.
|
|
387
387
|
|
|
388
|
+
9. **`workflow.use_worktrees` and branch divergence** -- When `use_worktrees` is `true` (default), executor worktrees are forked from `origin/HEAD` by the Claude Code harness. If your current branch has commits that `origin/HEAD` does not (for example an unmerged milestone or feature branch), GSD automatically degrades to sequential execution for that run and prints a one-line `⚠ Worktree base mismatch` warning. To restore parallel execution permanently, set `worktree.baseRef:"head"` in `.claude/settings.local.json` (run `node gsd-tools.cjs worktree set-baseref`). This makes the harness fork worktrees from the live HEAD instead of `origin/HEAD`. Both fresh installs and upgrades of GSD Core set this automatically (no-clobber) when `use_worktrees` is enabled; you can also run the command manually at any time. Setting `workflow.use_worktrees: false` is the alternative if worktrees are not needed at all.
|
|
389
|
+
|
|
388
390
|
---
|
|
389
391
|
|
|
390
392
|
## Example Configurations
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
When you need library or framework documentation, check in this order:
|
|
2
|
+
|
|
3
|
+
1. If Context7 MCP tools (`mcp__context7__*`) are available in your environment, use them:
|
|
4
|
+
- Resolve library ID: `mcp__context7__resolve-library-id` with `libraryName`
|
|
5
|
+
- Fetch docs: `mcp__context7__get-library-docs` with `context7CompatibleLibraryId` and `topic`
|
|
6
|
+
|
|
7
|
+
2. If Context7 MCP is not available (upstream bug anthropics/claude-code#13898 strips MCP
|
|
8
|
+
tools from agents with a `tools:` frontmatter restriction), use the CLI fallback via Bash:
|
|
9
|
+
|
|
10
|
+
Step 1 — Resolve library ID:
|
|
11
|
+
```bash
|
|
12
|
+
if command -v ctx7 &>/dev/null; then
|
|
13
|
+
ctx7 library <name> "<query>"
|
|
14
|
+
else
|
|
15
|
+
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
|
|
16
|
+
fi
|
|
17
|
+
```
|
|
18
|
+
Step 2 — Fetch documentation:
|
|
19
|
+
```bash
|
|
20
|
+
if command -v ctx7 &>/dev/null; then
|
|
21
|
+
ctx7 docs <libraryId> "<query>"
|
|
22
|
+
else
|
|
23
|
+
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
|
|
24
|
+
fi
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Do not skip documentation lookups because MCP tools are unavailable — the CLI fallback
|
|
28
|
+
works via Bash and produces equivalent output. Do NOT use `npx --yes` to auto-download
|
|
29
|
+
ctx7 — this silently executes unverified packages from the registry.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## Claude's Training as Hypothesis
|
|
2
|
+
|
|
3
|
+
Training data is 6-18 months stale. Treat pre-existing knowledge as hypothesis, not fact.
|
|
4
|
+
|
|
5
|
+
**The trap:** Claude "knows" things confidently, but knowledge may be outdated, incomplete, or wrong.
|
|
6
|
+
|
|
7
|
+
**The discipline:**
|
|
8
|
+
1. **Verify before asserting** — don't state library capabilities without checking Context7 or official docs
|
|
9
|
+
2. **Date your knowledge** — "As of my training" is a warning flag
|
|
10
|
+
3. **Prefer current sources** — Context7 and official docs trump training data
|
|
11
|
+
4. **Flag uncertainty** — LOW confidence when only training data supports a claim
|
|
12
|
+
|
|
13
|
+
## Honest Reporting
|
|
14
|
+
|
|
15
|
+
Research value comes from accuracy, not completeness theater.
|
|
16
|
+
|
|
17
|
+
**Report honestly:**
|
|
18
|
+
- "I couldn't find X" is valuable (now we know to investigate differently)
|
|
19
|
+
- "This is LOW confidence" is valuable (flags for validation)
|
|
20
|
+
- "Sources contradict" is valuable (surfaces real ambiguity)
|
|
21
|
+
|
|
22
|
+
**Avoid:** Padding findings, stating unverified claims as facts, hiding uncertainty behind confident language.
|
|
23
|
+
|
|
24
|
+
## Research is Investigation, Not Confirmation
|
|
25
|
+
|
|
26
|
+
**Bad research:** Start with hypothesis, find evidence to support it
|
|
27
|
+
**Good research:** Gather evidence, form conclusions from evidence
|
|
28
|
+
|
|
29
|
+
When researching "best library for X": find what the ecosystem actually uses, document tradeoffs honestly, let evidence drive recommendation.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Known Pitfalls
|
|
2
|
+
|
|
3
|
+
### Configuration Scope Blindness
|
|
4
|
+
**Trap:** Assuming global configuration means no project-scoping exists
|
|
5
|
+
**Prevention:** Verify ALL configuration scopes (global, project, local, workspace)
|
|
6
|
+
|
|
7
|
+
### Deprecated Features
|
|
8
|
+
**Trap:** Finding old documentation and concluding feature doesn't exist
|
|
9
|
+
**Prevention:** Check current official docs, review changelog, verify version numbers and dates
|
|
10
|
+
|
|
11
|
+
### Negative Claims Without Evidence
|
|
12
|
+
**Trap:** Making definitive "X is not possible" statements without official verification
|
|
13
|
+
**Prevention:** For any negative claim — is it verified by official docs? Have you checked recent updates? Are you confusing "didn't find it" with "doesn't exist"?
|
|
14
|
+
|
|
15
|
+
### Single Source Reliance
|
|
16
|
+
**Trap:** Relying on a single source for critical claims
|
|
17
|
+
**Prevention:** Require multiple sources: official docs (primary), release notes (currency), additional source (verification)
|
|
18
|
+
|
|
19
|
+
## Pre-Submission Checklist
|
|
20
|
+
|
|
21
|
+
- [ ] All research domains in this agent's scope investigated (e.g. stack, features, architecture, patterns, pitfalls — whichever apply)
|
|
22
|
+
- [ ] Negative claims verified with official docs
|
|
23
|
+
- [ ] Multiple sources cross-referenced for critical claims
|
|
24
|
+
- [ ] URLs provided for authoritative sources
|
|
25
|
+
- [ ] Publication dates checked (prefer recent/current)
|
|
26
|
+
- [ ] Confidence levels assigned honestly
|
|
27
|
+
- [ ] "What might I have missed?" review completed
|
|
@@ -92,6 +92,16 @@ if [ "$RUNTIME" = "codex" ] && [ "$USE_WORKTREES" != "false" ]; then
|
|
|
92
92
|
fi
|
|
93
93
|
# Sweep orphaned locked worktrees from prior crashed sessions before spawning executors (#3707).
|
|
94
94
|
[ "$USE_WORKTREES" != "false" ] && gsd_run query worktree.reap-orphans 2>/dev/null || true
|
|
95
|
+
# Auto-degrade to sequential if HEAD has diverged from the worktree fork base (#683).
|
|
96
|
+
# Only applies to Claude Code (isolation="worktree" is Claude-Code-specific).
|
|
97
|
+
if [ "$RUNTIME" = "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
|
|
98
|
+
_SHOULD_DEGRADE=$(gsd_run query worktree.base-check --pick shouldDegrade 2>/dev/null || true)
|
|
99
|
+
if [ "$_SHOULD_DEGRADE" = "true" ]; then
|
|
100
|
+
_DEGRADE_MSG=$(gsd_run query worktree.base-check --pick message 2>/dev/null || true)
|
|
101
|
+
[ -n "$_DEGRADE_MSG" ] && printf '%s\n' "$_DEGRADE_MSG" >&2
|
|
102
|
+
USE_WORKTREES=false
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
95
105
|
```
|
|
96
106
|
Codex maps subagents to `spawn_agent`, which has no direct Codex mapping for Claude Code's `isolation="worktree"` parameter. Failing closed prevents main-checkout edits while the workflow believes agents are isolated.
|
|
97
107
|
|
|
@@ -111,6 +121,8 @@ fi
|
|
|
111
121
|
|
|
112
122
|
When `USE_WORKTREES` (project-level) is `false`, all executor agents run without `isolation="worktree"` — they execute sequentially on the main working tree instead of in parallel worktrees. The per-plan decision below has no effect when worktrees are project-disabled.
|
|
113
123
|
|
|
124
|
+
`USE_WORKTREES` is also automatically set to `false` for the duration of a run when `worktree base-check` detects that the orchestrator HEAD has diverged from the worktree fork base (the #683 condition — e.g. an unmerged milestone or feature branch). This check runs only when `RUNTIME=claude` because `isolation="worktree"` is a Claude Code-specific feature; other runtimes do not use it. The auto-degrade prints a one-line warning to stderr and falls through to the sequential path so executors do not hit the exit-42 worktree-branch-check halt. To restore parallel worktree execution, set `worktree.baseRef:"head"` in `.claude/settings.local.json` (or run `gsd-tools worktree set-baseref`) — this makes the fork base track the live HEAD instead of a fixed remote ref. The `worktree-branch-check` exit-42 guard inside each executor remains in place as a backstop.
|
|
125
|
+
|
|
114
126
|
Read context window size for adaptive prompt enrichment:
|
|
115
127
|
|
|
116
128
|
```bash
|
|
@@ -188,7 +200,7 @@ if [ "$MVP_MODE" = "true" ] && [ "$TDD_MODE" = "true" ]; then
|
|
|
188
200
|
fi
|
|
189
201
|
fi
|
|
190
202
|
```
|
|
191
|
-
Pure doc-only / config-only / test-only tasks return `is_behavior_adding=false` and are exempt.
|
|
203
|
+
Pure doc-only / config-only / test-only tasks return `is_behavior_adding=false` and are exempt. When the gate trips, Read `~/.claude/gsd-core/references/execute-mvp-tdd.md` for the exact halt report format.
|
|
192
204
|
</step>
|
|
193
205
|
|
|
194
206
|
<step name="check_blocking_antipatterns" priority="first">
|
|
@@ -1413,16 +1425,15 @@ ${VERIFIER_SKILLS}",
|
|
|
1413
1425
|
|
|
1414
1426
|
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
|
1415
1427
|
|
|
1416
|
-
Read status:
|
|
1428
|
+
Read status via the canonical query (scoped to frontmatter, covers missing/unknown cases):
|
|
1417
1429
|
```bash
|
|
1418
|
-
|
|
1430
|
+
VERIFICATION=$(gsd_run query verification.status "$PHASE_DIR" 2>/dev/null)
|
|
1431
|
+
STATUS=$(printf '%s' "$VERIFICATION" | jq -r '.status' 2>/dev/null || echo "")
|
|
1432
|
+
NEXT_ACTION=$(printf '%s' "$VERIFICATION" | jq -r '.next_action' 2>/dev/null || echo "")
|
|
1433
|
+
NEXT_COMMAND=$(printf '%s' "$VERIFICATION" | jq -r '.next_command' 2>/dev/null || echo "")
|
|
1419
1434
|
```
|
|
1420
1435
|
|
|
1421
|
-
|
|
1422
|
-
|--------|--------|
|
|
1423
|
-
| `passed` | → update_roadmap |
|
|
1424
|
-
| `human_needed` | Persist and present human testing items; keep phase pending until verification reruns as `passed` |
|
|
1425
|
-
| `gaps_found` | Present gap summary, offer `/gsd:plan-phase {phase} --gaps ${GSD_WS}` |
|
|
1436
|
+
Route on `$STATUS`: if `passed`, proceed to update_roadmap. Otherwise keep the phase pending — present `$NEXT_ACTION` to the user and, when `$NEXT_COMMAND` is non-empty, show it as the next command to run. The query covers all cases including missing files (`missing`) and unexpected values (`unknown`), so no per-status arm needs to be listed here.
|
|
1426
1437
|
|
|
1427
1438
|
**If human_needed:**
|
|
1428
1439
|
|