@pareto4/pp-core 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +23 -0
- package/GEMINI.md +53 -0
- package/LICENSE +22 -0
- package/README.md +100 -0
- package/agents/pp-advisor-researcher.md +108 -0
- package/agents/pp-ai-researcher.md +114 -0
- package/agents/pp-assumptions-analyzer.md +105 -0
- package/agents/pp-code-fixer.md +669 -0
- package/agents/pp-code-reviewer.md +387 -0
- package/agents/pp-codebase-mapper.md +853 -0
- package/agents/pp-debug-session-manager.md +315 -0
- package/agents/pp-debugger.md +1453 -0
- package/agents/pp-doc-classifier.md +168 -0
- package/agents/pp-doc-synthesizer.md +204 -0
- package/agents/pp-doc-verifier.md +217 -0
- package/agents/pp-doc-writer.md +616 -0
- package/agents/pp-domain-researcher.md +147 -0
- package/agents/pp-eval-auditor.md +191 -0
- package/agents/pp-eval-planner.md +154 -0
- package/agents/pp-executor.md +808 -0
- package/agents/pp-framework-selector.md +160 -0
- package/agents/pp-integration-checker.md +470 -0
- package/agents/pp-intel-updater.md +339 -0
- package/agents/pp-mempalace-curator.md +47 -0
- package/agents/pp-nyquist-auditor.md +204 -0
- package/agents/pp-pattern-mapper.md +335 -0
- package/agents/pp-phase-researcher.md +867 -0
- package/agents/pp-plan-checker.md +988 -0
- package/agents/pp-planner.md +1052 -0
- package/agents/pp-project-researcher.md +612 -0
- package/agents/pp-research-synthesizer.md +260 -0
- package/agents/pp-roadmapper.md +741 -0
- package/agents/pp-security-auditor.md +156 -0
- package/agents/pp-ui-auditor.md +495 -0
- package/agents/pp-ui-checker.md +309 -0
- package/agents/pp-ui-researcher.md +375 -0
- package/agents/pp-user-profiler.md +171 -0
- package/agents/pp-verifier.md +967 -0
- package/assets/gsd-logo-2000-transparent.png +0 -0
- package/assets/gsd-logo-2000-transparent.svg +17 -0
- package/assets/gsd-logo-2000.png +0 -0
- package/assets/gsd-logo-2000.svg +21 -0
- package/assets/terminal.svg +68 -0
- package/bin/install.js +12370 -0
- package/bin/lib/ui-safety-gate.cjs +107 -0
- package/commands/pp-add-tests.md +42 -0
- package/commands/pp-ai-integration-phase.md +37 -0
- package/commands/pp-audit-fix.md +34 -0
- package/commands/pp-audit-milestone.md +37 -0
- package/commands/pp-audit-uat.md +24 -0
- package/commands/pp-autonomous.md +51 -0
- package/commands/pp-capture.md +62 -0
- package/commands/pp-cleanup.md +24 -0
- package/commands/pp-code-review.md +59 -0
- package/commands/pp-complete-milestone.md +143 -0
- package/commands/pp-config.md +56 -0
- package/commands/pp-debug.md +52 -0
- package/commands/pp-discovery.md +34 -0
- package/commands/pp-discuss-phase.md +77 -0
- package/commands/pp-docs-update.md +49 -0
- package/commands/pp-eval-review.md +33 -0
- package/commands/pp-execute-phase.md +65 -0
- package/commands/pp-explore.md +27 -0
- package/commands/pp-extract-learnings.md +23 -0
- package/commands/pp-fast.md +31 -0
- package/commands/pp-forensics.md +57 -0
- package/commands/pp-graphify.md +204 -0
- package/commands/pp-health.md +31 -0
- package/commands/pp-help.md +28 -0
- package/commands/pp-import.md +45 -0
- package/commands/pp-inbox.md +39 -0
- package/commands/pp-ingest-docs.md +42 -0
- package/commands/pp-manager.md +45 -0
- package/commands/pp-map-codebase.md +83 -0
- package/commands/pp-mempalace-capture.md +71 -0
- package/commands/pp-mempalace-recall.md +102 -0
- package/commands/pp-milestone-summary.md +51 -0
- package/commands/pp-mvp-phase.md +45 -0
- package/commands/pp-new-milestone.md +45 -0
- package/commands/pp-new-project.md +47 -0
- package/commands/pp-ns-context.md +25 -0
- package/commands/pp-ns-ideate.md +24 -0
- package/commands/pp-ns-manage.md +36 -0
- package/commands/pp-ns-project.md +27 -0
- package/commands/pp-ns-review.md +29 -0
- package/commands/pp-ns-workflow.md +35 -0
- package/commands/pp-pause-work.md +43 -0
- package/commands/pp-phase.md +56 -0
- package/commands/pp-plan-phase.md +63 -0
- package/commands/pp-plan-review-convergence.md +60 -0
- package/commands/pp-pr-branch.md +26 -0
- package/commands/pp-profile-user.md +46 -0
- package/commands/pp-progress.md +49 -0
- package/commands/pp-quick.md +174 -0
- package/commands/pp-resume-work.md +30 -0
- package/commands/pp-review-backlog.md +63 -0
- package/commands/pp-review.md +42 -0
- package/commands/pp-secure-phase.md +36 -0
- package/commands/pp-settings.md +29 -0
- package/commands/pp-ship.md +24 -0
- package/commands/pp-sketch.md +60 -0
- package/commands/pp-spec-phase.md +63 -0
- package/commands/pp-spike.md +57 -0
- package/commands/pp-stats.md +20 -0
- package/commands/pp-surface.md +162 -0
- package/commands/pp-thread.md +24 -0
- package/commands/pp-ui-phase.md +35 -0
- package/commands/pp-ui-review.md +33 -0
- package/commands/pp-ultraplan-phase.md +34 -0
- package/commands/pp-undo.md +35 -0
- package/commands/pp-update.md +49 -0
- package/commands/pp-validate-phase.md +36 -0
- package/commands/pp-verify-work.md +39 -0
- package/commands/pp-workspace.md +52 -0
- package/commands/pp-workstreams.md +70 -0
- package/gemini-extension.json +6 -0
- package/hooks/dist/lib/git-cmd.js +150 -0
- package/hooks/dist/lib/pp-graphify-rebuild.sh +65 -0
- package/hooks/dist/managed-hooks-registry.cjs +39 -0
- package/hooks/dist/pp-check-update-worker.js +108 -0
- package/hooks/dist/pp-check-update.js +66 -0
- package/hooks/dist/pp-config-reload.js +133 -0
- package/hooks/dist/pp-context-monitor.js +196 -0
- package/hooks/dist/pp-cursor-post-tool.js +75 -0
- package/hooks/dist/pp-cursor-session-start.js +52 -0
- package/hooks/dist/pp-ensure-canonical-path.js +305 -0
- package/hooks/dist/pp-graphify-update.sh +158 -0
- package/hooks/dist/pp-phase-boundary.sh +47 -0
- package/hooks/dist/pp-prompt-guard.js +97 -0
- package/hooks/dist/pp-read-guard.js +101 -0
- package/hooks/dist/pp-read-injection-scanner.js +203 -0
- package/hooks/dist/pp-session-state.sh +59 -0
- package/hooks/dist/pp-statusline.js +566 -0
- package/hooks/dist/pp-update-banner.js +138 -0
- package/hooks/dist/pp-validate-commit.sh +57 -0
- package/hooks/dist/pp-workflow-guard.js +167 -0
- package/hooks/dist/pp-worktree-path-guard.js +185 -0
- package/hooks/hooks.json +70 -0
- package/hooks/lib/git-cmd.js +150 -0
- package/hooks/lib/pp-graphify-rebuild.sh +65 -0
- package/hooks/managed-hooks-registry.cjs +39 -0
- package/hooks/pp-check-update-worker.js +108 -0
- package/hooks/pp-check-update.js +66 -0
- package/hooks/pp-config-reload.js +133 -0
- package/hooks/pp-context-monitor.js +196 -0
- package/hooks/pp-cursor-post-tool.js +75 -0
- package/hooks/pp-cursor-session-start.js +52 -0
- package/hooks/pp-ensure-canonical-path.js +305 -0
- package/hooks/pp-graphify-update.sh +158 -0
- package/hooks/pp-phase-boundary.sh +47 -0
- package/hooks/pp-prompt-guard.js +97 -0
- package/hooks/pp-read-guard.js +101 -0
- package/hooks/pp-read-injection-scanner.js +203 -0
- package/hooks/pp-session-state.sh +59 -0
- package/hooks/pp-statusline.js +566 -0
- package/hooks/pp-update-banner.js +138 -0
- package/hooks/pp-validate-commit.sh +57 -0
- package/hooks/pp-workflow-guard.js +167 -0
- package/hooks/pp-worktree-path-guard.js +185 -0
- package/package.json +125 -0
- package/pp-core/bin/check-latest-version.cjs +161 -0
- package/pp-core/bin/lib/active-workstream-store.cjs +297 -0
- package/pp-core/bin/lib/adr-parser.cjs +399 -0
- package/pp-core/bin/lib/agent-command-router.cjs +68 -0
- package/pp-core/bin/lib/agent-install-check.cjs +143 -0
- package/pp-core/bin/lib/artifacts.cjs +51 -0
- package/pp-core/bin/lib/audit-command-router.cjs +61 -0
- package/pp-core/bin/lib/audit.cjs +743 -0
- package/pp-core/bin/lib/capability-activation.cjs +113 -0
- package/pp-core/bin/lib/capability-registry.cjs +3727 -0
- package/pp-core/bin/lib/capability-state.cjs +497 -0
- package/pp-core/bin/lib/capability-writer.cjs +355 -0
- package/pp-core/bin/lib/check-command-router.cjs +800 -0
- package/pp-core/bin/lib/cjs-command-router-adapter.cjs +81 -0
- package/pp-core/bin/lib/cli-exit.cjs +61 -0
- package/pp-core/bin/lib/clock.cjs +95 -0
- package/pp-core/bin/lib/clusters.cjs +131 -0
- package/pp-core/bin/lib/code-review-flags.cjs +59 -0
- package/pp-core/bin/lib/command-aliases.cjs +809 -0
- package/pp-core/bin/lib/command-arg-projection.cjs +55 -0
- package/pp-core/bin/lib/command-roster.cjs +19 -0
- package/pp-core/bin/lib/command-routing-hub.cjs +300 -0
- package/pp-core/bin/lib/commands.cjs +1244 -0
- package/pp-core/bin/lib/config-loader.cjs +714 -0
- package/pp-core/bin/lib/config-schema.cjs +58 -0
- package/pp-core/bin/lib/config-types.cjs +20 -0
- package/pp-core/bin/lib/config.cjs +796 -0
- package/pp-core/bin/lib/configuration.cjs +209 -0
- package/pp-core/bin/lib/context-utilization.cjs +48 -0
- package/pp-core/bin/lib/core-utils.cjs +198 -0
- package/pp-core/bin/lib/decisions.cjs +136 -0
- package/pp-core/bin/lib/docs.cjs +264 -0
- package/pp-core/bin/lib/drift.cjs +364 -0
- package/pp-core/bin/lib/edge-probe.cjs +196 -0
- package/pp-core/bin/lib/fallow-runner.cjs +153 -0
- package/pp-core/bin/lib/federated-config.cjs +182 -0
- package/pp-core/bin/lib/frontmatter.cjs +494 -0
- package/pp-core/bin/lib/gap-checker.cjs +260 -0
- package/pp-core/bin/lib/git-base-branch.cjs +220 -0
- package/pp-core/bin/lib/graphify-command-router.cjs +72 -0
- package/pp-core/bin/lib/graphify.cjs +472 -0
- package/pp-core/bin/lib/gsd2-import.cjs +456 -0
- package/pp-core/bin/lib/init-command-router.cjs +62 -0
- package/pp-core/bin/lib/init.cjs +1912 -0
- package/pp-core/bin/lib/install-profiles.cjs +814 -0
- package/pp-core/bin/lib/installer-migration-authoring.cjs +122 -0
- package/pp-core/bin/lib/installer-migration-report.cjs +351 -0
- package/pp-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +225 -0
- package/pp-core/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +48 -0
- package/pp-core/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +94 -0
- package/pp-core/bin/lib/installer-migrations/003-rename-get-shit-done-to-gsd-core.cjs +108 -0
- package/pp-core/bin/lib/installer-migrations/004-prune-stale-pristine-snapshots.cjs +123 -0
- package/pp-core/bin/lib/installer-migrations/005-rename-gsd-core-to-pp-core.cjs +109 -0
- package/pp-core/bin/lib/installer-migrations/006-rename-gsd-command-namespace.cjs +135 -0
- package/pp-core/bin/lib/installer-migrations.cjs +823 -0
- package/pp-core/bin/lib/intel-command-router.cjs +119 -0
- package/pp-core/bin/lib/intel.cjs +584 -0
- package/pp-core/bin/lib/io.cjs +220 -0
- package/pp-core/bin/lib/learnings.cjs +270 -0
- package/pp-core/bin/lib/legacy-cleanup.cjs +253 -0
- package/pp-core/bin/lib/loop-host-contract.cjs +105 -0
- package/pp-core/bin/lib/loop-resolver.cjs +479 -0
- package/pp-core/bin/lib/milestone.cjs +381 -0
- package/pp-core/bin/lib/model-catalog.cjs +154 -0
- package/pp-core/bin/lib/model-profiles.cjs +24 -0
- package/pp-core/bin/lib/model-resolver.cjs +467 -0
- package/pp-core/bin/lib/observability/event.cjs +51 -0
- package/pp-core/bin/lib/observability/logger.cjs +146 -0
- package/pp-core/bin/lib/observability/redaction.cjs +48 -0
- package/pp-core/bin/lib/package-identity.cjs +35 -0
- package/pp-core/bin/lib/package-legitimacy.cjs +368 -0
- package/pp-core/bin/lib/phase-command-router.cjs +209 -0
- package/pp-core/bin/lib/phase-id.cjs +213 -0
- package/pp-core/bin/lib/phase-lifecycle.cjs +74 -0
- package/pp-core/bin/lib/phase-locator.cjs +145 -0
- package/pp-core/bin/lib/phase.cjs +1446 -0
- package/pp-core/bin/lib/phases-command-router.cjs +43 -0
- package/pp-core/bin/lib/plan-drift-guard.cjs +117 -0
- package/pp-core/bin/lib/plan-scan.cjs +91 -0
- package/pp-core/bin/lib/planning-workspace.cjs +245 -0
- package/pp-core/bin/lib/probe-core.cjs +402 -0
- package/pp-core/bin/lib/profile-output.cjs +1164 -0
- package/pp-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
- package/pp-core/bin/lib/profile-pipeline.cjs +519 -0
- package/pp-core/bin/lib/prohibition-enforcement.cjs +660 -0
- package/pp-core/bin/lib/project-root.cjs +119 -0
- package/pp-core/bin/lib/prompt-budget.cjs +305 -0
- package/pp-core/bin/lib/research-provider.cjs +137 -0
- package/pp-core/bin/lib/research-store.cjs +167 -0
- package/pp-core/bin/lib/review-reviewer-selection.cjs +121 -0
- package/pp-core/bin/lib/roadmap-command-router.cjs +166 -0
- package/pp-core/bin/lib/roadmap-parser.cjs +456 -0
- package/pp-core/bin/lib/roadmap-upgrade.cjs +476 -0
- package/pp-core/bin/lib/roadmap.cjs +738 -0
- package/pp-core/bin/lib/runtime-artifact-conversion.cjs +1966 -0
- package/pp-core/bin/lib/runtime-artifact-layout.cjs +342 -0
- package/pp-core/bin/lib/runtime-config-adapter-registry.cjs +103 -0
- package/pp-core/bin/lib/runtime-homes.cjs +272 -0
- package/pp-core/bin/lib/runtime-hooks-surface.cjs +1460 -0
- package/pp-core/bin/lib/runtime-name-policy.cjs +97 -0
- package/pp-core/bin/lib/runtime-slash.cjs +137 -0
- package/pp-core/bin/lib/schema-detect.cjs +159 -0
- package/pp-core/bin/lib/secrets.cjs +34 -0
- package/pp-core/bin/lib/security.cjs +480 -0
- package/pp-core/bin/lib/semver-compare.cjs +42 -0
- package/pp-core/bin/lib/shell-command-projection.cjs +550 -0
- package/pp-core/bin/lib/state-command-router.cjs +172 -0
- package/pp-core/bin/lib/state-document.cjs +312 -0
- package/pp-core/bin/lib/state.cjs +2587 -0
- package/pp-core/bin/lib/surface.cjs +505 -0
- package/pp-core/bin/lib/task-command-router.cjs +85 -0
- package/pp-core/bin/lib/teams-status.cjs +74 -0
- package/pp-core/bin/lib/template.cjs +246 -0
- package/pp-core/bin/lib/uat-predicate.cjs +329 -0
- package/pp-core/bin/lib/uat.cjs +359 -0
- package/pp-core/bin/lib/ui-safety-gate.cjs +98 -0
- package/pp-core/bin/lib/update-context.cjs +221 -0
- package/pp-core/bin/lib/validate-command-router.cjs +91 -0
- package/pp-core/bin/lib/validate.cjs +122 -0
- package/pp-core/bin/lib/verification-command-router.cjs +31 -0
- package/pp-core/bin/lib/verification.cjs +196 -0
- package/pp-core/bin/lib/verify-command-router.cjs +44 -0
- package/pp-core/bin/lib/verify.cjs +1932 -0
- package/pp-core/bin/lib/workstream-inventory-builder.cjs +81 -0
- package/pp-core/bin/lib/workstream-inventory.cjs +147 -0
- package/pp-core/bin/lib/workstream-name-policy.cjs +91 -0
- package/pp-core/bin/lib/workstream.cjs +386 -0
- package/pp-core/bin/lib/worktree-base-ref.cjs +350 -0
- package/pp-core/bin/lib/worktree-safety.cjs +985 -0
- package/pp-core/bin/pp-tools.cjs +2239 -0
- package/pp-core/bin/pp_run +20 -0
- package/pp-core/bin/shared/config-defaults.manifest.json +97 -0
- package/pp-core/bin/shared/config-schema.manifest.json +176 -0
- package/pp-core/bin/shared/model-catalog.json +160 -0
- package/pp-core/bin/shared/runtime-aliases.manifest.json +79 -0
- package/pp-core/bin/verify-reapply-patches.cjs +400 -0
- package/pp-core/contexts/dev.md +21 -0
- package/pp-core/contexts/research.md +22 -0
- package/pp-core/contexts/review.md +23 -0
- package/pp-core/references/agent-contracts.md +79 -0
- package/pp-core/references/ai-evals.md +156 -0
- package/pp-core/references/ai-frameworks.md +186 -0
- package/pp-core/references/artifact-types.md +131 -0
- package/pp-core/references/autonomous-smart-discuss.md +277 -0
- package/pp-core/references/checkpoints.md +814 -0
- package/pp-core/references/common-bug-patterns.md +114 -0
- package/pp-core/references/context-budget.md +85 -0
- package/pp-core/references/continuation-format.md +253 -0
- package/pp-core/references/debugger-philosophy.md +76 -0
- package/pp-core/references/decimal-phase-calculation.md +64 -0
- package/pp-core/references/doc-conflict-engine.md +91 -0
- package/pp-core/references/domain-probes.md +125 -0
- package/pp-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
- package/pp-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
- package/pp-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
- package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
- package/pp-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
- package/pp-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
- package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
- package/pp-core/references/edge-probe.md +272 -0
- package/pp-core/references/execute-mvp-tdd.md +81 -0
- package/pp-core/references/executor-examples.md +110 -0
- package/pp-core/references/few-shot-examples/plan-checker.md +73 -0
- package/pp-core/references/few-shot-examples/verifier.md +109 -0
- package/pp-core/references/gate-prompts.md +100 -0
- package/pp-core/references/gates.md +70 -0
- package/pp-core/references/git-integration.md +298 -0
- package/pp-core/references/git-planning-commit.md +40 -0
- package/pp-core/references/ios-scaffold.md +123 -0
- package/pp-core/references/loop-hook-dispatch.md +61 -0
- package/pp-core/references/mandatory-initial-read.md +2 -0
- package/pp-core/references/model-profile-resolution.md +38 -0
- package/pp-core/references/model-profiles.md +245 -0
- package/pp-core/references/mvp-concepts.md +49 -0
- package/pp-core/references/phase-argument-parsing.md +61 -0
- package/pp-core/references/planner-antipatterns.md +176 -0
- package/pp-core/references/planner-chunked.md +49 -0
- package/pp-core/references/planner-gap-closure.md +62 -0
- package/pp-core/references/planner-graphify-auto-update.md +67 -0
- package/pp-core/references/planner-guidance.md +186 -0
- package/pp-core/references/planner-human-verify-mode.md +57 -0
- package/pp-core/references/planner-interface-context.md +62 -0
- package/pp-core/references/planner-load-graph-context.md +36 -0
- package/pp-core/references/planner-mvp-mode.md +53 -0
- package/pp-core/references/planner-reviews.md +42 -0
- package/pp-core/references/planner-revision.md +87 -0
- package/pp-core/references/planner-source-audit.md +73 -0
- package/pp-core/references/planning-config.md +477 -0
- package/pp-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
- package/pp-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
- package/pp-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
- package/pp-core/references/prohibition-probe.md +326 -0
- package/pp-core/references/project-skills-discovery.md +19 -0
- package/pp-core/references/questioning.md +162 -0
- package/pp-core/references/research-documentation-lookup.md +29 -0
- package/pp-core/references/research-philosophy.md +29 -0
- package/pp-core/references/research-verification-protocol.md +27 -0
- package/pp-core/references/revision-loop.md +97 -0
- package/pp-core/references/scout-codebase.md +51 -0
- package/pp-core/references/skeleton-template.md +48 -0
- package/pp-core/references/sketch-interactivity.md +41 -0
- package/pp-core/references/sketch-theme-system.md +94 -0
- package/pp-core/references/sketch-tooling.md +45 -0
- package/pp-core/references/sketch-variant-patterns.md +81 -0
- package/pp-core/references/spidr-splitting.md +69 -0
- package/pp-core/references/tdd.md +330 -0
- package/pp-core/references/thinking-models-debug.md +44 -0
- package/pp-core/references/thinking-models-execution.md +50 -0
- package/pp-core/references/thinking-models-planning.md +62 -0
- package/pp-core/references/thinking-models-research.md +50 -0
- package/pp-core/references/thinking-models-verification.md +55 -0
- package/pp-core/references/thinking-partner.md +96 -0
- package/pp-core/references/ui-brand.md +162 -0
- package/pp-core/references/universal-anti-patterns.md +63 -0
- package/pp-core/references/user-profiling.md +681 -0
- package/pp-core/references/user-story-template.md +58 -0
- package/pp-core/references/verification-overrides.md +227 -0
- package/pp-core/references/verification-patterns.md +612 -0
- package/pp-core/references/verify-mvp-mode.md +85 -0
- package/pp-core/references/workstream-flag.md +111 -0
- package/pp-core/references/worktree-branch-check.md +44 -0
- package/pp-core/references/worktree-path-safety.md +67 -0
- package/pp-core/templates/AI-SPEC.md +246 -0
- package/pp-core/templates/DEBUG.md +169 -0
- package/pp-core/templates/README.md +77 -0
- package/pp-core/templates/SECURITY.md +61 -0
- package/pp-core/templates/UAT.md +265 -0
- package/pp-core/templates/UI-SPEC.md +100 -0
- package/pp-core/templates/VALIDATION.md +76 -0
- package/pp-core/templates/claude-md.md +145 -0
- package/pp-core/templates/codebase/architecture.md +255 -0
- package/pp-core/templates/codebase/concerns.md +310 -0
- package/pp-core/templates/codebase/conventions.md +307 -0
- package/pp-core/templates/codebase/integrations.md +280 -0
- package/pp-core/templates/codebase/stack.md +186 -0
- package/pp-core/templates/codebase/structure.md +285 -0
- package/pp-core/templates/codebase/testing.md +480 -0
- package/pp-core/templates/config.json +62 -0
- package/pp-core/templates/context.md +352 -0
- package/pp-core/templates/continue-here.md +78 -0
- package/pp-core/templates/copilot-instructions.md +7 -0
- package/pp-core/templates/debug-subagent-prompt.md +91 -0
- package/pp-core/templates/dev-preferences.md +21 -0
- package/pp-core/templates/discovery.md +146 -0
- package/pp-core/templates/discussion-log.md +63 -0
- package/pp-core/templates/milestone-archive.md +123 -0
- package/pp-core/templates/milestone.md +115 -0
- package/pp-core/templates/phase-prompt.md +610 -0
- package/pp-core/templates/planner-subagent-prompt.md +117 -0
- package/pp-core/templates/project.md +203 -0
- package/pp-core/templates/requirements.md +231 -0
- package/pp-core/templates/research-project/ARCHITECTURE.md +204 -0
- package/pp-core/templates/research-project/FEATURES.md +147 -0
- package/pp-core/templates/research-project/PITFALLS.md +200 -0
- package/pp-core/templates/research-project/STACK.md +120 -0
- package/pp-core/templates/research-project/SUMMARY.md +170 -0
- package/pp-core/templates/research.md +592 -0
- package/pp-core/templates/retrospective.md +54 -0
- package/pp-core/templates/roadmap.md +202 -0
- package/pp-core/templates/spec.md +333 -0
- package/pp-core/templates/state.md +195 -0
- package/pp-core/templates/summary-complex.md +60 -0
- package/pp-core/templates/summary-minimal.md +42 -0
- package/pp-core/templates/summary-standard.md +49 -0
- package/pp-core/templates/summary.md +249 -0
- package/pp-core/templates/user-profile.md +146 -0
- package/pp-core/templates/user-setup.md +311 -0
- package/pp-core/templates/verification-report.md +335 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -0
- package/pp-core/workflows/add-backlog.md +91 -0
- package/pp-core/workflows/add-phase.md +113 -0
- package/pp-core/workflows/add-tests.md +355 -0
- package/pp-core/workflows/add-todo.md +161 -0
- package/pp-core/workflows/ai-integration-phase.md +295 -0
- package/pp-core/workflows/analyze-dependencies.md +96 -0
- package/pp-core/workflows/audit-fix.md +178 -0
- package/pp-core/workflows/audit-milestone.md +362 -0
- package/pp-core/workflows/audit-uat.md +110 -0
- package/pp-core/workflows/autonomous.md +886 -0
- package/pp-core/workflows/check-todos.md +180 -0
- package/pp-core/workflows/cleanup.md +195 -0
- package/pp-core/workflows/code-review-fix.md +506 -0
- package/pp-core/workflows/code-review.md +696 -0
- package/pp-core/workflows/complete-milestone.md +857 -0
- package/pp-core/workflows/debug.md +237 -0
- package/pp-core/workflows/diagnose-issues.md +245 -0
- package/pp-core/workflows/discovery-phase.md +291 -0
- package/pp-core/workflows/discovery-sprint.md +1546 -0
- package/pp-core/workflows/discuss-phase/modes/advisor.md +176 -0
- package/pp-core/workflows/discuss-phase/modes/all.md +28 -0
- package/pp-core/workflows/discuss-phase/modes/analyze.md +44 -0
- package/pp-core/workflows/discuss-phase/modes/auto.md +57 -0
- package/pp-core/workflows/discuss-phase/modes/batch.md +52 -0
- package/pp-core/workflows/discuss-phase/modes/chain.md +98 -0
- package/pp-core/workflows/discuss-phase/modes/default.md +141 -0
- package/pp-core/workflows/discuss-phase/modes/power.md +44 -0
- package/pp-core/workflows/discuss-phase/modes/text.md +55 -0
- package/pp-core/workflows/discuss-phase/templates/checkpoint.json +18 -0
- package/pp-core/workflows/discuss-phase/templates/context.md +136 -0
- package/pp-core/workflows/discuss-phase/templates/discussion-log.md +50 -0
- package/pp-core/workflows/discuss-phase-assumptions.md +675 -0
- package/pp-core/workflows/discuss-phase-power.md +291 -0
- package/pp-core/workflows/discuss-phase.md +520 -0
- package/pp-core/workflows/do.md +111 -0
- package/pp-core/workflows/docs-update.md +1168 -0
- package/pp-core/workflows/edit-phase.md +295 -0
- package/pp-core/workflows/eval-review.md +159 -0
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +95 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +117 -0
- package/pp-core/workflows/execute-phase/steps/worktree-recovery-policy.md +9 -0
- package/pp-core/workflows/execute-phase.md +1707 -0
- package/pp-core/workflows/execute-plan.md +541 -0
- package/pp-core/workflows/explore.md +146 -0
- package/pp-core/workflows/extract-learnings.md +243 -0
- package/pp-core/workflows/fast.md +124 -0
- package/pp-core/workflows/forensics.md +279 -0
- package/pp-core/workflows/graduation.md +196 -0
- package/pp-core/workflows/health.md +224 -0
- package/pp-core/workflows/help/modes/brief.md +22 -0
- package/pp-core/workflows/help/modes/default.md +50 -0
- package/pp-core/workflows/help/modes/full.md +791 -0
- package/pp-core/workflows/help/modes/topic.md +74 -0
- package/pp-core/workflows/help.md +24 -0
- package/pp-core/workflows/import.md +256 -0
- package/pp-core/workflows/inbox.md +387 -0
- package/pp-core/workflows/ingest-docs.md +340 -0
- package/pp-core/workflows/insert-phase.md +152 -0
- package/pp-core/workflows/list-phase-assumptions.md +178 -0
- package/pp-core/workflows/list-workspaces.md +57 -0
- package/pp-core/workflows/manager.md +435 -0
- package/pp-core/workflows/map-codebase.md +444 -0
- package/pp-core/workflows/milestone-summary.md +224 -0
- package/pp-core/workflows/mvp-phase.md +222 -0
- package/pp-core/workflows/new-milestone.md +643 -0
- package/pp-core/workflows/new-project.md +1563 -0
- package/pp-core/workflows/new-workspace.md +240 -0
- package/pp-core/workflows/next.md +347 -0
- package/pp-core/workflows/node-repair.md +92 -0
- package/pp-core/workflows/note.md +158 -0
- package/pp-core/workflows/pause-work.md +250 -0
- package/pp-core/workflows/plan-milestone-gaps.md +281 -0
- package/pp-core/workflows/plan-phase.md +1755 -0
- package/pp-core/workflows/plan-review-convergence.md +369 -0
- package/pp-core/workflows/plant-seed.md +230 -0
- package/pp-core/workflows/pr-branch.md +159 -0
- package/pp-core/workflows/profile-user.md +455 -0
- package/pp-core/workflows/progress.md +756 -0
- package/pp-core/workflows/quick.md +1045 -0
- package/pp-core/workflows/reapply-patches.md +443 -0
- package/pp-core/workflows/remove-phase.md +156 -0
- package/pp-core/workflows/remove-workspace.md +108 -0
- package/pp-core/workflows/resume-project.md +348 -0
- package/pp-core/workflows/review.md +765 -0
- package/pp-core/workflows/scan.md +107 -0
- package/pp-core/workflows/secure-phase.md +184 -0
- package/pp-core/workflows/session-report.md +146 -0
- package/pp-core/workflows/settings-advanced.md +816 -0
- package/pp-core/workflows/settings-integrations.md +312 -0
- package/pp-core/workflows/settings.md +592 -0
- package/pp-core/workflows/ship.md +465 -0
- package/pp-core/workflows/sketch-wrap-up.md +286 -0
- package/pp-core/workflows/sketch.md +361 -0
- package/pp-core/workflows/spec-phase.md +495 -0
- package/pp-core/workflows/spike-wrap-up.md +307 -0
- package/pp-core/workflows/spike.md +453 -0
- package/pp-core/workflows/stats.md +80 -0
- package/pp-core/workflows/sync-skills.md +182 -0
- package/pp-core/workflows/thread.md +222 -0
- package/pp-core/workflows/transition.md +694 -0
- package/pp-core/workflows/ui-phase.md +328 -0
- package/pp-core/workflows/ui-review.md +193 -0
- package/pp-core/workflows/ultraplan-phase.md +199 -0
- package/pp-core/workflows/undo.md +314 -0
- package/pp-core/workflows/update.md +501 -0
- package/pp-core/workflows/validate-phase.md +183 -0
- package/pp-core/workflows/verify-phase.md +569 -0
- package/pp-core/workflows/verify-work.md +804 -0
- package/scripts/affected-tests-lib.cjs +554 -0
- package/scripts/audit-workflow-script-paths.cjs +73 -0
- package/scripts/base64-scan.sh +351 -0
- package/scripts/build-hooks.js +254 -0
- package/scripts/changeset/README.md +129 -0
- package/scripts/changeset/cli.cjs +597 -0
- package/scripts/changeset/github-release-notes.cjs +199 -0
- package/scripts/changeset/lint.cjs +147 -0
- package/scripts/changeset/new.cjs +151 -0
- package/scripts/changeset/parse.cjs +114 -0
- package/scripts/changeset/render.cjs +34 -0
- package/scripts/changeset/serialize.cjs +130 -0
- package/scripts/check-alias-drift.cjs +114 -0
- package/scripts/check-env.cjs +312 -0
- package/scripts/check-npm-integrity.cjs +215 -0
- package/scripts/ci-guard-runner.cjs +22 -0
- package/scripts/ci-prepare-test-scope.cjs +93 -0
- package/scripts/ci-rebase-check.cjs +86 -0
- package/scripts/ci-test-scope.cjs +441 -0
- package/scripts/command-contract-helpers.cjs +64 -0
- package/scripts/diff-touches-shipped-paths.cjs +149 -0
- package/scripts/fix-slash-commands.cjs +161 -0
- package/scripts/gen-capability-registry.cjs +2646 -0
- package/scripts/gen-commands-reference.cjs +139 -0
- package/scripts/gen-inventory-manifest.cjs +114 -0
- package/scripts/gen-loop-host-contract.cjs +526 -0
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/generate-package-identity.cjs +134 -0
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/issue-version-gate.cjs +140 -0
- package/scripts/lib/allowlist-ratchet.cjs +236 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +328 -0
- package/scripts/lint-allow-test-rule-refs.cjs +162 -0
- package/scripts/lint-command-contract.cjs +118 -0
- package/scripts/lint-descriptions.cjs +87 -0
- package/scripts/lint-docs-required.cjs +222 -0
- package/scripts/lint-legacy-dir-name.cjs +166 -0
- package/scripts/lint-package-identity-drift.cjs +141 -0
- package/scripts/lint-pr-check-project-dir.cjs +99 -0
- package/scripts/lint-regression-test-names.allowlist.json +269 -0
- package/scripts/lint-regression-test-names.cjs +117 -0
- package/scripts/lint-shell-command-projection-drift.cjs +62 -0
- package/scripts/lint-skill-deps.cjs +185 -0
- package/scripts/lint-test-file-count.allowlist.json +181 -0
- package/scripts/lint-test-file-count.cjs +246 -0
- package/scripts/lint-windows-test-portability.cjs +178 -0
- package/scripts/mutation-matrix.cjs +323 -0
- package/scripts/pr-target-policy.cjs +63 -0
- package/scripts/pr-template-policy.cjs +268 -0
- package/scripts/prompt-injection-scan.sh +207 -0
- package/scripts/registry-smoke.cjs +337 -0
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/release-notes/format-github-release-notes.cjs +261 -0
- package/scripts/release-tarball-smoke.cjs +763 -0
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +7 -0
- package/scripts/run-tests.cjs +589 -0
- package/scripts/secret-scan-lint.sh +231 -0
- package/scripts/secret-scan.sh +358 -0
- package/scripts/setup-branch-protection.sh +234 -0
- package/scripts/strip-prose-atrefs.cjs +106 -0
- package/scripts/sync-manifest-versions.cjs +119 -0
- package/scripts/sync-next-version.cjs +133 -0
- package/scripts/sync-rulesets.sh +34 -0
- package/scripts/sync-runtime-launcher.cjs +425 -0
- package/scripts/test-failure-reasons.cjs +34 -0
- package/scripts/update-size-baseline.cjs +68 -0
- package/scripts/verify-npm-publish.cjs +240 -0
- package/scripts/verify-opengsd-sweep.cjs +288 -0
- package/scripts/verify-phase-02-rename.sh +258 -0
- package/scripts/verify-phase-03-rename.sh +376 -0
- package/scripts/workflow-policy.cjs +483 -0
- package/scripts/workflow-size.cjs +90 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Roadmap — Roadmap parsing and update operations
|
|
4
|
+
*
|
|
5
|
+
* ADR-457 build-at-publish: the hand-written bin/lib/roadmap.cjs collapsed
|
|
6
|
+
* to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour
|
|
7
|
+
* from the prior hand-written .cjs; only strict types are added.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
+
const ioMod = require("./io.cjs");
|
|
17
|
+
const { output, error } = ioMod;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
20
|
+
const { escapeRegex, normalizePhaseName, phaseMarkdownRegexSource, phaseMarkdownRegexSourceExact, phaseTokenMatches } = phaseIdMod;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
23
|
+
const { findPhaseInternal } = phaseLocatorMod;
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
|
+
const roadmapParserModule = require("./roadmap-parser.cjs");
|
|
26
|
+
const { stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone } = roadmapParserModule;
|
|
27
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
const planningWorkspace = require("./planning-workspace.cjs");
|
|
30
|
+
const { planningPaths, withPlanningLock, findContextMdIn } = planningWorkspace;
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
|
+
const scanPhasePlans = require("./plan-scan.cjs");
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const frontmatter = require("./frontmatter.cjs");
|
|
35
|
+
const { extractFrontmatter, parseMustHavesBlock } = frontmatter;
|
|
36
|
+
// ─── coerceTruthToString ──────────────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* Coerce an arbitrary YAML scalar/object into a string for cross-cutting
|
|
39
|
+
* truth aggregation. Handles:
|
|
40
|
+
* - strings (passthrough)
|
|
41
|
+
* - numbers / booleans (String() coercion — issue #2770: bare YAML ints
|
|
42
|
+
* like `- 3` must be surfaced, not silently skipped)
|
|
43
|
+
* - kv-shaped objects from parseMustHavesBlock continuation kv (issue
|
|
44
|
+
* #2757) — extract the first meaningful string field
|
|
45
|
+
*
|
|
46
|
+
* Returns the empty string when no usable text can be derived; callers should
|
|
47
|
+
* skip empty results.
|
|
48
|
+
*/
|
|
49
|
+
function coerceTruthToString(t) {
|
|
50
|
+
if (t === null || t === undefined)
|
|
51
|
+
return '';
|
|
52
|
+
if (typeof t === 'string')
|
|
53
|
+
return t;
|
|
54
|
+
if (typeof t === 'number' || typeof t === 'boolean' || typeof t === 'bigint') {
|
|
55
|
+
return String(t);
|
|
56
|
+
}
|
|
57
|
+
if (typeof t === 'object') {
|
|
58
|
+
// Prefer common title-bearing keys produced by parseMustHavesBlock
|
|
59
|
+
for (const k of ['title', 'text', 'name', 'rule', 'path', 'provides']) {
|
|
60
|
+
const v = t[k];
|
|
61
|
+
if (typeof v === 'string' && v.trim())
|
|
62
|
+
return v;
|
|
63
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
64
|
+
return String(v);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
// ─── countPhasePlansAndSummaries ──────────────────────────────────────────────
|
|
70
|
+
function countPhasePlansAndSummaries(phaseDir) {
|
|
71
|
+
const { planCount, summaryCount } = scanPhasePlans(phaseDir);
|
|
72
|
+
// hasContext and hasResearch are not plan-scan concerns — read the directory
|
|
73
|
+
// once and share the listing for all non-plan metadata that cmdRoadmapAnalyze needs.
|
|
74
|
+
let phaseFiles = [];
|
|
75
|
+
try {
|
|
76
|
+
phaseFiles = node_fs_1.default.readdirSync(phaseDir);
|
|
77
|
+
}
|
|
78
|
+
catch { /* empty */ }
|
|
79
|
+
return {
|
|
80
|
+
planCount,
|
|
81
|
+
summaryCount,
|
|
82
|
+
hasContext: findContextMdIn(phaseFiles) !== null,
|
|
83
|
+
hasResearch: phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// `phaseMarkdownRegexSource` lives in phase-id.cjs (#3537) and is imported above.
|
|
87
|
+
// ─── searchPhaseInContent ─────────────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Search for a phase header (and its section) within the given content string.
|
|
90
|
+
* Returns a result object if found (either a full match or a malformed_roadmap
|
|
91
|
+
* checklist-only match), or null if the phase is not present at all.
|
|
92
|
+
*/
|
|
93
|
+
function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
|
94
|
+
// Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
|
|
95
|
+
const phasePattern = new RegExp(`#{2,4}\\s*(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
|
|
96
|
+
const headerMatch = content.match(phasePattern);
|
|
97
|
+
if (!headerMatch) {
|
|
98
|
+
// Fallback: check if phase exists in summary list but missing detail section
|
|
99
|
+
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`, 'i');
|
|
100
|
+
const checklistMatch = content.match(checklistPattern);
|
|
101
|
+
if (checklistMatch) {
|
|
102
|
+
return {
|
|
103
|
+
found: false,
|
|
104
|
+
phase_number: phaseNum,
|
|
105
|
+
phase_name: checklistMatch[1].trim(),
|
|
106
|
+
error: 'malformed_roadmap',
|
|
107
|
+
message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const phaseName = headerMatch[1].trim();
|
|
113
|
+
const headerIndex = headerMatch.index;
|
|
114
|
+
// Find the end of this section (next ## or ### phase header, or end of file).
|
|
115
|
+
// Also matches bracket-prefixed headings like ### [GSD] Phase 2-01:.
|
|
116
|
+
const restOfContent = content.slice(headerIndex);
|
|
117
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+[\w][\w.-]*/i);
|
|
118
|
+
const sectionEnd = nextHeaderMatch
|
|
119
|
+
? headerIndex + nextHeaderMatch.index
|
|
120
|
+
: content.length;
|
|
121
|
+
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
122
|
+
// Extract goal if present (supports both **Goal:** and **Goal**: formats)
|
|
123
|
+
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
124
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
125
|
+
// Mode: vertical-MVP slice mode flag. Lowercased + trimmed for canonical
|
|
126
|
+
// comparison; unrecognized values are preserved verbatim for forward-compat.
|
|
127
|
+
const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
128
|
+
const mode = modeMatch ? modeMatch[1].trim().toLowerCase() : null;
|
|
129
|
+
// Extract success criteria as structured array
|
|
130
|
+
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
|
131
|
+
const success_criteria = criteriaMatch
|
|
132
|
+
? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
|
|
133
|
+
: [];
|
|
134
|
+
return {
|
|
135
|
+
found: true,
|
|
136
|
+
phase_number: phaseNum,
|
|
137
|
+
phase_name: phaseName,
|
|
138
|
+
goal,
|
|
139
|
+
mode,
|
|
140
|
+
success_criteria,
|
|
141
|
+
section,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// ─── getRoadmapPhaseWithFallback ──────────────────────────────────────────────
|
|
145
|
+
/**
|
|
146
|
+
* Two-pass phase lookup that mirrors cmdRoadmapGetPhase's resolution strategy.
|
|
147
|
+
*
|
|
148
|
+
* Pass 1: current-milestone slice (extractCurrentMilestone).
|
|
149
|
+
* Pass 2: full roadmap content (stripShippedMilestones) — covers cross-milestone
|
|
150
|
+
* and older frontend phases that are no longer in the current milestone slice.
|
|
151
|
+
*
|
|
152
|
+
* Returns the phase section string if found, null if ROADMAP.md is missing,
|
|
153
|
+
* or throws if ROADMAP.md read fails.
|
|
154
|
+
*
|
|
155
|
+
* Used by check-command-router (computeUiPlanGate) so ui-plan-gate uses the SAME
|
|
156
|
+
* phase resolution as `roadmap.get-phase` — not a milestone-only subset.
|
|
157
|
+
*/
|
|
158
|
+
function getRoadmapPhaseWithFallback(cwd, phaseNum) {
|
|
159
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
160
|
+
if (!node_fs_1.default.existsSync(roadmapPath))
|
|
161
|
+
return null;
|
|
162
|
+
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
163
|
+
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
164
|
+
const fullContent = stripShippedMilestones(rawContent);
|
|
165
|
+
const exactSource = phaseMarkdownRegexSourceExact(phaseNum);
|
|
166
|
+
if (exactSource) {
|
|
167
|
+
const exactMilestone = searchPhaseInContent(milestoneContent, exactSource, phaseNum);
|
|
168
|
+
if (exactMilestone && !exactMilestone.error)
|
|
169
|
+
return exactMilestone.section ?? null;
|
|
170
|
+
const exactFull = searchPhaseInContent(fullContent, exactSource, phaseNum);
|
|
171
|
+
if (exactFull && !exactFull.error)
|
|
172
|
+
return exactFull.section ?? null;
|
|
173
|
+
}
|
|
174
|
+
const escapedPhase = phaseMarkdownRegexSource(phaseNum);
|
|
175
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, escapedPhase, phaseNum);
|
|
176
|
+
const result = (milestoneResult && !milestoneResult.error)
|
|
177
|
+
? milestoneResult
|
|
178
|
+
: searchPhaseInContent(fullContent, escapedPhase, phaseNum) || milestoneResult;
|
|
179
|
+
if (!result || result.error)
|
|
180
|
+
return null;
|
|
181
|
+
return result.section ?? null;
|
|
182
|
+
}
|
|
183
|
+
// ─── cmdRoadmapGetPhase ───────────────────────────────────────────────────────
|
|
184
|
+
function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
185
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
186
|
+
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
187
|
+
output({ found: false, error: 'ROADMAP.md not found' }, raw, '');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
192
|
+
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
193
|
+
// #3599 two-pass: when the caller passes a project-code-prefixed ID like
|
|
194
|
+
// `PROJ-42`, try the exact-prefixed heading first (`### Phase PROJ-42:`).
|
|
195
|
+
// If no match, fall back to the #3537 padding-tolerant numeric form so
|
|
196
|
+
// a `CK-01` query still resolves to `### Phase 1:`. Doing this at the
|
|
197
|
+
// call site (instead of inside phaseMarkdownRegexSource) avoids the
|
|
198
|
+
// alternation-order ambiguity where a bare `### Phase 42:` heading in
|
|
199
|
+
// the same document would intercept the match for a `PROJ-42` query.
|
|
200
|
+
const fullContent = stripShippedMilestones(rawContent);
|
|
201
|
+
const exactSource = phaseMarkdownRegexSourceExact(phaseNum);
|
|
202
|
+
if (exactSource) {
|
|
203
|
+
const exactMilestone = searchPhaseInContent(milestoneContent, exactSource, phaseNum);
|
|
204
|
+
if (exactMilestone && !exactMilestone.error) {
|
|
205
|
+
output(exactMilestone, raw, exactMilestone.section);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const exactFull = searchPhaseInContent(fullContent, exactSource, phaseNum);
|
|
209
|
+
if (exactFull && !exactFull.error) {
|
|
210
|
+
output(exactFull, raw, exactFull.section);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// #3537: padding-tolerant fragment so callers passing `02.7` still match
|
|
215
|
+
// un-padded ROADMAP prose (`### Phase 2.7:`).
|
|
216
|
+
const escapedPhase = phaseMarkdownRegexSource(phaseNum);
|
|
217
|
+
// Search the current milestone slice first, then fall back to full roadmap.
|
|
218
|
+
// A malformed_roadmap result (checklist-only) from the milestone should not
|
|
219
|
+
// block finding a full header match in the wider roadmap content.
|
|
220
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, escapedPhase, phaseNum);
|
|
221
|
+
const result = (milestoneResult && !milestoneResult.error)
|
|
222
|
+
? milestoneResult
|
|
223
|
+
: searchPhaseInContent(fullContent, escapedPhase, phaseNum) || milestoneResult;
|
|
224
|
+
if (!result) {
|
|
225
|
+
output({ found: false, phase_number: phaseNum }, raw, '');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (result.error) {
|
|
229
|
+
output(result, raw, '');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
output(result, raw, result.section);
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
error('Failed to read ROADMAP.md: ' + e.message);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// ─── cmdRoadmapAnalyze ────────────────────────────────────────────────────────
|
|
239
|
+
function cmdRoadmapAnalyze(cwd, raw) {
|
|
240
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
241
|
+
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
242
|
+
output({ error: 'ROADMAP.md not found', milestones: [], phases: [], current_phase: null }, raw, undefined);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
246
|
+
const content = extractCurrentMilestone(rawContent, cwd);
|
|
247
|
+
const phasesDir = planningPaths(cwd).phases;
|
|
248
|
+
// Extract all phase headings: ## Phase N: Name or ### Phase N: Name
|
|
249
|
+
const phasePattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+(\d+[A-Z]?(?:[.-]\d+)*)\s*:\s*([^\n]+)/gi;
|
|
250
|
+
const phases = [];
|
|
251
|
+
let match;
|
|
252
|
+
// Build phase directory lookup once (O(1) readdir instead of O(N) per phase)
|
|
253
|
+
const _phaseDirNames = (() => {
|
|
254
|
+
try {
|
|
255
|
+
return node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true })
|
|
256
|
+
.filter(e => e.isDirectory())
|
|
257
|
+
.map(e => e.name);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
})();
|
|
263
|
+
while ((match = phasePattern.exec(content)) !== null) {
|
|
264
|
+
const phaseNum = match[1];
|
|
265
|
+
const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim();
|
|
266
|
+
// Extract goal from the section
|
|
267
|
+
const sectionStart = match.index;
|
|
268
|
+
const restOfContent = content.slice(sectionStart);
|
|
269
|
+
// #3691: `\d` → `\d[\d.]*` so decimal phase headings (e.g. `### Phase 02.3:`) are
|
|
270
|
+
// recognised as section boundaries.
|
|
271
|
+
const nextHeader = restOfContent.match(/\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+\d[\d.-]*/i);
|
|
272
|
+
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
273
|
+
const section = content.slice(sectionStart, sectionEnd);
|
|
274
|
+
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
275
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
276
|
+
const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
277
|
+
const mode = modeMatch ? modeMatch[1].trim().toLowerCase() : null;
|
|
278
|
+
const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
279
|
+
const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
|
|
280
|
+
// Check completion on disk
|
|
281
|
+
const normalized = normalizePhaseName(phaseNum);
|
|
282
|
+
let diskStatus = 'no_directory';
|
|
283
|
+
let planCount = 0;
|
|
284
|
+
let summaryCount = 0;
|
|
285
|
+
let hasContext = false;
|
|
286
|
+
let hasResearch = false;
|
|
287
|
+
try {
|
|
288
|
+
const dirMatch = _phaseDirNames.find(d => phaseTokenMatches(d, normalized));
|
|
289
|
+
if (dirMatch) {
|
|
290
|
+
const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch));
|
|
291
|
+
planCount = counts.planCount;
|
|
292
|
+
summaryCount = counts.summaryCount;
|
|
293
|
+
hasContext = counts.hasContext;
|
|
294
|
+
hasResearch = counts.hasResearch;
|
|
295
|
+
if (summaryCount >= planCount && planCount > 0)
|
|
296
|
+
diskStatus = 'complete';
|
|
297
|
+
else if (summaryCount > 0)
|
|
298
|
+
diskStatus = 'partial';
|
|
299
|
+
else if (planCount > 0)
|
|
300
|
+
diskStatus = 'planned';
|
|
301
|
+
else if (hasResearch)
|
|
302
|
+
diskStatus = 'researched';
|
|
303
|
+
else if (hasContext)
|
|
304
|
+
diskStatus = 'discussed';
|
|
305
|
+
else
|
|
306
|
+
diskStatus = 'empty';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch { /* intentionally empty */ }
|
|
310
|
+
// Check ROADMAP checkbox status.
|
|
311
|
+
// #3537: padding-tolerant fragment — the heading discovered above may use
|
|
312
|
+
// a different padding than the summary-bullet checkbox below it (mixed
|
|
313
|
+
// padding inside one ROADMAP is legal and seen in real projects).
|
|
314
|
+
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseMarkdownRegexSource(phaseNum)}[:\\s]`, 'i');
|
|
315
|
+
const checkboxMatch = content.match(checkboxPattern);
|
|
316
|
+
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
|
|
317
|
+
// If roadmap marks phase complete, trust that over disk file structure.
|
|
318
|
+
// Phases completed before GSD tracking (or via external tools) may lack
|
|
319
|
+
// the standard PLAN/SUMMARY pairs but are still done.
|
|
320
|
+
if (roadmapComplete && diskStatus !== 'complete') {
|
|
321
|
+
diskStatus = 'complete';
|
|
322
|
+
}
|
|
323
|
+
phases.push({
|
|
324
|
+
number: phaseNum,
|
|
325
|
+
name: phaseName,
|
|
326
|
+
goal,
|
|
327
|
+
mode,
|
|
328
|
+
depends_on,
|
|
329
|
+
plan_count: planCount,
|
|
330
|
+
summary_count: summaryCount,
|
|
331
|
+
has_context: hasContext,
|
|
332
|
+
has_research: hasResearch,
|
|
333
|
+
disk_status: diskStatus,
|
|
334
|
+
roadmap_complete: roadmapComplete,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
// Extract milestone info
|
|
338
|
+
const milestones = [];
|
|
339
|
+
const milestonePattern = /##\s*(.*v(\d+(?:\.\d+)+)[^(\n]*)/gi;
|
|
340
|
+
let mMatch;
|
|
341
|
+
while ((mMatch = milestonePattern.exec(content)) !== null) {
|
|
342
|
+
milestones.push({
|
|
343
|
+
heading: mMatch[1].trim(),
|
|
344
|
+
version: 'v' + mMatch[2],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// Find current and next phase
|
|
348
|
+
const currentPhase = phases.find(p => p.disk_status === 'planned' || p.disk_status === 'partial') || null;
|
|
349
|
+
const nextPhase = phases.find(p => p.disk_status === 'empty' || p.disk_status === 'no_directory' || p.disk_status === 'discussed' || p.disk_status === 'researched') || null;
|
|
350
|
+
// Aggregated stats
|
|
351
|
+
const totalPlans = phases.reduce((sum, p) => sum + p.plan_count, 0);
|
|
352
|
+
const totalSummaries = phases.reduce((sum, p) => sum + p.summary_count, 0);
|
|
353
|
+
const completedPhases = phases.filter(p => p.disk_status === 'complete').length;
|
|
354
|
+
// Detect phases in summary list without detail sections (malformed ROADMAP)
|
|
355
|
+
const checklistPattern = /-\s*\[[ x]\]\s*\*\*Phase\s+(\d+[A-Z]?(?:\.\d+)*)/gi;
|
|
356
|
+
const checklistPhases = new Set();
|
|
357
|
+
let checklistMatch;
|
|
358
|
+
while ((checklistMatch = checklistPattern.exec(content)) !== null) {
|
|
359
|
+
checklistPhases.add(checklistMatch[1]);
|
|
360
|
+
}
|
|
361
|
+
const detailPhases = new Set(phases.map(p => p.number));
|
|
362
|
+
const missingDetails = [...checklistPhases].filter(p => !detailPhases.has(p));
|
|
363
|
+
const result = {
|
|
364
|
+
milestones,
|
|
365
|
+
phases,
|
|
366
|
+
phase_count: phases.length,
|
|
367
|
+
completed_phases: completedPhases,
|
|
368
|
+
total_plans: totalPlans,
|
|
369
|
+
total_summaries: totalSummaries,
|
|
370
|
+
progress_percent: totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0,
|
|
371
|
+
current_phase: currentPhase ? currentPhase.number : null,
|
|
372
|
+
next_phase: nextPhase ? nextPhase.number : null,
|
|
373
|
+
missing_phase_details: missingDetails.length > 0 ? missingDetails : null,
|
|
374
|
+
};
|
|
375
|
+
output(result, raw, undefined);
|
|
376
|
+
}
|
|
377
|
+
// ─── cmdRoadmapUpdatePlanProgress ─────────────────────────────────────────────
|
|
378
|
+
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
379
|
+
if (!phaseNum) {
|
|
380
|
+
error('phase number required for roadmap update-plan-progress');
|
|
381
|
+
}
|
|
382
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
383
|
+
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
384
|
+
if (!phaseInfo) {
|
|
385
|
+
error(`Phase ${phaseNum} not found`);
|
|
386
|
+
}
|
|
387
|
+
const planCount = phaseInfo.plans.length;
|
|
388
|
+
const summaryCount = phaseInfo.summaries.length;
|
|
389
|
+
if (planCount === 0) {
|
|
390
|
+
output({ updated: false, reason: 'No plans found', plan_count: 0, summary_count: 0 }, raw, 'no plans');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const isComplete = summaryCount >= planCount;
|
|
394
|
+
const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
|
|
395
|
+
const today = clock_cjs_1.realClock.today();
|
|
396
|
+
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
397
|
+
output({ updated: false, reason: 'ROADMAP.md not found', plan_count: planCount, summary_count: summaryCount }, raw, 'no roadmap');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
// Wrap entire read-modify-write in lock to prevent concurrent corruption
|
|
401
|
+
withPlanningLock(cwd, () => {
|
|
402
|
+
let roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
403
|
+
const phasePattern = phaseMarkdownRegexSource(phaseNum);
|
|
404
|
+
// Progress table row: update Plans/Status/Date columns (handles 4 or 5 column tables)
|
|
405
|
+
const tableRowPattern = new RegExp(`^(\\|\\s*${phasePattern}\\.?\\s[^|]*(?:\\|[^\\n]*))$`, 'im');
|
|
406
|
+
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
|
|
407
|
+
const cells = fullRow.split('|').slice(1, -1); // drop leading/trailing empty from split
|
|
408
|
+
const dateShape = /^\d{4}-\d{2}-\d{2}$/;
|
|
409
|
+
if (cells.length === 5) {
|
|
410
|
+
// 5-col: Phase | Milestone | Plans | Status | Completed
|
|
411
|
+
cells[2] = ` ${summaryCount}/${planCount} `;
|
|
412
|
+
cells[3] = ` ${status.padEnd(11)}`;
|
|
413
|
+
// Preserve only a valid ISO date (#1161: idempotent; self-heal garbage)
|
|
414
|
+
const existingDate5 = cells[4].trim();
|
|
415
|
+
cells[4] = isComplete
|
|
416
|
+
? (dateShape.test(existingDate5) ? cells[4] : ` ${today} `)
|
|
417
|
+
: ' ';
|
|
418
|
+
}
|
|
419
|
+
else if (cells.length === 4) {
|
|
420
|
+
// 4-col: Phase | Plans | Status | Completed
|
|
421
|
+
cells[1] = ` ${summaryCount}/${planCount} `;
|
|
422
|
+
cells[2] = ` ${status.padEnd(11)}`;
|
|
423
|
+
// Preserve only a valid ISO date (#1161: idempotent; self-heal garbage)
|
|
424
|
+
const existingDate4 = cells[3].trim();
|
|
425
|
+
cells[3] = isComplete
|
|
426
|
+
? (dateShape.test(existingDate4) ? cells[3] : ` ${today} `)
|
|
427
|
+
: ' ';
|
|
428
|
+
}
|
|
429
|
+
return '|' + cells.join('|') + '|';
|
|
430
|
+
});
|
|
431
|
+
// Update plan count in phase detail section.
|
|
432
|
+
// Three recognised forms (all tolerated; canonical template uses the first):
|
|
433
|
+
// `**Plans**: N plans` — bold word + outer colon (gsd-core/templates/roadmap.md)
|
|
434
|
+
// `**Plans:** N plans` — bold "Plans:" (colon inside bold)
|
|
435
|
+
// `Plans: N plans` — plain text header
|
|
436
|
+
const planCountPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*|(?:^|\\n)Plans:)\\s*)[^\\n]+`, 'i');
|
|
437
|
+
const planCountText = isComplete
|
|
438
|
+
? `${summaryCount}/${planCount} plans complete`
|
|
439
|
+
: `${summaryCount}/${planCount} plans executed`;
|
|
440
|
+
roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, `$1${planCountText}`);
|
|
441
|
+
// If complete: check checkbox
|
|
442
|
+
if (isComplete) {
|
|
443
|
+
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phasePattern}[:\\s][^\\n]*)`, 'i');
|
|
444
|
+
roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
|
|
445
|
+
}
|
|
446
|
+
// Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md", "- [ ] 50-01:", or "- [ ] **50-01**")
|
|
447
|
+
for (const summaryFile of phaseInfo.summaries) {
|
|
448
|
+
const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
449
|
+
if (!planId)
|
|
450
|
+
continue;
|
|
451
|
+
const planEscaped = escapeRegex(planId);
|
|
452
|
+
const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
|
|
453
|
+
roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
|
|
454
|
+
}
|
|
455
|
+
// Compute the active (post-</details>) region offset ONCE. Both the
|
|
456
|
+
// missing-plan DETECTION and the row INSERTION must use the same active
|
|
457
|
+
// region string so that a plan row that exists only in an archived <details>
|
|
458
|
+
// block is not counted as "already present" in the active milestone section.
|
|
459
|
+
// (Finding 1 code-review: detection was previously running against the full
|
|
460
|
+
// roadmapContent, causing archived rows to suppress active-section inserts.)
|
|
461
|
+
const lastDetailsClose = roadmapContent.lastIndexOf('</details>');
|
|
462
|
+
const activeRegion = lastDetailsClose === -1
|
|
463
|
+
? roadmapContent
|
|
464
|
+
: roadmapContent.slice(lastDetailsClose + '</details>'.length);
|
|
465
|
+
// Compute which plan files are MISSING a checkbox row in the ACTIVE region.
|
|
466
|
+
// This handles three cases:
|
|
467
|
+
// (a) Fresh template — no rows at all: all plans are missing.
|
|
468
|
+
// (b) Partial gap — some rows exist, others don't: only the absent ones.
|
|
469
|
+
// (c) All rows present — nothing to insert (idempotent).
|
|
470
|
+
//
|
|
471
|
+
// Detection is scoped to the active region so a plan that appears in an
|
|
472
|
+
// archived <details> block is still correctly detected as missing from the
|
|
473
|
+
// active milestone section.
|
|
474
|
+
const missingPlans = phaseInfo.plans.filter((planFile) => {
|
|
475
|
+
const planEscaped = escapeRegex(planFile);
|
|
476
|
+
return !new RegExp(`-\\s*\\[[x ]\\]\\s*(?:\\*\\*)?${planEscaped}`, 'i').test(activeRegion);
|
|
477
|
+
});
|
|
478
|
+
if (missingPlans.length > 0) {
|
|
479
|
+
// Insert missing plan checklist rows (#1163). We prefer to anchor to the
|
|
480
|
+
// bare `Plans:` checklist header (canonical template form) and fall back to
|
|
481
|
+
// the bold `**Plans**:`/`**Plans:**` summary line only when no bare header
|
|
482
|
+
// is present. Using two separate patterns avoids the lazy-quantifier trap
|
|
483
|
+
// where a single alternation would stop at the first matching alternative
|
|
484
|
+
// (the bold summary) before reaching the checklist header.
|
|
485
|
+
//
|
|
486
|
+
// Canonical template (gsd-core/templates/roadmap.md) uses BOTH lines:
|
|
487
|
+
// **Plans**: N plans ← summary (colon outside bold)
|
|
488
|
+
// Plans: ← checklist header (PREFERRED insertion anchor)
|
|
489
|
+
// Rows must land after `Plans:`, not between the summary and the header.
|
|
490
|
+
//
|
|
491
|
+
// Pattern A: anchor to bare `Plans:` header (preferred).
|
|
492
|
+
// Pattern B: fallback to bold summary when no bare header exists.
|
|
493
|
+
const insertRowsPatternA = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}(?=[:\\s])[\\s\\S]*?(?:^|\\n)(?:Plans:)[^\\n]*)`, 'i');
|
|
494
|
+
const insertRowsPatternB = new RegExp(`(#{2,4}\\s*Phase\\s+${phasePattern}(?=[:\\s])[\\s\\S]*?(?:\\*\\*Plans\\*\\*:|\\*\\*Plans:\\*\\*)[^\\n]*)`, 'i');
|
|
495
|
+
const sortedMissing = [...missingPlans].sort();
|
|
496
|
+
const newRows = sortedMissing.map(p => `- [ ] ${p}`).join('\n');
|
|
497
|
+
const inserter = (match) => `${match}\n${newRows}`;
|
|
498
|
+
// Scope insertion to the active (post-</details>) milestone region to
|
|
499
|
+
// prevent duplicate phase headings in archived sections from receiving rows.
|
|
500
|
+
// replaceInCurrentMilestone only accepts a string replacement, so we
|
|
501
|
+
// perform the scoped replace manually here (same strategy as that helper).
|
|
502
|
+
// Note: lastDetailsClose was computed above (shared with detection).
|
|
503
|
+
const scopedReplace = (src, pat) => src.replace(pat, inserter);
|
|
504
|
+
let withRows;
|
|
505
|
+
if (lastDetailsClose === -1) {
|
|
506
|
+
// activeRegion === roadmapContent when there are no </details> blocks.
|
|
507
|
+
const regionA = scopedReplace(activeRegion, insertRowsPatternA);
|
|
508
|
+
withRows = regionA !== activeRegion ? regionA : scopedReplace(activeRegion, insertRowsPatternB);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
const beforeDetails = roadmapContent.slice(0, lastDetailsClose + '</details>'.length);
|
|
512
|
+
const regionA = scopedReplace(activeRegion, insertRowsPatternA);
|
|
513
|
+
const afterWithRows = regionA !== activeRegion ? regionA : scopedReplace(activeRegion, insertRowsPatternB);
|
|
514
|
+
withRows = beforeDetails + afterWithRows;
|
|
515
|
+
}
|
|
516
|
+
if (withRows !== roadmapContent) {
|
|
517
|
+
roadmapContent = withRows;
|
|
518
|
+
// Mark any newly-inserted rows that already have summaries as complete
|
|
519
|
+
for (const summaryFile of phaseInfo.summaries) {
|
|
520
|
+
const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
521
|
+
if (!planId)
|
|
522
|
+
continue;
|
|
523
|
+
const planEscaped = escapeRegex(planId);
|
|
524
|
+
const planCheckboxPattern = new RegExp(`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
|
|
525
|
+
roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, roadmapContent);
|
|
530
|
+
});
|
|
531
|
+
output({
|
|
532
|
+
updated: true,
|
|
533
|
+
phase: phaseNum,
|
|
534
|
+
plan_count: planCount,
|
|
535
|
+
summary_count: summaryCount,
|
|
536
|
+
status,
|
|
537
|
+
complete: isComplete,
|
|
538
|
+
}, raw, `${summaryCount}/${planCount} ${status}`);
|
|
539
|
+
}
|
|
540
|
+
// ─── cmdRoadmapAnnotateDependencies ───────────────────────────────────────────
|
|
541
|
+
/**
|
|
542
|
+
* Annotate the ROADMAP.md plan list for a phase with wave dependency notes
|
|
543
|
+
* and a cross-cutting constraints subsection derived from PLAN frontmatter.
|
|
544
|
+
*
|
|
545
|
+
* Wave dependency notes: "Wave 2 — blocked on Wave 1 completion" inserted as
|
|
546
|
+
* bold headers before each wave group in the plan checklist.
|
|
547
|
+
*
|
|
548
|
+
* Cross-cutting constraints: must_haves.truths strings that appear in 2+ plans
|
|
549
|
+
* are surfaced in a "Cross-cutting constraints" subsection below the plan list.
|
|
550
|
+
*
|
|
551
|
+
* The operation is idempotent: if wave headers already exist in the section
|
|
552
|
+
* the function returns without modifying the file.
|
|
553
|
+
*/
|
|
554
|
+
function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
|
|
555
|
+
if (!phaseNum) {
|
|
556
|
+
error('phase number required for roadmap annotate-dependencies');
|
|
557
|
+
}
|
|
558
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
559
|
+
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
560
|
+
output({ updated: false, reason: 'ROADMAP.md not found' }, raw, 'no roadmap');
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
564
|
+
if (!phaseInfo || phaseInfo.plans.length === 0) {
|
|
565
|
+
output({ updated: false, reason: 'no plans found for phase', phase: phaseNum }, raw, 'no plans');
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
// Read each PLAN.md and extract wave + must_haves.truths
|
|
569
|
+
const planData = [];
|
|
570
|
+
for (const planFile of phaseInfo.plans) {
|
|
571
|
+
const planPath = node_path_1.default.join(node_path_1.default.resolve(cwd, phaseInfo.directory), planFile);
|
|
572
|
+
try {
|
|
573
|
+
const content = node_fs_1.default.readFileSync(planPath, 'utf-8');
|
|
574
|
+
const fm = extractFrontmatter(content);
|
|
575
|
+
const wave = parseInt(fm.wave, 10) || 1;
|
|
576
|
+
const planId = planFile.replace(/-PLAN\.md$/i, '').replace(/PLAN\.md$/i, '');
|
|
577
|
+
const truths = parseMustHavesBlock(content, 'truths') || [];
|
|
578
|
+
planData.push({ planFile, planId, wave, truths });
|
|
579
|
+
}
|
|
580
|
+
catch { /* skip unreadable plans */ }
|
|
581
|
+
}
|
|
582
|
+
if (planData.length === 0) {
|
|
583
|
+
output({ updated: false, reason: 'could not read plan frontmatter' }, raw, 'no frontmatter');
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
// Group plans by wave (sorted)
|
|
587
|
+
const waveGroups = new Map();
|
|
588
|
+
for (const p of planData) {
|
|
589
|
+
if (!waveGroups.has(p.wave))
|
|
590
|
+
waveGroups.set(p.wave, []);
|
|
591
|
+
waveGroups.get(p.wave).push(p);
|
|
592
|
+
}
|
|
593
|
+
const waves = [...waveGroups.keys()].sort((a, b) => a - b);
|
|
594
|
+
// Find cross-cutting truths: appear in 2+ plans (de-duplicated, case-insensitive).
|
|
595
|
+
//
|
|
596
|
+
// Issue #2770: must **coerce, not skip**. A previous guard
|
|
597
|
+
// `if (typeof t !== 'string') continue` silently dropped numeric scalars
|
|
598
|
+
// (YAML ints like `- 3`) and kv-shaped truths (`- title: X`), so the
|
|
599
|
+
// cross-cutting analysis lost real constraints rather than crashing on
|
|
600
|
+
// `t.trim()`. We coerce primitives via `String(t)` and extract a sensible
|
|
601
|
+
// string field from object-shaped items produced by parseMustHavesBlock's
|
|
602
|
+
// continuation-kv path (issue #2757 produces those shapes for nested keys).
|
|
603
|
+
const truthCounts = new Map();
|
|
604
|
+
for (const { truths } of planData) {
|
|
605
|
+
const seen = new Set();
|
|
606
|
+
for (const t of truths) {
|
|
607
|
+
const text = coerceTruthToString(t);
|
|
608
|
+
if (!text)
|
|
609
|
+
continue;
|
|
610
|
+
const trimmed = text.trim();
|
|
611
|
+
const key = trimmed.toLowerCase();
|
|
612
|
+
if (!key || seen.has(key))
|
|
613
|
+
continue;
|
|
614
|
+
seen.add(key);
|
|
615
|
+
if (!truthCounts.has(key))
|
|
616
|
+
truthCounts.set(key, { count: 0, text: trimmed });
|
|
617
|
+
truthCounts.get(key).count++;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const crossCuttingTruths = [...truthCounts.values()]
|
|
621
|
+
.filter(v => v.count >= 2)
|
|
622
|
+
.map(v => v.text);
|
|
623
|
+
// Patch ROADMAP.md
|
|
624
|
+
let updated = false;
|
|
625
|
+
withPlanningLock(cwd, () => {
|
|
626
|
+
const content = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
627
|
+
// Find the phase section.
|
|
628
|
+
// #3537: padding-tolerant fragment so the caller's resolved padded id
|
|
629
|
+
// matches un-padded ROADMAP headings.
|
|
630
|
+
const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
|
|
631
|
+
const phaseHeaderPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${phaseEscaped}:[^\\n]*)`, 'i');
|
|
632
|
+
const phaseMatch = content.match(phaseHeaderPattern);
|
|
633
|
+
if (!phaseMatch)
|
|
634
|
+
return;
|
|
635
|
+
const phaseStart = phaseMatch.index;
|
|
636
|
+
const restAfterHeader = content.slice(phaseStart);
|
|
637
|
+
const nextPhaseOffset = restAfterHeader.slice(1).search(/\n#{2,4}\s+Phase\s+\d/i);
|
|
638
|
+
const phaseEnd = nextPhaseOffset >= 0 ? phaseStart + 1 + nextPhaseOffset : content.length;
|
|
639
|
+
const phaseSection = content.slice(phaseStart, phaseEnd);
|
|
640
|
+
// Idempotency: skip if annotation markers already present
|
|
641
|
+
if (/\*\*Wave\s+\d+/i.test(phaseSection) ||
|
|
642
|
+
/\*\*Cross-cutting constraints:\*\*/i.test(phaseSection))
|
|
643
|
+
return;
|
|
644
|
+
// Find the Plans: section within the phase section.
|
|
645
|
+
// #3691 Bug 1: `Plans:\s*\n` required no text after the colon, missing variants like
|
|
646
|
+
// `Plans: 3 plans across 2 waves\n` or `**Plans:** 3 plans\n` (bold-wrapped).
|
|
647
|
+
// `\*{0,2}Plans\*{0,2}:[^\n]*\n` accepts any text (or none) after the colon
|
|
648
|
+
// and tolerates optional `**` markdown bold wrappers on either side.
|
|
649
|
+
// The checklist group uses `+` (not `*`) so that a bold `**Plans:**` description
|
|
650
|
+
// line with no immediately-following checklist items (e.g. a summary line above a
|
|
651
|
+
// separate bare `Plans:` block) does not consume the match and prevent the actual
|
|
652
|
+
// list from being found.
|
|
653
|
+
// Review fix (F2): `(?:^|\n)` anchors the match to start-of-line so mid-line
|
|
654
|
+
// occurrences like `***Plans:***` embedded in a sentence or `OpenPlans: foo`
|
|
655
|
+
// do not trigger a false match. Groups 1 and 2 retain the same semantics.
|
|
656
|
+
const plansBlockMatch = phaseSection.match(/(?:^|\n)(\*{0,2}Plans\*{0,2}:[^\n]*\n)((?:\s*-\s*\[[ x]\][^\n]*\n?)+)/i);
|
|
657
|
+
if (!plansBlockMatch)
|
|
658
|
+
return;
|
|
659
|
+
const plansHeader = plansBlockMatch[1];
|
|
660
|
+
const existingList = plansBlockMatch[2];
|
|
661
|
+
const listLines = existingList.split('\n').filter(l => /^\s*-\s*\[/.test(l));
|
|
662
|
+
if (listLines.length === 0)
|
|
663
|
+
return;
|
|
664
|
+
// #314 perf: build a first-wins Map so per-line lookup is O(1) instead of O(plans).
|
|
665
|
+
// First-wins mirrors .find() semantics: if the same planId appears more than once
|
|
666
|
+
// in planData, the earlier entry wins — identical to what .find() returned before.
|
|
667
|
+
const planById = new Map();
|
|
668
|
+
for (const p of planData) {
|
|
669
|
+
if (!planById.has(p.planId))
|
|
670
|
+
planById.set(p.planId, p);
|
|
671
|
+
}
|
|
672
|
+
// Build wave-annotated plan list
|
|
673
|
+
const linesByWave = new Map();
|
|
674
|
+
for (const line of listLines) {
|
|
675
|
+
// Match plan ID from line: "- [ ] 01-01-PLAN.md — ..." or "- [ ] 01-01: ..."
|
|
676
|
+
// #3691 Bug 3: `[\w-]+?` excluded `.`, so decimal IDs like `02.3-01` were captured
|
|
677
|
+
// as `02` only and never matched planData entries. `[\w.-]+?` preserves the
|
|
678
|
+
// terminating alternation (`-PLAN.md|.md|:|\s—`) as the boundary anchor.
|
|
679
|
+
const idMatch = line.match(/\[\s*[x ]\s*\]\s*([\w.-]+?)(?:-PLAN\.md|\.md|:|\s—)/i);
|
|
680
|
+
const planId = idMatch ? idMatch[1] : null;
|
|
681
|
+
// Review fix (F3): reject malformed IDs that start with `.`, contain consecutive
|
|
682
|
+
// dots, or otherwise violate the `^\w[\w.-]*$` contract. A leading-dot ID
|
|
683
|
+
// (e.g. `.invalid-PLAN.md`) would silently default to wave 1 — defensively
|
|
684
|
+
// skip the line instead so corrupted ROADMAP entries don't corrupt wave layout.
|
|
685
|
+
if (planId && !/^\w[\w.-]*$/.test(planId))
|
|
686
|
+
continue;
|
|
687
|
+
const planEntry = planId ? (planById.get(planId) || null) : null;
|
|
688
|
+
const wave = planEntry ? planEntry.wave : 1;
|
|
689
|
+
if (!linesByWave.has(wave))
|
|
690
|
+
linesByWave.set(wave, []);
|
|
691
|
+
linesByWave.get(wave).push(line);
|
|
692
|
+
}
|
|
693
|
+
const annotatedLines = [];
|
|
694
|
+
const sortedWaves = [...linesByWave.keys()].sort((a, b) => a - b);
|
|
695
|
+
for (let i = 0; i < sortedWaves.length; i++) {
|
|
696
|
+
const w = sortedWaves[i];
|
|
697
|
+
const waveLines = linesByWave.get(w);
|
|
698
|
+
if (sortedWaves.length > 1) {
|
|
699
|
+
const dep = i > 0 ? ` *(blocked on Wave ${sortedWaves[i - 1]} completion)*` : '';
|
|
700
|
+
annotatedLines.push(`**Wave ${w}**${dep}`);
|
|
701
|
+
}
|
|
702
|
+
annotatedLines.push(...waveLines);
|
|
703
|
+
if (i < sortedWaves.length - 1)
|
|
704
|
+
annotatedLines.push('');
|
|
705
|
+
}
|
|
706
|
+
// Append cross-cutting constraints subsection if any found
|
|
707
|
+
if (crossCuttingTruths.length > 0) {
|
|
708
|
+
annotatedLines.push('');
|
|
709
|
+
annotatedLines.push('**Cross-cutting constraints:**');
|
|
710
|
+
for (const t of crossCuttingTruths) {
|
|
711
|
+
annotatedLines.push(`- ${t}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const newListBlock = annotatedLines.join('\n') + '\n';
|
|
715
|
+
// #1103: when `(?:^|\n)` consumed a leading `\n` (mid-string match), re-emit it
|
|
716
|
+
// so the line preceding the Plans: header is not fused onto it.
|
|
717
|
+
const leadingNewline = plansBlockMatch[0].startsWith('\n') ? '\n' : '';
|
|
718
|
+
const newPhaseSection = phaseSection.replace(plansBlockMatch[0], leadingNewline + plansHeader + newListBlock);
|
|
719
|
+
const nextContent = content.slice(0, phaseStart) + newPhaseSection + content.slice(phaseEnd);
|
|
720
|
+
if (nextContent === content)
|
|
721
|
+
return;
|
|
722
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, nextContent);
|
|
723
|
+
updated = true;
|
|
724
|
+
});
|
|
725
|
+
output({
|
|
726
|
+
updated,
|
|
727
|
+
phase: phaseNum,
|
|
728
|
+
waves: waves.length,
|
|
729
|
+
cross_cutting_constraints: crossCuttingTruths.length,
|
|
730
|
+
}, raw, updated ? `annotated ${waves.length} wave(s), ${crossCuttingTruths.length} constraint(s)` : 'skipped (already annotated or no plan list)');
|
|
731
|
+
}
|
|
732
|
+
module.exports = {
|
|
733
|
+
cmdRoadmapGetPhase,
|
|
734
|
+
getRoadmapPhaseWithFallback,
|
|
735
|
+
cmdRoadmapAnalyze,
|
|
736
|
+
cmdRoadmapUpdatePlanProgress,
|
|
737
|
+
cmdRoadmapAnnotateDependencies,
|
|
738
|
+
};
|