@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
|
@@ -15,6 +15,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
15
15
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
16
16
|
const clock_cjs_1 = require("./clock.cjs");
|
|
17
17
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
18
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
19
20
|
const io = require("./io.cjs");
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
@@ -34,6 +35,9 @@ const phaseId = require("./phase-id.cjs");
|
|
|
34
35
|
const worktreeSafety = require("./worktree-safety.cjs");
|
|
35
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
36
37
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
39
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
40
|
+
const { SCOPE } = planningScopeMod;
|
|
37
41
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
38
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
39
43
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
@@ -83,9 +87,9 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
83
87
|
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocator;
|
|
84
88
|
const { getRoadmapPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
85
89
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
86
|
-
const { normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase } = phaseId;
|
|
90
|
+
const { comparePhaseNum, normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken, scopeToPhase, renderPhaseBranchName } = phaseId;
|
|
87
91
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
88
|
-
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
92
|
+
const { planningPaths, planningDir, planningRoot, todosDir, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, findContextMdIn, } = planningWorkspace;
|
|
89
93
|
const { determinePhaseStatus } = commandsMod;
|
|
90
94
|
const { extractFrontmatter } = frontmatterMod;
|
|
91
95
|
const { isPhaseComplete, resolveVerificationFile, resolveUatFile } = verificationMod;
|
|
@@ -254,7 +258,15 @@ function withProjectRoot(cwd, result) {
|
|
|
254
258
|
if (config.project_code) {
|
|
255
259
|
result['project_code'] = config.project_code;
|
|
256
260
|
}
|
|
257
|
-
|
|
261
|
+
// #4455 follow-up (self-discovered): PROJECT.md is shared across a
|
|
262
|
+
// project's own workstreams (never cloned per workstream) but DOES
|
|
263
|
+
// respect the separate GSD_PROJECT multi-project namespace (#3749) — see
|
|
264
|
+
// cmdInitCompleteMilestone's projectPath comment for the full evidence.
|
|
265
|
+
// `ws` explicitly nulled, `project` left to default from GSD_PROJECT.
|
|
266
|
+
// Reading via the workstream-aware planningDir(cwd) meant every init.*
|
|
267
|
+
// call's project_title silently vanished whenever a workstream was
|
|
268
|
+
// active, since no PROJECT.md ever exists at the workstream path.
|
|
269
|
+
const projectMdPath = node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md');
|
|
258
270
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(projectMdPath);
|
|
259
271
|
if (content) {
|
|
260
272
|
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
@@ -474,6 +486,16 @@ function readConfigJsonBoolean(cwd, keyPath) {
|
|
|
474
486
|
return false;
|
|
475
487
|
}
|
|
476
488
|
}
|
|
489
|
+
/** Reads `filePath`; returns its content, or `null` when missing/unreadable/empty. */
|
|
490
|
+
function readNonEmptyFileOrNull(filePath) {
|
|
491
|
+
try {
|
|
492
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
|
|
493
|
+
return content && content.length > 0 ? content : null;
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
477
499
|
/**
|
|
478
500
|
* Bounded, non-throwing read of a dotted key path from `.planning/config.json`,
|
|
479
501
|
* returning the raw resolved value (any JSON type) or `undefined` on any
|
|
@@ -771,7 +793,6 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
771
793
|
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).join(', ')
|
|
772
794
|
: null;
|
|
773
795
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
774
|
-
const wf = (config.workflow ?? {});
|
|
775
796
|
// #3188: these paths are null when the file is absent, matching the contract
|
|
776
797
|
// the conditional sibling fields (context_path, patterns_path, ...) already
|
|
777
798
|
// honour and that ultraplan-phase.md / execute-phase.md gate on. Hoisted so
|
|
@@ -782,7 +803,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
782
803
|
const result = {
|
|
783
804
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
784
805
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
785
|
-
tdd_mode: options['tdd'] || Boolean(
|
|
806
|
+
tdd_mode: options['tdd'] || Boolean(config.tdd_mode) || false,
|
|
786
807
|
commit_docs: config.commit_docs,
|
|
787
808
|
sub_repos: config.sub_repos,
|
|
788
809
|
parallelization: config.parallelization,
|
|
@@ -826,10 +847,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
826
847
|
runnable_plans: phaseInfo?.['runnable_plans'] || [],
|
|
827
848
|
runnable_count: phaseInfo?.['runnable_plans']?.length || 0,
|
|
828
849
|
branch_name: config.branching_strategy === 'phase' && phaseInfo
|
|
829
|
-
? config.phase_branch_template
|
|
830
|
-
.replace('{project}', config.project_code || '')
|
|
831
|
-
.replace('{phase}', normalizePhaseName(phaseInfo['phase_number']))
|
|
832
|
-
.replace('{slug}', phaseInfo['phase_slug'] || 'phase')
|
|
850
|
+
? renderPhaseBranchName(config.phase_branch_template.replace('{project}', config.project_code || ''), phaseInfo['phase_number'], phaseInfo['phase_slug'])
|
|
833
851
|
: config.branching_strategy === 'milestone'
|
|
834
852
|
? config.milestone_branch_template
|
|
835
853
|
.replace('{milestone}', milestone['version'] ?? '')
|
|
@@ -845,6 +863,17 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
845
863
|
// #3188: null when the file is absent (parity with patterns_path/context_path).
|
|
846
864
|
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
847
865
|
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
866
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
867
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
868
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
869
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
870
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
871
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
872
|
+
// caught the regression immediately. The diagram is stale for
|
|
873
|
+
// config.json specifically (same class of staleness already found for
|
|
874
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
875
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
876
|
+
// ADR-governed, tested contract.
|
|
848
877
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
849
878
|
// #2376: execute-phase.md's verify_phase_goal step reads this instead of
|
|
850
879
|
// hardcoding '.planning/REQUIREMENTS.md' into the gsd-verifier spawn prompt.
|
|
@@ -924,7 +953,6 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
924
953
|
const granularityOverride = options['granularity'];
|
|
925
954
|
assertValidGranularityOverride(granularityOverride, error);
|
|
926
955
|
const granularity = resolveGranularityInternal(cwd, 'planning', granularityOverride || undefined);
|
|
927
|
-
const wf = (config.workflow ?? {});
|
|
928
956
|
// #3188: see cmdInitExecutePhase — null when absent, parity with the
|
|
929
957
|
// conditional sibling fields in this same result object.
|
|
930
958
|
const statePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
@@ -934,11 +962,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
934
962
|
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
935
963
|
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
936
964
|
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
937
|
-
tdd_mode: options['tdd'] || Boolean(
|
|
965
|
+
tdd_mode: options['tdd'] || Boolean(config.tdd_mode) || false,
|
|
938
966
|
granularity,
|
|
939
|
-
research_enabled:
|
|
967
|
+
research_enabled: config.research,
|
|
940
968
|
plan_checker_enabled: config.plan_checker,
|
|
941
|
-
nyquist_validation_enabled:
|
|
969
|
+
nyquist_validation_enabled: config.nyquist_validation,
|
|
942
970
|
commit_docs: config.commit_docs,
|
|
943
971
|
text_mode: config.text_mode,
|
|
944
972
|
auto_advance: !!(config.auto_advance),
|
|
@@ -960,6 +988,11 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
960
988
|
: 'Pending',
|
|
961
989
|
has_research: phaseInfo?.['has_research'] || false,
|
|
962
990
|
has_context: phaseInfo?.['has_context'] || false,
|
|
991
|
+
// #4014 (epic #3473 B4-unreadable): additive scope signal adjacent to
|
|
992
|
+
// has_context — SCOPE.COMPLETE by default (no phase directory to read is
|
|
993
|
+
// a genuine, not-unreadable answer), overwritten below to whatever
|
|
994
|
+
// findContextMdIn(phaseDirFull) reports once a directory is known.
|
|
995
|
+
context_scope: SCOPE.COMPLETE,
|
|
963
996
|
has_reviews: phaseInfo?.['has_reviews'] || false,
|
|
964
997
|
has_plans: (phaseInfo?.['plans']?.length || 0) > 0,
|
|
965
998
|
plan_count: phaseInfo?.['plans']?.length || 0,
|
|
@@ -974,6 +1007,13 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
974
1007
|
};
|
|
975
1008
|
if (phaseInfo?.['directory']) {
|
|
976
1009
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
1010
|
+
// #4014 (epic #3473 B4-unreadable): findContextMdIn's directory-string
|
|
1011
|
+
// form never throws, so this can record the real scope BEFORE the
|
|
1012
|
+
// pre-existing `fs.readdirSync(phaseDirFull)` immediately below
|
|
1013
|
+
// (unchanged) throws on the same unreadable directory and is caught
|
|
1014
|
+
// exactly as before — additive only, the failure control-flow for
|
|
1015
|
+
// context_path/research_path/etc. is untouched.
|
|
1016
|
+
result['context_scope'] = findContextMdIn(phaseDirFull).scope;
|
|
977
1017
|
try {
|
|
978
1018
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
979
1019
|
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
@@ -1100,6 +1140,40 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
1100
1140
|
result['prior_verify_commands'] = priorVerifyCommands;
|
|
1101
1141
|
output(withProjectRoot(cwd, result), raw);
|
|
1102
1142
|
}
|
|
1143
|
+
// #4040: shared partial-init discriminator for the init.progress / init.resume /
|
|
1144
|
+
// init.new-project payloads. A bootstrap interrupted before the core quartet
|
|
1145
|
+
// (PROJECT.md / REQUIREMENTS.md / ROADMAP.md / STATE.md) all landed is a
|
|
1146
|
+
// DISTINCT routing state from "new project" and from "between milestones";
|
|
1147
|
+
// pre-#4040 the payloads could not express it, so progress.md mis-routed it to
|
|
1148
|
+
// Route F and resume-project.md offered STATE.md reconstruction.
|
|
1149
|
+
//
|
|
1150
|
+
// Negative-space guard: `milestone.complete` archives ROADMAP.md (and
|
|
1151
|
+
// REQUIREMENTS.md) but always leaves MILESTONES.md behind — so MILESTONES.md
|
|
1152
|
+
// present proves missing core files are archival (between-milestones), never an
|
|
1153
|
+
// unfinished bootstrap. REQUIREMENTS.md is written by new-project BEFORE
|
|
1154
|
+
// ROADMAP/STATE, so its absence also proves init never finished.
|
|
1155
|
+
function buildInitCompletenessFields(cwd) {
|
|
1156
|
+
const dir = planningDir(cwd);
|
|
1157
|
+
const planningExists = node_fs_1.default.existsSync(dir);
|
|
1158
|
+
const requirementsExists = node_fs_1.default.existsSync(node_path_1.default.join(dir, 'REQUIREMENTS.md'));
|
|
1159
|
+
const milestonesExists = node_fs_1.default.existsSync(node_path_1.default.join(dir, 'MILESTONES.md'));
|
|
1160
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1161
|
+
// workstreams (see cmdInitCompleteMilestone's projectPath comment for the
|
|
1162
|
+
// full evidence) — checked at planningRoot(cwd), never the workstream-scoped
|
|
1163
|
+
// `dir`, so a workstream whose own REQUIREMENTS/ROADMAP/STATE are all
|
|
1164
|
+
// present isn't wrongly reported incomplete just because the shared
|
|
1165
|
+
// PROJECT.md isn't ALSO duplicated under its own directory.
|
|
1166
|
+
const coreComplete = node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')) &&
|
|
1167
|
+
requirementsExists &&
|
|
1168
|
+
node_fs_1.default.existsSync(node_path_1.default.join(dir, 'ROADMAP.md')) &&
|
|
1169
|
+
node_fs_1.default.existsSync(node_path_1.default.join(dir, 'STATE.md'));
|
|
1170
|
+
return {
|
|
1171
|
+
planning_exists: planningExists,
|
|
1172
|
+
requirements_exists: requirementsExists,
|
|
1173
|
+
milestones_exists: milestonesExists,
|
|
1174
|
+
init_incomplete: planningExists && !coreComplete && !milestonesExists,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1103
1177
|
function cmdInitNewProject(cwd, raw, options = {}) {
|
|
1104
1178
|
const config = loadConfig(cwd);
|
|
1105
1179
|
const homedir = node_os_1.default.homedir();
|
|
@@ -1119,23 +1193,51 @@ function cmdInitNewProject(cwd, raw, options = {}) {
|
|
|
1119
1193
|
synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
|
|
1120
1194
|
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
|
|
1121
1195
|
commit_docs: config.commit_docs,
|
|
1122
|
-
|
|
1196
|
+
// #4040: partial-init discriminator (see buildInitCompletenessFields).
|
|
1197
|
+
// Spread BEFORE this literal's own planning_exists so the existing
|
|
1198
|
+
// root-scoped (`pathExistsInternal(cwd, '.planning')`) semantics for that
|
|
1199
|
+
// one key stay byte-identical for existing consumers.
|
|
1200
|
+
...buildInitCompletenessFields(cwd),
|
|
1201
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1202
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1203
|
+
// the full evidence.
|
|
1204
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1123
1205
|
has_codebase_map: hasCodebaseMap,
|
|
1124
1206
|
planning_exists: pathExistsInternal(cwd, '.planning'),
|
|
1125
1207
|
has_existing_code: hasCode,
|
|
1126
1208
|
has_package_file: hasPackageFile,
|
|
1127
1209
|
is_brownfield: isBrownfield,
|
|
1210
|
+
// #4458: new-project.md's Step 5.1 (Sub-Repo Detection) used to run its own
|
|
1211
|
+
// narrower `find ... -exec test -d "{}/.git"` predicate, which requires
|
|
1212
|
+
// .git to be a DIRECTORY and so silently excluded linked git worktree
|
|
1213
|
+
// children (.git is a FILE there). detectSubRepos already handled this
|
|
1214
|
+
// correctly (fs.existsSync, not isDirectory) but had zero callers anywhere
|
|
1215
|
+
// in the codebase — reused here instead of leaving the workflow to
|
|
1216
|
+
// maintain its own duplicate, narrower detection logic.
|
|
1217
|
+
sub_repos_detected: coreUtils.detectSubRepos(cwd),
|
|
1128
1218
|
needs_codebase_map: isBrownfield && !hasCodebaseMap,
|
|
1129
1219
|
...getInitGitState(cwd),
|
|
1130
1220
|
brave_search_available: hasBraveSearch,
|
|
1131
1221
|
firecrawl_available: hasFirecrawl,
|
|
1132
1222
|
exa_search_available: hasExaSearch,
|
|
1133
1223
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1134
|
-
|
|
1224
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1225
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1135
1226
|
// #2376: new-project.md's research-synthesizer/roadmapper spawn prompts
|
|
1136
1227
|
// read these instead of hardcoding '.planning/...' literals.
|
|
1137
1228
|
requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
|
|
1138
1229
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1230
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
1231
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
1232
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
1233
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
1234
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
1235
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
1236
|
+
// caught the regression immediately. The diagram is stale for
|
|
1237
|
+
// config.json specifically (same class of staleness already found for
|
|
1238
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
1239
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
1240
|
+
// ADR-governed, tested contract.
|
|
1139
1241
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
1140
1242
|
research_dir: toPosixPath(node_path_1.default.join(planningRoot(cwd), 'research')),
|
|
1141
1243
|
};
|
|
@@ -1158,13 +1260,12 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1158
1260
|
// owns — routed through it instead of a local readdirSync + hand-rolled
|
|
1159
1261
|
// window filter (which also never excluded sentinels, unlike the owner).
|
|
1160
1262
|
const phaseDirCount = listMilestonePhaseDirs(phasesDir, { cwd }).value.length;
|
|
1161
|
-
const wf = (config.workflow ?? {});
|
|
1162
1263
|
const result = {
|
|
1163
1264
|
researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
|
|
1164
1265
|
synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
|
|
1165
1266
|
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
|
|
1166
1267
|
commit_docs: config.commit_docs,
|
|
1167
|
-
research_enabled:
|
|
1268
|
+
research_enabled: config.research,
|
|
1168
1269
|
// #3216 review Finding 2: `?? null` so an unresolved milestone still emits
|
|
1169
1270
|
// the key with an explicit `null` rather than letting JSON.stringify drop
|
|
1170
1271
|
// it — an omitted key reaches the prompt layer's `{current_milestone}`
|
|
@@ -1178,18 +1279,41 @@ function cmdInitNewMilestone(cwd, raw, options = {}) {
|
|
|
1178
1279
|
phase_archive_path: latestCompleted
|
|
1179
1280
|
? toPosixPath(node_path_1.default.join(planningRoot(cwd), 'milestones', `${latestCompleted.version}-phases`))
|
|
1180
1281
|
: null,
|
|
1181
|
-
|
|
1282
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1283
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1284
|
+
// the full evidence.
|
|
1285
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1182
1286
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1183
1287
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1184
|
-
|
|
1288
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1289
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1185
1290
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1186
1291
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1187
1292
|
// #2376: new-milestone.md's research-synthesizer/roadmapper spawn prompts
|
|
1188
1293
|
// read these instead of hardcoding '.planning/...' literals.
|
|
1189
1294
|
requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
|
|
1295
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
1296
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
1297
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
1298
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
1299
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
1300
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
1301
|
+
// caught the regression immediately. The diagram is stale for
|
|
1302
|
+
// config.json specifically (same class of staleness already found for
|
|
1303
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
1304
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
1305
|
+
// ADR-governed, tested contract.
|
|
1190
1306
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
1191
1307
|
research_dir: toPosixPath(node_path_1.default.join(planningRoot(cwd), 'research')),
|
|
1192
1308
|
milestones_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'MILESTONES.md')),
|
|
1309
|
+
// #4456: new-milestone.md's Step 6 stages the phase-archive move
|
|
1310
|
+
// (`git add .planning/milestones/ .planning/phases/`) — both
|
|
1311
|
+
// workstream-scoped (phases_dir mirrors the phasesDir local above;
|
|
1312
|
+
// archive_dir mirrors cmdInitCompleteMilestone's own field of the same
|
|
1313
|
+
// name), so a literal root `git add` misses the actual files
|
|
1314
|
+
// phases.clear just moved under an active workstream.
|
|
1315
|
+
phases_dir: toPosixPath(phasesDir),
|
|
1316
|
+
archive_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'milestones')),
|
|
1193
1317
|
};
|
|
1194
1318
|
// `state:flat-mode` (#2994): whether NO workstream is active — the inverse
|
|
1195
1319
|
// of `state:workstream-active` (introduced for `cmdInitTransition` below).
|
|
@@ -1246,6 +1370,10 @@ function cmdInitQuick(cwd, description, raw, options = {}) {
|
|
|
1246
1370
|
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
1247
1371
|
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
1248
1372
|
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
1373
|
+
// #3936: Step 4.75 dispatches gsd-phase-researcher; resolve its own tier
|
|
1374
|
+
// (parity with cmdInitPlanPhase) so the research spawn stops pinning
|
|
1375
|
+
// planner_model.
|
|
1376
|
+
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
1249
1377
|
// #2072: the quick review step spawns gsd-code-reviewer; resolve its own model
|
|
1250
1378
|
// so model_overrides / models.verification apply (was reusing executor_model).
|
|
1251
1379
|
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
|
|
@@ -1281,10 +1409,52 @@ function cmdInitQuick(cwd, description, raw, options = {}) {
|
|
|
1281
1409
|
result['section_manifest'] = buildSectionManifestField(cwd, null, sectionManifestOptions, 'quick');
|
|
1282
1410
|
output(withProjectRoot(cwd, result), raw);
|
|
1283
1411
|
}
|
|
1412
|
+
/**
|
|
1413
|
+
* `init.quick-batch` (#3676, Phase 4 of epic #3344, ADR-1239 "Quick-batch
|
|
1414
|
+
* binding"). Unlike `cmdInitQuick`, this init bundle does NOT allocate a
|
|
1415
|
+
* quick id / slug / task directory itself — batch-level id allocation and
|
|
1416
|
+
* `BATCH.json` creation is the job of the `quick-batch create` CLI verb
|
|
1417
|
+
* (`src/quick-batch-command-router.cts`, wrapping `createBatch` in
|
|
1418
|
+
* `src/quick-batch.cts`). This bundle supplies the per-role model profiles,
|
|
1419
|
+
* `commit_docs`, the roadmap/planning existence checks `quick-batch.md`'s
|
|
1420
|
+
* ROADMAP.md gate needs (same check `cmdInitQuick` runs), the `.planning/quick`
|
|
1421
|
+
* directory path, and the `section_manifest` field gating the optional
|
|
1422
|
+
* `--research`/`--validate` step fragments — the same `flag:--research`/
|
|
1423
|
+
* `flag:--validate` atoms `quick`'s own section manifest already uses
|
|
1424
|
+
* (`WHEN_VOCABULARY` is workflow-agnostic; no new atom is needed). `--discuss`/
|
|
1425
|
+
* `--full` are rejected by `quick-batch-dispatch.cts`'s `parseQuickBatchArgs`
|
|
1426
|
+
* before this init bundle is ever reached, so no `discuss`/`full` flag key is
|
|
1427
|
+
* accepted here (unlike `cmdInitQuick`, which still supports both).
|
|
1428
|
+
*/
|
|
1429
|
+
function cmdInitQuickBatch(cwd, raw, options = {}) {
|
|
1430
|
+
const config = loadConfig(cwd);
|
|
1431
|
+
const result = {
|
|
1432
|
+
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
|
|
1433
|
+
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
|
|
1434
|
+
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
|
|
1435
|
+
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
|
|
1436
|
+
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
|
|
1437
|
+
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
|
|
1438
|
+
commit_docs: config.commit_docs,
|
|
1439
|
+
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase; a
|
|
1440
|
+
// per-item task_dir is re-derived by the workflow itself (quick_id +
|
|
1441
|
+
// generate-slug over that item's description), never allocated here.
|
|
1442
|
+
quick_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'quick')),
|
|
1443
|
+
quick_batches_dir: toPosixPath(node_path_1.default.join(planningDir(cwd), 'quick-batches')),
|
|
1444
|
+
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1445
|
+
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1446
|
+
};
|
|
1447
|
+
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
1448
|
+
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'quick-batch');
|
|
1449
|
+
output(withProjectRoot(cwd, result), raw);
|
|
1450
|
+
}
|
|
1284
1451
|
function cmdInitIngestDocs(cwd, raw) {
|
|
1285
1452
|
const config = loadConfig(cwd);
|
|
1286
1453
|
const result = {
|
|
1287
|
-
|
|
1454
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1455
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1456
|
+
// the full evidence.
|
|
1457
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1288
1458
|
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1289
1459
|
...getInitGitState(cwd),
|
|
1290
1460
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase. The
|
|
@@ -1293,7 +1463,8 @@ function cmdInitIngestDocs(cwd, raw) {
|
|
|
1293
1463
|
// hardcoded bare '.planning/intel/...', '.planning/PROJECT.md', etc.
|
|
1294
1464
|
// literals into their Agent(prompt=...) blocks; those now interpolate
|
|
1295
1465
|
// these fields instead.
|
|
1296
|
-
|
|
1466
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1467
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1297
1468
|
requirements_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'REQUIREMENTS.md')),
|
|
1298
1469
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1299
1470
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
@@ -1305,12 +1476,11 @@ function cmdInitIngestDocs(cwd, raw) {
|
|
|
1305
1476
|
}
|
|
1306
1477
|
function cmdInitOnboard(cwd, raw, options = {}) {
|
|
1307
1478
|
const config = loadConfig(cwd);
|
|
1308
|
-
const workflowConfig = (config.workflow ?? {});
|
|
1309
1479
|
const result = {
|
|
1310
1480
|
...buildOnboardProjection(cwd, {
|
|
1311
1481
|
commitDocs: !!config.commit_docs,
|
|
1312
1482
|
fast: options['fast'] === true,
|
|
1313
|
-
textMode: options['text'] === true || !!config.text_mode
|
|
1483
|
+
textMode: options['text'] === true || !!config.text_mode,
|
|
1314
1484
|
}),
|
|
1315
1485
|
...getInitGitState(cwd),
|
|
1316
1486
|
};
|
|
@@ -1323,14 +1493,23 @@ function cmdInitResume(cwd, raw) {
|
|
|
1323
1493
|
if (agentIdRaw !== null)
|
|
1324
1494
|
interruptedAgentId = agentIdRaw.trim();
|
|
1325
1495
|
const result = {
|
|
1496
|
+
// #4040: partial-init discriminator (see buildInitCompletenessFields).
|
|
1497
|
+
// Spread FIRST so this literal's own root-scoped planning_exists
|
|
1498
|
+
// (planningRoot) keeps its existing semantics; init_incomplete itself
|
|
1499
|
+
// keys off the artifact dir (planningDir) where the core docs live.
|
|
1500
|
+
...buildInitCompletenessFields(cwd),
|
|
1326
1501
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1327
1502
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1328
|
-
|
|
1503
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
1504
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
1505
|
+
// the full evidence.
|
|
1506
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1329
1507
|
planning_exists: node_fs_1.default.existsSync(planningRoot(cwd)),
|
|
1330
1508
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1331
1509
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1332
1510
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1333
|
-
|
|
1511
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
1512
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
1334
1513
|
has_interrupted_agent: !!interruptedAgentId,
|
|
1335
1514
|
interrupted_agent_id: interruptedAgentId,
|
|
1336
1515
|
commit_docs: config.commit_docs,
|
|
@@ -1720,6 +1899,9 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1720
1899
|
padded_phase: phaseNumber ? normalizePhaseName(phaseNumber) : null,
|
|
1721
1900
|
has_research: phaseInfo?.['has_research'] || false,
|
|
1722
1901
|
has_context: phaseInfo?.['has_context'] || false,
|
|
1902
|
+
// #4014 (epic #3473 B4-unreadable): see the parallel field in
|
|
1903
|
+
// cmdInitPlanPhase — additive scope signal adjacent to has_context.
|
|
1904
|
+
context_scope: SCOPE.COMPLETE,
|
|
1723
1905
|
has_plans: (phaseInfo?.['plans']?.length || 0) > 0,
|
|
1724
1906
|
has_verification: phaseInfo?.['has_verification'] || false,
|
|
1725
1907
|
has_reviews: phaseInfo?.['has_reviews'] || false,
|
|
@@ -1734,6 +1916,9 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1734
1916
|
};
|
|
1735
1917
|
if (phaseInfo?.['directory']) {
|
|
1736
1918
|
const phaseDirFull = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
1919
|
+
// #4014 (epic #3473 B4-unreadable): see the parallel site in
|
|
1920
|
+
// cmdInitPlanPhase — additive only, failure control-flow below unchanged.
|
|
1921
|
+
result['context_scope'] = findContextMdIn(phaseDirFull).scope;
|
|
1737
1922
|
try {
|
|
1738
1923
|
const files = node_fs_1.default.readdirSync(phaseDirFull);
|
|
1739
1924
|
const phaseDirName = node_path_1.default.basename(phaseDirFull);
|
|
@@ -1800,13 +1985,156 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1800
1985
|
}
|
|
1801
1986
|
output(withProjectRoot(cwd, result), raw);
|
|
1802
1987
|
}
|
|
1988
|
+
// #2618: bullet-cap and title-floor for renderPendingTodosMarkdown below.
|
|
1989
|
+
// 240 matches the bound already vetted by maintainer review on the prior
|
|
1990
|
+
// attempt at this issue (PR #2662) — re-deriving a different number would be
|
|
1991
|
+
// pure bikeshedding, not a correctness improvement. See
|
|
1992
|
+
// .gsd/phase/feat-2618-compact-todo-pointers/40-design.md.
|
|
1993
|
+
const PENDING_TODO_BULLET_MAX_CHARS = 240;
|
|
1994
|
+
const PENDING_TODO_TITLE_FLOOR = 15;
|
|
1995
|
+
const PENDING_TODO_AREA_FLOOR = 3;
|
|
1996
|
+
function sanitizePendingTodoInline(value) {
|
|
1997
|
+
// Defensive: the regex captures that populate title/area/needs can only
|
|
1998
|
+
// ever match a single line, so this is belt-and-suspenders against any
|
|
1999
|
+
// future non-regex-sourced input, not a reachable case today.
|
|
2000
|
+
return value.replace(/[\r\n]+/g, ' ').trim();
|
|
2001
|
+
}
|
|
2002
|
+
function truncatePendingTodoText(value, maxLen) {
|
|
2003
|
+
if (value.length <= maxLen)
|
|
2004
|
+
return value;
|
|
2005
|
+
if (maxLen <= 1)
|
|
2006
|
+
return value.slice(0, Math.max(0, maxLen));
|
|
2007
|
+
return `${value.slice(0, maxLen - 1)}…`;
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* #2618: pure renderer for STATE.md's "### Pending Todos" section BODY (not
|
|
2011
|
+
* the heading). One bullet per todo, each capped at
|
|
2012
|
+
* PENDING_TODO_BULLET_MAX_CHARS. `gsd-core/workflows/add-todo.md` and
|
|
2013
|
+
* `check-todos.md` splice this string in verbatim instead of free-hand
|
|
2014
|
+
* editing STATE.md — see the design doc for why this is real, unit-tested
|
|
2015
|
+
* code rather than a prose algorithm (DEFECT.GENERATIVE-FIX: a prose
|
|
2016
|
+
* algorithm duplicated as a test oracle is exactly the divergence class
|
|
2017
|
+
* this avoids).
|
|
2018
|
+
*
|
|
2019
|
+
* #4384 regression fix: the optional `projectRoot` makes the bullet's
|
|
2020
|
+
* `[todo file](…)` link repo-relative (see pendingTodoLinkTarget) so the cap
|
|
2021
|
+
* is deterministic w.r.t. where the repo is checked out. Omitting it keeps
|
|
2022
|
+
* the legacy absolute-link behavior for existing direct callers.
|
|
2023
|
+
*/
|
|
2024
|
+
function renderPendingTodosMarkdown(todos, projectRoot) {
|
|
2025
|
+
if (!Array.isArray(todos) || todos.length === 0) {
|
|
2026
|
+
return 'None yet.';
|
|
2027
|
+
}
|
|
2028
|
+
return todos.map((todo) => renderPendingTodoBullet(todo, projectRoot)).join('\n');
|
|
2029
|
+
}
|
|
2030
|
+
function pendingTodoFieldAsString(value, fallback) {
|
|
2031
|
+
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* #4384 regression fix: the bullet's markdown link target, rendered
|
|
2035
|
+
* repo-relative when `projectRoot` is given and the todo's `path` is
|
|
2036
|
+
* absolute. The JSON `todos[].path` field stays absolute (#2376 contract);
|
|
2037
|
+
* only the rendered display link changes — embedding the machine-variable
|
|
2038
|
+
* absolute base let macOS's /private/var/folders/… temp paths consume the
|
|
2039
|
+
* 240-char budget and drop the "Needs <solution>" clause on long-path
|
|
2040
|
+
* machines only (next's own macos CI shard went red on exactly this, run
|
|
2041
|
+
* 34038716700). Repo-relative links also resolve correctly from STATE.md at
|
|
2042
|
+
* the repo root and survive repo moves.
|
|
2043
|
+
*/
|
|
2044
|
+
function pendingTodoLinkTarget(todo, projectRoot) {
|
|
2045
|
+
const raw = pendingTodoFieldAsString(todo['path'], '');
|
|
2046
|
+
if (typeof projectRoot !== 'string' || projectRoot.length === 0 || !node_path_1.default.isAbsolute(raw)) {
|
|
2047
|
+
return raw;
|
|
2048
|
+
}
|
|
2049
|
+
const rel = toPosixPath(node_path_1.default.relative(projectRoot, raw));
|
|
2050
|
+
if (rel.length === 0 || node_path_1.default.isAbsolute(rel)) {
|
|
2051
|
+
// Degenerate (path === projectRoot) or Windows cross-drive fallback:
|
|
2052
|
+
// keep the raw target rather than emitting an empty or incorrect link.
|
|
2053
|
+
return raw;
|
|
2054
|
+
}
|
|
2055
|
+
return rel;
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* #4439: the stored `created:` frontmatter (and the JSON `todos[].created`
|
|
2059
|
+
* field it round-trips through) is always a full ISO-8601 timestamp, by
|
|
2060
|
+
* design — this is the display-only seam that reformats it to the
|
|
2061
|
+
* date-only `[date]` bullet documented in docs/reference/state-md.md and
|
|
2062
|
+
* docs/COMMANDS.md. A value that doesn't start with a well-formed
|
|
2063
|
+
* `YYYY-MM-DD` (the 'unknown' fallback, or any other non-conforming
|
|
2064
|
+
* string) passes through unchanged rather than being mangled.
|
|
2065
|
+
*/
|
|
2066
|
+
function pendingTodoDateOnly(value) {
|
|
2067
|
+
const match = value.match(/^\d{4}-\d{2}-\d{2}/);
|
|
2068
|
+
return match ? match[0] : value;
|
|
2069
|
+
}
|
|
2070
|
+
function renderPendingTodoBullet(todo, projectRoot) {
|
|
2071
|
+
const date = pendingTodoDateOnly(sanitizePendingTodoInline(pendingTodoFieldAsString(todo['created'], 'unknown')));
|
|
2072
|
+
let area = sanitizePendingTodoInline(pendingTodoFieldAsString(todo['area'], 'general'));
|
|
2073
|
+
let title = sanitizePendingTodoInline(pendingTodoFieldAsString(todo['title'], 'Untitled'));
|
|
2074
|
+
// Strip trailing "." so the fixed "Needs ....` template below never
|
|
2075
|
+
// produces a doubled period when the source text already ended in one.
|
|
2076
|
+
let needs = typeof todo['needs'] === 'string'
|
|
2077
|
+
? sanitizePendingTodoInline(todo['needs']).replace(/\.+$/, '')
|
|
2078
|
+
: '';
|
|
2079
|
+
const link = `[todo file](${pendingTodoLinkTarget(todo, projectRoot)})`;
|
|
2080
|
+
const assemble = () => {
|
|
2081
|
+
const needsClause = needs ? ` — Needs ${needs}.` : '';
|
|
2082
|
+
return `- [${date}] [${area}] ${title} — ${link}${needsClause}`;
|
|
2083
|
+
};
|
|
2084
|
+
let line = assemble();
|
|
2085
|
+
if (line.length <= PENDING_TODO_BULLET_MAX_CHARS)
|
|
2086
|
+
return line;
|
|
2087
|
+
// 1) Drop the needs clause entirely first — date/area/title/link untouched.
|
|
2088
|
+
needs = '';
|
|
2089
|
+
line = assemble();
|
|
2090
|
+
if (line.length <= PENDING_TODO_BULLET_MAX_CHARS)
|
|
2091
|
+
return line;
|
|
2092
|
+
// 2) Shorten the title next, down to a floor — date/area/link untouched.
|
|
2093
|
+
const titleOverage = line.length - PENDING_TODO_BULLET_MAX_CHARS;
|
|
2094
|
+
const targetTitleLen = Math.max(PENDING_TODO_TITLE_FLOOR, title.length - titleOverage);
|
|
2095
|
+
if (targetTitleLen < title.length) {
|
|
2096
|
+
title = truncatePendingTodoText(title, targetTitleLen);
|
|
2097
|
+
line = assemble();
|
|
2098
|
+
}
|
|
2099
|
+
if (line.length <= PENDING_TODO_BULLET_MAX_CHARS)
|
|
2100
|
+
return line;
|
|
2101
|
+
// 3) Shorten area as a last resort — date and the markdown link are never
|
|
2102
|
+
// altered (link correctness > strict cap; see design doc "Known limits").
|
|
2103
|
+
const areaOverage = line.length - PENDING_TODO_BULLET_MAX_CHARS;
|
|
2104
|
+
const targetAreaLen = Math.max(PENDING_TODO_AREA_FLOOR, area.length - areaOverage);
|
|
2105
|
+
if (targetAreaLen < area.length) {
|
|
2106
|
+
area = truncatePendingTodoText(area, targetAreaLen);
|
|
2107
|
+
line = assemble();
|
|
2108
|
+
}
|
|
2109
|
+
return line;
|
|
2110
|
+
}
|
|
1803
2111
|
function cmdInitTodos(cwd, area, raw) {
|
|
1804
2112
|
const config = loadConfig(cwd);
|
|
1805
|
-
|
|
2113
|
+
// #4256: todos are root-scoped shared state (migrateToWorkstreams keeps
|
|
2114
|
+
// them at .planning/todos/ and every workflow writer writes that literal
|
|
2115
|
+
// path), so this read resolves via todosDir(cwd) — NOT planningDir(cwd),
|
|
2116
|
+
// which would look in .planning/workstreams/<ws>/todos/ under a workstream
|
|
2117
|
+
// (a directory nothing creates) and report existing todos as absent.
|
|
2118
|
+
const todosRoot = todosDir(cwd);
|
|
2119
|
+
const pendingDir = node_path_1.default.join(todosRoot, 'pending');
|
|
1806
2120
|
let count = 0;
|
|
1807
2121
|
const todos = [];
|
|
2122
|
+
// #2618: distinct from "genuinely zero pending todos" — false only when
|
|
2123
|
+
// readdirSync itself failed for a reason OTHER than the directory simply
|
|
2124
|
+
// not existing yet (ENOENT), mirroring the ENOENT-vs-other-errno split
|
|
2125
|
+
// already used above in this file (#3885, ADR-3473 §8.5). Without this,
|
|
2126
|
+
// a real I/O/permission error on the pending dir would look identical to
|
|
2127
|
+
// "no pending todos" and could wipe an existing, non-empty Pending Todos
|
|
2128
|
+
// section in STATE.md on refresh — the fail-safe requirement for #2618.
|
|
2129
|
+
let pendingReadOk = true;
|
|
1808
2130
|
try {
|
|
1809
|
-
|
|
2131
|
+
// #2618: sorted so pending_todos_markdown's bullet order is stable across
|
|
2132
|
+
// runs — readdirSync's order is filesystem-dependent, not contractually
|
|
2133
|
+
// stable, and an unstable order would reorder every bullet on an
|
|
2134
|
+
// unrelated re-render, turning a one-line git diff into a full-section
|
|
2135
|
+
// rewrite (must-have #3). Filenames are `YYYY-MM-DD-slug.md`, so this
|
|
2136
|
+
// also yields a sensible chronological order as a side effect.
|
|
2137
|
+
const files = node_fs_1.default.readdirSync(pendingDir).filter((f) => f.endsWith('.md')).sort();
|
|
1810
2138
|
for (const file of files) {
|
|
1811
2139
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(pendingDir, file));
|
|
1812
2140
|
if (content === null)
|
|
@@ -1818,6 +2146,21 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1818
2146
|
// #2337: kept in parity with cmdListTodos — surface severity when
|
|
1819
2147
|
// present, omit the key entirely for todos with no severity line.
|
|
1820
2148
|
const severityMatch = content.match(/^severity:\s*(.+)$/m);
|
|
2149
|
+
// #2618: first non-empty line of the `## Solution` body, used as the
|
|
2150
|
+
// bullet's "Needs ..." clause. "TBD" (the create_file template's own
|
|
2151
|
+
// placeholder for an unresolved solution) renders no clause at all
|
|
2152
|
+
// rather than the useless literal "Needs TBD.".
|
|
2153
|
+
const solutionSection = (0, markdown_sectionizer_cjs_1.collectSection)(content, (h) => h.level === 2 && h.text.trim() === 'Solution');
|
|
2154
|
+
let needs;
|
|
2155
|
+
if (solutionSection) {
|
|
2156
|
+
const firstLine = solutionSection.body
|
|
2157
|
+
.split('\n')
|
|
2158
|
+
.map((l) => l.trim())
|
|
2159
|
+
.find((l) => l.length > 0);
|
|
2160
|
+
if (firstLine && firstLine.toUpperCase() !== 'TBD') {
|
|
2161
|
+
needs = firstLine;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
1821
2164
|
const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
|
|
1822
2165
|
if (area && todoArea !== area)
|
|
1823
2166
|
continue;
|
|
@@ -1828,8 +2171,9 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1828
2171
|
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
|
|
1829
2172
|
area: todoArea,
|
|
1830
2173
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1831
|
-
path: toPosixPath(node_path_1.default.join(
|
|
2174
|
+
path: toPosixPath(node_path_1.default.join(pendingDir, file)),
|
|
1832
2175
|
...(severityMatch ? { severity: severityMatch[1].trim() } : {}),
|
|
2176
|
+
...(needs ? { needs } : {}),
|
|
1833
2177
|
});
|
|
1834
2178
|
}
|
|
1835
2179
|
catch {
|
|
@@ -1837,8 +2181,11 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1837
2181
|
}
|
|
1838
2182
|
}
|
|
1839
2183
|
}
|
|
1840
|
-
catch {
|
|
1841
|
-
|
|
2184
|
+
catch (err) {
|
|
2185
|
+
const code = err?.code;
|
|
2186
|
+
if (code !== 'ENOENT') {
|
|
2187
|
+
pendingReadOk = false;
|
|
2188
|
+
}
|
|
1842
2189
|
}
|
|
1843
2190
|
const result = {
|
|
1844
2191
|
commit_docs: config.commit_docs,
|
|
@@ -1848,11 +2195,22 @@ function cmdInitTodos(cwd, area, raw) {
|
|
|
1848
2195
|
todos,
|
|
1849
2196
|
area_filter: area || null,
|
|
1850
2197
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
1851
|
-
|
|
1852
|
-
|
|
2198
|
+
// #4256: both dir fields probe the ROOT todos tree via todosDir(cwd).
|
|
2199
|
+
pending_dir: toPosixPath(pendingDir),
|
|
2200
|
+
completed_dir: toPosixPath(node_path_1.default.join(todosRoot, 'completed')),
|
|
2201
|
+
// planning_exists intentionally stays workstream/project-scoped — it
|
|
2202
|
+
// answers "does the ACTIVE planning dir exist", not a todos question.
|
|
1853
2203
|
planning_exists: node_fs_1.default.existsSync(planningDir(cwd)),
|
|
1854
|
-
todos_dir_exists: node_fs_1.default.existsSync(
|
|
1855
|
-
pending_dir_exists: node_fs_1.default.existsSync(
|
|
2204
|
+
todos_dir_exists: node_fs_1.default.existsSync(todosRoot),
|
|
2205
|
+
pending_dir_exists: node_fs_1.default.existsSync(pendingDir),
|
|
2206
|
+
// #2618: see PENDING_TODO_BULLET_MAX_CHARS comment / design doc. Consumed
|
|
2207
|
+
// by add-todo.md / check-todos.md's update_state step; omitted entirely
|
|
2208
|
+
// (rather than emitted with possibly-wrong data) when pendingReadOk is
|
|
2209
|
+
// false, so the workflow's fail-safe check can key off field presence.
|
|
2210
|
+
pending_read_ok: pendingReadOk,
|
|
2211
|
+
// #4384 fix: pass cwd as projectRoot so the bullet link renders
|
|
2212
|
+
// repo-relative — see pendingTodoLinkTarget.
|
|
2213
|
+
...(pendingReadOk ? { pending_todos_markdown: renderPendingTodosMarkdown(todos, cwd) } : {}),
|
|
1856
2214
|
};
|
|
1857
2215
|
output(withProjectRoot(cwd, result), raw);
|
|
1858
2216
|
}
|
|
@@ -1967,7 +2325,10 @@ function cmdInitMilestoneOp(cwd, raw) {
|
|
|
1967
2325
|
all_phases_complete: phaseCount > 0 && phaseCount === completedPhases,
|
|
1968
2326
|
archived_milestones: archivedMilestones,
|
|
1969
2327
|
archive_count: archivedMilestones.length,
|
|
1970
|
-
|
|
2328
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
2329
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
2330
|
+
// the full evidence.
|
|
2331
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
1971
2332
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
1972
2333
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
1973
2334
|
archive_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningRoot(cwd), 'archive')),
|
|
@@ -2062,6 +2423,9 @@ function cmdInitManager(cwd, raw) {
|
|
|
2062
2423
|
let hasResearch = false;
|
|
2063
2424
|
let lastActivity = null;
|
|
2064
2425
|
let isActive = false;
|
|
2426
|
+
// #4014 (epic #3473 B4-unreadable): default COMPLETE — no directory at
|
|
2427
|
+
// all (dirMatch not found) is a genuine, not-unreadable answer.
|
|
2428
|
+
let contextScope = SCOPE.COMPLETE;
|
|
2065
2429
|
let completion = buildPhaseCompletionProjection(cwd, phaseNum, null, planCount, summaryCount, _slashRuntime);
|
|
2066
2430
|
try {
|
|
2067
2431
|
// #3185 (ADR-3180 Decision 2) moved this lookup off the
|
|
@@ -2072,6 +2436,17 @@ function cmdInitManager(cwd, raw) {
|
|
|
2072
2436
|
if (dirMatch) {
|
|
2073
2437
|
const fullDir = node_path_1.default.join(phasesDir, dirMatch);
|
|
2074
2438
|
const phaseDirRel = toPosixPath(node_path_1.default.relative(cwd, fullDir));
|
|
2439
|
+
// #4014 (epic #3473 B4-unreadable): this whole block used to swallow
|
|
2440
|
+
// ANY readdirSync failure below into the bare `catch { /* empty */ }`
|
|
2441
|
+
// at the bottom — an unreadable phase directory reported the exact
|
|
2442
|
+
// same `has_context: false` / `disk_status: 'no_directory'` as a
|
|
2443
|
+
// directory that never existed. findContextMdIn's directory-string
|
|
2444
|
+
// form never throws, so it can record the real scope (COMPLETE vs
|
|
2445
|
+
// UNREADABLE) here, BEFORE the pre-existing `fs.readdirSync(fullDir)`
|
|
2446
|
+
// immediately below (unchanged) throws on the exact same unreadable
|
|
2447
|
+
// directory and is caught exactly as before — this line is additive
|
|
2448
|
+
// only, the failure control-flow is untouched.
|
|
2449
|
+
contextScope = findContextMdIn(fullDir).scope;
|
|
2075
2450
|
const phaseFiles = node_fs_1.default.readdirSync(fullDir);
|
|
2076
2451
|
planCount = listPhasePlanFiles(fullDir).length;
|
|
2077
2452
|
summaryCount = listPhaseSummaryFiles(fullDir).length;
|
|
@@ -2146,6 +2521,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
2146
2521
|
...completion,
|
|
2147
2522
|
last_activity: lastActivity,
|
|
2148
2523
|
is_active: isActive,
|
|
2524
|
+
context_scope: contextScope,
|
|
2149
2525
|
});
|
|
2150
2526
|
}
|
|
2151
2527
|
const MAX_NAME_WIDTH = 20;
|
|
@@ -2334,10 +2710,23 @@ function cmdInitManager(cwd, raw) {
|
|
|
2334
2710
|
recommended_actions: filteredActions,
|
|
2335
2711
|
waiting_signal: waitingSignal,
|
|
2336
2712
|
all_complete: completedCount === nonBacklogPhases.length && nonBacklogPhases.length > 0,
|
|
2337
|
-
|
|
2713
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
2714
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
2715
|
+
// the full evidence.
|
|
2716
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
2338
2717
|
roadmap_exists: true,
|
|
2339
2718
|
state_exists: true,
|
|
2340
2719
|
manager_flags: managerFlags,
|
|
2720
|
+
// #4455: workstream-scoped STATE/ROADMAP/milestone-archive paths — same
|
|
2721
|
+
// pattern cmdInitPlanPhase already uses (existence-checked, toPosixPath'd,
|
|
2722
|
+
// null when absent) plus the archive dir composition milestone.cts's
|
|
2723
|
+
// `cmdMilestoneComplete` uses (#1911: planningPaths(cwd).planning +
|
|
2724
|
+
// 'milestones', workstream-aware). autonomous.md's discover_phases/
|
|
2725
|
+
// iterate/lifecycle steps consume these instead of hardcoding
|
|
2726
|
+
// `.planning/STATE.md` / `.planning/milestones/...`.
|
|
2727
|
+
state_path: node_fs_1.default.existsSync(paths.state) ? toPosixPath(paths.state) : null,
|
|
2728
|
+
roadmap_path: node_fs_1.default.existsSync(paths.roadmap) ? toPosixPath(paths.roadmap) : null,
|
|
2729
|
+
archive_dir: toPosixPath(node_path_1.default.join(paths.planning, 'milestones')),
|
|
2341
2730
|
};
|
|
2342
2731
|
output(withProjectRoot(cwd, result), raw);
|
|
2343
2732
|
}
|
|
@@ -2356,10 +2745,57 @@ function cmdInitManager(cwd, raw) {
|
|
|
2356
2745
|
*/
|
|
2357
2746
|
function cmdInitCompleteMilestone(cwd, raw, options = {}) {
|
|
2358
2747
|
const gitCreateTag = detectGitCreateTag(cwd);
|
|
2748
|
+
// #4455: workstream-scoped STATE/ROADMAP/milestone-archive paths for
|
|
2749
|
+
// complete-milestone.md's reorganize_roadmap_and_delete_originals step —
|
|
2750
|
+
// same pattern cmdInitPlanPhase already uses, mirrored here since this is
|
|
2751
|
+
// that workflow's own dedicated init entry point.
|
|
2752
|
+
const planningBase = planningDir(cwd);
|
|
2753
|
+
const statePath = node_path_1.default.join(planningBase, 'STATE.md');
|
|
2754
|
+
const roadmapPath = node_path_1.default.join(planningBase, 'ROADMAP.md');
|
|
2755
|
+
const archiveDir = node_path_1.default.join(planningBase, 'milestones');
|
|
2756
|
+
// #4455 follow-up (code-review finding): MILESTONES.md is workstream-scoped
|
|
2757
|
+
// too — cmdMilestoneComplete (src/milestone.cts) writes it via
|
|
2758
|
+
// planningPaths(cwd).planning (the workstream base, not root; #1911). It is
|
|
2759
|
+
// not the deliberately-root-scoped exception `todos` is (#4256) — an
|
|
2760
|
+
// earlier version of this fix wrongly treated it as a shared root file,
|
|
2761
|
+
// which would have made the safety commit below silently miss the actual
|
|
2762
|
+
// file milestone.complete just wrote under an active workstream.
|
|
2763
|
+
const milestonesPath = node_path_1.default.join(planningBase, 'MILESTONES.md');
|
|
2764
|
+
// #4455 follow-up round 2 (self-discovered regression): PROJECT.md, unlike
|
|
2765
|
+
// MILESTONES.md, is genuinely SHARED across a project's own workstreams —
|
|
2766
|
+
// never cloned per workstream. gsd-core/references/workstream-flag.md's
|
|
2767
|
+
// directory diagram marks it `# Shared`; new-milestone.md states it
|
|
2768
|
+
// outright ("PROJECT.md is shared across workstreams") and explicitly
|
|
2769
|
+
// SKIPS writing its `## Current Milestone` heading under an active
|
|
2770
|
+
// workstream specifically to avoid clobbering the one shared file (#2308);
|
|
2771
|
+
// cmdWorkstreamCreate (src/workstream.cts) never creates a PROJECT.md
|
|
2772
|
+
// under a workstream directory. The first version of this #4455 follow-up
|
|
2773
|
+
// wrongly generalized from planningPaths()'s structural shape (which
|
|
2774
|
+
// composes `project` under the workstream base) without checking an
|
|
2775
|
+
// actual PROJECT.md write path — resolved against planningRoot(cwd)
|
|
2776
|
+
// (round 2), but that ALSO ignores the separate GSD_PROJECT dimension
|
|
2777
|
+
// (multi-project namespacing, #3749: PROJECT.md legitimately lives at
|
|
2778
|
+
// `.planning/<project>/PROJECT.md` when GSD_PROJECT is set — a real,
|
|
2779
|
+
// tested, pre-existing feature planningRoot's blanket root-only read
|
|
2780
|
+
// broke), caught by gsd-test on this fix's own first push. `planningDir`
|
|
2781
|
+
// with `ws` explicitly nulled (never read from GSD_WORKSTREAM) but
|
|
2782
|
+
// `project` left to default from GSD_PROJECT is the correct middle
|
|
2783
|
+
// ground: respects project-namespacing, ignores workstream-namespacing.
|
|
2784
|
+
const projectPath = node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md');
|
|
2785
|
+
// REQUIREMENTS.md is workstream-scoped the same way (planningPaths(cwd).requirements,
|
|
2786
|
+
// src/planning-workspace.cts) — the git-rm-after-archive step needs the
|
|
2787
|
+
// resolved path too, not the literal root file.
|
|
2788
|
+
const requirementsPath = node_path_1.default.join(planningBase, 'REQUIREMENTS.md');
|
|
2359
2789
|
const result = {
|
|
2360
2790
|
// #2994: hoisted from complete-milestone.md's git_tag step
|
|
2361
2791
|
// <config-check> resolver (git.create_tag, fail-open default true).
|
|
2362
2792
|
git_create_tag: gitCreateTag,
|
|
2793
|
+
state_path: node_fs_1.default.existsSync(statePath) ? toPosixPath(statePath) : null,
|
|
2794
|
+
roadmap_path: node_fs_1.default.existsSync(roadmapPath) ? toPosixPath(roadmapPath) : null,
|
|
2795
|
+
archive_dir: toPosixPath(archiveDir),
|
|
2796
|
+
milestones_path: node_fs_1.default.existsSync(milestonesPath) ? toPosixPath(milestonesPath) : null,
|
|
2797
|
+
project_path: node_fs_1.default.existsSync(projectPath) ? toPosixPath(projectPath) : null,
|
|
2798
|
+
requirements_path: node_fs_1.default.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
|
|
2363
2799
|
};
|
|
2364
2800
|
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'complete-milestone', {
|
|
2365
2801
|
gitCreateTag,
|
|
@@ -2522,7 +2958,7 @@ function cmdInitTransition(cwd, raw, options = {}) {
|
|
|
2522
2958
|
* location has one source instead of two kept in sync by hand.
|
|
2523
2959
|
* - `debugger_model` — `resolveModelInternal`, which IS what `query
|
|
2524
2960
|
* resolve-model --pick model` returns (`cmdResolveModel`, src/commands.cts).
|
|
2525
|
-
* - `tdd_mode` — the `Boolean(
|
|
2961
|
+
* - `tdd_mode` — the `Boolean(config.tdd_mode)` idiom `cmdInitExecutePhase` and
|
|
2526
2962
|
* `cmdInitPlanPhase` already use. `/gsd:debug` has no `--tdd` flag, so the
|
|
2527
2963
|
* sibling handlers' `options['tdd'] ||` disjunct is deliberately omitted
|
|
2528
2964
|
* rather than carried as a phantom.
|
|
@@ -2540,7 +2976,6 @@ function cmdInitTransition(cwd, raw, options = {}) {
|
|
|
2540
2976
|
*/
|
|
2541
2977
|
function cmdInitDebug(cwd, raw, options = {}) {
|
|
2542
2978
|
const config = loadConfig(cwd);
|
|
2543
|
-
const wf = (config.workflow ?? {});
|
|
2544
2979
|
const result = {
|
|
2545
2980
|
commit_docs: config.commit_docs,
|
|
2546
2981
|
// #2376: absolute — debug.md builds `debug_file_path` as
|
|
@@ -2548,7 +2983,7 @@ function cmdInitDebug(cwd, raw, options = {}) {
|
|
|
2548
2983
|
// own cwd may differ from the orchestrator's.
|
|
2549
2984
|
debug_dir: toPosixPath(planningPaths(cwd).debug),
|
|
2550
2985
|
debugger_model: resolveModelInternal(cwd, 'gsd-debugger'),
|
|
2551
|
-
tdd_mode: Boolean(
|
|
2986
|
+
tdd_mode: Boolean(config.tdd_mode),
|
|
2552
2987
|
diagnose: options['diagnose'] === true,
|
|
2553
2988
|
};
|
|
2554
2989
|
// Additive, optional field — degrades to null while `debug` has no key in
|
|
@@ -2709,7 +3144,7 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2709
3144
|
}
|
|
2710
3145
|
}
|
|
2711
3146
|
}
|
|
2712
|
-
phases.sort((a, b) =>
|
|
3147
|
+
phases.sort((a, b) => comparePhaseNum(a['number'], b['number']));
|
|
2713
3148
|
// #3581: the frontier is ROADMAP ORDER, not artifact presence. The disk loop
|
|
2714
3149
|
// above could claim nextPhase from a stray out-of-order artifact directory
|
|
2715
3150
|
// (a phase-9 UAT evidence dir while roadmap phase 8 was pending and
|
|
@@ -2768,13 +3203,32 @@ function cmdInitProgress(cwd, raw, options = {}) {
|
|
|
2768
3203
|
paused_at: pausedAt,
|
|
2769
3204
|
has_work_in_progress: !!currentPhase,
|
|
2770
3205
|
phase_mvp_mode: phaseMvpMode,
|
|
2771
|
-
|
|
3206
|
+
// #4455 follow-up (code-review finding): PROJECT.md is shared across
|
|
3207
|
+
// workstreams — see cmdInitCompleteMilestone's projectPath comment for
|
|
3208
|
+
// the full evidence.
|
|
3209
|
+
project_exists: pathExistsInternal(cwd, toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')))),
|
|
2772
3210
|
roadmap_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
2773
3211
|
state_exists: node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
3212
|
+
// #4040: partial-init discriminator (see buildInitCompletenessFields) —
|
|
3213
|
+
// also adds planning_exists / requirements_exists / milestones_exists so
|
|
3214
|
+
// progress.md's init_context routing never has to fall back to Glob.
|
|
3215
|
+
...buildInitCompletenessFields(cwd),
|
|
2774
3216
|
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
|
|
2775
3217
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
2776
3218
|
roadmap_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'ROADMAP.md')),
|
|
2777
|
-
|
|
3219
|
+
// #4455 follow-up: PROJECT.md is shared across workstreams.
|
|
3220
|
+
project_path: toPosixPath(node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md')),
|
|
3221
|
+
// #4456 correction: an isolated review pass initially "fixed" this to
|
|
3222
|
+
// planningDir(cwd, null) on the assumption that config.json is shared
|
|
3223
|
+
// like PROJECT.md (workstream-flag.md's directory diagram marks it
|
|
3224
|
+
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
|
|
3225
|
+
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
|
|
3226
|
+
// for execute-phase/new-project/new-milestone/progress, and gsd-test
|
|
3227
|
+
// caught the regression immediately. The diagram is stale for
|
|
3228
|
+
// config.json specifically (same class of staleness already found for
|
|
3229
|
+
// `milestones/` during the #4455 follow-up) — reverted to the
|
|
3230
|
+
// workstream-aware planningDir(cwd), matching the established,
|
|
3231
|
+
// ADR-governed, tested contract.
|
|
2778
3232
|
config_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'config.json')),
|
|
2779
3233
|
};
|
|
2780
3234
|
// #2992 (Phase 6.1): additive, optional field — degrades to null, never throws.
|
|
@@ -3009,12 +3463,9 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
3009
3463
|
warn(`[agent-skills] WARNING: Global skill not found at "${displayPath}/SKILL.md" — skipping\n`);
|
|
3010
3464
|
continue;
|
|
3011
3465
|
}
|
|
3012
|
-
const
|
|
3013
|
-
if (
|
|
3014
|
-
const acceptedViaTrustedRoot = trustedGlobalRoots.some((root) =>
|
|
3015
|
-
const rootCheck = (0, security_cjs_1.validatePath)(globalSkillMd, root, { allowAbsolute: true });
|
|
3016
|
-
return Boolean(rootCheck['safe']);
|
|
3017
|
-
});
|
|
3466
|
+
const globalSkillMdContained = (0, security_cjs_1.tryWithinRoot)(globalSkillMd, globalSkillsBase, security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
3467
|
+
if (globalSkillMdContained === null) {
|
|
3468
|
+
const acceptedViaTrustedRoot = trustedGlobalRoots.some((root) => (0, security_cjs_1.tryWithinRoot)(globalSkillMd, root, security_cjs_1.PathAcceptance.AbsoluteInsideRoot) !== null);
|
|
3018
3469
|
if (!acceptedViaTrustedRoot) {
|
|
3019
3470
|
warn(`[agent-skills] WARNING: Global skill "${skillName}" failed path check (symlink escape?) — skipping\n`);
|
|
3020
3471
|
continue;
|
|
@@ -3023,15 +3474,23 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
3023
3474
|
// trace, not a skip, so it must not land in the diagnostics warnings[].
|
|
3024
3475
|
process.stderr.write(`[agent-skills] NOTE: Global skill "${skillName}" accepted via trusted_global_roots (resolves outside the default skills dir)\n`);
|
|
3025
3476
|
}
|
|
3477
|
+
// `ref` is an emitted display token, not a path anything reads or writes
|
|
3478
|
+
// through — the containment check above is a gate, not a path producer.
|
|
3479
|
+
// Emitting the validated (realpath-resolved, platform-separator) value
|
|
3480
|
+
// instead of this literal broke symlinked skill dirs and Windows output.
|
|
3481
|
+
// The only filesystem read here (existsSync above) already ran on the
|
|
3482
|
+
// lexical path before containment was checked, so ADR-4650's "use the
|
|
3483
|
+
// validated value" rule doesn't apply to this emission.
|
|
3026
3484
|
validEntries.push({ kind: 'include', ref: `${globalSkillDir}/SKILL.md`, display: displayPath });
|
|
3027
3485
|
continue;
|
|
3028
3486
|
}
|
|
3029
|
-
const
|
|
3030
|
-
if (
|
|
3031
|
-
warn(`[agent-skills] WARNING: Skipping unsafe path "${skillPath}":
|
|
3487
|
+
const skillPathContained = (0, security_cjs_1.tryWithinRoot)(skillPath, projectRoot);
|
|
3488
|
+
if (skillPathContained === null) {
|
|
3489
|
+
warn(`[agent-skills] WARNING: Skipping unsafe path "${skillPath}": not confined to the project directory\n`);
|
|
3032
3490
|
continue;
|
|
3033
3491
|
}
|
|
3034
|
-
|
|
3492
|
+
// ADR-4650: the validated value is the value used — never re-derive from raw input.
|
|
3493
|
+
const skillMdPath = node_path_1.default.join(skillPathContained, 'SKILL.md');
|
|
3035
3494
|
if (!node_fs_1.default.existsSync(skillMdPath)) {
|
|
3036
3495
|
// #2941: if the bare name matches a global skill, hint at the global: prefix.
|
|
3037
3496
|
// The bare name resolves as project-relative (which doesn't exist), but the
|
|
@@ -3087,20 +3546,37 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
3087
3546
|
// persona fallback. Triggering the fallback for claude would change the
|
|
3088
3547
|
// documented "unconfigured → empty block" contract that agent-skills tests
|
|
3089
3548
|
// pin.
|
|
3549
|
+
//
|
|
3550
|
+
// #4407 (ADR-4139 stream 2): this is the one place GSD's own agent-persona
|
|
3551
|
+
// content is served through a real code seam rather than an eagerly
|
|
3552
|
+
// @-included file, so the compact/canonical choice is made here in code
|
|
3553
|
+
// (a real exit code) instead of a prose config-get gate. Compact is tried
|
|
3554
|
+
// first when requested; a missing compact sibling falls back to canonical
|
|
3555
|
+
// with the fallback disclosed in the payload itself, never a silent switch.
|
|
3556
|
+
let agentPayloadVariant = null;
|
|
3090
3557
|
if (!block) {
|
|
3091
3558
|
const runtime = (config && config['runtime']) || process.env['GSD_RUNTIME'] || 'claude';
|
|
3092
3559
|
if (runtime !== 'claude') {
|
|
3093
3560
|
const agentCheck = checkAgentsInstalled(runtime, projectRoot);
|
|
3094
3561
|
const agentsDir = agentCheck?.agents_dir;
|
|
3095
3562
|
if (typeof agentsDir === 'string' && agentsDir.length > 0) {
|
|
3096
|
-
const
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3563
|
+
const compactRequested = readConfigJsonBoolean(projectRoot, ['workflow', 'compact_content']);
|
|
3564
|
+
const compactContent = compactRequested
|
|
3565
|
+
? readNonEmptyFileOrNull(node_path_1.default.join(agentsDir, `${agentType}.compact.md`))
|
|
3566
|
+
: null;
|
|
3567
|
+
if (compactContent !== null) {
|
|
3568
|
+
block = compactContent;
|
|
3569
|
+
agentPayloadVariant = 'compact';
|
|
3570
|
+
}
|
|
3571
|
+
else {
|
|
3572
|
+
const canonicalContent = readNonEmptyFileOrNull(node_path_1.default.join(agentsDir, `${agentType}.md`));
|
|
3573
|
+
if (canonicalContent !== null) {
|
|
3574
|
+
block = compactRequested
|
|
3575
|
+
? `<!-- gsd: no compact payload registered for ${agentType}; serving canonical -->\n\n${canonicalContent}`
|
|
3576
|
+
: canonicalContent;
|
|
3577
|
+
agentPayloadVariant = 'canonical';
|
|
3101
3578
|
}
|
|
3102
3579
|
}
|
|
3103
|
-
catch { /* agent file not found — fall through to empty block */ }
|
|
3104
3580
|
}
|
|
3105
3581
|
}
|
|
3106
3582
|
}
|
|
@@ -3147,8 +3623,8 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
3147
3623
|
if (jsonMode) {
|
|
3148
3624
|
// Build the Resolution<AgentSkillsValue> envelope and embed .value additively.
|
|
3149
3625
|
// Flat fields are retained unchanged for back-compat; value formalises the
|
|
3150
|
-
// Resolution convention (ADR-1411 P3, #1416). source/degraded
|
|
3151
|
-
// config-provenance extras, outside the Resolution<T> envelope.
|
|
3626
|
+
// Resolution convention (ADR-1411 P3, #1416). source/degraded/agent_payload_variant
|
|
3627
|
+
// remain config-provenance extras, outside the Resolution<T> envelope.
|
|
3152
3628
|
const resolution = (0, resolution_cjs_1.makeResolution)({ block: block || '', skills_count: normalizedPaths.length }, { configured, reason, warnings: diagnostics.warnings });
|
|
3153
3629
|
output({
|
|
3154
3630
|
agent_type: agentType,
|
|
@@ -3159,6 +3635,7 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
3159
3635
|
reason,
|
|
3160
3636
|
source,
|
|
3161
3637
|
degraded,
|
|
3638
|
+
agent_payload_variant: agentPayloadVariant,
|
|
3162
3639
|
value: resolution.value,
|
|
3163
3640
|
}, raw);
|
|
3164
3641
|
return;
|
|
@@ -3419,6 +3896,7 @@ module.exports = {
|
|
|
3419
3896
|
cmdInitNewProject,
|
|
3420
3897
|
cmdInitNewMilestone,
|
|
3421
3898
|
cmdInitQuick,
|
|
3899
|
+
cmdInitQuickBatch,
|
|
3422
3900
|
cmdInitIngestDocs,
|
|
3423
3901
|
cmdInitOnboard,
|
|
3424
3902
|
cmdInitResume,
|
|
@@ -3446,4 +3924,5 @@ module.exports = {
|
|
|
3446
3924
|
cmdAgentSkills,
|
|
3447
3925
|
buildSkillManifest,
|
|
3448
3926
|
cmdSkillManifest,
|
|
3927
|
+
renderPendingTodosMarkdown,
|
|
3449
3928
|
};
|