gsdd-cli 0.24.0 → 0.26.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/README.md +97 -546
- package/bin/adapters/claude.mjs +18 -8
- package/bin/adapters/opencode.mjs +3 -3
- package/bin/gsdd.mjs +16 -33
- package/bin/lib/closeout-report.mjs +318 -0
- package/bin/lib/control-map.mjs +698 -26
- package/bin/lib/global-install.mjs +616 -0
- package/bin/lib/global-manifest.mjs +122 -0
- package/bin/lib/health.mjs +48 -30
- package/bin/lib/init-flow.mjs +3 -0
- package/bin/lib/init-prompts.mjs +6 -4
- package/bin/lib/init-runtime.mjs +33 -3
- package/bin/lib/lifecycle-preflight.mjs +58 -1
- package/bin/lib/models.mjs +136 -5
- package/bin/lib/next.mjs +834 -0
- package/bin/lib/phase.mjs +188 -41
- package/bin/lib/rendering.mjs +15 -1
- package/bin/lib/work-context.mjs +760 -0
- package/bin/lib/workflows.mjs +27 -0
- package/distilled/DESIGN.md +17 -9
- package/distilled/README.md +32 -3
- package/distilled/templates/agents.block.md +1 -1
- package/distilled/templates/agents.md +0 -1
- package/distilled/templates/codebase/architecture.md +0 -1
- package/distilled/templates/codebase/concerns.md +0 -1
- package/distilled/templates/codebase/conventions.md +0 -1
- package/distilled/templates/codebase/stack.md +0 -1
- package/distilled/templates/roadmap.md +0 -1
- package/distilled/templates/spec.md +0 -1
- package/distilled/workflows/new-project.md +1 -1
- package/distilled/workflows/verify.md +1 -1
- package/docs/RUNTIME-SUPPORT.md +23 -3
- package/docs/USER-GUIDE.md +40 -4
- package/docs/VERIFICATION-DISCIPLINE.md +3 -1
- package/package.json +2 -2
package/bin/lib/phase.mjs
CHANGED
|
@@ -210,42 +210,118 @@ function normalizeUiProofIssue(issue) {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
213
|
+
function stripInlineComment(value) {
|
|
214
|
+
return String(value || '').replace(/\s+#.*$/, '').trim();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function stripOuterScalarQuotes(value) {
|
|
218
|
+
return String(value)
|
|
219
|
+
.trim()
|
|
220
|
+
.replace(/^(['"])([\s\S]*)\1$/g, '$2')
|
|
221
|
+
.trim();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function normalizeNullableFrontmatterValue(value) {
|
|
225
|
+
const stripped = stripOuterScalarQuotes(value);
|
|
226
|
+
if (!stripped) return '';
|
|
227
|
+
if (stripped === '~') return '';
|
|
228
|
+
if (/^null$/i.test(stripped)) return '';
|
|
229
|
+
return stripped;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function extractUiProofSlotIds(value) {
|
|
233
|
+
const ids = [];
|
|
234
|
+
const slotPattern = /['"]?slot_id['"]?\s*:\s*['"]?([^,'"\]\s}]+)['"]?/g;
|
|
235
|
+
for (const match of String(value || '').matchAll(slotPattern)) {
|
|
236
|
+
ids.push(match[1].replace(/^['"]|['"]$/g, ''));
|
|
225
237
|
}
|
|
226
|
-
return
|
|
238
|
+
return ids;
|
|
227
239
|
}
|
|
228
240
|
|
|
229
|
-
function
|
|
230
|
-
const
|
|
231
|
-
if (!
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
for (
|
|
241
|
+
function readPlanFrontmatter(planContent) {
|
|
242
|
+
const content = String(planContent || '');
|
|
243
|
+
if (!content.startsWith('---')) return '';
|
|
244
|
+
const lines = content.split(/\r?\n/);
|
|
245
|
+
const frontmatter = [];
|
|
246
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
247
|
+
if (lines[index].trim() === '---') return frontmatter.join('\n');
|
|
248
|
+
frontmatter.push(lines[index]);
|
|
249
|
+
}
|
|
250
|
+
return '';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function frontmatterKeyBlock(frontmatter, key) {
|
|
254
|
+
const lines = String(frontmatter || '').split(/\r?\n/);
|
|
255
|
+
const keyPattern = new RegExp(`^${key}:[ \\t]*(.*)$`);
|
|
256
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
257
|
+
const match = lines[index].match(keyPattern);
|
|
258
|
+
if (!match) continue;
|
|
259
|
+
const block = [];
|
|
260
|
+
for (let next = index + 1; next < lines.length; next += 1) {
|
|
261
|
+
const line = lines[next];
|
|
262
|
+
if (/^\S[^:\n]*:\s*/.test(line)) break;
|
|
263
|
+
block.push(line);
|
|
264
|
+
}
|
|
265
|
+
return { inline: stripInlineComment(match[1]), block };
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function frontmatterScalar(frontmatter, key) {
|
|
271
|
+
const entry = frontmatterKeyBlock(frontmatter, key);
|
|
272
|
+
if (!entry) return null;
|
|
273
|
+
if (entry.inline && !['|', '>'].includes(entry.inline)) return entry.inline;
|
|
274
|
+
return entry.block
|
|
275
|
+
.map((line) => line.trim())
|
|
276
|
+
.filter(Boolean)
|
|
277
|
+
.join(' ')
|
|
278
|
+
.trim();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function readPlanUiProofContract(planContent) {
|
|
282
|
+
const frontmatter = readPlanFrontmatter(planContent);
|
|
283
|
+
const slotsEntry = frontmatterKeyBlock(frontmatter, 'ui_proof_slots');
|
|
284
|
+
const rationale = normalizeNullableFrontmatterValue(frontmatterScalar(frontmatter, 'no_ui_proof_rationale') || '');
|
|
285
|
+
const result = {
|
|
286
|
+
hasUiProofKey: Boolean(slotsEntry),
|
|
287
|
+
declaresSlots: false,
|
|
288
|
+
explicitEmptySlots: false,
|
|
289
|
+
noUiProofRationale: rationale,
|
|
290
|
+
hasNoUiProofRationale: Boolean(rationale.trim()),
|
|
291
|
+
slotIds: [],
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
if (!slotsEntry) return result;
|
|
295
|
+
|
|
296
|
+
if (slotsEntry.inline) {
|
|
297
|
+
if (['[]', 'null', '~'].includes(slotsEntry.inline)) {
|
|
298
|
+
result.explicitEmptySlots = true;
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
result.declaresSlots = true;
|
|
302
|
+
result.slotIds.push(...extractUiProofSlotIds(slotsEntry.inline));
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let sawMeaningfulLine = false;
|
|
307
|
+
for (const line of slotsEntry.block) {
|
|
235
308
|
const trimmed = line.trim();
|
|
236
309
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
237
|
-
|
|
238
|
-
if (/^\
|
|
239
|
-
|
|
240
|
-
if (slotMatch) slotIds.push(slotMatch[1].replace(/^['"]|['"]$/g, ''));
|
|
310
|
+
sawMeaningfulLine = true;
|
|
311
|
+
if (/^\s+-\s+/.test(line)) result.declaresSlots = true;
|
|
312
|
+
result.slotIds.push(...extractUiProofSlotIds(trimmed));
|
|
241
313
|
}
|
|
242
|
-
|
|
314
|
+
|
|
315
|
+
result.explicitEmptySlots = !result.declaresSlots && !sawMeaningfulLine;
|
|
316
|
+
return result;
|
|
243
317
|
}
|
|
244
318
|
|
|
245
319
|
function findUiProofSlotPlansAndFiles(planningDir, planDisplayPaths) {
|
|
246
320
|
const candidates = new Set();
|
|
247
321
|
const declaredPlans = [];
|
|
248
322
|
const declaredSlotIds = [];
|
|
323
|
+
const noUiPlans = [];
|
|
324
|
+
const contractErrors = [];
|
|
249
325
|
const names = new Set([
|
|
250
326
|
'ui-proof-slots.json',
|
|
251
327
|
'ui-proof-slots.md',
|
|
@@ -259,21 +335,45 @@ function findUiProofSlotPlansAndFiles(planningDir, planDisplayPaths) {
|
|
|
259
335
|
const fullPlanPath = join(planningDir, 'phases', planDisplayPath);
|
|
260
336
|
if (!existsSync(fullPlanPath)) continue;
|
|
261
337
|
const planContent = readFileSync(fullPlanPath, 'utf-8');
|
|
262
|
-
if (!planDeclaresUiProofSlots(planContent)) continue;
|
|
263
338
|
const relPlanPath = relative(planningDir, fullPlanPath).replace(/\\/g, '/');
|
|
339
|
+
const contract = readPlanUiProofContract(planContent);
|
|
340
|
+
const planDir = dirname(fullPlanPath);
|
|
341
|
+
const sidecars = [];
|
|
342
|
+
if (existsSync(planDir)) {
|
|
343
|
+
for (const entry of readdirSync(planDir, { withFileTypes: true })) {
|
|
344
|
+
if (entry.isFile() && names.has(entry.name)) {
|
|
345
|
+
sidecars.push(join(planDir, entry.name));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (contract.hasUiProofKey && !contract.declaresSlots && !contract.hasNoUiProofRationale) {
|
|
351
|
+
contractErrors.push(normalizeUiProofIssue({
|
|
352
|
+
code: 'missing_no_ui_proof_rationale',
|
|
353
|
+
path: `${relPlanPath}.no_ui_proof_rationale`,
|
|
354
|
+
message: 'Plan declares empty ui_proof_slots but does not provide a no_ui_proof_rationale.',
|
|
355
|
+
fix: 'Add a nonblank no_ui_proof_rationale for non-UI work, or declare required UI proof slots and add a planned slots artifact.',
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (contract.hasNoUiProofRationale && !contract.declaresSlots) {
|
|
360
|
+
noUiPlans.push({ plan: relPlanPath, sidecars });
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (!contract.declaresSlots) continue;
|
|
365
|
+
|
|
264
366
|
declaredPlans.push(relPlanPath);
|
|
265
|
-
for (const slotId of
|
|
367
|
+
for (const slotId of contract.slotIds) {
|
|
266
368
|
declaredSlotIds.push({ plan: relPlanPath, slot_id: slotId });
|
|
267
369
|
}
|
|
268
|
-
const planDir = dirname(fullPlanPath);
|
|
269
|
-
if (!existsSync(planDir)) continue;
|
|
270
370
|
for (const entry of readdirSync(planDir, { withFileTypes: true })) {
|
|
271
371
|
if (entry.isFile() && names.has(entry.name)) {
|
|
272
372
|
candidates.add(join(planDir, entry.name));
|
|
273
373
|
}
|
|
274
374
|
}
|
|
275
375
|
}
|
|
276
|
-
return { declaredPlans, declaredSlotIds, files: [...candidates].sort() };
|
|
376
|
+
return { declaredPlans, declaredSlotIds, noUiPlans, contractErrors, files: [...candidates].sort() };
|
|
277
377
|
}
|
|
278
378
|
|
|
279
379
|
function comparePhaseUiProof({ planningDir, workspaceRoot, planDisplayPaths }) {
|
|
@@ -285,9 +385,23 @@ function comparePhaseUiProof({ planningDir, workspaceRoot, planDisplayPaths }) {
|
|
|
285
385
|
|
|
286
386
|
const plannedSlots = [];
|
|
287
387
|
const errors = [];
|
|
388
|
+
const warnings = [];
|
|
288
389
|
const planned = [];
|
|
289
390
|
const observed = [];
|
|
290
391
|
|
|
392
|
+
errors.push(...plannedDiscovery.contractErrors);
|
|
393
|
+
for (const noUiPlan of plannedDiscovery.noUiPlans) {
|
|
394
|
+
for (const filePath of noUiPlan.sidecars) {
|
|
395
|
+
warnings.push({
|
|
396
|
+
code: 'stale_ui_proof_sidecar_ignored',
|
|
397
|
+
severity: 'warn',
|
|
398
|
+
path: relative(workspaceRoot, filePath).replace(/\\/g, '/'),
|
|
399
|
+
message: `Plan ${noUiPlan.plan} records no_ui_proof_rationale, so the UI proof sidecar is ignored for closure.`,
|
|
400
|
+
fix_hint: 'Remove or classify the stale sidecar if it no longer belongs to this non-UI phase.',
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
291
405
|
for (const filePath of plannedFiles) {
|
|
292
406
|
const rel = relative(workspaceRoot, filePath).replace(/\\/g, '/');
|
|
293
407
|
const parsed = parseUiProofSlotsContent(readFileSync(filePath, 'utf-8'), rel);
|
|
@@ -343,6 +457,7 @@ function comparePhaseUiProof({ planningDir, workspaceRoot, planDisplayPaths }) {
|
|
|
343
457
|
status: 'missing',
|
|
344
458
|
comparison: { status: 'missing', slots: [], errors: [missingError] },
|
|
345
459
|
errors: [missingError],
|
|
460
|
+
warnings,
|
|
346
461
|
};
|
|
347
462
|
}
|
|
348
463
|
|
|
@@ -350,9 +465,10 @@ function comparePhaseUiProof({ planningDir, workspaceRoot, planDisplayPaths }) {
|
|
|
350
465
|
return {
|
|
351
466
|
planned,
|
|
352
467
|
observed,
|
|
353
|
-
status: 'not_applicable',
|
|
468
|
+
status: errors.length > 0 ? 'partial' : 'not_applicable',
|
|
354
469
|
comparison: null,
|
|
355
470
|
errors,
|
|
471
|
+
warnings,
|
|
356
472
|
};
|
|
357
473
|
}
|
|
358
474
|
|
|
@@ -366,6 +482,7 @@ function comparePhaseUiProof({ planningDir, workspaceRoot, planDisplayPaths }) {
|
|
|
366
482
|
status: comparison.status,
|
|
367
483
|
comparison,
|
|
368
484
|
errors: comparison.errors || errors,
|
|
485
|
+
warnings,
|
|
369
486
|
};
|
|
370
487
|
}
|
|
371
488
|
|
|
@@ -534,26 +651,41 @@ export function cmdFindPhase(...args) {
|
|
|
534
651
|
});
|
|
535
652
|
}
|
|
536
653
|
|
|
537
|
-
export function
|
|
654
|
+
export function buildPhaseVerificationReport(...args) {
|
|
538
655
|
const { args: normalizedArgs, workspaceRoot, planningDir, invalid, error } = resolveWorkspaceContext(args);
|
|
539
656
|
if (invalid) {
|
|
540
|
-
|
|
541
|
-
process.exitCode = 1;
|
|
542
|
-
return;
|
|
657
|
+
return { ok: false, error, exitCode: 1 };
|
|
543
658
|
}
|
|
544
659
|
const phaseNum = normalizedArgs[0];
|
|
545
660
|
if (!phaseNum) {
|
|
546
|
-
|
|
547
|
-
process.exitCode = 1; return;
|
|
661
|
+
return { ok: false, error: 'Usage: gsdd verify <phase-number>', exitCode: 1 };
|
|
548
662
|
}
|
|
549
663
|
|
|
550
664
|
if (!existsSync(planningDir)) {
|
|
551
|
-
|
|
552
|
-
process.exitCode = 1; return;
|
|
665
|
+
return { ok: false, error: 'No .planning/ directory found.', exitCode: 1 };
|
|
553
666
|
}
|
|
554
667
|
const phasesDir = join(planningDir, 'phases');
|
|
555
668
|
const matchingPlans = findFiles(phasesDir, `${padPhase(phaseNum)}-PLAN`);
|
|
556
669
|
const matchingSummaries = findFiles(phasesDir, `${padPhase(phaseNum)}-SUMMARY`);
|
|
670
|
+
const prerequisiteBlockers = [];
|
|
671
|
+
if (matchingPlans.length === 0) {
|
|
672
|
+
prerequisiteBlockers.push({
|
|
673
|
+
code: 'missing_phase_plan',
|
|
674
|
+
severity: 'blocker',
|
|
675
|
+
path: `.planning/phases/${padPhase(phaseNum)}-*/${padPhase(phaseNum)}-PLAN.md`,
|
|
676
|
+
message: `No PLAN.md artifact was found for phase ${normalizePhaseToken(phaseNum)}.`,
|
|
677
|
+
fix_hint: `Run /gsdd-plan ${normalizePhaseToken(phaseNum)} before verifying this phase.`,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
if (matchingPlans.length > 0 && matchingSummaries.length === 0) {
|
|
681
|
+
prerequisiteBlockers.push({
|
|
682
|
+
code: 'missing_phase_summary',
|
|
683
|
+
severity: 'blocker',
|
|
684
|
+
path: `.planning/phases/${padPhase(phaseNum)}-*/${padPhase(phaseNum)}-SUMMARY.md`,
|
|
685
|
+
message: `No SUMMARY.md artifact was found for phase ${normalizePhaseToken(phaseNum)}.`,
|
|
686
|
+
fix_hint: `Run /gsdd-execute ${normalizePhaseToken(phaseNum)} before verifying this phase.`,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
557
689
|
const artifacts = matchingPlans.flatMap((planPath) => {
|
|
558
690
|
const fullPath = join(phasesDir, planPath);
|
|
559
691
|
return existsSync(fullPath)
|
|
@@ -576,10 +708,11 @@ export function cmdVerify(...args) {
|
|
|
576
708
|
required_block: uiProof.status !== 'not_applicable' && !uiProofSatisfied ? 'ui-proof-failed' : null,
|
|
577
709
|
};
|
|
578
710
|
const blockedOn = [
|
|
711
|
+
...(prerequisiteBlockers.length > 0 ? ['prerequisites'] : []),
|
|
579
712
|
...(artifactStatus.satisfied ? [] : ['artifacts']),
|
|
580
713
|
...(uiProofGate.blocks_verification ? ['ui_proof'] : []),
|
|
581
714
|
];
|
|
582
|
-
const closureVerified = legacyVerified && artifactStatus.satisfied && uiProofSatisfied;
|
|
715
|
+
const closureVerified = legacyVerified && prerequisiteBlockers.length === 0 && artifactStatus.satisfied && uiProofSatisfied;
|
|
583
716
|
|
|
584
717
|
const result = {
|
|
585
718
|
phase: normalizePhaseToken(phaseNum),
|
|
@@ -593,12 +726,26 @@ export function cmdVerify(...args) {
|
|
|
593
726
|
verified: closureVerified,
|
|
594
727
|
legacy_verified: legacyVerified,
|
|
595
728
|
phase_artifacts_present: legacyVerified,
|
|
729
|
+
prerequisite_status: {
|
|
730
|
+
satisfied: prerequisiteBlockers.length === 0,
|
|
731
|
+
blockers: prerequisiteBlockers,
|
|
732
|
+
},
|
|
596
733
|
ui_proof: uiProofGate,
|
|
597
734
|
blocked_on: blockedOn,
|
|
598
735
|
blocks_verification: blockedOn.length > 0,
|
|
599
736
|
};
|
|
600
|
-
|
|
601
|
-
|
|
737
|
+
return { ok: true, result, exitCode: closureVerified ? 0 : 1 };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export function cmdVerify(...args) {
|
|
741
|
+
const report = buildPhaseVerificationReport(...args);
|
|
742
|
+
if (!report.ok) {
|
|
743
|
+
console.error(report.error);
|
|
744
|
+
process.exitCode = report.exitCode;
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
output(report.result);
|
|
748
|
+
if (report.exitCode !== 0) process.exitCode = report.exitCode;
|
|
602
749
|
}
|
|
603
750
|
|
|
604
751
|
export function cmdScaffold(...args) {
|
package/bin/lib/rendering.mjs
CHANGED
|
@@ -7,6 +7,7 @@ const __dirname = dirname(__filename);
|
|
|
7
7
|
const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
|
|
8
8
|
const HELPER_LIB_FILES = Object.freeze([
|
|
9
9
|
'cli-utils.mjs',
|
|
10
|
+
'closeout-report.mjs',
|
|
10
11
|
'control-map.mjs',
|
|
11
12
|
'evidence-contract.mjs',
|
|
12
13
|
'file-ops.mjs',
|
|
@@ -47,19 +48,28 @@ function renderPlanningCliLauncher() {
|
|
|
47
48
|
|
|
48
49
|
import { cmdFileOp } from './lib/file-ops.mjs';
|
|
49
50
|
import { cmdLifecyclePreflight } from './lib/lifecycle-preflight.mjs';
|
|
50
|
-
import { cmdPhaseStatus } from './lib/phase.mjs';
|
|
51
|
+
import { cmdPhaseStatus, cmdVerify } from './lib/phase.mjs';
|
|
51
52
|
import { cmdSessionFingerprint } from './lib/session-fingerprint.mjs';
|
|
52
53
|
import { cmdUiProof } from './lib/ui-proof.mjs';
|
|
53
54
|
import { cmdControlMap } from './lib/control-map.mjs';
|
|
55
|
+
import { createCmdCloseoutReport } from './lib/closeout-report.mjs';
|
|
54
56
|
import { bootstrapHelperWorkspace, consumeWorkspaceRootArg, resolveWorkspaceContext } from './lib/workspace-root.mjs';
|
|
55
57
|
|
|
58
|
+
const HELPER_CONTEXT = {
|
|
59
|
+
workflows: [],
|
|
60
|
+
frameworkVersion: 'generated-helper',
|
|
61
|
+
};
|
|
62
|
+
const cmdCloseoutReport = createCmdCloseoutReport(HELPER_CONTEXT);
|
|
63
|
+
|
|
56
64
|
const COMMANDS = {
|
|
57
65
|
'file-op': cmdFileOp,
|
|
58
66
|
'lifecycle-preflight': cmdLifecyclePreflight,
|
|
59
67
|
'phase-status': cmdPhaseStatus,
|
|
68
|
+
verify: cmdVerify,
|
|
60
69
|
'session-fingerprint': cmdSessionFingerprint,
|
|
61
70
|
'ui-proof': cmdUiProof,
|
|
62
71
|
'control-map': cmdControlMap,
|
|
72
|
+
'closeout-report': cmdCloseoutReport,
|
|
63
73
|
};
|
|
64
74
|
|
|
65
75
|
function printHelp() {
|
|
@@ -72,6 +82,8 @@ function printHelp() {
|
|
|
72
82
|
' Example: node .planning/bin/gsdd.mjs file-op delete .planning/.continue-here.bak --missing ok',
|
|
73
83
|
' phase-status <N> <status> Update ROADMAP.md phase status ([ ] / [-] / [x])',
|
|
74
84
|
' Example: node .planning/bin/gsdd.mjs phase-status 1 done',
|
|
85
|
+
' verify <N> Run direct phase artifact and UI-proof gate checks',
|
|
86
|
+
' Example: node .planning/bin/gsdd.mjs verify 1',
|
|
75
87
|
' lifecycle-preflight <surface> [phase]',
|
|
76
88
|
' Inspect lifecycle gate results for a workflow surface',
|
|
77
89
|
' Example: node .planning/bin/gsdd.mjs lifecycle-preflight verify 1 --expects-mutation phase-status',
|
|
@@ -83,6 +95,8 @@ function printHelp() {
|
|
|
83
95
|
' Compare planned UI proof slots against observed bundles',
|
|
84
96
|
' control-map [--json] [--with-ignored] [--annotations <path>]',
|
|
85
97
|
' Report computed repo/worktree/planning state and local annotations',
|
|
98
|
+
' closeout-report [--json] [--phase <N>]',
|
|
99
|
+
' Replay read-only closeout status from control-map, health, preflight, verify, and UI-proof signals',
|
|
86
100
|
'',
|
|
87
101
|
'Advanced option:',
|
|
88
102
|
' --workspace-root <path> Override workspace root discovery before or after the subcommand',
|