@opengsd/gsd-core 1.16.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -2
- package/.opencode/plugins/gsd-core.js +251 -1
- package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
- package/GEMINI.md +1 -1
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.compact.md +6 -0
- package/agents/gsd-debug-session-manager.md +6 -0
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +1 -1
- package/bin/gsd-mcp-server.js +3 -1
- package/bin/install.js +398 -95
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +34 -12
- package/gsd-core/bin/lib/adr-parser.cjs +37 -5
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +29 -7
- package/gsd-core/bin/lib/capability-registry.cjs +138 -80
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +6 -3
- package/gsd-core/bin/lib/capability-validator.cjs +8 -5
- package/gsd-core/bin/lib/capability-writer.cjs +2 -1
- package/gsd-core/bin/lib/commands.cjs +33 -9
- package/gsd-core/bin/lib/config-loader.cjs +3 -3
- package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +5 -4
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
- package/gsd-core/bin/lib/gate-ui-safety.cjs +2 -1
- package/gsd-core/bin/lib/graphify.cjs +3 -2
- package/gsd-core/bin/lib/init.cjs +43 -16
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +11 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +5 -3
- package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
- package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
- package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
- package/gsd-core/bin/lib/mcp-server.cjs +5 -1
- package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
- package/gsd-core/bin/lib/opencode-version.cjs +486 -0
- package/gsd-core/bin/lib/phase.cjs +141 -11
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +2 -1
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap.cjs +56 -5
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +142 -6
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +58 -3
- package/gsd-core/bin/lib/runtime-slash.cjs +13 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
- package/gsd-core/bin/lib/smart-entry.cjs +100 -53
- package/gsd-core/bin/lib/state-document.cjs +37 -3
- package/gsd-core/bin/lib/state-transition.cjs +98 -19
- package/gsd-core/bin/lib/state.cjs +544 -126
- package/gsd-core/bin/lib/task-command-router.cjs +2 -3
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/update-context.cjs +18 -1
- package/gsd-core/bin/lib/verification.cjs +107 -17
- package/gsd-core/bin/lib/verify.cjs +36 -1
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +3 -3
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/tdd.md +4 -3
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +21 -2
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone.md +25 -5
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
- package/gsd-core/workflows/discuss-phase.md +5 -1
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
- package/gsd-core/workflows/execute-phase.md +40 -7
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/import.md +5 -1
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +41 -73
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +22 -7
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +19 -11
- package/gsd-core/workflows/review.md +6 -2
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +3 -3
- package/gsd-core/workflows/ship.md +13 -10
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +11 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
- package/hooks/dist/gsd-check-update.js +26 -20
- package/hooks/dist/gsd-context-monitor.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
- package/hooks/dist/gsd-secret-read-guard.js +189 -15
- package/hooks/dist/gsd-statusline.js +321 -22
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-agent-isolation-guard.js +3 -1
- package/hooks/gsd-check-update.js +26 -20
- package/hooks/gsd-context-monitor.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +2 -1
- package/hooks/gsd-secret-read-guard.js +189 -15
- package/hooks/gsd-statusline.js +321 -22
- package/hooks/gsd-validate-commit.sh +27 -5
- package/package.json +1 -1
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
- package/scripts/diagnose-validate-commit-phases.cjs +348 -0
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +2 -0
- package/scripts/gen-capability-registry.cjs +7 -1
- package/scripts/gen-loop-host-contract.cjs +4 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +13 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +26 -0
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/mutation-matrix.cjs +3 -1
- package/scripts/release-tarball-smoke.cjs +15 -7
- package/scripts/xtrace-phase-timing.cjs +205 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -43,6 +43,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
43
43
|
const node_path_1 = __importDefault(require("node:path"));
|
|
44
44
|
const node_child_process_1 = require("node:child_process");
|
|
45
45
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
46
|
+
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
46
47
|
// #3696: the calendar-validity predicate moved to the STATE.md document module
|
|
47
48
|
// so `state validate` can assert the same `last_activity` invariant this reader
|
|
48
49
|
// already enforces (ADR-227). Two copies would let the two surfaces disagree
|
|
@@ -52,7 +53,7 @@ const ioMod = require("./io.cjs");
|
|
|
52
53
|
const { output } = ioMod;
|
|
53
54
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
54
55
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
55
|
-
const { planningPaths } = planningWorkspace;
|
|
56
|
+
const { planningPaths, resolvePhaseIdConvention } = planningWorkspace;
|
|
56
57
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
57
58
|
const frontmatter = require("./frontmatter.cjs");
|
|
58
59
|
const { extractFrontmatter, frontmatterBlock } = frontmatter;
|
|
@@ -60,6 +61,9 @@ const { extractFrontmatter, frontmatterBlock } = frontmatter;
|
|
|
60
61
|
const phaseLifecycle = require("./phase-lifecycle.cjs");
|
|
61
62
|
const { deriveProgressFromRoadmap } = phaseLifecycle;
|
|
62
63
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
64
|
+
const roadmapParser = require("./roadmap-parser.cjs");
|
|
65
|
+
const { extractCurrentMilestone, scanMilestonePhaseIds } = roadmapParser;
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
63
67
|
const stateDocument = require("./state-document.cjs");
|
|
64
68
|
const { stateFieldValue } = stateDocument;
|
|
65
69
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -352,6 +356,7 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
352
356
|
stale_activity: false,
|
|
353
357
|
roadmap_total_phases: null,
|
|
354
358
|
roadmap_completed_phases: null,
|
|
359
|
+
milestone_phases: [],
|
|
355
360
|
// No STATE.md (or unreadable) → no stamp to compare. Unknown, not fresh.
|
|
356
361
|
state_commits_behind: null,
|
|
357
362
|
state_commit_stale: null,
|
|
@@ -425,12 +430,18 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
425
430
|
// Progress table — isComplete falls back to the legacy comparison in that case.
|
|
426
431
|
let roadmapTotalPhases = null;
|
|
427
432
|
let roadmapCompletedPhases = null;
|
|
433
|
+
let milestonePhases = [];
|
|
428
434
|
if (hasRoadmap) {
|
|
429
435
|
try {
|
|
430
436
|
const roadmapContent = node_fs_1.default.readFileSync(paths.roadmap, 'utf8');
|
|
431
437
|
const derived = deriveProgressFromRoadmap(roadmapContent);
|
|
432
438
|
roadmapTotalPhases = derived.totalPhases;
|
|
433
439
|
roadmapCompletedPhases = derived.completedPhases;
|
|
440
|
+
// #5228: the ordered phase list of the current milestone, from the same
|
|
441
|
+
// scan the milestone-scoped commands use.
|
|
442
|
+
milestonePhases = [
|
|
443
|
+
...scanMilestonePhaseIds(extractCurrentMilestone(roadmapContent, cwd), resolvePhaseIdConvention(cwd)),
|
|
444
|
+
].sort(comparePhaseNum);
|
|
434
445
|
}
|
|
435
446
|
catch {
|
|
436
447
|
/* ROADMAP.md unreadable — leave null; isComplete falls back to legacy. */
|
|
@@ -457,6 +468,7 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
457
468
|
stale_activity: staleActivity,
|
|
458
469
|
roadmap_total_phases: roadmapTotalPhases,
|
|
459
470
|
roadmap_completed_phases: roadmapCompletedPhases,
|
|
471
|
+
milestone_phases: milestonePhases,
|
|
460
472
|
state_commits_behind: freshness.commits_behind,
|
|
461
473
|
state_commit_stale: freshness.commit_stale,
|
|
462
474
|
};
|
|
@@ -488,10 +500,21 @@ function isComplete(s) {
|
|
|
488
500
|
// Legacy path: STATE.md comparison. It only fires when ROADMAP.md is
|
|
489
501
|
// absent or has no Progress table, and uses canonical phase-id ordering
|
|
490
502
|
// so dotted phase ids are never coerced to JavaScript numbers.
|
|
491
|
-
if (s.
|
|
492
|
-
return false;
|
|
493
|
-
if (comparePhaseNum(s.current_phase, s.total_phases) < 0)
|
|
503
|
+
if (s.current_phase === null)
|
|
494
504
|
return false;
|
|
505
|
+
if (s.milestone_phases.length > 0) {
|
|
506
|
+
// #5228: compare against the milestone's LAST phase. The phase count is
|
|
507
|
+
// not a phase number once numbering continues from an earlier milestone.
|
|
508
|
+
const last = s.milestone_phases[s.milestone_phases.length - 1];
|
|
509
|
+
if (comparePhaseNum(s.current_phase, last) < 0)
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
if (s.total_phases === null)
|
|
514
|
+
return false;
|
|
515
|
+
if (comparePhaseNum(s.current_phase, s.total_phases) < 0)
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
495
518
|
}
|
|
496
519
|
// Status regex: require milestone-level completion language. The pre-fix
|
|
497
520
|
// regex matched any "shipped" / "done" substring (per-phase language).
|
|
@@ -558,52 +581,54 @@ function rec(id, label, command) {
|
|
|
558
581
|
return action(id, label, command, true);
|
|
559
582
|
}
|
|
560
583
|
/** Per-situation ordered action list; recommended action first. */
|
|
561
|
-
function actionsFor(situation, s) {
|
|
584
|
+
function actionsFor(situation, s, runtime = 'claude') {
|
|
585
|
+
// #4859: commands are printed verbatim, so name them the way this runtime registers them.
|
|
586
|
+
const cmd = (name) => (0, runtime_slash_cjs_1.formatGsdSlash)(name, runtime);
|
|
562
587
|
const phaseN = s.current_phase ?? '';
|
|
563
588
|
const execLabel = phaseN === '' ? 'Continue executing' : `Continue executing phase ${phaseN}`;
|
|
564
589
|
switch (situation) {
|
|
565
590
|
case 'no-project':
|
|
566
591
|
return [
|
|
567
|
-
rec('new-project', 'Start a new project', '
|
|
568
|
-
action('map-codebase', 'Map an existing codebase', '
|
|
569
|
-
action('quick', 'Quick task', '
|
|
570
|
-
action('help', 'Show help', '
|
|
592
|
+
rec('new-project', 'Start a new project', cmd('new-project')),
|
|
593
|
+
action('map-codebase', 'Map an existing codebase', cmd('map-codebase')),
|
|
594
|
+
action('quick', 'Quick task', cmd('quick')),
|
|
595
|
+
action('help', 'Show help', cmd('help')),
|
|
571
596
|
];
|
|
572
597
|
case 'paused':
|
|
573
598
|
return [
|
|
574
|
-
rec('resume-work', 'Resume work', '
|
|
575
|
-
action('progress', 'Show progress', '
|
|
576
|
-
action('quick', 'Quick task', '
|
|
577
|
-
action('help', 'Show help', '
|
|
599
|
+
rec('resume-work', 'Resume work', cmd('resume-work')),
|
|
600
|
+
action('progress', 'Show progress', cmd('progress')),
|
|
601
|
+
action('quick', 'Quick task', cmd('quick')),
|
|
602
|
+
action('help', 'Show help', cmd('help')),
|
|
578
603
|
];
|
|
579
604
|
case 'blocked':
|
|
580
605
|
return [
|
|
581
|
-
rec('debug', 'Debug the blocker', '
|
|
582
|
-
action('verify-work', 'Verify current work', '
|
|
583
|
-
action('capture', 'Capture a note', '
|
|
584
|
-
action('progress', 'Show progress', '
|
|
606
|
+
rec('debug', 'Debug the blocker', cmd('debug')),
|
|
607
|
+
action('verify-work', 'Verify current work', cmd('verify-work')),
|
|
608
|
+
action('capture', 'Capture a note', cmd('capture')),
|
|
609
|
+
action('progress', 'Show progress', cmd('progress')),
|
|
585
610
|
];
|
|
586
611
|
case 'verify-failed':
|
|
587
612
|
return [
|
|
588
|
-
rec('verify-work', 'Re-verify work', '
|
|
589
|
-
action('debug', 'Debug the failure', '
|
|
590
|
-
action('code-review', 'Review recent work', '
|
|
591
|
-
action('progress', 'Show progress', '
|
|
613
|
+
rec('verify-work', 'Re-verify work', cmd('verify-work')),
|
|
614
|
+
action('debug', 'Debug the failure', cmd('debug')),
|
|
615
|
+
action('code-review', 'Review recent work', cmd('code-review')),
|
|
616
|
+
action('progress', 'Show progress', cmd('progress')),
|
|
592
617
|
];
|
|
593
618
|
case 'needs-first-phase':
|
|
594
619
|
return [
|
|
595
|
-
rec('discuss-phase', 'Discuss the first phase', '
|
|
596
|
-
action('plan-phase', 'Plan phase 1', '
|
|
597
|
-
action('quick', 'Quick task', '
|
|
598
|
-
action('progress', 'Show progress', '
|
|
620
|
+
rec('discuss-phase', 'Discuss the first phase', cmd('discuss-phase')),
|
|
621
|
+
action('plan-phase', 'Plan phase 1', cmd('plan-phase')),
|
|
622
|
+
action('quick', 'Quick task', cmd('quick')),
|
|
623
|
+
action('progress', 'Show progress', cmd('progress')),
|
|
599
624
|
];
|
|
600
625
|
case 'planning':
|
|
601
626
|
// Forward motion → delegate to the single gated engine (see 'executing').
|
|
602
627
|
return [
|
|
603
|
-
rec('progress-next', `Advance to the next step (plan phase ${phaseN || 1})`, '
|
|
604
|
-
action('plan-phase', `Plan phase ${phaseN || 1}`, '
|
|
605
|
-
action('discuss-phase', 'Discuss before planning', '
|
|
606
|
-
action('progress', 'Show progress', '
|
|
628
|
+
rec('progress-next', `Advance to the next step (plan phase ${phaseN || 1})`, cmd('progress --next')),
|
|
629
|
+
action('plan-phase', `Plan phase ${phaseN || 1}`, cmd('plan-phase')),
|
|
630
|
+
action('discuss-phase', 'Discuss before planning', cmd('discuss-phase')),
|
|
631
|
+
action('progress', 'Show progress', cmd('progress')),
|
|
607
632
|
];
|
|
608
633
|
case 'executing':
|
|
609
634
|
// In-project forward motion delegates to the single gated engine
|
|
@@ -612,44 +637,66 @@ function actionsFor(situation, s) {
|
|
|
612
637
|
// /gsd:execute-phase here — that divergence is why the old flat /gsd-next
|
|
613
638
|
// was removed (#3054). Direct execute stays as an explicit secondary choice.
|
|
614
639
|
return [
|
|
615
|
-
rec('progress-next', 'Advance to the next step', '
|
|
616
|
-
action('execute-phase', execLabel, '
|
|
617
|
-
action('quick', 'Quick task', '
|
|
618
|
-
action('code-review', 'Review recent work', '
|
|
640
|
+
rec('progress-next', 'Advance to the next step', cmd('progress --next')),
|
|
641
|
+
action('execute-phase', execLabel, cmd('execute-phase')),
|
|
642
|
+
action('quick', 'Quick task', cmd('quick')),
|
|
643
|
+
action('code-review', 'Review recent work', cmd('code-review')),
|
|
619
644
|
];
|
|
620
645
|
case 'verify-pending':
|
|
621
646
|
// Forward motion → delegate to the single gated engine (see 'executing').
|
|
622
647
|
return [
|
|
623
|
-
rec('progress-next', 'Advance to the next step (verify)', '
|
|
624
|
-
action('verify-work', 'Verify work', '
|
|
625
|
-
action('code-review', 'Review recent work', '
|
|
626
|
-
action('ship', 'Ship completed work', '
|
|
648
|
+
rec('progress-next', 'Advance to the next step (verify)', cmd('progress --next')),
|
|
649
|
+
action('verify-work', 'Verify work', cmd('verify-work')),
|
|
650
|
+
action('code-review', 'Review recent work', cmd('code-review')),
|
|
651
|
+
action('ship', 'Ship completed work', cmd('ship')),
|
|
627
652
|
];
|
|
628
653
|
case 'idle-stranded':
|
|
629
654
|
return [
|
|
630
|
-
rec('ship', 'Ship committed work', '
|
|
631
|
-
action('complete-milestone', 'Complete the milestone', '
|
|
632
|
-
action('progress', 'Show progress', '
|
|
633
|
-
action('capture', 'Capture a note', '
|
|
655
|
+
rec('ship', 'Ship committed work', cmd('ship')),
|
|
656
|
+
action('complete-milestone', 'Complete the milestone', cmd('complete-milestone')),
|
|
657
|
+
action('progress', 'Show progress', cmd('progress')),
|
|
658
|
+
action('capture', 'Capture a note', cmd('capture')),
|
|
634
659
|
];
|
|
635
660
|
case 'complete':
|
|
636
661
|
return [
|
|
637
|
-
rec('new-milestone', 'Start a new milestone', '
|
|
638
|
-
action('extract-learnings', 'Extract learnings', '
|
|
639
|
-
action('quick', 'Quick task', '
|
|
640
|
-
action('progress', 'Show progress', '
|
|
662
|
+
rec('new-milestone', 'Start a new milestone', cmd('new-milestone')),
|
|
663
|
+
action('extract-learnings', 'Extract learnings', cmd('extract-learnings')),
|
|
664
|
+
action('quick', 'Quick task', cmd('quick')),
|
|
665
|
+
action('progress', 'Show progress', cmd('progress')),
|
|
641
666
|
];
|
|
642
667
|
case 'unknown':
|
|
643
668
|
default:
|
|
644
669
|
return [
|
|
645
|
-
rec('progress', 'Show progress', '
|
|
646
|
-
action('progress-next', 'Advance to the next step', '
|
|
647
|
-
action('quick', 'Quick task', '
|
|
648
|
-
action('help', 'Show help', '
|
|
670
|
+
rec('progress', 'Show progress', cmd('progress')),
|
|
671
|
+
action('progress-next', 'Advance to the next step', cmd('progress --next')),
|
|
672
|
+
action('quick', 'Quick task', cmd('quick')),
|
|
673
|
+
action('help', 'Show help', cmd('help')),
|
|
649
674
|
];
|
|
650
675
|
}
|
|
651
676
|
}
|
|
652
677
|
// ─── Summary line ─────────────────────────────────────────────────────────────
|
|
678
|
+
function phaseSummary(s) {
|
|
679
|
+
const phase = s.current_phase;
|
|
680
|
+
// Preserve the milestone ordinal introduced by #5228 when it is known.
|
|
681
|
+
const at = phase === null ? -1 : s.milestone_phases.findIndex((id) => comparePhaseNum(id, phase) === 0);
|
|
682
|
+
if (at >= 0)
|
|
683
|
+
return `Phase ${at + 1} of ${s.milestone_phases.length}`;
|
|
684
|
+
const total = s.total_phases;
|
|
685
|
+
const legacy = `Phase ${phase ?? '?'} of ${total ?? '?'}`;
|
|
686
|
+
const roadmapTotal = s.roadmap_total_phases;
|
|
687
|
+
// Keep the recorded pair when roadmap counts are unavailable, or when the
|
|
688
|
+
// pair can describe a single milestone without changing its existing text.
|
|
689
|
+
if (phase === null || total === null || roadmapTotal === null || roadmapTotal <= 0
|
|
690
|
+
|| (total === roadmapTotal && comparePhaseNum(phase, total) <= 0)) {
|
|
691
|
+
return legacy;
|
|
692
|
+
}
|
|
693
|
+
// When the recorded pair cannot safely describe this roadmap, label its
|
|
694
|
+
// counts separately rather than printing a mixed-scope ratio.
|
|
695
|
+
const roadmapDone = s.roadmap_completed_phases;
|
|
696
|
+
return roadmapDone === null
|
|
697
|
+
? `Phase ${phase} (${roadmapTotal} roadmap phases)`
|
|
698
|
+
: `Phase ${phase} (${roadmapDone} of ${roadmapTotal} roadmap phases complete)`;
|
|
699
|
+
}
|
|
653
700
|
function buildSummary(situation, s) {
|
|
654
701
|
switch (situation) {
|
|
655
702
|
case 'no-project':
|
|
@@ -663,7 +710,7 @@ function buildSummary(situation, s) {
|
|
|
663
710
|
case 'needs-first-phase':
|
|
664
711
|
return 'Project initialized — plan your first phase';
|
|
665
712
|
case 'planning':
|
|
666
|
-
return
|
|
713
|
+
return `${phaseSummary(s)} — needs a plan`;
|
|
667
714
|
case 'executing':
|
|
668
715
|
return progressLine('executing', s);
|
|
669
716
|
case 'verify-pending':
|
|
@@ -680,7 +727,7 @@ function buildSummary(situation, s) {
|
|
|
680
727
|
function progressLine(tail, s) {
|
|
681
728
|
const parts = [];
|
|
682
729
|
if (s.current_phase !== null && s.total_phases !== null) {
|
|
683
|
-
parts.push(
|
|
730
|
+
parts.push(phaseSummary(s));
|
|
684
731
|
}
|
|
685
732
|
else if (s.current_phase !== null) {
|
|
686
733
|
parts.push(`Phase ${s.current_phase}`);
|
|
@@ -699,7 +746,7 @@ function progressLine(tail, s) {
|
|
|
699
746
|
function classifyProject(cwd, now = Date.now) {
|
|
700
747
|
const signals = detectSignals(cwd, now);
|
|
701
748
|
const situation = classify(signals);
|
|
702
|
-
const actions = actionsFor(situation, signals);
|
|
749
|
+
const actions = actionsFor(situation, signals, (0, runtime_slash_cjs_1.resolveRuntime)(cwd));
|
|
703
750
|
const recommended = actions.find((a) => a.recommended)?.id ?? 'progress';
|
|
704
751
|
const summary = buildSummary(situation, signals);
|
|
705
752
|
return { situation, recommended, summary, signals, actions };
|
|
@@ -718,6 +765,6 @@ function runSmartEntry(cwd, args, raw) {
|
|
|
718
765
|
return;
|
|
719
766
|
}
|
|
720
767
|
// Human mode: a compact one-liner plus the recommended action.
|
|
721
|
-
const human = `${result.summary}\nRecommended: ${result.recommended} → ${result.actions.find((a) => a.id === result.recommended)?.command ?? '
|
|
768
|
+
const human = `${result.summary}\nRecommended: ${result.recommended} → ${result.actions.find((a) => a.id === result.recommended)?.command ?? (0, runtime_slash_cjs_1.runtimeCommand)(cwd, 'progress')}`;
|
|
722
769
|
output(null, true, human);
|
|
723
770
|
}
|
|
@@ -14,6 +14,7 @@ exports.isUnfilledFieldValue = isUnfilledFieldValue;
|
|
|
14
14
|
exports.leadingCalendarDate = leadingCalendarDate;
|
|
15
15
|
exports.isRealCalendarDate = isRealCalendarDate;
|
|
16
16
|
exports.stateFieldContinuation = stateFieldContinuation;
|
|
17
|
+
exports.fieldContinuationEnd = fieldContinuationEnd;
|
|
17
18
|
exports.stateExtractField = stateExtractField;
|
|
18
19
|
exports.stateFieldValue = stateFieldValue;
|
|
19
20
|
exports.stateCurrentPositionSlice = stateCurrentPositionSlice;
|
|
@@ -378,7 +379,16 @@ function stateFieldContinuation(content, fieldName) {
|
|
|
378
379
|
if (!rest.startsWith('\n'))
|
|
379
380
|
return null; // end of file: nothing follows
|
|
380
381
|
const lines = rest.slice(1).split('\n').map((line) => line.replace(/\r$/, ''));
|
|
381
|
-
const continuation =
|
|
382
|
+
const continuation = lines.slice(0, continuationLineCount(lines)).map((line) => line.trim());
|
|
383
|
+
return continuation.length ? continuation.join(' ') : null;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* How many of the lines directly below a single-line field belong to its value: the scan
|
|
387
|
+
* `stateFieldContinuation` reports on, shared so a writer that replaces the value removes exactly
|
|
388
|
+
* the lines a validator would call its continuation (#4945). `lines` are CR-free.
|
|
389
|
+
*/
|
|
390
|
+
function continuationLineCount(lines) {
|
|
391
|
+
let count = 0;
|
|
382
392
|
for (let i = 0; i < lines.length; i++) {
|
|
383
393
|
const line = lines[i];
|
|
384
394
|
if (!line.trim())
|
|
@@ -391,9 +401,33 @@ function stateFieldContinuation(content, fieldName) {
|
|
|
391
401
|
// title, so stop before consuming it rather than after.
|
|
392
402
|
if (i + 1 < lines.length && SETEXT_UNDERLINE_RE.test(lines[i + 1]))
|
|
393
403
|
break;
|
|
394
|
-
|
|
404
|
+
count++;
|
|
395
405
|
}
|
|
396
|
-
return
|
|
406
|
+
return count;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The index just past a single-line field's value AND the continuation lines that wrap it
|
|
410
|
+
* (#4945). `valueEnd` is where the field's own line ends (before its line terminator). Replacing
|
|
411
|
+
* `content.slice(valueEnd, result)` with nothing removes every continuation line and the
|
|
412
|
+
* terminator in front of each, so the terminator after the last one stays. Returns `valueEnd`
|
|
413
|
+
* when nothing continues the value.
|
|
414
|
+
*/
|
|
415
|
+
function fieldContinuationEnd(content, valueEnd) {
|
|
416
|
+
const tail = content.slice(valueEnd);
|
|
417
|
+
const first = /^\r?\n/.exec(tail);
|
|
418
|
+
if (!first)
|
|
419
|
+
return valueEnd;
|
|
420
|
+
const rawLines = tail.slice(first[0].length).split('\n');
|
|
421
|
+
const count = continuationLineCount(rawLines.map((line) => line.replace(/\r$/, '')));
|
|
422
|
+
if (count === 0)
|
|
423
|
+
return valueEnd;
|
|
424
|
+
let removed = first[0].length + (count - 1);
|
|
425
|
+
for (let i = 0; i < count; i++)
|
|
426
|
+
removed += rawLines[i].length;
|
|
427
|
+
// The terminator after the last continuation line stays; on a CRLF document that includes its CR.
|
|
428
|
+
if (rawLines[count - 1].endsWith('\r'))
|
|
429
|
+
removed -= 1;
|
|
430
|
+
return valueEnd + removed;
|
|
397
431
|
}
|
|
398
432
|
function stateExtractField(content, fieldName) {
|
|
399
433
|
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
@@ -417,7 +417,41 @@ function createStateWriteIntent(transaction, init = {}) {
|
|
|
417
417
|
err.code = 'STATE_WRITE_INTENT_TRANSACTION_REQUIRED';
|
|
418
418
|
throw err;
|
|
419
419
|
}
|
|
420
|
-
|
|
420
|
+
// C2 (ADR-4629 §8.2): an unknown `requirement` is a construction error. §8.2
|
|
421
|
+
// judges only `'required'` assertions, so a mistyped value (`'Required'`)
|
|
422
|
+
// would otherwise be silently skipped: a fail-open verifier.
|
|
423
|
+
for (const a of init.assertions ?? []) {
|
|
424
|
+
if (a.requirement !== 'required' && a.requirement !== 'best-effort') {
|
|
425
|
+
const err = new Error(`createStateWriteIntent: assertion ${JSON.stringify(a.field)} has unknown requirement ` +
|
|
426
|
+
`${JSON.stringify(a.requirement)}; expected 'required' or 'best-effort' (ADR-4629 §8.1).`);
|
|
427
|
+
err.code = 'STATE_WRITE_INTENT_REQUIREMENT_INVALID';
|
|
428
|
+
throw err;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (init.scope !== undefined && init.scope !== 'narrow' && init.scope !== 'broad') {
|
|
432
|
+
const err = new Error(`createStateWriteIntent: unknown scope ${JSON.stringify(init.scope)}; expected 'narrow' or 'broad' ` +
|
|
433
|
+
'(ADR-4629 §8.1).');
|
|
434
|
+
err.code = 'STATE_WRITE_INTENT_SCOPE_INVALID';
|
|
435
|
+
throw err;
|
|
436
|
+
}
|
|
437
|
+
// C2 (ADR-4629 §8.2): an unknown `target` is a construction error, never a
|
|
438
|
+
// silent fall-back to field semantics (a JS caller passing `'sections'`).
|
|
439
|
+
for (const a of init.assertions ?? []) {
|
|
440
|
+
if (a.target !== undefined && a.target !== 'field' && a.target !== 'section') {
|
|
441
|
+
const err = new Error(`createStateWriteIntent: assertion ${JSON.stringify(a.field)} has unknown target ` +
|
|
442
|
+
`${JSON.stringify(a.target)}; expected 'field' or 'section' (ADR-4629 §8.2).`);
|
|
443
|
+
err.code = 'STATE_WRITE_INTENT_TARGET_INVALID';
|
|
444
|
+
throw err;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// C2 (ADR-4629 §8.2): `target` / `value` are copied only when the caller set
|
|
448
|
+
// them, so a C1-shaped assertion stays exactly `{ field, requirement }`.
|
|
449
|
+
const assertions = Object.freeze((init.assertions ?? []).map((a) => Object.freeze({
|
|
450
|
+
field: a.field,
|
|
451
|
+
requirement: a.requirement,
|
|
452
|
+
...(a.target !== undefined ? { target: a.target } : {}),
|
|
453
|
+
...(a.value !== undefined ? { value: a.value } : {}),
|
|
454
|
+
})));
|
|
421
455
|
return Object.freeze({
|
|
422
456
|
...transaction,
|
|
423
457
|
assertions,
|
|
@@ -888,6 +922,8 @@ function transitionCore(content, intent, deps) {
|
|
|
888
922
|
*/
|
|
889
923
|
function beginPhaseCore(content, intent, deps) {
|
|
890
924
|
const updated = [];
|
|
925
|
+
// #4945: a re-entry whose earlier plans already have summaries starts at the first plan still to run.
|
|
926
|
+
const startPlan = intent.startPlan && intent.startPlan > 0 ? Math.floor(intent.startPlan) : 1;
|
|
891
927
|
// #1255: body-field replacements operate on body only (frontmatter stripped),
|
|
892
928
|
// not on the full content. The YAML `status:` key matches `^Status:\s*`
|
|
893
929
|
// before the body pipe-table row if full content is passed.
|
|
@@ -943,7 +979,7 @@ function beginPhaseCore(content, intent, deps) {
|
|
|
943
979
|
if (intent.phaseName) {
|
|
944
980
|
tryField('Current Phase Name', intent.phaseName);
|
|
945
981
|
}
|
|
946
|
-
tryField('Current Plan',
|
|
982
|
+
tryField('Current Plan', String(startPlan));
|
|
947
983
|
if (intent.planCount) {
|
|
948
984
|
tryField('Total Plans in Phase', String(intent.planCount));
|
|
949
985
|
}
|
|
@@ -962,14 +998,21 @@ function beginPhaseCore(content, intent, deps) {
|
|
|
962
998
|
// end-of-line (inert here since `.` never crosses line terminators
|
|
963
999
|
// without `/s`, which is not set).
|
|
964
1000
|
const focusPattern = /^([ \t]*\*\*Current focus:\*\*[ \t]*)(.*)$/im;
|
|
965
|
-
|
|
966
|
-
|
|
1001
|
+
const focusMatch = focusPattern.exec(body);
|
|
1002
|
+
if (focusMatch) {
|
|
1003
|
+
// #4945: a value an author wrapped onto further lines is replaced whole, so its tail is not
|
|
1004
|
+
// left behind under the new value. The lines that count are the ones `state validate` already
|
|
1005
|
+
// reports as a field's continuation (stops at a blank line, a heading, a list item, a table
|
|
1006
|
+
// row, a fence or a sibling field). An empty first line has no value to continue.
|
|
1007
|
+
const valueEnd = focusMatch.index + focusMatch[0].length;
|
|
1008
|
+
const end = focusMatch[2].trim() === '' ? valueEnd : (0, state_document_cjs_2.fieldContinuationEnd)(body, valueEnd);
|
|
1009
|
+
body = body.slice(0, focusMatch.index) + focusMatch[1] + focusLabel + body.slice(end);
|
|
967
1010
|
updated.push('Current focus');
|
|
968
1011
|
}
|
|
969
1012
|
// ## Current Position section mutation (#1104, #1365).
|
|
970
1013
|
// `locateCurrentPosition` (fence-aware, tokenizeHeadings-based) locates
|
|
971
1014
|
// the section; mirrors state.cts:2261-2324 byte-for-behaviour.
|
|
972
|
-
body = mutateCurrentPositionFirstTime(body, intent, today, updated);
|
|
1015
|
+
body = mutateCurrentPositionFirstTime(body, { ...intent, startPlan }, today, updated);
|
|
973
1016
|
}
|
|
974
1017
|
else {
|
|
975
1018
|
// Resume path: only update Last activity timestamp in Current Position
|
|
@@ -1073,7 +1116,7 @@ function mutateCurrentPositionFirstTime(body, intent, today, updated) {
|
|
|
1073
1116
|
sectionBody = replaced;
|
|
1074
1117
|
}
|
|
1075
1118
|
// Plan line.
|
|
1076
|
-
const planValue =
|
|
1119
|
+
const planValue = `${intent.startPlan} of ${intent.planCount || '?'}`;
|
|
1077
1120
|
if (/^Plan:/m.test(sectionBody)) {
|
|
1078
1121
|
sectionBody = sectionBody.replace(/^Plan:.*$/m, `Plan: ${planValue}`);
|
|
1079
1122
|
}
|
|
@@ -1575,9 +1618,30 @@ function advancePlanCore(content, deps) {
|
|
|
1575
1618
|
data: { advanced: true, previous_plan: currentPlan, current_plan: newPlan, total_plans: totalPlans },
|
|
1576
1619
|
};
|
|
1577
1620
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1621
|
+
/**
|
|
1622
|
+
* #4824: Status / Current Plan / Stopped-at wording for the phase a
|
|
1623
|
+
* completion hands off to. The historical `Ready to plan` is only true for a
|
|
1624
|
+
* phase with no plans; a phase that already has PLANs routes to execution,
|
|
1625
|
+
* and one whose plans all have SUMMARYs awaits verification — never back to
|
|
1626
|
+
* `/gsd-plan-phase`. Which phase is selected stays #4078's rule.
|
|
1627
|
+
*/
|
|
1628
|
+
function nextPhaseWording(disk) {
|
|
1629
|
+
switch (disk?.status) {
|
|
1630
|
+
case 'PLANNED':
|
|
1631
|
+
return { status: 'Ready to execute', currentPlan: 'Not started', stoppedAt: 'ready to execute' };
|
|
1632
|
+
case 'IN_PROGRESS':
|
|
1633
|
+
return { status: 'Ready to execute', currentPlan: String(disk.summaryCount + 1), stoppedAt: 'ready to execute' };
|
|
1634
|
+
case 'EXECUTED':
|
|
1635
|
+
case 'NEEDS_REVIEW':
|
|
1636
|
+
return {
|
|
1637
|
+
status: 'Phase complete — ready for verification',
|
|
1638
|
+
currentPlan: String(disk.planCount),
|
|
1639
|
+
stoppedAt: 'ready to verify',
|
|
1640
|
+
};
|
|
1641
|
+
default:
|
|
1642
|
+
return { status: 'Ready to plan', currentPlan: 'Not started', stoppedAt: 'ready to plan' };
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1581
1645
|
/**
|
|
1582
1646
|
* Apply a `completePhase` transition to STATE.md content.
|
|
1583
1647
|
*
|
|
@@ -1585,7 +1649,9 @@ function advancePlanCore(content, deps) {
|
|
|
1585
1649
|
* (phase.cts) onto the substrate. Owns the field-classification-governed body
|
|
1586
1650
|
* mutations: Current Phase (preserving the `of total` shape and phase name),
|
|
1587
1651
|
* Current Phase Name, Status (`All phases complete` on the last phase, else
|
|
1588
|
-
* `Ready to plan` per ADR-2207
|
|
1652
|
+
* `Ready to plan` per ADR-2207 — or, #4824, the next phase's disk-derived
|
|
1653
|
+
* wording when it is already planned or executed), Current Plan (`Not started`
|
|
1654
|
+
* unless the next phase is in flight), Last Activity + Description,
|
|
1589
1655
|
* and the Completed/Total Phases + Progress percent block (re-derived from the
|
|
1590
1656
|
* roadmap via the injected `roadmapProvider`).
|
|
1591
1657
|
*
|
|
@@ -1602,6 +1668,7 @@ function advancePlanCore(content, deps) {
|
|
|
1602
1668
|
function completePhaseCore(content, intent, deps) {
|
|
1603
1669
|
const updated = [];
|
|
1604
1670
|
const today = deps.clock.localToday();
|
|
1671
|
+
const nextWording = nextPhaseWording(intent.isLastPhase ? null : intent.nextPhaseDisk ?? null);
|
|
1605
1672
|
// Consult the field-classification table for the frontmatter keys this
|
|
1606
1673
|
// transition touches (same guard beginPhaseCore applies). A missing row is a
|
|
1607
1674
|
// substrate defect — fail loudly rather than silently re-encoding policy.
|
|
@@ -1665,7 +1732,7 @@ function completePhaseCore(content, intent, deps) {
|
|
|
1665
1732
|
// Status — `All phases complete` on the final phase (ADR-2207), otherwise
|
|
1666
1733
|
// `Ready to plan`. Milestone termination (`<version> milestone complete`) is
|
|
1667
1734
|
// owned solely by the milestone-close verb (milestoneCompleteCore).
|
|
1668
|
-
const statusValue = intent.isLastPhase ? 'All phases complete' :
|
|
1735
|
+
const statusValue = intent.isLastPhase ? 'All phases complete' : nextWording.status;
|
|
1669
1736
|
const statusAfter = (0, state_document_cjs_1.stateReplaceFieldWithFallback)(body, 'Status', null, statusValue);
|
|
1670
1737
|
if (statusAfter !== body) {
|
|
1671
1738
|
body = statusAfter;
|
|
@@ -1682,10 +1749,10 @@ function completePhaseCore(content, intent, deps) {
|
|
|
1682
1749
|
let planAfter;
|
|
1683
1750
|
const positionScope = (0, state_document_cjs_1.stateCurrentPositionSlice)(body);
|
|
1684
1751
|
if (positionScope !== null) {
|
|
1685
|
-
planAfter = (0, markdown_sectionizer_cjs_1.withSection)(body, (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position', (sectionBody) => (0, state_document_cjs_1.stateReplaceFieldWithFallback)(sectionBody, 'Current Plan', 'Plan',
|
|
1752
|
+
planAfter = (0, markdown_sectionizer_cjs_1.withSection)(body, (h) => (h.level === 2 || h.level === 3) && h.text.trim().toLowerCase() === 'current position', (sectionBody) => (0, state_document_cjs_1.stateReplaceFieldWithFallback)(sectionBody, 'Current Plan', 'Plan', nextWording.currentPlan), { levelBounded: true });
|
|
1686
1753
|
}
|
|
1687
1754
|
else {
|
|
1688
|
-
planAfter = (0, state_document_cjs_1.stateReplaceFieldWithFallback)(body, 'Current Plan', 'Plan',
|
|
1755
|
+
planAfter = (0, state_document_cjs_1.stateReplaceFieldWithFallback)(body, 'Current Plan', 'Plan', nextWording.currentPlan);
|
|
1689
1756
|
}
|
|
1690
1757
|
if (planAfter !== body) {
|
|
1691
1758
|
body = planAfter;
|
|
@@ -1729,7 +1796,7 @@ function completePhaseCore(content, intent, deps) {
|
|
|
1729
1796
|
// milestone termination wording stays owned by milestoneCompleteCore.
|
|
1730
1797
|
const stoppedAtLine = intent.isLastPhase
|
|
1731
1798
|
? `Phase ${intent.phaseNum} complete — all phases complete`
|
|
1732
|
-
: `Phase ${intent.phaseNum} complete${intent.nextPhaseNum ? `,
|
|
1799
|
+
: `Phase ${intent.phaseNum} complete${intent.nextPhaseNum ? `, ${nextWording.stoppedAt} Phase ${intent.nextPhaseNum}` : ''}`;
|
|
1733
1800
|
const stoppedAfter = (0, state_document_cjs_1.stateReplaceFieldInSession)(body, 'Stopped At', 'Stopped at', stoppedAtLine);
|
|
1734
1801
|
if (stoppedAfter !== body) {
|
|
1735
1802
|
body = stoppedAfter;
|
|
@@ -1740,15 +1807,27 @@ function completePhaseCore(content, intent, deps) {
|
|
|
1740
1807
|
// Only runs when a Completed Phases field exists (the existing guard).
|
|
1741
1808
|
const completedRaw = (0, state_document_cjs_1.stateExtractField)(body, 'Completed Phases');
|
|
1742
1809
|
if (completedRaw !== null) {
|
|
1743
|
-
|
|
1810
|
+
const recordedCompleted = parseInt(completedRaw, 10);
|
|
1811
|
+
let newCompleted = recordedCompleted;
|
|
1744
1812
|
let derivedTotalPhases = null;
|
|
1745
1813
|
const roadmapContent = deps.roadmapProvider ? deps.roadmapProvider() : null;
|
|
1746
1814
|
if (roadmapContent) {
|
|
1747
1815
|
const derived = (0, phase_lifecycle_cjs_1.deriveProgressFromRoadmap)(roadmapContent);
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1816
|
+
// #4901: completed counters only ratchet up, like the frontmatter twin
|
|
1817
|
+
// (applyAuthoritativeProgressSubkeys, completedOnlyRaise). A derivation
|
|
1818
|
+
// that lands BELOW the recorded counter read a narrower scope than the
|
|
1819
|
+
// counter spans (e.g. the first `## Progress` section of a multi-milestone
|
|
1820
|
+
// roadmap), so its total is just as suspect: keep the recorded counter and
|
|
1821
|
+
// fall back to the recorded Total Phases for the percent below.
|
|
1822
|
+
const derivedLowered = derived.completedPhases !== null
|
|
1823
|
+
&& Number.isFinite(recordedCompleted)
|
|
1824
|
+
&& derived.completedPhases < recordedCompleted;
|
|
1825
|
+
if (!derivedLowered) {
|
|
1826
|
+
if (derived.completedPhases !== null)
|
|
1827
|
+
newCompleted = derived.completedPhases;
|
|
1828
|
+
if (derived.totalPhases !== null)
|
|
1829
|
+
derivedTotalPhases = derived.totalPhases;
|
|
1830
|
+
}
|
|
1752
1831
|
}
|
|
1753
1832
|
// #3057 B9: only mark 'Completed Phases' updated when the text actually
|
|
1754
1833
|
// changed. `stateReplaceField` returns the full (re-)substituted content
|