@opengsd/gsd-core 1.17.0 → 1.19.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 +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +3 -3
- package/agents/gsd-code-fixer.md +5 -4
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +460 -105
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +124 -18
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +41 -8
- package/gsd-core/bin/lib/capability-registry.cjs +162 -74
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +20 -11
- package/gsd-core/bin/lib/capability-validator.cjs +23 -4
- package/gsd-core/bin/lib/commands.cjs +5 -1
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +93 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +21 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +19 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +114 -18
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +76 -5
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -7
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +235 -37
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +89 -30
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +428 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +123 -16
- package/gsd-core/bin/lib/smart-entry.cjs +37 -16
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +47 -5
- package/gsd-core/bin/lib/state-transition.cjs +150 -34
- package/gsd-core/bin/lib/state.cjs +140 -88
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +7 -28
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +38 -1
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +14 -3
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +5 -4
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +66 -8
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +64 -157
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +34 -6
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +44 -5
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +11 -11
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +19 -8
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +21 -3
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +7 -3
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +46 -11
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +6 -2
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +7 -3
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +54 -86
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +23 -8
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +5 -1
- package/gsd-core/workflows/review.md +22 -15
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +4 -3
- package/gsd-core/workflows/ship.md +16 -14
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +15 -6
- package/hooks/dist/gsd-agent-isolation-guard.js +14 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -3
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +244 -53
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +14 -2
- package/hooks/gsd-cursor-subagent-start.js +8 -3
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +244 -53
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +19 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +41 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/release-tarball-smoke.cjs +1 -0
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -14,11 +14,14 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
16
|
const ioMod = require("./io.cjs");
|
|
17
|
-
const { output, error, declineNoOp, formatDiagnosticToken } = ioMod;
|
|
17
|
+
const { output, error, declineNoOp, formatDiagnosticToken, ERROR_REASON } = ioMod;
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
19
|
const cliExitModule = require("./cli-exit.cjs");
|
|
20
20
|
const { ExitError } = cliExitModule;
|
|
21
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const capabilityLedgerMod = require("./capability-ledger.cjs");
|
|
23
|
+
const { readSmallRegularFile } = capabilityLedgerMod;
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
25
|
const stateContract = require("./state-contract.cjs");
|
|
23
26
|
const { publishStateContract } = stateContract;
|
|
24
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -316,19 +319,51 @@ function cmdStateGet(cwd, section, raw) {
|
|
|
316
319
|
output({ error: `Section or field "${section}" not found` }, raw, '');
|
|
317
320
|
}
|
|
318
321
|
}
|
|
319
|
-
|
|
322
|
+
/**
|
|
323
|
+
* #4926: cap on a `--<flag>-file` text input. Twice the 128 KiB one argv string
|
|
324
|
+
* can carry on Linux, so the file form never takes less than the inline form,
|
|
325
|
+
* and the entry echoed back on stdout stays small.
|
|
326
|
+
*/
|
|
327
|
+
const TEXT_FILE_MAX_BYTES = 256 * 1024;
|
|
328
|
+
/**
|
|
329
|
+
* Resolve a text argument that may instead be supplied as a file (`--<flag>`
|
|
330
|
+
* or `--<flag>-file`).
|
|
331
|
+
*
|
|
332
|
+
* #4926: the file is a read-once INPUT — its contents are copied into
|
|
333
|
+
* STATE.md and the path itself is never written, recorded, or resolved
|
|
334
|
+
* again — so it is not a project-write resource and is not confined to the
|
|
335
|
+
* project root. A scratch file from an agent's temp directory is accepted.
|
|
336
|
+
* A relative path still resolves against the project root.
|
|
337
|
+
*
|
|
338
|
+
* A file that cannot be read is a caller error and goes through the fault
|
|
339
|
+
* path — stderr, non-zero exit (USAGE), `--json-errors` envelope — rather than
|
|
340
|
+
* an `{ added: false }` payload at exit 0 that a shell caller gating on `$?`
|
|
341
|
+
* reads as success while nothing was written.
|
|
342
|
+
*
|
|
343
|
+
* An unconfined path is hostile input, so the read goes through
|
|
344
|
+
* `readSmallRegularFile`: a FIFO, a device (`/dev/zero`, a piped `/dev/stdin`)
|
|
345
|
+
* or a directory is refused without blocking, and a file over
|
|
346
|
+
* TEXT_FILE_MAX_BYTES is refused without being read.
|
|
347
|
+
*/
|
|
348
|
+
function readTextArgOrFile(cwd, value, filePath, flag) {
|
|
320
349
|
if (!filePath)
|
|
321
350
|
return value;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const { assertWithinRoot, PathAcceptance } = require('./security.cjs');
|
|
325
|
-
const contained = assertWithinRoot(filePath, cwd, `${label} path`, PathAcceptance.AbsoluteInsideRoot);
|
|
351
|
+
const refuse = (why) => error(`--${flag}-file could not be read: ${formatDiagnosticToken(filePath)}${why ? ` (${why})` : ''}`, ERROR_REASON.USAGE);
|
|
352
|
+
let content;
|
|
326
353
|
try {
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
throw new Error(`${label} file not found: ${filePath}`);
|
|
354
|
+
content = readSmallRegularFile(node_path_1.default.resolve(cwd, filePath), TEXT_FILE_MAX_BYTES);
|
|
331
355
|
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
const code = err.code;
|
|
358
|
+
if (code === 'EFBIG')
|
|
359
|
+
return refuse(`larger than ${TEXT_FILE_MAX_BYTES} bytes`);
|
|
360
|
+
if (code === 'EISDIR' || code === 'ENXIO')
|
|
361
|
+
return refuse('not a regular file');
|
|
362
|
+
return refuse(code);
|
|
363
|
+
}
|
|
364
|
+
if (content === null)
|
|
365
|
+
return refuse('ENOENT');
|
|
366
|
+
return content.trimEnd();
|
|
332
367
|
}
|
|
333
368
|
function cmdStatePatch(cwd, patches, raw) {
|
|
334
369
|
// Validate all field names before processing
|
|
@@ -735,7 +770,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
735
770
|
// silently absorbed.
|
|
736
771
|
const body = stripFrontmatter(content);
|
|
737
772
|
const positionScope = matchCurrentPositionSection(body) ?? body;
|
|
738
|
-
const positionPhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase')).phase;
|
|
773
|
+
const positionPhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase'), resolvePhaseIdConvention(cwd)).phase;
|
|
739
774
|
// #4093: a Current Position section with ZERO labeled fields has no
|
|
740
775
|
// `Phase:` line either; frontmatter `current_phase` is the documented
|
|
741
776
|
// survivor of body drift (the reporter's document still carried it, and
|
|
@@ -1224,25 +1259,19 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
1224
1259
|
}
|
|
1225
1260
|
function cmdStateAddDecision(cwd, options, raw) {
|
|
1226
1261
|
const statePath = planningPaths(cwd).state;
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1262
|
+
// #4926: every refusal from this verb goes through the fault path (non-zero
|
|
1263
|
+
// exit), so a caller gating on `$?` never reads success while nothing was written.
|
|
1264
|
+
if (!node_fs_1.default.existsSync(statePath))
|
|
1265
|
+
return error('STATE.md not found', ERROR_REASON.USAGE);
|
|
1231
1266
|
const { phase, summary, summary_file, rationale, rationale_file } = options;
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
if (!summaryText) {
|
|
1243
|
-
output({ error: 'summary required' }, raw, undefined);
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1267
|
+
const summaryText = readTextArgOrFile(cwd, summary, summary_file, 'summary');
|
|
1268
|
+
// The rationale is optional: blank, inline or file, means "no rationale".
|
|
1269
|
+
const rationaleInput = readTextArgOrFile(cwd, rationale || '', rationale_file, 'rationale') || '';
|
|
1270
|
+
const rationaleText = rationaleInput.trim() ? rationaleInput : '';
|
|
1271
|
+
// A blank inline value is refused the same as a blank file (the file form is
|
|
1272
|
+
// trimEnd()ed by readTextArgOrFile, the inline form is not).
|
|
1273
|
+
if (!summaryText || !summaryText.trim())
|
|
1274
|
+
return error('summary required — pass a non-blank --summary or --summary-file', ERROR_REASON.USAGE);
|
|
1246
1275
|
// #3231/#3481: `--phase` omitted → resolve from the STATE.md being written, via
|
|
1247
1276
|
// the canonical ladder `state prune` uses. A decision entry is a permanent
|
|
1248
1277
|
// record, so a literal `[Phase ?]` written while `current_phase` sat three
|
|
@@ -1254,7 +1283,7 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
1254
1283
|
if (!phaseId) {
|
|
1255
1284
|
const rawState = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
1256
1285
|
const fm = extractFrontmatter(rawState, statePath);
|
|
1257
|
-
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState)) ?? undefined;
|
|
1286
|
+
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState), resolvePhaseIdConvention(cwd)) ?? undefined;
|
|
1258
1287
|
}
|
|
1259
1288
|
const entry = `- [Phase ${phaseId || '?'}]: ${summaryText}${rationaleText ? ` — ${rationaleText}` : ''}`;
|
|
1260
1289
|
let _added = false;
|
|
@@ -1310,23 +1339,14 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
1310
1339
|
}
|
|
1311
1340
|
function cmdStateAddBlocker(cwd, text, raw) {
|
|
1312
1341
|
const statePath = planningPaths(cwd).state;
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1342
|
+
// #4926: every refusal from this verb goes through the fault path (non-zero
|
|
1343
|
+
// exit), so a caller gating on `$?` never reads success while nothing was written.
|
|
1344
|
+
if (!node_fs_1.default.existsSync(statePath))
|
|
1345
|
+
return error('STATE.md not found', ERROR_REASON.USAGE);
|
|
1317
1346
|
const blockerOptions = typeof text === 'object' && text !== null ? text : { text: text };
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
}
|
|
1322
|
-
catch (err) {
|
|
1323
|
-
output({ added: false, reason: err.message }, raw, 'false');
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
if (!blockerText) {
|
|
1327
|
-
output({ error: 'text required' }, raw, undefined);
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1347
|
+
const blockerText = readTextArgOrFile(cwd, blockerOptions.text, blockerOptions.text_file, 'text');
|
|
1348
|
+
if (!blockerText || !blockerText.trim())
|
|
1349
|
+
return error('text required — pass a non-blank --text or --text-file', ERROR_REASON.USAGE);
|
|
1330
1350
|
const entry = `- ${blockerText}`;
|
|
1331
1351
|
let _added = false;
|
|
1332
1352
|
let created = false;
|
|
@@ -1379,25 +1399,16 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
1379
1399
|
}
|
|
1380
1400
|
function cmdStateAddRoadmapEvolution(cwd, options, raw) {
|
|
1381
1401
|
const statePath = planningPaths(cwd).state;
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1402
|
+
// #4926: every refusal from this verb goes through the fault path (non-zero
|
|
1403
|
+
// exit), so a caller gating on `$?` never reads success while nothing was written.
|
|
1404
|
+
if (!node_fs_1.default.existsSync(statePath))
|
|
1405
|
+
return error('STATE.md not found', ERROR_REASON.USAGE);
|
|
1386
1406
|
const { phase, action, after, note, note_file, urgent } = options;
|
|
1387
|
-
|
|
1388
|
-
try {
|
|
1389
|
-
noteText = readTextArgOrFile(cwd, note, note_file, 'note');
|
|
1390
|
-
}
|
|
1391
|
-
catch (err) {
|
|
1392
|
-
output({ added: false, reason: err.message }, raw, 'false');
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1407
|
+
const noteText = readTextArgOrFile(cwd, note, note_file, 'note');
|
|
1395
1408
|
// Reject missing / empty / whitespace-only notes — an evolution entry with no
|
|
1396
1409
|
// narrative is meaningless and would corrupt the section with a dangling bullet.
|
|
1397
|
-
if (!noteText || !noteText.trim())
|
|
1398
|
-
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1410
|
+
if (!noteText || !noteText.trim())
|
|
1411
|
+
return error('note required — pass a non-blank --note or --note-file', ERROR_REASON.USAGE);
|
|
1401
1412
|
// Flatten line breaks so the entry is always a single Markdown bullet. The
|
|
1402
1413
|
// dedupe + rendering contract is line-oriented; a multiline --note-file would
|
|
1403
1414
|
// otherwise spill continuation lines outside the bullet and defeat dedupe.
|
|
@@ -1417,7 +1428,7 @@ function cmdStateAddRoadmapEvolution(cwd, options, raw) {
|
|
|
1417
1428
|
if (!phaseId) {
|
|
1418
1429
|
const rawState = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
1419
1430
|
const fm = extractFrontmatter(rawState, statePath);
|
|
1420
|
-
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState)) ?? undefined;
|
|
1431
|
+
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState), resolvePhaseIdConvention(cwd)) ?? undefined;
|
|
1421
1432
|
}
|
|
1422
1433
|
const entry = `- Phase ${phaseId || '?'} ${actionText}${afterText}: ${flatNote}${urgentText}`;
|
|
1423
1434
|
let duplicate = false;
|
|
@@ -1984,26 +1995,27 @@ function preferNewerLastActivity(existingFm, derivedFm) {
|
|
|
1984
1995
|
derivedFm['last_activity'] = exRaw;
|
|
1985
1996
|
}
|
|
1986
1997
|
}
|
|
1987
|
-
function parseProsePhaseField(value) {
|
|
1998
|
+
function parseProsePhaseField(value, convention) {
|
|
1988
1999
|
// #2121 Phase 2 (#2125): delegate to the canonical anchored parser so this
|
|
1989
2000
|
// module holds no independent prose phase-id regex. Drives #2111 — the
|
|
1990
2001
|
// anchored parser returns { phase: null } for a "Milestone vX.Y complete"
|
|
1991
2002
|
// body line (the old unanchored regex mined the minor-version digit, e.g.
|
|
1992
2003
|
// v0.5 -> "5"), so syncStateFrontmatter's #905 guard preserves the real
|
|
1993
2004
|
// current_phase instead of clobbering it.
|
|
1994
|
-
|
|
2005
|
+
// #5626: under `milestone-prefixed` the id is the whole `M-NN`; the convention comes from its owner.
|
|
2006
|
+
return parsePhaseFromProse(value, convention);
|
|
1995
2007
|
}
|
|
1996
|
-
function resolveStatePhase(fm, body) {
|
|
2008
|
+
function resolveStatePhase(fm, body, convention) {
|
|
1997
2009
|
const currentPositionScope = matchCurrentPositionSection(body) ?? body;
|
|
1998
2010
|
const frontmatterRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase', null).value;
|
|
1999
2011
|
const legacyRaw = (0, state_document_cjs_1.stateFieldValue)(fm, currentPositionScope, null, 'Current Phase').value;
|
|
2000
2012
|
const currentPositionRaw = (0, state_document_cjs_1.stateFieldValue)(fm, currentPositionScope, null, 'Phase').value;
|
|
2001
2013
|
const sources = {
|
|
2002
|
-
frontmatter: parseProsePhaseField(frontmatterRaw).phase,
|
|
2003
|
-
legacy_current_phase: parseProsePhaseField(legacyRaw).phase,
|
|
2004
|
-
current_position_phase: parseProsePhaseField(currentPositionRaw).phase,
|
|
2014
|
+
frontmatter: parseProsePhaseField(frontmatterRaw, convention).phase,
|
|
2015
|
+
legacy_current_phase: parseProsePhaseField(legacyRaw, convention).phase,
|
|
2016
|
+
current_position_phase: parseProsePhaseField(currentPositionRaw, convention).phase,
|
|
2005
2017
|
};
|
|
2006
|
-
const prosePhase = parseProsePhaseField(currentPositionRaw);
|
|
2018
|
+
const prosePhase = parseProsePhaseField(currentPositionRaw, convention);
|
|
2007
2019
|
return {
|
|
2008
2020
|
phase: sources.frontmatter ?? sources.legacy_current_phase ?? sources.current_position_phase,
|
|
2009
2021
|
name: (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase_name', null).value
|
|
@@ -2058,9 +2070,9 @@ function resolveStatePhase(fm, body) {
|
|
|
2058
2070
|
* Reconcile the two only by giving `resolveStatePhase` an explicit scope
|
|
2059
2071
|
* parameter — never by pointing this at it and dropping the difference.
|
|
2060
2072
|
*/
|
|
2061
|
-
function resolveCurrentPhaseId(fm, body) {
|
|
2073
|
+
function resolveCurrentPhaseId(fm, body, convention) {
|
|
2062
2074
|
const positionSection = sliceCurrentPositionSection(body);
|
|
2063
|
-
const prosePhase = positionSection !== null ? parseProsePhaseField((0, state_document_cjs_1.stateFieldValue)(fm, positionSection, null, 'Phase').value).phase : null;
|
|
2075
|
+
const prosePhase = positionSection !== null ? parseProsePhaseField((0, state_document_cjs_1.stateFieldValue)(fm, positionSection, null, 'Phase').value, convention).phase : null;
|
|
2064
2076
|
return (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase', 'Current Phase').value ?? prosePhase;
|
|
2065
2077
|
}
|
|
2066
2078
|
function parseProseLastActivityField(value) {
|
|
@@ -2098,7 +2110,7 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
2098
2110
|
// so it is scopeable exactly like Stopped At under ## Session. Fall back to
|
|
2099
2111
|
// full-body search only when no ## Current Position section exists, so files
|
|
2100
2112
|
// with no section heading keep their current behaviour.
|
|
2101
|
-
const resolvedPhase = resolveStatePhase(fm, body);
|
|
2113
|
+
const resolvedPhase = resolveStatePhase(fm, body, resolvePhaseIdConvention(cwd));
|
|
2102
2114
|
const currentPhase = resolvedPhase.phase;
|
|
2103
2115
|
const currentPhaseName = resolvedPhase.name;
|
|
2104
2116
|
const totalPhasesRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'total_phases', 'Total Phases').value;
|
|
@@ -2520,7 +2532,7 @@ milestoneExplicitlyNull) {
|
|
|
2520
2532
|
// section overwrites current_phase here, and the next read surfaces it. Fall
|
|
2521
2533
|
// back to full-body search when no ## Current Position section exists.
|
|
2522
2534
|
const currentPositionScope = matchCurrentPositionSection(bodyContent) ?? bodyContent;
|
|
2523
|
-
const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(currentPositionScope, 'Phase'));
|
|
2535
|
+
const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(currentPositionScope, 'Phase'), cwd ? resolvePhaseIdConvention(cwd) : null);
|
|
2524
2536
|
const currentPhase = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase') ?? prosePhase.phase;
|
|
2525
2537
|
const currentPhaseName = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase Name') ?? prosePhase.name;
|
|
2526
2538
|
const currentPlan = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Plan');
|
|
@@ -2788,8 +2800,14 @@ milestoneExplicitlyNull) {
|
|
|
2788
2800
|
// — no second ROADMAP parser here. A ROADMAP without a canonical
|
|
2789
2801
|
// `## Progress` table resolves no table → floor inert (disk count
|
|
2790
2802
|
// stands), the owner's own answer to "what is countable".
|
|
2803
|
+
// #5165: the milestone-grouped `## Progress` table keeps every
|
|
2804
|
+
// milestone's rows and sits OUTSIDE any narrower roadmapScope, so the
|
|
2805
|
+
// floor names the milestone STATE asserts (assertedMilestoneVersion —
|
|
2806
|
+
// present on every resync path, unlike the optional storedMilestone
|
|
2807
|
+
// parameter) and the owner counts only that milestone's rows. No row
|
|
2808
|
+
// for it => nulls => the floor is inert and the disk count stands.
|
|
2791
2809
|
const roadmapCompletedPhases = roadmapScope !== null && safeToUseRoadmapCount && !diskCountsWithheld
|
|
2792
|
-
? deriveProgressFromRoadmap(roadmapScope).completedPhases
|
|
2810
|
+
? deriveProgressFromRoadmap(roadmapScope, assertedMilestoneVersion ?? storedMilestone).completedPhases
|
|
2793
2811
|
: null;
|
|
2794
2812
|
const flooredCompletedPhases = roadmapCompletedPhases !== null
|
|
2795
2813
|
? Math.max(diskCompletedPhases, roadmapCompletedPhases)
|
|
@@ -3216,6 +3234,8 @@ function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
|
|
|
3216
3234
|
for (const [subkey, value] of Object.entries(intent)) {
|
|
3217
3235
|
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
3218
3236
|
continue;
|
|
3237
|
+
if (subkey === 'percent')
|
|
3238
|
+
continue; // composed below, never applied raw (#5165)
|
|
3219
3239
|
if (!getFieldClassification(`progress.${subkey}`))
|
|
3220
3240
|
continue;
|
|
3221
3241
|
const isCompletedCounter = subkey === 'completed_phases' || subkey === 'completed_plans';
|
|
@@ -3233,10 +3253,18 @@ function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
|
|
|
3233
3253
|
}
|
|
3234
3254
|
// percent: applied only when a completed counter moved (coherent with the
|
|
3235
3255
|
// counters that just landed) or when no percent exists to contradict.
|
|
3256
|
+
// #5165 / ADR-4629 §8.5: `computeProgressPercent` is the one composition
|
|
3257
|
+
// owner (min(plan fraction, phase fraction)) that every resyncing write uses.
|
|
3258
|
+
// When the block carries the counters it needs, the percent is composed by
|
|
3259
|
+
// that owner over the counters just landed, so a later resyncing write
|
|
3260
|
+
// cannot move what this transition wrote. The intent's own percent (a
|
|
3261
|
+
// phase-only fraction) is only the fallback for a block without plan counts.
|
|
3236
3262
|
const intentPercent = intent['percent'];
|
|
3237
3263
|
if (typeof intentPercent === 'number' && Number.isFinite(intentPercent) && (completedMoved || (0, state_document_cjs_1.toFiniteNumber)(base['percent']) === null)) {
|
|
3238
|
-
|
|
3239
|
-
|
|
3264
|
+
const composed = (0, state_document_cjs_1.computeProgressPercent)((0, state_document_cjs_1.toFiniteNumber)(base['completed_plans']), (0, state_document_cjs_1.toFiniteNumber)(base['total_plans']), (0, state_document_cjs_1.toFiniteNumber)(base['completed_phases']), (0, state_document_cjs_1.toFiniteNumber)(base['total_phases']), SCOPE.COMPLETE);
|
|
3265
|
+
const nextPercent = composed !== null ? composed : intentPercent;
|
|
3266
|
+
if (!Object.is(base['percent'], nextPercent)) {
|
|
3267
|
+
base['percent'] = nextPercent;
|
|
3240
3268
|
changed = true;
|
|
3241
3269
|
}
|
|
3242
3270
|
}
|
|
@@ -3820,7 +3848,10 @@ function assertStatePreservationOptions(options, caller) {
|
|
|
3820
3848
|
throw err;
|
|
3821
3849
|
}
|
|
3822
3850
|
}
|
|
3823
|
-
function applyPostSyncPreservation(originalContent, transformedContent, syncedContent, statePath, options
|
|
3851
|
+
function applyPostSyncPreservation(originalContent, transformedContent, syncedContent, statePath, options,
|
|
3852
|
+
// #5626: the project's phase-id convention, resolved by the caller from its cwd (null: unset). Trailing and
|
|
3853
|
+
// optional so `options` stays the fifth parameter the options guard and its callers rely on.
|
|
3854
|
+
phaseIdConvention = null) {
|
|
3824
3855
|
assertStatePreservationOptions(options, 'applyPostSyncPreservation');
|
|
3825
3856
|
const { resync, authoritativeFm, deriveProgressKeys, divergedFields, explicitProgressField, preWriteState } = options;
|
|
3826
3857
|
// Bug #1230: delta heuristic — snapshot pre-transform body source fields so
|
|
@@ -3877,7 +3908,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
3877
3908
|
const preCurrentPositionScope = matchCurrentPositionSection(preBody) ?? preBody;
|
|
3878
3909
|
const preBodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(preBody, 'Current Plan');
|
|
3879
3910
|
const preBodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(preBody, 'Current Phase')
|
|
3880
|
-
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(preCurrentPositionScope, 'Phase')).phase;
|
|
3911
|
+
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(preCurrentPositionScope, 'Phase'), phaseIdConvention).phase;
|
|
3881
3912
|
const preBodyPausedAt = (0, state_document_cjs_1.stateExtractField)(preSessionScope, 'Paused At');
|
|
3882
3913
|
const preBodyLastActivityRaw = (0, state_document_cjs_1.stateExtractField)(preBody, 'Last Activity')
|
|
3883
3914
|
?? (0, state_document_cjs_1.stateExtractField)(preBody, 'Last activity');
|
|
@@ -3903,7 +3934,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
3903
3934
|
const postCurrentPositionScope = matchCurrentPositionSection(postBody) ?? postBody;
|
|
3904
3935
|
const postBodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(postBody, 'Current Plan');
|
|
3905
3936
|
const postBodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(postBody, 'Current Phase')
|
|
3906
|
-
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(postCurrentPositionScope, 'Phase')).phase;
|
|
3937
|
+
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(postCurrentPositionScope, 'Phase'), phaseIdConvention).phase;
|
|
3907
3938
|
const postBodyPausedAt = (0, state_document_cjs_1.stateExtractField)(postSessionScope, 'Paused At');
|
|
3908
3939
|
const postBodyLastActivityRaw = (0, state_document_cjs_1.stateExtractField)(postBody, 'Last Activity')
|
|
3909
3940
|
?? (0, state_document_cjs_1.stateExtractField)(postBody, 'Last activity');
|
|
@@ -4120,7 +4151,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
4120
4151
|
function syncAndPreserveStateMd(originalContent, transformedContent, statePath, cwd, options) {
|
|
4121
4152
|
assertStatePreservationOptions(options, 'syncAndPreserveStateMd');
|
|
4122
4153
|
const synced = syncStateFrontmatter(transformedContent, cwd, options.authoritativeFm);
|
|
4123
|
-
return applyPostSyncPreservation(originalContent, transformedContent, synced, statePath, options);
|
|
4154
|
+
return applyPostSyncPreservation(originalContent, transformedContent, synced, statePath, options, cwd ? resolvePhaseIdConvention(cwd) : null);
|
|
4124
4155
|
}
|
|
4125
4156
|
/**
|
|
4126
4157
|
* Atomic read-modify-write for STATE.md.
|
|
@@ -4778,8 +4809,9 @@ function stateSectionScopeUnits(units, sectionTargets) {
|
|
|
4778
4809
|
* matching `readStateTarget`'s long-standing special case.
|
|
4779
4810
|
*/
|
|
4780
4811
|
function verifyStateWriteIntent(intent, preContent, postContent, statePath, bodyDeltas) {
|
|
4781
|
-
|
|
4782
|
-
const
|
|
4812
|
+
// #5165: the write lands scoped to what it changed (platformWriteSync reads the file it replaces).
|
|
4813
|
+
const pre = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, preContent, { prior: preContent }).content;
|
|
4814
|
+
const post = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, postContent, { prior: preContent }).content;
|
|
4783
4815
|
const preFm = extractFrontmatter(pre, statePath);
|
|
4784
4816
|
const postFm = extractFrontmatter(post, statePath);
|
|
4785
4817
|
const preBody = stripFrontmatter(pre);
|
|
@@ -4978,7 +5010,7 @@ function applyStateWriteIntent(statePath, intent, transformFn, cwd, options = {}
|
|
|
4978
5010
|
return unwritten(verification);
|
|
4979
5011
|
(0, shell_command_projection_cjs_1.platformWriteSync)(statePath, candidate);
|
|
4980
5012
|
const reread = (0, shell_command_projection_cjs_1.platformReadSync)(statePath) || '';
|
|
4981
|
-
if (reread !== (0, shell_command_projection_cjs_1.normalizeContent)(statePath, candidate).content) {
|
|
5013
|
+
if (reread !== (0, shell_command_projection_cjs_1.normalizeContent)(statePath, candidate, { prior: pre }).content) {
|
|
4982
5014
|
return Object.freeze({
|
|
4983
5015
|
ok: false,
|
|
4984
5016
|
reasons: Object.freeze([STATE_WRITE_INTENT_FAILURE.REREAD_MISMATCH]),
|
|
@@ -5044,7 +5076,7 @@ function cmdStateJson(cwd, raw) {
|
|
|
5044
5076
|
const bodyPausedAt = (0, state_document_cjs_1.stateExtractField)(sessionScope, 'Paused At');
|
|
5045
5077
|
const bodyPhaseSource = (0, state_document_cjs_1.stateExtractField)(body, 'Phase');
|
|
5046
5078
|
const bodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(body, 'Current Phase')
|
|
5047
|
-
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase')).phase;
|
|
5079
|
+
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase'), resolvePhaseIdConvention(cwd)).phase;
|
|
5048
5080
|
const bodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(body, 'Current Plan');
|
|
5049
5081
|
const bodyStatus = (0, state_document_cjs_1.stateExtractField)(body, 'Status');
|
|
5050
5082
|
// #3836: mirrors applyPostSyncPreservation's own derivation (state.cts
|
|
@@ -5097,6 +5129,23 @@ function cmdStateJson(cwd, raw) {
|
|
|
5097
5129
|
preferNewerLastActivity(existingFm, built);
|
|
5098
5130
|
output(built, raw, JSON.stringify(built, null, 2));
|
|
5099
5131
|
}
|
|
5132
|
+
/**
|
|
5133
|
+
* 1-based position of the first plan with no summary, when SOME of the phase's plans already have
|
|
5134
|
+
* one (a gap-closure re-entry: plans 1..17 are done and 18 and 19 are the gap plans, #4945).
|
|
5135
|
+
* `null` when no plan is summarized yet, when every plan is, or when the phase directory cannot be
|
|
5136
|
+
* found: the phase then starts at plan 1, exactly as before. Plans and summaries are paired by the
|
|
5137
|
+
* phase locator's own rule, so this agrees with `init` and `phase-plan-index`.
|
|
5138
|
+
*/
|
|
5139
|
+
function firstPlanToRun(cwd, phaseNumber) {
|
|
5140
|
+
const located = phaseLocatorMod.findPhaseInternal(cwd, String(phaseNumber));
|
|
5141
|
+
if (!located || !located.found)
|
|
5142
|
+
return null;
|
|
5143
|
+
const { plans, incomplete_plans: incomplete } = located;
|
|
5144
|
+
if (incomplete.length === 0 || incomplete.length === plans.length)
|
|
5145
|
+
return null;
|
|
5146
|
+
const index = plans.indexOf(incomplete[0]);
|
|
5147
|
+
return index === -1 ? null : index + 1;
|
|
5148
|
+
}
|
|
5100
5149
|
/**
|
|
5101
5150
|
* Update STATE.md when a new phase begins execution.
|
|
5102
5151
|
* Updates body text fields (Current focus, Status, Last Activity, Current Position)
|
|
@@ -5144,6 +5193,8 @@ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
|
5144
5193
|
phaseNumber: phaseNumber,
|
|
5145
5194
|
phaseName: phaseName ?? null,
|
|
5146
5195
|
planCount: planCount ?? null,
|
|
5196
|
+
// #4945: a gap-closure re-entry starts at the first plan still to run, not at plan 1.
|
|
5197
|
+
startPlan: firstPlanToRun(cwd, phaseNumber),
|
|
5147
5198
|
};
|
|
5148
5199
|
const deps = {
|
|
5149
5200
|
clock: clock_cjs_1.realClock,
|
|
@@ -5497,7 +5548,8 @@ function cmdStatePlannedPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
|
5497
5548
|
if (!intent.phaseName) {
|
|
5498
5549
|
const preFm = extractFrontmatter(content, statePath);
|
|
5499
5550
|
const curatedName = preFm['current_phase_name'];
|
|
5500
|
-
|
|
5551
|
+
// #5369: a status fragment a pre-fix write left as the name is not curated; do not reassert it.
|
|
5552
|
+
if (typeof curatedName === 'string' && curatedName.trim().length > 0 && !phaseIdMod.isPlannedPhaseStatusPhrase(curatedName)) {
|
|
5501
5553
|
rmwOptions.authoritativeFm = { current_phase_name: curatedName };
|
|
5502
5554
|
}
|
|
5503
5555
|
}
|
|
@@ -5726,7 +5778,7 @@ function cmdStateValidate(cwd, raw, opts = {}) {
|
|
|
5726
5778
|
const { fm, body, scope: initialScope } = readStateFrontmatterScoped(content, statePath);
|
|
5727
5779
|
const scope = initialScope;
|
|
5728
5780
|
const status = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'status', 'Status').value || '';
|
|
5729
|
-
const resolvedPhase = resolveStatePhase(fm, body);
|
|
5781
|
+
const resolvedPhase = resolveStatePhase(fm, body, resolvePhaseIdConvention(cwd));
|
|
5730
5782
|
const currentPhase = resolvedPhase.phase;
|
|
5731
5783
|
const totalPlansRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'total_plans_in_phase', 'Total Plans in Phase').value;
|
|
5732
5784
|
const totalPlansInPhase = totalPlansRaw ? parseInt(totalPlansRaw, 10) : null;
|
|
@@ -6180,7 +6232,7 @@ function cmdStatePrune(cwd, options, raw) {
|
|
|
6180
6232
|
const body = stripFrontmatter(rawState);
|
|
6181
6233
|
// Prune needs an integer cutoff, so it parses the resolved id itself; a
|
|
6182
6234
|
// non-numeric or absent id lands on 0 and prune bails, as before.
|
|
6183
|
-
const currentPhase = parseInt(String(resolveCurrentPhaseId(fm, body)), 10) || 0;
|
|
6235
|
+
const currentPhase = parseInt(String(resolveCurrentPhaseId(fm, body, resolvePhaseIdConvention(cwd))), 10) || 0;
|
|
6184
6236
|
const cutoff = currentPhase - keepRecent;
|
|
6185
6237
|
if (cutoff <= 0) {
|
|
6186
6238
|
emit({ pruned: false, reason: `Only ${currentPhase} phases — nothing to prune with --keep-recent ${keepRecent}` }, raw, 'false');
|
|
@@ -36,6 +36,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
36
36
|
const node_path_1 = __importDefault(require("node:path"));
|
|
37
37
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
38
38
|
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
39
|
+
const args_terminator_cjs_1 = require("./args-terminator.cjs");
|
|
39
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
41
|
const installProfiles = require("./install-profiles.cjs");
|
|
41
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -322,6 +323,14 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
322
323
|
if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
|
|
323
324
|
throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
|
|
324
325
|
}
|
|
326
|
+
// #5637: re-staged commands and skills keep the typed-text terminator nonce the install wrote,
|
|
327
|
+
// when it wrote one; this process is not an installer, so nothing armed it.
|
|
328
|
+
// A directory with no nonce (an install from before this change) is re-staged canonical, and a
|
|
329
|
+
// nonce armed earlier in this process for another directory is not carried over.
|
|
330
|
+
if ((0, args_terminator_cjs_1.readInstalledNonce)(layout.configDir) !== null)
|
|
331
|
+
(0, args_terminator_cjs_1.armArgsNonce)(layout.configDir, true);
|
|
332
|
+
else
|
|
333
|
+
(0, args_terminator_cjs_1.disarmArgsNonce)();
|
|
325
334
|
// #3712: the dest selection below prefers `kind.home` over layout.configDir and
|
|
326
335
|
// then hands it to the destructive _syncGsdDir, so surface apply is a third
|
|
327
336
|
// escape route into the developer's real home alongside install/uninstall.
|
|
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const security_cjs_1 = require("./security.cjs");
|
|
15
|
+
const tdd_test_files_cjs_1 = require("./tdd-test-files.cjs");
|
|
15
16
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
17
|
const ioMod = require("./io.cjs");
|
|
17
18
|
const { output, error, ERROR_REASON } = ioMod;
|
|
@@ -19,10 +20,8 @@ const { output, error, ERROR_REASON } = ioMod;
|
|
|
19
20
|
const planDocumentMod = require("./plan-document.cjs");
|
|
20
21
|
const { parsePlanDocument } = planDocumentMod;
|
|
21
22
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
-
const capabilityLoaderMod = require("./capability-loader.cjs");
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
23
|
const taskContentResolutionMod = require("./task-content-resolution.cjs");
|
|
25
|
-
const { resolveTaskContent,
|
|
24
|
+
const { resolveTaskContent, loadResolverCapabilities, isResolverFailure, } = taskContentResolutionMod;
|
|
26
25
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
27
26
|
function isBehaviorAddingTaskContent(content) {
|
|
28
27
|
const tddTrue = /\btdd\s*=\s*["']true["']/i.test(content);
|
|
@@ -37,9 +36,7 @@ function isBehaviorAddingTaskContent(content) {
|
|
|
37
36
|
.filter(Boolean);
|
|
38
37
|
hasSourceFiles = fileLines.some((file) => !/\.md$/i.test(file) &&
|
|
39
38
|
!/\.json$/i.test(file) &&
|
|
40
|
-
|
|
41
|
-
!/\.spec\.[^.]+$/i.test(file) &&
|
|
42
|
-
!/(^|[\\/])tests?[\\/]/i.test(file) &&
|
|
39
|
+
!(0, tdd_test_files_cjs_1.isTestFilePath)(file) &&
|
|
43
40
|
!/\.(yml|yaml|toml|ini|cfg|conf|properties)$/i.test(file) &&
|
|
44
41
|
!/(^|[\\/])\.env(\..+)?$/i.test(file));
|
|
45
42
|
}
|
|
@@ -61,19 +58,6 @@ function isBehaviorAddingTaskContent(content) {
|
|
|
61
58
|
reason: isBehaviorAdding ? null : `Not behavior-adding: ${missing.join('; ')}`,
|
|
62
59
|
};
|
|
63
60
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Default (production) capability loader for `resolve-content`: the merged
|
|
66
|
-
* first-party + validated-installed-overlay registry (ADR-1244 D2), the
|
|
67
|
-
* established runtime read path for "installed capabilities including
|
|
68
|
-
* third-party" — as opposed to `capability-loader.cts`'s heavier build-time
|
|
69
|
-
* validation entry points or the static `capability-registry.cjs` alone
|
|
70
|
-
* (first-party only, would miss a third-party capability's
|
|
71
|
-
* `taskContentResolver` declaration entirely).
|
|
72
|
-
*/
|
|
73
|
-
function defaultLoadCapabilities(cwd) {
|
|
74
|
-
const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
|
|
75
|
-
return Object.values(registry.capabilities ?? {});
|
|
76
|
-
}
|
|
77
61
|
function parseResolveContentArgs(args) {
|
|
78
62
|
let plan = null;
|
|
79
63
|
let taskId = null;
|
|
@@ -94,12 +78,10 @@ function parseResolveContentArgs(args) {
|
|
|
94
78
|
* (ADR-3646 Decision 2). Resolves one task's content from the external
|
|
95
79
|
* tracker its `tracker-id` attribute names, via `task-content-resolution.cts`.
|
|
96
80
|
*
|
|
97
|
-
* HARD-HALT CONTRACT: a thrown
|
|
98
|
-
* / `ResolverTimeoutError` / `ResolverMalformedOutputError` from
|
|
81
|
+
* HARD-HALT CONTRACT: a thrown resolver failure (`isResolverFailure`) from
|
|
99
82
|
* `resolveTaskContent` is turned into this CLI's own non-zero exit via
|
|
100
83
|
* `error()` — never swallowed into a `{resolved: false}` JSON answer. Any
|
|
101
|
-
* other thrown error is not
|
|
102
|
-
* classes and is allowed to propagate uncaught.
|
|
84
|
+
* other thrown error is not a documented resolver-error class and is allowed to propagate uncaught.
|
|
103
85
|
*/
|
|
104
86
|
function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
105
87
|
const usage = 'Usage: task resolve-content --plan <path> --task-id <tracker-id> --raw';
|
|
@@ -128,7 +110,7 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
|
128
110
|
error(`No task with tracker-id '${taskId}' found in plan: ${plan}`, ERROR_REASON.USAGE);
|
|
129
111
|
return;
|
|
130
112
|
}
|
|
131
|
-
const loadCapabilities = deps.loadCapabilities ??
|
|
113
|
+
const loadCapabilities = deps.loadCapabilities ?? loadResolverCapabilities;
|
|
132
114
|
const capabilities = loadCapabilities(projectRoot);
|
|
133
115
|
const resolveFn = deps.resolveTaskContentFn ?? resolveTaskContent;
|
|
134
116
|
let result;
|
|
@@ -136,10 +118,7 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
|
136
118
|
result = resolveFn({ trackerId: task.trackerId, capabilities });
|
|
137
119
|
}
|
|
138
120
|
catch (err) {
|
|
139
|
-
if (err
|
|
140
|
-
err instanceof ResolverFailedError ||
|
|
141
|
-
err instanceof ResolverTimeoutError ||
|
|
142
|
-
err instanceof ResolverMalformedOutputError) {
|
|
121
|
+
if (isResolverFailure(err)) {
|
|
143
122
|
error(err.message, ERROR_REASON.UNKNOWN);
|
|
144
123
|
return;
|
|
145
124
|
}
|
|
@@ -43,6 +43,8 @@ const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
|
43
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
44
|
const ioMod = require("./io.cjs");
|
|
45
45
|
const { formatDiagnosticToken } = ioMod;
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
const capabilityLoaderMod = require("./capability-loader.cjs");
|
|
46
48
|
// ─── Result taxonomy ──────────────────────────────────────────────────────────
|
|
47
49
|
/**
|
|
48
50
|
* The four non-throwing outcomes of `resolveTaskContent`. Frozen because the
|
|
@@ -102,6 +104,17 @@ class ResolverFailedError extends Error {
|
|
|
102
104
|
this.stderrTail = stderrTail;
|
|
103
105
|
}
|
|
104
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The tracker-id's id segment cannot safely be one argv element: a leading `-`
|
|
109
|
+
* reads as an option to the resolver binary, and control characters are never
|
|
110
|
+
* legitimate. Thrown before any spawn.
|
|
111
|
+
*/
|
|
112
|
+
class ResolverUnsafeIdError extends Error {
|
|
113
|
+
constructor() {
|
|
114
|
+
super('tracker-id rejected: id must not start with "-" or contain control characters');
|
|
115
|
+
this.name = 'ResolverUnsafeIdError';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
105
118
|
/** The resolver subprocess exceeded its declared `invoke.timeoutMs` bound. */
|
|
106
119
|
class ResolverTimeoutError extends Error {
|
|
107
120
|
timeoutMs;
|
|
@@ -312,6 +325,9 @@ function resolveTaskContent(input) {
|
|
|
312
325
|
const split = splitTrackerId(input.trackerId);
|
|
313
326
|
if (split === null)
|
|
314
327
|
return { kind: TASK_CONTENT_RESULT.NOT_APPLICABLE };
|
|
328
|
+
if (split.id.trimStart().startsWith('-') || /[\u0000-\u001f\u007f]/.test(input.trackerId ?? '')) {
|
|
329
|
+
throw new ResolverUnsafeIdError();
|
|
330
|
+
}
|
|
315
331
|
const resolver = findResolver(split.prefix, input.capabilities ?? []);
|
|
316
332
|
if (resolver === null)
|
|
317
333
|
return { kind: TASK_CONTENT_RESULT.NO_RESOLVER };
|
|
@@ -327,7 +343,7 @@ function resolveTaskContent(input) {
|
|
|
327
343
|
throw new ResolverAmbiguousError(split.prefix, ids);
|
|
328
344
|
}
|
|
329
345
|
const invocation = buildInvocation(resolver, split.id);
|
|
330
|
-
const timeoutMs = input.timeoutOverrideMs ?? invocation.timeoutMs;
|
|
346
|
+
const timeoutMs = Math.floor(Math.min(input.timeoutOverrideMs ?? invocation.timeoutMs, invocation.timeoutMs));
|
|
331
347
|
const execFn = input.execFn ?? realExec;
|
|
332
348
|
const result = execFn(invocation.binary, invocation.args, { timeout: timeoutMs });
|
|
333
349
|
if (isExecTimeout(result)) {
|
|
@@ -354,12 +370,41 @@ function resolveTaskContent(input) {
|
|
|
354
370
|
}
|
|
355
371
|
return { kind: TASK_CONTENT_RESULT.RESOLVED, content };
|
|
356
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Production capability loader for tracker-id resolution: the merged
|
|
375
|
+
* first-party + validated-installed-overlay registry (ADR-1244 D2), the
|
|
376
|
+
* established runtime read path for "installed capabilities including
|
|
377
|
+
* third-party" — as opposed to `capability-loader.cts`'s heavier build-time
|
|
378
|
+
* validation entry points or the static `capability-registry.cjs` alone
|
|
379
|
+
* (first-party only, would miss a third-party capability's
|
|
380
|
+
* `taskContentResolver` declaration entirely).
|
|
381
|
+
*/
|
|
382
|
+
function loadResolverCapabilities(cwd) {
|
|
383
|
+
const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
|
|
384
|
+
return Object.values(registry.capabilities ?? {});
|
|
385
|
+
}
|
|
386
|
+
/** True for every error `resolveTaskContent` throws as a hard halt (the one classifier all consumers share). */
|
|
387
|
+
function isResolverFailure(err) {
|
|
388
|
+
return (err instanceof ResolverAmbiguousError ||
|
|
389
|
+
err instanceof ResolverFailedError ||
|
|
390
|
+
err instanceof ResolverTimeoutError ||
|
|
391
|
+
err instanceof ResolverMalformedOutputError ||
|
|
392
|
+
err instanceof ResolverUnsafeIdError);
|
|
393
|
+
}
|
|
394
|
+
/** Every text field of resolved content, so a consumer scanning for text cannot miss one when the shape grows. */
|
|
395
|
+
function contentTexts(c) {
|
|
396
|
+
return [c.action, c.verify, ...c.acceptanceCriteria, ...c.readFirst, c.done].filter((v) => v !== null);
|
|
397
|
+
}
|
|
357
398
|
const taskContentResolution = {
|
|
358
399
|
TASK_CONTENT_RESULT,
|
|
359
400
|
splitTrackerId,
|
|
360
401
|
findResolver,
|
|
361
402
|
buildInvocation,
|
|
362
403
|
resolveTaskContent,
|
|
404
|
+
loadResolverCapabilities,
|
|
405
|
+
isResolverFailure,
|
|
406
|
+
contentTexts,
|
|
407
|
+
ResolverUnsafeIdError,
|
|
363
408
|
ResolverAmbiguousError,
|
|
364
409
|
ResolverFailedError,
|
|
365
410
|
ResolverTimeoutError,
|