@opengsd/gsd-core 1.5.0-rc.4 → 1.5.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/plugin.json +1 -1
- package/LICENSE +1 -1
- package/agents/gsd-executor.md +22 -0
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-planner.md +4 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-verifier.md +45 -13
- package/bin/install.js +56 -100
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +85 -53
- package/gsd-core/bin/lib/agent-command-router.cjs +2 -2
- package/gsd-core/bin/lib/agent-install-check.cjs +143 -0
- package/gsd-core/bin/lib/audit-command-router.cjs +4 -4
- package/gsd-core/bin/lib/capability-activation.cjs +37 -10
- package/gsd-core/bin/lib/capability-registry.cjs +2 -0
- package/gsd-core/bin/lib/capability-state.cjs +80 -30
- package/gsd-core/bin/lib/capability-writer.cjs +5 -4
- package/gsd-core/bin/lib/check-command-router.cjs +50 -51
- package/gsd-core/bin/lib/commands.cjs +20 -2
- package/gsd-core/bin/lib/config-loader.cjs +3 -4
- package/gsd-core/bin/lib/config-schema.cjs +1 -1
- package/gsd-core/bin/lib/config-types.cjs +2 -1
- package/gsd-core/bin/lib/config.cjs +5 -2
- package/gsd-core/bin/lib/decisions.cjs +19 -1
- package/gsd-core/bin/lib/docs.cjs +14 -2
- package/gsd-core/bin/lib/frontmatter.cjs +2 -2
- package/gsd-core/bin/lib/gap-checker.cjs +5 -2
- package/gsd-core/bin/lib/git-base-branch.cjs +27 -1
- package/gsd-core/bin/lib/graphify-command-router.cjs +6 -8
- package/gsd-core/bin/lib/graphify.cjs +7 -31
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -2
- package/gsd-core/bin/lib/init.cjs +30 -4
- package/gsd-core/bin/lib/intel-command-router.cjs +6 -3
- package/gsd-core/bin/lib/intel.cjs +28 -34
- package/gsd-core/bin/lib/io.cjs +2 -4
- package/gsd-core/bin/lib/learnings.cjs +2 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +45 -167
- package/gsd-core/bin/lib/milestone.cjs +13 -5
- package/gsd-core/bin/lib/model-resolver.cjs +3 -4
- package/gsd-core/bin/lib/phase-id.cjs +3 -5
- package/gsd-core/bin/lib/phase-locator.cjs +3 -6
- package/gsd-core/bin/lib/phase.cjs +59 -13
- package/gsd-core/bin/lib/probe-core.cjs +40 -11
- package/gsd-core/bin/lib/profile-output.cjs +5 -2
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +660 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +28 -25
- package/gsd-core/bin/lib/roadmap.cjs +9 -4
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +4 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +24 -3
- package/gsd-core/bin/lib/state.cjs +75 -17
- package/gsd-core/bin/lib/task-command-router.cjs +2 -2
- package/gsd-core/bin/lib/teams-status.cjs +74 -0
- package/gsd-core/bin/lib/template.cjs +11 -2
- package/gsd-core/bin/lib/uat.cjs +64 -2
- package/gsd-core/bin/lib/verification.cjs +8 -5
- package/gsd-core/bin/lib/verify.cjs +311 -4
- package/gsd-core/bin/lib/workstream-inventory.cjs +2 -2
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +44 -3
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/planner-antipatterns.md +46 -0
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/prohibition-probe.md +80 -2
- package/gsd-core/references/worktree-branch-check.md +11 -5
- package/gsd-core/templates/verification-report.md +16 -3
- package/gsd-core/workflows/docs-update.md +23 -31
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +9 -0
- package/gsd-core/workflows/execute-phase.md +7 -2
- package/gsd-core/workflows/map-codebase.md +8 -10
- package/gsd-core/workflows/plan-phase.md +6 -0
- package/gsd-core/workflows/quick.md +26 -2
- package/gsd-core/workflows/settings-advanced.md +5 -5
- package/gsd-core/workflows/settings-integrations.md +5 -5
- package/gsd-core/workflows/spec-phase.md +30 -2
- package/gsd-core/workflows/verify-phase.md +22 -7
- package/hooks/dist/gsd-worktree-path-guard.js +34 -18
- package/hooks/gsd-worktree-path-guard.js +34 -18
- package/package.json +3 -3
- package/scripts/ci-prepare-test-scope.cjs +56 -14
- package/scripts/diff-touches-shipped-paths.cjs +5 -11
- package/scripts/gen-capability-registry.cjs +27 -1
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/research-profiles.cjs +2 -2
- package/scripts/run-tests.cjs +1 -0
- package/gsd-core/bin/lib/core.cjs +0 -345
|
@@ -22,8 +22,8 @@ const roadmapUpgrade = require("./roadmap-upgrade.cjs");
|
|
|
22
22
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
23
23
|
const { planningDir } = planningWorkspace;
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
|
-
const
|
|
26
|
-
const { loadConfig } =
|
|
25
|
+
const configLoaderMod = require("./config-loader.cjs");
|
|
26
|
+
const { loadConfig } = configLoaderMod;
|
|
27
27
|
// ─── W021 Implementation ──────────────────────────────────────────────────────
|
|
28
28
|
/**
|
|
29
29
|
* Check each phase entry in a milestone-prefixed ROADMAP.md for W021 violations.
|
|
@@ -6,12 +6,10 @@
|
|
|
6
6
|
* Owns shipped-milestone slicing, current-milestone extraction,
|
|
7
7
|
* milestone/phase lookups, and milestone-phase filtering.
|
|
8
8
|
* Behaviour is preserved byte-for-behaviour from the prior location;
|
|
9
|
-
* only the module boundary moved. core.cjs re-
|
|
10
|
-
*
|
|
9
|
+
* only the module boundary moved. The core.cjs re-export spine was retired
|
|
10
|
+
* in epic #1267; callers import roadmap-parser helpers directly.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Dependencies (leaf modules only — no core.cjs, no loadConfig):
|
|
12
|
+
* Dependencies (leaf modules only — no loadConfig):
|
|
15
13
|
* - node:fs / node:path (stdlib)
|
|
16
14
|
* - ./phase-id.cjs (escapeRegex, phaseMarkdownRegexSource)
|
|
17
15
|
* - ./planning-workspace.cjs (planningDir)
|
|
@@ -180,6 +178,27 @@ function replaceInCurrentMilestone(content, pattern, replacement) {
|
|
|
180
178
|
const after = content.slice(offset);
|
|
181
179
|
return before + after.replace(pattern, replacement);
|
|
182
180
|
}
|
|
181
|
+
function findRoadmapPhaseInContent(content, phaseNum) {
|
|
182
|
+
const phasePattern = new RegExp(`#{2,4}\\s*(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${phaseMarkdownRegexSource(phaseNum)}:\\s*([^\\n]+)`, 'i');
|
|
183
|
+
const headerMatch = content.match(phasePattern);
|
|
184
|
+
if (!headerMatch)
|
|
185
|
+
return null;
|
|
186
|
+
const phaseName = headerMatch[1].trim();
|
|
187
|
+
const headerIndex = headerMatch.index;
|
|
188
|
+
const restOfContent = content.slice(headerIndex);
|
|
189
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+[\w]/i);
|
|
190
|
+
const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
|
|
191
|
+
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
192
|
+
const goalMatch = section.match(/\*\*Goal(?:\*\*:|\*?\*?:\*\*)\s*([^\n]+)/i);
|
|
193
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
194
|
+
return {
|
|
195
|
+
found: true,
|
|
196
|
+
phase_number: String(phaseNum),
|
|
197
|
+
phase_name: phaseName,
|
|
198
|
+
goal,
|
|
199
|
+
section,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
183
202
|
function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
184
203
|
if (!phaseNum)
|
|
185
204
|
return null;
|
|
@@ -191,26 +210,10 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
191
210
|
if (roadmapRaw === null)
|
|
192
211
|
throw new Error('missing');
|
|
193
212
|
const content = extractCurrentMilestone(roadmapRaw, cwd);
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const phaseName = headerMatch[1].trim();
|
|
199
|
-
const headerIndex = headerMatch.index;
|
|
200
|
-
const restOfContent = content.slice(headerIndex);
|
|
201
|
-
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+[\w]/i);
|
|
202
|
-
const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
|
|
203
|
-
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
204
|
-
const goalMatch = section.match(/\*\*Goal(?:\*\*:|\*?\*?:\*\*)\s*([^\n]+)/i);
|
|
205
|
-
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
206
|
-
return {
|
|
207
|
-
found: true,
|
|
208
|
-
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
209
|
-
phase_number: String(phaseNum),
|
|
210
|
-
phase_name: phaseName,
|
|
211
|
-
goal,
|
|
212
|
-
section,
|
|
213
|
-
};
|
|
213
|
+
const scopedResult = findRoadmapPhaseInContent(content, phaseNum);
|
|
214
|
+
if (scopedResult)
|
|
215
|
+
return scopedResult;
|
|
216
|
+
return findRoadmapPhaseInContent(stripShippedMilestones(roadmapRaw), phaseNum);
|
|
214
217
|
}
|
|
215
218
|
catch {
|
|
216
219
|
return null;
|
|
@@ -13,8 +13,14 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const clock_cjs_1 = require("./clock.cjs");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
-
const
|
|
17
|
-
const {
|
|
16
|
+
const ioMod = require("./io.cjs");
|
|
17
|
+
const { output, error } = ioMod;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
20
|
+
const { escapeRegex, normalizePhaseName, phaseMarkdownRegexSource, phaseMarkdownRegexSourceExact, phaseTokenMatches } = phaseIdMod;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
23
|
+
const { findPhaseInternal } = phaseLocatorMod;
|
|
18
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
25
|
const roadmapParserModule = require("./roadmap-parser.cjs");
|
|
20
26
|
const { stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone } = roadmapParserModule;
|
|
@@ -77,8 +83,7 @@ function countPhasePlansAndSummaries(phaseDir) {
|
|
|
77
83
|
hasResearch: phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
|
|
78
84
|
};
|
|
79
85
|
}
|
|
80
|
-
// `phaseMarkdownRegexSource`
|
|
81
|
-
// core.cjs itself can consume it without circular deps. Imported above.
|
|
86
|
+
// `phaseMarkdownRegexSource` lives in phase-id.cjs (#3537) and is imported above.
|
|
82
87
|
// ─── searchPhaseInContent ─────────────────────────────────────────────────────
|
|
83
88
|
/**
|
|
84
89
|
* Search for a phase header (and its section) within the given content string.
|
|
@@ -287,6 +287,9 @@ function skillFrontmatterName(skillDirName) {
|
|
|
287
287
|
// Return the hyphen form as-is (gsd-<cmd>) — canonical since #2808.
|
|
288
288
|
return skillDirName;
|
|
289
289
|
}
|
|
290
|
+
function normalizeClaudeSkillEffort(effort) {
|
|
291
|
+
return effort === 'xhigh' ? 'max' : effort;
|
|
292
|
+
}
|
|
290
293
|
/**
|
|
291
294
|
* Qwen Code skills accept an optional numeric `priority` frontmatter field.
|
|
292
295
|
* Per the Qwen skills spec (qwen-code/docs/users/features/skills.md, verified
|
|
@@ -386,7 +389,7 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
386
389
|
if (context)
|
|
387
390
|
fm += `context: ${context}\n`;
|
|
388
391
|
if (effort)
|
|
389
|
-
fm += `effort: ${effort}\n`;
|
|
392
|
+
fm += `effort: ${normalizeClaudeSkillEffort(effort)}\n`;
|
|
390
393
|
if (toolsBlock)
|
|
391
394
|
fm += toolsBlock;
|
|
392
395
|
fm += '---';
|
|
@@ -406,7 +406,25 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
406
406
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
407
407
|
parsed = {};
|
|
408
408
|
const usesNestedHooksObject = parsed['hooks'] && typeof parsed['hooks'] === 'object' && !Array.isArray(parsed['hooks']);
|
|
409
|
-
|
|
409
|
+
// #1348: canonicalize every write to the nested { hooks: { <Event>: [...] } }
|
|
410
|
+
// shape. Lift ANY top-level event array (legacy, empty, OR mixed nested+top-level)
|
|
411
|
+
// into the nested table — merging when the same event exists in both — so
|
|
412
|
+
// user/legacy entries are preserved under `hooks` and no stray top-level event
|
|
413
|
+
// key survives (Codex deny_unknown_fields rejects them). Mirrors reconcileCursorHooksJson.
|
|
414
|
+
const hookTable = usesNestedHooksObject
|
|
415
|
+
? parsed['hooks']
|
|
416
|
+
: {};
|
|
417
|
+
for (const key of Object.keys(parsed)) {
|
|
418
|
+
if (key === 'hooks')
|
|
419
|
+
continue;
|
|
420
|
+
if (Array.isArray(parsed[key])) {
|
|
421
|
+
const lifted = parsed[key];
|
|
422
|
+
const existing = Array.isArray(hookTable[key]) ? hookTable[key] : [];
|
|
423
|
+
hookTable[key] = [...lifted, ...existing];
|
|
424
|
+
delete parsed[key];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
parsed['hooks'] = hookTable;
|
|
410
428
|
const eventEntries = Array.isArray(hookTable[eventName]) ? hookTable[eventName] : [];
|
|
411
429
|
let removedLegacy = false;
|
|
412
430
|
const sanitizedEntries = [];
|
|
@@ -452,8 +470,11 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
452
470
|
else {
|
|
453
471
|
delete hookTable[eventName];
|
|
454
472
|
}
|
|
455
|
-
|
|
456
|
-
|
|
473
|
+
// Avoid writing an empty `{ "hooks": {} }` artifact (e.g. removal on an absent
|
|
474
|
+
// file): collapse an empty hook table back to `{}` so the existing
|
|
475
|
+
// shouldWrite/no-write-on-empty behavior is preserved.
|
|
476
|
+
if (Object.keys(hookTable).length === 0)
|
|
477
|
+
delete parsed['hooks'];
|
|
457
478
|
const nextContent = `${JSON.stringify(parsed, null, 2)}\n`;
|
|
458
479
|
const changed = currentContent !== nextContent;
|
|
459
480
|
const shouldWrite = changed && (currentContent !== null || Object.keys(parsed).length > 0);
|
|
@@ -12,8 +12,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
|
-
const
|
|
16
|
-
const {
|
|
15
|
+
const ioMod = require("./io.cjs");
|
|
16
|
+
const { output, error } = ioMod;
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const configLoaderMod = require("./config-loader.cjs");
|
|
19
|
+
const { loadConfig } = configLoaderMod;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
22
|
+
const { escapeRegex } = phaseIdMod;
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
|
+
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
25
|
+
const { getMilestoneInfo, getMilestonePhaseFilter, extractCurrentMilestone } = roadmapParserMod;
|
|
17
26
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
18
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
28
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -25,6 +34,19 @@ const { extractFrontmatter, reconstructFrontmatter } = frontmatter;
|
|
|
25
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
35
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
27
36
|
const state_document_cjs_1 = require("./state-document.cjs");
|
|
37
|
+
const STATE_PROGRESS_RESYNC_FIELDS = new Set([
|
|
38
|
+
'Progress',
|
|
39
|
+
'Total Plans in Phase',
|
|
40
|
+
'Total Phases',
|
|
41
|
+
]);
|
|
42
|
+
function shouldResyncStateProgress(fields) {
|
|
43
|
+
for (const field of fields) {
|
|
44
|
+
if (STATE_PROGRESS_RESYNC_FIELDS.has(field)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
28
50
|
// ─── Cache ────────────────────────────────────────────────────────────────────
|
|
29
51
|
// Cache disk scan results from buildStateFrontmatter per cwd per process (#1967).
|
|
30
52
|
// Avoids re-reading N+1 directories on every state write when the phase structure
|
|
@@ -148,6 +170,7 @@ function cmdStatePatch(cwd, patches, raw) {
|
|
|
148
170
|
const statePath = planningPaths(cwd).state;
|
|
149
171
|
try {
|
|
150
172
|
const results = { updated: [], failed: [] };
|
|
173
|
+
const shouldResync = shouldResyncStateProgress(Object.keys(patches));
|
|
151
174
|
// Use atomic read-modify-write to prevent lost updates from concurrent agents
|
|
152
175
|
readModifyWriteStateMd(statePath, (content) => {
|
|
153
176
|
for (const [field, value] of Object.entries(patches)) {
|
|
@@ -161,7 +184,7 @@ function cmdStatePatch(cwd, patches, raw) {
|
|
|
161
184
|
}
|
|
162
185
|
}
|
|
163
186
|
return content;
|
|
164
|
-
}, cwd);
|
|
187
|
+
}, cwd, { resync: shouldResync });
|
|
165
188
|
output(results, raw, results.updated.length > 0 ? 'true' : 'false');
|
|
166
189
|
}
|
|
167
190
|
catch {
|
|
@@ -182,7 +205,7 @@ function cmdStateUpdate(cwd, field, value) {
|
|
|
182
205
|
const statePath = planningPaths(cwd).state;
|
|
183
206
|
try {
|
|
184
207
|
let updated = false;
|
|
185
|
-
const shouldResync = [
|
|
208
|
+
const shouldResync = shouldResyncStateProgress([field]);
|
|
186
209
|
// Preserve curated progress for body-only updates, but allow fields that
|
|
187
210
|
// directly project into progress.* frontmatter to rebuild after mutation.
|
|
188
211
|
readModifyWriteStateMd(statePath, (content) => {
|
|
@@ -922,6 +945,32 @@ function matchSessionSection(body) {
|
|
|
922
945
|
return body.match(/(?:^|\n)##[ \t]*Session[ \t]*\n([\s\S]*?)(?=\n##|$)/i)
|
|
923
946
|
|| body.match(/(?:^|\n)##[ \t]*Session Continuity[ \t]*\n([\s\S]*?)(?=\n##|$)/i);
|
|
924
947
|
}
|
|
948
|
+
function parseProsePhaseField(value) {
|
|
949
|
+
if (!value)
|
|
950
|
+
return { phase: null, name: null };
|
|
951
|
+
const phaseMatch = value.match(/\b(\d+[A-Z]?(?:\.\d+)*)\b/i);
|
|
952
|
+
const parenName = value.match(/\(([^)]+)\)/);
|
|
953
|
+
const dashName = value.match(/—\s*([^(\n]+?)(?:\s*\(|$)/);
|
|
954
|
+
const rawName = parenName?.[1] ?? dashName?.[1] ?? null;
|
|
955
|
+
const name = rawName && !/^(?:complete|executing|not started)$/i.test(rawName.trim())
|
|
956
|
+
? rawName.trim()
|
|
957
|
+
: null;
|
|
958
|
+
return {
|
|
959
|
+
phase: phaseMatch ? phaseMatch[1] : null,
|
|
960
|
+
name,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function parseProseLastActivityField(value) {
|
|
964
|
+
if (!value)
|
|
965
|
+
return { date: null, description: null };
|
|
966
|
+
const match = value.match(/^(\d{4}-\d{2}-\d{2})(?:\s+[—-]{1,2}\s+(.+))?$/);
|
|
967
|
+
if (!match)
|
|
968
|
+
return { date: value, description: null };
|
|
969
|
+
return {
|
|
970
|
+
date: match[1],
|
|
971
|
+
description: match[2]?.trim() || null,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
925
974
|
function cmdStateSnapshot(cwd, raw) {
|
|
926
975
|
const statePath = planningPaths(cwd).state;
|
|
927
976
|
if (!node_fs_1.default.existsSync(statePath)) {
|
|
@@ -950,15 +999,18 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
950
999
|
return null;
|
|
951
1000
|
};
|
|
952
1001
|
// Extract basic fields — frontmatter keys take precedence over body
|
|
953
|
-
const
|
|
954
|
-
const
|
|
1002
|
+
const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(body, 'Phase'));
|
|
1003
|
+
const currentPhase = fmScalar('current_phase') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Current Phase') ?? prosePhase.phase;
|
|
1004
|
+
const currentPhaseName = fmScalar('current_phase_name') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Current Phase Name') ?? prosePhase.name;
|
|
955
1005
|
const totalPhasesRaw = fmScalar('total_phases') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Total Phases');
|
|
956
1006
|
const currentPlan = fmScalar('current_plan') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Current Plan');
|
|
957
1007
|
const totalPlansRaw = fmScalar('total_plans_in_phase') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Total Plans in Phase');
|
|
958
1008
|
const status = fmScalar('status') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Status');
|
|
959
1009
|
const progressRaw = fmScalar('progress') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Progress');
|
|
960
|
-
const
|
|
961
|
-
const
|
|
1010
|
+
const rawLastActivity = (0, state_document_cjs_1.stateExtractField)(body, 'Last Activity') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Last activity');
|
|
1011
|
+
const proseLastActivity = parseProseLastActivityField(rawLastActivity);
|
|
1012
|
+
const lastActivity = fmScalar('last_activity') ?? proseLastActivity.date ?? rawLastActivity;
|
|
1013
|
+
const lastActivityDesc = fmScalar('last_activity_desc') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Last Activity Description') ?? proseLastActivity.description;
|
|
962
1014
|
const pausedAt = fmScalar('paused_at') ?? (0, state_document_cjs_1.stateExtractField)(body, 'Paused At');
|
|
963
1015
|
// Parse numeric fields
|
|
964
1016
|
const totalPhases = totalPhasesRaw ? parseInt(totalPhasesRaw, 10) : null;
|
|
@@ -1043,14 +1095,18 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
1043
1095
|
* reliably via `state json` instead of fragile regex parsing.
|
|
1044
1096
|
*/
|
|
1045
1097
|
function buildStateFrontmatter(bodyContent, cwd) {
|
|
1046
|
-
const
|
|
1047
|
-
const
|
|
1098
|
+
const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(bodyContent, 'Phase'));
|
|
1099
|
+
const currentPhase = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase') ?? prosePhase.phase;
|
|
1100
|
+
const currentPhaseName = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase Name') ?? prosePhase.name;
|
|
1048
1101
|
const currentPlan = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Plan');
|
|
1049
1102
|
const totalPhasesRaw = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Total Phases');
|
|
1050
1103
|
const totalPlansRaw = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Total Plans in Phase');
|
|
1051
1104
|
const status = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Status');
|
|
1052
1105
|
const progressRaw = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Progress');
|
|
1053
|
-
const
|
|
1106
|
+
const rawLastActivity = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Last Activity') ?? (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Last activity');
|
|
1107
|
+
const proseLastActivity = parseProseLastActivityField(rawLastActivity);
|
|
1108
|
+
const lastActivity = proseLastActivity.date ?? rawLastActivity;
|
|
1109
|
+
const lastActivityDesc = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Last Activity Description') ?? proseLastActivity.description;
|
|
1054
1110
|
// Bug #2444: scope Stopped At extraction to the ## Session section so that
|
|
1055
1111
|
// historical "Stopped at:" prose elsewhere in the body (e.g. in a
|
|
1056
1112
|
// Session Continuity Archive section) never overwrites the current value.
|
|
@@ -1194,6 +1250,8 @@ function buildStateFrontmatter(bodyContent, cwd) {
|
|
|
1194
1250
|
fm['last_updated'] = clock_cjs_1.realClock.nowIso();
|
|
1195
1251
|
if (lastActivity)
|
|
1196
1252
|
fm['last_activity'] = lastActivity;
|
|
1253
|
+
if (lastActivityDesc)
|
|
1254
|
+
fm['last_activity_desc'] = lastActivityDesc;
|
|
1197
1255
|
const progress = {};
|
|
1198
1256
|
if (totalPhases !== null)
|
|
1199
1257
|
progress['total_phases'] = totalPhases;
|
|
@@ -1745,14 +1803,14 @@ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
|
1745
1803
|
posBody = replaced;
|
|
1746
1804
|
}
|
|
1747
1805
|
// Update Last activity line if present
|
|
1748
|
-
const newActivity = `Last activity: ${today}
|
|
1806
|
+
const newActivity = `Last activity: ${today} — Phase ${phaseNumber} execution started`;
|
|
1749
1807
|
if (/^Last activity:/im.test(posBody)) {
|
|
1750
1808
|
posBody = posBody.replace(/^Last activity:.*$/im, newActivity);
|
|
1751
1809
|
}
|
|
1752
1810
|
else {
|
|
1753
1811
|
// Pipe-table format in Current Position (#1255)
|
|
1754
1812
|
// Value must match the inline branch (date + narrative), not bare date.
|
|
1755
|
-
const activityValue = `${today}
|
|
1813
|
+
const activityValue = `${today} — Phase ${phaseNumber} execution started`;
|
|
1756
1814
|
const replaced = (0, state_document_cjs_1.stateReplaceField)(posBody, 'Last Activity', activityValue)
|
|
1757
1815
|
?? (0, state_document_cjs_1.stateReplaceField)(posBody, 'Last activity', activityValue);
|
|
1758
1816
|
if (replaced !== null)
|
|
@@ -1770,7 +1828,7 @@ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
|
1770
1828
|
if (positionMatch) {
|
|
1771
1829
|
const header = positionMatch[1];
|
|
1772
1830
|
let posBody = positionMatch[2];
|
|
1773
|
-
const resumeActivity = `Last activity: ${today}
|
|
1831
|
+
const resumeActivity = `Last activity: ${today} — Phase ${phaseNumber} execution resumed (wave continue)`;
|
|
1774
1832
|
if (/^Last activity:/im.test(posBody)) {
|
|
1775
1833
|
posBody = posBody.replace(/^Last activity:.*$/im, resumeActivity);
|
|
1776
1834
|
body = body.replace(positionPattern, () => `${header}${posBody}`);
|
|
@@ -1933,7 +1991,7 @@ function cmdStatePlannedPhase(cwd, phaseNumber, planCount, raw) {
|
|
|
1933
1991
|
// Update Current Position section
|
|
1934
1992
|
body = updateCurrentPositionFields(body, {
|
|
1935
1993
|
status: 'Ready to execute',
|
|
1936
|
-
lastActivity: `${today}
|
|
1994
|
+
lastActivity: `${today} — Phase ${phaseNumber} planning complete`,
|
|
1937
1995
|
});
|
|
1938
1996
|
return reassemble(body);
|
|
1939
1997
|
}, cwd, { resync: false });
|
|
@@ -2472,14 +2530,14 @@ function cmdStateCompletePhase(cwd, raw, overridePhase) {
|
|
|
2472
2530
|
posBody = replaced;
|
|
2473
2531
|
}
|
|
2474
2532
|
// Update Last activity line if present
|
|
2475
|
-
const newActivity = `Last activity: ${today}
|
|
2533
|
+
const newActivity = `Last activity: ${today} — Phase ${currentPhase} marked complete`;
|
|
2476
2534
|
if (/^Last activity:/im.test(posBody)) {
|
|
2477
2535
|
posBody = posBody.replace(/^Last activity:.*$/im, newActivity);
|
|
2478
2536
|
}
|
|
2479
2537
|
else {
|
|
2480
2538
|
// Pipe-table format in Current Position (#1255)
|
|
2481
2539
|
// Value must match the inline branch (date + narrative), not bare date.
|
|
2482
|
-
const activityValue = `${today}
|
|
2540
|
+
const activityValue = `${today} — Phase ${currentPhase} marked complete`;
|
|
2483
2541
|
const replaced = (0, state_document_cjs_1.stateReplaceField)(posBody, 'Last Activity', activityValue)
|
|
2484
2542
|
?? (0, state_document_cjs_1.stateReplaceField)(posBody, 'Last activity', activityValue);
|
|
2485
2543
|
if (replaced !== null)
|
|
@@ -12,8 +12,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
|
-
const
|
|
16
|
-
const { output, error, ERROR_REASON } =
|
|
15
|
+
const ioMod = require("./io.cjs");
|
|
16
|
+
const { output, error, ERROR_REASON } = ioMod;
|
|
17
17
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
18
18
|
function isBehaviorAddingTaskContent(content) {
|
|
19
19
|
const tddTrue = /\btdd\s*=\s*["']true["']/i.test(content);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Teams Status Module — issue #1355
|
|
4
|
+
*
|
|
5
|
+
* Read-only detector for claude-code's experimental agent-teams feature.
|
|
6
|
+
* Exposes a PURE core function (env injected, no process.env/disk inside) and
|
|
7
|
+
* a thin CLI wrapper that reuses resolveRuntime from runtime-slash.cjs.
|
|
8
|
+
*
|
|
9
|
+
* Exports:
|
|
10
|
+
* resolveTeamsStatus({ runtime, env }) → TeamsStatus
|
|
11
|
+
* cmdTeamsStatus(cwd, opts) — I/O entry point
|
|
12
|
+
*
|
|
13
|
+
* resolveTeamsStatus is PURE: env and runtime are injected, no process.env or
|
|
14
|
+
* disk access inside the function. Pass process.env explicitly at call sites.
|
|
15
|
+
*
|
|
16
|
+
* cmdTeamsStatus is the I/O handler. It reads process.env, resolves the
|
|
17
|
+
* runtime via resolveRuntime(cwd) from runtime-slash.cjs (GSD_RUNTIME →
|
|
18
|
+
* config.runtime → 'claude' precedence), then:
|
|
19
|
+
* - default: prints JSON.stringify(status) to stdout via io.output, exits 0.
|
|
20
|
+
* - --active: prints nothing, exits 0 if status.active, exit 1 otherwise.
|
|
21
|
+
*
|
|
22
|
+
* Strictly read-only — no config writes, no disk mutation.
|
|
23
|
+
*
|
|
24
|
+
* Dependencies:
|
|
25
|
+
* - ./io.cjs (output)
|
|
26
|
+
* - ./runtime-slash.cjs (resolveRuntime)
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.resolveTeamsStatus = resolveTeamsStatus;
|
|
30
|
+
exports.cmdTeamsStatus = cmdTeamsStatus;
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
|
+
const ioMod = require("./io.cjs");
|
|
33
|
+
const { output: coreOutput } = ioMod;
|
|
34
|
+
// ─── Pure core ────────────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the agent-teams status from injected runtime and env.
|
|
37
|
+
*
|
|
38
|
+
* Strict truthiness: only '1' and 'true' (case-insensitive, trimmed) are on.
|
|
39
|
+
* '0', 'false', '', and unset are all off.
|
|
40
|
+
*
|
|
41
|
+
* @param opts.runtime The resolved runtime name (e.g. 'claude', 'codex')
|
|
42
|
+
* @param opts.env The environment map to read from (typically process.env)
|
|
43
|
+
*/
|
|
44
|
+
function resolveTeamsStatus(opts) {
|
|
45
|
+
const raw = (opts.env['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] ?? '').trim().toLowerCase();
|
|
46
|
+
const envOn = raw === '1' || raw === 'true'; // strict: never '0'/'false'/'' as on
|
|
47
|
+
const isClaude = opts.runtime === 'claude';
|
|
48
|
+
const source = !isClaude ? 'off: non-claude' : (envOn ? 'on: env' : 'off: flag absent');
|
|
49
|
+
return { active: envOn && isClaude, runtime: opts.runtime, env_present: envOn, source };
|
|
50
|
+
}
|
|
51
|
+
// ─── CLI command handler ──────────────────────────────────────────────────────
|
|
52
|
+
/**
|
|
53
|
+
* Command entry point: resolve runtime via resolveRuntime(cwd), read process.env,
|
|
54
|
+
* call resolveTeamsStatus, and emit the result.
|
|
55
|
+
*
|
|
56
|
+
* @param cwd Project root directory (used by resolveRuntime for config.json)
|
|
57
|
+
* @param opts Command options
|
|
58
|
+
* @param opts.active When true: print nothing, exit 0 if active, exit 1 otherwise
|
|
59
|
+
*/
|
|
60
|
+
function cmdTeamsStatus(cwd, opts) {
|
|
61
|
+
// Resolve runtime via the canonical precedence:
|
|
62
|
+
// GSD_RUNTIME → config.runtime → 'claude'
|
|
63
|
+
// Reuses resolveRuntime from runtime-slash.cjs — no reimplementation.
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
65
|
+
const runtimeSlash = require('./runtime-slash.cjs');
|
|
66
|
+
const runtime = runtimeSlash.resolveRuntime(cwd);
|
|
67
|
+
const status = resolveTeamsStatus({ runtime, env: process.env });
|
|
68
|
+
if (opts.active) {
|
|
69
|
+
// --active mode: no output, exit code encodes the boolean
|
|
70
|
+
process.exit(status.active ? 0 : 1);
|
|
71
|
+
}
|
|
72
|
+
// Default: emit JSON to stdout via io.output, exit 0
|
|
73
|
+
coreOutput(status, false);
|
|
74
|
+
}
|
|
@@ -12,8 +12,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
|
-
const
|
|
16
|
-
const {
|
|
15
|
+
const ioMod = require("./io.cjs");
|
|
16
|
+
const { output, error } = ioMod;
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const coreUtilsMod = require("./core-utils.cjs");
|
|
19
|
+
const { toPosixPath, generateSlugInternal } = coreUtilsMod;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
22
|
+
const { normalizePhaseName } = phaseIdMod;
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
|
+
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
25
|
+
const { findPhaseInternal } = phaseLocatorMod;
|
|
17
26
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
18
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
28
|
const planningWorkspace = require("./planning-workspace.cjs");
|
package/gsd-core/bin/lib/uat.cjs
CHANGED
|
@@ -15,8 +15,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
16
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
-
const
|
|
19
|
-
const { output, error
|
|
18
|
+
const io = require("./io.cjs");
|
|
19
|
+
const { output, error } = io;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
|
+
const roadmapParser = require("./roadmap-parser.cjs");
|
|
22
|
+
const { getMilestonePhaseFilter } = roadmapParser;
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
|
+
const coreUtils = require("./core-utils.cjs");
|
|
25
|
+
const { toPosixPath } = coreUtils;
|
|
20
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
27
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
22
28
|
const { planningDir } = planningWorkspace;
|
|
@@ -139,6 +145,13 @@ function parseCurrentTest(content) {
|
|
|
139
145
|
|| section.match(/^expected:\s*\|\n([\s\S]+)/m);
|
|
140
146
|
const expectedInlineMatch = section.match(/^expected:\s*(.+)\s*$/m);
|
|
141
147
|
if (!numberMatch || !nameMatch || (!expectedBlockMatch && !expectedInlineMatch)) {
|
|
148
|
+
if (!numberMatch && !nameMatch && !expectedBlockMatch && !expectedInlineMatch) {
|
|
149
|
+
const pendingTest = parseFirstPendingTest(content);
|
|
150
|
+
if (pendingTest) {
|
|
151
|
+
return pendingTest;
|
|
152
|
+
}
|
|
153
|
+
error('Current Test section is non-structured and no pending UAT test remains to resume');
|
|
154
|
+
}
|
|
142
155
|
error('Current Test section is malformed');
|
|
143
156
|
}
|
|
144
157
|
let expected;
|
|
@@ -159,6 +172,55 @@ function parseCurrentTest(content) {
|
|
|
159
172
|
expected: (0, security_cjs_1.sanitizeForDisplay)(expected),
|
|
160
173
|
};
|
|
161
174
|
}
|
|
175
|
+
function parseFirstPendingTest(content) {
|
|
176
|
+
const testsMatch = content.match(/##\s*Tests\s*\n([\s\S]*?)(?=\n##\s|$)/i);
|
|
177
|
+
if (!testsMatch) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const testsSection = testsMatch[1];
|
|
181
|
+
const headingPattern = /^###\s*(\d+)\.\s*([^\n]+)\s*$/gm;
|
|
182
|
+
const headings = [];
|
|
183
|
+
let headingMatch;
|
|
184
|
+
while ((headingMatch = headingPattern.exec(testsSection)) !== null) {
|
|
185
|
+
headings.push({
|
|
186
|
+
index: headingMatch.index,
|
|
187
|
+
number: parseInt(headingMatch[1], 10),
|
|
188
|
+
name: headingMatch[2].trim(),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
for (let i = 0; i < headings.length; i += 1) {
|
|
192
|
+
const current = headings[i];
|
|
193
|
+
const next = headings[i + 1];
|
|
194
|
+
const block = testsSection.slice(current.index, next ? next.index : undefined);
|
|
195
|
+
if (!/^result:\s*\[?pending\]?\s*$/im.test(block)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const expected = parseExpectedFromTestBlock(block);
|
|
199
|
+
if (!expected) {
|
|
200
|
+
error(`Pending UAT test ${current.number} is missing an expected field`);
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
complete: false,
|
|
204
|
+
number: current.number,
|
|
205
|
+
name: (0, security_cjs_1.sanitizeForDisplay)(current.name),
|
|
206
|
+
expected: (0, security_cjs_1.sanitizeForDisplay)(expected),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function parseExpectedFromTestBlock(block) {
|
|
212
|
+
const expectedBlockMatch = block.match(/^expected:\s*\|\n([\s\S]*?)(?=^\w[\w-]*:\s)/m)
|
|
213
|
+
|| block.match(/^expected:\s*\|\n([\s\S]+)/m);
|
|
214
|
+
if (expectedBlockMatch) {
|
|
215
|
+
return expectedBlockMatch[1]
|
|
216
|
+
.split('\n')
|
|
217
|
+
.map((line) => line.replace(/^ {2}/, ''))
|
|
218
|
+
.join('\n')
|
|
219
|
+
.trim();
|
|
220
|
+
}
|
|
221
|
+
const expectedInlineMatch = block.match(/^expected:\s*(.+)\s*$/m);
|
|
222
|
+
return expectedInlineMatch ? expectedInlineMatch[1].trim() : null;
|
|
223
|
+
}
|
|
162
224
|
// ─── buildCheckpoint ──────────────────────────────────────────────────────────
|
|
163
225
|
function buildCheckpoint(currentTest) {
|
|
164
226
|
return [
|
|
@@ -21,11 +21,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
};
|
|
22
22
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports --
|
|
25
|
-
const
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
25
|
+
const io = require("./io.cjs");
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
27
|
+
const phaseId = require("./phase-id.cjs");
|
|
26
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
27
29
|
const frontmatterMod = require("./frontmatter.cjs");
|
|
28
|
-
const { output,
|
|
30
|
+
const { output, error } = io;
|
|
31
|
+
const { extractPhaseToken } = phaseId;
|
|
29
32
|
const { extractFrontmatter } = frontmatterMod;
|
|
30
33
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
31
34
|
/** The set of status values that the gsd-verifier agent emits. */
|
|
@@ -170,7 +173,7 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
170
173
|
}
|
|
171
174
|
/**
|
|
172
175
|
* CLI command handler: resolve phaseDir against cwd, call readVerificationStatus,
|
|
173
|
-
* emit via
|
|
176
|
+
* emit via io.output().
|
|
174
177
|
*
|
|
175
178
|
* @param cwd - Current working directory (used to resolve phaseDirArg).
|
|
176
179
|
* @param phaseDirArg - Phase directory path (absolute or relative to cwd).
|
|
@@ -178,7 +181,7 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
178
181
|
*/
|
|
179
182
|
function cmdVerificationStatus(cwd, phaseDirArg, raw) {
|
|
180
183
|
if (!phaseDirArg) {
|
|
181
|
-
|
|
184
|
+
error('phase directory required for verification.status');
|
|
182
185
|
return;
|
|
183
186
|
}
|
|
184
187
|
const phaseDir = node_path_1.default.resolve(cwd, phaseDirArg);
|