@opengsd/gsd-core 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -16,10 +16,10 @@ const pattern_cjs_1 = require("./pattern.cjs");
|
|
|
16
16
|
const text_lines_cjs_1 = require("./text-lines.cjs");
|
|
17
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
18
|
const ioMod = require("./io.cjs");
|
|
19
|
-
const { output, error, formatDiagnosticToken } = ioMod;
|
|
19
|
+
const { output, error, formatDiagnosticToken, declineNoOp } = ioMod;
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
21
|
const phaseIdMod = require("./phase-id.cjs");
|
|
22
|
-
const { normalizePhaseName, phaseMarkdownRegexSource, matchPhaseDirs, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE, roadmapPhaseLookupSources, isSentinelPhaseId, scopeToPhase } = phaseIdMod;
|
|
22
|
+
const { normalizePhaseName, phaseMarkdownRegexSource, matchPhaseDirs, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE, roadmapPhaseLookupSources, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, isSentinelPhaseId, scopeToPhase, bracketQualifiedKey, foldBracketId } = phaseIdMod;
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
24
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
25
25
|
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
|
|
@@ -35,7 +35,7 @@ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
|
35
35
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
37
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
38
|
-
const { planningPaths, withPlanningLock, findContextMdIn } = planningWorkspace;
|
|
38
|
+
const { planningPaths, withPlanningLock, findContextMdIn, resolvePhaseIdConvention } = planningWorkspace;
|
|
39
39
|
// #3641: milestone-scope's convention resolution reads the project config
|
|
40
40
|
// (no cycle — config-loader does not import this module).
|
|
41
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -89,38 +89,39 @@ function coerceTruthToString(t) {
|
|
|
89
89
|
return '';
|
|
90
90
|
}
|
|
91
91
|
// ─── countPhasePlansAndSummaries ──────────────────────────────────────────────
|
|
92
|
-
function countPhasePlansAndSummaries(phaseDir) {
|
|
92
|
+
function countPhasePlansAndSummaries(phaseDir, convention) {
|
|
93
93
|
const { planCount, summaryCount } = scanPhasePlans(phaseDir);
|
|
94
94
|
// hasContext and hasResearch are not plan-scan concerns — read the directory
|
|
95
95
|
// once and share the listing for all non-plan metadata that cmdRoadmapAnalyze needs.
|
|
96
|
-
|
|
97
|
-
// #
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
}
|
|
96
|
+
//
|
|
97
|
+
// #4014 (epic #3473 B4): the listing + unreadable-vs-empty discrimination
|
|
98
|
+
// is now owned by findContextMdIn's directory-string form, retiring this
|
|
99
|
+
// function's own readdirSync try/catch (mirrors core-utils.cts's
|
|
100
|
+
// getPhaseFileStats / phase-locator.cts's listMilestonePhaseDirs
|
|
101
|
+
// SCOPE.UNREADABLE discriminator).
|
|
102
|
+
const { files: phaseFiles, scope } = findContextMdIn(phaseDir);
|
|
103
|
+
// #3885 (ADR-3473 §8.5): `contextReadError` stays additive for the shipped
|
|
104
|
+
// `AnalyzePhase.context_read_error` JSON field — derived from `scope`
|
|
105
|
+
// rather than from its own caught error, since findContextMdIn's
|
|
106
|
+
// directory-string form reports SCOPE, not the raw errno message.
|
|
107
|
+
const contextReadError = scope === SCOPE.UNREADABLE
|
|
108
|
+
? `Could not read phase directory ${formatDiagnosticToken(phaseDir)}`
|
|
109
|
+
: null;
|
|
112
110
|
// #3511: scope the raw listing to this phase dir before the
|
|
113
111
|
// phase-numbered-artifact predicates (hasContext/hasResearch) — planCount/
|
|
114
112
|
// summaryCount above stay on scanPhasePlans's own unscoped listing since a
|
|
115
113
|
// PLAN/SUMMARY leading number is a plan sequence number, not a phase
|
|
116
114
|
// number. Mirrors core-utils.cts's getPhaseFileStats.
|
|
117
|
-
|
|
115
|
+
// #612: `convention` threaded from the one caller (which already threads it
|
|
116
|
+
// into matchPhaseDirs) so a bracket dir scopes by its real token.
|
|
117
|
+
const scopedFiles = scopeToPhase(phaseFiles, node_path_1.default.basename(phaseDir), convention);
|
|
118
118
|
return {
|
|
119
119
|
planCount,
|
|
120
120
|
summaryCount,
|
|
121
121
|
hasContext: findContextMdIn(scopedFiles) !== null,
|
|
122
122
|
hasResearch: scopedFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
|
|
123
123
|
contextReadError,
|
|
124
|
+
scope,
|
|
124
125
|
};
|
|
125
126
|
}
|
|
126
127
|
// `phaseMarkdownRegexSource` lives in phase-id.cjs (#3537) and is imported above.
|
|
@@ -131,22 +132,24 @@ function countPhasePlansAndSummaries(phaseDir) {
|
|
|
131
132
|
* exact production pattern instead of hand-duplicating it.
|
|
132
133
|
* #1729: OPTIONAL_PHASE_TAG_SOURCE after the number tolerates a pre-colon ( ) tag.
|
|
133
134
|
*/
|
|
134
|
-
function buildPhaseHeadingRegex(escapedPhase) {
|
|
135
|
-
return new RegExp(
|
|
135
|
+
function buildPhaseHeadingRegex(escapedPhase, convention) {
|
|
136
|
+
return new RegExp(`^${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention)}${escapedPhase}${OPTIONAL_PHASE_TAG_SOURCE}:\\s*(.+)$`, 'i');
|
|
136
137
|
}
|
|
137
138
|
/**
|
|
138
139
|
* Search for a phase header (and its section) within the given content string.
|
|
139
140
|
* Returns a result object if found (either a full match or a malformed_roadmap
|
|
140
141
|
* checklist-only match), or null if the phase is not present at all.
|
|
141
142
|
*/
|
|
142
|
-
function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
|
143
|
-
const headingPattern = buildPhaseHeadingRegex(escapedPhase);
|
|
143
|
+
function searchPhaseInContent(content, escapedPhase, phaseNum, convention) {
|
|
144
|
+
const headingPattern = buildPhaseHeadingRegex(escapedPhase, convention);
|
|
144
145
|
const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
|
|
145
146
|
const headingIndex = headings.findIndex((heading) => headingPattern.test(heading.text));
|
|
146
147
|
const headerMatch = headingIndex === -1 ? null : headings[headingIndex].text.match(headingPattern);
|
|
147
148
|
if (!headerMatch) {
|
|
148
149
|
// Fallback: check if phase exists in summary list but missing detail section
|
|
149
|
-
|
|
150
|
+
// A BARE `Phase\s+` at base — takes the label-only baseline, so a bracket
|
|
151
|
+
// repo gains the bracket-ID form and nothing else.
|
|
152
|
+
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}${escapedPhase}${OPTIONAL_PHASE_TAG_SOURCE}:\\s*([^*]+)\\*\\*`, 'i');
|
|
150
153
|
const checklistMatch = content.match(checklistPattern);
|
|
151
154
|
if (checklistMatch) {
|
|
152
155
|
return {
|
|
@@ -232,11 +235,12 @@ function getRoadmapPhaseWithFallback(cwd, phaseNum) {
|
|
|
232
235
|
// #2121/#2114: iterate the shared lookup-source list (exact → numeric →
|
|
233
236
|
// prefix-tolerant) so this resolver matches getRoadmapPhaseInternal and a
|
|
234
237
|
// bare-number query resolves a drifted project-code-prefixed heading.
|
|
238
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
235
239
|
for (const source of roadmapPhaseLookupSources(phaseNum)) {
|
|
236
|
-
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum);
|
|
240
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum, convention);
|
|
237
241
|
if (milestoneResult && !milestoneResult.error)
|
|
238
242
|
return milestoneResult.section ?? null;
|
|
239
|
-
const fullResult = searchPhaseInContent(fullContent, source, phaseNum);
|
|
243
|
+
const fullResult = searchPhaseInContent(fullContent, source, phaseNum, convention);
|
|
240
244
|
if (fullResult && !fullResult.error)
|
|
241
245
|
return fullResult.section ?? null;
|
|
242
246
|
}
|
|
@@ -258,6 +262,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
258
262
|
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
259
263
|
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
260
264
|
const fullContent = stripShippedMilestones(rawContent);
|
|
265
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
261
266
|
// #2121/#2114: iterate the shared lookup-source list (exact → numeric →
|
|
262
267
|
// prefix-tolerant) so all three roadmap resolvers share one contract and a
|
|
263
268
|
// bare-number query resolves a drifted `### Phase AB-29:` heading. This
|
|
@@ -268,12 +273,12 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
268
273
|
// heading — so a milestone checklist never blocks a full-roadmap header.
|
|
269
274
|
let malformed = null;
|
|
270
275
|
for (const source of roadmapPhaseLookupSources(phaseNum)) {
|
|
271
|
-
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum);
|
|
276
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum, convention);
|
|
272
277
|
if (milestoneResult && !milestoneResult.error) {
|
|
273
278
|
output(milestoneResult, raw, milestoneResult.section);
|
|
274
279
|
return;
|
|
275
280
|
}
|
|
276
|
-
const fullResult = searchPhaseInContent(fullContent, source, phaseNum);
|
|
281
|
+
const fullResult = searchPhaseInContent(fullContent, source, phaseNum, convention);
|
|
277
282
|
if (fullResult && !fullResult.error) {
|
|
278
283
|
output(fullResult, raw, fullResult.section);
|
|
279
284
|
return;
|
|
@@ -302,6 +307,30 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
302
307
|
error('Failed to read ROADMAP.md: ' + e.message);
|
|
303
308
|
}
|
|
304
309
|
}
|
|
310
|
+
// #612 composes the convention-qualified sentinel reading with upstream's
|
|
311
|
+
// canonical legacy sentinel owner. A reserved bracket milestone OR a reserved
|
|
312
|
+
// phase token excludes the occurrence.
|
|
313
|
+
const isSentinelPhase = (num, bracketId) => {
|
|
314
|
+
if (bracketId && isSentinelPhaseId(`${bracketId}-${num}`, 'bracket'))
|
|
315
|
+
return true;
|
|
316
|
+
return isSentinelPhaseId(num);
|
|
317
|
+
};
|
|
318
|
+
// #2761 M1: missing-detail identity is milestone-qualified under bracket.
|
|
319
|
+
// Prefer the canonical qualified-key owner, which case-folds accepted ids, so
|
|
320
|
+
// `[gsd.02] 01` and `[GSD.02] 01` are one occurrence. It is intentionally not
|
|
321
|
+
// padding-tolerant: the milestone grammar has one canonical spelling (pad2
|
|
322
|
+
// below 100, no leading zero above), so `[GSD.2]` is malformed rather than an
|
|
323
|
+
// alternate spelling of `[GSD.02]`. Hyphenated tokens and other shapes the
|
|
324
|
+
// qualified-key owner refuses retain a folded composite, keeping distinct
|
|
325
|
+
// bracket/token pairs from collapsing onto one missing-detail verdict.
|
|
326
|
+
const occurrenceKey = (num, bracketId) => {
|
|
327
|
+
if (!bracketId)
|
|
328
|
+
return num;
|
|
329
|
+
const qualified = num.includes('-')
|
|
330
|
+
? null
|
|
331
|
+
: bracketQualifiedKey(`${bracketId}-${num}`, 'bracket');
|
|
332
|
+
return qualified ?? `${foldBracketId(bracketId)}|${num}`;
|
|
333
|
+
};
|
|
305
334
|
/**
|
|
306
335
|
* #3165: scan `content` for phase-detail headings (`##/###/#### Phase N: Name`)
|
|
307
336
|
* and enrich each with its on-disk plan/summary/completion status and ROADMAP
|
|
@@ -311,29 +340,56 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
311
340
|
* `cmdRoadmapAnalyze`'s former inline loop so the fallback re-runs the EXACT
|
|
312
341
|
* same enrichment, not a second derivation.
|
|
313
342
|
*/
|
|
314
|
-
function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
343
|
+
function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
|
|
315
344
|
// Extract all phase headings: ## Phase N: Name or ### Phase N: Name
|
|
316
345
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
346
|
+
// #612: CAPTURING intro under the bracket convention — group 1 is the
|
|
347
|
+
// `[CODE.MM]` bracket id (undefined otherwise), group 2 the token, group 3 the
|
|
348
|
+
// name. The bracket id is what the sentinel filter needs: READING-B puts the
|
|
349
|
+
// sentinel milestone in the bracket, not in the token.
|
|
317
350
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
318
351
|
// #3036: widen the id capture to accept non-numeric-leading ids (e.g. B7, P0.3-2)
|
|
319
352
|
// that get-phase/execute-phase already resolve. An optional leading letter prefix
|
|
320
353
|
// ([A-Za-z]?) covers letter-prefixed ids without breaking numeric-leading ones.
|
|
354
|
+
// #4478: line-anchored (`^ {0,3}`, `/m`) — unanchored, `#{2,4}` matched a
|
|
355
|
+
// `### Phase N:`-shaped mention ANYWHERE `exec()`'s scan reached: mid-sentence
|
|
356
|
+
// prose, inside a blockquote, inside an inline code span (backtick-quoted on
|
|
357
|
+
// the same line, not a fenced block `tokenizeHeadings` would exclude). Any
|
|
358
|
+
// such line minted a phantom phase entry, inflating phase_count and able to
|
|
359
|
+
// collide on a phase NUMBER with a real heading nearby. `{0,3}` leading
|
|
360
|
+
// spaces mirrors `tokenizeHeadings`'s own CommonMark ATX-heading tolerance
|
|
361
|
+
// (src/markdown-sectionizer.cts:453) so a legitimately-indented heading that
|
|
362
|
+
// matched before this fix still matches after it.
|
|
321
363
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
322
|
-
const phasePattern =
|
|
364
|
+
const phasePattern = new RegExp(`^ {0,3}#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention, true)}([A-Za-z]?\\d+[A-Z]?(?:[.-]\\d+)*)(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gim');
|
|
365
|
+
// The capturing intro inserts the bracket id at group 1 only under the
|
|
366
|
+
// bracket convention; the token and name shift by the same offset.
|
|
367
|
+
const G = convention === 'bracket' ? 1 : 0;
|
|
323
368
|
const phases = [];
|
|
324
369
|
let match;
|
|
370
|
+
// The caller needs the exact occurrence identities from the same scan that
|
|
371
|
+
// built `phases`; returning them together also keeps fallback rescans atomic.
|
|
372
|
+
const detailKeys = new Set();
|
|
325
373
|
while ((match = phasePattern.exec(content)) !== null) {
|
|
326
|
-
const
|
|
327
|
-
|
|
374
|
+
const bracketId = G ? match[1] : undefined;
|
|
375
|
+
const phaseNum = match[1 + G];
|
|
376
|
+
if (isSentinelPhase(phaseNum, bracketId))
|
|
328
377
|
continue;
|
|
329
|
-
|
|
378
|
+
detailKeys.add(occurrenceKey(phaseNum, bracketId));
|
|
379
|
+
const phaseName = match[2 + G].replace(/\(INSERTED\)/i, '').trim();
|
|
330
380
|
// Extract goal from the section
|
|
331
381
|
const sectionStart = match.index;
|
|
332
382
|
const restOfContent = content.slice(sectionStart);
|
|
333
383
|
// #3691: `\d` → `\d[\d.]*` so decimal phase headings (e.g. `### Phase 02.3:`) are
|
|
334
384
|
// recognised as section boundaries. #3036: `[A-Za-z]?\d` so non-numeric-leading ids
|
|
335
385
|
// (e.g. B7) are also recognised.
|
|
336
|
-
|
|
386
|
+
// #4478 follow-up (independent code review on this same fix): ` {0,3}` after
|
|
387
|
+
// the literal `\n` mirrors phasePattern's own new leading-space tolerance
|
|
388
|
+
// above -- without it, a legitimately-indented (1-3 space) NEXT phase
|
|
389
|
+
// heading was invisible to this boundary lookup, letting the PRIOR phase's
|
|
390
|
+
// goal/mode/depends_on extraction bleed across the section boundary into
|
|
391
|
+
// the next phase's own body.
|
|
392
|
+
const nextHeader = restOfContent.match(new RegExp(`\\n {0,3}#{2,4}\\s+${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention)}[A-Za-z]?\\d[\\d.-]*`, 'i'));
|
|
337
393
|
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
338
394
|
const section = content.slice(sectionStart, sectionEnd);
|
|
339
395
|
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
@@ -353,27 +409,51 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
353
409
|
// hit a non-ENOENT error — no directory at all is `disk_status:
|
|
354
410
|
// 'no_directory'`, a real (if uninteresting) answer, not a read error.
|
|
355
411
|
let contextReadError = null;
|
|
412
|
+
// #4014 (epic #3473 B4): additive sibling — SCOPE.COMPLETE by default
|
|
413
|
+
// (no directory at all is a genuine, not-unreadable answer), overwritten
|
|
414
|
+
// below only when dirMatch resolves.
|
|
415
|
+
let contextScope = SCOPE.COMPLETE;
|
|
356
416
|
// DEAD catch removed (#2245 audit): matchPhaseDirs(...) is a pure
|
|
357
417
|
// array lookup on an already-resolved string array, and
|
|
358
418
|
// countPhasePlansAndSummaries is itself fully defensive (its own
|
|
359
419
|
// readdirSync is self-guarded, and it delegates to scanPhasePlans, which
|
|
360
420
|
// never throws) — nothing in this block can throw, so the try/catch could
|
|
361
421
|
// never be triggered.
|
|
362
|
-
|
|
422
|
+
// #612: the DIRECTORY read is selected by the same `convention` the four
|
|
423
|
+
// heading/checklist patterns above already thread. Left two-argument, this
|
|
424
|
+
// one call reported EVERY canonical `{CODE}.{MM}-{PP}-slug` directory as
|
|
425
|
+
// `disk_status: "no_directory"` with `plan_count`/`summary_count` 0 —
|
|
426
|
+
// `extractPhaseToken('GSD.02-01-one')` with no convention returns the whole
|
|
427
|
+
// dir name — while the same build resolved those same directories correctly
|
|
428
|
+
// in three other places on the same repo (W006/W007 through their shared
|
|
429
|
+
// directory matcher, `state json` via the milestone filter, and the W026
|
|
430
|
+
// milestone-complete read through the same convention-aware owner). It
|
|
431
|
+
// failed ONLY for the directory shape the convention exists to name: a
|
|
432
|
+
// mid-migration bracket repo carrying legacy `01-one` dirs resolved fine.
|
|
433
|
+
// That is verbatim the asymmetry the note above the W026 rule says this PR
|
|
434
|
+
// closed — the directory read widens with the heading read, or every bracket
|
|
435
|
+
// phase resolves to nothing.
|
|
436
|
+
// Upstream centralized this choice in `matchPhaseDirs`; thread the same
|
|
437
|
+
// convention into that owner rather than reviving the primitive `.find()`.
|
|
438
|
+
const dirMatch = matchPhaseDirs(phaseDirNames, normalized, convention).matches[0];
|
|
363
439
|
if (dirMatch) {
|
|
364
|
-
const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch));
|
|
440
|
+
const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch), convention);
|
|
365
441
|
planCount = counts.planCount;
|
|
366
442
|
summaryCount = counts.summaryCount;
|
|
367
443
|
hasContext = counts.hasContext;
|
|
368
444
|
hasResearch = counts.hasResearch;
|
|
369
445
|
contextReadError = counts.contextReadError;
|
|
446
|
+
contextScope = counts.scope;
|
|
370
447
|
// ADR-3180 §7.4 (issue #3186, disk-strict, #3168 fix): route "is this
|
|
371
448
|
// phase complete" through the canonical owner (`isPhaseComplete`),
|
|
372
449
|
// which calls readVerificationStatus UNCONDITIONALLY — plan count is
|
|
373
450
|
// NOT a precondition, so a zero-plan phase with a passing
|
|
374
451
|
// `*-VERIFICATION.md` reports complete here too, not just via
|
|
375
452
|
// `phase.complete`.
|
|
376
|
-
|
|
453
|
+
// #612: `convention` (a parameter of this function, same thread as
|
|
454
|
+
// matchPhaseDirs above) rides into completion so a bracket phase dir
|
|
455
|
+
// resolves and scopes its verification report like its legacy twin.
|
|
456
|
+
const completionResult = isPhaseComplete(node_path_1.default.join(phasesDir, dirMatch), { convention });
|
|
377
457
|
if (completionResult.value.complete)
|
|
378
458
|
diskStatus = 'complete';
|
|
379
459
|
else if (summaryCount > 0)
|
|
@@ -401,7 +481,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
401
481
|
// checkbox — no passing `*-VERIFICATION.md`, plans outstanding — now
|
|
402
482
|
// reports incomplete; this is the deliberate Tier-2 break (ADR-3180 §7.4
|
|
403
483
|
// Decision 3).
|
|
404
|
-
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s
|
|
484
|
+
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}${phaseMarkdownRegexSource(phaseNum)}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i');
|
|
405
485
|
const checkboxMatch = content.match(checkboxPattern);
|
|
406
486
|
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
|
|
407
487
|
phases.push({
|
|
@@ -417,6 +497,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
417
497
|
disk_status: diskStatus,
|
|
418
498
|
roadmap_complete: roadmapComplete,
|
|
419
499
|
context_read_error: contextReadError,
|
|
500
|
+
context_scope: contextScope,
|
|
420
501
|
});
|
|
421
502
|
}
|
|
422
503
|
// #3577: markdown-table row declarations join the enumeration — same
|
|
@@ -426,6 +507,9 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
426
507
|
const stripPadA = (s) => s.replace(/^0+(?=.)/, '');
|
|
427
508
|
const seen = new Set(phases.map((ph) => stripPadA(ph.number)));
|
|
428
509
|
for (const tr of collectTablePhaseRows(content)) {
|
|
510
|
+
// #3577 table declarations were part of the pre-existing detail set.
|
|
511
|
+
// Preserve that behavior while heading occurrences gain bracket identity.
|
|
512
|
+
detailKeys.add(occurrenceKey(tr.id));
|
|
429
513
|
if (seen.has(stripPadA(tr.id)))
|
|
430
514
|
continue;
|
|
431
515
|
const dirMatchA = matchPhaseDirs(phaseDirNames, normalizePhaseName(tr.id)).matches[0];
|
|
@@ -434,6 +518,9 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
434
518
|
let tHasContext = false;
|
|
435
519
|
let tHasResearch = false;
|
|
436
520
|
let tContextReadError = null;
|
|
521
|
+
// #4014 (epic #3473 B4): additive sibling, same default rule as the
|
|
522
|
+
// heading-declared branch above.
|
|
523
|
+
let tContextScope = SCOPE.COMPLETE;
|
|
437
524
|
if (dirMatchA) {
|
|
438
525
|
const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatchA));
|
|
439
526
|
tPlanCount = counts.planCount;
|
|
@@ -445,6 +532,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
445
532
|
// existsSync (which cannot itself distinguish EACCES from absent), but
|
|
446
533
|
// an unreadable phase directory is still surfaced via the sibling call.
|
|
447
534
|
tContextReadError = counts.contextReadError;
|
|
535
|
+
tContextScope = counts.scope;
|
|
448
536
|
}
|
|
449
537
|
phases.push({
|
|
450
538
|
number: tr.id,
|
|
@@ -459,9 +547,10 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
459
547
|
disk_status: dirMatchA ? 'ok' : 'no_directory',
|
|
460
548
|
roadmap_complete: false,
|
|
461
549
|
context_read_error: tContextReadError,
|
|
550
|
+
context_scope: tContextScope,
|
|
462
551
|
});
|
|
463
552
|
}
|
|
464
|
-
return phases;
|
|
553
|
+
return { phases, detailKeys };
|
|
465
554
|
}
|
|
466
555
|
function cmdRoadmapAnalyze(cwd, raw) {
|
|
467
556
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
@@ -475,6 +564,10 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
475
564
|
// indistinguishable from a genuinely empty milestone.
|
|
476
565
|
const { value: content, scope } = extractCurrentMilestoneScoped(rawContent, cwd);
|
|
477
566
|
const phasesDir = planningPaths(cwd).phases;
|
|
567
|
+
// #612: resolve once per command and thread the same reading through both
|
|
568
|
+
// the scoped scan and any fallback scan.
|
|
569
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
570
|
+
const G = convention === 'bracket' ? 1 : 0;
|
|
478
571
|
// Build phase directory lookup once (O(1) readdir instead of O(N) per phase)
|
|
479
572
|
// #3185 exemption reason (ADR-3180 Decision 4a): this is a heading->directory
|
|
480
573
|
// LOOKUP INDEX, not a milestone enumeration. It must see the PHYSICAL set so
|
|
@@ -490,7 +583,9 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
490
583
|
// Scan the scoped milestone window for phase-detail headings and enrich each
|
|
491
584
|
// with its on-disk status. Extracted into `collectAnalyzePhases` (#3165) so
|
|
492
585
|
// the SAME enrichment re-runs on the fallback below — not a second copy.
|
|
493
|
-
let
|
|
586
|
+
let collected = collectAnalyzePhases(content, phasesDir, _phaseDirNames, convention);
|
|
587
|
+
let phases = collected.phases;
|
|
588
|
+
let detailKeys = collected.detailKeys;
|
|
494
589
|
// `effectiveContent` is what the downstream checklist scan (missing_details)
|
|
495
590
|
// iterates. Defaults to the scoped window; switched to the fallback document
|
|
496
591
|
// when the recovery path below fires, so a phase found via fallback is not
|
|
@@ -512,9 +607,11 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
512
607
|
// populated, flagged result.
|
|
513
608
|
if (phases.length === 0 && scope !== SCOPE.COMPLETE && _phaseDirNames.length > 0) {
|
|
514
609
|
const fallbackContent = stripShippedMilestones(rawContent);
|
|
515
|
-
const
|
|
516
|
-
if (
|
|
517
|
-
|
|
610
|
+
const fallbackCollection = collectAnalyzePhases(fallbackContent, phasesDir, _phaseDirNames, convention);
|
|
611
|
+
if (fallbackCollection.phases.length > 0) {
|
|
612
|
+
collected = fallbackCollection;
|
|
613
|
+
phases = collected.phases;
|
|
614
|
+
detailKeys = collected.detailKeys;
|
|
518
615
|
effectiveContent = fallbackContent;
|
|
519
616
|
}
|
|
520
617
|
}
|
|
@@ -537,17 +634,41 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
537
634
|
// The char class must allow `-` (not just `.`) so dash-separated milestone-prefixed
|
|
538
635
|
// IDs (e.g. `1-01`) match the detail-heading scanner above; otherwise they truncate
|
|
539
636
|
// at the dash (`1-01` -> `1`) and every such phase reports a phantom missing detail.
|
|
637
|
+
// #612: CAPTURING label-only intro — the bracket id rides along so the
|
|
638
|
+
// sentinel filter below is not blind to `- [ ] **[GSD.999] 01: Icebox**`.
|
|
540
639
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
541
640
|
// #3036: widen to accept non-numeric-leading ids (same widening as the detail-heading pattern above).
|
|
542
641
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
543
|
-
const checklistPattern =
|
|
544
|
-
|
|
642
|
+
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, true)}([A-Za-z]?\\d+[A-Z]?(?:[.-]\\d+)*)`, 'gi');
|
|
643
|
+
// #2761 M1: an OCCURRENCE list keyed by `occurrenceKey`, not a token->bracket
|
|
644
|
+
// map. The map was first-wins on the bare token, so of two checklist entries
|
|
645
|
+
// sharing a token across brackets the FIRST one's bracket id classified BOTH:
|
|
646
|
+
// `- [ ] **[GSD.999] 01: Icebox**` written above `- [ ] **[GSD.02] 01: …**`
|
|
647
|
+
// made the real phase inherit the icebox's sentinel verdict and vanish from
|
|
648
|
+
// `missing_phase_details`; written below it, the same document reported it.
|
|
649
|
+
// Dedupe still happens — it is now per PHASE rather than per token, which is
|
|
650
|
+
// what makes the classification order-independent.
|
|
651
|
+
const checklistOccurrences = [];
|
|
652
|
+
const seenChecklistKeys = new Set();
|
|
545
653
|
let checklistMatch;
|
|
546
654
|
while ((checklistMatch = checklistPattern.exec(effectiveContent)) !== null) {
|
|
547
|
-
|
|
655
|
+
const token = checklistMatch[1 + G];
|
|
656
|
+
const bracketId = G ? checklistMatch[1] : undefined;
|
|
657
|
+
const key = occurrenceKey(token, bracketId);
|
|
658
|
+
if (seenChecklistKeys.has(key))
|
|
659
|
+
continue;
|
|
660
|
+
seenChecklistKeys.add(key);
|
|
661
|
+
checklistOccurrences.push({ token, bracketId });
|
|
548
662
|
}
|
|
549
|
-
|
|
550
|
-
|
|
663
|
+
// The EMITTED value stays the bare token, unchanged: `phases[].number` is a
|
|
664
|
+
// token under every convention, and `missing_phase_details` is read against
|
|
665
|
+
// it. Only the classification moved to the qualified key — so two different
|
|
666
|
+
// brackets' `01` both missing report `01` once, rather than one of them
|
|
667
|
+
// silently covering for the other.
|
|
668
|
+
const missingDetails = [...new Set(checklistOccurrences
|
|
669
|
+
.filter(o => !detailKeys.has(occurrenceKey(o.token, o.bracketId))
|
|
670
|
+
&& !isSentinelPhase(o.token, o.bracketId))
|
|
671
|
+
.map(o => o.token))];
|
|
551
672
|
// #3217 (ADR-3180 §7.6 rules 3-4): `progress_percent` used to accumulate
|
|
552
673
|
// `totalPlans`/`totalSummaries` above — a heading-matched enumeration
|
|
553
674
|
// (`phasePattern` over the milestone-windowed `content`) paired against
|
|
@@ -672,7 +793,7 @@ function cmdRoadmapMilestoneScope(cwd, raw) {
|
|
|
672
793
|
}
|
|
673
794
|
const { value: window, scope } = extractCurrentMilestoneScoped(rawContent, cwd, undefined, phaseIdConvention);
|
|
674
795
|
// Document order (Set insertion order) — deterministic for a given document.
|
|
675
|
-
const phases = [...scanMilestonePhaseIds(window)];
|
|
796
|
+
const phases = [...scanMilestonePhaseIds(window, phaseIdConvention)];
|
|
676
797
|
output({ scope, phases, phase_count: phases.length }, raw, undefined);
|
|
677
798
|
}
|
|
678
799
|
// ─── cmdRoadmapUpdatePlanProgress ─────────────────────────────────────────────
|
|
@@ -724,7 +845,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
724
845
|
// inflate summary_count and silently flip the phase to Complete.
|
|
725
846
|
const summaryCount = countMatchedSummaries(phaseInfo.plans, phaseInfo.summaries);
|
|
726
847
|
if (planCount === 0) {
|
|
727
|
-
|
|
848
|
+
declineNoOp(raw, 'updated', 'No plans found', 'roadmap update-plan-progress skipped — no plans found for this phase. ROADMAP.md was left unchanged.', { plan_count: 0, summary_count: 0 });
|
|
728
849
|
return;
|
|
729
850
|
}
|
|
730
851
|
// Verification gate (#2022): do NOT check the phase checkbox or stamp a
|
|
@@ -740,7 +861,11 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
740
861
|
// the same phase (ADR-3180 §7.4's headline: one predicate for the read
|
|
741
862
|
// path and the write path).
|
|
742
863
|
const phaseDir = node_path_1.default.join(cwd, phaseInfo.directory);
|
|
743
|
-
|
|
864
|
+
// ADR-3180 §7.4 read/write-path symmetry with the threaded site at ~583:
|
|
865
|
+
// thread convention here too, so this write path's completion reading
|
|
866
|
+
// agrees with the read path's under the bracket convention.
|
|
867
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
868
|
+
const completionResult = isPhaseComplete(phaseDir, { convention });
|
|
744
869
|
const verificationResult = completionResult.value.verification;
|
|
745
870
|
// #2648 precedent, applied at this write site (ADR-3180 §7.4 / #3186):
|
|
746
871
|
// `isPhaseComplete` deliberately carries NO plan-count precondition — the
|
|
@@ -766,13 +891,63 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
766
891
|
const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
|
|
767
892
|
const today = clock_cjs_1.realClock.localToday();
|
|
768
893
|
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
769
|
-
|
|
894
|
+
declineNoOp(raw, 'updated', 'ROADMAP.md not found', 'roadmap update-plan-progress skipped — ROADMAP.md not found.', { plan_count: planCount, summary_count: summaryCount });
|
|
770
895
|
return;
|
|
771
896
|
}
|
|
772
897
|
// Wrap entire read-modify-write in lock to prevent concurrent corruption
|
|
898
|
+
let updated = false;
|
|
899
|
+
// #4247: the refusal flag. The write/report decision below must be keyed to
|
|
900
|
+
// "a writable roadmap representation of THIS phase was found", never to "any
|
|
901
|
+
// byte moved". On a checklist-form ROADMAP (`- [ ] **Phase N: …**`, the
|
|
902
|
+
// roadmapper's own summary-checklist form) every phase-targeted grammar
|
|
903
|
+
// below requires an ATX `#{2,4} Phase N` heading and therefore finds
|
|
904
|
+
// nothing; the Progress-table row is the only other writable target, and
|
|
905
|
+
// when its Phase cell does not match `phaseCellRe` (e.g. a word-prefixed
|
|
906
|
+
// `Phase 68` cell — deliberately unrecognized on the read side too,
|
|
907
|
+
// `deriveProgressFromRoadmap`'s `/^\d/` data-row filter) the command used
|
|
908
|
+
// to fall through to unrelated byte deltas (an UN-scoped plan-checkbox mark
|
|
909
|
+
// anywhere in the document) and report `updated: true` while the phase's
|
|
910
|
+
// own row stayed untouched — with the file-global write then letting the
|
|
911
|
+
// platform write seam's markdown normalization inject blank lines around
|
|
912
|
+
// other phases' bullets, splitting hand-wrapped sentences mid-entry. The
|
|
913
|
+
// refusal below declines with the analyzer's own `missing_phase_details`
|
|
914
|
+
// vocabulary and leaves ROADMAP.md byte-identical.
|
|
915
|
+
let missingPhaseDetails = false;
|
|
773
916
|
withPlanningLock(cwd, () => {
|
|
774
|
-
|
|
917
|
+
// #3957 (B9.4): captured BEFORE any transform runs, so the write/report
|
|
918
|
+
// decision below reflects whether the transforms actually changed
|
|
919
|
+
// anything — not just that they ran. Every transform below still runs
|
|
920
|
+
// unconditionally exactly as before; only the final write-and-report
|
|
921
|
+
// step becomes conditional on `roadmapContent !== originalContent`.
|
|
922
|
+
const originalContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
923
|
+
let roadmapContent = originalContent;
|
|
775
924
|
const phasePattern = phaseMarkdownRegexSource(phaseNum);
|
|
925
|
+
// #4247: ONE local source for the ATX phase-heading anchor that every
|
|
926
|
+
// section-scoped writer below (`planCountPattern`,
|
|
927
|
+
// `insertRowsPatternA|B`) starts with — extracted so the target-detection
|
|
928
|
+
// gate below reads the SAME grammar the writers anchor on, and a future
|
|
929
|
+
// edit to one cannot drift from the other three copies.
|
|
930
|
+
const phaseHeadingAnchor = `#{2,4}\\s*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}(?=[:\\s])`;
|
|
931
|
+
// #4247: target detection runs against the ORIGINAL content's active
|
|
932
|
+
// (post-</details>) region — the same milestone scoping every writer
|
|
933
|
+
// below applies — so the gate asks "does the file carry a writable phase
|
|
934
|
+
// representation" rather than "did some regex fire mid-transform".
|
|
935
|
+
const gateDetailsClose = originalContent.lastIndexOf('</details>');
|
|
936
|
+
const gateActiveRegion = gateDetailsClose === -1
|
|
937
|
+
? originalContent
|
|
938
|
+
: originalContent.slice(gateDetailsClose + '</details>'.length);
|
|
939
|
+
// Heading target: the exact grammar `planCountPattern` /
|
|
940
|
+
// `insertRowsPatternA|B` anchor on (an ATX phase heading for this phase).
|
|
941
|
+
const headingTargetFound = new RegExp(phaseHeadingAnchor, 'i').test(gateActiveRegion);
|
|
942
|
+
// Checklist target: when the phase is complete, its own checklist bullet
|
|
943
|
+
// (`- [ ] **Phase N: …**`) IS a writable phase row — the completion
|
|
944
|
+
// checkbox stamp below updates it. Same grammar as that writer, widened
|
|
945
|
+
// one notch to `[ x]` so an ALREADY-checked bullet still counts as a
|
|
946
|
+
// found target: an idempotent re-run then takes the honest
|
|
947
|
+
// "no changes were needed" decline instead of this refusal.
|
|
948
|
+
const checklistTargetFound = isComplete && new RegExp(`-\\s*\\[[ x]\\]\\s*.*Phase\\s+${phasePattern}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i').test(gateActiveRegion);
|
|
949
|
+
// Table-row target: set by the row-scoped cell updates below.
|
|
950
|
+
let tableRowFound = false;
|
|
776
951
|
// Progress table row: update Plans Complete/Status/Completed columns BY
|
|
777
952
|
// COLUMN NAME (handles 4- or 5-column RoadmapProgress tables regardless of
|
|
778
953
|
// Milestone-column presence) via the markdown-table seam (ADR-2143 §7) —
|
|
@@ -791,11 +966,15 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
791
966
|
roadmapContent = editProgressTableSlice(roadmapContent, (scoped) => {
|
|
792
967
|
let text = scoped;
|
|
793
968
|
const plansResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Plans Complete', ` ${summaryCount}/${planCount} `);
|
|
794
|
-
if (plansResult.ok)
|
|
969
|
+
if (plansResult.ok) {
|
|
795
970
|
text = plansResult.value;
|
|
971
|
+
tableRowFound = true;
|
|
972
|
+
}
|
|
796
973
|
const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', ` ${status.padEnd(11)}`);
|
|
797
|
-
if (statusResult.ok)
|
|
974
|
+
if (statusResult.ok) {
|
|
798
975
|
text = statusResult.value;
|
|
976
|
+
tableRowFound = true;
|
|
977
|
+
}
|
|
799
978
|
// Preserve only a valid ISO date (#1161: idempotent; self-heal garbage).
|
|
800
979
|
// Ragged-tolerant (#2245 Blocker 2): probe the CURRENT Completed cell via
|
|
801
980
|
// a no-op updateTableCell write (its own tolerant row scan) rather than
|
|
@@ -810,8 +989,10 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
810
989
|
}
|
|
811
990
|
return ' ';
|
|
812
991
|
});
|
|
813
|
-
if (completedResult.ok)
|
|
992
|
+
if (completedResult.ok) {
|
|
814
993
|
text = completedResult.value;
|
|
994
|
+
tableRowFound = true;
|
|
995
|
+
}
|
|
815
996
|
return text;
|
|
816
997
|
});
|
|
817
998
|
// Update plan count in phase detail section.
|
|
@@ -852,7 +1033,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
852
1033
|
// `_match` unchanged. An untouched first line cannot orphan its own
|
|
853
1034
|
// continuation on the next line, since the pattern never spans past
|
|
854
1035
|
// `\n` in the first place.
|
|
855
|
-
const planCountPattern = new RegExp(`(
|
|
1036
|
+
const planCountPattern = new RegExp(`(${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*|(?:^|\\n)Plans:)\\s*)(\\d+\\s*\\/\\s*\\d+\\s+plans(?:\\s+(?:complete|executed))?|\\d+\\s+plans?)?([^\\r\\n]*)`, 'i');
|
|
856
1037
|
const planCountText = isComplete
|
|
857
1038
|
? `${summaryCount}/${planCount} plans complete`
|
|
858
1039
|
: `${summaryCount}/${planCount} plans executed`;
|
|
@@ -932,8 +1113,8 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
932
1113
|
//
|
|
933
1114
|
// Pattern A: anchor to bare `Plans:` header (preferred).
|
|
934
1115
|
// Pattern B: fallback to bold summary when no bare header exists.
|
|
935
|
-
const insertRowsPatternA = new RegExp(`(
|
|
936
|
-
const insertRowsPatternB = new RegExp(`(
|
|
1116
|
+
const insertRowsPatternA = new RegExp(`(${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:^|\\n)(?:Plans:)[^\\n]*)`, 'i');
|
|
1117
|
+
const insertRowsPatternB = new RegExp(`(${phaseHeadingAnchor}(?:(?!\\n#{1,4}\\s)[\\s\\S])*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, 'i');
|
|
937
1118
|
const sortedMissing = [...missingPlans].sort();
|
|
938
1119
|
const newRows = sortedMissing.map(p => `- [ ] ${p}`).join('\n');
|
|
939
1120
|
const inserter = (match) => `${match}\n${newRows}`;
|
|
@@ -968,17 +1149,51 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
968
1149
|
}
|
|
969
1150
|
}
|
|
970
1151
|
}
|
|
971
|
-
(
|
|
1152
|
+
// #3957 (B9.4): write and report an update only when the transforms
|
|
1153
|
+
// above actually produced different bytes — mirroring the sibling
|
|
1154
|
+
// `cmdRoadmapAnnotateDependencies`'s existing `nextContent !== content`
|
|
1155
|
+
// gate. Previously this wrote and reported `updated: true`
|
|
1156
|
+
// unconditionally, even on an idempotent re-run that changed nothing.
|
|
1157
|
+
//
|
|
1158
|
+
// #4247: ...but ONLY when a writable representation of THIS phase was
|
|
1159
|
+
// found. Without the target gate, a byte delta from an unrelated
|
|
1160
|
+
// transform (the un-scoped plan-checkbox mark) satisfied the #3957 gate
|
|
1161
|
+
// and produced a success-shaped `updated: true` while the phase's own
|
|
1162
|
+
// row stayed untouched — and the file-global write let the platform
|
|
1163
|
+
// write seam's markdown normalization reflow unrelated entries. When no
|
|
1164
|
+
// target exists the command refuses: no write at all, so ROADMAP.md is
|
|
1165
|
+
// left byte-identical, and the caller gets a typed
|
|
1166
|
+
// `missing_phase_details` decline instead of a false green.
|
|
1167
|
+
const phaseRepresentationFound = tableRowFound || headingTargetFound || checklistTargetFound;
|
|
1168
|
+
if (phaseRepresentationFound && roadmapContent !== originalContent) {
|
|
1169
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, roadmapContent);
|
|
1170
|
+
updated = true;
|
|
1171
|
+
}
|
|
1172
|
+
if (!phaseRepresentationFound) {
|
|
1173
|
+
missingPhaseDetails = true;
|
|
1174
|
+
}
|
|
972
1175
|
});
|
|
973
|
-
|
|
974
|
-
updated: true,
|
|
1176
|
+
const computed = {
|
|
975
1177
|
phase: phaseNum,
|
|
976
1178
|
plan_count: planCount,
|
|
977
1179
|
summary_count: summaryCount,
|
|
978
1180
|
status,
|
|
979
1181
|
complete: isComplete,
|
|
980
1182
|
verification_stale_check_indeterminate: verificationStaleCheckIndeterminate,
|
|
981
|
-
}
|
|
1183
|
+
};
|
|
1184
|
+
if (updated) {
|
|
1185
|
+
output({ updated: true, ...computed }, raw, `${summaryCount}/${planCount} ${status}`);
|
|
1186
|
+
}
|
|
1187
|
+
else if (missingPhaseDetails) {
|
|
1188
|
+
// #4247: honest refusal — the reason names the real condition (the
|
|
1189
|
+
// analyzer's `missing_phase_details` vocabulary), never "already
|
|
1190
|
+
// reflects", which was false: the ROADMAP was never able to record this
|
|
1191
|
+
// phase's progress in the first place.
|
|
1192
|
+
declineNoOp(raw, 'updated', 'missing_phase_details', `roadmap update-plan-progress skipped — ROADMAP.md has no writable entry for phase ${formatDiagnosticToken(String(phaseNum))} (no matching Progress-table row, no phase detail section, and no checklist entry this command can update). ROADMAP.md was left unchanged.`, computed);
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
declineNoOp(raw, 'updated', "no changes were needed — ROADMAP.md already reflects this phase's plan/summary counts and status", "roadmap update-plan-progress skipped — no changes were needed; ROADMAP.md already reflects this phase's plan/summary counts and status.", computed);
|
|
1196
|
+
}
|
|
982
1197
|
}
|
|
983
1198
|
// ─── cmdRoadmapAnnotateDependencies ───────────────────────────────────────────
|
|
984
1199
|
/**
|
|
@@ -1000,12 +1215,20 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
|
|
|
1000
1215
|
}
|
|
1001
1216
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
1002
1217
|
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
1003
|
-
|
|
1218
|
+
declineNoOp(raw, 'updated', 'ROADMAP.md not found', 'roadmap annotate-dependencies skipped — ROADMAP.md not found.');
|
|
1004
1219
|
return;
|
|
1005
1220
|
}
|
|
1006
1221
|
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
1007
|
-
|
|
1008
|
-
|
|
1222
|
+
// #3957 (B9.1): distinguish "phase does not resolve at all" from "phase
|
|
1223
|
+
// resolves but has zero plans" — previously both collapsed into the same
|
|
1224
|
+
// 'no plans found for phase' reason, which is simply false for the first
|
|
1225
|
+
// case (there IS no such phase to have plans).
|
|
1226
|
+
if (!phaseInfo) {
|
|
1227
|
+
declineNoOp(raw, 'updated', `phase ${phaseNum} not found`, `roadmap annotate-dependencies skipped — phase ${formatDiagnosticToken(String(phaseNum))} not found.`, { phase: phaseNum });
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if (phaseInfo.plans.length === 0) {
|
|
1231
|
+
declineNoOp(raw, 'updated', `phase ${phaseNum} has no plans`, `roadmap annotate-dependencies skipped — phase ${formatDiagnosticToken(String(phaseNum))} has no plans.`, { phase: phaseNum });
|
|
1009
1232
|
return;
|
|
1010
1233
|
}
|
|
1011
1234
|
// Read each PLAN.md and extract wave + must_haves.truths
|
|
@@ -1023,7 +1246,7 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
|
|
|
1023
1246
|
catch { /* skip unreadable plans */ }
|
|
1024
1247
|
}
|
|
1025
1248
|
if (planData.length === 0) {
|
|
1026
|
-
|
|
1249
|
+
declineNoOp(raw, 'updated', 'could not read plan frontmatter', 'roadmap annotate-dependencies skipped — could not read plan frontmatter for any plan in this phase.');
|
|
1027
1250
|
return;
|
|
1028
1251
|
}
|
|
1029
1252
|
// Group plans by wave (sorted)
|