@bhargavvc/sdd-cc 1.35.0 → 1.42.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja-JP.md +54 -52
- package/README.ko-KR.md +47 -45
- package/README.md +86 -733
- package/README.pt-BR.md +14 -12
- package/README.zh-CN.md +31 -29
- package/agents/sdd-ai-researcher.md +2 -2
- package/agents/sdd-code-fixer.md +169 -17
- package/agents/sdd-code-reviewer.md +40 -8
- package/agents/sdd-codebase-mapper.md +89 -6
- package/agents/sdd-debug-session-manager.md +314 -0
- package/agents/sdd-debugger.md +147 -80
- package/agents/sdd-doc-classifier.md +168 -0
- package/agents/sdd-doc-synthesizer.md +204 -0
- package/agents/sdd-doc-verifier.md +20 -4
- package/agents/sdd-doc-writer.md +22 -9
- package/agents/sdd-domain-researcher.md +2 -2
- package/agents/sdd-eval-auditor.md +30 -3
- package/agents/sdd-eval-planner.md +2 -2
- package/agents/sdd-executor.md +203 -43
- package/agents/sdd-framework-selector.md +1 -1
- package/agents/sdd-integration-checker.md +30 -3
- package/agents/sdd-intel-updater.md +51 -23
- package/agents/sdd-nyquist-auditor.md +31 -4
- package/agents/sdd-pattern-mapper.md +335 -0
- package/agents/sdd-phase-researcher.md +195 -32
- package/agents/sdd-plan-checker.md +135 -24
- package/agents/sdd-planner.md +190 -205
- package/agents/sdd-project-researcher.md +7 -7
- package/agents/sdd-research-synthesizer.md +6 -6
- package/agents/sdd-roadmapper.md +19 -10
- package/agents/sdd-security-auditor.md +33 -6
- package/agents/sdd-ui-auditor.md +23 -7
- package/agents/sdd-ui-checker.md +16 -7
- package/agents/sdd-ui-researcher.md +8 -8
- package/agents/sdd-verifier.md +124 -27
- package/bin/install.js +5016 -372
- package/bin/sdd-sdk.js +37 -0
- package/commands/sdd/add-tests.md +5 -4
- package/commands/sdd/ai-integration-phase.md +4 -3
- package/commands/sdd/audit-fix.md +2 -1
- package/commands/sdd/audit-milestone.md +3 -2
- package/commands/sdd/autonomous.md +3 -3
- package/commands/sdd/capture.md +62 -0
- package/commands/sdd/cleanup.md +2 -1
- package/commands/sdd/code-review.md +8 -4
- package/commands/sdd/commit.md +39 -0
- package/commands/sdd/complete-milestone.md +15 -8
- package/commands/sdd/config.md +58 -0
- package/commands/sdd/debug.md +21 -155
- package/commands/sdd/discuss-phase.md +18 -11
- package/commands/sdd/docs-update.md +3 -2
- package/commands/sdd/eval-review.md +4 -3
- package/commands/sdd/execute-phase.md +5 -4
- package/commands/sdd/explore.md +3 -3
- package/commands/sdd/extract-learnings.md +23 -0
- package/commands/sdd/fast.md +4 -3
- package/commands/sdd/forensics.md +3 -2
- package/commands/sdd/graphify.md +199 -0
- package/commands/sdd/health.md +12 -3
- package/commands/sdd/help.md +1 -1
- package/commands/sdd/import.md +10 -5
- package/commands/sdd/inbox.md +39 -0
- package/commands/sdd/ingest-docs.md +42 -0
- package/commands/sdd/manager.md +8 -3
- package/commands/sdd/map-codebase.md +18 -6
- package/commands/sdd/milestone-summary.md +1 -1
- package/commands/sdd/mvp-phase.md +45 -0
- package/commands/sdd/new-milestone.md +4 -3
- package/commands/sdd/new-project.md +4 -3
- package/commands/sdd/ns-context.md +23 -0
- package/commands/sdd/ns-ideate.md +24 -0
- package/commands/sdd/ns-manage.md +29 -0
- package/commands/sdd/ns-project.md +22 -0
- package/commands/sdd/ns-review.md +26 -0
- package/commands/sdd/ns-workflow.md +28 -0
- package/commands/sdd/pause-work.md +6 -1
- package/commands/sdd/phase.md +56 -0
- package/commands/sdd/plan-phase.md +15 -5
- package/commands/sdd/plan-review-convergence.md +59 -0
- package/commands/sdd/pr-branch.md +2 -1
- package/commands/sdd/profile-user.md +1 -1
- package/commands/sdd/progress.md +27 -5
- package/commands/sdd/quick.md +128 -3
- package/commands/sdd/resume-work.md +2 -12
- package/commands/sdd/review-backlog.md +3 -2
- package/commands/sdd/review.md +3 -2
- package/commands/sdd/secure-phase.md +3 -2
- package/commands/sdd/settings.md +2 -9
- package/commands/sdd/ship.md +2 -1
- package/commands/sdd/sketch.md +60 -0
- package/commands/sdd/spec-phase.md +63 -0
- package/commands/sdd/spike.md +57 -0
- package/commands/sdd/stats.md +2 -1
- package/commands/sdd/surface.md +129 -0
- package/commands/sdd/thread.md +8 -111
- package/commands/sdd/ui-phase.md +3 -2
- package/commands/sdd/ui-review.md +3 -2
- package/commands/sdd/ultraplan-phase.md +34 -0
- package/commands/sdd/undo.md +2 -1
- package/commands/sdd/update.md +21 -10
- package/commands/sdd/validate-phase.md +3 -2
- package/commands/sdd/verify-work.md +5 -4
- package/commands/sdd/workspace.md +52 -0
- package/commands/sdd/workstreams.md +12 -11
- package/hooks/dist/sdd-check-update-worker.js +116 -0
- package/hooks/dist/sdd-check-update.js +13 -88
- package/hooks/dist/sdd-context-monitor.js +28 -1
- package/hooks/dist/sdd-phase-boundary.sh +23 -3
- package/hooks/dist/sdd-read-guard.js +21 -2
- package/hooks/dist/sdd-read-injection-scanner.js +152 -0
- package/hooks/dist/sdd-session-state.sh +38 -12
- package/hooks/dist/sdd-statusline.js +324 -28
- package/hooks/dist/sdd-update-banner.js +134 -0
- package/hooks/dist/sdd-validate-commit.sh +15 -5
- package/hooks/dist/sdd-workflow-guard.js +2 -2
- package/hooks/lib/git-cmd.js +150 -0
- package/hooks/sdd-check-update-worker.js +116 -0
- package/hooks/sdd-check-update.js +13 -88
- package/hooks/sdd-context-monitor.js +28 -1
- package/hooks/sdd-phase-boundary.sh +23 -3
- package/hooks/sdd-read-guard.js +21 -2
- package/hooks/sdd-read-injection-scanner.js +152 -0
- package/hooks/sdd-session-state.sh +38 -12
- package/hooks/sdd-statusline.js +324 -28
- package/hooks/sdd-update-banner.js +134 -0
- package/hooks/sdd-validate-commit.sh +15 -5
- package/hooks/sdd-workflow-guard.js +2 -2
- package/package.json +32 -7
- package/scripts/audit-workflow-script-paths.cjs +73 -0
- package/scripts/build-hooks.js +98 -4
- package/scripts/changeset/cli.cjs +269 -0
- package/scripts/changeset/github-release-notes.cjs +198 -0
- package/scripts/changeset/lint.cjs +110 -0
- package/scripts/changeset/new.cjs +137 -0
- package/scripts/changeset/parse.cjs +60 -0
- package/scripts/changeset/render.cjs +34 -0
- package/scripts/changeset/serialize.cjs +74 -0
- package/scripts/command-contract-helpers.cjs +61 -0
- package/scripts/diff-touches-shipped-paths.cjs +147 -0
- package/scripts/fix-slash-commands.cjs +106 -0
- package/scripts/gen-inventory-manifest.cjs +109 -0
- package/scripts/lint-command-contract.cjs +108 -0
- package/scripts/lint-descriptions.cjs +83 -0
- package/scripts/lint-no-source-grep-extras.cjs +81 -0
- package/scripts/lint-no-source-grep.cjs +174 -0
- package/scripts/lint-shell-command-projection-drift.cjs +57 -0
- package/scripts/lint-skill-deps.cjs +180 -0
- package/scripts/pr-template-policy.cjs +169 -0
- package/scripts/prompt-injection-scan.sh +2 -0
- package/scripts/rebrand-gsd-to-sdd.sh +4 -3
- package/scripts/strip-prose-atrefs.cjs +106 -0
- package/scripts/verify-tarball-sdk-dist.sh +69 -0
- package/sdd/bin/check-latest-version.cjs +104 -0
- package/sdd/bin/lib/active-workstream-store.cjs +85 -0
- package/sdd/bin/lib/adr-parser.cjs +394 -0
- package/sdd/bin/lib/artifacts.cjs +53 -0
- package/sdd/bin/lib/audit.cjs +755 -0
- package/sdd/bin/lib/cjs-command-router-adapter.cjs +39 -0
- package/sdd/bin/lib/clusters.cjs +136 -0
- package/sdd/bin/lib/command-aliases.generated.cjs +838 -0
- package/sdd/bin/lib/commands.cjs +108 -98
- package/sdd/bin/lib/config-schema.cjs +135 -0
- package/sdd/bin/lib/config.cjs +253 -68
- package/sdd/bin/lib/context-utilization.cjs +47 -0
- package/sdd/bin/lib/core.cjs +997 -607
- package/sdd/bin/lib/decisions.cjs +48 -0
- package/sdd/bin/lib/docs.cjs +36 -33
- package/sdd/bin/lib/drift.cjs +379 -0
- package/sdd/bin/lib/fallow-runner.cjs +109 -0
- package/sdd/bin/lib/frontmatter.cjs +19 -11
- package/sdd/bin/lib/gap-checker.cjs +197 -0
- package/sdd/bin/lib/graphify.cjs +577 -0
- package/sdd/bin/lib/init-command-router.cjs +70 -0
- package/sdd/bin/lib/init.cjs +603 -102
- package/sdd/bin/lib/install-profiles.cjs +572 -0
- package/sdd/bin/lib/installer-migration-authoring.cjs +117 -0
- package/sdd/bin/lib/installer-migration-report.cjs +328 -0
- package/sdd/bin/lib/installer-migrations/000-first-time-baseline.cjs +220 -0
- package/sdd/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +41 -0
- package/sdd/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +80 -0
- package/sdd/bin/lib/installer-migrations.cjs +703 -0
- package/sdd/bin/lib/intel.cjs +35 -52
- package/sdd/bin/lib/learnings.cjs +2 -1
- package/sdd/bin/lib/milestone.cjs +313 -283
- package/sdd/bin/lib/model-catalog.cjs +136 -0
- package/sdd/bin/lib/model-profiles.cjs +25 -70
- package/sdd/bin/lib/phase-command-router.cjs +96 -0
- package/sdd/bin/lib/phase.cjs +556 -102
- package/sdd/bin/lib/phases-command-router.cjs +39 -0
- package/sdd/bin/lib/plan-scan.cjs +138 -0
- package/sdd/bin/lib/planning-workspace.cjs +361 -0
- package/sdd/bin/lib/profile-output.cjs +109 -43
- package/sdd/bin/lib/profile-pipeline.cjs +1 -1
- package/sdd/bin/lib/review-reviewer-selection.cjs +125 -0
- package/sdd/bin/lib/roadmap-command-router.cjs +23 -0
- package/sdd/bin/lib/roadmap.cjs +278 -17
- package/sdd/bin/lib/runtime-homes.cjs +178 -0
- package/sdd/bin/lib/sdd2-import.cjs +3 -3
- package/sdd/bin/lib/secrets.cjs +33 -0
- package/sdd/bin/lib/security.cjs +8 -7
- package/sdd/bin/lib/shell-command-projection.cjs +548 -0
- package/sdd/bin/lib/state-command-router.cjs +100 -0
- package/sdd/bin/lib/state-document.cjs +12 -0
- package/sdd/bin/lib/state-document.generated.cjs +127 -0
- package/sdd/bin/lib/state.cjs +720 -218
- package/sdd/bin/lib/surface.cjs +398 -0
- package/sdd/bin/lib/template.cjs +4 -2
- package/sdd/bin/lib/uat.cjs +9 -2
- package/sdd/bin/lib/validate-command-router.cjs +55 -0
- package/sdd/bin/lib/verify-command-router.cjs +34 -0
- package/sdd/bin/lib/verify.cjs +464 -137
- package/sdd/bin/lib/workstream-inventory.cjs +159 -0
- package/sdd/bin/lib/workstream-name-policy.cjs +33 -0
- package/sdd/bin/lib/workstream.cjs +73 -195
- package/sdd/bin/lib/worktree-safety.cjs +563 -0
- package/sdd/bin/sdd-tools.cjs +386 -252
- package/sdd/bin/verify-reapply-patches.cjs +247 -0
- package/sdd/contexts/review.md +1 -0
- package/sdd/references/artifact-types.md +18 -0
- package/sdd/references/autonomous-smart-discuss.md +277 -0
- package/sdd/references/checkpoints.md +36 -0
- package/sdd/references/context-budget.md +85 -49
- package/sdd/references/continuation-format.md +26 -22
- package/sdd/references/debugger-philosophy.md +76 -0
- package/sdd/references/decimal-phase-calculation.md +5 -5
- package/sdd/references/doc-conflict-engine.md +91 -0
- package/sdd/references/domain-probes.md +1 -1
- package/sdd/references/execute-mvp-tdd.md +81 -0
- package/sdd/references/executor-examples.md +110 -0
- package/sdd/references/gate-prompts.md +2 -2
- package/sdd/references/git-integration.md +10 -7
- package/sdd/references/git-planning-commit.md +6 -4
- package/sdd/references/mandatory-initial-read.md +2 -0
- package/sdd/references/model-profiles.md +106 -6
- package/sdd/references/mvp-concepts.md +49 -0
- package/sdd/references/phase-argument-parsing.md +3 -3
- package/sdd/references/planner-antipatterns.md +89 -0
- package/sdd/references/planner-chunked.md +49 -0
- package/sdd/references/planner-human-verify-mode.md +57 -0
- package/sdd/references/planner-mvp-mode.md +53 -0
- package/sdd/references/planner-revision.md +1 -1
- package/sdd/references/planner-source-audit.md +73 -0
- package/sdd/references/planning-config.md +30 -13
- package/sdd/references/project-skills-discovery.md +19 -0
- package/sdd/references/scout-codebase.md +51 -0
- package/sdd/references/skeleton-template.md +48 -0
- package/sdd/references/sketch-interactivity.md +41 -0
- package/sdd/references/sketch-theme-system.md +94 -0
- package/sdd/references/sketch-tooling.md +45 -0
- package/sdd/references/sketch-variant-patterns.md +81 -0
- package/sdd/references/spidr-splitting.md +69 -0
- package/sdd/references/tdd.md +67 -0
- package/sdd/references/thinking-partner.md +1 -1
- package/sdd/references/universal-anti-patterns.md +4 -4
- package/sdd/references/user-story-template.md +58 -0
- package/sdd/references/verification-overrides.md +3 -3
- package/sdd/references/verify-mvp-mode.md +85 -0
- package/sdd/references/workstream-flag.md +11 -11
- package/sdd/references/worktree-path-safety.md +89 -0
- package/sdd/templates/AI-SPEC.md +1 -1
- package/sdd/templates/DEBUG.md +8 -3
- package/sdd/templates/README.md +77 -0
- package/sdd/templates/UAT.md +4 -4
- package/sdd/templates/VALIDATION.md +1 -1
- package/sdd/templates/claude-md.md +5 -5
- package/sdd/templates/config.json +16 -2
- package/sdd/templates/debug-subagent-prompt.md +1 -1
- package/sdd/templates/dev-preferences.md +1 -1
- package/sdd/templates/discovery.md +2 -2
- package/sdd/templates/phase-prompt.md +1 -1
- package/sdd/templates/planner-subagent-prompt.md +3 -3
- package/sdd/templates/project.md +1 -1
- package/sdd/templates/research.md +41 -1
- package/sdd/templates/spec.md +307 -0
- package/sdd/templates/state.md +9 -1
- package/sdd/workflows/add-backlog.md +90 -0
- package/sdd/workflows/add-phase.md +7 -7
- package/sdd/workflows/add-tests.md +12 -12
- package/sdd/workflows/add-todo.md +4 -4
- package/sdd/workflows/ai-integration-phase.md +26 -16
- package/sdd/workflows/analyze-dependencies.md +3 -3
- package/sdd/workflows/audit-fix.md +23 -3
- package/sdd/workflows/audit-milestone.md +37 -20
- package/sdd/workflows/audit-uat.md +3 -3
- package/sdd/workflows/autonomous.md +31 -301
- package/sdd/workflows/check-todos.md +6 -6
- package/sdd/workflows/cleanup.md +1 -1
- package/sdd/workflows/code-review-fix.md +20 -16
- package/sdd/workflows/code-review.md +114 -16
- package/sdd/workflows/commit.md +166 -0
- package/sdd/workflows/complete-milestone.md +68 -15
- package/sdd/workflows/debug.md +231 -0
- package/sdd/workflows/diagnose-issues.md +8 -6
- package/sdd/workflows/discovery-phase.md +3 -3
- package/sdd/workflows/discuss-phase/modes/advisor.md +175 -0
- package/sdd/workflows/discuss-phase/modes/all.md +28 -0
- package/sdd/workflows/discuss-phase/modes/analyze.md +44 -0
- package/sdd/workflows/discuss-phase/modes/auto.md +56 -0
- package/sdd/workflows/discuss-phase/modes/batch.md +52 -0
- package/sdd/workflows/discuss-phase/modes/chain.md +97 -0
- package/sdd/workflows/discuss-phase/modes/default.md +141 -0
- package/sdd/workflows/discuss-phase/modes/power.md +44 -0
- package/sdd/workflows/discuss-phase/modes/text.md +55 -0
- package/sdd/workflows/discuss-phase/templates/checkpoint.json +18 -0
- package/sdd/workflows/discuss-phase/templates/context.md +136 -0
- package/sdd/workflows/discuss-phase/templates/discussion-log.md +50 -0
- package/sdd/workflows/discuss-phase-assumptions.md +24 -21
- package/sdd/workflows/discuss-phase-power.md +3 -3
- package/sdd/workflows/discuss-phase.md +203 -905
- package/sdd/workflows/do.md +25 -21
- package/sdd/workflows/docs-update.md +23 -17
- package/sdd/workflows/edit-phase.md +294 -0
- package/sdd/workflows/eval-review.md +7 -7
- package/sdd/workflows/execute-phase/steps/codebase-drift-gate.md +81 -0
- package/sdd/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/sdd/workflows/execute-phase/steps/post-merge-gate.md +116 -0
- package/sdd/workflows/execute-phase.md +650 -252
- package/sdd/workflows/execute-plan.md +75 -35
- package/sdd/workflows/explore.md +7 -3
- package/sdd/workflows/extract-learnings.md +242 -0
- package/sdd/workflows/fast.md +13 -12
- package/sdd/workflows/forensics.md +19 -6
- package/sdd/workflows/graduation.md +195 -0
- package/sdd/workflows/health.md +49 -7
- package/sdd/workflows/help.md +340 -149
- package/sdd/workflows/import.md +20 -43
- package/sdd/workflows/inbox.md +5 -5
- package/sdd/workflows/ingest-docs.md +339 -0
- package/sdd/workflows/insert-phase.md +33 -12
- package/sdd/workflows/list-phase-assumptions.md +2 -2
- package/sdd/workflows/list-workspaces.md +2 -2
- package/sdd/workflows/manager.md +38 -11
- package/sdd/workflows/map-codebase.md +87 -23
- package/sdd/workflows/milestone-summary.md +8 -8
- package/sdd/workflows/mvp-phase.md +221 -0
- package/sdd/workflows/new-milestone.md +163 -23
- package/sdd/workflows/new-project.md +254 -53
- package/sdd/workflows/new-workspace.md +8 -8
- package/sdd/workflows/next.md +85 -18
- package/sdd/workflows/note.md +2 -2
- package/sdd/workflows/pause-work.md +13 -9
- package/sdd/workflows/plan-milestone-gaps.md +18 -11
- package/sdd/workflows/plan-phase.md +767 -94
- package/sdd/workflows/plan-review-convergence.md +329 -0
- package/sdd/workflows/plant-seed.md +146 -89
- package/sdd/workflows/pr-branch.md +1 -1
- package/sdd/workflows/profile-user.md +15 -15
- package/sdd/workflows/progress.md +198 -56
- package/sdd/workflows/quick.md +318 -54
- package/{commands/sdd → sdd/workflows}/reapply-patches.md +102 -23
- package/sdd/workflows/remove-phase.md +10 -10
- package/sdd/workflows/remove-workspace.md +21 -6
- package/sdd/workflows/resume-project.md +26 -23
- package/sdd/workflows/review.md +151 -20
- package/sdd/workflows/scan.md +5 -3
- package/sdd/workflows/secure-phase.md +26 -13
- package/sdd/workflows/settings-advanced.md +579 -0
- package/sdd/workflows/settings-integrations.md +281 -0
- package/sdd/workflows/settings.md +202 -23
- package/sdd/workflows/ship.md +131 -16
- package/sdd/workflows/sketch-wrap-up.md +285 -0
- package/sdd/workflows/sketch.md +360 -0
- package/sdd/workflows/spec-phase.md +262 -0
- package/sdd/workflows/spike-wrap-up.md +306 -0
- package/sdd/workflows/spike.md +452 -0
- package/sdd/workflows/stats.md +21 -2
- package/sdd/workflows/sync-skills.md +182 -0
- package/sdd/workflows/thread.md +221 -0
- package/sdd/workflows/transition.md +52 -30
- package/sdd/workflows/ui-phase.md +37 -20
- package/sdd/workflows/ui-review.md +12 -10
- package/sdd/workflows/ultraplan-phase.md +198 -0
- package/sdd/workflows/undo.md +9 -9
- package/sdd/workflows/update.md +187 -17
- package/sdd/workflows/validate-phase.md +12 -10
- package/sdd/workflows/verify-phase.md +112 -27
- package/sdd/workflows/verify-work.md +97 -28
- package/sdk/dist/cli-transport.d.ts +19 -0
- package/sdk/dist/cli-transport.d.ts.map +1 -0
- package/sdk/dist/cli-transport.js +104 -0
- package/sdk/dist/cli-transport.js.map +1 -0
- package/sdk/dist/cli.d.ts +46 -0
- package/sdk/dist/cli.d.ts.map +1 -0
- package/sdk/dist/cli.js +511 -0
- package/sdk/dist/cli.js.map +1 -0
- package/sdk/dist/config.d.ts +84 -0
- package/sdk/dist/config.d.ts.map +1 -0
- package/sdk/dist/config.js +135 -0
- package/sdk/dist/config.js.map +1 -0
- package/sdk/dist/context-engine.d.ts +49 -0
- package/sdk/dist/context-engine.d.ts.map +1 -0
- package/sdk/dist/context-engine.js +142 -0
- package/sdk/dist/context-engine.js.map +1 -0
- package/sdk/dist/context-truncation.d.ts +33 -0
- package/sdk/dist/context-truncation.d.ts.map +1 -0
- package/sdk/dist/context-truncation.js +197 -0
- package/sdk/dist/context-truncation.js.map +1 -0
- package/sdk/dist/errors.d.ts +46 -0
- package/sdk/dist/errors.d.ts.map +1 -0
- package/sdk/dist/errors.js +64 -0
- package/sdk/dist/errors.js.map +1 -0
- package/sdk/dist/event-stream.d.ts +53 -0
- package/sdk/dist/event-stream.d.ts.map +1 -0
- package/sdk/dist/event-stream.js +321 -0
- package/sdk/dist/event-stream.js.map +1 -0
- package/sdk/dist/golden/capture.d.ts +15 -0
- package/sdk/dist/golden/capture.d.ts.map +1 -0
- package/sdk/dist/golden/capture.js +67 -0
- package/sdk/dist/golden/capture.js.map +1 -0
- package/sdk/dist/golden/golden-integration-covered.d.ts +6 -0
- package/sdk/dist/golden/golden-integration-covered.d.ts.map +1 -0
- package/sdk/dist/golden/golden-integration-covered.js +30 -0
- package/sdk/dist/golden/golden-integration-covered.js.map +1 -0
- package/sdk/dist/golden/golden-mutation-covered.d.ts +7 -0
- package/sdk/dist/golden/golden-mutation-covered.d.ts.map +1 -0
- package/sdk/dist/golden/golden-mutation-covered.js +17 -0
- package/sdk/dist/golden/golden-mutation-covered.js.map +1 -0
- package/sdk/dist/golden/golden-policy.d.ts +10 -0
- package/sdk/dist/golden/golden-policy.d.ts.map +1 -0
- package/sdk/dist/golden/golden-policy.js +98 -0
- package/sdk/dist/golden/golden-policy.js.map +1 -0
- package/sdk/dist/golden/init-golden-normalize.d.ts +8 -0
- package/sdk/dist/golden/init-golden-normalize.d.ts.map +1 -0
- package/sdk/dist/golden/init-golden-normalize.js +14 -0
- package/sdk/dist/golden/init-golden-normalize.js.map +1 -0
- package/sdk/dist/golden/read-only-golden-rows.d.ts +20 -0
- package/sdk/dist/golden/read-only-golden-rows.d.ts.map +1 -0
- package/sdk/dist/golden/read-only-golden-rows.js +67 -0
- package/sdk/dist/golden/read-only-golden-rows.js.map +1 -0
- package/sdk/dist/golden/registry-canonical-commands.d.ts +6 -0
- package/sdk/dist/golden/registry-canonical-commands.d.ts.map +1 -0
- package/sdk/dist/golden/registry-canonical-commands.js +30 -0
- package/sdk/dist/golden/registry-canonical-commands.js.map +1 -0
- package/sdk/dist/index.d.ts +125 -0
- package/sdk/dist/index.d.ts.map +1 -0
- package/sdk/dist/index.js +298 -0
- package/sdk/dist/index.js.map +1 -0
- package/sdk/dist/init-runner.d.ts +90 -0
- package/sdk/dist/init-runner.d.ts.map +1 -0
- package/sdk/dist/init-runner.js +613 -0
- package/sdk/dist/init-runner.js.map +1 -0
- package/sdk/dist/logger.d.ts +50 -0
- package/sdk/dist/logger.d.ts.map +1 -0
- package/sdk/dist/logger.js +70 -0
- package/sdk/dist/logger.js.map +1 -0
- package/sdk/dist/model-catalog.d.ts +31 -0
- package/sdk/dist/model-catalog.d.ts.map +1 -0
- package/sdk/dist/model-catalog.js +31 -0
- package/sdk/dist/model-catalog.js.map +1 -0
- package/sdk/dist/phase-prompt.d.ts +72 -0
- package/sdk/dist/phase-prompt.d.ts.map +1 -0
- package/sdk/dist/phase-prompt.js +213 -0
- package/sdk/dist/phase-prompt.js.map +1 -0
- package/sdk/dist/phase-runner.d.ts +145 -0
- package/sdk/dist/phase-runner.d.ts.map +1 -0
- package/sdk/dist/phase-runner.js +1206 -0
- package/sdk/dist/phase-runner.js.map +1 -0
- package/sdk/dist/plan-parser.d.ts +55 -0
- package/sdk/dist/plan-parser.d.ts.map +1 -0
- package/sdk/dist/plan-parser.js +389 -0
- package/sdk/dist/plan-parser.js.map +1 -0
- package/sdk/dist/planning-journal.d.ts +64 -0
- package/sdk/dist/planning-journal.d.ts.map +1 -0
- package/sdk/dist/planning-journal.js +88 -0
- package/sdk/dist/planning-journal.js.map +1 -0
- package/sdk/dist/planning-runtime.d.ts +67 -0
- package/sdk/dist/planning-runtime.d.ts.map +1 -0
- package/sdk/dist/planning-runtime.js +58 -0
- package/sdk/dist/planning-runtime.js.map +1 -0
- package/sdk/dist/prompt-builder.d.ts +44 -0
- package/sdk/dist/prompt-builder.d.ts.map +1 -0
- package/sdk/dist/prompt-builder.js +180 -0
- package/sdk/dist/prompt-builder.js.map +1 -0
- package/sdk/dist/prompt-sanitizer.d.ts +35 -0
- package/sdk/dist/prompt-sanitizer.d.ts.map +1 -0
- package/sdk/dist/prompt-sanitizer.js +101 -0
- package/sdk/dist/prompt-sanitizer.js.map +1 -0
- package/sdk/dist/query/active-workstream-store.d.ts +7 -0
- package/sdk/dist/query/active-workstream-store.d.ts.map +1 -0
- package/sdk/dist/query/active-workstream-store.js +56 -0
- package/sdk/dist/query/active-workstream-store.js.map +1 -0
- package/sdk/dist/query/agent-failure-classifier.d.ts +38 -0
- package/sdk/dist/query/agent-failure-classifier.d.ts.map +1 -0
- package/sdk/dist/query/agent-failure-classifier.js +83 -0
- package/sdk/dist/query/agent-failure-classifier.js.map +1 -0
- package/sdk/dist/query/audit-open.d.ts +46 -0
- package/sdk/dist/query/audit-open.d.ts.map +1 -0
- package/sdk/dist/query/audit-open.js +662 -0
- package/sdk/dist/query/audit-open.js.map +1 -0
- package/sdk/dist/query/check-auto-mode.d.ts +13 -0
- package/sdk/dist/query/check-auto-mode.d.ts.map +1 -0
- package/sdk/dist/query/check-auto-mode.js +40 -0
- package/sdk/dist/query/check-auto-mode.js.map +1 -0
- package/sdk/dist/query/check-completion.d.ts +10 -0
- package/sdk/dist/query/check-completion.d.ts.map +1 -0
- package/sdk/dist/query/check-completion.js +157 -0
- package/sdk/dist/query/check-completion.js.map +1 -0
- package/sdk/dist/query/check-decision-coverage.d.ts +33 -0
- package/sdk/dist/query/check-decision-coverage.d.ts.map +1 -0
- package/sdk/dist/query/check-decision-coverage.js +472 -0
- package/sdk/dist/query/check-decision-coverage.js.map +1 -0
- package/sdk/dist/query/check-gates.d.ts +10 -0
- package/sdk/dist/query/check-gates.d.ts.map +1 -0
- package/sdk/dist/query/check-gates.js +89 -0
- package/sdk/dist/query/check-gates.js.map +1 -0
- package/sdk/dist/query/check-ship-ready.d.ts +10 -0
- package/sdk/dist/query/check-ship-ready.d.ts.map +1 -0
- package/sdk/dist/query/check-ship-ready.js +93 -0
- package/sdk/dist/query/check-ship-ready.js.map +1 -0
- package/sdk/dist/query/check-verification-status.d.ts +10 -0
- package/sdk/dist/query/check-verification-status.d.ts.map +1 -0
- package/sdk/dist/query/check-verification-status.js +142 -0
- package/sdk/dist/query/check-verification-status.js.map +1 -0
- package/sdk/dist/query/command-aliases.generated.d.ts +31 -0
- package/sdk/dist/query/command-aliases.generated.d.ts.map +1 -0
- package/sdk/dist/query/command-aliases.generated.js +135 -0
- package/sdk/dist/query/command-aliases.generated.js.map +1 -0
- package/sdk/dist/query/command-catalog.d.ts +9 -0
- package/sdk/dist/query/command-catalog.d.ts.map +1 -0
- package/sdk/dist/query/command-catalog.js +17 -0
- package/sdk/dist/query/command-catalog.js.map +1 -0
- package/sdk/dist/query/command-definition.d.ts +19 -0
- package/sdk/dist/query/command-definition.d.ts.map +1 -0
- package/sdk/dist/query/command-definition.js +44 -0
- package/sdk/dist/query/command-definition.js.map +1 -0
- package/sdk/dist/query/command-family-handlers.d.ts +3 -0
- package/sdk/dist/query/command-family-handlers.d.ts.map +1 -0
- package/sdk/dist/query/command-family-handlers.js +94 -0
- package/sdk/dist/query/command-family-handlers.js.map +1 -0
- package/sdk/dist/query/command-manifest.d.ts +2 -0
- package/sdk/dist/query/command-manifest.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.init.d.ts +6 -0
- package/sdk/dist/query/command-manifest.init.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.init.js +23 -0
- package/sdk/dist/query/command-manifest.init.js.map +1 -0
- package/sdk/dist/query/command-manifest.js +17 -0
- package/sdk/dist/query/command-manifest.js.map +1 -0
- package/sdk/dist/query/command-manifest.non-family.d.ts +9 -0
- package/sdk/dist/query/command-manifest.non-family.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.non-family.js +59 -0
- package/sdk/dist/query/command-manifest.non-family.js.map +1 -0
- package/sdk/dist/query/command-manifest.phase.d.ts +6 -0
- package/sdk/dist/query/command-manifest.phase.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.phase.js +15 -0
- package/sdk/dist/query/command-manifest.phase.js.map +1 -0
- package/sdk/dist/query/command-manifest.phases.d.ts +7 -0
- package/sdk/dist/query/command-manifest.phases.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.phases.js +10 -0
- package/sdk/dist/query/command-manifest.phases.js.map +1 -0
- package/sdk/dist/query/command-manifest.roadmap.d.ts +6 -0
- package/sdk/dist/query/command-manifest.roadmap.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.roadmap.js +10 -0
- package/sdk/dist/query/command-manifest.roadmap.js.map +1 -0
- package/sdk/dist/query/command-manifest.state.d.ts +9 -0
- package/sdk/dist/query/command-manifest.state.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.state.js +30 -0
- package/sdk/dist/query/command-manifest.state.js.map +1 -0
- package/sdk/dist/query/command-manifest.types.d.ts +12 -0
- package/sdk/dist/query/command-manifest.types.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.types.js +2 -0
- package/sdk/dist/query/command-manifest.types.js.map +1 -0
- package/sdk/dist/query/command-manifest.validate.d.ts +6 -0
- package/sdk/dist/query/command-manifest.validate.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.validate.js +10 -0
- package/sdk/dist/query/command-manifest.validate.js.map +1 -0
- package/sdk/dist/query/command-manifest.verify.d.ts +6 -0
- package/sdk/dist/query/command-manifest.verify.d.ts.map +1 -0
- package/sdk/dist/query/command-manifest.verify.js +14 -0
- package/sdk/dist/query/command-manifest.verify.js.map +1 -0
- package/sdk/dist/query/command-static-catalog-domain.d.ts +3 -0
- package/sdk/dist/query/command-static-catalog-domain.d.ts.map +1 -0
- package/sdk/dist/query/command-static-catalog-domain.js +116 -0
- package/sdk/dist/query/command-static-catalog-domain.js.map +1 -0
- package/sdk/dist/query/command-static-catalog-foundation.d.ts +7 -0
- package/sdk/dist/query/command-static-catalog-foundation.d.ts.map +1 -0
- package/sdk/dist/query/command-static-catalog-foundation.js +98 -0
- package/sdk/dist/query/command-static-catalog-foundation.js.map +1 -0
- package/sdk/dist/query/command-topology.d.ts +32 -0
- package/sdk/dist/query/command-topology.d.ts.map +1 -0
- package/sdk/dist/query/command-topology.js +66 -0
- package/sdk/dist/query/command-topology.js.map +1 -0
- package/sdk/dist/query/commands-list.d.ts +14 -0
- package/sdk/dist/query/commands-list.d.ts.map +1 -0
- package/sdk/dist/query/commands-list.js +18 -0
- package/sdk/dist/query/commands-list.js.map +1 -0
- package/sdk/dist/query/commit.d.ts +79 -0
- package/sdk/dist/query/commit.d.ts.map +1 -0
- package/sdk/dist/query/commit.js +340 -0
- package/sdk/dist/query/commit.js.map +1 -0
- package/sdk/dist/query/config-gates.d.ts +12 -0
- package/sdk/dist/query/config-gates.d.ts.map +1 -0
- package/sdk/dist/query/config-gates.js +66 -0
- package/sdk/dist/query/config-gates.js.map +1 -0
- package/sdk/dist/query/config-mutation.d.ts +86 -0
- package/sdk/dist/query/config-mutation.d.ts.map +1 -0
- package/sdk/dist/query/config-mutation.js +518 -0
- package/sdk/dist/query/config-mutation.js.map +1 -0
- package/sdk/dist/query/config-query.d.ts +57 -0
- package/sdk/dist/query/config-query.d.ts.map +1 -0
- package/sdk/dist/query/config-query.js +208 -0
- package/sdk/dist/query/config-query.js.map +1 -0
- package/sdk/dist/query/config-schema.d.ts +36 -0
- package/sdk/dist/query/config-schema.d.ts.map +1 -0
- package/sdk/dist/query/config-schema.js +147 -0
- package/sdk/dist/query/config-schema.js.map +1 -0
- package/sdk/dist/query/decisions.d.ts +58 -0
- package/sdk/dist/query/decisions.d.ts.map +1 -0
- package/sdk/dist/query/decisions.js +161 -0
- package/sdk/dist/query/decisions.js.map +1 -0
- package/sdk/dist/query/detect-custom-files.d.ts +11 -0
- package/sdk/dist/query/detect-custom-files.d.ts.map +1 -0
- package/sdk/dist/query/detect-custom-files.js +89 -0
- package/sdk/dist/query/detect-custom-files.js.map +1 -0
- package/sdk/dist/query/detect-phase-type.d.ts +9 -0
- package/sdk/dist/query/detect-phase-type.d.ts.map +1 -0
- package/sdk/dist/query/detect-phase-type.js +124 -0
- package/sdk/dist/query/detect-phase-type.js.map +1 -0
- package/sdk/dist/query/docs-init.d.ts +26 -0
- package/sdk/dist/query/docs-init.d.ts.map +1 -0
- package/sdk/dist/query/docs-init.js +231 -0
- package/sdk/dist/query/docs-init.js.map +1 -0
- package/sdk/dist/query/fallow-audit.d.ts +44 -0
- package/sdk/dist/query/fallow-audit.d.ts.map +1 -0
- package/sdk/dist/query/fallow-audit.js +44 -0
- package/sdk/dist/query/fallow-audit.js.map +1 -0
- package/sdk/dist/query/frontmatter-mutation.d.ts +77 -0
- package/sdk/dist/query/frontmatter-mutation.d.ts.map +1 -0
- package/sdk/dist/query/frontmatter-mutation.js +317 -0
- package/sdk/dist/query/frontmatter-mutation.js.map +1 -0
- package/sdk/dist/query/frontmatter.d.ts +93 -0
- package/sdk/dist/query/frontmatter.d.ts.map +1 -0
- package/sdk/dist/query/frontmatter.js +365 -0
- package/sdk/dist/query/frontmatter.js.map +1 -0
- package/sdk/dist/query/helpers.d.ts +191 -0
- package/sdk/dist/query/helpers.d.ts.map +1 -0
- package/sdk/dist/query/helpers.js +613 -0
- package/sdk/dist/query/helpers.js.map +1 -0
- package/sdk/dist/query/index.d.ts +8 -0
- package/sdk/dist/query/index.d.ts.map +1 -0
- package/sdk/dist/query/index.js +6 -0
- package/sdk/dist/query/index.js.map +1 -0
- package/sdk/dist/query/init-complex.d.ts +47 -0
- package/sdk/dist/query/init-complex.d.ts.map +1 -0
- package/sdk/dist/query/init-complex.js +718 -0
- package/sdk/dist/query/init-complex.js.map +1 -0
- package/sdk/dist/query/init.d.ts +106 -0
- package/sdk/dist/query/init.d.ts.map +1 -0
- package/sdk/dist/query/init.js +1159 -0
- package/sdk/dist/query/init.js.map +1 -0
- package/sdk/dist/query/intel.d.ts +43 -0
- package/sdk/dist/query/intel.d.ts.map +1 -0
- package/sdk/dist/query/intel.js +416 -0
- package/sdk/dist/query/intel.js.map +1 -0
- package/sdk/dist/query/mutation-event-decorator.d.ts +5 -0
- package/sdk/dist/query/mutation-event-decorator.d.ts.map +1 -0
- package/sdk/dist/query/mutation-event-decorator.js +28 -0
- package/sdk/dist/query/mutation-event-decorator.js.map +1 -0
- package/sdk/dist/query/mutation-event-mapper.d.ts +4 -0
- package/sdk/dist/query/mutation-event-mapper.d.ts.map +1 -0
- package/sdk/dist/query/mutation-event-mapper.js +70 -0
- package/sdk/dist/query/mutation-event-mapper.js.map +1 -0
- package/sdk/dist/query/mvp.d.ts +113 -0
- package/sdk/dist/query/mvp.d.ts.map +1 -0
- package/sdk/dist/query/mvp.js +225 -0
- package/sdk/dist/query/mvp.js.map +1 -0
- package/sdk/dist/query/phase-filesystem-adapter.d.ts +4 -0
- package/sdk/dist/query/phase-filesystem-adapter.d.ts.map +1 -0
- package/sdk/dist/query/phase-filesystem-adapter.js +33 -0
- package/sdk/dist/query/phase-filesystem-adapter.js.map +1 -0
- package/sdk/dist/query/phase-lifecycle-policy.d.ts +34 -0
- package/sdk/dist/query/phase-lifecycle-policy.d.ts.map +1 -0
- package/sdk/dist/query/phase-lifecycle-policy.js +138 -0
- package/sdk/dist/query/phase-lifecycle-policy.js.map +1 -0
- package/sdk/dist/query/phase-lifecycle.d.ts +116 -0
- package/sdk/dist/query/phase-lifecycle.d.ts.map +1 -0
- package/sdk/dist/query/phase-lifecycle.js +1486 -0
- package/sdk/dist/query/phase-lifecycle.js.map +1 -0
- package/sdk/dist/query/phase-list-queries.d.ts +18 -0
- package/sdk/dist/query/phase-list-queries.d.ts.map +1 -0
- package/sdk/dist/query/phase-list-queries.js +129 -0
- package/sdk/dist/query/phase-list-queries.js.map +1 -0
- package/sdk/dist/query/phase-ready.d.ts +9 -0
- package/sdk/dist/query/phase-ready.d.ts.map +1 -0
- package/sdk/dist/query/phase-ready.js +132 -0
- package/sdk/dist/query/phase-ready.js.map +1 -0
- package/sdk/dist/query/phase-roadmap-mutation.d.ts +13 -0
- package/sdk/dist/query/phase-roadmap-mutation.d.ts.map +1 -0
- package/sdk/dist/query/phase-roadmap-mutation.js +65 -0
- package/sdk/dist/query/phase-roadmap-mutation.js.map +1 -0
- package/sdk/dist/query/phase.d.ts +48 -0
- package/sdk/dist/query/phase.d.ts.map +1 -0
- package/sdk/dist/query/phase.js +451 -0
- package/sdk/dist/query/phase.js.map +1 -0
- package/sdk/dist/query/pipeline.d.ts +53 -0
- package/sdk/dist/query/pipeline.d.ts.map +1 -0
- package/sdk/dist/query/pipeline.js +198 -0
- package/sdk/dist/query/pipeline.js.map +1 -0
- package/sdk/dist/query/plan-scan.d.ts +14 -0
- package/sdk/dist/query/plan-scan.d.ts.map +1 -0
- package/sdk/dist/query/plan-scan.js +70 -0
- package/sdk/dist/query/plan-scan.js.map +1 -0
- package/sdk/dist/query/plan-task-structure.d.ts +9 -0
- package/sdk/dist/query/plan-task-structure.d.ts.map +1 -0
- package/sdk/dist/query/plan-task-structure.js +59 -0
- package/sdk/dist/query/plan-task-structure.js.map +1 -0
- package/sdk/dist/query/profile-extract-messages.d.ts +40 -0
- package/sdk/dist/query/profile-extract-messages.d.ts.map +1 -0
- package/sdk/dist/query/profile-extract-messages.js +195 -0
- package/sdk/dist/query/profile-extract-messages.js.map +1 -0
- package/sdk/dist/query/profile-output.d.ts +11 -0
- package/sdk/dist/query/profile-output.d.ts.map +1 -0
- package/sdk/dist/query/profile-output.js +873 -0
- package/sdk/dist/query/profile-output.js.map +1 -0
- package/sdk/dist/query/profile-questionnaire-data.d.ts +21 -0
- package/sdk/dist/query/profile-questionnaire-data.d.ts.map +1 -0
- package/sdk/dist/query/profile-questionnaire-data.js +171 -0
- package/sdk/dist/query/profile-questionnaire-data.js.map +1 -0
- package/sdk/dist/query/profile-sample.d.ts +22 -0
- package/sdk/dist/query/profile-sample.d.ts.map +1 -0
- package/sdk/dist/query/profile-sample.js +136 -0
- package/sdk/dist/query/profile-sample.js.map +1 -0
- package/sdk/dist/query/profile-scan-sessions.d.ts +49 -0
- package/sdk/dist/query/profile-scan-sessions.d.ts.map +1 -0
- package/sdk/dist/query/profile-scan-sessions.js +137 -0
- package/sdk/dist/query/profile-scan-sessions.js.map +1 -0
- package/sdk/dist/query/profile.d.ts +61 -0
- package/sdk/dist/query/profile.d.ts.map +1 -0
- package/sdk/dist/query/profile.js +307 -0
- package/sdk/dist/query/profile.js.map +1 -0
- package/sdk/dist/query/progress.d.ts +77 -0
- package/sdk/dist/query/progress.d.ts.map +1 -0
- package/sdk/dist/query/progress.js +481 -0
- package/sdk/dist/query/progress.js.map +1 -0
- package/sdk/dist/query/query-cli-adapter.d.ts +8 -0
- package/sdk/dist/query/query-cli-adapter.d.ts.map +1 -0
- package/sdk/dist/query/query-cli-adapter.js +32 -0
- package/sdk/dist/query/query-cli-adapter.js.map +1 -0
- package/sdk/dist/query/query-cli-output.d.ts +9 -0
- package/sdk/dist/query/query-cli-output.d.ts.map +1 -0
- package/sdk/dist/query/query-cli-output.js +28 -0
- package/sdk/dist/query/query-cli-output.js.map +1 -0
- package/sdk/dist/query/query-command-diagnosis.d.ts +6 -0
- package/sdk/dist/query/query-command-diagnosis.d.ts.map +1 -0
- package/sdk/dist/query/query-command-diagnosis.js +6 -0
- package/sdk/dist/query/query-command-diagnosis.js.map +1 -0
- package/sdk/dist/query/query-command-resolution-strategy.d.ts +29 -0
- package/sdk/dist/query/query-command-resolution-strategy.d.ts.map +1 -0
- package/sdk/dist/query/query-command-resolution-strategy.js +103 -0
- package/sdk/dist/query/query-command-resolution-strategy.js.map +1 -0
- package/sdk/dist/query/query-command-semantics.d.ts +7 -0
- package/sdk/dist/query/query-command-semantics.d.ts.map +1 -0
- package/sdk/dist/query/query-command-semantics.js +7 -0
- package/sdk/dist/query/query-command-semantics.js.map +1 -0
- package/sdk/dist/query/query-dispatch-contract.d.ts +21 -0
- package/sdk/dist/query/query-dispatch-contract.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-contract.js +2 -0
- package/sdk/dist/query/query-dispatch-contract.js.map +1 -0
- package/sdk/dist/query/query-dispatch-error-mapper.d.ts +6 -0
- package/sdk/dist/query/query-dispatch-error-mapper.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-error-mapper.js +6 -0
- package/sdk/dist/query/query-dispatch-error-mapper.js.map +1 -0
- package/sdk/dist/query/query-dispatch-formatting.d.ts +6 -0
- package/sdk/dist/query/query-dispatch-formatting.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-formatting.js +6 -0
- package/sdk/dist/query/query-dispatch-formatting.js.map +1 -0
- package/sdk/dist/query/query-dispatch-input-validation.d.ts +6 -0
- package/sdk/dist/query/query-dispatch-input-validation.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-input-validation.js +6 -0
- package/sdk/dist/query/query-dispatch-input-validation.js.map +1 -0
- package/sdk/dist/query/query-dispatch-observability.d.ts +2 -0
- package/sdk/dist/query/query-dispatch-observability.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-observability.js +7 -0
- package/sdk/dist/query/query-dispatch-observability.js.map +1 -0
- package/sdk/dist/query/query-dispatch-plan.d.ts +6 -0
- package/sdk/dist/query/query-dispatch-plan.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-plan.js +6 -0
- package/sdk/dist/query/query-dispatch-plan.js.map +1 -0
- package/sdk/dist/query/query-dispatch-result-builder.d.ts +6 -0
- package/sdk/dist/query/query-dispatch-result-builder.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch-result-builder.js +6 -0
- package/sdk/dist/query/query-dispatch-result-builder.js.map +1 -0
- package/sdk/dist/query/query-dispatch.d.ts +48 -0
- package/sdk/dist/query/query-dispatch.d.ts.map +1 -0
- package/sdk/dist/query/query-dispatch.js +175 -0
- package/sdk/dist/query/query-dispatch.js.map +1 -0
- package/sdk/dist/query/query-error-details-schema.d.ts +19 -0
- package/sdk/dist/query/query-error-details-schema.d.ts.map +1 -0
- package/sdk/dist/query/query-error-details-schema.js +10 -0
- package/sdk/dist/query/query-error-details-schema.js.map +1 -0
- package/sdk/dist/query/query-error-taxonomy.d.ts +38 -0
- package/sdk/dist/query/query-error-taxonomy.d.ts.map +1 -0
- package/sdk/dist/query/query-error-taxonomy.js +74 -0
- package/sdk/dist/query/query-error-taxonomy.js.map +1 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.d.ts +14 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.js +33 -0
- package/sdk/dist/query/query-fallback-bridge-adapter.js.map +1 -0
- package/sdk/dist/query/query-fallback-executor.d.ts +11 -0
- package/sdk/dist/query/query-fallback-executor.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-executor.js +31 -0
- package/sdk/dist/query/query-fallback-executor.js.map +1 -0
- package/sdk/dist/query/query-fallback-output-classifier.d.ts +6 -0
- package/sdk/dist/query/query-fallback-output-classifier.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-output-classifier.js +27 -0
- package/sdk/dist/query/query-fallback-output-classifier.js.map +1 -0
- package/sdk/dist/query/query-fallback-policy.d.ts +6 -0
- package/sdk/dist/query/query-fallback-policy.d.ts.map +1 -0
- package/sdk/dist/query/query-fallback-policy.js +7 -0
- package/sdk/dist/query/query-fallback-policy.js.map +1 -0
- package/sdk/dist/query/query-native-dispatch-adapter.d.ts +7 -0
- package/sdk/dist/query/query-native-dispatch-adapter.d.ts.map +1 -0
- package/sdk/dist/query/query-native-dispatch-adapter.js +6 -0
- package/sdk/dist/query/query-native-dispatch-adapter.js.map +1 -0
- package/sdk/dist/query/query-policy-capability.d.ts +10 -0
- package/sdk/dist/query/query-policy-capability.d.ts.map +1 -0
- package/sdk/dist/query/query-policy-capability.js +17 -0
- package/sdk/dist/query/query-policy-capability.js.map +1 -0
- package/sdk/dist/query/query-runtime-context.d.ts +19 -0
- package/sdk/dist/query/query-runtime-context.d.ts.map +1 -0
- package/sdk/dist/query/query-runtime-context.js +31 -0
- package/sdk/dist/query/query-runtime-context.js.map +1 -0
- package/sdk/dist/query/query-unknown-command-hints.d.ts +2 -0
- package/sdk/dist/query/query-unknown-command-hints.d.ts.map +1 -0
- package/sdk/dist/query/query-unknown-command-hints.js +6 -0
- package/sdk/dist/query/query-unknown-command-hints.js.map +1 -0
- package/sdk/dist/query/registry-assembly-descriptor.d.ts +12 -0
- package/sdk/dist/query/registry-assembly-descriptor.d.ts.map +1 -0
- package/sdk/dist/query/registry-assembly-descriptor.js +61 -0
- package/sdk/dist/query/registry-assembly-descriptor.js.map +1 -0
- package/sdk/dist/query/registry-assembly-invariants.d.ts +30 -0
- package/sdk/dist/query/registry-assembly-invariants.d.ts.map +1 -0
- package/sdk/dist/query/registry-assembly-invariants.js +77 -0
- package/sdk/dist/query/registry-assembly-invariants.js.map +1 -0
- package/sdk/dist/query/registry-assembly.d.ts +10 -0
- package/sdk/dist/query/registry-assembly.d.ts.map +1 -0
- package/sdk/dist/query/registry-assembly.js +53 -0
- package/sdk/dist/query/registry-assembly.js.map +1 -0
- package/sdk/dist/query/registry.d.ts +90 -0
- package/sdk/dist/query/registry.d.ts.map +1 -0
- package/sdk/dist/query/registry.js +129 -0
- package/sdk/dist/query/registry.js.map +1 -0
- package/sdk/dist/query/requirements-extract-from-plans.d.ts +9 -0
- package/sdk/dist/query/requirements-extract-from-plans.d.ts.map +1 -0
- package/sdk/dist/query/requirements-extract-from-plans.js +76 -0
- package/sdk/dist/query/requirements-extract-from-plans.js.map +1 -0
- package/sdk/dist/query/roadmap-update-plan-progress.d.ts +11 -0
- package/sdk/dist/query/roadmap-update-plan-progress.d.ts.map +1 -0
- package/sdk/dist/query/roadmap-update-plan-progress.js +124 -0
- package/sdk/dist/query/roadmap-update-plan-progress.js.map +1 -0
- package/sdk/dist/query/roadmap.d.ts +137 -0
- package/sdk/dist/query/roadmap.d.ts.map +1 -0
- package/sdk/dist/query/roadmap.js +753 -0
- package/sdk/dist/query/roadmap.js.map +1 -0
- package/sdk/dist/query/route-next-action.d.ts +9 -0
- package/sdk/dist/query/route-next-action.d.ts.map +1 -0
- package/sdk/dist/query/route-next-action.js +318 -0
- package/sdk/dist/query/route-next-action.js.map +1 -0
- package/sdk/dist/query/schema-detect.d.ts +21 -0
- package/sdk/dist/query/schema-detect.d.ts.map +1 -0
- package/sdk/dist/query/schema-detect.js +146 -0
- package/sdk/dist/query/schema-detect.js.map +1 -0
- package/sdk/dist/query/secrets.d.ts +27 -0
- package/sdk/dist/query/secrets.d.ts.map +1 -0
- package/sdk/dist/query/secrets.js +42 -0
- package/sdk/dist/query/secrets.js.map +1 -0
- package/sdk/dist/query/skill-manifest.d.ts +50 -0
- package/sdk/dist/query/skill-manifest.d.ts.map +1 -0
- package/sdk/dist/query/skill-manifest.js +171 -0
- package/sdk/dist/query/skill-manifest.js.map +1 -0
- package/sdk/dist/query/skills.d.ts +27 -0
- package/sdk/dist/query/skills.d.ts.map +1 -0
- package/sdk/dist/query/skills.js +137 -0
- package/sdk/dist/query/skills.js.map +1 -0
- package/sdk/dist/query/state-document.d.ts +14 -0
- package/sdk/dist/query/state-document.d.ts.map +1 -0
- package/sdk/dist/query/state-document.js +110 -0
- package/sdk/dist/query/state-document.js.map +1 -0
- package/sdk/dist/query/state-mutation.d.ts +224 -0
- package/sdk/dist/query/state-mutation.d.ts.map +1 -0
- package/sdk/dist/query/state-mutation.js +1539 -0
- package/sdk/dist/query/state-mutation.js.map +1 -0
- package/sdk/dist/query/state-project-load.d.ts +23 -0
- package/sdk/dist/query/state-project-load.d.ts.map +1 -0
- package/sdk/dist/query/state-project-load.js +75 -0
- package/sdk/dist/query/state-project-load.js.map +1 -0
- package/sdk/dist/query/state.d.ts +78 -0
- package/sdk/dist/query/state.d.ts.map +1 -0
- package/sdk/dist/query/state.js +430 -0
- package/sdk/dist/query/state.js.map +1 -0
- package/sdk/dist/query/summary.d.ts +18 -0
- package/sdk/dist/query/summary.d.ts.map +1 -0
- package/sdk/dist/query/summary.js +249 -0
- package/sdk/dist/query/summary.js.map +1 -0
- package/sdk/dist/query/template.d.ts +46 -0
- package/sdk/dist/query/template.d.ts.map +1 -0
- package/sdk/dist/query/template.js +210 -0
- package/sdk/dist/query/template.js.map +1 -0
- package/sdk/dist/query/uat.d.ts +34 -0
- package/sdk/dist/query/uat.d.ts.map +1 -0
- package/sdk/dist/query/uat.js +339 -0
- package/sdk/dist/query/uat.js.map +1 -0
- package/sdk/dist/query/utils.d.ts +59 -0
- package/sdk/dist/query/utils.d.ts.map +1 -0
- package/sdk/dist/query/utils.js +74 -0
- package/sdk/dist/query/utils.js.map +1 -0
- package/sdk/dist/query/validate.d.ts +67 -0
- package/sdk/dist/query/validate.d.ts.map +1 -0
- package/sdk/dist/query/validate.js +908 -0
- package/sdk/dist/query/validate.js.map +1 -0
- package/sdk/dist/query/verify.d.ts +110 -0
- package/sdk/dist/query/verify.d.ts.map +1 -0
- package/sdk/dist/query/verify.js +631 -0
- package/sdk/dist/query/verify.js.map +1 -0
- package/sdk/dist/query/websearch.d.ts +24 -0
- package/sdk/dist/query/websearch.d.ts.map +1 -0
- package/sdk/dist/query/websearch.js +68 -0
- package/sdk/dist/query/websearch.js.map +1 -0
- package/sdk/dist/query/workspace.d.ts +62 -0
- package/sdk/dist/query/workspace.d.ts.map +1 -0
- package/sdk/dist/query/workspace.js +104 -0
- package/sdk/dist/query/workspace.js.map +1 -0
- package/sdk/dist/query/workstream-inventory.d.ts +52 -0
- package/sdk/dist/query/workstream-inventory.d.ts.map +1 -0
- package/sdk/dist/query/workstream-inventory.js +141 -0
- package/sdk/dist/query/workstream-inventory.js.map +1 -0
- package/sdk/dist/query/workstream.d.ts +35 -0
- package/sdk/dist/query/workstream.d.ts.map +1 -0
- package/sdk/dist/query/workstream.js +298 -0
- package/sdk/dist/query/workstream.js.map +1 -0
- package/sdk/dist/query/worktree.d.ts +3 -0
- package/sdk/dist/query/worktree.d.ts.map +1 -0
- package/sdk/dist/query/worktree.js +36 -0
- package/sdk/dist/query/worktree.js.map +1 -0
- package/sdk/dist/query-command-executor.d.ts +22 -0
- package/sdk/dist/query-command-executor.d.ts.map +1 -0
- package/sdk/dist/query-command-executor.js +22 -0
- package/sdk/dist/query-command-executor.js.map +1 -0
- package/sdk/dist/query-execution-policy.d.ts +24 -0
- package/sdk/dist/query-execution-policy.d.ts.map +1 -0
- package/sdk/dist/query-execution-policy.js +27 -0
- package/sdk/dist/query-execution-policy.js.map +1 -0
- package/sdk/dist/query-failure-classification.d.ts +9 -0
- package/sdk/dist/query-failure-classification.d.ts.map +1 -0
- package/sdk/dist/query-failure-classification.js +32 -0
- package/sdk/dist/query-failure-classification.js.map +1 -0
- package/sdk/dist/query-hotpath-methods.d.ts +19 -0
- package/sdk/dist/query-hotpath-methods.d.ts.map +1 -0
- package/sdk/dist/query-hotpath-methods.js +34 -0
- package/sdk/dist/query-hotpath-methods.js.map +1 -0
- package/sdk/dist/query-native-direct-adapter.d.ts +20 -0
- package/sdk/dist/query-native-direct-adapter.d.ts.map +1 -0
- package/sdk/dist/query-native-direct-adapter.js +52 -0
- package/sdk/dist/query-native-direct-adapter.js.map +1 -0
- package/sdk/dist/query-native-hotpath-adapter.d.ts +15 -0
- package/sdk/dist/query-native-hotpath-adapter.d.ts.map +1 -0
- package/sdk/dist/query-native-hotpath-adapter.js +32 -0
- package/sdk/dist/query-native-hotpath-adapter.js.map +1 -0
- package/sdk/dist/query-raw-output-projection.d.ts +6 -0
- package/sdk/dist/query-raw-output-projection.d.ts.map +1 -0
- package/sdk/dist/query-raw-output-projection.js +67 -0
- package/sdk/dist/query-raw-output-projection.js.map +1 -0
- package/sdk/dist/query-runtime-bridge.d.ts +61 -0
- package/sdk/dist/query-runtime-bridge.d.ts.map +1 -0
- package/sdk/dist/query-runtime-bridge.js +144 -0
- package/sdk/dist/query-runtime-bridge.js.map +1 -0
- package/sdk/dist/query-sdd-tools-path.d.ts +2 -0
- package/sdk/dist/query-sdd-tools-path.d.ts.map +1 -0
- package/sdk/dist/query-sdd-tools-path.js +2 -0
- package/sdk/dist/query-sdd-tools-path.js.map +1 -0
- package/sdk/dist/query-sdd-tools-runtime.d.ts +20 -0
- package/sdk/dist/query-sdd-tools-runtime.d.ts.map +1 -0
- package/sdk/dist/query-sdd-tools-runtime.js +47 -0
- package/sdk/dist/query-sdd-tools-runtime.js.map +1 -0
- package/sdk/dist/query-subprocess-adapter.d.ts +18 -0
- package/sdk/dist/query-subprocess-adapter.d.ts.map +1 -0
- package/sdk/dist/query-subprocess-adapter.js +92 -0
- package/sdk/dist/query-subprocess-adapter.js.map +1 -0
- package/sdk/dist/query-tools-error-factory.d.ts +16 -0
- package/sdk/dist/query-tools-error-factory.d.ts.map +1 -0
- package/sdk/dist/query-tools-error-factory.js +33 -0
- package/sdk/dist/query-tools-error-factory.js.map +1 -0
- package/sdk/dist/research-gate.d.ts +24 -0
- package/sdk/dist/research-gate.d.ts.map +1 -0
- package/sdk/dist/research-gate.js +70 -0
- package/sdk/dist/research-gate.js.map +1 -0
- package/sdk/dist/runtime-gate.d.ts +14 -0
- package/sdk/dist/runtime-gate.d.ts.map +1 -0
- package/sdk/dist/runtime-gate.js +48 -0
- package/sdk/dist/runtime-gate.js.map +1 -0
- package/sdk/dist/sdd-tools-error.d.ts +23 -0
- package/sdk/dist/sdd-tools-error.d.ts.map +1 -0
- package/sdk/dist/sdd-tools-error.js +29 -0
- package/sdk/dist/sdd-tools-error.js.map +1 -0
- package/sdk/dist/sdd-tools.d.ts +97 -0
- package/sdk/dist/sdd-tools.d.ts.map +1 -0
- package/sdk/dist/sdd-tools.js +168 -0
- package/sdk/dist/sdd-tools.js.map +1 -0
- package/sdk/dist/sdd-transport-policy.d.ts +10 -0
- package/sdk/dist/sdd-transport-policy.d.ts.map +1 -0
- package/sdk/dist/sdd-transport-policy.js +32 -0
- package/sdk/dist/sdd-transport-policy.js.map +1 -0
- package/sdk/dist/sdd-transport.d.ts +39 -0
- package/sdk/dist/sdd-transport.d.ts.map +1 -0
- package/sdk/dist/sdd-transport.js +78 -0
- package/sdk/dist/sdd-transport.js.map +1 -0
- package/sdk/dist/sdk-package-compatibility.d.ts +38 -0
- package/sdk/dist/sdk-package-compatibility.d.ts.map +1 -0
- package/sdk/dist/sdk-package-compatibility.js +90 -0
- package/sdk/dist/sdk-package-compatibility.js.map +1 -0
- package/sdk/dist/session-runner.d.ts +40 -0
- package/sdk/dist/session-runner.d.ts.map +1 -0
- package/sdk/dist/session-runner.js +274 -0
- package/sdk/dist/session-runner.js.map +1 -0
- package/sdk/dist/tool-scoping.d.ts +31 -0
- package/sdk/dist/tool-scoping.d.ts.map +1 -0
- package/sdk/dist/tool-scoping.js +54 -0
- package/sdk/dist/tool-scoping.js.map +1 -0
- package/sdk/dist/types.d.ts +794 -0
- package/sdk/dist/types.d.ts.map +1 -0
- package/sdk/dist/types.js +77 -0
- package/sdk/dist/types.js.map +1 -0
- package/sdk/dist/workstream-name-policy.d.ts +13 -0
- package/sdk/dist/workstream-name-policy.d.ts.map +1 -0
- package/sdk/dist/workstream-name-policy.js +24 -0
- package/sdk/dist/workstream-name-policy.js.map +1 -0
- package/sdk/dist/workstream-utils.d.ts +15 -0
- package/sdk/dist/workstream-utils.d.ts.map +1 -0
- package/sdk/dist/workstream-utils.js +21 -0
- package/sdk/dist/workstream-utils.js.map +1 -0
- package/sdk/dist/ws-transport.d.ts +32 -0
- package/sdk/dist/ws-transport.d.ts.map +1 -0
- package/sdk/dist/ws-transport.js +84 -0
- package/sdk/dist/ws-transport.js.map +1 -0
- package/sdk/package-lock.json +2502 -0
- package/sdk/package.json +57 -0
- package/sdk/prompts/templates/project.md +186 -0
- package/sdk/prompts/templates/requirements.md +231 -0
- package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
- package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
- package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
- package/sdk/prompts/templates/research-project/STACK.md +120 -0
- package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
- package/sdk/prompts/templates/roadmap.md +202 -0
- package/sdk/prompts/templates/state.md +175 -0
- package/sdk/shared/model-catalog.json +122 -0
- package/sdk/src/assembled-prompts.test.ts +349 -0
- package/sdk/src/bug-3591-sddtools-runtime-workstream.test.ts +179 -0
- package/sdk/src/cli-transport.test.ts +388 -0
- package/sdk/src/cli-transport.ts +130 -0
- package/sdk/src/cli.test.ts +426 -0
- package/sdk/src/cli.ts +589 -0
- package/sdk/src/config.test.ts +271 -0
- package/sdk/src/config.ts +218 -0
- package/sdk/src/context-engine.test.ts +295 -0
- package/sdk/src/context-engine.ts +170 -0
- package/sdk/src/context-truncation.test.ts +163 -0
- package/sdk/src/context-truncation.ts +233 -0
- package/sdk/src/e2e.integration.test.ts +181 -0
- package/sdk/src/errors.ts +72 -0
- package/sdk/src/event-stream.test.ts +661 -0
- package/sdk/src/event-stream.ts +441 -0
- package/sdk/src/golden/capture.ts +95 -0
- package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
- package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
- package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
- package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
- package/sdk/src/golden/golden-integration-covered.ts +30 -0
- package/sdk/src/golden/golden-mutation-covered.ts +17 -0
- package/sdk/src/golden/golden-policy.test.ts +8 -0
- package/sdk/src/golden/golden-policy.ts +120 -0
- package/sdk/src/golden/golden.integration.test.ts +677 -0
- package/sdk/src/golden/init-golden-normalize.ts +15 -0
- package/sdk/src/golden/read-only-golden-rows.ts +77 -0
- package/sdk/src/golden/read-only-parity.integration.test.ts +133 -0
- package/sdk/src/golden/registry-canonical-commands.ts +31 -0
- package/sdk/src/index.ts +352 -0
- package/sdk/src/init-e2e.integration.test.ts +138 -0
- package/sdk/src/init-runner.test.ts +740 -0
- package/sdk/src/init-runner.ts +734 -0
- package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
- package/sdk/src/logger.test.ts +149 -0
- package/sdk/src/logger.ts +113 -0
- package/sdk/src/milestone-runner.test.ts +421 -0
- package/sdk/src/model-catalog.ts +70 -0
- package/sdk/src/phase-prompt.test.ts +535 -0
- package/sdk/src/phase-prompt.ts +259 -0
- package/sdk/src/phase-runner-types.test.ts +421 -0
- package/sdk/src/phase-runner.integration.test.ts +377 -0
- package/sdk/src/phase-runner.test.ts +2720 -0
- package/sdk/src/phase-runner.ts +1442 -0
- package/sdk/src/plan-parser.test.ts +579 -0
- package/sdk/src/plan-parser.ts +431 -0
- package/sdk/src/planning-journal.test.ts +70 -0
- package/sdk/src/planning-journal.ts +153 -0
- package/sdk/src/planning-runtime.test.ts +29 -0
- package/sdk/src/planning-runtime.ts +100 -0
- package/sdk/src/prompt-builder.test.ts +318 -0
- package/sdk/src/prompt-builder.ts +218 -0
- package/sdk/src/prompt-sanitizer.test.ts +260 -0
- package/sdk/src/prompt-sanitizer.ts +116 -0
- package/sdk/src/query/QUERY-HANDLERS.md +349 -0
- package/sdk/src/query/active-workstream-store.ts +50 -0
- package/sdk/src/query/agent-failure-classifier.test.ts +157 -0
- package/sdk/src/query/agent-failure-classifier.ts +105 -0
- package/sdk/src/query/audit-open.ts +722 -0
- package/sdk/src/query/check-auto-mode.test.ts +77 -0
- package/sdk/src/query/check-auto-mode.ts +49 -0
- package/sdk/src/query/check-completion.test.ts +113 -0
- package/sdk/src/query/check-completion.ts +182 -0
- package/sdk/src/query/check-decision-coverage.test.ts +519 -0
- package/sdk/src/query/check-decision-coverage.ts +554 -0
- package/sdk/src/query/check-gates.test.ts +103 -0
- package/sdk/src/query/check-gates.ts +112 -0
- package/sdk/src/query/check-ship-ready.test.ts +111 -0
- package/sdk/src/query/check-ship-ready.ts +104 -0
- package/sdk/src/query/check-verification-status.test.ts +143 -0
- package/sdk/src/query/check-verification-status.ts +160 -0
- package/sdk/src/query/command-aliases.generated.ts +156 -0
- package/sdk/src/query/command-catalog.ts +31 -0
- package/sdk/src/query/command-definition.test.ts +47 -0
- package/sdk/src/query/command-definition.ts +70 -0
- package/sdk/src/query/command-family-handlers.ts +117 -0
- package/sdk/src/query/command-manifest.init.ts +24 -0
- package/sdk/src/query/command-manifest.non-family.ts +85 -0
- package/sdk/src/query/command-manifest.phase.ts +16 -0
- package/sdk/src/query/command-manifest.phases.ts +11 -0
- package/sdk/src/query/command-manifest.roadmap.ts +11 -0
- package/sdk/src/query/command-manifest.state.ts +31 -0
- package/sdk/src/query/command-manifest.ts +17 -0
- package/sdk/src/query/command-manifest.types.ts +13 -0
- package/sdk/src/query/command-manifest.validate.ts +11 -0
- package/sdk/src/query/command-manifest.verify.ts +15 -0
- package/sdk/src/query/command-resolution.test.ts +70 -0
- package/sdk/src/query/command-seam-coverage.test.ts +118 -0
- package/sdk/src/query/command-static-catalog-domain.ts +117 -0
- package/sdk/src/query/command-static-catalog-foundation.ts +103 -0
- package/sdk/src/query/command-topology.test.ts +28 -0
- package/sdk/src/query/command-topology.ts +114 -0
- package/sdk/src/query/commands-list.test.ts +36 -0
- package/sdk/src/query/commands-list.ts +19 -0
- package/sdk/src/query/commit.test.ts +485 -0
- package/sdk/src/query/commit.ts +383 -0
- package/sdk/src/query/config-gates.test.ts +89 -0
- package/sdk/src/query/config-gates.ts +69 -0
- package/sdk/src/query/config-mutation.test.ts +598 -0
- package/sdk/src/query/config-mutation.ts +575 -0
- package/sdk/src/query/config-query.test.ts +367 -0
- package/sdk/src/query/config-query.ts +244 -0
- package/sdk/src/query/config-schema.ts +159 -0
- package/sdk/src/query/decisions.test.ts +215 -0
- package/sdk/src/query/decisions.ts +192 -0
- package/sdk/src/query/decomposed-handlers.test.ts +431 -0
- package/sdk/src/query/detect-custom-files.test.ts +115 -0
- package/sdk/src/query/detect-custom-files.ts +96 -0
- package/sdk/src/query/detect-phase-type.test.ts +105 -0
- package/sdk/src/query/detect-phase-type.ts +141 -0
- package/sdk/src/query/docs-init.ts +258 -0
- package/sdk/src/query/fallow-audit.ts +88 -0
- package/sdk/src/query/frontmatter-array.test.ts +14 -0
- package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
- package/sdk/src/query/frontmatter-mutation.ts +343 -0
- package/sdk/src/query/frontmatter.test.ts +326 -0
- package/sdk/src/query/frontmatter.ts +395 -0
- package/sdk/src/query/helpers.test.ts +615 -0
- package/sdk/src/query/helpers.ts +646 -0
- package/sdk/src/query/index-thin-seam.test.ts +16 -0
- package/sdk/src/query/index.ts +9 -0
- package/sdk/src/query/init-complex.test.ts +616 -0
- package/sdk/src/query/init-complex.ts +799 -0
- package/sdk/src/query/init-progress-precedence.test.ts +177 -0
- package/sdk/src/query/init-workstream-milestone-op.test.ts +321 -0
- package/sdk/src/query/init.test.ts +792 -0
- package/sdk/src/query/init.ts +1262 -0
- package/sdk/src/query/intel.test.ts +90 -0
- package/sdk/src/query/intel.ts +404 -0
- package/sdk/src/query/mutation-event-decorator.test.ts +45 -0
- package/sdk/src/query/mutation-event-decorator.ts +37 -0
- package/sdk/src/query/mutation-event-mapper.test.ts +33 -0
- package/sdk/src/query/mutation-event-mapper.ts +102 -0
- package/sdk/src/query/mvp.test.ts +335 -0
- package/sdk/src/query/mvp.ts +292 -0
- package/sdk/src/query/normalize-query-command.test.ts +102 -0
- package/sdk/src/query/phase-filesystem-adapter.ts +35 -0
- package/sdk/src/query/phase-lifecycle-policy.ts +171 -0
- package/sdk/src/query/phase-lifecycle.test.ts +1750 -0
- package/sdk/src/query/phase-lifecycle.ts +1833 -0
- package/sdk/src/query/phase-list-queries.test.ts +88 -0
- package/sdk/src/query/phase-list-queries.ts +152 -0
- package/sdk/src/query/phase-ready.test.ts +65 -0
- package/sdk/src/query/phase-ready.ts +159 -0
- package/sdk/src/query/phase-roadmap-mutation.ts +77 -0
- package/sdk/src/query/phase.test.ts +651 -0
- package/sdk/src/query/phase.ts +550 -0
- package/sdk/src/query/pipeline.test.ts +169 -0
- package/sdk/src/query/pipeline.ts +243 -0
- package/sdk/src/query/plan-scan.test.ts +35 -0
- package/sdk/src/query/plan-scan.ts +82 -0
- package/sdk/src/query/plan-task-structure.test.ts +65 -0
- package/sdk/src/query/plan-task-structure.ts +63 -0
- package/sdk/src/query/policy-convergence.test.ts +28 -0
- package/sdk/src/query/profile-extract-messages.ts +247 -0
- package/sdk/src/query/profile-output.ts +929 -0
- package/sdk/src/query/profile-questionnaire-data.ts +181 -0
- package/sdk/src/query/profile-sample.ts +184 -0
- package/sdk/src/query/profile-scan-sessions.ts +174 -0
- package/sdk/src/query/profile.test.ts +136 -0
- package/sdk/src/query/profile.ts +337 -0
- package/sdk/src/query/progress.test.ts +156 -0
- package/sdk/src/query/progress.ts +566 -0
- package/sdk/src/query/query-cli-adapter.test.ts +79 -0
- package/sdk/src/query/query-cli-adapter.ts +39 -0
- package/sdk/src/query/query-cli-output.test.ts +33 -0
- package/sdk/src/query/query-cli-output.ts +35 -0
- package/sdk/src/query/query-command-diagnosis.test.ts +22 -0
- package/sdk/src/query/query-command-diagnosis.ts +5 -0
- package/sdk/src/query/query-command-resolution-strategy.test.ts +34 -0
- package/sdk/src/query/query-command-resolution-strategy.ts +121 -0
- package/sdk/src/query/query-command-semantics.test.ts +22 -0
- package/sdk/src/query/query-command-semantics.ts +22 -0
- package/sdk/src/query/query-dispatch-contract.ts +30 -0
- package/sdk/src/query/query-dispatch-error-mapper.test.ts +62 -0
- package/sdk/src/query/query-dispatch-error-mapper.ts +5 -0
- package/sdk/src/query/query-dispatch-formatting.test.ts +28 -0
- package/sdk/src/query/query-dispatch-formatting.ts +5 -0
- package/sdk/src/query/query-dispatch-input-validation.test.ts +23 -0
- package/sdk/src/query/query-dispatch-input-validation.ts +5 -0
- package/sdk/src/query/query-dispatch-observability.test.ts +10 -0
- package/sdk/src/query/query-dispatch-observability.ts +6 -0
- package/sdk/src/query/query-dispatch-plan.test.ts +25 -0
- package/sdk/src/query/query-dispatch-plan.ts +5 -0
- package/sdk/src/query/query-dispatch-result-builder.test.ts +16 -0
- package/sdk/src/query/query-dispatch-result-builder.ts +5 -0
- package/sdk/src/query/query-dispatch.test.ts +399 -0
- package/sdk/src/query/query-dispatch.ts +243 -0
- package/sdk/src/query/query-error-details-schema.ts +29 -0
- package/sdk/src/query/query-error-taxonomy.test.ts +39 -0
- package/sdk/src/query/query-error-taxonomy.ts +117 -0
- package/sdk/src/query/query-fallback-bridge-adapter.test.ts +32 -0
- package/sdk/src/query/query-fallback-bridge-adapter.ts +54 -0
- package/sdk/src/query/query-fallback-executor.test.ts +82 -0
- package/sdk/src/query/query-fallback-executor.ts +44 -0
- package/sdk/src/query/query-fallback-output-classifier.test.ts +36 -0
- package/sdk/src/query/query-fallback-output-classifier.ts +31 -0
- package/sdk/src/query/query-fallback-policy.test.ts +13 -0
- package/sdk/src/query/query-fallback-policy.ts +11 -0
- package/sdk/src/query/query-native-dispatch-adapter.ts +16 -0
- package/sdk/src/query/query-policy-capability.test.ts +10 -0
- package/sdk/src/query/query-policy-capability.ts +26 -0
- package/sdk/src/query/query-policy-snapshot.test.ts +9 -0
- package/sdk/src/query/query-registry-capability.test.ts +14 -0
- package/sdk/src/query/query-runtime-context.ts +44 -0
- package/sdk/src/query/query-unknown-command-hints.test.ts +9 -0
- package/sdk/src/query/query-unknown-command-hints.ts +5 -0
- package/sdk/src/query/registry-assembly-descriptor.ts +87 -0
- package/sdk/src/query/registry-assembly-invariants.ts +127 -0
- package/sdk/src/query/registry-assembly.test.ts +138 -0
- package/sdk/src/query/registry-assembly.ts +78 -0
- package/sdk/src/query/registry.test.ts +208 -0
- package/sdk/src/query/registry.ts +142 -0
- package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
- package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
- package/sdk/src/query/roadmap-update-plan-progress.test.ts +233 -0
- package/sdk/src/query/roadmap-update-plan-progress.ts +159 -0
- package/sdk/src/query/roadmap.test.ts +1181 -0
- package/sdk/src/query/roadmap.ts +894 -0
- package/sdk/src/query/route-next-action.test.ts +61 -0
- package/sdk/src/query/route-next-action.ts +345 -0
- package/sdk/src/query/schema-detect.ts +189 -0
- package/sdk/src/query/secrets.test.ts +66 -0
- package/sdk/src/query/secrets.ts +43 -0
- package/sdk/src/query/skill-manifest.test.ts +62 -0
- package/sdk/src/query/skill-manifest.ts +216 -0
- package/sdk/src/query/skills.test.ts +234 -0
- package/sdk/src/query/skills.ts +143 -0
- package/sdk/src/query/state-document.test.ts +197 -0
- package/sdk/src/query/state-document.ts +129 -0
- package/sdk/src/query/state-mutation.test.ts +1198 -0
- package/sdk/src/query/state-mutation.ts +1718 -0
- package/sdk/src/query/state-project-load.ts +80 -0
- package/sdk/src/query/state.test.ts +616 -0
- package/sdk/src/query/state.ts +463 -0
- package/sdk/src/query/sub-repos-root.integration.test.ts +79 -0
- package/sdk/src/query/summary.test.ts +95 -0
- package/sdk/src/query/summary.ts +296 -0
- package/sdk/src/query/template.test.ts +180 -0
- package/sdk/src/query/template.ts +242 -0
- package/sdk/src/query/uat.test.ts +77 -0
- package/sdk/src/query/uat.ts +365 -0
- package/sdk/src/query/utils.test.ts +82 -0
- package/sdk/src/query/utils.ts +106 -0
- package/sdk/src/query/validate.test.ts +831 -0
- package/sdk/src/query/validate.ts +952 -0
- package/sdk/src/query/verify.test.ts +414 -0
- package/sdk/src/query/verify.ts +692 -0
- package/sdk/src/query/websearch.test.ts +31 -0
- package/sdk/src/query/websearch.ts +82 -0
- package/sdk/src/query/workspace.test.ts +120 -0
- package/sdk/src/query/workspace.ts +145 -0
- package/sdk/src/query/workstream-inventory.ts +195 -0
- package/sdk/src/query/workstream.test.ts +153 -0
- package/sdk/src/query/workstream.ts +324 -0
- package/sdk/src/query/worktree.ts +39 -0
- package/sdk/src/query-command-executor.ts +31 -0
- package/sdk/src/query-execution-policy.test.ts +52 -0
- package/sdk/src/query-execution-policy.ts +46 -0
- package/sdk/src/query-failure-classification.test.ts +23 -0
- package/sdk/src/query-failure-classification.ts +42 -0
- package/sdk/src/query-hotpath-methods.ts +48 -0
- package/sdk/src/query-native-direct-adapter.test.ts +35 -0
- package/sdk/src/query-native-direct-adapter.ts +70 -0
- package/sdk/src/query-native-hotpath-adapter.test.ts +43 -0
- package/sdk/src/query-native-hotpath-adapter.ts +45 -0
- package/sdk/src/query-raw-output-projection.test.ts +39 -0
- package/sdk/src/query-raw-output-projection.ts +74 -0
- package/sdk/src/query-runtime-bridge.test.ts +150 -0
- package/sdk/src/query-runtime-bridge.ts +215 -0
- package/sdk/src/query-runtime-seam-coverage.test.ts +20 -0
- package/sdk/src/query-sdd-tools-path.ts +1 -0
- package/sdk/src/query-sdd-tools-runtime.ts +89 -0
- package/sdk/src/query-subprocess-adapter.test.ts +84 -0
- package/sdk/src/query-subprocess-adapter.ts +146 -0
- package/sdk/src/query-tools-error-factory.test.ts +35 -0
- package/sdk/src/query-tools-error-factory.ts +76 -0
- package/sdk/src/research-gate.test.ts +190 -0
- package/sdk/src/research-gate.ts +94 -0
- package/sdk/src/runtime-bridge-options.test.ts +33 -0
- package/sdk/src/runtime-gate.test.ts +84 -0
- package/sdk/src/runtime-gate.ts +52 -0
- package/sdk/src/sdd-tools-error.test.ts +21 -0
- package/sdk/src/sdd-tools-error.ts +65 -0
- package/sdk/src/sdd-tools.test.ts +472 -0
- package/sdk/src/sdd-tools.ts +237 -0
- package/sdk/src/sdd-transport-policy.test.ts +34 -0
- package/sdk/src/sdd-transport-policy.ts +48 -0
- package/sdk/src/sdd-transport.test.ts +292 -0
- package/sdk/src/sdd-transport.ts +117 -0
- package/sdk/src/sdk-package-compatibility.test.ts +97 -0
- package/sdk/src/sdk-package-compatibility.ts +141 -0
- package/sdk/src/session-runner.test.ts +164 -0
- package/sdk/src/session-runner.ts +327 -0
- package/sdk/src/tool-scoping.test.ts +160 -0
- package/sdk/src/tool-scoping.ts +61 -0
- package/sdk/src/types.ts +927 -0
- package/sdk/src/workflow-agent-skills-consistency.test.ts +98 -0
- package/sdk/src/workstream-name-policy.ts +24 -0
- package/sdk/src/workstream-utils.ts +21 -0
- package/sdk/src/ws-flag.test.ts +285 -0
- package/sdk/src/ws-transport.test.ts +161 -0
- package/sdk/src/ws-transport.ts +93 -0
- package/sdk/tsconfig.json +20 -0
- package/commands/sdd/add-backlog.md +0 -76
- package/commands/sdd/add-phase.md +0 -43
- package/commands/sdd/add-todo.md +0 -47
- package/commands/sdd/analyze-dependencies.md +0 -34
- package/commands/sdd/check-todos.md +0 -45
- package/commands/sdd/code-review-fix.md +0 -52
- package/commands/sdd/do.md +0 -30
- package/commands/sdd/from-sdd2.md +0 -45
- package/commands/sdd/insert-phase.md +0 -32
- package/commands/sdd/intel.md +0 -179
- package/commands/sdd/join-discord.md +0 -19
- package/commands/sdd/list-phase-assumptions.md +0 -46
- package/commands/sdd/list-workspaces.md +0 -19
- package/commands/sdd/new-workspace.md +0 -44
- package/commands/sdd/next.md +0 -26
- package/commands/sdd/note.md +0 -34
- package/commands/sdd/plan-milestone-gaps.md +0 -34
- package/commands/sdd/plant-seed.md +0 -28
- package/commands/sdd/remove-phase.md +0 -31
- package/commands/sdd/remove-workspace.md +0 -26
- package/commands/sdd/research-phase.md +0 -195
- package/commands/sdd/scan.md +0 -26
- package/commands/sdd/session-report.md +0 -19
- package/commands/sdd/set-profile.md +0 -12
- package/scripts/sync-upstream.sh +0 -56
- package/sdd/workflows/research-phase.md +0 -82
package/sdd/bin/lib/core.cjs
CHANGED
|
@@ -5,37 +5,25 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
let cachedControllingTtyToken = null;
|
|
28
|
-
let didProbeControllingTtyToken = false;
|
|
29
|
-
|
|
30
|
-
// Track all .planning/.lock files held by this process so they can be removed
|
|
31
|
-
// on exit. process.on('exit') fires even on process.exit(1), unlike try/finally
|
|
32
|
-
// which is skipped when error() calls process.exit(1) inside a locked region (#1916).
|
|
33
|
-
const _heldPlanningLocks = new Set();
|
|
34
|
-
process.on('exit', () => {
|
|
35
|
-
for (const lockPath of _heldPlanningLocks) {
|
|
36
|
-
try { fs.unlinkSync(lockPath); } catch { /* already gone */ }
|
|
37
|
-
}
|
|
38
|
-
});
|
|
8
|
+
const { execGit, platformWriteSync, platformReadSync, platformEnsureDir } = require('./shell-command-projection.cjs');
|
|
9
|
+
const { MODEL_PROFILES, AGENT_TO_PHASE_TYPE, VALID_PHASE_TYPES, AGENT_DEFAULT_TIERS, VALID_AGENT_TIERS, nextTier } = require('./model-profiles.cjs');
|
|
10
|
+
const { MODEL_ALIAS_MAP, RUNTIME_PROFILE_MAP, KNOWN_RUNTIMES, RUNTIMES_WITH_REASONING_EFFORT } = require('./model-catalog.cjs');
|
|
11
|
+
const {
|
|
12
|
+
resolveWorktreeContext,
|
|
13
|
+
parseWorktreePorcelain: parseWorktreePorcelainPolicy,
|
|
14
|
+
planWorktreePrune,
|
|
15
|
+
executeWorktreePrunePlan,
|
|
16
|
+
inspectWorktreeHealth,
|
|
17
|
+
} = require('./worktree-safety.cjs');
|
|
18
|
+
// Compatibility shim: new imports should use planning-workspace.cjs directly.
|
|
19
|
+
const {
|
|
20
|
+
planningDir,
|
|
21
|
+
planningRoot,
|
|
22
|
+
planningPaths,
|
|
23
|
+
withPlanningLock,
|
|
24
|
+
getActiveWorkstream,
|
|
25
|
+
setActiveWorkstream,
|
|
26
|
+
} = require('./planning-workspace.cjs');
|
|
39
27
|
|
|
40
28
|
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
41
29
|
|
|
@@ -119,7 +107,9 @@ function findProjectRoot(startDir) {
|
|
|
119
107
|
if (fs.existsSync(parentPlanning) && fs.statSync(parentPlanning).isDirectory()) {
|
|
120
108
|
const configPath = path.join(parentPlanning, 'config.json');
|
|
121
109
|
try {
|
|
122
|
-
const
|
|
110
|
+
const raw = platformReadSync(configPath);
|
|
111
|
+
if (raw === null) throw new Error('missing');
|
|
112
|
+
const config = JSON.parse(raw);
|
|
123
113
|
const subRepos = config.sub_repos || config.planning?.sub_repos || [];
|
|
124
114
|
|
|
125
115
|
// Check explicit sub_repos list
|
|
@@ -159,14 +149,25 @@ function findProjectRoot(startDir) {
|
|
|
159
149
|
* @param {number} opts.maxAgeMs - max age in ms before removal (default: 5 min)
|
|
160
150
|
* @param {boolean} opts.dirsOnly - if true, only remove directories (default: false)
|
|
161
151
|
*/
|
|
152
|
+
/**
|
|
153
|
+
* Dedicated SDD temp directory: path.join(os.tmpdir(), 'sdd').
|
|
154
|
+
* Created on first use. Keeps SDD temp files isolated from the system
|
|
155
|
+
* temp directory so reap scans only SDD files (#1975).
|
|
156
|
+
*/
|
|
157
|
+
const SDD_TEMP_DIR = path.join(require('os').tmpdir(), 'sdd');
|
|
158
|
+
|
|
159
|
+
function ensureSddTempDir() {
|
|
160
|
+
platformEnsureDir(SDD_TEMP_DIR);
|
|
161
|
+
}
|
|
162
|
+
|
|
162
163
|
function reapStaleTempFiles(prefix = 'sdd-', { maxAgeMs = 5 * 60 * 1000, dirsOnly = false } = {}) {
|
|
163
164
|
try {
|
|
164
|
-
|
|
165
|
+
ensureSddTempDir();
|
|
165
166
|
const now = Date.now();
|
|
166
|
-
const entries = fs.readdirSync(
|
|
167
|
+
const entries = fs.readdirSync(SDD_TEMP_DIR);
|
|
167
168
|
for (const entry of entries) {
|
|
168
169
|
if (!entry.startsWith(prefix)) continue;
|
|
169
|
-
const fullPath = path.join(
|
|
170
|
+
const fullPath = path.join(SDD_TEMP_DIR, entry);
|
|
170
171
|
try {
|
|
171
172
|
const stat = fs.statSync(fullPath);
|
|
172
173
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
@@ -195,8 +196,9 @@ function output(result, raw, rawValue) {
|
|
|
195
196
|
// Write to tmpfile and output the path prefixed with @file: so callers can detect it.
|
|
196
197
|
if (json.length > 50000) {
|
|
197
198
|
reapStaleTempFiles();
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
ensureSddTempDir();
|
|
200
|
+
const tmpPath = path.join(SDD_TEMP_DIR, `sdd-${Date.now()}.json`);
|
|
201
|
+
platformWriteSync(tmpPath, json);
|
|
200
202
|
data = '@file:' + tmpPath;
|
|
201
203
|
} else {
|
|
202
204
|
data = json;
|
|
@@ -209,21 +211,73 @@ function output(result, raw, rawValue) {
|
|
|
209
211
|
fs.writeSync(1, data);
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Frozen enum of typed reason codes used by error() for structured errors.
|
|
216
|
+
* Each subcommand contributes its own codes; the enum exists so tests can
|
|
217
|
+
* assert against typed values instead of grepping stderr (#2974).
|
|
218
|
+
*
|
|
219
|
+
* Adding a new code:
|
|
220
|
+
* - Pick a snake_case lowercase value (the JSON wire form)
|
|
221
|
+
* - Group by subsystem prefix (CONFIG_*, SDK_*, etc)
|
|
222
|
+
* - Pass it to error(msg, ERROR_REASON.NEW_CODE) at the call site
|
|
223
|
+
*/
|
|
224
|
+
const ERROR_REASON = Object.freeze({
|
|
225
|
+
// config-get / config-set
|
|
226
|
+
CONFIG_KEY_NOT_FOUND: 'config_key_not_found',
|
|
227
|
+
CONFIG_NO_FILE: 'config_no_file',
|
|
228
|
+
CONFIG_PARSE_FAILED: 'config_parse_failed',
|
|
229
|
+
CONFIG_INVALID_KEY: 'config_invalid_key',
|
|
230
|
+
// SDK / sdd-tools dispatch
|
|
231
|
+
SDK_FAIL_FAST: 'sdk_fail_fast',
|
|
232
|
+
SDK_UNKNOWN_COMMAND: 'sdk_unknown_command',
|
|
233
|
+
SDK_MISSING_ARG: 'sdk_missing_arg',
|
|
234
|
+
// workflow / phase
|
|
235
|
+
PHASE_NOT_FOUND: 'phase_not_found',
|
|
236
|
+
SUMMARY_NO_PLANNING: 'summary_no_planning',
|
|
237
|
+
// graphify
|
|
238
|
+
GRAPHIFY_NO_GRAPH: 'graphify_no_graph',
|
|
239
|
+
GRAPHIFY_INVALID_QUERY: 'graphify_invalid_query',
|
|
240
|
+
// hooks
|
|
241
|
+
HOOKS_OPT_OUT: 'hooks_opt_out',
|
|
242
|
+
// security-scan
|
|
243
|
+
SECURITY_SCAN_FAILED: 'security_scan_failed',
|
|
244
|
+
// generic
|
|
245
|
+
USAGE: 'usage',
|
|
246
|
+
UNKNOWN: 'unknown',
|
|
247
|
+
});
|
|
216
248
|
|
|
217
|
-
|
|
249
|
+
/**
|
|
250
|
+
* Process-level flag: when true, error() emits structured JSON to stderr
|
|
251
|
+
* instead of plain "Error: <message>" text. Set by sdd-tools.cjs when the
|
|
252
|
+
* CLI is invoked with `--json-errors`. Tests opt in to typed-IR error
|
|
253
|
+
* assertions by passing that flag and parsing the JSON.
|
|
254
|
+
*
|
|
255
|
+
* Default off so existing callers and human operators keep their plain-text
|
|
256
|
+
* diagnostics. The structured form is opt-in for tooling and tests (#2974).
|
|
257
|
+
*/
|
|
258
|
+
let _jsonErrorMode = false;
|
|
259
|
+
function setJsonErrorMode(v) { _jsonErrorMode = !!v; }
|
|
260
|
+
function getJsonErrorMode() { return _jsonErrorMode; }
|
|
218
261
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Emit an error and exit. When the second argument is provided it must be
|
|
264
|
+
* a value from ERROR_REASON; tests can assert on `result.reason`. When the
|
|
265
|
+
* process is in JSON-error mode, stderr receives `{ ok: false, reason,
|
|
266
|
+
* message }` so callers can parse it; otherwise stderr keeps the plain
|
|
267
|
+
* text form for human operators.
|
|
268
|
+
*/
|
|
269
|
+
function error(message, reason = ERROR_REASON.UNKNOWN) {
|
|
270
|
+
if (_jsonErrorMode) {
|
|
271
|
+
const payload = JSON.stringify({ ok: false, reason, message }) + '\n';
|
|
272
|
+
fs.writeSync(2, payload);
|
|
273
|
+
} else {
|
|
274
|
+
fs.writeSync(2, 'Error: ' + message + '\n');
|
|
224
275
|
}
|
|
276
|
+
process.exit(1);
|
|
225
277
|
}
|
|
226
278
|
|
|
279
|
+
// ─── File & Config utilities ──────────────────────────────────────────────────
|
|
280
|
+
|
|
227
281
|
/**
|
|
228
282
|
* Canonical config defaults. Single source of truth — imported by config.cjs and verify.cjs.
|
|
229
283
|
*/
|
|
@@ -251,79 +305,189 @@ const CONFIG_DEFAULTS = {
|
|
|
251
305
|
phase_naming: 'sequential', // 'sequential' (default, auto-increment) or 'custom' (arbitrary string IDs)
|
|
252
306
|
project_code: null, // optional short prefix for phase dirs (e.g., 'CK' → 'CK-01-foundation')
|
|
253
307
|
subagent_timeout: 300000, // 5 min default; increase for large codebases or slower models (ms)
|
|
308
|
+
security_enforcement: true, // workflow.security_enforcement — threat-model-anchored security verification via /sdd:secure-phase
|
|
309
|
+
security_asvs_level: 1, // workflow.security_asvs_level — OWASP ASVS verification level (1=opportunistic, 2=standard, 3=comprehensive)
|
|
310
|
+
security_block_on: 'high', // workflow.security_block_on — minimum severity that blocks phase advancement ('high' | 'medium' | 'low')
|
|
311
|
+
post_planning_gaps: true, // workflow.post_planning_gaps — unified post-planning gap report (#2493): scan REQUIREMENTS.md + CONTEXT.md decisions vs all PLAN.md files
|
|
254
312
|
};
|
|
255
313
|
|
|
256
|
-
|
|
257
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Deep-merge two plain config objects. `overlay` wins on key conflict.
|
|
316
|
+
* Explicit `null` in overlay overrides base (null means "unset this key").
|
|
317
|
+
* Arrays are replaced, not merged. Non-object primitives use overlay value.
|
|
318
|
+
*
|
|
319
|
+
* Note: `undefined` in overlay is treated as "no value provided" and falls
|
|
320
|
+
* back to base (preserves inheritance). Explicit `null` overrides base.
|
|
321
|
+
*/
|
|
322
|
+
function _deepMergeConfig(base, overlay) {
|
|
323
|
+
if (overlay === null || overlay === undefined) return overlay;
|
|
324
|
+
if (typeof base !== 'object' || typeof overlay !== 'object') return overlay;
|
|
325
|
+
const result = { ...base };
|
|
326
|
+
for (const key of Object.keys(overlay)) {
|
|
327
|
+
if (overlay[key] !== null && typeof overlay[key] === 'object' && !Array.isArray(overlay[key])) {
|
|
328
|
+
result[key] = _deepMergeConfig(base[key] ?? {}, overlay[key]);
|
|
329
|
+
} else {
|
|
330
|
+
result[key] = overlay[key];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function loadConfig(cwd, options = {}) {
|
|
337
|
+
const activeWorkstream = Object.prototype.hasOwnProperty.call(options, 'workstream')
|
|
338
|
+
? options.workstream
|
|
339
|
+
: (process.env.SDD_WORKSTREAM || null);
|
|
340
|
+
// When SDD_WORKSTREAM is set, load root config first so workstream config
|
|
341
|
+
// can inherit from it. This prevents users from duplicating model_overrides,
|
|
342
|
+
// workflow.*, etc. across every workstream config (#2714).
|
|
343
|
+
const ws = activeWorkstream;
|
|
344
|
+
let rootParsed = null;
|
|
345
|
+
if (ws) {
|
|
346
|
+
const rootConfigPath = path.join(planningRoot(cwd), 'config.json');
|
|
347
|
+
try {
|
|
348
|
+
const raw = platformReadSync(rootConfigPath);
|
|
349
|
+
if (raw === null) throw new Error('missing');
|
|
350
|
+
rootParsed = JSON.parse(raw);
|
|
351
|
+
if (Object.prototype.hasOwnProperty.call(rootParsed, 'branching_strategy')) {
|
|
352
|
+
if (!rootParsed.git) rootParsed.git = {};
|
|
353
|
+
if (rootParsed.git.branching_strategy === undefined) {
|
|
354
|
+
rootParsed.git.branching_strategy = rootParsed.branching_strategy;
|
|
355
|
+
}
|
|
356
|
+
delete rootParsed.branching_strategy;
|
|
357
|
+
try { platformWriteSync(rootConfigPath, JSON.stringify(rootParsed, null, 2)); } catch {}
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
// Root config missing or unparseable — workstream config stands alone
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const configPath = path.join(planningDir(cwd, ws), 'config.json');
|
|
258
365
|
const defaults = CONFIG_DEFAULTS;
|
|
259
366
|
|
|
260
367
|
try {
|
|
261
|
-
const raw =
|
|
262
|
-
|
|
368
|
+
const raw = platformReadSync(configPath);
|
|
369
|
+
if (raw === null) throw new Error('missing');
|
|
370
|
+
// `fileData` is the parsed content of the config.json file on disk — used
|
|
371
|
+
// for migrations and writes so we never persist merged values back to disk.
|
|
372
|
+
const fileData = JSON.parse(raw);
|
|
263
373
|
|
|
264
374
|
// Migrate deprecated "depth" key to "granularity" with value mapping
|
|
265
|
-
if ('depth' in
|
|
375
|
+
if ('depth' in fileData && !('granularity' in fileData)) {
|
|
266
376
|
const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
|
|
267
|
-
|
|
268
|
-
delete
|
|
269
|
-
try {
|
|
377
|
+
fileData.granularity = depthToGranularity[fileData.depth] || fileData.depth;
|
|
378
|
+
delete fileData.depth;
|
|
379
|
+
try { platformWriteSync(configPath, JSON.stringify(fileData, null, 2)); } catch { /* intentionally empty */ }
|
|
270
380
|
}
|
|
271
381
|
|
|
272
382
|
// Auto-detect and sync sub_repos: scan for child directories with .git
|
|
273
383
|
let configDirty = false;
|
|
274
384
|
|
|
275
|
-
// Migrate legacy "multiRepo: true" boolean → sub_repos array
|
|
276
|
-
|
|
385
|
+
// Migrate legacy "multiRepo: true" boolean → planning.sub_repos array.
|
|
386
|
+
// Canonical location is planning.sub_repos (#2561); writing to top-level
|
|
387
|
+
// would be flagged as unknown by the validator below (#2638).
|
|
388
|
+
if (fileData.multiRepo === true && !fileData.sub_repos && !fileData.planning?.sub_repos) {
|
|
277
389
|
const detected = detectSubRepos(cwd);
|
|
278
390
|
if (detected.length > 0) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
delete
|
|
391
|
+
if (!fileData.planning) fileData.planning = {};
|
|
392
|
+
fileData.planning.sub_repos = detected;
|
|
393
|
+
fileData.planning.commit_docs = false;
|
|
394
|
+
delete fileData.multiRepo;
|
|
283
395
|
configDirty = true;
|
|
284
396
|
}
|
|
285
397
|
}
|
|
286
398
|
|
|
287
|
-
//
|
|
288
|
-
|
|
399
|
+
// Self-heal legacy/buggy installs: strip any stale top-level sub_repos,
|
|
400
|
+
// preserving its value as the planning.sub_repos seed if that slot is empty.
|
|
401
|
+
if (Object.prototype.hasOwnProperty.call(fileData, 'sub_repos')) {
|
|
402
|
+
if (!fileData.planning) fileData.planning = {};
|
|
403
|
+
if (!fileData.planning.sub_repos) {
|
|
404
|
+
fileData.planning.sub_repos = fileData.sub_repos;
|
|
405
|
+
}
|
|
406
|
+
delete fileData.sub_repos;
|
|
407
|
+
configDirty = true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// #3523 — Migrate legacy top-level branching_strategy → git.branching_strategy.
|
|
411
|
+
// Canonical location is git.branching_strategy (per config-schema.cjs); writing
|
|
412
|
+
// at the top level trips the unknown-key warning even though loadConfig:485 actively
|
|
413
|
+
// reads it via the nested fallback. This migration mirrors the multiRepo → sub_repos
|
|
414
|
+
// precedent: graft then delete so the warning never fires again on this project.
|
|
415
|
+
// The nested value wins if already set (matches SDK mergeDefaults precedence, PR #3116).
|
|
416
|
+
if (Object.prototype.hasOwnProperty.call(fileData, 'branching_strategy')) {
|
|
417
|
+
if (!fileData.git) fileData.git = {};
|
|
418
|
+
if (fileData.git.branching_strategy === undefined) {
|
|
419
|
+
fileData.git.branching_strategy = fileData.branching_strategy;
|
|
420
|
+
}
|
|
421
|
+
delete fileData.branching_strategy;
|
|
422
|
+
configDirty = true;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Keep planning.sub_repos in sync with actual filesystem
|
|
426
|
+
const currentSubRepos = fileData.planning?.sub_repos || [];
|
|
289
427
|
if (Array.isArray(currentSubRepos) && currentSubRepos.length > 0) {
|
|
290
428
|
const detected = detectSubRepos(cwd);
|
|
291
429
|
if (detected.length > 0) {
|
|
292
430
|
const sorted = [...currentSubRepos].sort();
|
|
293
431
|
if (JSON.stringify(sorted) !== JSON.stringify(detected)) {
|
|
294
|
-
|
|
432
|
+
if (!fileData.planning) fileData.planning = {};
|
|
433
|
+
fileData.planning.sub_repos = detected;
|
|
295
434
|
configDirty = true;
|
|
296
435
|
}
|
|
297
436
|
}
|
|
298
437
|
}
|
|
299
438
|
|
|
300
|
-
// Persist sub_repos changes (migration or sync)
|
|
439
|
+
// Persist sub_repos changes (migration or sync) — write only the on-disk
|
|
440
|
+
// file contents, never the merged result, to avoid polluting workstream configs.
|
|
301
441
|
if (configDirty) {
|
|
302
|
-
try {
|
|
442
|
+
try { platformWriteSync(configPath, JSON.stringify(fileData, null, 2)); } catch {}
|
|
303
443
|
}
|
|
304
444
|
|
|
445
|
+
// Now apply root→workstream inheritance. `parsed` is the effective config
|
|
446
|
+
// used for value extraction below; fileData is kept for disk writes only.
|
|
447
|
+
const parsed = rootParsed ? _deepMergeConfig(rootParsed, fileData) : fileData;
|
|
448
|
+
|
|
305
449
|
// Warn about unrecognized top-level keys so users don't silently lose config.
|
|
306
450
|
// Derived from config-set's VALID_CONFIG_KEYS (canonical source) plus internal-only
|
|
307
451
|
// keys that loadConfig handles but config-set doesn't expose. This avoids maintaining
|
|
308
452
|
// a hardcoded duplicate that drifts when new config keys are added.
|
|
309
|
-
|
|
453
|
+
// DYNAMIC_KEY_PATTERNS supplies topLevel for each pattern so adding a new
|
|
454
|
+
// dynamic-pattern namespace to config-schema.cjs automatically updates this set
|
|
455
|
+
// — no more drift between the read side and the write side (#2687).
|
|
456
|
+
const { VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS } = require('./config-schema.cjs');
|
|
310
457
|
const KNOWN_TOP_LEVEL = new Set([
|
|
311
458
|
// Extract top-level key names from dot-notation paths (e.g., 'workflow.research' → 'workflow')
|
|
312
459
|
...[...VALID_CONFIG_KEYS].map(k => k.split('.')[0]),
|
|
313
|
-
//
|
|
314
|
-
|
|
460
|
+
// Dynamic-pattern top-level containers (e.g. review, model_profile_overrides)
|
|
461
|
+
...DYNAMIC_KEY_PATTERNS.map(p => p.topLevel),
|
|
315
462
|
// Internal keys loadConfig reads but config-set doesn't expose
|
|
316
|
-
'model_overrides', '
|
|
463
|
+
'model_overrides', 'context_window', 'resolve_model_ids', 'claude_md_path',
|
|
317
464
|
// Deprecated keys (still accepted for migration, not in config-set)
|
|
318
|
-
'
|
|
465
|
+
// 'branching_strategy' is kept here as a safety net: it is migrated to
|
|
466
|
+
// git.branching_strategy above (#3523), but on the first read of a root
|
|
467
|
+
// config that feeds into a workstream merge, `parsed` may still surface it.
|
|
468
|
+
'depth', 'multiRepo', 'branching_strategy',
|
|
319
469
|
]);
|
|
320
470
|
const unknownKeys = Object.keys(parsed).filter(k => !KNOWN_TOP_LEVEL.has(k));
|
|
321
471
|
if (unknownKeys.length > 0) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
472
|
+
// Deduplicate: a single `init phase-op N` invocation calls loadConfig twice
|
|
473
|
+
// (once for the sub-command setup, once for git-config resolution). Guard with
|
|
474
|
+
// a module-level Set so the same message never fires more than once per process.
|
|
475
|
+
const warnKey = unknownKeys.join(',');
|
|
476
|
+
if (!_warnedUnknownConfigKeys.has(warnKey)) {
|
|
477
|
+
_warnedUnknownConfigKeys.add(warnKey);
|
|
478
|
+
process.stderr.write(
|
|
479
|
+
`sdd-tools: warning: unknown config key(s) in .planning/config.json: ${unknownKeys.join(', ')} — these will be ignored\n`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
325
482
|
}
|
|
326
483
|
|
|
484
|
+
// #2517 — Validate runtime/tier values for keys that loadConfig handles but
|
|
485
|
+
// can be edited directly into config.json (bypassing config-set's enum check).
|
|
486
|
+
// This catches typos like `runtime: "codx"` and `model_profile_overrides.codex.banana`
|
|
487
|
+
// at read time without rejecting back-compat values from new runtimes
|
|
488
|
+
// (review findings #10, #13).
|
|
489
|
+
_warnUnknownProfileOverrides(parsed, '.planning/config.json');
|
|
490
|
+
|
|
327
491
|
const get = (key, nested) => {
|
|
328
492
|
if (parsed[key] !== undefined) return parsed[key];
|
|
329
493
|
if (nested && parsed[nested.section] && parsed[nested.section][nested.field] !== undefined) {
|
|
@@ -359,11 +523,17 @@ function loadConfig(cwd) {
|
|
|
359
523
|
plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker,
|
|
360
524
|
verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier,
|
|
361
525
|
nyquist_validation: get('nyquist_validation', { section: 'workflow', field: 'nyquist_validation' }) ?? defaults.nyquist_validation,
|
|
526
|
+
post_planning_gaps: get('post_planning_gaps', { section: 'workflow', field: 'post_planning_gaps' }) ?? defaults.post_planning_gaps,
|
|
362
527
|
parallelization,
|
|
363
528
|
brave_search: get('brave_search') ?? defaults.brave_search,
|
|
364
529
|
firecrawl: get('firecrawl') ?? defaults.firecrawl,
|
|
365
530
|
exa_search: get('exa_search') ?? defaults.exa_search,
|
|
531
|
+
tdd_mode: get('tdd_mode', { section: 'workflow', field: 'tdd_mode' }) ?? false,
|
|
532
|
+
mvp_mode: get('mvp_mode', { section: 'workflow', field: 'mvp_mode' }) ?? false,
|
|
366
533
|
text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode,
|
|
534
|
+
auto_advance: get('auto_advance', { section: 'workflow', field: 'auto_advance' }) ?? false,
|
|
535
|
+
_auto_chain_active: get('_auto_chain_active', { section: 'workflow', field: '_auto_chain_active' }) ?? false,
|
|
536
|
+
mode: get('mode') ?? 'interactive',
|
|
367
537
|
sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos,
|
|
368
538
|
resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids,
|
|
369
539
|
context_window: get('context_window') ?? defaults.context_window,
|
|
@@ -371,20 +541,53 @@ function loadConfig(cwd) {
|
|
|
371
541
|
project_code: get('project_code') ?? defaults.project_code,
|
|
372
542
|
subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
|
|
373
543
|
model_overrides: parsed.model_overrides || null,
|
|
544
|
+
// #3023 — per-phase-type model map. Six named slots
|
|
545
|
+
// (planning/discuss/research/execution/verification/completion).
|
|
546
|
+
// Resolves between per-agent override and profile-derived tier in
|
|
547
|
+
// resolveModelInternal. Defaults to null so configs without it
|
|
548
|
+
// behave exactly as today.
|
|
549
|
+
models: parsed.models || null,
|
|
550
|
+
// #3024 — dynamic routing block. When `enabled: true`, the
|
|
551
|
+
// resolveModelForTier() resolver picks tier_models[default_tier]
|
|
552
|
+
// for the agent and escalates one tier per attempt up to
|
|
553
|
+
// max_escalations. Disabled by default for backward compat.
|
|
554
|
+
dynamic_routing: parsed.dynamic_routing || null,
|
|
555
|
+
// #2517 — runtime-aware profiles. `runtime` defaults to null (back-compat).
|
|
556
|
+
// When null, resolveModelInternal preserves today's Claude-native behavior.
|
|
557
|
+
// NOTE: `runtime` and `model_profile_overrides` are intentionally read
|
|
558
|
+
// flat-only (not via `get()` with a workflow.X fallback) — they are
|
|
559
|
+
// top-level keys per docs/CONFIGURATION.md. The lighter-touch decision
|
|
560
|
+
// here was to document the constraint rather than introduce nested
|
|
561
|
+
// resolution edge cases for two new keys (review finding #9). The
|
|
562
|
+
// schema validation in `_warnUnknownProfileOverrides` runs against the
|
|
563
|
+
// raw `parsed` blob, so direct `.planning/config.json` edits surface
|
|
564
|
+
// unknown runtime/tier names at load time, not silently (review finding #10).
|
|
565
|
+
runtime: parsed.runtime || null,
|
|
566
|
+
model_profile_overrides: parsed.model_profile_overrides || null,
|
|
374
567
|
agent_skills: parsed.agent_skills || {},
|
|
375
568
|
manager: parsed.manager || {},
|
|
376
569
|
response_language: get('response_language') || null,
|
|
570
|
+
claude_md_path: get('claude_md_path') || null,
|
|
571
|
+
claude_md_assembly: parsed.claude_md_assembly || null,
|
|
377
572
|
};
|
|
378
573
|
} catch {
|
|
379
574
|
// Fall back to ~/.sdd/defaults.json only for truly pre-project contexts (#1683)
|
|
380
|
-
// If .planning/ exists, the project is initialized — just missing config.json
|
|
381
|
-
|
|
575
|
+
// If .planning/ exists, the project is initialized — just missing config.json.
|
|
576
|
+
// When SDD_WORKSTREAM is set and root config was loaded, the workstream config
|
|
577
|
+
// doesn't exist — treat root config as the effective config for this workstream.
|
|
578
|
+
if (fs.existsSync(planningDir(cwd, ws))) {
|
|
579
|
+
if (rootParsed) {
|
|
580
|
+
// Workstream has no config.json: re-parse using root config as the sole source.
|
|
581
|
+
// Keep env immutable by explicitly reloading with workstream context cleared.
|
|
582
|
+
return loadConfig(cwd, { workstream: null });
|
|
583
|
+
}
|
|
382
584
|
return defaults;
|
|
383
585
|
}
|
|
384
586
|
try {
|
|
385
587
|
const home = process.env.SDD_HOME || os.homedir();
|
|
386
588
|
const globalDefaultsPath = path.join(home, '.sdd', 'defaults.json');
|
|
387
|
-
const raw =
|
|
589
|
+
const raw = platformReadSync(globalDefaultsPath);
|
|
590
|
+
if (raw === null) throw new Error('missing');
|
|
388
591
|
const globalDefaults = JSON.parse(raw);
|
|
389
592
|
return {
|
|
390
593
|
...defaults,
|
|
@@ -394,12 +597,17 @@ function loadConfig(cwd) {
|
|
|
394
597
|
plan_checker: globalDefaults.plan_checker ?? defaults.plan_checker,
|
|
395
598
|
verifier: globalDefaults.verifier ?? defaults.verifier,
|
|
396
599
|
nyquist_validation: globalDefaults.nyquist_validation ?? defaults.nyquist_validation,
|
|
600
|
+
post_planning_gaps: globalDefaults.post_planning_gaps
|
|
601
|
+
?? globalDefaults.workflow?.post_planning_gaps
|
|
602
|
+
?? defaults.post_planning_gaps,
|
|
397
603
|
parallelization: globalDefaults.parallelization ?? defaults.parallelization,
|
|
398
604
|
text_mode: globalDefaults.text_mode ?? defaults.text_mode,
|
|
399
605
|
resolve_model_ids: globalDefaults.resolve_model_ids ?? defaults.resolve_model_ids,
|
|
400
606
|
context_window: globalDefaults.context_window ?? defaults.context_window,
|
|
401
607
|
subagent_timeout: globalDefaults.subagent_timeout ?? defaults.subagent_timeout,
|
|
402
608
|
model_overrides: globalDefaults.model_overrides || null,
|
|
609
|
+
models: globalDefaults.models || null,
|
|
610
|
+
dynamic_routing: globalDefaults.dynamic_routing || null,
|
|
403
611
|
agent_skills: globalDefaults.agent_skills || {},
|
|
404
612
|
response_language: globalDefaults.response_language || null,
|
|
405
613
|
};
|
|
@@ -411,151 +619,26 @@ function loadConfig(cwd) {
|
|
|
411
619
|
|
|
412
620
|
// ─── Git utilities ────────────────────────────────────────────────────────────
|
|
413
621
|
|
|
622
|
+
// Module-level deduplication for unknown-key warnings (#3523).
|
|
623
|
+
// A single `init phase-op N` call invokes loadConfig more than once; this Set
|
|
624
|
+
// prevents the same warning from being echoed on each invocation.
|
|
625
|
+
const _warnedUnknownConfigKeys = new Set();
|
|
626
|
+
|
|
414
627
|
const _gitIgnoredCache = new Map();
|
|
415
628
|
|
|
416
629
|
function isGitIgnored(cwd, targetPath) {
|
|
417
630
|
const key = cwd + '::' + targetPath;
|
|
418
631
|
if (_gitIgnoredCache.has(key)) return _gitIgnoredCache.get(key);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
});
|
|
430
|
-
_gitIgnoredCache.set(key, true);
|
|
431
|
-
return true;
|
|
432
|
-
} catch {
|
|
433
|
-
_gitIgnoredCache.set(key, false);
|
|
434
|
-
return false;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// ─── Markdown normalization ─────────────────────────────────────────────────
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Normalize markdown to fix common markdownlint violations.
|
|
442
|
-
* Applied at write points so SDD-generated .planning/ files are IDE-friendly.
|
|
443
|
-
*
|
|
444
|
-
* Rules enforced:
|
|
445
|
-
* MD022 — Blank lines around headings
|
|
446
|
-
* MD031 — Blank lines around fenced code blocks
|
|
447
|
-
* MD032 — Blank lines around lists
|
|
448
|
-
* MD012 — No multiple consecutive blank lines (collapsed to 2 max)
|
|
449
|
-
* MD047 — Files end with a single newline
|
|
450
|
-
*/
|
|
451
|
-
function normalizeMd(content) {
|
|
452
|
-
if (!content || typeof content !== 'string') return content;
|
|
453
|
-
|
|
454
|
-
// Normalize line endings to LF for consistent processing
|
|
455
|
-
let text = content.replace(/\r\n/g, '\n');
|
|
456
|
-
|
|
457
|
-
const lines = text.split('\n');
|
|
458
|
-
const result = [];
|
|
459
|
-
|
|
460
|
-
// Pre-compute fence state in a single O(n) pass instead of O(n^2) per-line scanning
|
|
461
|
-
const fenceRegex = /^```/;
|
|
462
|
-
const insideFence = new Array(lines.length);
|
|
463
|
-
let fenceOpen = false;
|
|
464
|
-
for (let i = 0; i < lines.length; i++) {
|
|
465
|
-
if (fenceRegex.test(lines[i].trimEnd())) {
|
|
466
|
-
if (fenceOpen) {
|
|
467
|
-
// This is a closing fence — mark as NOT inside (it's the boundary)
|
|
468
|
-
insideFence[i] = false;
|
|
469
|
-
fenceOpen = false;
|
|
470
|
-
} else {
|
|
471
|
-
// This is an opening fence
|
|
472
|
-
insideFence[i] = false;
|
|
473
|
-
fenceOpen = true;
|
|
474
|
-
}
|
|
475
|
-
} else {
|
|
476
|
-
insideFence[i] = fenceOpen;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
for (let i = 0; i < lines.length; i++) {
|
|
481
|
-
const line = lines[i];
|
|
482
|
-
const prev = i > 0 ? lines[i - 1] : '';
|
|
483
|
-
const prevTrimmed = prev.trimEnd();
|
|
484
|
-
const trimmed = line.trimEnd();
|
|
485
|
-
const isFenceLine = fenceRegex.test(trimmed);
|
|
486
|
-
|
|
487
|
-
// MD022: Blank line before headings (skip first line and frontmatter delimiters)
|
|
488
|
-
if (/^#{1,6}\s/.test(trimmed) && i > 0 && prevTrimmed !== '' && prevTrimmed !== '---') {
|
|
489
|
-
result.push('');
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// MD031: Blank line before fenced code blocks (opening fences only)
|
|
493
|
-
if (isFenceLine && i > 0 && prevTrimmed !== '' && !insideFence[i] && (i === 0 || !insideFence[i - 1] || isFenceLine)) {
|
|
494
|
-
// Only add blank before opening fences (not closing ones)
|
|
495
|
-
if (i === 0 || !insideFence[i - 1]) {
|
|
496
|
-
result.push('');
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// MD032: Blank line before lists (- item, * item, N. item, - [ ] item)
|
|
501
|
-
if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 &&
|
|
502
|
-
prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) &&
|
|
503
|
-
prevTrimmed !== '---') {
|
|
504
|
-
result.push('');
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
result.push(line);
|
|
508
|
-
|
|
509
|
-
// MD022: Blank line after headings
|
|
510
|
-
if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1) {
|
|
511
|
-
const next = lines[i + 1];
|
|
512
|
-
if (next !== undefined && next.trimEnd() !== '') {
|
|
513
|
-
result.push('');
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// MD031: Blank line after closing fenced code blocks
|
|
518
|
-
if (/^```\s*$/.test(trimmed) && i > 0 && insideFence[i - 1] && i < lines.length - 1) {
|
|
519
|
-
const next = lines[i + 1];
|
|
520
|
-
if (next !== undefined && next.trimEnd() !== '') {
|
|
521
|
-
result.push('');
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// MD032: Blank line after last list item in a block
|
|
526
|
-
if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i < lines.length - 1) {
|
|
527
|
-
const next = lines[i + 1];
|
|
528
|
-
if (next !== undefined && next.trimEnd() !== '' &&
|
|
529
|
-
!/^(\s*[-*+]\s|\s*\d+\.\s)/.test(next) &&
|
|
530
|
-
!/^\s/.test(next)) {
|
|
531
|
-
// Only add blank line if next line is not a continuation/indented line
|
|
532
|
-
result.push('');
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
text = result.join('\n');
|
|
538
|
-
|
|
539
|
-
// MD012: Collapse 3+ consecutive blank lines to 2
|
|
540
|
-
text = text.replace(/\n{3,}/g, '\n\n');
|
|
541
|
-
|
|
542
|
-
// MD047: Ensure file ends with exactly one newline
|
|
543
|
-
text = text.replace(/\n*$/, '\n');
|
|
544
|
-
|
|
545
|
-
return text;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function execGit(cwd, args) {
|
|
549
|
-
const result = spawnSync('git', args, {
|
|
550
|
-
cwd,
|
|
551
|
-
stdio: 'pipe',
|
|
552
|
-
encoding: 'utf-8',
|
|
553
|
-
});
|
|
554
|
-
return {
|
|
555
|
-
exitCode: result.status ?? 1,
|
|
556
|
-
stdout: (result.stdout ?? '').toString().trim(),
|
|
557
|
-
stderr: (result.stderr ?? '').toString().trim(),
|
|
558
|
-
};
|
|
632
|
+
// --no-index checks .gitignore rules regardless of whether the file is tracked.
|
|
633
|
+
// Without it, git check-ignore returns "not ignored" for tracked files even when
|
|
634
|
+
// .gitignore explicitly lists them — a common source of confusion when .planning/
|
|
635
|
+
// was committed before being added to .gitignore.
|
|
636
|
+
// Array args (via the seam) prevent shell interpretation of special characters in
|
|
637
|
+
// file paths — avoids command injection via crafted path names.
|
|
638
|
+
const result = execGit(['check-ignore', '-q', '--no-index', '--', targetPath], { cwd });
|
|
639
|
+
const ignored = result.exitCode === 0;
|
|
640
|
+
_gitIgnoredCache.set(key, ignored);
|
|
641
|
+
return ignored;
|
|
559
642
|
}
|
|
560
643
|
|
|
561
644
|
// ─── Common path helpers ──────────────────────────────────────────────────────
|
|
@@ -566,330 +649,58 @@ function execGit(cwd, args) {
|
|
|
566
649
|
* Returns the main worktree path, or cwd if not in a worktree.
|
|
567
650
|
*/
|
|
568
651
|
function resolveWorktreeRoot(cwd) {
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const gitDir = execGit(cwd, ['rev-parse', '--git-dir']);
|
|
577
|
-
const commonDir = execGit(cwd, ['rev-parse', '--git-common-dir']);
|
|
578
|
-
|
|
579
|
-
if (gitDir.exitCode !== 0 || commonDir.exitCode !== 0) return cwd;
|
|
580
|
-
|
|
581
|
-
// In a linked worktree, .git is a file pointing to .git/worktrees/<name>
|
|
582
|
-
// and git-common-dir points to the main repo's .git directory
|
|
583
|
-
const gitDirResolved = path.resolve(cwd, gitDir.stdout);
|
|
584
|
-
const commonDirResolved = path.resolve(cwd, commonDir.stdout);
|
|
585
|
-
|
|
586
|
-
if (gitDirResolved !== commonDirResolved) {
|
|
587
|
-
// We're in a linked worktree — resolve main worktree root
|
|
588
|
-
// The common dir is the main repo's .git, so its parent is the main worktree root
|
|
589
|
-
return path.dirname(commonDirResolved);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
return cwd;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Acquire a file-based lock for .planning/ writes.
|
|
597
|
-
* Prevents concurrent worktrees from corrupting shared planning files.
|
|
598
|
-
* Lock is auto-released after the callback completes.
|
|
599
|
-
*/
|
|
600
|
-
function withPlanningLock(cwd, fn) {
|
|
601
|
-
const lockPath = path.join(planningDir(cwd), '.lock');
|
|
602
|
-
const lockTimeout = 10000; // 10 seconds
|
|
603
|
-
const retryDelay = 100;
|
|
604
|
-
const start = Date.now();
|
|
605
|
-
|
|
606
|
-
// Ensure .planning/ exists
|
|
607
|
-
try { fs.mkdirSync(planningDir(cwd), { recursive: true }); } catch { /* ok */ }
|
|
608
|
-
|
|
609
|
-
while (Date.now() - start < lockTimeout) {
|
|
610
|
-
try {
|
|
611
|
-
// Atomic create — fails if file exists
|
|
612
|
-
fs.writeFileSync(lockPath, JSON.stringify({
|
|
613
|
-
pid: process.pid,
|
|
614
|
-
cwd,
|
|
615
|
-
acquired: new Date().toISOString(),
|
|
616
|
-
}), { flag: 'wx' });
|
|
617
|
-
|
|
618
|
-
// Register for exit-time cleanup so process.exit(1) inside a locked region
|
|
619
|
-
// cannot leave a stale lock file (#1916).
|
|
620
|
-
_heldPlanningLocks.add(lockPath);
|
|
621
|
-
|
|
622
|
-
// Lock acquired — run the function
|
|
623
|
-
try {
|
|
624
|
-
return fn();
|
|
625
|
-
} finally {
|
|
626
|
-
_heldPlanningLocks.delete(lockPath);
|
|
627
|
-
try { fs.unlinkSync(lockPath); } catch { /* already released */ }
|
|
628
|
-
}
|
|
629
|
-
} catch (err) {
|
|
630
|
-
if (err.code === 'EEXIST') {
|
|
631
|
-
// Lock exists — check if stale (>30s old)
|
|
632
|
-
try {
|
|
633
|
-
const stat = fs.statSync(lockPath);
|
|
634
|
-
if (Date.now() - stat.mtimeMs > 30000) {
|
|
635
|
-
fs.unlinkSync(lockPath);
|
|
636
|
-
continue; // retry
|
|
637
|
-
}
|
|
638
|
-
} catch { continue; }
|
|
639
|
-
|
|
640
|
-
// Wait and retry (cross-platform, no shell dependency)
|
|
641
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
throw err;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
// Timeout — force acquire (stale lock recovery)
|
|
648
|
-
try { fs.unlinkSync(lockPath); } catch { /* ok */ }
|
|
649
|
-
return fn();
|
|
652
|
+
// Omit execGit so worktree-safety uses its own execGitDefault — that wrapper
|
|
653
|
+
// delegates to the seam and derives the `timedOut` field that pruneResult
|
|
654
|
+
// branches on below.
|
|
655
|
+
const context = resolveWorktreeContext(cwd, {
|
|
656
|
+
existsSync: fs.existsSync,
|
|
657
|
+
});
|
|
658
|
+
return context.effectiveRoot;
|
|
650
659
|
}
|
|
651
660
|
|
|
652
661
|
/**
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
* 1. If SDD_PROJECT is set (env var or explicit `project` arg), routes to
|
|
657
|
-
* `.planning/{project}/` — supports multi-project workspaces where several
|
|
658
|
-
* independent projects share a single `.planning/` root directory (e.g.,
|
|
659
|
-
* an Obsidian vault or monorepo knowledge base used as a command center).
|
|
660
|
-
* 2. If SDD_WORKSTREAM is set, routes to `.planning/workstreams/{ws}/`.
|
|
661
|
-
* 3. Otherwise returns `.planning/`.
|
|
662
|
+
* Parse `git worktree list --porcelain` output into an array of
|
|
663
|
+
* { path, branch } objects. Entries with a detached HEAD (no branch line)
|
|
664
|
+
* are skipped because we cannot safely reason about their merge status.
|
|
662
665
|
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
* @param {string} cwd - project root
|
|
667
|
-
* @param {string} [ws] - explicit workstream name; if omitted, checks SDD_WORKSTREAM env var
|
|
668
|
-
* @param {string} [project] - explicit project name; if omitted, checks SDD_PROJECT env var
|
|
666
|
+
* @param {string} porcelain - raw output from git worktree list --porcelain
|
|
667
|
+
* @returns {{ path: string, branch: string }[]}
|
|
669
668
|
*/
|
|
670
|
-
function
|
|
671
|
-
|
|
672
|
-
if (ws === undefined) ws = process.env.SDD_WORKSTREAM || null;
|
|
673
|
-
|
|
674
|
-
// Reject path separators and traversal components in project/workstream names
|
|
675
|
-
const BAD_SEGMENT = /[/\\]|\.\./;
|
|
676
|
-
if (project && BAD_SEGMENT.test(project)) {
|
|
677
|
-
throw new Error(`SDD_PROJECT contains invalid path characters: ${project}`);
|
|
678
|
-
}
|
|
679
|
-
if (ws && BAD_SEGMENT.test(ws)) {
|
|
680
|
-
throw new Error(`SDD_WORKSTREAM contains invalid path characters: ${ws}`);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
let base = path.join(cwd, '.planning');
|
|
684
|
-
if (project) base = path.join(base, project);
|
|
685
|
-
if (ws) base = path.join(base, 'workstreams', ws);
|
|
686
|
-
return base;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/** Always returns the root .planning/ path, ignoring workstreams and projects. For shared resources. */
|
|
690
|
-
function planningRoot(cwd) {
|
|
691
|
-
return path.join(cwd, '.planning');
|
|
669
|
+
function parseWorktreePorcelain(porcelain) {
|
|
670
|
+
return parseWorktreePorcelainPolicy(porcelain);
|
|
692
671
|
}
|
|
693
672
|
|
|
694
673
|
/**
|
|
695
|
-
*
|
|
674
|
+
* Clear stale worktree metadata references via `git worktree prune`.
|
|
696
675
|
*
|
|
697
|
-
*
|
|
698
|
-
* env var and active workstream. This matches loadConfig() above (line 256),
|
|
699
|
-
* which has always read config.json via planningDir(cwd). Previously project
|
|
700
|
-
* and config were resolved against the unrouted .planning/ root, which broke
|
|
701
|
-
* `sdd-tools config-get` in multi-project layouts (the CRUD writers and the
|
|
702
|
-
* reader pointed at different files).
|
|
703
|
-
*/
|
|
704
|
-
function planningPaths(cwd, ws) {
|
|
705
|
-
const base = planningDir(cwd, ws);
|
|
706
|
-
return {
|
|
707
|
-
planning: base,
|
|
708
|
-
state: path.join(base, 'STATE.md'),
|
|
709
|
-
roadmap: path.join(base, 'ROADMAP.md'),
|
|
710
|
-
project: path.join(base, 'PROJECT.md'),
|
|
711
|
-
config: path.join(base, 'config.json'),
|
|
712
|
-
phases: path.join(base, 'phases'),
|
|
713
|
-
requirements: path.join(base, 'REQUIREMENTS.md'),
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// ─── Active Workstream Detection ─────────────────────────────────────────────
|
|
718
|
-
|
|
719
|
-
function sanitizeWorkstreamSessionToken(value) {
|
|
720
|
-
if (value === null || value === undefined) return null;
|
|
721
|
-
const token = String(value).trim().replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
722
|
-
return token ? token.slice(0, 160) : null;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
function probeControllingTtyToken() {
|
|
726
|
-
if (didProbeControllingTtyToken) return cachedControllingTtyToken;
|
|
727
|
-
didProbeControllingTtyToken = true;
|
|
728
|
-
|
|
729
|
-
// `tty` reads stdin. When stdin is already non-interactive, spawning it only
|
|
730
|
-
// adds avoidable failures on the routing hot path and cannot reveal a stable token.
|
|
731
|
-
if (!(process.stdin && process.stdin.isTTY)) {
|
|
732
|
-
return cachedControllingTtyToken;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
try {
|
|
736
|
-
const ttyPath = execFileSync('tty', [], {
|
|
737
|
-
encoding: 'utf-8',
|
|
738
|
-
stdio: ['inherit', 'pipe', 'ignore'],
|
|
739
|
-
}).trim();
|
|
740
|
-
if (ttyPath && ttyPath !== 'not a tty') {
|
|
741
|
-
const token = sanitizeWorkstreamSessionToken(ttyPath.replace(/^\/dev\//, ''));
|
|
742
|
-
if (token) cachedControllingTtyToken = `tty-${token}`;
|
|
743
|
-
}
|
|
744
|
-
} catch {}
|
|
745
|
-
|
|
746
|
-
return cachedControllingTtyToken;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
function getControllingTtyToken() {
|
|
750
|
-
for (const envKey of ['TTY', 'SSH_TTY']) {
|
|
751
|
-
const token = sanitizeWorkstreamSessionToken(process.env[envKey]);
|
|
752
|
-
if (token) return `tty-${token.replace(/^dev_/, '')}`;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return probeControllingTtyToken();
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* Resolve a deterministic session key for workstream-local routing.
|
|
676
|
+
* Destructive linked-worktree removal is disabled by default for safety.
|
|
760
677
|
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
* 3. One best-effort `tty` probe when stdin is interactive
|
|
765
|
-
* 4. `null`, which tells callers to use the legacy shared pointer fallback
|
|
678
|
+
* @param {string} repoRoot - absolute path to the main (or any) worktree of
|
|
679
|
+
* the repository; used as `cwd` for git commands.
|
|
680
|
+
* @returns {string[]} list of worktree paths that were removed (always empty)
|
|
766
681
|
*/
|
|
767
|
-
function
|
|
768
|
-
for (const envKey of WORKSTREAM_SESSION_ENV_KEYS) {
|
|
769
|
-
const raw = process.env[envKey];
|
|
770
|
-
const token = sanitizeWorkstreamSessionToken(raw);
|
|
771
|
-
if (token) return `${envKey.toLowerCase().replace(/[^a-z0-9]+/g, '-')}-${token}`;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
return getControllingTtyToken();
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
function getSessionScopedWorkstreamFile(cwd) {
|
|
778
|
-
const sessionKey = getWorkstreamSessionKey();
|
|
779
|
-
if (!sessionKey) return null;
|
|
780
|
-
|
|
781
|
-
// Use realpathSync.native so the hash is derived from the canonical filesystem
|
|
782
|
-
// path. On Windows, path.resolve returns whatever case the caller supplied,
|
|
783
|
-
// while realpathSync.native returns the case the OS recorded — they differ on
|
|
784
|
-
// case-insensitive NTFS, producing different hashes and different tmpdir slots.
|
|
785
|
-
// Fall back to path.resolve when the directory does not yet exist.
|
|
786
|
-
let planningAbs;
|
|
682
|
+
function pruneOrphanedWorktrees(repoRoot) {
|
|
787
683
|
try {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
.
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
filePath: path.join(dirPath, sessionKey),
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
function clearActiveWorkstreamPointer(filePath, cleanupDirPath) {
|
|
807
|
-
try { fs.unlinkSync(filePath); } catch {}
|
|
808
|
-
|
|
809
|
-
// Session-scoped pointers for a repo share one tmp directory. Only remove it
|
|
810
|
-
// when it is empty so clearing or self-healing one session never deletes siblings.
|
|
811
|
-
// Explicitly check remaining entries rather than relying on rmdirSync throwing
|
|
812
|
-
// ENOTEMPTY — that error is not raised reliably on Windows.
|
|
813
|
-
if (cleanupDirPath) {
|
|
814
|
-
try {
|
|
815
|
-
const remaining = fs.readdirSync(cleanupDirPath);
|
|
816
|
-
if (remaining.length === 0) {
|
|
817
|
-
fs.rmdirSync(cleanupDirPath);
|
|
818
|
-
}
|
|
819
|
-
} catch {}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/**
|
|
824
|
-
* Pointer files are self-healing: invalid names or deleted-workstream pointers
|
|
825
|
-
* are removed on read so the session falls back to `null` instead of carrying
|
|
826
|
-
* silent stale state forward. Session-scoped callers may also prune an empty
|
|
827
|
-
* per-project tmp directory; shared `.planning/active-workstream` callers do not.
|
|
828
|
-
*/
|
|
829
|
-
function readActiveWorkstreamPointer(filePath, cwd, cleanupDirPath = null) {
|
|
830
|
-
try {
|
|
831
|
-
const name = fs.readFileSync(filePath, 'utf-8').trim();
|
|
832
|
-
if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
833
|
-
clearActiveWorkstreamPointer(filePath, cleanupDirPath);
|
|
834
|
-
return null;
|
|
835
|
-
}
|
|
836
|
-
const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
|
|
837
|
-
if (!fs.existsSync(wsDir)) {
|
|
838
|
-
clearActiveWorkstreamPointer(filePath, cleanupDirPath);
|
|
839
|
-
return null;
|
|
684
|
+
const plan = planWorktreePrune(
|
|
685
|
+
repoRoot,
|
|
686
|
+
{ allowDestructive: false },
|
|
687
|
+
{ parseWorktreePorcelain }
|
|
688
|
+
);
|
|
689
|
+
const pruneResult = executeWorktreePrunePlan(plan);
|
|
690
|
+
if (pruneResult && pruneResult.timedOut) {
|
|
691
|
+
// AC2: surface structured warning instead of silently swallowing the timeout.
|
|
692
|
+
// Uses process.stderr.write to match the [sdd-tools] WARNING prefix style.
|
|
693
|
+
process.stderr.write(
|
|
694
|
+
'[sdd-tools] WARNING: worktree health check degraded' +
|
|
695
|
+
' — git worktree prune timed out after 10s.' +
|
|
696
|
+
' Orphaned worktree metadata may remain until the next successful run.\n'
|
|
697
|
+
);
|
|
840
698
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
return null;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* Get the active workstream name.
|
|
849
|
-
*
|
|
850
|
-
* Resolution priority:
|
|
851
|
-
* 1. Session-scoped pointer (tmpdir) when the runtime exposes a stable session key
|
|
852
|
-
* 2. Legacy shared `.planning/active-workstream` file when no session key is available
|
|
853
|
-
*
|
|
854
|
-
* The shared file is intentionally ignored when a session key exists so multiple
|
|
855
|
-
* concurrent sessions do not overwrite each other's active workstream.
|
|
856
|
-
*/
|
|
857
|
-
function getActiveWorkstream(cwd) {
|
|
858
|
-
const sessionScoped = getSessionScopedWorkstreamFile(cwd);
|
|
859
|
-
if (sessionScoped) {
|
|
860
|
-
return readActiveWorkstreamPointer(sessionScoped.filePath, cwd, sessionScoped.dirPath);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
const sharedFilePath = path.join(planningRoot(cwd), 'active-workstream');
|
|
864
|
-
return readActiveWorkstreamPointer(sharedFilePath, cwd);
|
|
699
|
+
} catch { /* never crash the caller */ }
|
|
700
|
+
return [];
|
|
865
701
|
}
|
|
866
702
|
|
|
867
|
-
|
|
868
|
-
* Set the active workstream. Pass null to clear.
|
|
869
|
-
*
|
|
870
|
-
* When a stable session key is available, this updates a tmpdir-backed
|
|
871
|
-
* session-scoped pointer. Otherwise it falls back to the legacy shared
|
|
872
|
-
* `.planning/active-workstream` file for backward compatibility.
|
|
873
|
-
*/
|
|
874
|
-
function setActiveWorkstream(cwd, name) {
|
|
875
|
-
const sessionScoped = getSessionScopedWorkstreamFile(cwd);
|
|
876
|
-
const filePath = sessionScoped
|
|
877
|
-
? sessionScoped.filePath
|
|
878
|
-
: path.join(planningRoot(cwd), 'active-workstream');
|
|
879
|
-
|
|
880
|
-
if (!name) {
|
|
881
|
-
clearActiveWorkstreamPointer(filePath, sessionScoped ? sessionScoped.dirPath : null);
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
885
|
-
throw new Error('Invalid workstream name: must be alphanumeric, hyphens, and underscores only');
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (sessionScoped) {
|
|
889
|
-
fs.mkdirSync(sessionScoped.dirPath, { recursive: true });
|
|
890
|
-
}
|
|
891
|
-
fs.writeFileSync(filePath, name + '\n', 'utf-8');
|
|
892
|
-
}
|
|
703
|
+
// ─── Planning workspace (pathing + active workstream + lock) moved to planning-workspace.cjs ───
|
|
893
704
|
|
|
894
705
|
// ─── Phase utilities ──────────────────────────────────────────────────────────
|
|
895
706
|
|
|
@@ -916,6 +727,50 @@ function normalizePhaseName(phase) {
|
|
|
916
727
|
return str;
|
|
917
728
|
}
|
|
918
729
|
|
|
730
|
+
/**
|
|
731
|
+
* Render a regex source fragment matching a phase number against ROADMAP/STATE
|
|
732
|
+
* prose regardless of zero-padding on either side. Skills pass the resolved
|
|
733
|
+
* padded form (`02.7`), but human-authored ROADMAP prose is conventionally
|
|
734
|
+
* un-padded (`### Phase 2.7:`); a naive `escapeRegex(phaseNum)` fragment never
|
|
735
|
+
* matches when the two diverge. Strips leading zeros from the integer part
|
|
736
|
+
* before re-emitting with a `0*` prefix, so the fragment matches both `2.7`
|
|
737
|
+
* and `02.7` (and `002.7`).
|
|
738
|
+
*
|
|
739
|
+
* Falls back to `escapeRegex(phaseNum)` for non-numeric IDs (custom project
|
|
740
|
+
* codes like `PROJ-42`) so callers can substitute it unconditionally.
|
|
741
|
+
*
|
|
742
|
+
* See #3537 — wired into every ROADMAP-prose regex builder.
|
|
743
|
+
*/
|
|
744
|
+
function phaseMarkdownRegexSource(phaseNum) {
|
|
745
|
+
const stripped = String(phaseNum).replace(/^[A-Z]{1,6}-(?=\d)/i, '');
|
|
746
|
+
const match = stripped.match(/^0*(\d+)([A-Z])?((?:\.\d+)*)$/i);
|
|
747
|
+
if (!match) return escapeRegex(phaseNum);
|
|
748
|
+
|
|
749
|
+
const integer = match[1].replace(/^0+/, '') || '0';
|
|
750
|
+
const letter = match[2] ? escapeRegex(match[2]) : '';
|
|
751
|
+
const decimal = match[3] ? escapeRegex(match[3]) : '';
|
|
752
|
+
return `0*${escapeRegex(integer)}${letter}${decimal}`;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* #3599: when the caller passed a project-code-prefixed ID like `PROJ-42`,
|
|
757
|
+
* return the exact-escaped form so the caller can search the ROADMAP for
|
|
758
|
+
* `### Phase PROJ-42:` BEFORE falling back to the padding-tolerant numeric
|
|
759
|
+
* form. Returns null when the input has no project-code prefix — in that
|
|
760
|
+
* case the numeric form (`phaseMarkdownRegexSource`) is the only thing the
|
|
761
|
+
* caller needs.
|
|
762
|
+
*
|
|
763
|
+
* Two-pass at the call site preserves the #3537 contract (`CK-01` directory
|
|
764
|
+
* names mapping to `Phase 1:` prose) while letting `PROJ-42` resolve to its
|
|
765
|
+
* own prefixed heading without cross-matching a bare `### Phase 42:` that
|
|
766
|
+
* happens to share the trailing integer.
|
|
767
|
+
*/
|
|
768
|
+
function phaseMarkdownRegexSourceExact(phaseNum) {
|
|
769
|
+
const raw = String(phaseNum);
|
|
770
|
+
if (!/^[A-Z]{1,6}-(?=\d)/i.test(raw)) return null;
|
|
771
|
+
return escapeRegex(raw);
|
|
772
|
+
}
|
|
773
|
+
|
|
919
774
|
function comparePhaseNum(a, b) {
|
|
920
775
|
// Strip optional project_code prefix before comparing (e.g., 'CK-01-name' → '01-name')
|
|
921
776
|
const sa = String(a).replace(/^[A-Z]{1,6}-/, '');
|
|
@@ -982,6 +837,17 @@ function phaseTokenMatches(dirName, normalized) {
|
|
|
982
837
|
return false;
|
|
983
838
|
}
|
|
984
839
|
|
|
840
|
+
function extractCanonicalPlanId(filename) {
|
|
841
|
+
const base = filename.replace(/-PLAN\.md$/i, '').replace(/-SUMMARY\.md$/i, '').replace(/\.md$/i, '');
|
|
842
|
+
const parts = base.split('-').filter(Boolean);
|
|
843
|
+
const tokenRe = /^\d+[A-Z]?(?:\.\d+)*$/i;
|
|
844
|
+
const phaseIdx = parts.findIndex(p => tokenRe.test(p));
|
|
845
|
+
if (phaseIdx >= 0 && phaseIdx + 1 < parts.length && tokenRe.test(parts[phaseIdx + 1])) {
|
|
846
|
+
return `${parts[phaseIdx]}-${parts[phaseIdx + 1]}`;
|
|
847
|
+
}
|
|
848
|
+
return base;
|
|
849
|
+
}
|
|
850
|
+
|
|
985
851
|
function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
986
852
|
try {
|
|
987
853
|
const dirs = readSubdirectories(baseDir, true);
|
|
@@ -1002,11 +868,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
1002
868
|
const summaries = unsortedSummaries.sort();
|
|
1003
869
|
|
|
1004
870
|
const completedPlanIds = new Set(
|
|
1005
|
-
summaries.
|
|
871
|
+
summaries.flatMap(s => {
|
|
872
|
+
const exact = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
873
|
+
const canonical = extractCanonicalPlanId(s);
|
|
874
|
+
return canonical === exact ? [exact] : [exact, canonical];
|
|
875
|
+
})
|
|
1006
876
|
);
|
|
1007
877
|
const incompletePlans = plans.filter(p => {
|
|
1008
878
|
const planId = p.replace('-PLAN.md', '').replace('PLAN.md', '');
|
|
1009
|
-
|
|
879
|
+
const canonical = extractCanonicalPlanId(p);
|
|
880
|
+
return !completedPlanIds.has(planId) && !completedPlanIds.has(canonical);
|
|
1010
881
|
});
|
|
1011
882
|
|
|
1012
883
|
return {
|
|
@@ -1135,8 +1006,8 @@ function extractCurrentMilestone(content, cwd) {
|
|
|
1135
1006
|
let version = null;
|
|
1136
1007
|
try {
|
|
1137
1008
|
const statePath = path.join(planningDir(cwd), 'STATE.md');
|
|
1138
|
-
|
|
1139
|
-
|
|
1009
|
+
const stateRaw = platformReadSync(statePath);
|
|
1010
|
+
if (stateRaw !== null) {
|
|
1140
1011
|
const milestoneMatch = stateRaw.match(/^milestone:\s*(.+)/m);
|
|
1141
1012
|
if (milestoneMatch) {
|
|
1142
1013
|
version = milestoneMatch[1].trim();
|
|
@@ -1168,21 +1039,42 @@ function extractCurrentMilestone(content, cwd) {
|
|
|
1168
1039
|
|
|
1169
1040
|
const sectionStart = sectionMatch.index;
|
|
1170
1041
|
|
|
1171
|
-
// Find the end: next milestone heading at same or higher level, or EOF
|
|
1042
|
+
// Find the end: next milestone heading at same or higher level, or EOF.
|
|
1172
1043
|
// Milestone headings look like: ## v2.0, ## Roadmap v2.0, ## ✅ v1.0, etc.
|
|
1044
|
+
// Scan line-by-line so that heading-like lines inside fenced code blocks
|
|
1045
|
+
// (``` or ~~~) are not mistaken for milestone boundaries. See #2787.
|
|
1173
1046
|
const headingLevel = sectionMatch[1].match(/^(#{1,3})\s/)[1].length;
|
|
1174
1047
|
const restContent = content.slice(sectionStart + sectionMatch[0].length);
|
|
1048
|
+
// Exclude phase headings (e.g. "### Phase 12: v1.0 Tech-Debt Closure") from
|
|
1049
|
+
// being treated as milestone boundaries just because they mention vX.Y in
|
|
1050
|
+
// the title. Phase headings always start with the literal `Phase `. See #2619.
|
|
1175
1051
|
const nextMilestonePattern = new RegExp(
|
|
1176
|
-
`^#{1,${headingLevel}}\\s+(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
|
|
1177
|
-
'
|
|
1052
|
+
`^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`,
|
|
1053
|
+
'i'
|
|
1178
1054
|
);
|
|
1179
|
-
const nextMatch = restContent.match(nextMilestonePattern);
|
|
1180
1055
|
|
|
1181
|
-
let sectionEnd;
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1056
|
+
let sectionEnd = content.length;
|
|
1057
|
+
let fenceChar = null;
|
|
1058
|
+
let fenceLen = 0;
|
|
1059
|
+
let charOffset = 0;
|
|
1060
|
+
for (const line of restContent.split('\n')) {
|
|
1061
|
+
const fenceMatch = line.match(/^\s{0,3}((?:`{3,}|~{3,}))(.*)/);
|
|
1062
|
+
if (fenceMatch) {
|
|
1063
|
+
const char = fenceMatch[1][0];
|
|
1064
|
+
const len = fenceMatch[1].length;
|
|
1065
|
+
const trailing = fenceMatch[2] || '';
|
|
1066
|
+
if (!fenceChar) {
|
|
1067
|
+
fenceChar = char;
|
|
1068
|
+
fenceLen = len;
|
|
1069
|
+
} else if (char === fenceChar && len >= fenceLen && /^\s*$/.test(trailing)) {
|
|
1070
|
+
fenceChar = null;
|
|
1071
|
+
fenceLen = 0;
|
|
1072
|
+
}
|
|
1073
|
+
} else if (!fenceChar && nextMilestonePattern.test(line)) {
|
|
1074
|
+
sectionEnd = sectionStart + sectionMatch[0].length + charOffset;
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
charOffset += line.length + 1;
|
|
1186
1078
|
}
|
|
1187
1079
|
|
|
1188
1080
|
// Return everything before the current milestone section (non-milestone content
|
|
@@ -1221,10 +1113,16 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
1221
1113
|
if (!fs.existsSync(roadmapPath)) return null;
|
|
1222
1114
|
|
|
1223
1115
|
try {
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1116
|
+
const roadmapRaw = platformReadSync(roadmapPath);
|
|
1117
|
+
if (roadmapRaw === null) throw new Error('missing');
|
|
1118
|
+
const content = extractCurrentMilestone(roadmapRaw, cwd);
|
|
1119
|
+
// #3537: route through canonical padding-tolerant fragment. The prior
|
|
1120
|
+
// hand-rolled `isNumeric` branch only stripped padding on integer-only
|
|
1121
|
+
// ids and missed decimal padding (`02.7` against `Phase 2.7:` headings).
|
|
1122
|
+
const phasePattern = new RegExp(
|
|
1123
|
+
`#{2,4}\\s*Phase\\s+${phaseMarkdownRegexSource(phaseNum)}:\\s*([^\\n]+)`,
|
|
1124
|
+
'i'
|
|
1125
|
+
);
|
|
1228
1126
|
const headerMatch = content.match(phasePattern);
|
|
1229
1127
|
if (!headerMatch) return null;
|
|
1230
1128
|
|
|
@@ -1315,43 +1213,202 @@ function checkAgentsInstalled() {
|
|
|
1315
1213
|
|
|
1316
1214
|
// ─── Model alias resolution ───────────────────────────────────────────────────
|
|
1317
1215
|
|
|
1216
|
+
const RUNTIME_OVERRIDE_TIERS = new Set(['opus', 'sonnet', 'haiku']);
|
|
1217
|
+
const _warnedConfigKeys = new Set();
|
|
1218
|
+
|
|
1219
|
+
function _warnUnknownProfileOverrides(parsed, configLabel) {
|
|
1220
|
+
if (!parsed || typeof parsed !== 'object') return;
|
|
1221
|
+
|
|
1222
|
+
const runtime = parsed.runtime;
|
|
1223
|
+
if (runtime && typeof runtime === 'string' && !KNOWN_RUNTIMES.has(runtime)) {
|
|
1224
|
+
const key = `${configLabel}::runtime::${runtime}`;
|
|
1225
|
+
if (!_warnedConfigKeys.has(key)) {
|
|
1226
|
+
_warnedConfigKeys.add(key);
|
|
1227
|
+
try {
|
|
1228
|
+
process.stderr.write(
|
|
1229
|
+
`sdd: warning — config key "runtime" has unknown value "${runtime}". ` +
|
|
1230
|
+
`Known runtimes: ${[...KNOWN_RUNTIMES].sort().join(', ')}. ` +
|
|
1231
|
+
`Resolution will fall back to safe defaults. (#2517)\n`
|
|
1232
|
+
);
|
|
1233
|
+
} catch { /* stderr might be closed in some test harnesses */ }
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const overrides = parsed.model_profile_overrides;
|
|
1238
|
+
if (!overrides || typeof overrides !== 'object') return;
|
|
1239
|
+
for (const [overrideRuntime, tierMap] of Object.entries(overrides)) {
|
|
1240
|
+
if (!KNOWN_RUNTIMES.has(overrideRuntime)) {
|
|
1241
|
+
const key = `${configLabel}::override-runtime::${overrideRuntime}`;
|
|
1242
|
+
if (!_warnedConfigKeys.has(key)) {
|
|
1243
|
+
_warnedConfigKeys.add(key);
|
|
1244
|
+
try {
|
|
1245
|
+
process.stderr.write(
|
|
1246
|
+
`sdd: warning — model_profile_overrides.${overrideRuntime}.* uses ` +
|
|
1247
|
+
`unknown runtime "${overrideRuntime}". Known runtimes: ` +
|
|
1248
|
+
`${[...KNOWN_RUNTIMES].sort().join(', ')}. (#2517)\n`
|
|
1249
|
+
);
|
|
1250
|
+
} catch { /* ok */ }
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (!tierMap || typeof tierMap !== 'object') continue;
|
|
1254
|
+
for (const tierName of Object.keys(tierMap)) {
|
|
1255
|
+
if (!RUNTIME_OVERRIDE_TIERS.has(tierName)) {
|
|
1256
|
+
const key = `${configLabel}::override-tier::${overrideRuntime}.${tierName}`;
|
|
1257
|
+
if (!_warnedConfigKeys.has(key)) {
|
|
1258
|
+
_warnedConfigKeys.add(key);
|
|
1259
|
+
try {
|
|
1260
|
+
process.stderr.write(
|
|
1261
|
+
`sdd: warning — model_profile_overrides.${overrideRuntime}.${tierName} ` +
|
|
1262
|
+
`uses unknown tier "${tierName}". Allowed tiers: opus, sonnet, haiku. (#2517)\n`
|
|
1263
|
+
);
|
|
1264
|
+
} catch { /* ok */ }
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Internal helper exposed for tests so per-process warning state can be reset
|
|
1272
|
+
// between cases that intentionally exercise the warning path repeatedly.
|
|
1273
|
+
function _resetRuntimeWarningCacheForTests() {
|
|
1274
|
+
_warnedConfigKeys.clear();
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1318
1277
|
/**
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1278
|
+
* #2517 — Resolve the runtime-aware tier entry for (runtime, tier).
|
|
1279
|
+
*
|
|
1280
|
+
* Single source of truth shared by core.cjs (resolveModelInternal /
|
|
1281
|
+
* resolveReasoningEffortInternal) and bin/install.js (Codex/OpenCode TOML emit
|
|
1282
|
+
* paths). Always merges built-in defaults with user overrides at the field
|
|
1283
|
+
* level so partial overrides keep the unspecified fields:
|
|
1284
|
+
*
|
|
1285
|
+
* `{ codex: { opus: "gpt-5-pro" } }` keeps reasoning_effort: 'xhigh'
|
|
1286
|
+
* `{ codex: { opus: { reasoning_effort: 'low' } } }` keeps model: 'gpt-5.4'
|
|
1287
|
+
*
|
|
1288
|
+
* Without this field-merge, the documented string-shorthand example silently
|
|
1289
|
+
* dropped reasoning_effort and a partial-object override silently dropped the
|
|
1290
|
+
* model — both reported as critical findings in the #2609 review.
|
|
1291
|
+
*
|
|
1292
|
+
* Inputs:
|
|
1293
|
+
* - runtime: string (e.g. 'codex', 'claude', 'opencode')
|
|
1294
|
+
* - tier: 'opus' | 'sonnet' | 'haiku'
|
|
1295
|
+
* - overrides: optional `model_profile_overrides` blob (may be null/undefined)
|
|
1296
|
+
*
|
|
1297
|
+
* Returns `{ model: string, reasoning_effort?: string } | null`.
|
|
1322
1298
|
*/
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1299
|
+
function resolveTierEntry({ runtime, tier, overrides }) {
|
|
1300
|
+
if (!runtime || !tier) return null;
|
|
1301
|
+
|
|
1302
|
+
const builtin = RUNTIME_PROFILE_MAP[runtime]?.[tier] || null;
|
|
1303
|
+
const userRaw = overrides?.[runtime]?.[tier];
|
|
1304
|
+
|
|
1305
|
+
// String shorthand from CONFIGURATION.md examples — `{ codex: { opus: "gpt-5-pro" } }`.
|
|
1306
|
+
// Treat as `{ model: "gpt-5-pro" }` so the field-merge below still preserves
|
|
1307
|
+
// reasoning_effort from the built-in defaults.
|
|
1308
|
+
let userEntry = null;
|
|
1309
|
+
if (userRaw) {
|
|
1310
|
+
userEntry = typeof userRaw === 'string' ? { model: userRaw } : userRaw;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if (!builtin && !userEntry) return null;
|
|
1314
|
+
// Field-merge: user fields win, built-in fills the gaps.
|
|
1315
|
+
return { ...(builtin || {}), ...(userEntry || {}) };
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Convenience wrapper used by resolveModelInternal / resolveReasoningEffortInternal.
|
|
1320
|
+
* Pulls runtime + overrides out of a loaded config and delegates to resolveTierEntry.
|
|
1321
|
+
*/
|
|
1322
|
+
function _resolveRuntimeTier(config, tier) {
|
|
1323
|
+
return resolveTierEntry({
|
|
1324
|
+
runtime: config.runtime,
|
|
1325
|
+
tier,
|
|
1326
|
+
overrides: config.model_profile_overrides,
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1328
1329
|
|
|
1329
1330
|
function resolveModelInternal(cwd, agentType) {
|
|
1330
1331
|
const config = loadConfig(cwd);
|
|
1331
1332
|
|
|
1332
|
-
//
|
|
1333
|
+
// 1. Per-agent override — always respected; highest precedence.
|
|
1333
1334
|
// Users who set fully-qualified model IDs (e.g., "openai/gpt-5.4") get exactly that.
|
|
1334
1335
|
const override = config.model_overrides?.[agentType];
|
|
1335
1336
|
if (override) {
|
|
1336
1337
|
return override;
|
|
1337
1338
|
}
|
|
1338
1339
|
|
|
1339
|
-
//
|
|
1340
|
-
//
|
|
1341
|
-
//
|
|
1340
|
+
// 2. Compute the tier (opus/sonnet/haiku/inherit) for this agent.
|
|
1341
|
+
//
|
|
1342
|
+
// #3023: phase-type slot can override the profile-derived tier.
|
|
1343
|
+
// Precedence: per-agent override (above) > phase-type slot > profile.
|
|
1344
|
+
// Phase-type values are tier aliases (opus/sonnet/haiku/inherit) — same
|
|
1345
|
+
// shape as model_profile output — so the runtime-resolution chain
|
|
1346
|
+
// (step 3), resolve_model_ids handling (step 4), and profile lookup
|
|
1347
|
+
// (step 5) all stay correct without further branching.
|
|
1348
|
+
const profile = String(config.model_profile || 'balanced').toLowerCase();
|
|
1349
|
+
const agentModels = MODEL_PROFILES[agentType];
|
|
1350
|
+
const phaseType = AGENT_TO_PHASE_TYPE[agentType];
|
|
1351
|
+
const phaseTypeTier = (phaseType && config.models && typeof config.models === 'object')
|
|
1352
|
+
? config.models[phaseType]
|
|
1353
|
+
: undefined;
|
|
1354
|
+
// Only honor phase-type tier if it's one of the recognized aliases.
|
|
1355
|
+
// Anything else falls through to profile lookup so a typo doesn't
|
|
1356
|
+
// silently break tier resolution.
|
|
1357
|
+
const VALID_TIERS = new Set(['opus', 'sonnet', 'haiku', 'inherit']);
|
|
1358
|
+
// Resolve tier: phase-type wins when valid; else profile-derived; else
|
|
1359
|
+
// (when profile === 'inherit') propagate inherit so the later short-
|
|
1360
|
+
// circuit fires. CR Major (#3030): a config like
|
|
1361
|
+
// { model_profile: 'inherit', models: { execution: 'opus' } }
|
|
1362
|
+
// must honor the phase-type opus, not return 'inherit'. Synthesizing
|
|
1363
|
+
// tier='inherit' only when there's no phase-type override keeps the
|
|
1364
|
+
// original inherit semantics intact while letting a valid phase-type
|
|
1365
|
+
// tier win.
|
|
1366
|
+
const tier = (phaseTypeTier && VALID_TIERS.has(phaseTypeTier))
|
|
1367
|
+
? phaseTypeTier
|
|
1368
|
+
: (profile === 'inherit'
|
|
1369
|
+
? 'inherit'
|
|
1370
|
+
: (agentModels ? (agentModels[profile] || agentModels['balanced']) : null));
|
|
1371
|
+
|
|
1372
|
+
// 3. Runtime-aware resolution (#2517) — only when `runtime` is explicitly set
|
|
1373
|
+
// to a non-Claude runtime. `runtime: "claude"` is the implicit default and is
|
|
1374
|
+
// treated as a no-op here so it does not silently override `resolve_model_ids:
|
|
1375
|
+
// "omit"` (review finding #4). Deliberate ordering for non-Claude runtimes:
|
|
1376
|
+
// explicit opt-in beats `resolve_model_ids: "omit"` so users on Codex installs
|
|
1377
|
+
// that auto-set "omit" can still flip on tiered behavior by setting runtime
|
|
1378
|
+
// alone. Gate on tier !== 'inherit' (not profile !== 'inherit') so a
|
|
1379
|
+
// valid phase-type tier flips runtime resolution on even when the
|
|
1380
|
+
// profile is inherit.
|
|
1381
|
+
if (config.runtime && config.runtime !== 'claude' && tier && tier !== 'inherit') {
|
|
1382
|
+
const entry = _resolveRuntimeTier(config, tier);
|
|
1383
|
+
if (entry?.model) return entry.model;
|
|
1384
|
+
// Unknown runtime with no user-supplied overrides — fall through to Claude-safe
|
|
1385
|
+
// default rather than emit an ID the runtime can't accept.
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// 4. resolve_model_ids: "omit" — return empty string so the runtime uses its
|
|
1389
|
+
// configured default model. For non-Claude runtimes (OpenCode, Codex, etc.) that
|
|
1390
|
+
// don't recognize Claude aliases. Set automatically during install. See #1156.
|
|
1342
1391
|
if (config.resolve_model_ids === 'omit') {
|
|
1343
1392
|
return '';
|
|
1344
1393
|
}
|
|
1345
1394
|
|
|
1346
|
-
//
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
//
|
|
1354
|
-
//
|
|
1395
|
+
// 5. Profile lookup (Claude-native default).
|
|
1396
|
+
if (!agentModels) {
|
|
1397
|
+
return profile === 'quality' ? 'opus'
|
|
1398
|
+
: profile === 'budget' ? 'haiku'
|
|
1399
|
+
: profile === 'inherit' ? 'inherit'
|
|
1400
|
+
: 'sonnet';
|
|
1401
|
+
}
|
|
1402
|
+
// Gate on tier (not profile) so a valid phase-type override beats
|
|
1403
|
+
// profile=inherit (#3030 CR Major).
|
|
1404
|
+
if (tier === 'inherit') return 'inherit';
|
|
1405
|
+
// `tier` is guaranteed truthy here: agentModels exists, and MODEL_PROFILES
|
|
1406
|
+
// entries always define `balanced`, so `agentModels[profile] || agentModels.balanced`
|
|
1407
|
+
// resolves to a string. Keep the local for readability — no defensive fallback.
|
|
1408
|
+
const alias = tier;
|
|
1409
|
+
|
|
1410
|
+
// resolve_model_ids: true — map alias to full Claude model ID.
|
|
1411
|
+
// Prevents 404s when the Task tool passes aliases directly to the API.
|
|
1355
1412
|
if (config.resolve_model_ids) {
|
|
1356
1413
|
return MODEL_ALIAS_MAP[alias] || alias;
|
|
1357
1414
|
}
|
|
@@ -1359,6 +1416,156 @@ function resolveModelInternal(cwd, agentType) {
|
|
|
1359
1416
|
return alias;
|
|
1360
1417
|
}
|
|
1361
1418
|
|
|
1419
|
+
/**
|
|
1420
|
+
* #3024 — Resolve a model for a specific dynamic-routing attempt.
|
|
1421
|
+
*
|
|
1422
|
+
* The orchestrator (workflow agent) tracks the attempt counter. On
|
|
1423
|
+
* the first spawn, it calls with attempt=0. If the orchestrator detects
|
|
1424
|
+
* a soft failure (verification inconclusive, plan-check FLAG, etc.),
|
|
1425
|
+
* it re-spawns with attempt=1, which escalates the agent's tier one
|
|
1426
|
+
* step up. `max_escalations` caps how many escalations are allowed.
|
|
1427
|
+
*
|
|
1428
|
+
* Resolution precedence (highest → lowest):
|
|
1429
|
+
* 1. config.model_overrides[agent] (full IDs accepted)
|
|
1430
|
+
* 2. dynamic_routing.tier_models[escalated_tier] (when enabled)
|
|
1431
|
+
* 3. models[phase_type] / model_profile (existing chain via
|
|
1432
|
+
* resolveModelInternal)
|
|
1433
|
+
*
|
|
1434
|
+
* When dynamic_routing is null/disabled, this function is identical
|
|
1435
|
+
* to resolveModelInternal — orchestrators can call it unconditionally
|
|
1436
|
+
* without breaking back-compat.
|
|
1437
|
+
*
|
|
1438
|
+
* @param {string} cwd - Project directory.
|
|
1439
|
+
* @param {string} agentType - Agent name (e.g. 'sdd-verifier').
|
|
1440
|
+
* @param {number} [attempt=0] - 0 for first spawn; 1+ for escalation.
|
|
1441
|
+
* Capped internally at max_escalations.
|
|
1442
|
+
* @returns {string} Model alias (opus/sonnet/haiku) or full ID.
|
|
1443
|
+
*/
|
|
1444
|
+
function resolveModelForTier(cwd, agentType, attempt) {
|
|
1445
|
+
const config = loadConfig(cwd);
|
|
1446
|
+
const attemptN = Number.isInteger(attempt) && attempt > 0 ? attempt : 0;
|
|
1447
|
+
|
|
1448
|
+
// Per-agent override always wins — same as resolveModelInternal step 1.
|
|
1449
|
+
// User-supplied full IDs bypass the entire tier mechanism.
|
|
1450
|
+
const override = config.model_overrides?.[agentType];
|
|
1451
|
+
if (override) return override;
|
|
1452
|
+
|
|
1453
|
+
const dr = config.dynamic_routing;
|
|
1454
|
+
// Disabled / missing / non-object → fall back to the existing resolver.
|
|
1455
|
+
if (!dr || typeof dr !== 'object' || dr.enabled !== true) {
|
|
1456
|
+
return resolveModelInternal(cwd, agentType);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
const tierModels = dr.tier_models;
|
|
1460
|
+
if (!tierModels || typeof tierModels !== 'object') {
|
|
1461
|
+
// tier_models missing — can't dynamic-route; fall back.
|
|
1462
|
+
return resolveModelInternal(cwd, agentType);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
const defaultTier = AGENT_DEFAULT_TIERS[agentType];
|
|
1466
|
+
if (!defaultTier || !VALID_AGENT_TIERS.has(defaultTier)) {
|
|
1467
|
+
// Unmapped agent — no default tier; fall back so we don't silently
|
|
1468
|
+
// pick the wrong model.
|
|
1469
|
+
return resolveModelInternal(cwd, agentType);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Cap effective escalation at max_escalations (default 1). Beyond
|
|
1473
|
+
// the cap, the resolver returns the model for the cap level so the
|
|
1474
|
+
// orchestrator can log "max escalations reached" without burning
|
|
1475
|
+
// further budget.
|
|
1476
|
+
//
|
|
1477
|
+
// CR Major (#3031): `escalate_on_failure: false` is the kill-switch
|
|
1478
|
+
// for escalation — when false, every attempt resolves to the default
|
|
1479
|
+
// tier regardless of the attempt counter. Without this guard, an
|
|
1480
|
+
// orchestrator that blindly bumps the counter on retry would silently
|
|
1481
|
+
// escalate even though the user opted out.
|
|
1482
|
+
const maxEscalations = Number.isInteger(dr.max_escalations) && dr.max_escalations >= 0
|
|
1483
|
+
? dr.max_escalations
|
|
1484
|
+
: 1;
|
|
1485
|
+
const escalationEnabled = dr.escalate_on_failure !== false;
|
|
1486
|
+
const effectiveAttempt = escalationEnabled
|
|
1487
|
+
? Math.min(attemptN, maxEscalations)
|
|
1488
|
+
: 0;
|
|
1489
|
+
|
|
1490
|
+
// Walk the escalation chain N times from the default tier.
|
|
1491
|
+
let tier = defaultTier;
|
|
1492
|
+
for (let i = 0; i < effectiveAttempt; i += 1) {
|
|
1493
|
+
const next = nextTier(tier);
|
|
1494
|
+
if (!next || next === tier) break; // already at top
|
|
1495
|
+
tier = next;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const alias = tierModels[tier];
|
|
1499
|
+
if (typeof alias !== 'string' || alias.length === 0) {
|
|
1500
|
+
// Misconfigured tier_models — missing slot. Fall back rather
|
|
1501
|
+
// than emit an empty model id.
|
|
1502
|
+
return resolveModelInternal(cwd, agentType);
|
|
1503
|
+
}
|
|
1504
|
+
return alias;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* #2517 — Resolve runtime-specific reasoning_effort for an agent.
|
|
1509
|
+
* Returns null unless:
|
|
1510
|
+
* - `runtime` is explicitly set in config,
|
|
1511
|
+
* - the runtime supports reasoning_effort (currently: codex),
|
|
1512
|
+
* - profile is not 'inherit',
|
|
1513
|
+
* - the resolved tier entry has a `reasoning_effort` value.
|
|
1514
|
+
*
|
|
1515
|
+
* Never returns a value for Claude — keeps reasoning_effort out of Claude spawn paths.
|
|
1516
|
+
*/
|
|
1517
|
+
function resolveReasoningEffortInternal(cwd, agentType) {
|
|
1518
|
+
const config = loadConfig(cwd);
|
|
1519
|
+
if (!config.runtime) return null;
|
|
1520
|
+
// Strict allowlist: reasoning_effort only propagates for runtimes whose
|
|
1521
|
+
// install path actually accepts it. Adding a new runtime here is the only
|
|
1522
|
+
// way to enable effort propagation — overrides cannot bypass the gate.
|
|
1523
|
+
// Without this, a typo in `runtime` (e.g. `"codx"`) plus a user override
|
|
1524
|
+
// for that typo would leak `xhigh` into a Claude or unknown install
|
|
1525
|
+
// (review finding #3).
|
|
1526
|
+
if (!RUNTIMES_WITH_REASONING_EFFORT.has(config.runtime)) return null;
|
|
1527
|
+
// Per-agent override means user supplied a fully-qualified ID; reasoning_effort
|
|
1528
|
+
// for that case must be set via per-agent mechanism, not tier inference.
|
|
1529
|
+
if (config.model_overrides?.[agentType]) return null;
|
|
1530
|
+
|
|
1531
|
+
const profile = String(config.model_profile || 'balanced').toLowerCase();
|
|
1532
|
+
const agentModels = MODEL_PROFILES[agentType];
|
|
1533
|
+
if (!agentModels) return null;
|
|
1534
|
+
|
|
1535
|
+
// #3023 (CR Major): mirror the phase-type tier lookup from
|
|
1536
|
+
// resolveModelInternal. Without this, `model` and `reasoning_effort`
|
|
1537
|
+
// derive from different tier sources on Codex when models.<phase_type>
|
|
1538
|
+
// overrides the profile.
|
|
1539
|
+
//
|
|
1540
|
+
// #3030 CR follow-up: do NOT short-circuit on profile === 'inherit'
|
|
1541
|
+
// before reading the phase-type tier. A config like
|
|
1542
|
+
// { model_profile: 'inherit', models: { execution: 'opus' } }
|
|
1543
|
+
// must produce the opus runtime effort, not null. Compute tier from
|
|
1544
|
+
// phase-type first; only fall back to profile when there's no valid
|
|
1545
|
+
// phase-type override; only return null when the resolved tier is
|
|
1546
|
+
// 'inherit' or unknown.
|
|
1547
|
+
const phaseType = AGENT_TO_PHASE_TYPE[agentType];
|
|
1548
|
+
const phaseTypeTier = (phaseType && config.models && typeof config.models === 'object')
|
|
1549
|
+
? config.models[phaseType]
|
|
1550
|
+
: undefined;
|
|
1551
|
+
// Explicit phase-type 'inherit' is the user opting out of tier-based
|
|
1552
|
+
// effort for this phase — return null instead of falling through to
|
|
1553
|
+
// profile (which would silently emit the profile's effort and
|
|
1554
|
+
// contradict the user's choice).
|
|
1555
|
+
if (phaseTypeTier === 'inherit') return null;
|
|
1556
|
+
const VALID_TIERS = new Set(['opus', 'sonnet', 'haiku']);
|
|
1557
|
+
const tier = (phaseTypeTier && VALID_TIERS.has(phaseTypeTier))
|
|
1558
|
+
? phaseTypeTier
|
|
1559
|
+
: (profile === 'inherit'
|
|
1560
|
+
? 'inherit'
|
|
1561
|
+
: (agentModels[profile] || agentModels['balanced']));
|
|
1562
|
+
// 'inherit' (from profile fallback) yields no runtime effort.
|
|
1563
|
+
if (!tier || tier === 'inherit') return null;
|
|
1564
|
+
|
|
1565
|
+
const entry = _resolveRuntimeTier(config, tier);
|
|
1566
|
+
return entry?.reasoning_effort || null;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1362
1569
|
// ─── Summary body helpers ─────────────────────────────────────────────────
|
|
1363
1570
|
|
|
1364
1571
|
/**
|
|
@@ -1369,11 +1576,28 @@ function resolveModelInternal(cwd, agentType) {
|
|
|
1369
1576
|
*/
|
|
1370
1577
|
function extractOneLinerFromBody(content) {
|
|
1371
1578
|
if (!content) return null;
|
|
1579
|
+
// Normalize EOLs so matching works for LF and CRLF files.
|
|
1580
|
+
const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
1372
1581
|
// Strip frontmatter first
|
|
1373
|
-
const body =
|
|
1374
|
-
// Find the first **...** line after a # heading
|
|
1375
|
-
|
|
1376
|
-
|
|
1582
|
+
const body = normalized.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
|
1583
|
+
// Find the first **...** span on a line after a # heading.
|
|
1584
|
+
// Two supported template forms:
|
|
1585
|
+
// 1) Labeled: **One-liner:** Real prose here. (bug #2660 — new template)
|
|
1586
|
+
// 2) Bare: **Real prose here.** (legacy template)
|
|
1587
|
+
// For (1), the first bold span ends in a colon and the prose that follows
|
|
1588
|
+
// on the same line is the one-liner. For (2), the bold span itself is the
|
|
1589
|
+
// one-liner.
|
|
1590
|
+
const match = body.match(/^#[^\n]*\n+\*\*([^*\n]+)\*\*([^\n]*)/m);
|
|
1591
|
+
if (!match) return null;
|
|
1592
|
+
const boldInner = match[1].trim();
|
|
1593
|
+
const afterBold = match[2];
|
|
1594
|
+
// Labeled form: bold span is a "Label:" prefix — capture prose after it.
|
|
1595
|
+
if (/:\s*$/.test(boldInner)) {
|
|
1596
|
+
const prose = afterBold.trim();
|
|
1597
|
+
return prose.length > 0 ? prose : null;
|
|
1598
|
+
}
|
|
1599
|
+
// Bare form: the bold content itself is the one-liner.
|
|
1600
|
+
return boldInner.length > 0 ? boldInner : null;
|
|
1377
1601
|
}
|
|
1378
1602
|
|
|
1379
1603
|
// ─── Misc utilities ───────────────────────────────────────────────────────────
|
|
@@ -1388,6 +1612,48 @@ function pathExistsInternal(cwd, targetPath) {
|
|
|
1388
1612
|
}
|
|
1389
1613
|
}
|
|
1390
1614
|
|
|
1615
|
+
/**
|
|
1616
|
+
* Detect whether `cwd` sits inside a git worktree, and if so, return the
|
|
1617
|
+
* absolute path of the worktree root.
|
|
1618
|
+
*
|
|
1619
|
+
* Bug #3491: the previous shallow `pathExistsInternal(cwd, '.git')` check
|
|
1620
|
+
* only saw a `.git` entry directly in cwd, so subdirectories of an existing
|
|
1621
|
+
* repo reported `has_git: false` and the new-project workflow then ran
|
|
1622
|
+
* `git init` — creating a nested `.git` inside the outer repo's worktree.
|
|
1623
|
+
*
|
|
1624
|
+
* Mirrors `git rev-parse --is-inside-work-tree` semantics. Uses the existing
|
|
1625
|
+
* `execGit` seam so behaviour is consistent with the rest of the toolchain
|
|
1626
|
+
* (non-interactive env, 10s timeout, mockable in tests).
|
|
1627
|
+
*
|
|
1628
|
+
* Returns: { inside: boolean, worktreeRoot: string | null }
|
|
1629
|
+
* - inside=true → cwd is somewhere inside a git worktree
|
|
1630
|
+
* - inside=false → cwd is not inside any git worktree (or git is unavailable)
|
|
1631
|
+
*
|
|
1632
|
+
* Failure modes (git not installed, command times out, non-zero exit) all
|
|
1633
|
+
* collapse to `{ inside: false, worktreeRoot: null }` — the conservative
|
|
1634
|
+
* default that preserves pre-fix behaviour for environments without git.
|
|
1635
|
+
*/
|
|
1636
|
+
function gitWorktreeInfoInternal(cwd) {
|
|
1637
|
+
try {
|
|
1638
|
+
const insideResult = execGit(['rev-parse', '--is-inside-work-tree'], { cwd, timeout: 5000 });
|
|
1639
|
+
if (insideResult.exitCode !== 0) {
|
|
1640
|
+
return { inside: false, worktreeRoot: null };
|
|
1641
|
+
}
|
|
1642
|
+
const insideStdout = String(insideResult.stdout || '').trim();
|
|
1643
|
+
if (insideStdout !== 'true') {
|
|
1644
|
+
return { inside: false, worktreeRoot: null };
|
|
1645
|
+
}
|
|
1646
|
+
const rootResult = execGit(['rev-parse', '--show-toplevel'], { cwd, timeout: 5000 });
|
|
1647
|
+
if (rootResult.exitCode !== 0) {
|
|
1648
|
+
return { inside: true, worktreeRoot: null };
|
|
1649
|
+
}
|
|
1650
|
+
const root = String(rootResult.stdout || '').trim();
|
|
1651
|
+
return { inside: true, worktreeRoot: root || null };
|
|
1652
|
+
} catch {
|
|
1653
|
+
return { inside: false, worktreeRoot: null };
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1391
1657
|
function generateSlugInternal(text) {
|
|
1392
1658
|
if (!text) return null;
|
|
1393
1659
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 60);
|
|
@@ -1395,7 +1661,52 @@ function generateSlugInternal(text) {
|
|
|
1395
1661
|
|
|
1396
1662
|
function getMilestoneInfo(cwd) {
|
|
1397
1663
|
try {
|
|
1398
|
-
const roadmap =
|
|
1664
|
+
const roadmap = platformReadSync(path.join(planningDir(cwd), 'ROADMAP.md'));
|
|
1665
|
+
if (roadmap === null) throw new Error('missing');
|
|
1666
|
+
|
|
1667
|
+
// 0. Prefer STATE.md milestone: frontmatter as the authoritative source.
|
|
1668
|
+
// This prevents falling through to a regex that may match an old heading
|
|
1669
|
+
// when the active milestone's 🚧 marker is inside a <summary> tag without
|
|
1670
|
+
// **bold** formatting (bug #2409).
|
|
1671
|
+
let stateVersion = null;
|
|
1672
|
+
if (cwd) {
|
|
1673
|
+
try {
|
|
1674
|
+
const statePath = path.join(planningDir(cwd), 'STATE.md');
|
|
1675
|
+
const stateRaw = platformReadSync(statePath);
|
|
1676
|
+
if (stateRaw !== null) {
|
|
1677
|
+
const m = stateRaw.match(/^milestone:\s*(.+)/m);
|
|
1678
|
+
if (m) stateVersion = m[1].trim();
|
|
1679
|
+
}
|
|
1680
|
+
} catch { /* intentionally empty */ }
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
if (stateVersion) {
|
|
1684
|
+
// Look up the name for this version in ROADMAP.md
|
|
1685
|
+
const escapedVer = escapeRegex(stateVersion);
|
|
1686
|
+
// Match heading-format: ## Roadmap v2.9: Name or ## v2.9 Name
|
|
1687
|
+
const headingMatch = roadmap.match(
|
|
1688
|
+
new RegExp(`##[^\\n]*${escapedVer}[:\\s]+([^\\n(]+)`, 'i')
|
|
1689
|
+
);
|
|
1690
|
+
if (headingMatch) {
|
|
1691
|
+
// If the heading line contains ✅ the milestone is already shipped.
|
|
1692
|
+
// Fall through to normal detection so the NEW active milestone is returned
|
|
1693
|
+
// instead of the stale shipped one still recorded in STATE.md.
|
|
1694
|
+
if (!headingMatch[0].includes('✅')) {
|
|
1695
|
+
return { version: stateVersion, name: headingMatch[1].trim() };
|
|
1696
|
+
}
|
|
1697
|
+
// Shipped milestone — do not early-return; fall through to normal detection below.
|
|
1698
|
+
} else {
|
|
1699
|
+
// Match list-format: 🚧 **v2.9 Name** or 🚧 v2.9 Name
|
|
1700
|
+
const listMatch = roadmap.match(
|
|
1701
|
+
new RegExp(`🚧\\s*\\*?\\*?${escapedVer}\\s+([^*\\n]+)`, 'i')
|
|
1702
|
+
);
|
|
1703
|
+
if (listMatch) {
|
|
1704
|
+
return { version: stateVersion, name: listMatch[1].trim() };
|
|
1705
|
+
}
|
|
1706
|
+
// Version found in STATE.md but no name match in ROADMAP — return bare version
|
|
1707
|
+
return { version: stateVersion, name: 'milestone' };
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1399
1710
|
|
|
1400
1711
|
// First: check for list-format roadmaps using 🚧 (in-progress) marker
|
|
1401
1712
|
// e.g. "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
|
|
@@ -1408,11 +1719,14 @@ function getMilestoneInfo(cwd) {
|
|
|
1408
1719
|
};
|
|
1409
1720
|
}
|
|
1410
1721
|
|
|
1411
|
-
// Second: heading-format roadmaps — strip shipped milestones
|
|
1722
|
+
// Second: heading-format roadmaps — strip shipped milestones.
|
|
1723
|
+
// <details> blocks are stripped by stripShippedMilestones; heading-format ✅ markers
|
|
1724
|
+
// are excluded by the negative lookahead below so a stale STATE.md version (or any
|
|
1725
|
+
// shipped ✅ heading) never wins over the first non-shipped milestone heading.
|
|
1412
1726
|
const cleaned = stripShippedMilestones(roadmap);
|
|
1413
|
-
//
|
|
1727
|
+
// Negative lookahead skips headings that contain ✅ (shipped milestone marker).
|
|
1414
1728
|
// Supports 2+ segment versions: v1.2, v1.2.1, v2.0.1, etc.
|
|
1415
|
-
const headingMatch = cleaned.match(/## .*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
|
|
1729
|
+
const headingMatch = cleaned.match(/## (?!.*✅).*v(\d+(?:\.\d+)+)[:\s]+([^\n(]+)/);
|
|
1416
1730
|
if (headingMatch) {
|
|
1417
1731
|
return {
|
|
1418
1732
|
version: 'v' + headingMatch[1],
|
|
@@ -1435,10 +1749,63 @@ function getMilestoneInfo(cwd) {
|
|
|
1435
1749
|
* to the current milestone based on ROADMAP.md phase headings.
|
|
1436
1750
|
* If no ROADMAP exists or no phases are listed, returns a pass-all filter.
|
|
1437
1751
|
*/
|
|
1438
|
-
function getMilestonePhaseFilter(cwd) {
|
|
1752
|
+
function getMilestonePhaseFilter(cwd, versionOverride) {
|
|
1439
1753
|
const milestonePhaseNums = new Set();
|
|
1754
|
+
let missingExplicitVersion = false;
|
|
1440
1755
|
try {
|
|
1441
|
-
const
|
|
1756
|
+
const roadmapPath = path.join(planningDir(cwd), 'ROADMAP.md');
|
|
1757
|
+
const roadmapContent = platformReadSync(roadmapPath);
|
|
1758
|
+
if (roadmapContent === null) throw new Error('missing');
|
|
1759
|
+
let roadmap = extractCurrentMilestone(roadmapContent, cwd);
|
|
1760
|
+
|
|
1761
|
+
if (versionOverride) {
|
|
1762
|
+
const escapedVersion = escapeRegex(versionOverride);
|
|
1763
|
+
const sectionPattern = new RegExp(`(^#{1,3}\\s+.*${escapedVersion}[^\\n]*)`, 'mi');
|
|
1764
|
+
const sectionMatch = roadmapContent.match(sectionPattern);
|
|
1765
|
+
if (!sectionMatch) {
|
|
1766
|
+
// Only treat this as an error case when the roadmap is milestone-versioned.
|
|
1767
|
+
// Older/flat roadmap formats without vX.Y milestone headings should keep
|
|
1768
|
+
// legacy pass-through behavior for milestone.complete.
|
|
1769
|
+
const hasVersionedMilestones = /^#{1,3}\s+.*v\d+\.\d+/mi.test(roadmapContent);
|
|
1770
|
+
if (hasVersionedMilestones) {
|
|
1771
|
+
roadmap = '';
|
|
1772
|
+
missingExplicitVersion = true;
|
|
1773
|
+
}
|
|
1774
|
+
} else {
|
|
1775
|
+
const sectionStart = sectionMatch.index;
|
|
1776
|
+
const headingLevel = sectionMatch[1].match(/^(#{1,3})\s/)[1].length;
|
|
1777
|
+
const restContent = roadmapContent.slice(sectionStart + sectionMatch[0].length);
|
|
1778
|
+
const nextMilestonePattern = new RegExp(`^#{1,${headingLevel}}\\s+(?!Phase\\s+\\S)(?:.*v\\d+\\.\\d+|✅|📋|🚧)`, 'i');
|
|
1779
|
+
|
|
1780
|
+
let sectionEnd = roadmapContent.length;
|
|
1781
|
+
let fenceChar = null;
|
|
1782
|
+
let fenceLen = 0;
|
|
1783
|
+
let charOffset = 0;
|
|
1784
|
+
for (const line of restContent.split('\n')) {
|
|
1785
|
+
const fenceMatch = line.match(/^\s{0,3}((?:`{3,}|~{3,}))(.*)/);
|
|
1786
|
+
if (fenceMatch) {
|
|
1787
|
+
const char = fenceMatch[1][0];
|
|
1788
|
+
const len = fenceMatch[1].length;
|
|
1789
|
+
const trailing = fenceMatch[2] || '';
|
|
1790
|
+
if (!fenceChar) {
|
|
1791
|
+
fenceChar = char;
|
|
1792
|
+
fenceLen = len;
|
|
1793
|
+
} else if (char === fenceChar && len >= fenceLen && /^\s*$/.test(trailing)) {
|
|
1794
|
+
fenceChar = null;
|
|
1795
|
+
fenceLen = 0;
|
|
1796
|
+
}
|
|
1797
|
+
} else if (!fenceChar && nextMilestonePattern.test(line)) {
|
|
1798
|
+
sectionEnd = sectionStart + sectionMatch[0].length + charOffset;
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
charOffset += line.length + 1;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
const currentSection = roadmapContent.slice(sectionStart, sectionEnd);
|
|
1805
|
+
roadmap = currentSection;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1442
1809
|
// Match both numeric phases (Phase 1:) and custom IDs (Phase PROJ-42:)
|
|
1443
1810
|
const phasePattern = /#{2,4}\s*Phase\s+([\w][\w.-]*)\s*:/gi;
|
|
1444
1811
|
let m;
|
|
@@ -1450,11 +1817,12 @@ function getMilestonePhaseFilter(cwd) {
|
|
|
1450
1817
|
if (milestonePhaseNums.size === 0) {
|
|
1451
1818
|
const passAll = () => true;
|
|
1452
1819
|
passAll.phaseCount = 0;
|
|
1820
|
+
passAll.missingExplicitVersion = missingExplicitVersion;
|
|
1453
1821
|
return passAll;
|
|
1454
1822
|
}
|
|
1455
1823
|
|
|
1456
1824
|
const normalized = new Set(
|
|
1457
|
-
[...milestonePhaseNums].map(n => (n.replace(/^0
|
|
1825
|
+
[...milestonePhaseNums].map(n => (n.replace(/^0+(?=\d)/, '') || '0').toLowerCase())
|
|
1458
1826
|
);
|
|
1459
1827
|
|
|
1460
1828
|
function isDirInMilestone(dirName) {
|
|
@@ -1464,9 +1832,22 @@ function getMilestonePhaseFilter(cwd) {
|
|
|
1464
1832
|
// Try custom ID match (e.g. PROJ-42-description → PROJ-42)
|
|
1465
1833
|
const customMatch = dirName.match(/^([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*)/);
|
|
1466
1834
|
if (customMatch && normalized.has(customMatch[1].toLowerCase())) return true;
|
|
1835
|
+
// #3600: project-code-prefixed directory (`CK-01-name`) against a
|
|
1836
|
+
// numeric ROADMAP heading (`### Phase 1:`). Strip the same prefix
|
|
1837
|
+
// shape `normalizePhaseName` recognises (`^[A-Z]{1,6}-(?=\d)`) and
|
|
1838
|
+
// retry the numeric match. This runs AFTER the custom-ID match so
|
|
1839
|
+
// a roadmap that uses `Phase PROJ-42:` continues to win via the
|
|
1840
|
+
// existing custom-ID path; the strip-and-retry only fires when the
|
|
1841
|
+
// milestone is keyed on the bare numeric form.
|
|
1842
|
+
const stripped = dirName.replace(/^[A-Z]{1,6}-(?=\d)/i, '');
|
|
1843
|
+
if (stripped !== dirName) {
|
|
1844
|
+
const sm = stripped.match(/^0*(\d+[A-Za-z]?(?:\.\d+)*)/);
|
|
1845
|
+
if (sm && normalized.has(sm[1].toLowerCase())) return true;
|
|
1846
|
+
}
|
|
1467
1847
|
return false;
|
|
1468
1848
|
}
|
|
1469
1849
|
isDirInMilestone.phaseCount = milestonePhaseNums.size;
|
|
1850
|
+
isDirInMilestone.missingExplicitVersion = missingExplicitVersion;
|
|
1470
1851
|
return isDirInMilestone;
|
|
1471
1852
|
}
|
|
1472
1853
|
|
|
@@ -1514,48 +1895,44 @@ function readSubdirectories(dirPath, sort = false) {
|
|
|
1514
1895
|
}
|
|
1515
1896
|
}
|
|
1516
1897
|
|
|
1517
|
-
// ─── Atomic file writes ───────────────────────────────────────────────────────
|
|
1518
|
-
|
|
1519
1898
|
/**
|
|
1520
|
-
*
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
1523
|
-
* are on the same filesystem. This prevents a process killed mid-write from
|
|
1524
|
-
* leaving a truncated file that is unparseable on next read.
|
|
1525
|
-
*
|
|
1526
|
-
* The temp file is placed alongside the target so it is guaranteed to be on
|
|
1527
|
-
* the same filesystem (required for rename atomicity). The PID is embedded in
|
|
1528
|
-
* the temp file name so concurrent writers use distinct paths.
|
|
1529
|
-
*
|
|
1530
|
-
* If `renameSync` fails (e.g. cross-device move), the function falls back to a
|
|
1531
|
-
* direct `writeFileSync` so callers always get a best-effort write.
|
|
1532
|
-
*
|
|
1533
|
-
* @param {string} filePath Absolute path to write.
|
|
1534
|
-
* @param {string|Buffer} content File content.
|
|
1535
|
-
* @param {string} [encoding='utf-8'] Encoding passed to writeFileSync.
|
|
1899
|
+
* Format a Date as a fuzzy relative time string (e.g. "5 minutes ago").
|
|
1900
|
+
* @param {Date} date
|
|
1901
|
+
* @returns {string}
|
|
1536
1902
|
*/
|
|
1537
|
-
function
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
}
|
|
1903
|
+
function timeAgo(date) {
|
|
1904
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
1905
|
+
if (seconds < 5) return 'just now';
|
|
1906
|
+
if (seconds < 60) return `${seconds} seconds ago`;
|
|
1907
|
+
const minutes = Math.floor(seconds / 60);
|
|
1908
|
+
if (minutes === 1) return '1 minute ago';
|
|
1909
|
+
if (minutes < 60) return `${minutes} minutes ago`;
|
|
1910
|
+
const hours = Math.floor(minutes / 60);
|
|
1911
|
+
if (hours === 1) return '1 hour ago';
|
|
1912
|
+
if (hours < 24) return `${hours} hours ago`;
|
|
1913
|
+
const days = Math.floor(hours / 24);
|
|
1914
|
+
if (days === 1) return '1 day ago';
|
|
1915
|
+
if (days < 30) return `${days} days ago`;
|
|
1916
|
+
const months = Math.floor(days / 30);
|
|
1917
|
+
if (months === 1) return '1 month ago';
|
|
1918
|
+
if (months < 12) return `${months} months ago`;
|
|
1919
|
+
const years = Math.floor(days / 365);
|
|
1920
|
+
if (years === 1) return '1 year ago';
|
|
1921
|
+
return `${years} years ago`;
|
|
1547
1922
|
}
|
|
1548
1923
|
|
|
1549
1924
|
module.exports = {
|
|
1550
1925
|
output,
|
|
1551
1926
|
error,
|
|
1552
|
-
|
|
1927
|
+
ERROR_REASON,
|
|
1928
|
+
setJsonErrorMode,
|
|
1929
|
+
getJsonErrorMode,
|
|
1553
1930
|
loadConfig,
|
|
1554
1931
|
isGitIgnored,
|
|
1555
|
-
execGit,
|
|
1556
|
-
normalizeMd,
|
|
1557
1932
|
escapeRegex,
|
|
1558
1933
|
normalizePhaseName,
|
|
1934
|
+
phaseMarkdownRegexSource,
|
|
1935
|
+
phaseMarkdownRegexSourceExact,
|
|
1559
1936
|
comparePhaseNum,
|
|
1560
1937
|
searchPhaseInDir,
|
|
1561
1938
|
extractPhaseToken,
|
|
@@ -1564,7 +1941,16 @@ module.exports = {
|
|
|
1564
1941
|
getArchivedPhaseDirs,
|
|
1565
1942
|
getRoadmapPhaseInternal,
|
|
1566
1943
|
resolveModelInternal,
|
|
1944
|
+
resolveModelForTier,
|
|
1945
|
+
resolveReasoningEffortInternal,
|
|
1946
|
+
RUNTIME_PROFILE_MAP,
|
|
1947
|
+
RUNTIMES_WITH_REASONING_EFFORT,
|
|
1948
|
+
KNOWN_RUNTIMES,
|
|
1949
|
+
RUNTIME_OVERRIDE_TIERS,
|
|
1950
|
+
resolveTierEntry,
|
|
1951
|
+
_resetRuntimeWarningCacheForTests,
|
|
1567
1952
|
pathExistsInternal,
|
|
1953
|
+
gitWorktreeInfoInternal,
|
|
1568
1954
|
generateSlugInternal,
|
|
1569
1955
|
getMilestoneInfo,
|
|
1570
1956
|
getMilestonePhaseFilter,
|
|
@@ -1574,10 +1960,12 @@ module.exports = {
|
|
|
1574
1960
|
toPosixPath,
|
|
1575
1961
|
extractOneLinerFromBody,
|
|
1576
1962
|
resolveWorktreeRoot,
|
|
1963
|
+
// Deprecated re-exports — prefer direct import from planning-workspace.cjs
|
|
1577
1964
|
withPlanningLock,
|
|
1578
1965
|
findProjectRoot,
|
|
1579
1966
|
detectSubRepos,
|
|
1580
1967
|
reapStaleTempFiles,
|
|
1968
|
+
SDD_TEMP_DIR,
|
|
1581
1969
|
MODEL_ALIAS_MAP,
|
|
1582
1970
|
CONFIG_DEFAULTS,
|
|
1583
1971
|
planningDir,
|
|
@@ -1591,5 +1979,7 @@ module.exports = {
|
|
|
1591
1979
|
readSubdirectories,
|
|
1592
1980
|
getAgentsDir,
|
|
1593
1981
|
checkAgentsInstalled,
|
|
1594
|
-
|
|
1982
|
+
timeAgo,
|
|
1983
|
+
pruneOrphanedWorktrees,
|
|
1984
|
+
inspectWorktreeHealth,
|
|
1595
1985
|
};
|