@opengsd/gsd-core 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +459 -0
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +360 -0
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +848 -163
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +8 -3
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/review.md +2 -3
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +672 -146
- package/gsd-core/bin/lib/adr-parser.cjs +4 -2
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +119 -34
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +96 -189
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -2
- package/gsd-core/bin/lib/check-command-router.cjs +213 -49
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +823 -112
- package/gsd-core/bin/lib/config-loader.cjs +66 -4
- package/gsd-core/bin/lib/config.cjs +186 -45
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +164 -45
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +614 -86
- package/gsd-core/bin/lib/install-engine.cjs +29 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +37 -13
- package/gsd-core/bin/lib/model-resolver.cjs +253 -53
- package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +268 -27
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +393 -88
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +356 -42
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +181 -30
- package/gsd-core/bin/lib/state.cjs +265 -27
- package/gsd-core/bin/lib/surface.cjs +77 -3
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +9 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +315 -30
- package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
- package/gsd-core/bin/lib/verify.cjs +320 -48
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +13 -4
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +14 -2
- package/gsd-core/references/tdd.md +30 -4
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +433 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +29 -16
- package/gsd-core/workflows/check-todos.md +6 -4
- package/gsd-core/workflows/cleanup.md +5 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +216 -73
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +41 -264
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +17 -158
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +83 -172
- package/gsd-core/workflows/execute-plan.md +24 -10
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +52 -5
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +56 -14
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +39 -209
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +45 -187
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +132 -20
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +29 -10
- package/gsd-core/workflows/reapply-patches.md +86 -6
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +22 -9
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +14 -12
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +101 -196
- package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +71 -19
- package/hooks/dist/gsd-statusline.js +81 -20
- package/hooks/dist/gsd-validate-commit.sh +97 -8
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +66 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +71 -19
- package/hooks/gsd-statusline.js +81 -20
- package/hooks/gsd-validate-commit.sh +97 -8
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +11 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +131 -12
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +35 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +623 -13
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +29 -9
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
- package/scripts/prompt-injection-scan.sh +18 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +8 -3
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review/SKILL.md +2 -3
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -61,6 +61,13 @@ const planDependencyGraphMod = require("./plan-dependency-graph.cjs");
|
|
|
61
61
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
62
|
const verificationMod = require("./verification.cjs");
|
|
63
63
|
const { isPhaseComplete } = verificationMod;
|
|
64
|
+
// #4129: the single owner of "count the ROADMAP's milestone Complete rows"
|
|
65
|
+
// (phase-lifecycle.cts) — reused for the completed-phases numerator floor so
|
|
66
|
+
// this scan cannot grow a second ROADMAP parser. Pure computation module (no
|
|
67
|
+
// I/O), so it introduces no cycle on this path.
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
69
|
+
const phaseLifecycleMod = require("./phase-lifecycle.cjs");
|
|
70
|
+
const { deriveProgressFromRoadmap } = phaseLifecycleMod;
|
|
64
71
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
65
72
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
66
73
|
const { SCOPE } = planningScopeMod;
|
|
@@ -286,7 +293,7 @@ function cmdStateGet(cwd, section, raw) {
|
|
|
286
293
|
// Try to find markdown section or field
|
|
287
294
|
const fieldEscaped = (0, pattern_cjs_1.escapeRegex)(section);
|
|
288
295
|
// Check for **field:** value (bold format)
|
|
289
|
-
const boldPattern = new RegExp(
|
|
296
|
+
const boldPattern = new RegExp(`^[ \\t]*\\*\\*${fieldEscaped}:\\*\\*[ \\t]*(.*)`, 'im');
|
|
290
297
|
const boldMatch = content.match(boldPattern);
|
|
291
298
|
if (boldMatch) {
|
|
292
299
|
output({ [section]: boldMatch[1].trim() }, raw, boldMatch[1].trim());
|
|
@@ -314,13 +321,10 @@ function readTextArgOrFile(cwd, value, filePath, label) {
|
|
|
314
321
|
return value;
|
|
315
322
|
// Path traversal guard: ensure file resolves within project directory
|
|
316
323
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
317
|
-
const {
|
|
318
|
-
const
|
|
319
|
-
if (!pathCheck.safe) {
|
|
320
|
-
throw new Error(`${label} path rejected: ${pathCheck.error}`);
|
|
321
|
-
}
|
|
324
|
+
const { assertWithinRoot, PathAcceptance } = require('./security.cjs');
|
|
325
|
+
const contained = assertWithinRoot(filePath, cwd, `${label} path`, PathAcceptance.AbsoluteInsideRoot);
|
|
322
326
|
try {
|
|
323
|
-
return node_fs_1.default.readFileSync(
|
|
327
|
+
return node_fs_1.default.readFileSync(contained, 'utf-8').trimEnd();
|
|
324
328
|
}
|
|
325
329
|
catch {
|
|
326
330
|
throw new Error(`${label} file not found: ${filePath}`);
|
|
@@ -489,7 +493,31 @@ function cmdStateUpdate(cwd, field, value) {
|
|
|
489
493
|
// (#3345's direction) — reported separately from `updated` because this
|
|
490
494
|
// command's contract is a single-field boolean, not a per-field array.
|
|
491
495
|
const reconciled = reconcileReportedFields(statePath, preWriteState, updated ? [field] : [], divergedFields);
|
|
492
|
-
|
|
496
|
+
// #4488: `updateCore` itself already told us whether it matched the field
|
|
497
|
+
// (`updated`, captured above `readModifyWriteStateMd` runs it) — that is a
|
|
498
|
+
// real signal, not a guess. `reconcileReportedFields` answers a DIFFERENT
|
|
499
|
+
// question ("what changed on disk") and, per its own docstring, reports
|
|
500
|
+
// `[]` whenever `preWriteState.fm` is `undefined`. That happens in two
|
|
501
|
+
// known cases, both of which mean "no snapshot was ever captured", not
|
|
502
|
+
// "nothing happened": (a) `readModifyWriteStateMd`'s #948 no-op guard
|
|
503
|
+
// fires because the transform's output was byte-identical to the input —
|
|
504
|
+
// the requested value already equals what's on disk, so the field WAS
|
|
505
|
+
// found and there was simply nothing left to change; (b)
|
|
506
|
+
// `applyPostSyncPreservation`'s `isUnparseableFrontmatter` early return —
|
|
507
|
+
// the ORIGINAL frontmatter block was malformed, so preservation never
|
|
508
|
+
// runs, yet `readModifyWriteStateMd` still persists the transform's raw
|
|
509
|
+
// output via `platformWriteSync`. In neither case did preservation
|
|
510
|
+
// discard or rewrite what the transform wrote, so trusting the
|
|
511
|
+
// transform's own `updated` signal here is never a false positive.
|
|
512
|
+
// Collapsing either case into the same `false` as "field not found" is
|
|
513
|
+
// the #4488 bug — `explainUpdateFailure` then reports a message that is
|
|
514
|
+
// actively false (it tells the caller to add a line that is already
|
|
515
|
+
// there). Every other `false` origin (case-D fallback did not apply, or
|
|
516
|
+
// the transform genuinely found nothing) is unaffected: there
|
|
517
|
+
// `preWriteState.fm` is defined (a normal sync ran) or `updated` was
|
|
518
|
+
// already false before this line.
|
|
519
|
+
const noopBecauseAlreadyCorrect = updated && preWriteState.fm === undefined;
|
|
520
|
+
updated = reconciled.includes(field) || noopBecauseAlreadyCorrect;
|
|
493
521
|
const preserved = reconciled.filter((f) => f !== field);
|
|
494
522
|
if (updated) {
|
|
495
523
|
// #3699 case D: surfaced so a caller can tell "wrote the body source" from
|
|
@@ -1560,6 +1588,18 @@ function cmdStateResolveBlocker(cwd, text, raw) {
|
|
|
1560
1588
|
}
|
|
1561
1589
|
}
|
|
1562
1590
|
function cmdStateRecordSession(cwd, options, raw) {
|
|
1591
|
+
// #4186: a bare invocation is a usage error, not a heartbeat write. The
|
|
1592
|
+
// pre-#4186 handler accepted zero arguments and still refreshed
|
|
1593
|
+
// `Last session` / `Last Date` / `last_updated` — a caller probing the
|
|
1594
|
+
// command's signature (the way other subcommands encourage) silently
|
|
1595
|
+
// mutated STATE.md. Mirrors `state update`'s required-arg guard
|
|
1596
|
+
// (cmdStateUpdate: `error('field and value required for state update')`),
|
|
1597
|
+
// including its ordering: validation precedes the STATE.md existence
|
|
1598
|
+
// check. Either flag suffices — `--resume-file` alone carries an explicit
|
|
1599
|
+
// value the handler must persist.
|
|
1600
|
+
if (!options.stopped_at && (options.resume_file === undefined || options.resume_file === null)) {
|
|
1601
|
+
error('stopped-at or resume-file required for state record-session');
|
|
1602
|
+
}
|
|
1563
1603
|
const statePath = planningPaths(cwd).state;
|
|
1564
1604
|
if (!node_fs_1.default.existsSync(statePath)) {
|
|
1565
1605
|
output({ error: 'STATE.md not found' }, raw, undefined);
|
|
@@ -1569,10 +1609,31 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
1569
1609
|
const updated = [];
|
|
1570
1610
|
let sessionCreated = false;
|
|
1571
1611
|
const divergedFields = [];
|
|
1612
|
+
// #4763 (1): last-writer-wins stays (the recorded single-slot handoff design),
|
|
1613
|
+
// but a displaced record is no longer silent. The pre-write session record is
|
|
1614
|
+
// captured below and surfaced in the payload under `replacedRecord` whenever a
|
|
1615
|
+
// replacement actually changes a non-empty prior value.
|
|
1616
|
+
const priorRecord = {};
|
|
1572
1617
|
// ADR-3473 §8.7 (#3872): caller-allocated out-param, filled with the
|
|
1573
1618
|
// transaction's own pre-write snapshot + body by `applyPostSyncPreservation`.
|
|
1574
1619
|
const preWriteState = {};
|
|
1575
1620
|
readModifyWriteStateMd(statePath, (content) => {
|
|
1621
|
+
// #4763 (1): read the pre-write session record. The capture mirrors the
|
|
1622
|
+
// WRITER, not the snapshot reader: stateReplaceField replaces the FIRST
|
|
1623
|
+
// case-insensitive label match anywhere in the document, so the capture is
|
|
1624
|
+
// document-wide too — scoping it to ## Session would miss an archive-section
|
|
1625
|
+
// line the writer actually displaced. Continuation lines join via
|
|
1626
|
+
// stateFieldContinuation (the sanctioned joiner — stateExtractField alone is
|
|
1627
|
+
// first-line-only), so a wrapped multi-line handoff is surfaced whole.
|
|
1628
|
+
const capturePrior = (fieldName) => {
|
|
1629
|
+
const first = (0, state_document_cjs_1.stateExtractField)(content, fieldName);
|
|
1630
|
+
if (first === null)
|
|
1631
|
+
return undefined;
|
|
1632
|
+
const cont = (0, state_document_cjs_1.stateFieldContinuation)(content, fieldName);
|
|
1633
|
+
return cont ? `${first}\n${cont}` : first;
|
|
1634
|
+
};
|
|
1635
|
+
priorRecord.stoppedAt = capturePrior('Stopped At');
|
|
1636
|
+
priorRecord.resumeFile = capturePrior('Resume File');
|
|
1576
1637
|
// Update Last session / Last Date
|
|
1577
1638
|
let result = (0, state_document_cjs_1.stateReplaceField)(content, 'Last session', now);
|
|
1578
1639
|
if (result) {
|
|
@@ -1796,6 +1857,27 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
1796
1857
|
const result = { recorded: true, updated: reconciledUpdated };
|
|
1797
1858
|
if (sessionCreated)
|
|
1798
1859
|
result['created'] = true;
|
|
1860
|
+
// #4763 (1): surface any non-empty prior record the write displaced. Gated
|
|
1861
|
+
// on reconciledUpdated (the fields that actually persisted, post-#3957
|
|
1862
|
+
// reconcile) rather than the pre-reconciliation updated[]. Only fields with
|
|
1863
|
+
// real prior content differing from the caller's value count — same-value
|
|
1864
|
+
// rewrites, the insert path (no prior label), and the #944 template-default
|
|
1865
|
+
// DWIM (defaults match case-insensitively, so a case-variant 'none' →
|
|
1866
|
+
// 'None' rewrite is normalization, not displacement) are excluded.
|
|
1867
|
+
const isResumeTemplateDefault = priorRecord.resumeFile !== undefined
|
|
1868
|
+
&& state_document_cjs_1.KNOWN_TEMPLATE_DEFAULTS['Resume File'].some((d) => d.toLowerCase() === priorRecord.resumeFile?.toLowerCase());
|
|
1869
|
+
const replacedRecord = {};
|
|
1870
|
+
if (reconciledUpdated.includes('Stopped At') && priorRecord.stoppedAt
|
|
1871
|
+
&& priorRecord.stoppedAt !== options.stopped_at) {
|
|
1872
|
+
replacedRecord['Stopped At'] = priorRecord.stoppedAt;
|
|
1873
|
+
}
|
|
1874
|
+
if (reconciledUpdated.includes('Resume File') && priorRecord.resumeFile
|
|
1875
|
+
&& !isResumeTemplateDefault
|
|
1876
|
+
&& priorRecord.resumeFile !== (options.resume_file ?? undefined)) {
|
|
1877
|
+
replacedRecord['Resume File'] = priorRecord.resumeFile;
|
|
1878
|
+
}
|
|
1879
|
+
if (Object.keys(replacedRecord).length > 0)
|
|
1880
|
+
result['replacedRecord'] = replacedRecord;
|
|
1799
1881
|
output(result, raw, 'true');
|
|
1800
1882
|
}
|
|
1801
1883
|
else if (updated.length === 0) {
|
|
@@ -2512,7 +2594,10 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2512
2594
|
// own comment on that field). Folding this consumer onto the raw
|
|
2513
2595
|
// summaries-met flag was the exact "consolidate two of three and
|
|
2514
2596
|
// leave the third" gap §7.4's forcing function rules out.
|
|
2515
|
-
|
|
2597
|
+
// #612: `phaseConvention` threaded so a bracket phase dir resolves
|
|
2598
|
+
// and scopes its verification report like its legacy twin — the
|
|
2599
|
+
// read-side half of the same thread cmdStateSync gets below.
|
|
2600
|
+
if (isPhaseComplete(phaseDir, { convention: phaseConvention }).value.complete)
|
|
2516
2601
|
diskCompletedPhases++;
|
|
2517
2602
|
}
|
|
2518
2603
|
// Count phase headings from ROADMAP — single source of truth for
|
|
@@ -2619,6 +2704,34 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2619
2704
|
// write silently clobbered the three stored siblings with the
|
|
2620
2705
|
// under-scoped disk numbers.
|
|
2621
2706
|
const diskCountsWithheld = milestonedButUnbounded || roadmapAbsentWithAssertedMilestone;
|
|
2707
|
+
// #4129: floor the completed-phases numerator at the ROADMAP's own
|
|
2708
|
+
// milestone Complete-row count. The disk numerator counts ONLY
|
|
2709
|
+
// phase dirs whose *-VERIFICATION.md routes `passed` (isPhaseComplete,
|
|
2710
|
+
// #2957 disk-strict — the gate stays untouched), so a completed
|
|
2711
|
+
// phase whose verification reads `stale` (a SUMMARY committed or
|
|
2712
|
+
// edited after it, #2348 clean-commit-time clock) or `missing`
|
|
2713
|
+
// (pre-verification era, hand-flipped ROADMAP row) drops out of the
|
|
2714
|
+
// count forever — while every other surface (the ROADMAP row
|
|
2715
|
+
// `phase complete` just flipped, the body `Completed Phases` field
|
|
2716
|
+
// completePhaseCore derives from deriveProgressFromRoadmap) still
|
|
2717
|
+
// asserts the phase complete. max(disk, ROADMAP) keeps the disk
|
|
2718
|
+
// signal for gap detection (a verification-passed phase whose ROADMAP
|
|
2719
|
+
// row is not yet flipped still counts) while never UNDER-counting
|
|
2720
|
+
// what the ROADMAP asserts. Scoped exactly like the denominator:
|
|
2721
|
+
// the same milestone window (roadmapScope), the same
|
|
2722
|
+
// safeToUseRoadmapCount gate, and never under the #3354/#3573
|
|
2723
|
+
// withhold — a whole-document Complete-row count must not leak
|
|
2724
|
+
// through an untrustworthy scope. Reuses deriveProgressFromRoadmap
|
|
2725
|
+
// (phase-lifecycle.cts, the one owner of "read the Progress table")
|
|
2726
|
+
// — no second ROADMAP parser here. A ROADMAP without a canonical
|
|
2727
|
+
// `## Progress` table resolves no table → floor inert (disk count
|
|
2728
|
+
// stands), the owner's own answer to "what is countable".
|
|
2729
|
+
const roadmapCompletedPhases = roadmapScope !== null && safeToUseRoadmapCount && !diskCountsWithheld
|
|
2730
|
+
? deriveProgressFromRoadmap(roadmapScope).completedPhases
|
|
2731
|
+
: null;
|
|
2732
|
+
const flooredCompletedPhases = roadmapCompletedPhases !== null
|
|
2733
|
+
? Math.max(diskCompletedPhases, roadmapCompletedPhases)
|
|
2734
|
+
: diskCompletedPhases;
|
|
2622
2735
|
return {
|
|
2623
2736
|
// The two WITHHOLD shapes (#3354 milestoned-but-unbounded, #3573
|
|
2624
2737
|
// roadmap-absent-with-asserted-milestone) must be evaluated BEFORE
|
|
@@ -2629,7 +2742,7 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2629
2742
|
? null
|
|
2630
2743
|
: (safeToUseRoadmapCount ? Math.max(phaseDirs.length, roadmapPhaseCount) : phaseDirs.length),
|
|
2631
2744
|
milestoneBounded,
|
|
2632
|
-
completedPhases: diskCountsWithheld ? null :
|
|
2745
|
+
completedPhases: diskCountsWithheld ? null : flooredCompletedPhases,
|
|
2633
2746
|
totalPlans: diskCountsWithheld ? null : diskTotalPlans,
|
|
2634
2747
|
completedPlans: diskCountsWithheld ? null : diskTotalSummaries,
|
|
2635
2748
|
phaseDirScope,
|
|
@@ -2722,19 +2835,19 @@ storedCompletedPhases, storedTotalPlans, storedCompletedPlans) {
|
|
|
2722
2835
|
progressPercent = parseInt(pctMatch[1], 10);
|
|
2723
2836
|
}
|
|
2724
2837
|
let normalizedStatus = (0, state_document_cjs_1.normalizeStateStatus)(status, pausedAt);
|
|
2725
|
-
// #3578:
|
|
2726
|
-
//
|
|
2727
|
-
//
|
|
2728
|
-
//
|
|
2729
|
-
// prose must never decide milestone-level
|
|
2730
|
-
// totalPhases / diskScope, already derived above
|
|
2731
|
-
// the authority on whether the MILESTONE is actually
|
|
2732
|
-
// when: (a) normalizeStateStatus actually landed on
|
|
2733
|
-
// raw prose is UNAMBIGUOUSLY phase-completion prose —
|
|
2734
|
-
// pattern below deliberately excludes "All phases complete"
|
|
2735
|
-
// phase token) and milestone-close prose like "v1.0 milestone
|
|
2736
|
-
// (no leading "phase"); and (c) the counters are trustworthy (a
|
|
2737
|
-
// disk scope, both counts are finite numbers, and a positive
|
|
2838
|
+
// #3578: the declared status vocabulary (#4186) recognizes
|
|
2839
|
+
// `Phase ${N} complete` (state.cts's own phase-completion write) and maps
|
|
2840
|
+
// it to `completed`, so the phase-completion prose still collapses to the
|
|
2841
|
+
// milestone-level status even when other phases remain open — this guard
|
|
2842
|
+
// demotes it back. Phase-level prose must never decide milestone-level
|
|
2843
|
+
// status — completedPhases / totalPhases / diskScope, already derived above
|
|
2844
|
+
// from a disk scan, are the authority on whether the MILESTONE is actually
|
|
2845
|
+
// done. Only override when: (a) normalizeStateStatus actually landed on
|
|
2846
|
+
// 'completed'; (b) the raw prose is UNAMBIGUOUSLY phase-completion prose —
|
|
2847
|
+
// the anchored pattern below deliberately excludes "All phases complete"
|
|
2848
|
+
// (no `\S+` phase token) and milestone-close prose like "v1.0 milestone
|
|
2849
|
+
// complete" (no leading "phase"); and (c) the counters are trustworthy (a
|
|
2850
|
+
// COMPLETE disk scope, both counts are finite numbers, and a positive
|
|
2738
2851
|
// denominator) and affirmatively disagree with 'completed'. In every
|
|
2739
2852
|
// other case normalizedStatus is left exactly as normalizeStateStatus
|
|
2740
2853
|
// returned it.
|
|
@@ -2996,6 +3109,75 @@ function readStoredTotalPlans(existingFm) {
|
|
|
2996
3109
|
function readStoredCompletedPlans(existingFm) {
|
|
2997
3110
|
return readStoredProgressCounter(existingFm, 'completed_plans');
|
|
2998
3111
|
}
|
|
3112
|
+
/**
|
|
3113
|
+
* #4129: is this authoritativeFm value a PARTIAL progress intent? The #2736
|
|
3114
|
+
* seam was string-only (names); #4129 extends it with one object direction —
|
|
3115
|
+
* the `progress` key carrying the sub-keys a transition resolved
|
|
3116
|
+
* authoritatively (completePhase's ROADMAP-derived completed_phases/percent).
|
|
3117
|
+
* Anything else keeps the seam's existing contract untouched.
|
|
3118
|
+
*/
|
|
3119
|
+
function isPartialProgressIntent(value) {
|
|
3120
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3121
|
+
}
|
|
3122
|
+
/**
|
|
3123
|
+
* #4129: merge a PARTIAL progress intent (see isPartialProgressIntent) into a
|
|
3124
|
+
* frontmatter object's `progress` block. Sub-keys are accepted only when they
|
|
3125
|
+
* are a declared `progress.*` row in FIELD_CLASSIFICATION — the single policy
|
|
3126
|
+
* source (ADR-3408 §8.5) decides which leaves exist; an intent may not invent
|
|
3127
|
+
* one. Returns whether anything changed.
|
|
3128
|
+
*
|
|
3129
|
+
* `completedOnlyRaise` (both application sites use it): completed counters
|
|
3130
|
+
* apply only when strictly greater than what is already in the block, so no
|
|
3131
|
+
* intent can LOWER a count another trustworthy signal already established —
|
|
3132
|
+
* at the pre-preservation site the disk derivation's own count (a
|
|
3133
|
+
* verification-passed phase whose ROADMAP row drifted behind), at the
|
|
3134
|
+
* post-preservation re-assert the #2969 monotonic property preservation just
|
|
3135
|
+
* enforced. `percent` follows its sibling: it is applied when a completed
|
|
3136
|
+
* counter moved this call (the intent percent was computed from the intent
|
|
3137
|
+
* counters and is coherent with them) or when the block has no percent to
|
|
3138
|
+
* lose (a repair, never a regression of an upstream withhold — the withhold
|
|
3139
|
+
* nulled percent upstream precisely so no write would re-assert one over
|
|
3140
|
+
* untrustworthy counts; here the intent's own counts ARE the trustworthy
|
|
3141
|
+
* source, the post-completion ROADMAP).
|
|
3142
|
+
*/
|
|
3143
|
+
function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
|
|
3144
|
+
const current = fm['progress'];
|
|
3145
|
+
const base = isPartialProgressIntent(current)
|
|
3146
|
+
? { ...current }
|
|
3147
|
+
: {};
|
|
3148
|
+
let changed = false;
|
|
3149
|
+
let completedMoved = false;
|
|
3150
|
+
for (const [subkey, value] of Object.entries(intent)) {
|
|
3151
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
3152
|
+
continue;
|
|
3153
|
+
if (!getFieldClassification(`progress.${subkey}`))
|
|
3154
|
+
continue;
|
|
3155
|
+
const isCompletedCounter = subkey === 'completed_phases' || subkey === 'completed_plans';
|
|
3156
|
+
if (isCompletedCounter && opts.completedOnlyRaise) {
|
|
3157
|
+
const currentNum = (0, state_document_cjs_1.toFiniteNumber)(base[subkey]);
|
|
3158
|
+
if (currentNum !== null && currentNum >= value)
|
|
3159
|
+
continue;
|
|
3160
|
+
}
|
|
3161
|
+
if (isCompletedCounter && !Object.is(base[subkey], value))
|
|
3162
|
+
completedMoved = true;
|
|
3163
|
+
if (!Object.is(base[subkey], value)) {
|
|
3164
|
+
base[subkey] = value;
|
|
3165
|
+
changed = true;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
// percent: applied only when a completed counter moved (coherent with the
|
|
3169
|
+
// counters that just landed) or when no percent exists to contradict.
|
|
3170
|
+
const intentPercent = intent['percent'];
|
|
3171
|
+
if (typeof intentPercent === 'number' && Number.isFinite(intentPercent) && (completedMoved || (0, state_document_cjs_1.toFiniteNumber)(base['percent']) === null)) {
|
|
3172
|
+
if (!Object.is(base['percent'], intentPercent)) {
|
|
3173
|
+
base['percent'] = intentPercent;
|
|
3174
|
+
changed = true;
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
if (changed)
|
|
3178
|
+
fm['progress'] = base;
|
|
3179
|
+
return changed;
|
|
3180
|
+
}
|
|
2999
3181
|
function syncStateFrontmatter(content, cwd, authoritativeFm, sanctionedPermanentEmptyFallback) {
|
|
3000
3182
|
// Read existing frontmatter BEFORE stripping — it may contain values
|
|
3001
3183
|
// that the body no longer has (e.g., Status field removed by an agent).
|
|
@@ -3201,11 +3383,22 @@ function syncStateFrontmatter(content, cwd, authoritativeFm, sanctionedPermanent
|
|
|
3201
3383
|
// parenthetical (`Closer-ruling measurement (D1a)` → `D1a`) — never runs
|
|
3202
3384
|
// the final word on a field the transition just resolved. The prose parser
|
|
3203
3385
|
// remains the fallback for genuinely unknown prose only.
|
|
3386
|
+
// #4129: the `progress` key carries a PARTIAL block (the object direction of
|
|
3387
|
+
// this seam — see applyAuthoritativeProgressSubkeys) for the same reason:
|
|
3388
|
+
// completePhase holds the POST-completion ROADMAP, and the disk scan this
|
|
3389
|
+
// function drives reads the PRE-completion one. The intent is applied as a
|
|
3390
|
+
// FLOOR here too (completedOnlyRaise): a derivation that already counted
|
|
3391
|
+
// MORE completed phases than the ROADMAP table asserts (verification-passed
|
|
3392
|
+
// phases whose table rows drifted behind) must not be lowered by the intent
|
|
3393
|
+
// — the two signals agree on direction (up), never on subtraction.
|
|
3204
3394
|
if (authoritativeFm) {
|
|
3205
3395
|
for (const [key, value] of Object.entries(authoritativeFm)) {
|
|
3206
3396
|
if (typeof value === 'string' && value.trim().length > 0) {
|
|
3207
3397
|
derivedFm[key] = value;
|
|
3208
3398
|
}
|
|
3399
|
+
else if (key === 'progress' && isPartialProgressIntent(value)) {
|
|
3400
|
+
applyAuthoritativeProgressSubkeys(derivedFm, value, { completedOnlyRaise: true });
|
|
3401
|
+
}
|
|
3209
3402
|
}
|
|
3210
3403
|
}
|
|
3211
3404
|
// #3257: propagate full-line frontmatter comments from the extracted source onto the
|
|
@@ -3782,6 +3975,10 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
3782
3975
|
// (equal), so the #1695 restore fires and would put the stale pre-transition
|
|
3783
3976
|
// name back over the authoritative one. Intent beats both the prose
|
|
3784
3977
|
// re-derivation and the curated restore — the transition just resolved it.
|
|
3978
|
+
// #4129: for the `progress` key the re-assert is a FLOOR, not an override —
|
|
3979
|
+
// the #2969 monotonic property preservation just enforced (completed
|
|
3980
|
+
// counters never move down) must not be undone by the intent, so completed
|
|
3981
|
+
// sub-keys apply only-raise here (see applyAuthoritativeProgressSubkeys).
|
|
3785
3982
|
let authoritativeReasserted = false;
|
|
3786
3983
|
if (authoritativeFm) {
|
|
3787
3984
|
for (const [key, value] of Object.entries(authoritativeFm)) {
|
|
@@ -3789,6 +3986,11 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
3789
3986
|
preservation.postFm[key] = value;
|
|
3790
3987
|
authoritativeReasserted = true;
|
|
3791
3988
|
}
|
|
3989
|
+
else if (key === 'progress' && isPartialProgressIntent(value)) {
|
|
3990
|
+
if (applyAuthoritativeProgressSubkeys(preservation.postFm, value, { completedOnlyRaise: true })) {
|
|
3991
|
+
authoritativeReasserted = true;
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3792
3994
|
}
|
|
3793
3995
|
}
|
|
3794
3996
|
let finalContent = syncedContent;
|
|
@@ -4454,6 +4656,26 @@ function cmdStateJson(cwd, raw) {
|
|
|
4454
4656
|
* Fixes: #1102 (plan counts), #1103 (status/last_activity), #1104 (body text).
|
|
4455
4657
|
*/
|
|
4456
4658
|
function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
4659
|
+
// #4138: `--phase` is this verb's one required argument, and an invocation
|
|
4660
|
+
// that names no phase must fail closed BEFORE any read-modify-write runs —
|
|
4661
|
+
// previously the missing flag flowed through as null and the transition
|
|
4662
|
+
// serialised `String(null)` into the body (`Phase: null — EXECUTING`,
|
|
4663
|
+
// `Status: Executing Phase null`, `last_activity_desc: Phase null execution
|
|
4664
|
+
// started`) while the post-sync frontmatter rebuild dropped current_phase /
|
|
4665
|
+
// current_phase_name entirely, so a single argument-less call un-set the
|
|
4666
|
+
// phase identity. The guard mirrors the sibling usage errors that already
|
|
4667
|
+
// exit non-zero (`state update`'s "field and value required", the router's
|
|
4668
|
+
// "unexpected positional argument" / "Invalid --plans value"), NOT
|
|
4669
|
+
// `cmdStateMilestoneSwitch`'s `output({error})` form, which exits 0 — the
|
|
4670
|
+
// issue's Expected is explicit: "Exit non-zero with a usage message and
|
|
4671
|
+
// write nothing." Empty and whitespace-only values are the same missing
|
|
4672
|
+
// argument (CONTRIBUTING.md CLI matrix); a flag-shaped `--phase --name x`
|
|
4673
|
+
// resolves to null in parseNamedArgs and lands here too. Runs before the
|
|
4674
|
+
// STATE.md existence check so argument validation always precedes I/O, and
|
|
4675
|
+
// before claimMilestonePhase so no phase-"null" milestone claim is taken.
|
|
4676
|
+
if (phaseNumber == null || String(phaseNumber).trim() === '') {
|
|
4677
|
+
error('phase required (--phase <N>)');
|
|
4678
|
+
}
|
|
4457
4679
|
const statePath = planningPaths(cwd).state;
|
|
4458
4680
|
if (!node_fs_1.default.existsSync(statePath)) {
|
|
4459
4681
|
output({ error: 'STATE.md not found' }, raw, undefined);
|
|
@@ -4467,7 +4689,11 @@ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
|
4467
4689
|
// #1230 post-sync preservation, and the no-op write guard.
|
|
4468
4690
|
const intent = {
|
|
4469
4691
|
kind: 'beginPhase',
|
|
4470
|
-
|
|
4692
|
+
// The guard above made this non-null/non-empty; `error` is never-returning
|
|
4693
|
+
// at runtime but this module's destructured io binding does not narrow CFA,
|
|
4694
|
+
// so the narrowed fact is restated once (cmdStateUpdate's `field as string`
|
|
4695
|
+
// idiom, state.cts:782).
|
|
4696
|
+
phaseNumber: phaseNumber,
|
|
4471
4697
|
phaseName: phaseName ?? null,
|
|
4472
4698
|
planCount: planCount ?? null,
|
|
4473
4699
|
};
|
|
@@ -4763,6 +4989,12 @@ function updatePerformanceMetricsSection(content, cwd, phaseNum, planCount, summ
|
|
|
4763
4989
|
* Updates Status to "Ready to execute", Total Plans, Last Activity.
|
|
4764
4990
|
*/
|
|
4765
4991
|
function cmdStatePlannedPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
4992
|
+
// #4383: mirror begin-phase's command-boundary guard. A missing phase must
|
|
4993
|
+
// fail before even looking up STATE.md so no invalid invocation can enter
|
|
4994
|
+
// the read-modify-write path and serialize a null/blank phase identity.
|
|
4995
|
+
if (phaseNumber == null || String(phaseNumber).trim() === '') {
|
|
4996
|
+
error('phase required (--phase <N>)');
|
|
4997
|
+
}
|
|
4766
4998
|
const statePath = planningPaths(cwd).state;
|
|
4767
4999
|
if (!node_fs_1.default.existsSync(statePath)) {
|
|
4768
5000
|
output({ error: 'STATE.md not found' }, raw, undefined);
|
|
@@ -4778,7 +5010,7 @@ function cmdStatePlannedPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
|
4778
5010
|
// still owns the lock, the #1230 preservation, and the no-op write guard.
|
|
4779
5011
|
const intent = {
|
|
4780
5012
|
kind: 'plannedPhase',
|
|
4781
|
-
phaseNumber,
|
|
5013
|
+
phaseNumber: phaseNumber,
|
|
4782
5014
|
phaseName: phaseName ?? null,
|
|
4783
5015
|
planCount: planCount ?? null,
|
|
4784
5016
|
};
|
|
@@ -5134,7 +5366,10 @@ function cmdStateValidate(cwd, raw, opts = {}) {
|
|
|
5134
5366
|
// ("verification passed" drift), not a false S007.
|
|
5135
5367
|
const files = node_fs_1.default.readdirSync(phaseDirPath);
|
|
5136
5368
|
const phaseDirBaseName = node_path_1.default.basename(phaseDirPath);
|
|
5137
|
-
|
|
5369
|
+
// #612: `validateConvention` threaded (already resolved above for
|
|
5370
|
+
// `phaseKeyFromDir`) so the S006/S007 scan scopes bracket dirs by
|
|
5371
|
+
// their real token instead of the include-everything fail-safe.
|
|
5372
|
+
const verificationFiles = scopeToPhase(files.filter(f => f.includes('VERIFICATION') && f.endsWith('.md')), phaseDirBaseName, validateConvention);
|
|
5138
5373
|
for (const vf of verificationFiles) {
|
|
5139
5374
|
try {
|
|
5140
5375
|
const vContent = node_fs_1.default.readFileSync(node_path_1.default.join(phaseDirPath, vf), 'utf-8');
|
|
@@ -5325,7 +5560,10 @@ function cmdStateSync(cwd, options, raw) {
|
|
|
5325
5560
|
// was a second, independent consumer of the same raw field the initial
|
|
5326
5561
|
// migration missed — without it, `state sync` and `state json` disagreed
|
|
5327
5562
|
// on completed_phases for the identical disk state.
|
|
5328
|
-
|
|
5563
|
+
// #612: `syncConvention` threaded — the write-side half of
|
|
5564
|
+
// buildStateFrontmatter's thread above, so `state sync` and `state json`
|
|
5565
|
+
// keep agreeing on completed_phases under the bracket convention.
|
|
5566
|
+
if (isPhaseComplete(dirPath, { convention: syncConvention }).value.complete)
|
|
5329
5567
|
diskCompletedPhases++;
|
|
5330
5568
|
// Track the highest phase with incomplete plans (or any plans)
|
|
5331
5569
|
const phaseMatch = dir.match(new RegExp(`^(${PHASE_NUMBER_TOKEN_SOURCE})`, 'i'));
|
|
@@ -61,6 +61,18 @@ const runtimeArtifactInstallPlan = require("./runtime-artifact-install-plan.cjs"
|
|
|
61
61
|
const retiredArtifactCleanup = require("./retired-artifact-cleanup.cjs");
|
|
62
62
|
const { assertDestWithinConfigHome } = runtimeArtifactInstallPlan;
|
|
63
63
|
const SURFACE_FILE_NAME = '.gsd-surface.json';
|
|
64
|
+
const INSTALL_MANIFEST_FILE_NAME = 'gsd-file-manifest.json';
|
|
65
|
+
/** Read only the installer-owned relative-include style, failing closed. */
|
|
66
|
+
function readRelativeIncludePrefix(runtimeConfigDir) {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(runtimeConfigDir, INSTALL_MANIFEST_FILE_NAME), 'utf8'));
|
|
69
|
+
const prefix = parsed?.['relativeIncludePrefix'];
|
|
70
|
+
return typeof prefix === 'string' ? prefix : undefined;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
64
76
|
/**
|
|
65
77
|
* Read the surface state from a runtime config directory.
|
|
66
78
|
*
|
|
@@ -341,7 +353,11 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
341
353
|
const _isGlobal = (0, install_scope_cjs_1.isGlobalScope)(layout.scope ?? 'global');
|
|
342
354
|
const _isOpencode = layout.runtime === 'opencode';
|
|
343
355
|
const _isWindowsHost = (opts?.platform ?? process.platform) === 'win32';
|
|
344
|
-
|
|
356
|
+
// #4377: style is an install-time fact, not a process environment setting.
|
|
357
|
+
// A later gsd-tools surface apply runs in another process, so it must reuse
|
|
358
|
+
// the persisted, root-relative prefix the installer actually emitted.
|
|
359
|
+
const _relativeIncludePrefix = readRelativeIncludePrefix(runtimeConfigDir);
|
|
360
|
+
const _pathPrefix = runtimeArtifactConversion._computePathPrefix({ isGlobal: _isGlobal, isOpencode: _isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget: _resolvedTarget, homeDir: _homeDir, projectRelative: !_isGlobal && typeof _relativeIncludePrefix === 'string', projectRelativePath: _relativeIncludePrefix, localDirName: runtimeArtifactConversion._localIncludeDirName(layout.runtime) });
|
|
345
361
|
const _attribution = opts?.resolveAttribution ? opts.resolveAttribution(layout.runtime) : undefined;
|
|
346
362
|
// #2875 Part 2 (row I1): layout.configDir is this call's install root.
|
|
347
363
|
const agentCtx = { runtime: layout.runtime, pathPrefix: _pathPrefix, attribution: _attribution, targetDir: layout.configDir };
|
|
@@ -364,14 +380,25 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
364
380
|
try {
|
|
365
381
|
for (const kind of layout.kinds) {
|
|
366
382
|
let staged;
|
|
367
|
-
|
|
383
|
+
// #4211: kimi-agents is an AGENT kind — kimiAgentsKind.stage() forwards
|
|
384
|
+
// agentCtx into stageAgentsForRuntimeWithConverter exactly as agentsKind
|
|
385
|
+
// does, and createRuntimeArtifactInstallPlan hands every kind the
|
|
386
|
+
// context. Staging it bare here dropped the path-prefix rewrites and the
|
|
387
|
+
// attribution trailer from Kimi's generated subagents, and (under an
|
|
388
|
+
// unmodified `full` profile) staged only the skill-referenced subset the
|
|
389
|
+
// install path stages with `skills: '*'`.
|
|
390
|
+
if (kind.kind === 'agents' || kind.kind === 'kimi-agents') {
|
|
368
391
|
const agentProfile = _isUnmodifiedFull ? { ...resolved, skills: '*' } : resolved;
|
|
369
392
|
staged = kind.stage(agentProfile, agentCtx);
|
|
370
393
|
}
|
|
371
394
|
else {
|
|
372
395
|
staged = kind.stage(resolved);
|
|
373
396
|
}
|
|
374
|
-
|
|
397
|
+
// #4211: kimi-agents takes the skill-body rewrite too —
|
|
398
|
+
// createRuntimeArtifactInstallPlan routes `skills` and `kimi-agents`
|
|
399
|
+
// through rewriteStagedSkillBodies together, so omitting it here left
|
|
400
|
+
// Kimi's surface-materialized prompts with unrewritten paths.
|
|
401
|
+
if (kind.kind === 'skills' || kind.kind === 'kimi-agents') {
|
|
375
402
|
runtimeArtifactConversion.rewriteStagedSkillBodies(staged, {
|
|
376
403
|
runtime: layout.runtime,
|
|
377
404
|
configDir: layout.configDir,
|
|
@@ -572,6 +599,53 @@ function _syncGsdDir(stagedDir, destDir, kind, manifest, runtime) {
|
|
|
572
599
|
// (no agentFileExtension declared) keep the staged filename verbatim.
|
|
573
600
|
const _agentExt = runtime ? runtimeArtifactConversion.agentFileExtensionFor(runtime) : undefined;
|
|
574
601
|
const isRenamedAgents = !!_agentExt && kindName === 'agents';
|
|
602
|
+
if (kindName === 'kimi-agents') {
|
|
603
|
+
// #4211: Kimi's managed tree is `gsd.yaml` + `gsd.md` + `subagents/gsd-*.{yaml,md}`
|
|
604
|
+
// (runtime-artifact-layout.cts kimiAgentsKind), and install copies it
|
|
605
|
+
// RECURSIVELY (_copyStaged in src/install-engine.cts). Surface apply fell
|
|
606
|
+
// through to the flat command/agent branch below, which reads only `*.md`
|
|
607
|
+
// at the top level: the YAML half and the whole subagents/ subtree were
|
|
608
|
+
// dropped, and `gsd.md` was written as `gsdgsd.md` (the flat branch
|
|
609
|
+
// re-applies kind.prefix to a name that already carries it). A surface
|
|
610
|
+
// change could therefore corrupt Kimi's installed artifacts while still
|
|
611
|
+
// reporting success.
|
|
612
|
+
node_fs_1.default.cpSync(stagedDir, destDir, { recursive: true });
|
|
613
|
+
// Prune GSD-owned files the new surface no longer stages, with exactly the
|
|
614
|
+
// ownership rule install's _removeGsdEntries applies to this kind: the two
|
|
615
|
+
// root files, and `gsd-`-prefixed .yaml/.md under subagents/. Everything
|
|
616
|
+
// else in the directory is user-owned and is preserved.
|
|
617
|
+
const _rootStaged = new Set(node_fs_1.default.readdirSync(stagedDir));
|
|
618
|
+
for (const fileName of ['gsd.yaml', 'gsd.md']) {
|
|
619
|
+
if (!_rootStaged.has(fileName)) {
|
|
620
|
+
try {
|
|
621
|
+
node_fs_1.default.rmSync(node_path_1.default.join(destDir, fileName), { force: true });
|
|
622
|
+
}
|
|
623
|
+
catch { /* ignore */ }
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const _stagedSubagentsDir = node_path_1.default.join(stagedDir, 'subagents');
|
|
627
|
+
const _destSubagentsDir = node_path_1.default.join(destDir, 'subagents');
|
|
628
|
+
const _stagedSubagents = node_fs_1.default.existsSync(_stagedSubagentsDir)
|
|
629
|
+
? new Set(node_fs_1.default.readdirSync(_stagedSubagentsDir))
|
|
630
|
+
: new Set();
|
|
631
|
+
if (node_fs_1.default.existsSync(_destSubagentsDir)) {
|
|
632
|
+
for (const entry of node_fs_1.default.readdirSync(_destSubagentsDir, { withFileTypes: true })) {
|
|
633
|
+
if (!entry.isFile())
|
|
634
|
+
continue;
|
|
635
|
+
if (!entry.name.startsWith('gsd-'))
|
|
636
|
+
continue;
|
|
637
|
+
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md'))
|
|
638
|
+
continue;
|
|
639
|
+
if (_stagedSubagents.has(entry.name))
|
|
640
|
+
continue;
|
|
641
|
+
try {
|
|
642
|
+
node_fs_1.default.rmSync(node_path_1.default.join(_destSubagentsDir, entry.name), { force: true });
|
|
643
|
+
}
|
|
644
|
+
catch { /* ignore */ }
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
575
649
|
if (kindName === 'skills') {
|
|
576
650
|
// Skills kind: work with directories, not files.
|
|
577
651
|
// Each staged entry is a directory named ${prefix}${stem}.
|
|
@@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
};
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const security_cjs_1 = require("./security.cjs");
|
|
14
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
16
|
const ioMod = require("./io.cjs");
|
|
16
17
|
const { output, error, ERROR_REASON } = ioMod;
|
|
@@ -108,12 +109,14 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
110
111
|
const projectRoot = node_path_1.default.resolve(cwd || process.cwd());
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// Lexical containment (ADR-4650): this path is validated before existence is
|
|
113
|
+
// checked below, so realpath resolution is neither available nor required.
|
|
114
|
+
const contained = (0, security_cjs_1.tryWithinRootLexical)(plan, projectRoot);
|
|
115
|
+
if (contained === null) {
|
|
114
116
|
error(`Plan file is outside project scope: ${plan}`, ERROR_REASON.USAGE);
|
|
115
117
|
return;
|
|
116
118
|
}
|
|
119
|
+
const resolvedPlanPath = contained;
|
|
117
120
|
if (!node_fs_1.default.existsSync(resolvedPlanPath)) {
|
|
118
121
|
error(`Plan file not found: ${plan}`, ERROR_REASON.USAGE);
|
|
119
122
|
return;
|
|
@@ -173,11 +176,14 @@ function routeTaskCommand({ args, cwd, raw }) {
|
|
|
173
176
|
else if (args[2]) {
|
|
174
177
|
const projectRoot = node_path_1.default.resolve(cwd || process.cwd());
|
|
175
178
|
const requestedPath = args[2];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
// Lexical containment (ADR-4650): validated before existence is checked below.
|
|
180
|
+
// `error()` here does not return/throw (preserved from before this migration),
|
|
181
|
+
// so resolvedTaskPath must still be computed identically on the rejected path.
|
|
182
|
+
const contained = (0, security_cjs_1.tryWithinRootLexical)(requestedPath, projectRoot);
|
|
183
|
+
if (contained === null) {
|
|
179
184
|
error(`Task file is outside project scope: ${requestedPath}`, ERROR_REASON.USAGE);
|
|
180
185
|
}
|
|
186
|
+
const resolvedTaskPath = contained ?? node_path_1.default.resolve(projectRoot, requestedPath);
|
|
181
187
|
if (!node_fs_1.default.existsSync(resolvedTaskPath)) {
|
|
182
188
|
error(`Task file not found: ${requestedPath}`, ERROR_REASON.USAGE);
|
|
183
189
|
}
|