@opengsd/gsd-core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- 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 +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* readSurface(runtimeConfigDir)
|
|
14
14
|
* writeSurface(runtimeConfigDir, surfaceState)
|
|
15
15
|
* resolveSurface(runtimeConfigDir, manifest, clusterMap?, registry?)
|
|
16
|
-
* applySurface(runtimeConfigDir, layout, manifest, clusterMap?, registry?)
|
|
16
|
+
* applySurface(runtimeConfigDir, layout, manifest, clusterMap?, registry?, opts?, deps?)
|
|
17
17
|
* listSurface(runtimeConfigDir, manifest, clusterMap?, registry?)
|
|
18
18
|
* pruneSkillDirs(skillsDir, retainedNames, prefix, manifest)
|
|
19
19
|
*
|
|
@@ -37,6 +37,8 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
37
37
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
38
38
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
39
39
|
const installProfiles = require("./install-profiles.cjs");
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
|
+
const testHomeGuard = require("./real-home-guard.cjs");
|
|
40
42
|
const { readActiveProfile, resolveProfile, loadSkillsManifest,
|
|
41
43
|
// #2322 HIGH-3: shared marker name — single source of truth with the writer
|
|
42
44
|
// (install-profiles.cts stageSkillsForRuntimeAsSkills) so the prune reader
|
|
@@ -163,7 +165,7 @@ function normalizeSkillManifest(runtimeConfigDir, manifest) {
|
|
|
163
165
|
* - registry is threaded into resolveProfile so capability skills
|
|
164
166
|
* participate in the base skill set and their requires: chains expand.
|
|
165
167
|
*/
|
|
166
|
-
function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
|
|
168
|
+
function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry, surfaceOverride) {
|
|
167
169
|
// Merge capability clusters into the cluster map when registry is provided.
|
|
168
170
|
// The ADR-857 phase 4a HARD gate guarantees that when a capId matches a CLUSTERS
|
|
169
171
|
// key, the values are EQUAL — so the spread is idempotent for matching names.
|
|
@@ -203,7 +205,7 @@ function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
|
|
|
203
205
|
cm = merged;
|
|
204
206
|
}
|
|
205
207
|
const skillManifest = normalizeSkillManifest(runtimeConfigDir, manifest);
|
|
206
|
-
const surface = readSurface(runtimeConfigDir);
|
|
208
|
+
const surface = surfaceOverride === undefined ? readSurface(runtimeConfigDir) : surfaceOverride;
|
|
207
209
|
// Determine base profile name: from surface state or from .gsd-profile marker
|
|
208
210
|
const baseProfileName = (surface && surface.baseProfile)
|
|
209
211
|
? surface.baseProfile
|
|
@@ -303,15 +305,20 @@ function resolveSurface(runtimeConfigDir, manifest, clusterMap, registry) {
|
|
|
303
305
|
* Re-stage the active surface using the resolved layout.
|
|
304
306
|
* Iterates layout.kinds and syncs each artifact kind to its destination.
|
|
305
307
|
*/
|
|
306
|
-
function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry, opts) {
|
|
308
|
+
function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry, opts, deps = {}) {
|
|
307
309
|
if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
|
|
308
310
|
throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
|
|
309
311
|
}
|
|
312
|
+
// #3712: the dest selection below prefers `kind.home` over layout.configDir and
|
|
313
|
+
// then hands it to the destructive _syncGsdDir, so surface apply is a third
|
|
314
|
+
// escape route into the developer's real home alongside install/uninstall.
|
|
315
|
+
testHomeGuard.assertTestHomeSandboxed('applySurface', layout.runtime, layout.kinds, {
|
|
316
|
+
os: deps.os, env: deps.env,
|
|
317
|
+
});
|
|
310
318
|
const skillManifest = normalizeSkillManifest(layout.configDir, manifest);
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
retiredArtifactCleanup.pruneRetiredRuntimeArtifacts(layout.runtime, layout.configDir);
|
|
319
|
+
const hasCandidateState = opts !== undefined && Object.prototype.hasOwnProperty.call(opts, 'surfaceState');
|
|
320
|
+
const candidateState = hasCandidateState ? (opts.surfaceState ?? null) : undefined;
|
|
321
|
+
const resolved = resolveSurface(layout.configDir, skillManifest, clusterMap, registry, candidateState);
|
|
315
322
|
// #1575: agents kind now mirrors createRuntimeArtifactInstallPlan — build
|
|
316
323
|
// agentCtx (pathPrefix + attribution) and pass it to kind.stage() so
|
|
317
324
|
// stageAgentsForRuntimeWithConverter applies the full inline-loop pipeline
|
|
@@ -345,7 +352,7 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
345
352
|
// not referenced by any skill's _calls_agents_ manifest entry would be silently
|
|
346
353
|
// dropped from the surface path. For tiered profiles (core/standard) or when
|
|
347
354
|
// surface mods exist, pass the resolved set so only the filtered subset stages.
|
|
348
|
-
const _surfaceState = readSurface(layout.configDir);
|
|
355
|
+
const _surfaceState = candidateState === undefined ? readSurface(layout.configDir) : candidateState;
|
|
349
356
|
const _baseProfileName = (_surfaceState && _surfaceState.baseProfile)
|
|
350
357
|
? _surfaceState.baseProfile
|
|
351
358
|
: (readActiveProfile(layout.configDir) || 'full');
|
|
@@ -353,6 +360,7 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
353
360
|
_surfaceState.explicitAdds.length > 0 ||
|
|
354
361
|
_surfaceState.explicitRemoves.length > 0);
|
|
355
362
|
const _isUnmodifiedFull = _baseProfileName === 'full' && !_hasSurfaceMods;
|
|
363
|
+
const stagedKinds = [];
|
|
356
364
|
try {
|
|
357
365
|
for (const kind of layout.kinds) {
|
|
358
366
|
let staged;
|
|
@@ -388,7 +396,22 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
388
396
|
// the parity test in tests/runtime-artifact-layout-surface.test.cjs
|
|
389
397
|
// enforces that the two writers never diverge again.
|
|
390
398
|
const dest = assertDestWithinConfigHome(kind.home ?? layout.configDir, kind.destSubpath);
|
|
391
|
-
|
|
399
|
+
stagedKinds.push({ kind, staged, dest });
|
|
400
|
+
}
|
|
401
|
+
// Do not mutate installed artifacts until every kind has staged
|
|
402
|
+
// successfully. A missing source provider therefore leaves both artifacts
|
|
403
|
+
// and the persisted surface state untouched.
|
|
404
|
+
retiredArtifactCleanup.pruneRetiredRuntimeArtifacts(layout.runtime, layout.configDir);
|
|
405
|
+
for (const item of stagedKinds) {
|
|
406
|
+
_syncGsdDir(item.staged, item.dest, item.kind, skillManifest, layout.runtime);
|
|
407
|
+
}
|
|
408
|
+
if (hasCandidateState) {
|
|
409
|
+
if (candidateState === null) {
|
|
410
|
+
node_fs_1.default.rmSync(node_path_1.default.join(runtimeConfigDir, SURFACE_FILE_NAME), { force: true });
|
|
411
|
+
}
|
|
412
|
+
else if (candidateState !== undefined) {
|
|
413
|
+
writeSurface(runtimeConfigDir, candidateState);
|
|
414
|
+
}
|
|
392
415
|
}
|
|
393
416
|
}
|
|
394
417
|
finally {
|
|
@@ -14,6 +14,14 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
15
|
const ioMod = require("./io.cjs");
|
|
16
16
|
const { output, error, ERROR_REASON } = ioMod;
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const planDocumentMod = require("./plan-document.cjs");
|
|
19
|
+
const { parsePlanDocument } = planDocumentMod;
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
|
+
const capabilityLoaderMod = require("./capability-loader.cjs");
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
const taskContentResolutionMod = require("./task-content-resolution.cjs");
|
|
24
|
+
const { resolveTaskContent, ResolverAmbiguousError, ResolverFailedError, ResolverTimeoutError, ResolverMalformedOutputError, } = taskContentResolutionMod;
|
|
17
25
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
18
26
|
function isBehaviorAddingTaskContent(content) {
|
|
19
27
|
const tddTrue = /\btdd\s*=\s*["']true["']/i.test(content);
|
|
@@ -52,10 +60,111 @@ function isBehaviorAddingTaskContent(content) {
|
|
|
52
60
|
reason: isBehaviorAdding ? null : `Not behavior-adding: ${missing.join('; ')}`,
|
|
53
61
|
};
|
|
54
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Default (production) capability loader for `resolve-content`: the merged
|
|
65
|
+
* first-party + validated-installed-overlay registry (ADR-1244 D2), the
|
|
66
|
+
* established runtime read path for "installed capabilities including
|
|
67
|
+
* third-party" — as opposed to `capability-loader.cts`'s heavier build-time
|
|
68
|
+
* validation entry points or the static `capability-registry.cjs` alone
|
|
69
|
+
* (first-party only, would miss a third-party capability's
|
|
70
|
+
* `taskContentResolver` declaration entirely).
|
|
71
|
+
*/
|
|
72
|
+
function defaultLoadCapabilities(cwd) {
|
|
73
|
+
const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
|
|
74
|
+
return Object.values(registry.capabilities ?? {});
|
|
75
|
+
}
|
|
76
|
+
function parseResolveContentArgs(args) {
|
|
77
|
+
let plan = null;
|
|
78
|
+
let taskId = null;
|
|
79
|
+
for (let i = 2; i < args.length; i++) {
|
|
80
|
+
if (args[i] === '--plan') {
|
|
81
|
+
plan = args[i + 1] ?? null;
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
else if (args[i] === '--task-id') {
|
|
85
|
+
taskId = args[i + 1] ?? null;
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { plan, taskId };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* `task resolve-content --plan <PLAN.md path> --task-id <tracker-id value> --raw`
|
|
93
|
+
* (ADR-3646 Decision 2). Resolves one task's content from the external
|
|
94
|
+
* tracker its `tracker-id` attribute names, via `task-content-resolution.cts`.
|
|
95
|
+
*
|
|
96
|
+
* HARD-HALT CONTRACT: a thrown `ResolverAmbiguousError` / `ResolverFailedError`
|
|
97
|
+
* / `ResolverTimeoutError` / `ResolverMalformedOutputError` from
|
|
98
|
+
* `resolveTaskContent` is turned into this CLI's own non-zero exit via
|
|
99
|
+
* `error()` — never swallowed into a `{resolved: false}` JSON answer. Any
|
|
100
|
+
* other thrown error is not one of the four documented resolver-error
|
|
101
|
+
* classes and is allowed to propagate uncaught.
|
|
102
|
+
*/
|
|
103
|
+
function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
104
|
+
const usage = 'Usage: task resolve-content --plan <path> --task-id <tracker-id> --raw';
|
|
105
|
+
const { plan, taskId } = parseResolveContentArgs(args);
|
|
106
|
+
if (!plan || !taskId) {
|
|
107
|
+
error(usage, ERROR_REASON.USAGE);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const projectRoot = node_path_1.default.resolve(cwd || process.cwd());
|
|
111
|
+
const resolvedPlanPath = node_path_1.default.resolve(projectRoot, plan);
|
|
112
|
+
const rel = node_path_1.default.relative(projectRoot, resolvedPlanPath);
|
|
113
|
+
if (rel === '..' || rel.startsWith(`..${node_path_1.default.sep}`)) {
|
|
114
|
+
error(`Plan file is outside project scope: ${plan}`, ERROR_REASON.USAGE);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!node_fs_1.default.existsSync(resolvedPlanPath)) {
|
|
118
|
+
error(`Plan file not found: ${plan}`, ERROR_REASON.USAGE);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const planContent = node_fs_1.default.readFileSync(resolvedPlanPath, 'utf-8');
|
|
122
|
+
const parsedPlan = parsePlanDocument(planContent, resolvedPlanPath);
|
|
123
|
+
const task = (parsedPlan.tasks ?? []).find((t) => t.trackerId === taskId);
|
|
124
|
+
if (!task) {
|
|
125
|
+
error(`No task with tracker-id '${taskId}' found in plan: ${plan}`, ERROR_REASON.USAGE);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const loadCapabilities = deps.loadCapabilities ?? defaultLoadCapabilities;
|
|
129
|
+
const capabilities = loadCapabilities(projectRoot);
|
|
130
|
+
const resolveFn = deps.resolveTaskContentFn ?? resolveTaskContent;
|
|
131
|
+
let result;
|
|
132
|
+
try {
|
|
133
|
+
result = resolveFn({ trackerId: task.trackerId, capabilities });
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (err instanceof ResolverAmbiguousError ||
|
|
137
|
+
err instanceof ResolverFailedError ||
|
|
138
|
+
err instanceof ResolverTimeoutError ||
|
|
139
|
+
err instanceof ResolverMalformedOutputError) {
|
|
140
|
+
error(err.message, ERROR_REASON.UNKNOWN);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
switch (result.kind) {
|
|
146
|
+
case 'not-applicable':
|
|
147
|
+
output({ resolved: false }, raw, undefined);
|
|
148
|
+
return;
|
|
149
|
+
case 'no-resolver':
|
|
150
|
+
output({ resolved: false, reason: 'no-resolver' }, raw, undefined);
|
|
151
|
+
return;
|
|
152
|
+
case 'empty':
|
|
153
|
+
output({ resolved: false, reason: 'empty' }, raw, undefined);
|
|
154
|
+
return;
|
|
155
|
+
case 'resolved':
|
|
156
|
+
output({ resolved: true, content: result.content }, raw, undefined);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
55
160
|
function routeTaskCommand({ args, cwd, raw }) {
|
|
56
161
|
const subcommand = args[1];
|
|
162
|
+
if (subcommand === 'resolve-content') {
|
|
163
|
+
routeResolveContent({ args, cwd, raw });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
57
166
|
if (subcommand !== 'is-behavior-adding') {
|
|
58
|
-
error('Unknown task subcommand. Available: is-behavior-adding', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
167
|
+
error('Unknown task subcommand. Available: is-behavior-adding, resolve-content', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
59
168
|
}
|
|
60
169
|
let content = null;
|
|
61
170
|
if (args[2] === '--task-content') {
|
|
@@ -82,4 +191,5 @@ function routeTaskCommand({ args, cwd, raw }) {
|
|
|
82
191
|
module.exports = {
|
|
83
192
|
isBehaviorAddingTaskContent,
|
|
84
193
|
routeTaskCommand,
|
|
194
|
+
routeResolveContent,
|
|
85
195
|
};
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Task Content Resolution Module (ADR-3646 Phase 1, #3970).
|
|
4
|
+
*
|
|
5
|
+
* Given a task's `tracker-id` attribute value (parsed verbatim by
|
|
6
|
+
* `plan-document.cts`, never split there) and the set of installed
|
|
7
|
+
* capabilities' `taskContentResolver` declarations, resolves the task's
|
|
8
|
+
* content from the matching external tracker via a bounded subprocess call —
|
|
9
|
+
* or reports that no resolution applies.
|
|
10
|
+
*
|
|
11
|
+
* PURE / IMPURE SPLIT, loosely mirroring `review-lane-invocation.cts`'s
|
|
12
|
+
* resolve-then-run shape, but deliberately NOT copying its full machinery
|
|
13
|
+
* (Gall's Law — see `40-design.md`'s "Laws that apply" section): this problem
|
|
14
|
+
* has no probe/model/effort/prompt-channel axes, just one deterministic
|
|
15
|
+
* id-lookup. `splitTrackerId`, `findResolver`, and `buildInvocation` are pure
|
|
16
|
+
* and total (never throw, even on hostile third-party-shaped input — a
|
|
17
|
+
* capability manifest is third-party-authored, and while `capability-
|
|
18
|
+
* validator.cjs` validates it at install time, this module re-validates
|
|
19
|
+
* defensively rather than trusting that boundary). `resolveTaskContent` is
|
|
20
|
+
* the one impure boundary: it spawns exactly one bounded subprocess, through
|
|
21
|
+
* an injectable `execFn` so tests never spawn a real process or wait a real
|
|
22
|
+
* timeout (CLAUDE.md's clock-seam rule).
|
|
23
|
+
*
|
|
24
|
+
* HARD-HALT CONTRACT (ADR-3646 Decision 4): an ambiguous resolver match, a
|
|
25
|
+
* non-zero resolver exit, a timeout, or malformed resolver stdout all THROW.
|
|
26
|
+
* None of these degrade to a silently-empty or silently-picked result — a
|
|
27
|
+
* task-content resolution failure must halt the caller (`task-command-
|
|
28
|
+
* router.cts`'s `resolve-content` subcommand turns each throw into the CLI's
|
|
29
|
+
* own non-zero exit), never fall back to inline PLAN.md content pretending
|
|
30
|
+
* nothing happened.
|
|
31
|
+
*
|
|
32
|
+
* ADR-457 build-at-publish: source in src/task-content-resolution.cts,
|
|
33
|
+
* compiled to gsd-core/bin/lib/task-content-resolution.cjs (gitignored).
|
|
34
|
+
*/
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
// Use non-destructured namespace import so test-time mock.method(childProcess,
|
|
39
|
+
// 'spawnSync') can intercept calls from this seam — destructured imports
|
|
40
|
+
// capture references at load time and become un-mockable (matches the
|
|
41
|
+
// convention documented in shell-command-projection.cts).
|
|
42
|
+
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
|
+
const ioMod = require("./io.cjs");
|
|
45
|
+
const { formatDiagnosticToken } = ioMod;
|
|
46
|
+
// ─── Result taxonomy ──────────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* The four non-throwing outcomes of `resolveTaskContent`. Frozen because the
|
|
49
|
+
* `kind` discriminant is the product — callers (the CLI seam) switch on it
|
|
50
|
+
* directly rather than string-matching prose.
|
|
51
|
+
*/
|
|
52
|
+
const TASK_CONTENT_RESULT = Object.freeze({
|
|
53
|
+
NOT_APPLICABLE: 'not-applicable',
|
|
54
|
+
NO_RESOLVER: 'no-resolver',
|
|
55
|
+
RESOLVED: 'resolved',
|
|
56
|
+
EMPTY: 'empty',
|
|
57
|
+
});
|
|
58
|
+
// ─── Throwable error taxonomy ─────────────────────────────────────────────────
|
|
59
|
+
// These four ALWAYS throw — they are configuration/execution defects, never a
|
|
60
|
+
// value `resolveTaskContent` returns. See the module docstring's hard-halt
|
|
61
|
+
// contract.
|
|
62
|
+
/**
|
|
63
|
+
* Two or more installed capabilities declare a `taskContentResolver` for the
|
|
64
|
+
* same `trackerPrefix`. Structurally impossible in a correctly-validated
|
|
65
|
+
* install (`capability-validator.cjs` enforces cross-capability prefix
|
|
66
|
+
* uniqueness), but `findResolver` must still refuse to silently pick one if a
|
|
67
|
+
* test harness or a validator bug ever produces this shape.
|
|
68
|
+
*/
|
|
69
|
+
class ResolverAmbiguousError extends Error {
|
|
70
|
+
prefix;
|
|
71
|
+
capabilityIds;
|
|
72
|
+
constructor(prefix, capabilityIds) {
|
|
73
|
+
super(`tracker prefix '${prefix}' matches ${capabilityIds.length} installed capability resolvers ` +
|
|
74
|
+
`(${capabilityIds.join(', ')}) — ambiguous resolver registration must never silently pick one`);
|
|
75
|
+
this.name = 'ResolverAmbiguousError';
|
|
76
|
+
this.prefix = prefix;
|
|
77
|
+
this.capabilityIds = capabilityIds;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The resolver subprocess exited non-zero (or failed to spawn at all).
|
|
82
|
+
*
|
|
83
|
+
* `stderrTail` is UNTRUSTED subprocess-sourced text (the resolver binary is
|
|
84
|
+
* declared by a capability manifest and invoked with an argv token derived
|
|
85
|
+
* from a PLAN.md `tracker-id` attribute, which is often LLM/agent-authored —
|
|
86
|
+
* a hostile or buggy resolver could echo attacker-influenced text back on
|
|
87
|
+
* its own stderr). `.message` embeds it through `io.cjs`'s
|
|
88
|
+
* `formatDiagnosticToken()` so every caller of `resolveTaskContent` gets a
|
|
89
|
+
* `.message` that is already safe to write verbatim to a plain-text
|
|
90
|
+
* diagnostic — see that function's docstring for why this must happen here,
|
|
91
|
+
* at the point the untrusted substring is embedded, rather than at each
|
|
92
|
+
* call site.
|
|
93
|
+
*/
|
|
94
|
+
class ResolverFailedError extends Error {
|
|
95
|
+
exitCode;
|
|
96
|
+
stderrTail;
|
|
97
|
+
constructor(binary, exitCode, stderrTail) {
|
|
98
|
+
super(`resolver command '${binary}' exited ${exitCode === null ? 'with no exit code (spawn failure)' : exitCode}` +
|
|
99
|
+
(stderrTail ? `: ${formatDiagnosticToken(stderrTail)}` : ''));
|
|
100
|
+
this.name = 'ResolverFailedError';
|
|
101
|
+
this.exitCode = exitCode;
|
|
102
|
+
this.stderrTail = stderrTail;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** The resolver subprocess exceeded its declared `invoke.timeoutMs` bound. */
|
|
106
|
+
class ResolverTimeoutError extends Error {
|
|
107
|
+
timeoutMs;
|
|
108
|
+
constructor(binary, timeoutMs) {
|
|
109
|
+
super(`resolver command '${binary}' timed out after ${timeoutMs}ms`);
|
|
110
|
+
this.name = 'ResolverTimeoutError';
|
|
111
|
+
this.timeoutMs = timeoutMs;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The resolver's stdout was not valid JSON, or was valid JSON that is not a
|
|
116
|
+
* plain object (a `null`, array, string, number, or boolean top-level value
|
|
117
|
+
* is rejected — only a plain object can carry the `description`/`verify`/
|
|
118
|
+
* `acceptance_criteria`/`read_first`/`done` fields this seam reads).
|
|
119
|
+
*/
|
|
120
|
+
class ResolverMalformedOutputError extends Error {
|
|
121
|
+
stdoutSample;
|
|
122
|
+
constructor(binary, reason, stdoutSample) {
|
|
123
|
+
super(`resolver command '${binary}' produced malformed output: ${reason}` +
|
|
124
|
+
(stdoutSample ? ` (stdout sample: ${formatDiagnosticToken(stdoutSample)})` : ''));
|
|
125
|
+
this.name = 'ResolverMalformedOutputError';
|
|
126
|
+
this.stdoutSample = stdoutSample;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// ─── Pure functions ────────────────────────────────────────────────────────────
|
|
130
|
+
/**
|
|
131
|
+
* The SAME kebab-case grammar `capability-validator.cjs`'s `KEBAB_RE`
|
|
132
|
+
* enforces on `taskContentResolver.trackerPrefix` at install time
|
|
133
|
+
* (`validateTaskContentResolverFields`). Duplicated as a literal rather than
|
|
134
|
+
* imported — `.cts` (build-at-publish, ADR-457) and the hand-written
|
|
135
|
+
* `capability-validator.cjs` are genuinely two different build targets with
|
|
136
|
+
* no shared-constants module between them today — but `tests/task-content-
|
|
137
|
+
* resolver-grammar-parity.test.cjs` asserts both regexes agree on a shared
|
|
138
|
+
* table of inputs, so a future edit to either one that silently diverges from
|
|
139
|
+
* the other fails a test instead of drifting quietly (CLAUDE.md's Generative
|
|
140
|
+
* Fix Divergence rule).
|
|
141
|
+
*/
|
|
142
|
+
const TRACKER_PREFIX_RE = /^[a-z][a-z0-9-]*$/;
|
|
143
|
+
/**
|
|
144
|
+
* Split a `tracker-id` attribute value into its prefix and id, on the FIRST
|
|
145
|
+
* `:` only — colons after the first stay in the id verbatim (a tracker whose
|
|
146
|
+
* native ids contain colons, e.g. `beads:issue:GSD-1` → `{prefix: "beads",
|
|
147
|
+
* id: "issue:GSD-1"}`).
|
|
148
|
+
*
|
|
149
|
+
* PURE. Returns `null` for `null`/empty input, and for a string with no `:`
|
|
150
|
+
* at all (nothing to split — there is no prefix to match a resolver against).
|
|
151
|
+
*/
|
|
152
|
+
function splitTrackerId(trackerId) {
|
|
153
|
+
if (typeof trackerId !== 'string' || trackerId.length === 0)
|
|
154
|
+
return null;
|
|
155
|
+
const colonIdx = trackerId.indexOf(':');
|
|
156
|
+
if (colonIdx === -1)
|
|
157
|
+
return null;
|
|
158
|
+
const prefix = trackerId.slice(0, colonIdx);
|
|
159
|
+
const id = trackerId.slice(colonIdx + 1);
|
|
160
|
+
if (!prefix || !id)
|
|
161
|
+
return null;
|
|
162
|
+
return { prefix, id };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Defensively re-validate a raw `taskContentResolver` declaration's shape.
|
|
166
|
+
* `capability-validator.cjs` already enforces this at install time, but this
|
|
167
|
+
* module treats every capability manifest as third-party-authored input and
|
|
168
|
+
* never trusts a shape it has not itself checked — a malformed declaration is
|
|
169
|
+
* treated as though it does not exist for matching purposes, never thrown on.
|
|
170
|
+
*/
|
|
171
|
+
function parseResolverDeclaration(capabilityId, raw) {
|
|
172
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
173
|
+
return null;
|
|
174
|
+
const body = raw;
|
|
175
|
+
const trackerPrefix = typeof body.trackerPrefix === 'string' ? body.trackerPrefix.trim() : '';
|
|
176
|
+
if (!trackerPrefix || !TRACKER_PREFIX_RE.test(trackerPrefix))
|
|
177
|
+
return null;
|
|
178
|
+
const inv = body.invoke;
|
|
179
|
+
if (inv === null || typeof inv !== 'object' || Array.isArray(inv))
|
|
180
|
+
return null;
|
|
181
|
+
const invBody = inv;
|
|
182
|
+
const binary = typeof invBody.binary === 'string' ? invBody.binary.trim() : '';
|
|
183
|
+
if (!binary)
|
|
184
|
+
return null;
|
|
185
|
+
const args = Array.isArray(invBody.args)
|
|
186
|
+
? invBody.args.filter((a) => typeof a === 'string')
|
|
187
|
+
: null;
|
|
188
|
+
if (args === null || args.length !== invBody.args.length)
|
|
189
|
+
return null;
|
|
190
|
+
if (!args.includes('{{id}}'))
|
|
191
|
+
return null;
|
|
192
|
+
const timeoutMs = invBody.timeoutMs;
|
|
193
|
+
if (typeof timeoutMs !== 'number' || !Number.isInteger(timeoutMs) || timeoutMs <= 0)
|
|
194
|
+
return null;
|
|
195
|
+
return {
|
|
196
|
+
capabilityId,
|
|
197
|
+
trackerPrefix,
|
|
198
|
+
invoke: { binary, args, timeoutMs },
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Find the resolver declared for `prefix` among `capabilities`.
|
|
203
|
+
*
|
|
204
|
+
* PURE, total. Returns:
|
|
205
|
+
* - the single matching declaration when exactly one well-formed resolver
|
|
206
|
+
* declares `trackerPrefix === prefix`;
|
|
207
|
+
* - `null` when zero capabilities declare a well-formed resolver for it
|
|
208
|
+
* (an unrecognized prefix is a data case, not a defect — see design row 11);
|
|
209
|
+
* - the literal string `'ambiguous'` when two or more do — this must be
|
|
210
|
+
* structurally impossible in a correctly-validated install, but this
|
|
211
|
+
* function refuses to silently pick one regardless.
|
|
212
|
+
*/
|
|
213
|
+
function findResolver(prefix, capabilities) {
|
|
214
|
+
const matches = [];
|
|
215
|
+
for (const cap of capabilities ?? []) {
|
|
216
|
+
if (!cap || typeof cap !== 'object')
|
|
217
|
+
continue;
|
|
218
|
+
const decl = parseResolverDeclaration(cap.id, cap.taskContentResolver);
|
|
219
|
+
if (decl && decl.trackerPrefix === prefix)
|
|
220
|
+
matches.push(decl);
|
|
221
|
+
}
|
|
222
|
+
if (matches.length === 0)
|
|
223
|
+
return null;
|
|
224
|
+
if (matches.length > 1)
|
|
225
|
+
return 'ambiguous';
|
|
226
|
+
return matches[0];
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Expand a resolver's `invoke.args` template, replacing every `"{{id}}"`
|
|
230
|
+
* entry with the literal `id` string. Exact-match token replacement, not
|
|
231
|
+
* template-string interpolation — mirrors `review-lane-invocation.cts`'s
|
|
232
|
+
* argv-expansion discipline (a placeholder is a whole array element, not a
|
|
233
|
+
* substring).
|
|
234
|
+
*
|
|
235
|
+
* PURE.
|
|
236
|
+
*/
|
|
237
|
+
function buildInvocation(resolver, id) {
|
|
238
|
+
return {
|
|
239
|
+
binary: resolver.invoke.binary,
|
|
240
|
+
args: resolver.invoke.args.map((a) => (a === '{{id}}' ? id : a)),
|
|
241
|
+
timeoutMs: resolver.invoke.timeoutMs,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Real subprocess execution — the default `execFn`. Uses Node's `spawnSync`
|
|
246
|
+
* with the `timeout` option so a hung resolver is killed at the bound rather
|
|
247
|
+
* than hanging the caller (CLAUDE.md's Unbounded Subprocesses gauntlet line).
|
|
248
|
+
*/
|
|
249
|
+
function realExec(binary, args, opts) {
|
|
250
|
+
const result = node_child_process_1.default.spawnSync(binary, args, {
|
|
251
|
+
encoding: 'utf-8',
|
|
252
|
+
stdio: 'pipe',
|
|
253
|
+
timeout: opts.timeout,
|
|
254
|
+
windowsHide: true,
|
|
255
|
+
});
|
|
256
|
+
return {
|
|
257
|
+
status: result.status ?? null,
|
|
258
|
+
stdout: (result.stdout ?? '').toString(),
|
|
259
|
+
stderr: (result.stderr ?? '').toString(),
|
|
260
|
+
error: result.error ?? undefined,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* True when an `ExecResult` indicates the subprocess was killed by the
|
|
265
|
+
* `timeout` option, i.e. it never completed and reported a real answer. Only
|
|
266
|
+
* `error.code === 'ETIMEDOUT'` is checked — Node.js guarantees this
|
|
267
|
+
* cross-platform when `spawnSync`'s `timeout` option fires; pairing it with a
|
|
268
|
+
* `signal === 'SIGTERM'` check is platform-fragile (Windows does not
|
|
269
|
+
* necessarily report SIGTERM the same way) and risks a false negative. Same
|
|
270
|
+
* predicate discipline as `shell-command-projection.cts`'s `isSpawnTimeout`.
|
|
271
|
+
*/
|
|
272
|
+
function isExecTimeout(result) {
|
|
273
|
+
const err = result.error;
|
|
274
|
+
return err?.code === 'ETIMEDOUT';
|
|
275
|
+
}
|
|
276
|
+
// ─── Resolver JSON → content mapping ──────────────────────────────────────────
|
|
277
|
+
function coerceStringOrNull(value) {
|
|
278
|
+
return typeof value === 'string' ? value : null;
|
|
279
|
+
}
|
|
280
|
+
function coerceStringArray(value) {
|
|
281
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Map a resolver's validated JSON object onto `ResolvedTaskContent`. Every
|
|
285
|
+
* field is coerced defensively — the resolver's JSON is a third-party CLI's
|
|
286
|
+
* output, validated for exit code and JSON-object-shape upstream, but never
|
|
287
|
+
* trusted field-by-field. A missing or wrong-typed field degrades sanely
|
|
288
|
+
* (string/null fields fall back to `null`, array fields fall back to `[]`);
|
|
289
|
+
* only the caller's `description`-emptiness check throws no further errors
|
|
290
|
+
* here — this function is never the one that decides `resolved` vs `empty`.
|
|
291
|
+
*/
|
|
292
|
+
function mapResolverOutput(body) {
|
|
293
|
+
return {
|
|
294
|
+
action: coerceStringOrNull(body['description']),
|
|
295
|
+
verify: coerceStringOrNull(body['verify']),
|
|
296
|
+
acceptanceCriteria: coerceStringArray(body['acceptance_criteria']),
|
|
297
|
+
readFirst: coerceStringArray(body['read_first']),
|
|
298
|
+
done: coerceStringOrNull(body['done']),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Orchestrate one task's content resolution: split the `tracker-id`, find the
|
|
303
|
+
* matching capability's resolver, invoke it through the bounded subprocess
|
|
304
|
+
* boundary, and map its JSON output onto the four documented outcomes.
|
|
305
|
+
*
|
|
306
|
+
* The only impure boundary is `execFn` (defaults to a real `spawnSync` call).
|
|
307
|
+
* Injecting a fake `execFn` lets tests assert every outcome — including a
|
|
308
|
+
* timeout — deterministically, without spawning a real process or waiting a
|
|
309
|
+
* real `timeoutMs`.
|
|
310
|
+
*/
|
|
311
|
+
function resolveTaskContent(input) {
|
|
312
|
+
const split = splitTrackerId(input.trackerId);
|
|
313
|
+
if (split === null)
|
|
314
|
+
return { kind: TASK_CONTENT_RESULT.NOT_APPLICABLE };
|
|
315
|
+
const resolver = findResolver(split.prefix, input.capabilities ?? []);
|
|
316
|
+
if (resolver === null)
|
|
317
|
+
return { kind: TASK_CONTENT_RESULT.NO_RESOLVER };
|
|
318
|
+
if (resolver === 'ambiguous') {
|
|
319
|
+
// findResolver never returns the capability ids for the ambiguous case
|
|
320
|
+
// (it discards the losing matches) — re-derive them here for the error.
|
|
321
|
+
const ids = (input.capabilities ?? [])
|
|
322
|
+
.filter((cap) => {
|
|
323
|
+
const decl = parseResolverDeclaration(cap?.id, cap?.taskContentResolver);
|
|
324
|
+
return decl !== null && decl.trackerPrefix === split.prefix;
|
|
325
|
+
})
|
|
326
|
+
.map((cap) => cap.id);
|
|
327
|
+
throw new ResolverAmbiguousError(split.prefix, ids);
|
|
328
|
+
}
|
|
329
|
+
const invocation = buildInvocation(resolver, split.id);
|
|
330
|
+
const timeoutMs = input.timeoutOverrideMs ?? invocation.timeoutMs;
|
|
331
|
+
const execFn = input.execFn ?? realExec;
|
|
332
|
+
const result = execFn(invocation.binary, invocation.args, { timeout: timeoutMs });
|
|
333
|
+
if (isExecTimeout(result)) {
|
|
334
|
+
throw new ResolverTimeoutError(invocation.binary, timeoutMs);
|
|
335
|
+
}
|
|
336
|
+
if (result.error || result.status !== 0) {
|
|
337
|
+
const stderrTail = (result.stderr ?? '').trim().slice(-2000);
|
|
338
|
+
throw new ResolverFailedError(invocation.binary, result.status, stderrTail);
|
|
339
|
+
}
|
|
340
|
+
let parsed;
|
|
341
|
+
try {
|
|
342
|
+
parsed = JSON.parse(result.stdout);
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
throw new ResolverMalformedOutputError(invocation.binary, 'stdout is not valid JSON', result.stdout.slice(0, 200));
|
|
346
|
+
}
|
|
347
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
348
|
+
throw new ResolverMalformedOutputError(invocation.binary, `stdout parsed as valid JSON but is not a plain object (got ${Array.isArray(parsed) ? 'array' : typeof parsed})`, result.stdout.slice(0, 200));
|
|
349
|
+
}
|
|
350
|
+
const content = mapResolverOutput(parsed);
|
|
351
|
+
const description = typeof content.action === 'string' ? content.action.trim() : '';
|
|
352
|
+
if (description.length === 0) {
|
|
353
|
+
return { kind: TASK_CONTENT_RESULT.EMPTY };
|
|
354
|
+
}
|
|
355
|
+
return { kind: TASK_CONTENT_RESULT.RESOLVED, content };
|
|
356
|
+
}
|
|
357
|
+
const taskContentResolution = {
|
|
358
|
+
TASK_CONTENT_RESULT,
|
|
359
|
+
splitTrackerId,
|
|
360
|
+
findResolver,
|
|
361
|
+
buildInvocation,
|
|
362
|
+
resolveTaskContent,
|
|
363
|
+
ResolverAmbiguousError,
|
|
364
|
+
ResolverFailedError,
|
|
365
|
+
ResolverTimeoutError,
|
|
366
|
+
ResolverMalformedOutputError,
|
|
367
|
+
};
|
|
368
|
+
module.exports = taskContentResolution;
|