@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
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gen-hooks-cli-exit.cjs — generates hooks/lib/cli-exit.js from a fresh
|
|
4
|
+
* compile of src/cli-exit.cts.
|
|
5
|
+
*
|
|
6
|
+
* ADR-3889 Phase 7 (#3911): shipped hooks must be able to terminate through
|
|
7
|
+
* `terminateNow` WITHOUT depending on any build artifact. hooks/ runs
|
|
8
|
+
* straight from a raw, unbuilt clone (a hook is required by name via
|
|
9
|
+
* `require('./lib/cli-exit.js')` relative to the hook's own __dirname), so
|
|
10
|
+
* the generated file is compiled to a THROWAWAY outDir rather than read from
|
|
11
|
+
* gsd-core/bin/lib/ — reading the tracked build output would let a stale
|
|
12
|
+
* build produce a false green. This mirrors scripts/gen-scripts-cli-exit.cjs
|
|
13
|
+
* exactly (same compile-to-temp strategy, same banner/check/write shape);
|
|
14
|
+
* kept as a SIBLING script rather than folded into that one because
|
|
15
|
+
* gen-scripts-cli-exit.cjs is hard-coded to a single output path/extension
|
|
16
|
+
* (`scripts/lib/cli-exit.cjs`) throughout its BANNER prose and REASON
|
|
17
|
+
* messaging — parameterizing it for a second, differently-extensioned
|
|
18
|
+
* target (`.js`, not `.cjs`, to match the hooks/lib/*.js convention) would
|
|
19
|
+
* tangle a script that is otherwise simple and single-purpose.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* node scripts/gen-hooks-cli-exit.cjs # same as --write
|
|
23
|
+
* node scripts/gen-hooks-cli-exit.cjs --write # write hooks/lib/cli-exit.js
|
|
24
|
+
* node scripts/gen-hooks-cli-exit.cjs --check # exit 1 if committed file is stale
|
|
25
|
+
* node scripts/gen-hooks-cli-exit.cjs --out <path> # override the output path (default: hooks/lib/cli-exit.js) — honoured by BOTH --write and --check
|
|
26
|
+
*
|
|
27
|
+
* `--out` restores parity with the sibling scripts/gen-exit-code-registry.cjs,
|
|
28
|
+
* which already supports `--out`/`--scripts-out`/`--hooks-out`/`--dts-out`/
|
|
29
|
+
* `--sh-out` overrides honoured by its own `--check`. This script hardcoding
|
|
30
|
+
* `OUTPUT_PATH` with no override was an inconsistency between two sibling
|
|
31
|
+
* generators, not an intentionally narrower surface — tests need to redirect
|
|
32
|
+
* `--check` at a disposable tmpdir copy instead of corrupting the real
|
|
33
|
+
* committed artifact in place.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
'use strict';
|
|
37
|
+
|
|
38
|
+
const { execFileSync } = require('node:child_process');
|
|
39
|
+
const fs = require('node:fs');
|
|
40
|
+
const os = require('node:os');
|
|
41
|
+
const path = require('node:path');
|
|
42
|
+
|
|
43
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
44
|
+
const OUTPUT_PATH = path.join(REPO_ROOT, 'hooks', 'lib', 'cli-exit.js');
|
|
45
|
+
const COMPILE_TIMEOUT_MS = 60_000;
|
|
46
|
+
|
|
47
|
+
/** Frozen reason codes so tests assert on structure, not prose. */
|
|
48
|
+
const REASON = Object.freeze({
|
|
49
|
+
OK: 'ok_generated_sync',
|
|
50
|
+
DRIFTED: 'fail_generated_drifted',
|
|
51
|
+
BUILD_FAILED: 'fail_build_failed',
|
|
52
|
+
MISSING_EMIT: 'fail_missing_emit',
|
|
53
|
+
USAGE: 'fail_usage',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const USAGE_MESSAGE = [
|
|
57
|
+
'Usage: node scripts/gen-hooks-cli-exit.cjs [--write|--check] [--out <path>]',
|
|
58
|
+
' (no flag) same as --write',
|
|
59
|
+
' --write write hooks/lib/cli-exit.js',
|
|
60
|
+
' --check exit 1 if the committed file is stale',
|
|
61
|
+
' --out override the output artifact path (default: hooks/lib/cli-exit.js)',
|
|
62
|
+
].join('\n');
|
|
63
|
+
|
|
64
|
+
const BANNER = [
|
|
65
|
+
'// GENERATED FILE — DO NOT EDIT BY HAND.',
|
|
66
|
+
'// Source of truth: src/cli-exit.cts. Regenerate with:',
|
|
67
|
+
'// node scripts/gen-hooks-cli-exit.cjs --write',
|
|
68
|
+
'// Byte-compared by `npm run lint:generated-sync` (#3911, ADR-3889 Phase 7).',
|
|
69
|
+
'//',
|
|
70
|
+
'// Why this copy exists: hooks/ runs straight from a raw, unbuilt clone — a',
|
|
71
|
+
'// shipped hook must be able to `require(\'./lib/cli-exit.js\')` relative to',
|
|
72
|
+
'// its own __dirname and terminate through `terminateNow` without depending',
|
|
73
|
+
'// on any build artifact. gsd-core/bin/lib/cli-exit.cjs is gitignored tsc',
|
|
74
|
+
'// output and doubles as the build sentinel, so it cannot be required from',
|
|
75
|
+
'// here. `.js`, not `.cjs`, to match the hooks/lib/*.js convention. Hence one',
|
|
76
|
+
'// source, three emitted locations (gsd-core/bin/lib, scripts/lib, hooks/lib).',
|
|
77
|
+
'',
|
|
78
|
+
'',
|
|
79
|
+
].join('\n');
|
|
80
|
+
|
|
81
|
+
/** Compile the whole project to a throwaway outDir so the work tree is untouched. */
|
|
82
|
+
function compileToTemp() {
|
|
83
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-hooks-cli-exit-'));
|
|
84
|
+
try {
|
|
85
|
+
execFileSync(
|
|
86
|
+
process.execPath,
|
|
87
|
+
[
|
|
88
|
+
path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'),
|
|
89
|
+
'-p', path.join(REPO_ROOT, 'tsconfig.build.json'),
|
|
90
|
+
'--outDir', tmp,
|
|
91
|
+
// A throwaway outDir must not reuse the in-tree incremental state, or
|
|
92
|
+
// tsc skips emit for files it believes are already current.
|
|
93
|
+
'--incremental', 'false',
|
|
94
|
+
'--tsBuildInfoFile', 'null',
|
|
95
|
+
],
|
|
96
|
+
{ cwd: REPO_ROOT, encoding: 'utf8', stdio: 'pipe', timeout: COMPILE_TIMEOUT_MS },
|
|
97
|
+
);
|
|
98
|
+
return { ok: true, dir: tmp };
|
|
99
|
+
} catch (err) {
|
|
100
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
101
|
+
const detail = [err.stdout, err.stderr].filter(Boolean).join('\n').trim();
|
|
102
|
+
return { ok: false, detail };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Compile src/cli-exit.cts to a throwaway outDir and return the expected
|
|
108
|
+
* generated content (banner + compiled bytes, with the sibling registry
|
|
109
|
+
* require rewritten to the `.js` extension hooks/lib/ uses), or a failure
|
|
110
|
+
* descriptor.
|
|
111
|
+
*
|
|
112
|
+
* @returns {{ ok: true, content: string } | { ok: false, reason: string, detail?: string }}
|
|
113
|
+
*/
|
|
114
|
+
function buildExpectedContent() {
|
|
115
|
+
const build = compileToTemp();
|
|
116
|
+
if (!build.ok) {
|
|
117
|
+
return { ok: false, reason: REASON.BUILD_FAILED, detail: build.detail };
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const emitted = path.join(build.dir, 'cli-exit.cjs');
|
|
121
|
+
if (!fs.existsSync(emitted)) {
|
|
122
|
+
return { ok: false, reason: REASON.MISSING_EMIT, detail: `no emit produced at ${emitted} from src/cli-exit.cts` };
|
|
123
|
+
}
|
|
124
|
+
const compiled = fs.readFileSync(emitted, 'utf8');
|
|
125
|
+
// hooks/lib/ ships `.js`, not `.cjs` (matching the hooks/lib/*.js
|
|
126
|
+
// convention — see the module header), so the sibling registry require
|
|
127
|
+
// tsc emits for src/cli-exit.cts's `require('./exit-code-registry.cjs')`
|
|
128
|
+
// must be rewritten to resolve the `.js` copy scripts/gen-exit-code-
|
|
129
|
+
// registry.cjs emits alongside this file, not the `.cjs` one. A literal,
|
|
130
|
+
// anchored replace (never a regex) so this can only ever touch the exact
|
|
131
|
+
// string tsc is known to emit for that one import.
|
|
132
|
+
const REGISTRY_REQUIRE_CJS = 'require("./exit-code-registry.cjs")';
|
|
133
|
+
const REGISTRY_REQUIRE_JS = 'require("./exit-code-registry.js")';
|
|
134
|
+
if (!compiled.includes(REGISTRY_REQUIRE_CJS)) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
reason: REASON.MISSING_EMIT,
|
|
138
|
+
detail: `expected compiled output to contain ${REGISTRY_REQUIRE_CJS} (the sibling registry require) — tsc's emit shape may have changed`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const rewritten = compiled.split(REGISTRY_REQUIRE_CJS).join(REGISTRY_REQUIRE_JS);
|
|
142
|
+
return { ok: true, content: BANNER + rewritten };
|
|
143
|
+
} finally {
|
|
144
|
+
fs.rmSync(build.dir, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function doWrite(outPath) {
|
|
149
|
+
const result = buildExpectedContent();
|
|
150
|
+
if (!result.ok) {
|
|
151
|
+
console.error(`FAIL gen-hooks-cli-exit: ${result.reason}`);
|
|
152
|
+
if (result.detail) console.error(result.detail);
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
156
|
+
fs.writeFileSync(outPath, result.content, 'utf8');
|
|
157
|
+
console.log(`ok gen-hooks-cli-exit: wrote ${path.relative(REPO_ROOT, outPath)}`);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function doCheck(outPath) {
|
|
162
|
+
const result = buildExpectedContent();
|
|
163
|
+
if (!result.ok) {
|
|
164
|
+
console.error(`FAIL gen-hooks-cli-exit: ${result.reason}`);
|
|
165
|
+
if (result.detail) console.error(result.detail);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!fs.existsSync(outPath)) {
|
|
170
|
+
console.error(`FAIL gen-hooks-cli-exit: ${REASON.MISSING_EMIT}`);
|
|
171
|
+
console.error(` ${path.relative(REPO_ROOT, outPath)} does not exist. Run:`);
|
|
172
|
+
console.error(' node scripts/gen-hooks-cli-exit.cjs --write');
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const committed = fs.readFileSync(outPath, 'utf8');
|
|
177
|
+
if (committed !== result.content) {
|
|
178
|
+
console.error(`FAIL gen-hooks-cli-exit: ${REASON.DRIFTED}`);
|
|
179
|
+
console.error(
|
|
180
|
+
` ${path.relative(REPO_ROOT, outPath)} (${committed.length} bytes) != ` +
|
|
181
|
+
`compile of src/cli-exit.cts (${result.content.length} bytes)`,
|
|
182
|
+
);
|
|
183
|
+
console.error('');
|
|
184
|
+
console.error('Regenerate with:');
|
|
185
|
+
console.error(' node scripts/gen-hooks-cli-exit.cjs --write');
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(`ok gen-hooks-cli-exit: ${path.relative(REPO_ROOT, outPath)} matches src/cli-exit.cts`);
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @returns {{mode:'write'|'check', outPath:?string}}
|
|
195
|
+
*/
|
|
196
|
+
function parseArgs(argv) {
|
|
197
|
+
let mode = null;
|
|
198
|
+
let outPath = null;
|
|
199
|
+
|
|
200
|
+
for (let i = 0; i < argv.length; i++) {
|
|
201
|
+
const arg = argv[i];
|
|
202
|
+
if (arg === '--write' || arg === '--check') {
|
|
203
|
+
if (mode !== null) {
|
|
204
|
+
throw new Error(`conflicting mode flags: --${mode} and ${arg}`);
|
|
205
|
+
}
|
|
206
|
+
mode = arg === '--write' ? 'write' : 'check';
|
|
207
|
+
} else if (arg === '--out') {
|
|
208
|
+
const value = argv[++i];
|
|
209
|
+
if (value === undefined) throw new Error('--out requires a value');
|
|
210
|
+
outPath = value;
|
|
211
|
+
} else if (arg.startsWith('--out=')) {
|
|
212
|
+
outPath = arg.slice('--out='.length);
|
|
213
|
+
} else {
|
|
214
|
+
throw new Error(`unrecognized argument: ${arg}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { mode: mode || 'write', outPath };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function main() {
|
|
222
|
+
let args;
|
|
223
|
+
try {
|
|
224
|
+
args = parseArgs(process.argv.slice(2));
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.error(`FAIL gen-hooks-cli-exit: ${REASON.USAGE}`);
|
|
227
|
+
console.error(` ${err.message}`);
|
|
228
|
+
console.error(USAGE_MESSAGE);
|
|
229
|
+
return 1;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const outPath = args.outPath || OUTPUT_PATH;
|
|
233
|
+
|
|
234
|
+
return args.mode === 'check' ? doCheck(outPath) : doWrite(outPath);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (require.main === module) process.exitCode = main();
|
|
238
|
+
|
|
239
|
+
module.exports = { REASON, buildExpectedContent, OUTPUT_PATH, BANNER };
|
|
@@ -21,7 +21,7 @@ const ROOT = path.resolve(__dirname, '..');
|
|
|
21
21
|
// buildParityManifest) is the canonical single source of truth in
|
|
22
22
|
// tests/helpers/install-shared.cjs (issue #2266/#2267) — this generator does
|
|
23
23
|
// not keep its own inline copy of the walk/exclusion logic.
|
|
24
|
-
const { runMinimalInstall, RUNTIME_META, buildInstallTree, BUILD_SCRIPT } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
|
|
24
|
+
const { runMinimalInstall, RUNTIME_META, buildInstallTree, extraEmitRootsFor, BUILD_SCRIPT } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
|
|
25
25
|
const FIXTURE_DIR = path.join(ROOT, 'tests', 'fixtures', 'install-tree');
|
|
26
26
|
|
|
27
27
|
function cleanup(root) {
|
|
@@ -51,7 +51,7 @@ for (const runtime of targets) {
|
|
|
51
51
|
const { configDir, root } = runMinimalInstall({ runtime, scope: 'global' });
|
|
52
52
|
let actual;
|
|
53
53
|
try {
|
|
54
|
-
actual = buildInstallTree(configDir, root);
|
|
54
|
+
actual = buildInstallTree(configDir, root, extraEmitRootsFor(runtime, 'global', root));
|
|
55
55
|
} finally {
|
|
56
56
|
cleanup(root);
|
|
57
57
|
}
|
|
@@ -30,7 +30,13 @@ const CONTRACT_PATH = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'loop-host-contr
|
|
|
30
30
|
// The five step workflows in pipeline order
|
|
31
31
|
const STEP_WORKFLOWS = [
|
|
32
32
|
{ file: 'discuss-phase.md', step: 'discuss' },
|
|
33
|
-
{
|
|
33
|
+
{
|
|
34
|
+
file: 'plan-phase.md',
|
|
35
|
+
step: 'plan',
|
|
36
|
+
auxiliaryHosts: [
|
|
37
|
+
{ file: 'quick.md', point: 'plan:pre', kinds: ['contribution'], into: 'planner' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
34
40
|
{ file: 'execute-phase.md', step: 'execute' },
|
|
35
41
|
{ file: 'verify-work.md', step: 'verify' },
|
|
36
42
|
{ file: 'ship.md', step: 'ship' },
|
|
@@ -295,7 +301,7 @@ function buildContract(workflowsDir) {
|
|
|
295
301
|
const contract = [];
|
|
296
302
|
const allErrors = [];
|
|
297
303
|
|
|
298
|
-
for (const { file, step } of STEP_WORKFLOWS) {
|
|
304
|
+
for (const { file, step, auxiliaryHosts = [] } of STEP_WORKFLOWS) {
|
|
299
305
|
const filePath = path.join(resolvedDir, file);
|
|
300
306
|
let content;
|
|
301
307
|
try {
|
|
@@ -325,6 +331,34 @@ function buildContract(workflowsDir) {
|
|
|
325
331
|
const roleErrors = crossCheckRoles(content, entry.agentRoles, file);
|
|
326
332
|
allErrors.push(...roleErrors);
|
|
327
333
|
|
|
334
|
+
for (const auxiliary of auxiliaryHosts) {
|
|
335
|
+
let auxiliaryContent;
|
|
336
|
+
try {
|
|
337
|
+
auxiliaryContent = fs.readFileSync(path.join(resolvedDir, auxiliary.file), 'utf8');
|
|
338
|
+
} catch (err) {
|
|
339
|
+
allErrors.push('Could not read auxiliary host ' + auxiliary.file + ': ' + String(err.message));
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const wiredPoints = scanWiredPoints(auxiliaryContent);
|
|
344
|
+
if (!wiredPoints.has(auxiliary.point)) {
|
|
345
|
+
allErrors.push(
|
|
346
|
+
auxiliary.file + ': auxiliary host missing expected point "' + auxiliary.point + '"',
|
|
347
|
+
);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const wiredKinds = scanWiredKinds(auxiliaryContent, auxiliary.into).get(auxiliary.point) || new Set();
|
|
352
|
+
for (const kind of auxiliary.kinds) {
|
|
353
|
+
if (!wiredKinds.has(kind)) {
|
|
354
|
+
allErrors.push(
|
|
355
|
+
auxiliary.file + ': auxiliary host point "' + auxiliary.point +
|
|
356
|
+
'" missing expected kind "' + kind + '"',
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
328
362
|
contract.push(entry);
|
|
329
363
|
}
|
|
330
364
|
|
|
@@ -437,7 +471,10 @@ function main() {
|
|
|
437
471
|
* registry generator, conformance gate) must derive from this rather than maintaining
|
|
438
472
|
* a separate hardcoded list.
|
|
439
473
|
*/
|
|
440
|
-
const HOST_LOOP_FILES = STEP_WORKFLOWS.
|
|
474
|
+
const HOST_LOOP_FILES = STEP_WORKFLOWS.flatMap(({ file, auxiliaryHosts = [] }) => [
|
|
475
|
+
'gsd-core/workflows/' + file,
|
|
476
|
+
...auxiliaryHosts.map((host) => 'gsd-core/workflows/' + host.file),
|
|
477
|
+
]);
|
|
441
478
|
|
|
442
479
|
/**
|
|
443
480
|
* Pure function: scan a text string for `loop render-hooks <point>` call sites.
|
|
@@ -446,8 +483,11 @@ const HOST_LOOP_FILES = STEP_WORKFLOWS.map((w) => 'gsd-core/workflows/' + w.file
|
|
|
446
483
|
* @param {string} text Content of a workflow file (or any text).
|
|
447
484
|
* @returns {Set<string>}
|
|
448
485
|
*/
|
|
486
|
+
/** The call-site shape both scanners key on — one regex, two consumers (#3606). */
|
|
487
|
+
const CALL_SITE_RE = /loop render-hooks\s+([a-z:]+)/g;
|
|
488
|
+
|
|
449
489
|
function scanWiredPoints(text) {
|
|
450
|
-
const re =
|
|
490
|
+
const re = CALL_SITE_RE;
|
|
451
491
|
const result = new Set();
|
|
452
492
|
let m;
|
|
453
493
|
while ((m = re.exec(text)) !== null) {
|
|
@@ -456,6 +496,147 @@ function scanWiredPoints(text) {
|
|
|
456
496
|
return result;
|
|
457
497
|
}
|
|
458
498
|
|
|
499
|
+
// ─── Hook-kind coverage (#3606) ──────────────────────────────────────────────
|
|
500
|
+
|
|
501
|
+
const HOOK_KINDS = ['contribution', 'step', 'gate'];
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* The kinds one call site's dispatch text actually covers (#3606).
|
|
505
|
+
*
|
|
506
|
+
* A point having a `loop render-hooks <point>` call site proves the hooks are
|
|
507
|
+
* RENDERED, not that they are DISPATCHED — a consumer that iterates only
|
|
508
|
+
* `kind == "gate"` (or narrows `kind == "step"` to one `ref.skill`) silently
|
|
509
|
+
* drops every other registered kind. Coverage rules for the text following a
|
|
510
|
+
* call site, up to the next call site or the region cap, judged LINE by line:
|
|
511
|
+
*
|
|
512
|
+
* - A deferral line (one carrying an `@`-included path to the generic
|
|
513
|
+
* contract, e.g. `@gsd-core/references/loop-hook-dispatch.md`) that names a
|
|
514
|
+
* kind (`kind == "step"`) covers that kind; a deferral line with no kind
|
|
515
|
+
* discriminator ("apply each entry") covers every kind only when no role
|
|
516
|
+
* target is required. With `expectedInto`, the same segment must explicitly
|
|
517
|
+
* name both the kind and that exact `into` target. A bare §-citation of the
|
|
518
|
+
* reference (validation guidance only, no `@`) covers nothing — plan-phase
|
|
519
|
+
* cites the gate-validation section while dispatching only gates.
|
|
520
|
+
* - Otherwise a kind is covered when some LINE dispatches it unconditionally:
|
|
521
|
+
* a `kind == "<kind>"` discriminator with NO same-line narrowing to one
|
|
522
|
+
* hook (`ref.skill ==`, `ref.agent ==`, `ref.command ==`). A narrowed line
|
|
523
|
+
* special-cases ONE hook and proves nothing about the kind generally — the
|
|
524
|
+
* exact hand-rolled-consumer shape the reference warns about.
|
|
525
|
+
*
|
|
526
|
+
* Quote style and spacing vary across the corpus (`kind == "step"`,
|
|
527
|
+
* `kind === 'gate'`), so the matcher is tolerant of both quote characters and
|
|
528
|
+
* of `==`/`===`.
|
|
529
|
+
*
|
|
530
|
+
* Pure: same input, same output; CRLF-safe (line splitting tolerates \r).
|
|
531
|
+
*
|
|
532
|
+
* @param {string} region Dispatch text following one call site.
|
|
533
|
+
* @param {string} [expectedInto] Optional role target required in the same segment.
|
|
534
|
+
* @returns {Set<string>}
|
|
535
|
+
*/
|
|
536
|
+
function coveredKindsInRegion(region, expectedInto) {
|
|
537
|
+
const covered = new Set();
|
|
538
|
+
// Same-SEGMENT narrowing to ONE hook voids credit: `ref.skill ==`, `capId ==`,
|
|
539
|
+
// and `into ==` each special-case a subset, not the kind generally. An
|
|
540
|
+
// auxiliary host may name the one role it actually hosts via `expectedInto`;
|
|
541
|
+
// any other role target still voids credit.
|
|
542
|
+
// (plan-phase's `kind == "contribution" and capId == "security"` is the
|
|
543
|
+
// hand-rolled shape; `into == "planner"` covers only planner-targeted
|
|
544
|
+
// contributions). Segments, not lines: execute-phase legitimately writes
|
|
545
|
+
// "dispatch `kind == "step"` hooks per … . `ref.skill == "code-review"`:" —
|
|
546
|
+
// the deferral is one sentence, the specialization the next; narrowing in a
|
|
547
|
+
// DIFFERENT segment must not void the deferral's credit.
|
|
548
|
+
const identityNarrowingRe = /(?:ref\.(?:skill|agent|command)|capId)\s*={2,3}/;
|
|
549
|
+
const intoNarrowingRe = /into\s*={2,3}/;
|
|
550
|
+
const expectedIntoRe = expectedInto
|
|
551
|
+
? new RegExp(`into\\s*={2,3}\\s*["']${escapeRegExp(expectedInto)}["']`)
|
|
552
|
+
: null;
|
|
553
|
+
// Negated mentions describe an absence, not a dispatch ("Branch 1 — no active
|
|
554
|
+
// step hooks (`activeHooks` has no entry with `kind == "step"`)" — ship.md).
|
|
555
|
+
const negationRe = /\b(?:no|without|absent|lacks?|missing)\b[^.|]*kind\s*={2,3}/;
|
|
556
|
+
const deferralRe = /@\S*loop-hook-dispatch\.md/;
|
|
557
|
+
for (const line of region.split(/\r?\n/)) {
|
|
558
|
+
// Sentence segments: a `.`/`;` followed by whitespace ends a segment. A
|
|
559
|
+
// period NOT followed by whitespace (the `.md` inside a deferral path,
|
|
560
|
+
// `ref.skill`) is not a boundary.
|
|
561
|
+
for (const segment of line.split(/(?<=[.;])\s+/)) {
|
|
562
|
+
const kindDiscriminators = [];
|
|
563
|
+
for (const kind of HOOK_KINDS) {
|
|
564
|
+
if (new RegExp(`kind\\s*={2,3}\\s*["']${kind}["']`).test(segment)) kindDiscriminators.push(kind);
|
|
565
|
+
}
|
|
566
|
+
if (kindDiscriminators.length === 0) {
|
|
567
|
+
// A deferral with no kind discriminator ("apply each entry per …")
|
|
568
|
+
// still covers every kind for generic hosts. An auxiliary host with a
|
|
569
|
+
// required role target must state both its kind and target explicitly.
|
|
570
|
+
if (!expectedInto && deferralRe.test(segment)) for (const kind of HOOK_KINDS) covered.add(kind);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (negationRe.test(segment)) continue;
|
|
574
|
+
const narrowed = identityNarrowingRe.test(segment) ||
|
|
575
|
+
(expectedInto
|
|
576
|
+
? !expectedIntoRe.test(segment)
|
|
577
|
+
: intoNarrowingRe.test(segment));
|
|
578
|
+
if (deferralRe.test(segment)) {
|
|
579
|
+
// Deferral naming kinds ("dispatch `kind == "step"` hooks per …").
|
|
580
|
+
if (!narrowed) for (const kind of kindDiscriminators) covered.add(kind);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (!narrowed) for (const kind of kindDiscriminators) covered.add(kind);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return covered;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Scan every `loop render-hooks <point>` call site in `text` and accumulate,
|
|
591
|
+
* per point, the union of hook kinds its dispatch regions cover (#3606).
|
|
592
|
+
*
|
|
593
|
+
* @param {string} text Content of a workflow file (or any text).
|
|
594
|
+
* @param {string} [expectedInto] Optional role target an auxiliary host must dispatch.
|
|
595
|
+
* @returns {Map<string, Set<string>>} point → covered kinds.
|
|
596
|
+
*/
|
|
597
|
+
function scanWiredKinds(text, expectedInto) {
|
|
598
|
+
const result = new Map();
|
|
599
|
+
const siteRe = CALL_SITE_RE;
|
|
600
|
+
const sites = [];
|
|
601
|
+
let m;
|
|
602
|
+
while ((m = siteRe.exec(text)) !== null) sites.push({ point: m[1], start: m.index });
|
|
603
|
+
const REGION_CAP = 6000;
|
|
604
|
+
for (let i = 0; i < sites.length; i++) {
|
|
605
|
+
const regionEnd = i + 1 < sites.length ? sites[i + 1].start : Math.min(text.length, sites[i].start + REGION_CAP);
|
|
606
|
+
const region = text.slice(sites[i].start, regionEnd);
|
|
607
|
+
const covered = coveredKindsInRegion(region, expectedInto);
|
|
608
|
+
if (!result.has(sites[i].point)) result.set(sites[i].point, new Set());
|
|
609
|
+
for (const kind of covered) result.get(sites[i].point).add(kind);
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Read every host-loop workflow file and return, per point, the union of hook
|
|
616
|
+
* kinds its call sites' dispatch text covers (#3606).
|
|
617
|
+
*
|
|
618
|
+
* @param {string} [repoRoot] Path to the repository root. Defaults to ROOT.
|
|
619
|
+
* @returns {Map<string, Set<string>>}
|
|
620
|
+
*/
|
|
621
|
+
function getWiredKinds(repoRoot) {
|
|
622
|
+
const resolvedRoot = repoRoot !== undefined ? repoRoot : ROOT;
|
|
623
|
+
const result = new Map();
|
|
624
|
+
for (const relPath of HOST_LOOP_FILES) {
|
|
625
|
+
const absPath = path.join(resolvedRoot, relPath);
|
|
626
|
+
let content;
|
|
627
|
+
try {
|
|
628
|
+
content = fs.readFileSync(absPath, 'utf8');
|
|
629
|
+
} catch (err) {
|
|
630
|
+
throw new Error('getWiredKinds: cannot read host-loop file ' + absPath + ': ' + err.message);
|
|
631
|
+
}
|
|
632
|
+
for (const [point, kinds] of scanWiredKinds(content)) {
|
|
633
|
+
if (!result.has(point)) result.set(point, new Set());
|
|
634
|
+
for (const kind of kinds) result.get(point).add(kind);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return result;
|
|
638
|
+
}
|
|
639
|
+
|
|
459
640
|
/**
|
|
460
641
|
* Read every host-loop workflow file and return the union of all wired loop points
|
|
461
642
|
* (i.e. points that have a `loop render-hooks <point>` call site).
|
|
@@ -497,6 +678,10 @@ module.exports = {
|
|
|
497
678
|
ROLE_TO_AGENT,
|
|
498
679
|
scanWiredPoints,
|
|
499
680
|
getWiredLoopPoints,
|
|
681
|
+
coveredKindsInRegion,
|
|
682
|
+
scanWiredKinds,
|
|
683
|
+
getWiredKinds,
|
|
684
|
+
HOOK_KINDS,
|
|
500
685
|
};
|
|
501
686
|
|
|
502
687
|
// ─── CLI entry point ──────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gen-scripts-cli-exit.cjs — generates scripts/lib/cli-exit.cjs from a fresh
|
|
4
|
+
* compile of src/cli-exit.cts.
|
|
5
|
+
*
|
|
6
|
+
* ADR-3889 Phase 0 (#3904): scripts/lib/cli-exit.cjs used to be a hand-written
|
|
7
|
+
* fork of gsd-core/bin/lib/cli-exit.cjs (the compiled artifact of
|
|
8
|
+
* src/cli-exit.cts). The two drifted — only the .cts copy routed a
|
|
9
|
+
* non-ExitError throw through getJsonErrorMode() to emit a structured
|
|
10
|
+
* { ok:false, reason, message } envelope. This script makes scripts/lib/cli-exit.cjs
|
|
11
|
+
* a generated artifact of the SAME source, so the two surfaces cannot diverge
|
|
12
|
+
* again.
|
|
13
|
+
*
|
|
14
|
+
* scripts/ runs straight from the repo checkout and must work on an unbuilt
|
|
15
|
+
* clone (scripts/check-env.cjs requires this file before any build runs), so
|
|
16
|
+
* the generated file is compiled to a THROWAWAY outDir rather than read from
|
|
17
|
+
* gsd-core/bin/lib/ — reading the tracked build output would let a stale build
|
|
18
|
+
* produce a false green.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* node scripts/gen-scripts-cli-exit.cjs # same as --write
|
|
22
|
+
* node scripts/gen-scripts-cli-exit.cjs --write # write scripts/lib/cli-exit.cjs
|
|
23
|
+
* node scripts/gen-scripts-cli-exit.cjs --check # exit 1 if committed file is stale
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
const { execFileSync } = require('node:child_process');
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const os = require('node:os');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
|
|
33
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
34
|
+
const OUTPUT_PATH = path.join(REPO_ROOT, 'scripts', 'lib', 'cli-exit.cjs');
|
|
35
|
+
const COMPILE_TIMEOUT_MS = 60_000;
|
|
36
|
+
|
|
37
|
+
/** Frozen reason codes so tests assert on structure, not prose. */
|
|
38
|
+
const REASON = Object.freeze({
|
|
39
|
+
OK: 'ok_generated_sync',
|
|
40
|
+
DRIFTED: 'fail_generated_drifted',
|
|
41
|
+
BUILD_FAILED: 'fail_build_failed',
|
|
42
|
+
MISSING_EMIT: 'fail_missing_emit',
|
|
43
|
+
USAGE: 'fail_usage',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const USAGE_MESSAGE = [
|
|
47
|
+
'Usage: node scripts/gen-scripts-cli-exit.cjs [--write|--check]',
|
|
48
|
+
' (no flag) same as --write',
|
|
49
|
+
' --write write scripts/lib/cli-exit.cjs',
|
|
50
|
+
' --check exit 1 if the committed file is stale',
|
|
51
|
+
].join('\n');
|
|
52
|
+
|
|
53
|
+
const BANNER = [
|
|
54
|
+
'// GENERATED FILE — DO NOT EDIT BY HAND.',
|
|
55
|
+
'// Source of truth: src/cli-exit.cts. Regenerate with:',
|
|
56
|
+
'// node scripts/gen-scripts-cli-exit.cjs --write',
|
|
57
|
+
'// Byte-compared by `npm run lint:generated-sync` (#3904, ADR-3889 Phase 0).',
|
|
58
|
+
'//',
|
|
59
|
+
'// Why this copy exists: scripts/ runs straight from the repo checkout and must',
|
|
60
|
+
'// work on an unbuilt clone — 64+ scripts require this file, including',
|
|
61
|
+
'// check-env.cjs, which runs before any build. gsd-core/bin/lib/cli-exit.cjs is',
|
|
62
|
+
'// gitignored tsc output and doubles as the build sentinel, so it cannot be',
|
|
63
|
+
'// required from here. Hence one source, two emitted locations.',
|
|
64
|
+
'',
|
|
65
|
+
'',
|
|
66
|
+
].join('\n');
|
|
67
|
+
|
|
68
|
+
/** Compile the whole project to a throwaway outDir so the work tree is untouched. */
|
|
69
|
+
function compileToTemp() {
|
|
70
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-scripts-cli-exit-'));
|
|
71
|
+
try {
|
|
72
|
+
execFileSync(
|
|
73
|
+
process.execPath,
|
|
74
|
+
[
|
|
75
|
+
path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'),
|
|
76
|
+
'-p', path.join(REPO_ROOT, 'tsconfig.build.json'),
|
|
77
|
+
'--outDir', tmp,
|
|
78
|
+
// A throwaway outDir must not reuse the in-tree incremental state, or
|
|
79
|
+
// tsc skips emit for files it believes are already current.
|
|
80
|
+
'--incremental', 'false',
|
|
81
|
+
'--tsBuildInfoFile', 'null',
|
|
82
|
+
],
|
|
83
|
+
{ cwd: REPO_ROOT, encoding: 'utf8', stdio: 'pipe', timeout: COMPILE_TIMEOUT_MS },
|
|
84
|
+
);
|
|
85
|
+
return { ok: true, dir: tmp };
|
|
86
|
+
} catch (err) {
|
|
87
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
88
|
+
const detail = [err.stdout, err.stderr].filter(Boolean).join('\n').trim();
|
|
89
|
+
return { ok: false, detail };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Compile src/cli-exit.cts to a throwaway outDir and return the expected
|
|
95
|
+
* generated content (banner + compiled bytes), or a failure descriptor.
|
|
96
|
+
*
|
|
97
|
+
* @returns {{ ok: true, content: string } | { ok: false, reason: string, detail?: string }}
|
|
98
|
+
*/
|
|
99
|
+
function buildExpectedContent() {
|
|
100
|
+
const build = compileToTemp();
|
|
101
|
+
if (!build.ok) {
|
|
102
|
+
return { ok: false, reason: REASON.BUILD_FAILED, detail: build.detail };
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const emitted = path.join(build.dir, 'cli-exit.cjs');
|
|
106
|
+
if (!fs.existsSync(emitted)) {
|
|
107
|
+
return { ok: false, reason: REASON.MISSING_EMIT, detail: `no emit produced at ${emitted} from src/cli-exit.cts` };
|
|
108
|
+
}
|
|
109
|
+
const compiled = fs.readFileSync(emitted, 'utf8');
|
|
110
|
+
return { ok: true, content: BANNER + compiled };
|
|
111
|
+
} finally {
|
|
112
|
+
fs.rmSync(build.dir, { recursive: true, force: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function doWrite() {
|
|
117
|
+
const result = buildExpectedContent();
|
|
118
|
+
if (!result.ok) {
|
|
119
|
+
console.error(`FAIL gen-scripts-cli-exit: ${result.reason}`);
|
|
120
|
+
if (result.detail) console.error(result.detail);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });
|
|
124
|
+
fs.writeFileSync(OUTPUT_PATH, result.content, 'utf8');
|
|
125
|
+
console.log(`ok gen-scripts-cli-exit: wrote ${path.relative(REPO_ROOT, OUTPUT_PATH)}`);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function doCheck() {
|
|
130
|
+
const result = buildExpectedContent();
|
|
131
|
+
if (!result.ok) {
|
|
132
|
+
console.error(`FAIL gen-scripts-cli-exit: ${result.reason}`);
|
|
133
|
+
if (result.detail) console.error(result.detail);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(OUTPUT_PATH)) {
|
|
138
|
+
console.error(`FAIL gen-scripts-cli-exit: ${REASON.MISSING_EMIT}`);
|
|
139
|
+
console.error(` ${path.relative(REPO_ROOT, OUTPUT_PATH)} does not exist. Run:`);
|
|
140
|
+
console.error(' node scripts/gen-scripts-cli-exit.cjs --write');
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const committed = fs.readFileSync(OUTPUT_PATH, 'utf8');
|
|
145
|
+
if (committed !== result.content) {
|
|
146
|
+
console.error(`FAIL gen-scripts-cli-exit: ${REASON.DRIFTED}`);
|
|
147
|
+
console.error(
|
|
148
|
+
` ${path.relative(REPO_ROOT, OUTPUT_PATH)} (${committed.length} bytes) != ` +
|
|
149
|
+
`compile of src/cli-exit.cts (${result.content.length} bytes)`,
|
|
150
|
+
);
|
|
151
|
+
console.error('');
|
|
152
|
+
console.error('Regenerate with:');
|
|
153
|
+
console.error(' node scripts/gen-scripts-cli-exit.cjs --write');
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`ok gen-scripts-cli-exit: ${path.relative(REPO_ROOT, OUTPUT_PATH)} matches src/cli-exit.cts`);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function main() {
|
|
162
|
+
const flag = process.argv[2];
|
|
163
|
+
const extra = process.argv[3];
|
|
164
|
+
|
|
165
|
+
if (flag !== undefined && flag !== '--write' && flag !== '--check') {
|
|
166
|
+
console.error(`FAIL gen-scripts-cli-exit: ${REASON.USAGE}`);
|
|
167
|
+
console.error(` unrecognized argument: ${flag}`);
|
|
168
|
+
console.error(USAGE_MESSAGE);
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (extra !== undefined) {
|
|
173
|
+
console.error(`FAIL gen-scripts-cli-exit: ${REASON.USAGE}`);
|
|
174
|
+
console.error(` unexpected extra argument: ${extra}`);
|
|
175
|
+
console.error(USAGE_MESSAGE);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (flag === '--check') return doCheck();
|
|
180
|
+
return doWrite();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (require.main === module) process.exitCode = main();
|
|
184
|
+
|
|
185
|
+
module.exports = { REASON, buildExpectedContent, OUTPUT_PATH, BANNER };
|