@opengsd/gsd-core 1.10.0 → 1.12.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/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- 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 +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- 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/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- 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/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -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/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- 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-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -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/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- 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/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- 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/references/agent-contracts.md +44 -26
- 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 +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +17 -11
- package/gsd-core/references/failing-direction.md +78 -0
- 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 +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- 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/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- 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 +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- 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/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +13 -20
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- 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 +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
- 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 +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +53 -13
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- 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 +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
- 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 +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- 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 +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- 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 +162 -46
- 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 +92 -59
- 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 +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +162 -46
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -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 +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- 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-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- 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/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +471 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +488 -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-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
// GENERATED FILE — DO NOT EDIT BY HAND.
|
|
2
|
+
// Source of truth: src/cli-exit.cts. Regenerate with:
|
|
3
|
+
// node scripts/gen-hooks-cli-exit.cjs --write
|
|
4
|
+
// Byte-compared by `npm run lint:generated-sync` (#3911, ADR-3889 Phase 7).
|
|
5
|
+
//
|
|
6
|
+
// Why this copy exists: hooks/ runs straight from a raw, unbuilt clone — a
|
|
7
|
+
// shipped hook must be able to `require('./lib/cli-exit.js')` relative to
|
|
8
|
+
// its own __dirname and terminate through `terminateNow` without depending
|
|
9
|
+
// on any build artifact. gsd-core/bin/lib/cli-exit.cjs is gitignored tsc
|
|
10
|
+
// output and doubles as the build sentinel, so it cannot be required from
|
|
11
|
+
// here. `.js`, not `.cjs`, to match the hooks/lib/*.js convention. Hence one
|
|
12
|
+
// source, three emitted locations (gsd-core/bin/lib, scripts/lib, hooks/lib).
|
|
13
|
+
|
|
14
|
+
"use strict";
|
|
15
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Process-exit primitives (ExitError, runMain, terminateNow) plus the
|
|
20
|
+
* json-error-mode and contract-version cells.
|
|
21
|
+
*
|
|
22
|
+
* Must import nothing but `node:fs` and `./exit-code-registry.cjs` — this
|
|
23
|
+
* source is emitted to TWO locations, gsd-core/bin/lib/cli-exit.cjs (tsc
|
|
24
|
+
* build output) and scripts/lib/cli-exit.cjs (a generated, committed
|
|
25
|
+
* artifact regenerated by scripts/gen-scripts-cli-exit.cjs), and the latter
|
|
26
|
+
* must load on an unbuilt clone before anything under ./lib exists. The
|
|
27
|
+
* registry require is safe here for the same reason: scripts/gen-exit-code-
|
|
28
|
+
* registry.cjs (ADR-3889 Phase 1/2, #3905/#3906) dual-emits its OWN sibling
|
|
29
|
+
* artifact, exit-code-registry.cjs, into both of these exact locations, so
|
|
30
|
+
* a relative `./exit-code-registry.cjs` resolves next to whichever copy of
|
|
31
|
+
* this module loaded it.
|
|
32
|
+
*/
|
|
33
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
|
+
const exitCodeRegistryModule = require("./exit-code-registry.js");
|
|
36
|
+
// Called only as exitCodeRegistryModule.exitCodeFor(...), never destructured:
|
|
37
|
+
// @typescript-eslint/unbound-method flags a bare function-typed property
|
|
38
|
+
// pulled off an object at the point of destructuring, since a detached
|
|
39
|
+
// reference COULD be called with the wrong `this` — keeping the member
|
|
40
|
+
// access qualified sidesteps that regardless of whether the callee ever
|
|
41
|
+
// actually touches `this` (it does not; exitCodeFor is pure).
|
|
42
|
+
const exitCodeFor = (name) => exitCodeRegistryModule.exitCodeFor(name);
|
|
43
|
+
/**
|
|
44
|
+
* The wire value `runMain` stamps into its structured envelope. Declared HERE,
|
|
45
|
+
* not in io.cts, because this module must not import anything (see the module
|
|
46
|
+
* header): io.cts builds ERROR_REASON.SDK_FAIL_FAST from this constant, so the
|
|
47
|
+
* two surfaces share ONE definition rather than two literals kept in step by a
|
|
48
|
+
* parity test.
|
|
49
|
+
*/
|
|
50
|
+
const EXIT_ENVELOPE_REASON = 'sdk_fail_fast';
|
|
51
|
+
/**
|
|
52
|
+
* Process-level flag: when true, error paths emit structured JSON to stderr
|
|
53
|
+
* instead of plain text. Set by gsd-tools.cjs when the CLI is invoked with
|
|
54
|
+
* `--json-errors`; re-exported by io.cts, which is where most callers reach it.
|
|
55
|
+
*
|
|
56
|
+
* Held in a Symbol-keyed cell on globalThis rather than in module scope, and
|
|
57
|
+
* that is load-bearing: this module is emitted to TWO locations
|
|
58
|
+
* (gsd-core/bin/lib/cli-exit.cjs and the generated scripts/lib/cli-exit.cjs),
|
|
59
|
+
* so a process that loads both would get two independent module instances. A
|
|
60
|
+
* module-level `let` would give them two independent flags — one copy could
|
|
61
|
+
* think json mode is on while the other thought it was off, which is exactly
|
|
62
|
+
* the divergence class ADR-3889 exists to remove. One cell, keyed by a
|
|
63
|
+
* registry Symbol, makes that unrepresentable.
|
|
64
|
+
*/
|
|
65
|
+
const JSON_ERROR_MODE_KEY = Symbol.for('gsd.exit.jsonErrorMode');
|
|
66
|
+
function setJsonErrorMode(v) {
|
|
67
|
+
globalThis[JSON_ERROR_MODE_KEY] = !!v;
|
|
68
|
+
}
|
|
69
|
+
function getJsonErrorMode() {
|
|
70
|
+
return globalThis[JSON_ERROR_MODE_KEY] === true;
|
|
71
|
+
}
|
|
72
|
+
/** The single registered name code 2 may ever be produced for (ADR-3889 §1). */
|
|
73
|
+
const HOOK_DENY_NAME = 'HOOK_DENY';
|
|
74
|
+
const HOOK_DENY_CODE = exitCodeFor(HOOK_DENY_NAME);
|
|
75
|
+
/**
|
|
76
|
+
* Currently-resolved exit-contract version (ADR-3889 §4). Held in a
|
|
77
|
+
* Symbol-keyed globalThis cell rather than a module-level `let`, for the
|
|
78
|
+
* exact reason JSON_ERROR_MODE_KEY is (see its comment above): this module
|
|
79
|
+
* is emitted to two locations and thus loaded as two independent module
|
|
80
|
+
* instances in any process that requires both, so a module-level variable
|
|
81
|
+
* would let those two instances disagree about which contract is active.
|
|
82
|
+
* `resolveContractVersion` is the only writer; `terminateNow`/`runMain`
|
|
83
|
+
* read it internally when projecting a declared outcome.
|
|
84
|
+
*/
|
|
85
|
+
const CONTRACT_VERSION_KEY = Symbol.for('gsd.exit.contractVersion');
|
|
86
|
+
function setContractVersion(v) {
|
|
87
|
+
globalThis[CONTRACT_VERSION_KEY] = v;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the active exit-contract version, wiring the ambient process to the
|
|
91
|
+
* two terminators (ADR-3889 §4/§3). Mirrors how JSON_ERROR_MODE_KEY already
|
|
92
|
+
* works: a process-global cell means no entrypoint needs per-call wiring, so
|
|
93
|
+
* a `scripts/` tool or a hook gets the same behaviour as `gsd-tools` without
|
|
94
|
+
* this module touching either (P8 owns `gsd-tools`; P7 owns hooks).
|
|
95
|
+
*
|
|
96
|
+
* Precedence: if the cell already holds an explicit version, that wins —
|
|
97
|
+
* this is what lets `setContractVersion` override the ambient process (a
|
|
98
|
+
* later `GSD_EXIT_CONTRACT=v2` in the same process must NOT unseat an
|
|
99
|
+
* explicit `setContractVersion('v1')` call). Otherwise resolve from argv/env
|
|
100
|
+
* via `resolveContractVersion`, which itself persists the result into the
|
|
101
|
+
* cell — so this is a one-time resolution per process; every later read is
|
|
102
|
+
* just the cached cell value. An invalid ambient value (e.g. `v3`) is NOT
|
|
103
|
+
* softened to a silent v1 here: `resolveContractVersion` throws, and that
|
|
104
|
+
* throw propagates — swallowing it would reintroduce the "nothing fails with
|
|
105
|
+
* success" defect ADR-3889 exists to close, on the very selector meant to
|
|
106
|
+
* demonstrate the fix. Absent both flag and env, resolution still yields
|
|
107
|
+
* 'v1' (the documented default) and that too gets memoized.
|
|
108
|
+
*/
|
|
109
|
+
function getContractVersion() {
|
|
110
|
+
const cached = globalThis[CONTRACT_VERSION_KEY];
|
|
111
|
+
if (cached === 'v1' || cached === 'v2')
|
|
112
|
+
return cached;
|
|
113
|
+
return resolveContractVersion({ argv: process.argv, env: process.env });
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Project a declared outcome onto an integer exit code for a given contract
|
|
117
|
+
* version. Pure and total over its own input space: throws for anything not
|
|
118
|
+
* an exact-case registered name (mirrors exitCodeFor's contract) or an
|
|
119
|
+
* unrecognized version — it never returns undefined/NaN.
|
|
120
|
+
*
|
|
121
|
+
* PASS/FAIL and every registered name project IDENTICALLY under v1 and v2
|
|
122
|
+
* (registered names are version-invariant) — the sole exception is DEGRADED:
|
|
123
|
+
*
|
|
124
|
+
* v1: DEGRADED -> 0. Deliberate, NOT a bug: ADR-2980 ratified 60
|
|
125
|
+
* `output({error})` call sites that already exit 0 on a payload-carried
|
|
126
|
+
* error, and ADR-2980's own "Revisit if" clause is what ADR-3889 §4
|
|
127
|
+
* answers — normalizing this to a non-zero code was explicitly
|
|
128
|
+
* DECLINED there on measured blast radius. A future reader must not
|
|
129
|
+
* "fix" this to look more consistent with v2; the inconsistency IS the
|
|
130
|
+
* compatibility boundary.
|
|
131
|
+
* v2: DEGRADED -> exitCodeFor('DEGRADED') (80). Looked up through the
|
|
132
|
+
* registry, never hardcoded, so a re-allocation of DEGRADED's code
|
|
133
|
+
* cannot silently desync this projection from the shipped table.
|
|
134
|
+
*/
|
|
135
|
+
function projectOutcome(outcome, version) {
|
|
136
|
+
if (typeof outcome !== 'string' || outcome.length === 0) {
|
|
137
|
+
throw new Error(`projectOutcome: outcome must be a non-empty string, received ${JSON.stringify(outcome)}`);
|
|
138
|
+
}
|
|
139
|
+
if (version !== 'v1' && version !== 'v2') {
|
|
140
|
+
throw new Error(`projectOutcome: version must be 'v1' or 'v2', received ${JSON.stringify(version)}`);
|
|
141
|
+
}
|
|
142
|
+
if (outcome === 'PASS')
|
|
143
|
+
return 0;
|
|
144
|
+
if (outcome === 'FAIL')
|
|
145
|
+
return 1;
|
|
146
|
+
if (outcome === 'DEGRADED')
|
|
147
|
+
return version === 'v1' ? 0 : exitCodeFor('DEGRADED');
|
|
148
|
+
// Any other registered name: version-invariant, resolved through the
|
|
149
|
+
// registry (throws for anything unregistered/empty/non-string/wrong-case —
|
|
150
|
+
// exitCodeFor's own contract, which this function inherits verbatim).
|
|
151
|
+
return exitCodeFor(outcome);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Pending declared outcome (ADR-3889 §4, #3912): the outcome `output()`
|
|
155
|
+
* records when it detects a payload-carried error (`{ error }`, any key
|
|
156
|
+
* order) on a call that otherwise just returns — there is no thrown
|
|
157
|
+
* ExitError and no explicit `main()` return for `runMain` to project, so
|
|
158
|
+
* without this cell the declaration has nowhere to land. `runMain` reads it
|
|
159
|
+
* ONLY when `main()` itself returns no explicit code (void/undefined); an
|
|
160
|
+
* explicit number/string return always wins over whatever this cell holds.
|
|
161
|
+
*
|
|
162
|
+
* Held in a Symbol-keyed globalThis cell for the exact reason
|
|
163
|
+
* JSON_ERROR_MODE_KEY / CONTRACT_VERSION_KEY are (see their comments above):
|
|
164
|
+
* this module is emitted to three locations and thus loaded as independent
|
|
165
|
+
* module instances in any process that requires more than one, so a
|
|
166
|
+
* module-level variable would let those instances disagree about whether a
|
|
167
|
+
* degraded result was ever declared.
|
|
168
|
+
*
|
|
169
|
+
* LIFETIME (#3912 review fix): LAST DECLARATION WINS, CLEARED ON CONSUMPTION.
|
|
170
|
+
* This cell is NOT "was DEGRADED ever declared this process" — it is "is a
|
|
171
|
+
* degraded outcome pending RIGHT NOW". `output()` sets it to 'DEGRADED' on a
|
|
172
|
+
* payload-carried error and CLEARS it (`undefined`) on a clean payload, so a
|
|
173
|
+
* later clean `output()` call undoes an earlier degraded one in the same
|
|
174
|
+
* invocation. `runMain` clears it immediately after consuming it (in a
|
|
175
|
+
* `finally`, on both the pending-cell branch and the case where nothing was
|
|
176
|
+
* pending), so a second `runMain` in the same process starts clean. Without
|
|
177
|
+
* both halves the cell is monotonic for the life of the process: any later
|
|
178
|
+
* `main()` returning void would inherit a stale DEGRADED from an unrelated,
|
|
179
|
+
* earlier call — this is the leak #3912 review found and fixed.
|
|
180
|
+
*/
|
|
181
|
+
const PENDING_OUTCOME_KEY = Symbol.for('gsd.exit.pendingOutcome');
|
|
182
|
+
function setPendingOutcome(v) {
|
|
183
|
+
globalThis[PENDING_OUTCOME_KEY] = v;
|
|
184
|
+
}
|
|
185
|
+
function getPendingOutcome() {
|
|
186
|
+
return globalThis[PENDING_OUTCOME_KEY];
|
|
187
|
+
}
|
|
188
|
+
const EXIT_CONTRACT_FLAG_PREFIX = '--exit-contract=';
|
|
189
|
+
/** Scan argv for the FIRST `--exit-contract=<value>` token; undefined if absent. */
|
|
190
|
+
function findExitContractFlag(argv) {
|
|
191
|
+
for (const arg of argv) {
|
|
192
|
+
if (typeof arg === 'string' && arg.startsWith(EXIT_CONTRACT_FLAG_PREFIX)) {
|
|
193
|
+
return arg.slice(EXIT_CONTRACT_FLAG_PREFIX.length);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Resolve which exit-contract version is active from argv/env, per ADR-3889
|
|
200
|
+
* §4, and persist it to the shared contract-version cell so a later
|
|
201
|
+
* `terminateNow`/`runMain` call (through EITHER module copy) projects
|
|
202
|
+
* against it without re-parsing argv/env itself.
|
|
203
|
+
*
|
|
204
|
+
* Precedence: an explicit `--exit-contract=<v>` flag BEATS
|
|
205
|
+
* `GSD_EXIT_CONTRACT`, in both directions (flag=v1 + env=v2 -> v1; flag=v2 +
|
|
206
|
+
* env=v1 -> v2). Neither present -> 'v1' (the documented default). An empty
|
|
207
|
+
* env var reads as UNSET, not as an explicit empty selection — a shell that
|
|
208
|
+
* exports `GSD_EXIT_CONTRACT=` with nothing after the `=` must not silently
|
|
209
|
+
* select a version.
|
|
210
|
+
*
|
|
211
|
+
* Casing is decided, not accidental: only the exact lowercase tokens `v1`/
|
|
212
|
+
* `v2` are accepted (matching every example in ADR-3889 and this module's own
|
|
213
|
+
* usage docs, both of which write `v2` never `V2`). Anything else recognized
|
|
214
|
+
* as PRESENT but not a valid version — `v3`, `garbage`, or an explicitly
|
|
215
|
+
* empty flag value (`--exit-contract=`) — THROWS rather than silently
|
|
216
|
+
* defaulting to v1. A selector for a contract whose whole thesis is "nothing
|
|
217
|
+
* fails with success" must not itself fail open.
|
|
218
|
+
*/
|
|
219
|
+
function resolveContractVersion(opts = {}) {
|
|
220
|
+
const argv = opts.argv ?? process.argv;
|
|
221
|
+
const env = opts.env ?? process.env;
|
|
222
|
+
const flagValue = findExitContractFlag(argv);
|
|
223
|
+
const rawEnvValue = env.GSD_EXIT_CONTRACT;
|
|
224
|
+
const envValue = rawEnvValue === undefined || rawEnvValue === '' ? undefined : rawEnvValue;
|
|
225
|
+
const selected = flagValue !== undefined ? flagValue : envValue;
|
|
226
|
+
let resolved;
|
|
227
|
+
if (selected === undefined) {
|
|
228
|
+
resolved = 'v1';
|
|
229
|
+
}
|
|
230
|
+
else if (selected === 'v1' || selected === 'v2') {
|
|
231
|
+
resolved = selected;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
throw new Error(`resolveContractVersion: unrecognized exit-contract version ${JSON.stringify(selected)} `
|
|
235
|
+
+ `(expected 'v1' or 'v2')`);
|
|
236
|
+
}
|
|
237
|
+
setContractVersion(resolved);
|
|
238
|
+
return resolved;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Error carrying a process exit code. CLI logic throws this instead of calling
|
|
242
|
+
* process.exit() (banned by n/no-process-exit); runMain() translates it into
|
|
243
|
+
* process.exitCode at the entrypoint.
|
|
244
|
+
*/
|
|
245
|
+
class ExitError extends Error {
|
|
246
|
+
code;
|
|
247
|
+
hasUserMessage;
|
|
248
|
+
constructor(code = 1, message) {
|
|
249
|
+
super(message === undefined ? `process exit ${code}` : message);
|
|
250
|
+
this.name = 'ExitError';
|
|
251
|
+
this.code = code;
|
|
252
|
+
this.hasUserMessage = message !== undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Run a CLI main and translate its outcome into process.exitCode (never
|
|
257
|
+
* process.exit, so n/no-process-exit stays satisfied; output flushes and
|
|
258
|
+
* process.on('exit') cleanup still fires — this is precisely why runMain and
|
|
259
|
+
* terminateNow are two different functions: drain-then-exit vs write-then-
|
|
260
|
+
* terminate). main may be sync or async. Every arm below except the new
|
|
261
|
+
* string one and the void/pending-cell one is UNCHANGED from before
|
|
262
|
+
* ADR-3889 Phase 2:
|
|
263
|
+
* number return -> process.exitCode = it (unchanged)
|
|
264
|
+
* string return -> NEW: process.exitCode = projectOutcome(result, getContractVersion()),
|
|
265
|
+
* UNLESS that projection is the HOOK_DENY exit code (see
|
|
266
|
+
* the refusal below — 2 may only be produced by terminateNow).
|
|
267
|
+
* void/undefined return -> NEW (#3912, ADR-3889 §4): an explicit return
|
|
268
|
+
* already handled above always wins, so this arm only
|
|
269
|
+
* runs when main() declared no outcome of its own. If
|
|
270
|
+
* the pending-outcome cell holds a value (currently only
|
|
271
|
+
* ever 'DEGRADED', set by io.cts's output() on a
|
|
272
|
+
* payload-carried error), project THAT through the
|
|
273
|
+
* current contract version — BUT ONLY when
|
|
274
|
+
* process.exitCode is not already a non-zero value.
|
|
275
|
+
* FULL PRECEDENCE ORDER for the code a void-returning
|
|
276
|
+
* main() ends up with:
|
|
277
|
+
* 1. An explicit number/string return from main()
|
|
278
|
+
* (handled in the arms above) — always wins.
|
|
279
|
+
* 2. A non-zero process.exitCode already set by main()
|
|
280
|
+
* itself before it returned (e.g. `state validate
|
|
281
|
+
* --strict`'s `emit()` setting 1 directly) — wins
|
|
282
|
+
* over the pending cell.
|
|
283
|
+
* 3. The pending-outcome cell's projection — used only
|
|
284
|
+
* when process.exitCode is still unset/0.
|
|
285
|
+
* 4. Otherwise process.exitCode stays 0 (default).
|
|
286
|
+
* This is a regression fix: unconditionally projecting
|
|
287
|
+
* the pending cell here used to CLOBBER an
|
|
288
|
+
* already-non-zero process.exitCode down to DEGRADED's
|
|
289
|
+
* v1 projection (0) — turning a real declared failure
|
|
290
|
+
* (e.g. `state validate --strict` against a missing
|
|
291
|
+
* STATE.md, which sets process.exitCode = 1 directly)
|
|
292
|
+
* into a false success. `runMain` must never LOWER an
|
|
293
|
+
* exit code that main() itself already raised. DEGRADED
|
|
294
|
+
* still projects to 0 under v1 when nothing else set a
|
|
295
|
+
* code — the same value this arm produced before this
|
|
296
|
+
* phase by doing nothing — so v1 behavior is
|
|
297
|
+
* byte-identical for every caller that never sets its
|
|
298
|
+
* own exit code.
|
|
299
|
+
* thrown ExitError -> process.exitCode = err.code (+ stderr err.message if hasUserMessage && code!=0) (unchanged)
|
|
300
|
+
* other throw -> when json-error mode is active, emits structured { ok:false, reason, message }
|
|
301
|
+
* to stderr; otherwise writes raw stack trace. exit code = 1 in either case. (unchanged)
|
|
302
|
+
*/
|
|
303
|
+
function runMain(main) {
|
|
304
|
+
Promise.resolve()
|
|
305
|
+
.then(() => main())
|
|
306
|
+
.then((result) => {
|
|
307
|
+
// Cleared on EVERY branch below, not only the pending-cell-consuming
|
|
308
|
+
// void arm: an explicit number/string return means main() declared
|
|
309
|
+
// its own outcome and the cell (if anything set it earlier in this
|
|
310
|
+
// same invocation) is now stale — leaving it set would leak into the
|
|
311
|
+
// NEXT runMain call in this process, reintroducing the #3912 leak one
|
|
312
|
+
// level up. "Cleared on consumption" therefore means "consumption of
|
|
313
|
+
// this runMain call", not just "consumption of the pending value".
|
|
314
|
+
try {
|
|
315
|
+
if (typeof result === 'number') {
|
|
316
|
+
process.exitCode = result;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (typeof result === 'string') {
|
|
320
|
+
const projected = projectOutcome(result, getContractVersion());
|
|
321
|
+
// ADR-3889 §3: exit code 2 (the hook-protocol deny) may
|
|
322
|
+
// ONLY be produced by terminateNow, never by runMain. runMain is
|
|
323
|
+
// drain-then-exit; a deny drained this way can be truncated on
|
|
324
|
+
// Windows, which is exactly why terminateNow (write-then-terminate)
|
|
325
|
+
// exists. Gated on the PROJECTED code, not on the literal string
|
|
326
|
+
// `'HOOK_DENY'`, so a future registry rename that still resolves to
|
|
327
|
+
// this code cannot slip past the guard.
|
|
328
|
+
if (projected === HOOK_DENY_CODE) {
|
|
329
|
+
process.stderr.write(`runMain: refusing to exit with code ${HOOK_DENY_CODE} — outcome ${JSON.stringify(result)} `
|
|
330
|
+
+ `projects to the ${HOOK_DENY_NAME} exit code, which is reserved to terminateNow. `
|
|
331
|
+
+ `A hook-protocol deny must be delivered write-then-terminate via terminateNow(${JSON.stringify(result)}, payload), `
|
|
332
|
+
+ 'never drain-then-exit via runMain — a drained deny can be truncated on Windows. '
|
|
333
|
+
+ 'This is a caller bug: runMain must not be given a main() that returns HOOK_DENY.\n');
|
|
334
|
+
process.exitCode = exitCodeFor('INTERNAL');
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
process.exitCode = projected;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// result is undefined (void return): main declared no outcome itself.
|
|
341
|
+
// Fall back to the pending-outcome cell, if anything set it — but
|
|
342
|
+
// NEVER lower an exit code main() already raised on its own (see the
|
|
343
|
+
// precedence order in this function's doc comment above). Without
|
|
344
|
+
// this guard, a void-returning main() that set process.exitCode = 1
|
|
345
|
+
// directly (e.g. `state validate --strict` on a missing STATE.md)
|
|
346
|
+
// would have that 1 clobbered down to DEGRADED's v1 projection (0)
|
|
347
|
+
// by a payload-carried error the SAME call also recorded via
|
|
348
|
+
// io.cts's output() — a real failure silently reported as success.
|
|
349
|
+
const pending = getPendingOutcome();
|
|
350
|
+
if (typeof pending === 'string' && pending.length > 0 && !process.exitCode) {
|
|
351
|
+
process.exitCode = projectOutcome(pending, getContractVersion());
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
// Cell is consumed exactly once per runMain call regardless of which
|
|
356
|
+
// branch above ran (see the cell's own doc comment — "last
|
|
357
|
+
// declaration wins, cleared on consumption") — so a later `runMain`
|
|
358
|
+
// in the same process never inherits this one's declaration.
|
|
359
|
+
setPendingOutcome(undefined);
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
.catch((err) => {
|
|
363
|
+
if (err instanceof ExitError) {
|
|
364
|
+
if (err.hasUserMessage && err.code !== 0)
|
|
365
|
+
process.stderr.write(`${err.message}\n`);
|
|
366
|
+
process.exitCode = err.code;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (getJsonErrorMode()) {
|
|
370
|
+
const e = err;
|
|
371
|
+
const payload = JSON.stringify({
|
|
372
|
+
ok: false,
|
|
373
|
+
reason: EXIT_ENVELOPE_REASON,
|
|
374
|
+
message: (e && e.message) ? e.message : String(err),
|
|
375
|
+
}) + '\n';
|
|
376
|
+
node_fs_1.default.writeSync(2, payload);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const e = err;
|
|
380
|
+
process.stderr.write(`${e && e.stack ? e.stack : String(err)}\n`);
|
|
381
|
+
}
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Write `payload` fully to fd 1 (and, for a deny, fd 2 too) and terminate the
|
|
387
|
+
* process IMMEDIATELY with `outcome` projected through the current contract
|
|
388
|
+
* version. This is write-then-terminate, the other half of ADR-3889 §3's
|
|
389
|
+
* "two terminators over one registry": hooks fire from contexts (e.g. a
|
|
390
|
+
* `setTimeout` stdin-timeout guard) where `process.exitCode = N; return;`
|
|
391
|
+
* terminates nothing, so they need an immediate, synchronous exit — the
|
|
392
|
+
* exact gap `eslint.config.mjs:563-582` documents for `hooks/**`.
|
|
393
|
+
*
|
|
394
|
+
* This is THE ONLY sanctioned `process.exit` call site in the repo, and the
|
|
395
|
+
* only place exit code 2 can be produced: 2 is reserved to the hook-adapter
|
|
396
|
+
* protocol (ADR-3889 §1), and the registry's own one-owner rule already
|
|
397
|
+
* guarantees no other registered name resolves to it — the check below is a
|
|
398
|
+
* defense-in-depth assertion of that invariant, not the sole thing enforcing
|
|
399
|
+
* it.
|
|
400
|
+
*
|
|
401
|
+
* @param outcome - declared outcome name, projected via projectOutcome.
|
|
402
|
+
* @param payload - JSON-serializable value written to fd 1 (and, on a deny
|
|
403
|
+
* for which no `stderrPayload` is given, fd 2 too — this is the
|
|
404
|
+
* backward-compatible default every existing caller relies on).
|
|
405
|
+
* @param stderrPayload - optional, deny-only. When omitted (the default),
|
|
406
|
+
* fd 2 gets the SAME serialized `payload` fd 1 got — unchanged behavior.
|
|
407
|
+
* When provided, fd 2 gets THIS instead: a string is written raw
|
|
408
|
+
* (verbatim, not JSON-stringified), anything else is JSON-stringified
|
|
409
|
+
* like `payload`. This exists because `hooks/gsd-write-guard.js`'s
|
|
410
|
+
* emitBlock does NOT write the same bytes to both streams today — it
|
|
411
|
+
* writes the full JSON `output` to stdout but only the plain-text
|
|
412
|
+
* `output.reason` STRING to stderr, because Kimi's native hook bus reads
|
|
413
|
+
* stderr verbatim back to the model on exit 2. Migrating that call site
|
|
414
|
+
* onto terminateNow requires a way to say "fd 2 gets this different,
|
|
415
|
+
* plain-text value" — `stderrPayload` is that seam. Ignored entirely for
|
|
416
|
+
* a non-deny outcome: stderr is a deny-only channel.
|
|
417
|
+
*
|
|
418
|
+
* PAYLOAD-SIZE CONSTRAINT FOR CALLERS (measured for #3906, relevant to P7/
|
|
419
|
+
* #3911 wiring 19 enforcement hooks onto this function): the write-until-
|
|
420
|
+
* drained loop above delivers a payload whole regardless of size — verified
|
|
421
|
+
* up to 1MB (Node's own `spawnSync` default `maxBuffer`) with no truncation
|
|
422
|
+
* and no stall, both with a concurrently-draining async reader (~30ms for a
|
|
423
|
+
* 256KB payload) and with the default (internally-drained) pipe stdio a
|
|
424
|
+
* spawnSync-based test harness gets for free. Node's `spawnSync` does NOT
|
|
425
|
+
* suffer the classic "child blocks writing past the pipe buffer because
|
|
426
|
+
* nothing on the parent side is reading yet" deadlock some other languages'
|
|
427
|
+
* synchronous-subprocess primitives have; it drains stdout/stderr
|
|
428
|
+
* concurrently at the libuv layer while the child runs. The constraint that
|
|
429
|
+
* DOES bite on Linux is unrelated to pipe buffering: `execve(2)` enforces
|
|
430
|
+
* `MAX_ARG_STRLEN` (128KiB per single argv/envp string; see `man execve`
|
|
431
|
+
* NOTES) — so a CALLER that embeds a large literal payload directly into a
|
|
432
|
+
* spawned command line (e.g. `node -e "...<huge string>..."`) can fail to
|
|
433
|
+
* even start the child on Linux (macOS has no equivalent per-string cap),
|
|
434
|
+
* with no relation to this function's own behavior. See
|
|
435
|
+
* tests/cli-exit.test.cjs's "a large payload (bigger than a pipe buffer)
|
|
436
|
+
* arrives whole" test, which hit exactly this constructing its own fixture
|
|
437
|
+
* before being rewritten to build the payload inside the child instead.
|
|
438
|
+
*/
|
|
439
|
+
function terminateNow(outcome, payload, stderrPayload) {
|
|
440
|
+
// terminateNow is total by construction: its callers are enforcement hooks
|
|
441
|
+
// (P7/#3911, 19 of them) whose OWN outer catch may fail open (some end in
|
|
442
|
+
// `process.exit(0)`). If resolving the contract version, projecting the
|
|
443
|
+
// outcome, or the HOOK_DENY-collision guard below threw and that throw
|
|
444
|
+
// propagated out of this function, it would unwind straight into that
|
|
445
|
+
// caller's catch — turning a deny into a silent allow, exactly the defect
|
|
446
|
+
// ADR-3889 exists to close. So every one of those steps is wrapped here:
|
|
447
|
+
// on ANY failure this still terminates, deterministically, with INTERNAL
|
|
448
|
+
// (never by returning or re-throwing) — a malformed call is a programming
|
|
449
|
+
// error to be diagnosed on stderr, not a reason to hand control back.
|
|
450
|
+
let versionForDiagnostics = '(unresolved)';
|
|
451
|
+
try {
|
|
452
|
+
const version = getContractVersion();
|
|
453
|
+
versionForDiagnostics = version;
|
|
454
|
+
const projected = projectOutcome(outcome, version);
|
|
455
|
+
if (projected === HOOK_DENY_CODE && outcome !== HOOK_DENY_NAME) {
|
|
456
|
+
throw new Error(`terminateNow: exit code ${HOOK_DENY_CODE} is reserved to the ${HOOK_DENY_NAME} outcome; `
|
|
457
|
+
+ `got outcome ${JSON.stringify(outcome)}`);
|
|
458
|
+
}
|
|
459
|
+
// m2 (round 5, hooks/gsd-write-guard.js:159-175): emission must itself be
|
|
460
|
+
// exception-safe. A failed write (EPIPE, a full pipe buffer, a throwing
|
|
461
|
+
// fs.writeSync in a test) must NOT change the exit code — if it propagated
|
|
462
|
+
// out of this function, a caller whose payload could not be delivered
|
|
463
|
+
// would fall into ITS OWN outer catch and fail OPEN, which is the exact
|
|
464
|
+
// outcome the fail-closed branches this function serves exist to prevent.
|
|
465
|
+
// The decision to terminate with `projected` stands regardless of whether
|
|
466
|
+
// the payload could be delivered.
|
|
467
|
+
//
|
|
468
|
+
// The two streams are emitted in TWO SEPARATE try/catch blocks, not one
|
|
469
|
+
// shared block (the pre-#3911 defect): fd 1 and fd 2 (deny-only) are
|
|
470
|
+
// independent channels with independent failure modes, and a shared try
|
|
471
|
+
// meant a serialization failure on fd 1 (e.g. `payload` throwing on
|
|
472
|
+
// JSON.stringify) aborted the block before fd 2 ever ran — silently
|
|
473
|
+
// dropping a deny's reason. `deny(undefined, 'some reason')` used to exit
|
|
474
|
+
// 2 with EMPTY stderr because of exactly this. Each block independently
|
|
475
|
+
// treats an `undefined` value for ITS OWN stream as "nothing to write"
|
|
476
|
+
// and skips the write cleanly, rather than serializing `undefined` (which
|
|
477
|
+
// is not valid JSON text) and throwing into the catch.
|
|
478
|
+
try {
|
|
479
|
+
// fs.writeSync, never process.stdout.write: pipe writes via
|
|
480
|
+
// process.stdout/stderr are async on Windows, and process.exit() below
|
|
481
|
+
// does not wait for them to flush — a truncated payload is a silent
|
|
482
|
+
// half-emission. Looped over a Buffer (not a bare string call) so a
|
|
483
|
+
// payload larger than the destination pipe's buffer — where a single
|
|
484
|
+
// write() syscall can legitimately return fewer bytes written than
|
|
485
|
+
// requested — still arrives whole rather than truncated.
|
|
486
|
+
if (payload !== undefined) {
|
|
487
|
+
const buf = Buffer.from(JSON.stringify(payload), 'utf8');
|
|
488
|
+
let offset = 0;
|
|
489
|
+
while (offset < buf.length) {
|
|
490
|
+
offset += node_fs_1.default.writeSync(1, buf, offset, buf.length - offset);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
// fd 1 emission failed; the exit code decision still stands (see
|
|
496
|
+
// above), and fd 2 below is unaffected — it has its own try/catch.
|
|
497
|
+
}
|
|
498
|
+
if (projected === HOOK_DENY_CODE) {
|
|
499
|
+
try {
|
|
500
|
+
// Backward-compatible default: when no `stderrPayload` is given, fd 2
|
|
501
|
+
// gets the SAME value fd 1 got (still subject to fd 2's own
|
|
502
|
+
// undefined-skips-the-write and string-vs-JSON rules below).
|
|
503
|
+
const resolvedStderr = stderrPayload === undefined ? payload : stderrPayload;
|
|
504
|
+
if (resolvedStderr !== undefined) {
|
|
505
|
+
const stderrBuf = Buffer.from(typeof resolvedStderr === 'string' ? resolvedStderr : JSON.stringify(resolvedStderr), 'utf8');
|
|
506
|
+
let stderrOffset = 0;
|
|
507
|
+
while (stderrOffset < stderrBuf.length) {
|
|
508
|
+
stderrOffset += node_fs_1.default.writeSync(2, stderrBuf, stderrOffset, stderrBuf.length - stderrOffset);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
// fd 2 emission failed; independent of fd 1 above, and the exit code
|
|
514
|
+
// decision still stands regardless.
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// n/no-process-exit is not registered for src/**/*.cts (see the ADR-3889
|
|
518
|
+
// reference note in the module header) and both compiled .cjs copies of
|
|
519
|
+
// this module are lint-ignored build/generated artifacts, so no disable
|
|
520
|
+
// directive is needed here for the one sanctioned process.exit call site.
|
|
521
|
+
process.exit(projected);
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
// Anything above threw: an unrecognized --exit-contract/GSD_EXIT_CONTRACT
|
|
525
|
+
// value, a non-string/empty/unregistered `outcome`, or the HOOK_DENY
|
|
526
|
+
// collision guard. Diagnose on stderr — swallowing this silently would
|
|
527
|
+
// make a typo'd outcome name or a bad contract-version env var
|
|
528
|
+
// undebuggable — then terminate unconditionally. The diagnostic write
|
|
529
|
+
// itself gets its own swallow-on-failure guard, because even a failed
|
|
530
|
+
// diagnostic must not stop the exit below from happening.
|
|
531
|
+
try {
|
|
532
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
533
|
+
const message = `terminateNow: programming error — outcome=${JSON.stringify(outcome)} `
|
|
534
|
+
+ `version=${JSON.stringify(versionForDiagnostics)}: ${detail}\n`
|
|
535
|
+
+ `This is a caller bug (unrecognized outcome/exit-contract, or the HOOK_DENY collision `
|
|
536
|
+
+ `guard), not a declared outcome. Terminating with INTERNAL rather than propagating: an `
|
|
537
|
+
+ `enforcement-hook caller's own outer catch may fail open (process.exit(0)), and unwinding `
|
|
538
|
+
+ `into it here would silently convert a deny into an allow.\n`;
|
|
539
|
+
node_fs_1.default.writeSync(2, message);
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
// Diagnostic emission itself failed; the exit below is unconditional
|
|
543
|
+
// regardless.
|
|
544
|
+
}
|
|
545
|
+
process.exit(exitCodeFor('INTERNAL'));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
module.exports = {
|
|
549
|
+
ExitError,
|
|
550
|
+
runMain,
|
|
551
|
+
setJsonErrorMode,
|
|
552
|
+
getJsonErrorMode,
|
|
553
|
+
EXIT_ENVELOPE_REASON,
|
|
554
|
+
projectOutcome,
|
|
555
|
+
resolveContractVersion,
|
|
556
|
+
getContractVersion,
|
|
557
|
+
terminateNow,
|
|
558
|
+
setPendingOutcome,
|
|
559
|
+
getPendingOutcome,
|
|
560
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// GENERATED FILE — DO NOT EDIT BY HAND.
|
|
4
|
+
// Source of truth: gsd-core/bin/shared/exit-codes.json. Regenerate with:
|
|
5
|
+
// node scripts/gen-exit-code-registry.cjs --write
|
|
6
|
+
// This exact content is emitted to THREE locations — gsd-core/bin/lib/exit-code-registry.cjs,
|
|
7
|
+
// scripts/lib/exit-code-registry.cjs, and hooks/lib/exit-code-registry.js (the latter two
|
|
8
|
+
// committed so scripts/ and hooks/ consumers work on an unbuilt clone) — all byte-compared by
|
|
9
|
+
// `npm run lint:generated-sync` (#3905 ADR-3889 Phase 1; #3906 Phase 2 added the second copy;
|
|
10
|
+
// #3911 ADR-3889 Phase 7 added the hooks/lib/ copy).
|
|
11
|
+
//
|
|
12
|
+
// exitCodeFor(name) / nameForExitCode(code) are pure and total over this
|
|
13
|
+
// closed table — each throws for anything not registered here.
|
|
14
|
+
|
|
15
|
+
const EXIT_CODES = Object.freeze([
|
|
16
|
+
Object.freeze({
|
|
17
|
+
code: 2,
|
|
18
|
+
name: "HOOK_DENY",
|
|
19
|
+
meaning: "Hook protocol deny — the harness blocks the tool call",
|
|
20
|
+
owner: "hook-adapter",
|
|
21
|
+
authorizedBy: "ADR-3889",
|
|
22
|
+
}),
|
|
23
|
+
Object.freeze({
|
|
24
|
+
code: 64,
|
|
25
|
+
name: "USAGE",
|
|
26
|
+
meaning: "Caller error — bad argv, unknown subcommand, missing argument",
|
|
27
|
+
owner: "generic",
|
|
28
|
+
authorizedBy: "ADR-3889",
|
|
29
|
+
}),
|
|
30
|
+
Object.freeze({
|
|
31
|
+
code: 66,
|
|
32
|
+
name: "NO_INPUT",
|
|
33
|
+
meaning: "Ran; zero units were in scope, and that emptiness is known to be genuine",
|
|
34
|
+
owner: "generic",
|
|
35
|
+
authorizedBy: "ADR-3889",
|
|
36
|
+
}),
|
|
37
|
+
Object.freeze({
|
|
38
|
+
code: 69,
|
|
39
|
+
name: "UNAVAILABLE",
|
|
40
|
+
meaning: "Could not run — prerequisite absent, input unreadable, scope unestablished",
|
|
41
|
+
owner: "generic",
|
|
42
|
+
authorizedBy: "ADR-3889",
|
|
43
|
+
}),
|
|
44
|
+
Object.freeze({
|
|
45
|
+
code: 70,
|
|
46
|
+
name: "INTERNAL",
|
|
47
|
+
meaning: "Self-failure — crash, timeout, killed subprocess",
|
|
48
|
+
owner: "generic",
|
|
49
|
+
authorizedBy: "ADR-3889",
|
|
50
|
+
}),
|
|
51
|
+
Object.freeze({
|
|
52
|
+
code: 80,
|
|
53
|
+
name: "DEGRADED",
|
|
54
|
+
meaning: "Ran to completion and is reporting a condition through its result payload rather than as a process failure",
|
|
55
|
+
owner: "gsd-tools",
|
|
56
|
+
authorizedBy: "ADR-3889 + ADR-2980",
|
|
57
|
+
})
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const NAME_TO_CODE = new Map(EXIT_CODES.map((entry) => [entry.name, entry.code]));
|
|
61
|
+
const CODE_TO_NAME = new Map(EXIT_CODES.map((entry) => [entry.code, entry.name]));
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the registered exit code for a symbolic name. Pure, total: throws
|
|
65
|
+
* for anything not an exact, registered, exact-case key — including
|
|
66
|
+
* non-strings, the empty string, untrimmed strings, wrong case, and
|
|
67
|
+
* prototype-chain names like `__proto__`/`constructor`/`toString` (a Map
|
|
68
|
+
* lookup never touches the prototype chain, so these are indistinguishable
|
|
69
|
+
* from any other unregistered name).
|
|
70
|
+
*
|
|
71
|
+
* @param {string} name
|
|
72
|
+
* @returns {number}
|
|
73
|
+
*/
|
|
74
|
+
function exitCodeFor(name) {
|
|
75
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
76
|
+
throw new Error(`exitCodeFor: name must be a non-empty string, received ${JSON.stringify(name)}`);
|
|
77
|
+
}
|
|
78
|
+
if (!NAME_TO_CODE.has(name)) {
|
|
79
|
+
throw new Error(`exitCodeFor: unregistered exit code name: ${JSON.stringify(name)}`);
|
|
80
|
+
}
|
|
81
|
+
return NAME_TO_CODE.get(name);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reverse of exitCodeFor: resolve the symbolic name for a registered exit
|
|
86
|
+
* code. Pure, total: throws for anything not an exact, registered code.
|
|
87
|
+
*
|
|
88
|
+
* @param {number} code
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function nameForExitCode(code) {
|
|
92
|
+
if (!CODE_TO_NAME.has(code)) {
|
|
93
|
+
throw new Error(`nameForExitCode: unregistered exit code: ${JSON.stringify(code)}`);
|
|
94
|
+
}
|
|
95
|
+
return CODE_TO_NAME.get(code);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { EXIT_CODES, exitCodeFor, nameForExitCode };
|