@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
|
@@ -61,17 +61,43 @@ const SKIP_DIR_NAMES = new Set(['node_modules', 'dist', '.git']);
|
|
|
61
61
|
* previous regex silently MISSED every re-derivation using a
|
|
62
62
|
* cross-platform path-separator class for exactly this reason.
|
|
63
63
|
*/
|
|
64
|
+
// Deterministic regression seam for the MAJOR-2 bound (issue #3951/#3987): a
|
|
65
|
+
// counter of how many characters this tokenizer has actually examined, so a
|
|
66
|
+
// test can assert the bound HOLDS (total work stays a small linear multiple
|
|
67
|
+
// of the number of scan attempts × MAX_REGEX_LITERAL_LEN) without resorting
|
|
68
|
+
// to a wall-clock elapsed-time assertion, which this repo's test rules ban
|
|
69
|
+
// ("Clock Seams: Do not assert on wall-clock time.") and which is exactly
|
|
70
|
+
// what flaked on a slow shared CI runner. `resetRegexScanStats`/
|
|
71
|
+
// `getRegexScanStats` are read-modify-reset around a single scan under test;
|
|
72
|
+
// they are process-global and NOT safe under concurrent scans, which is fine
|
|
73
|
+
// for this synchronous, single-threaded CLI tool and its tests.
|
|
74
|
+
let regexScanStats = { calls: 0, charsExamined: 0 };
|
|
75
|
+
|
|
76
|
+
function resetRegexScanStats() {
|
|
77
|
+
regexScanStats = { calls: 0, charsExamined: 0 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getRegexScanStats() {
|
|
81
|
+
return { ...regexScanStats };
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
function readRegexLiteralAt(line, start) {
|
|
65
85
|
if (line[start] !== '/') return null;
|
|
86
|
+
regexScanStats.calls++;
|
|
66
87
|
const limit = Math.min(line.length, start + MAX_REGEX_LITERAL_LEN);
|
|
67
88
|
let inClass = false;
|
|
68
|
-
|
|
89
|
+
let i = start + 1;
|
|
90
|
+
for (; i < limit; i++) {
|
|
69
91
|
const ch = line[i];
|
|
70
92
|
if (ch === '\\') {
|
|
71
93
|
i++; // escape consumes the next character, whatever it is
|
|
72
94
|
continue;
|
|
73
95
|
}
|
|
74
|
-
if (ch === '\r' || ch === '\n')
|
|
96
|
+
if (ch === '\r' || ch === '\n') {
|
|
97
|
+
// a literal cannot span lines
|
|
98
|
+
regexScanStats.charsExamined += i - start;
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
75
101
|
if (ch === '[') {
|
|
76
102
|
inClass = true;
|
|
77
103
|
} else if (ch === ']') {
|
|
@@ -83,9 +109,11 @@ function readRegexLiteralAt(line, start) {
|
|
|
83
109
|
// MAX_REGEX_LITERAL_LEN either.
|
|
84
110
|
let end = i + 1;
|
|
85
111
|
while (end < limit && line[end] >= 'a' && line[end] <= 'z') end++;
|
|
112
|
+
regexScanStats.charsExamined += end - start;
|
|
86
113
|
return { text: line.slice(start, end), end };
|
|
87
114
|
}
|
|
88
115
|
}
|
|
116
|
+
regexScanStats.charsExamined += limit - start;
|
|
89
117
|
return null;
|
|
90
118
|
}
|
|
91
119
|
|
|
@@ -275,4 +303,6 @@ module.exports = {
|
|
|
275
303
|
MAX_REGEX_LITERAL_LEN,
|
|
276
304
|
sanitizeForReport,
|
|
277
305
|
scanTree,
|
|
306
|
+
resetRegexScanStats,
|
|
307
|
+
getRegexScanStats,
|
|
278
308
|
};
|
|
@@ -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 };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Machine-readable companion reporter for scripts/run-tests.cjs (#3889).
|
|
4
|
+
//
|
|
5
|
+
// node:test's built-in reporters (spec/tap) are human-formatted and
|
|
6
|
+
// scripts/run-tests.cjs spawns the child with `stdio: 'inherit'` — by design,
|
|
7
|
+
// per #3597/#1051, to avoid the maxBuffer and live-output risks of piping —
|
|
8
|
+
// so the parent process has no way to see WHICH file was executing when a
|
|
9
|
+
// per-chunk timeout kills the child. This reporter runs ALONGSIDE the normal
|
|
10
|
+
// human reporter (a second `--test-reporter` on the same invocation, per
|
|
11
|
+
// Node's documented multi-reporter pairing) and appends one JSON object per
|
|
12
|
+
// line to a path supplied via the GSD_RUN_TESTS_EVENTS_FILE env var. On a
|
|
13
|
+
// timeout, run-tests.cjs reads that file back to name the file(s) still
|
|
14
|
+
// in flight (a `test:dequeue` with no matching `test:pass`/`test:fail`).
|
|
15
|
+
//
|
|
16
|
+
// Durability, not `--test-reporter-destination` (#3889 root cause): a
|
|
17
|
+
// reporter that YIELDS strings has them piped by Node into a
|
|
18
|
+
// `fs.WriteStream` targeting the destination path, and that stream buffers.
|
|
19
|
+
// The parent's `execFileSync` timeout SIGKILLs the child on a hang, and
|
|
20
|
+
// SIGKILL is uncatchable and gives the process zero chance to flush — so a
|
|
21
|
+
// yield-based reporter can lose every event still sitting in the stream's
|
|
22
|
+
// buffer, which is exactly the case this feature exists to diagnose (proven
|
|
23
|
+
// live: a chunk killed at 2006ms produced a `killed after 2006ms` line from
|
|
24
|
+
// the TIMER, which lives in the parent, but zero usable events from the
|
|
25
|
+
// reporter, which lives in the child and never flushed). Writing each event
|
|
26
|
+
// with `fs.appendFileSync` — synchronous and unbuffered — makes it durable
|
|
27
|
+
// the instant it happens, before the process can be killed out from under
|
|
28
|
+
// it. The reporter therefore yields NOTHING; it is a pure side-effecting
|
|
29
|
+
// sink. Node still requires a `--test-reporter-destination` to pair with
|
|
30
|
+
// this `--test-reporter` (see run-tests.cjs's reporterArgsFor), but that
|
|
31
|
+
// destination is a throwaway sink that stays empty by design — the durable
|
|
32
|
+
// path is GSD_RUN_TESTS_EVENTS_FILE, not the destination Node manages.
|
|
33
|
+
//
|
|
34
|
+
// Contract targeted: Node's "Custom reporters" contract
|
|
35
|
+
// (https://nodejs.org/api/test.html#custom-reporters) — a reporter module's
|
|
36
|
+
// default export is a function receiving the test runner's event stream (an
|
|
37
|
+
// AsyncIterable of `{ type, data }` objects). Node feeds that function to
|
|
38
|
+
// `stream.compose` as the stream's "body". When the body is an async
|
|
39
|
+
// FUNCTION (not an `async function*` generator), `stream.compose`'s own
|
|
40
|
+
// contract (https://nodejs.org/api/stream.html#streamcomposestreams)
|
|
41
|
+
// requires it to return nully (undefined/null) — returning anything else,
|
|
42
|
+
// including an array, throws `ERR_INVALID_RETURN_VALUE` ("Expected nully to
|
|
43
|
+
// be returned from the 'body' function but got an instance of Array")
|
|
44
|
+
// exactly once the promise resolves. Verified directly against
|
|
45
|
+
// `stream.compose` in this repo's Node: calling it with a body that
|
|
46
|
+
// `return`s `[]` reproduces that same TypeError. A generator form
|
|
47
|
+
// (`async function*`) is the one that yields an iterable; the plain
|
|
48
|
+
// `async function` form used here is the one that must return nully. This
|
|
49
|
+
// repo's `engines.node` requires >=24.0.0 (package.json), where both
|
|
50
|
+
// contracts have been stable since Node 20.
|
|
51
|
+
//
|
|
52
|
+
// Kept intentionally tiny: only the five event types run-tests.cjs needs are
|
|
53
|
+
// handled — `test:enqueue`/`test:dequeue` (emitted by the RUNNER as it queues
|
|
54
|
+
// and begins each spawned test-file child, independent of whether anything
|
|
55
|
+
// inside that file ever completes) plus `test:start`/`test:pass`/`test:fail`
|
|
56
|
+
// (emitted per-subtest, once the child reports it). `test:dequeue` is the
|
|
57
|
+
// event that actually means "in flight": a subtest inside a file that hangs
|
|
58
|
+
// forever never reaches `test:start`/`test:pass`/`test:fail` at all, because
|
|
59
|
+
// node:test only surfaces those to the parent once the child COMPLETES that
|
|
60
|
+
// test — a hang, by definition, never completes. Recording `test:dequeue`
|
|
61
|
+
// closes that gap: it fires the moment the runner begins the file, so a
|
|
62
|
+
// killed hang still leaves a durable "this file was running" record.
|
|
63
|
+
// Everything else (diagnostics, plans, coverage) is ignored so a truncated
|
|
64
|
+
// events file (the process is SIGKILLed mid-`appendFileSync` on timeout — an
|
|
65
|
+
// individual write is unbuffered but not atomic, so the OS can still
|
|
66
|
+
// interleave a partial write with the kill) never leaves more than one
|
|
67
|
+
// dangling unparsable trailing line.
|
|
68
|
+
module.exports = async function ndjsonEventReporter(source) {
|
|
69
|
+
const eventsPath = process.env.GSD_RUN_TESTS_EVENTS_FILE;
|
|
70
|
+
// #3889: an init marker, written as this reporter's FIRST action — before
|
|
71
|
+
// the `for await` loop even begins consuming the event stream — so the
|
|
72
|
+
// events file's mere existence (and its exact contents) can distinguish
|
|
73
|
+
// "the reporter module never loaded in the child at all" (file absent)
|
|
74
|
+
// from "it loaded fine but no test:start reached it before the kill"
|
|
75
|
+
// (file contains only this one line) from "it's working" (file contains
|
|
76
|
+
// more than this line). Same appendFileSync durability rationale as every
|
|
77
|
+
// other write in this file: synchronous and unbuffered, so it survives an
|
|
78
|
+
// uncatchable SIGKILL landing a moment later.
|
|
79
|
+
if (eventsPath) {
|
|
80
|
+
try {
|
|
81
|
+
require('fs').appendFileSync(eventsPath, `${JSON.stringify({ type: 'reporter:init', ts: Date.now() })}\n`);
|
|
82
|
+
} catch {
|
|
83
|
+
// Best-effort, same as every other write below — must never crash the
|
|
84
|
+
// test run this reporter is only observing.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for await (const event of source) {
|
|
88
|
+
if (!eventsPath) continue; // no destination configured — nothing to record
|
|
89
|
+
if (
|
|
90
|
+
event.type === 'test:enqueue' ||
|
|
91
|
+
event.type === 'test:dequeue' ||
|
|
92
|
+
event.type === 'test:start' ||
|
|
93
|
+
event.type === 'test:pass' ||
|
|
94
|
+
event.type === 'test:fail'
|
|
95
|
+
) {
|
|
96
|
+
const { file, name, nesting, testNumber } = event.data || {};
|
|
97
|
+
const line = `${JSON.stringify({
|
|
98
|
+
type: event.type,
|
|
99
|
+
file,
|
|
100
|
+
name,
|
|
101
|
+
nesting,
|
|
102
|
+
testNumber,
|
|
103
|
+
ts: Date.now(),
|
|
104
|
+
})}\n`;
|
|
105
|
+
try {
|
|
106
|
+
require('fs').appendFileSync(eventsPath, line);
|
|
107
|
+
} catch {
|
|
108
|
+
// Best-effort: a write failure here (e.g. the events dir vanished)
|
|
109
|
+
// must never crash the test run this reporter is only observing.
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Falls through to an implicit `return undefined` (nully): this reporter is
|
|
114
|
+
// a pure side-effecting sink (see the durability note above), never a
|
|
115
|
+
// source of reporter OUTPUT, and `stream.compose` requires its async
|
|
116
|
+
// FUNCTION body to return nully — returning an iterable (e.g. `[]`) here
|
|
117
|
+
// raises `ERR_INVALID_RETURN_VALUE` (observed live: this exact `return []`
|
|
118
|
+
// crashed every chunk on the real remote run this regresses).
|
|
119
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* shellcheck-fetch.cjs
|
|
5
|
+
*
|
|
6
|
+
* Dependency-free replacement for the `shellcheck` npm package (removed in
|
|
7
|
+
* #4120 — its extraction step pulled in `decompress@4.2.1`, which carries an
|
|
8
|
+
* unpatched CRITICAL zip-slip vulnerability, GHSA-mp2f-45pm-3cg9, CVSS 9.1,
|
|
9
|
+
* plus two moderate findings, with no patched version available upstream).
|
|
10
|
+
*
|
|
11
|
+
* This module fetches a PINNED koalaman/shellcheck release directly from
|
|
12
|
+
* GitHub releases (never "latest" — see SHELLCHECK_VERSION below), extracts
|
|
13
|
+
* the single `shellcheck` binary from the release's `.tar.gz` asset using
|
|
14
|
+
* only Node's built-in `https`/`zlib` modules plus a small hand-written tar
|
|
15
|
+
* reader (no third-party archive library), and caches the extracted binary
|
|
16
|
+
* for reuse across runs.
|
|
17
|
+
*
|
|
18
|
+
* Zip-slip defense: unlike `decompress`, which wrote extracted files using
|
|
19
|
+
* PATHS TAKEN FROM THE ARCHIVE (the exact defect class in GHSA-mp2f-45pm-
|
|
20
|
+
* 3cg9 — a malicious archive entry named e.g. `../../etc/passwd` gets
|
|
21
|
+
* written there verbatim), this reader NEVER uses an archive-supplied name
|
|
22
|
+
* as a filesystem path. `extractFileFromTar` only ever returns the matched
|
|
23
|
+
* entry's raw byte content; the caller (`resolveShellcheckBin`) writes those
|
|
24
|
+
* bytes to a path it constructs itself (`<cacheDir>/shellcheck`), and the
|
|
25
|
+
* archive's own name field is used only for a string-equality/suffix CHECK
|
|
26
|
+
* (`name === targetName || name.endsWith('/' + targetName)`), never
|
|
27
|
+
* interpolated into a path passed to `fs.writeFileSync`/`fs.mkdirSync`/etc.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
const zlib = require('node:zlib');
|
|
33
|
+
const https = require('node:https');
|
|
34
|
+
const { ExitError } = require('./cli-exit.cjs');
|
|
35
|
+
|
|
36
|
+
// Pinned explicitly — verified against `koalaman/shellcheck`'s GitHub
|
|
37
|
+
// releases API as the current latest tag at the time this was written
|
|
38
|
+
// (2026-08-31). Never resolved dynamically ("latest") — a moving target
|
|
39
|
+
// would make this lint's exact ShellCheck version, and therefore its exact
|
|
40
|
+
// finding set against scripts/lint-workflow-shellcheck-baseline.json,
|
|
41
|
+
// non-reproducible across runs/machines/CI.
|
|
42
|
+
const SHELLCHECK_VERSION = 'v0.11.0';
|
|
43
|
+
|
|
44
|
+
const ROOT = path.join(__dirname, '..', '..');
|
|
45
|
+
const CACHE_DIR = path.join(ROOT, 'node_modules', '.cache', 'shellcheck', SHELLCHECK_VERSION);
|
|
46
|
+
const CACHED_BIN_PATH = path.join(CACHE_DIR, 'shellcheck');
|
|
47
|
+
|
|
48
|
+
// process.arch -> the arch token ShellCheck's release asset names use.
|
|
49
|
+
// Only the two architectures that actually matter for this repo (per
|
|
50
|
+
// .github/workflows/test.yml: the lint-tests job that runs this script only
|
|
51
|
+
// runs on ubuntu-latest, which is x86_64; and Apple Silicon dev machines are
|
|
52
|
+
// aarch64) are supported — anything else fails with a clear error rather
|
|
53
|
+
// than guessing.
|
|
54
|
+
const ARCH_MAP = { x64: 'x86_64', arm64: 'aarch64' };
|
|
55
|
+
|
|
56
|
+
const MAX_REDIRECTS = 5;
|
|
57
|
+
|
|
58
|
+
// Bounds each individual HTTP hop (the initial request AND every redirect
|
|
59
|
+
// hop get their own fresh 30s budget, rather than one shared budget across
|
|
60
|
+
// the whole redirect chain) — a stalled connection on any single hop is
|
|
61
|
+
// caught in a bounded time, mirroring lint-workflow-shellcheck.cjs's own
|
|
62
|
+
// SHELLCHECK_TIMEOUT_MS bound on the ShellCheck subprocess. A bare `timeout`
|
|
63
|
+
// option on the request does NOT abort it by itself — Node only emits a
|
|
64
|
+
// 'timeout' event, which must be handled by destroying the request (see the
|
|
65
|
+
// `req.on('timeout', ...)` below).
|
|
66
|
+
const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Issue one real HTTPS request. Exists as its own function purely so tests
|
|
70
|
+
* can inject a fake in its place (see `httpsGetFollowingRedirects`'s
|
|
71
|
+
* `requestFn` parameter) — production callers never pass an override, so
|
|
72
|
+
* the real download path always uses this exact implementation.
|
|
73
|
+
*/
|
|
74
|
+
function defaultRequestFn(url, options, callback) {
|
|
75
|
+
return https.get(url, options, callback);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* GET `url` following HTTP redirects manually — `https.get` does NOT follow
|
|
80
|
+
* redirects automatically, and GitHub release asset URLs redirect through
|
|
81
|
+
* `objects.githubusercontent.com`. Resolves with the full response body as a
|
|
82
|
+
* Buffer once a 200 response is received.
|
|
83
|
+
*
|
|
84
|
+
* `requestFn` defaults to a real `https.get`-based transport
|
|
85
|
+
* (`defaultRequestFn`) and is only ever overridden in tests, so calling this
|
|
86
|
+
* with zero/one arg from `resolveShellcheckBin` is unchanged behavior.
|
|
87
|
+
*/
|
|
88
|
+
function httpsGetFollowingRedirects(url, redirectsLeft = MAX_REDIRECTS, requestFn = defaultRequestFn) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const req = requestFn(
|
|
91
|
+
url,
|
|
92
|
+
{ headers: { 'User-Agent': 'gsd-core-shellcheck-fetch' }, timeout: DOWNLOAD_TIMEOUT_MS },
|
|
93
|
+
(res) => {
|
|
94
|
+
const status = res.statusCode || 0;
|
|
95
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
96
|
+
res.resume(); // drain so the socket can be reused/closed
|
|
97
|
+
if (redirectsLeft <= 0) {
|
|
98
|
+
reject(new Error(`too many redirects fetching ${url}`));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const next = new URL(res.headers.location, url).toString();
|
|
102
|
+
httpsGetFollowingRedirects(next, redirectsLeft - 1, requestFn).then(resolve, reject);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (status !== 200) {
|
|
106
|
+
res.resume();
|
|
107
|
+
reject(new Error(`unexpected HTTP ${status} fetching ${url}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const chunks = [];
|
|
111
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
112
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
113
|
+
res.on('error', reject);
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
req.on('error', reject);
|
|
117
|
+
// `timeout` in the options above only ARMS a timer — Node emits a
|
|
118
|
+
// 'timeout' event on the request but does not abort it. Without this
|
|
119
|
+
// handler the request (and this Promise) would hang indefinitely past
|
|
120
|
+
// the configured bound on a stalled connection.
|
|
121
|
+
req.on('timeout', () => {
|
|
122
|
+
req.destroy(new Error(`timed out after ${DOWNLOAD_TIMEOUT_MS}ms fetching ${url}`));
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Find the entry named (or path-ending-in) `targetName` inside a raw
|
|
129
|
+
* (already gunzipped) POSIX tar byte stream and return its content as a
|
|
130
|
+
* Buffer, or `null` if not found.
|
|
131
|
+
*
|
|
132
|
+
* Tar format: a sequence of 512-byte headers — name at offset 0/length 100,
|
|
133
|
+
* size at offset 124/length 12 (octal ASCII), typeflag at offset 156 — each
|
|
134
|
+
* followed by that many content bytes, padded up to the next 512-byte
|
|
135
|
+
* boundary, terminated by an all-zero 512-byte block. This deliberately
|
|
136
|
+
* implements only enough to locate ONE known entry name (no general
|
|
137
|
+
* multi-file extraction, no symlink handling, no GNU long-name `@LongLink`
|
|
138
|
+
* entries — ShellCheck's own release tarballs never need them) — see this
|
|
139
|
+
* module's header comment for why the entry's NAME is never used as a
|
|
140
|
+
* filesystem path.
|
|
141
|
+
*/
|
|
142
|
+
function extractFileFromTar(buffer, targetName) {
|
|
143
|
+
let offset = 0;
|
|
144
|
+
while (offset + 512 <= buffer.length) {
|
|
145
|
+
const header = buffer.subarray(offset, offset + 512);
|
|
146
|
+
if (header.every((b) => b === 0)) break; // end-of-archive marker
|
|
147
|
+
const name = header.subarray(0, 100).toString('utf8').replace(/\0.*$/, '');
|
|
148
|
+
const sizeRaw = header.subarray(124, 136).toString('utf8').replace(/\0.*$/, '').trim();
|
|
149
|
+
const size = sizeRaw === '' ? 0 : parseInt(sizeRaw, 8);
|
|
150
|
+
const typeflag = String.fromCharCode(header[156]);
|
|
151
|
+
const dataStart = offset + 512;
|
|
152
|
+
const isRegularFile = typeflag === '0' || typeflag === '\0';
|
|
153
|
+
if (isRegularFile && (name === targetName || name.endsWith(`/${targetName}`))) {
|
|
154
|
+
return buffer.subarray(dataStart, dataStart + size);
|
|
155
|
+
}
|
|
156
|
+
const contentBlocks = Math.ceil(size / 512);
|
|
157
|
+
offset = dataStart + contentBlocks * 512;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the local path to a working, executable `shellcheck` binary,
|
|
164
|
+
* downloading and caching the pinned release on first use. Subsequent calls
|
|
165
|
+
* (same version) reuse the cached binary with no network activity — mirrors
|
|
166
|
+
* the `fs.accessSync(bin, F_OK | X_OK)` cache-check pattern already used by
|
|
167
|
+
* this script's own `runShellcheck`.
|
|
168
|
+
*/
|
|
169
|
+
async function resolveShellcheckBin() {
|
|
170
|
+
try {
|
|
171
|
+
fs.accessSync(CACHED_BIN_PATH, fs.constants.F_OK | fs.constants.X_OK);
|
|
172
|
+
return CACHED_BIN_PATH;
|
|
173
|
+
} catch {
|
|
174
|
+
// not cached yet — fall through to download
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (process.platform === 'win32') {
|
|
178
|
+
throw new ExitError(
|
|
179
|
+
1,
|
|
180
|
+
'lint-workflow-shellcheck: automatic ShellCheck download is not supported on Windows yet ' +
|
|
181
|
+
'(this lint only ever runs in the ubuntu-latest lint-tests CI job — see .github/workflows/test.yml — ' +
|
|
182
|
+
'so this is an honest platform gap, not expected to be hit in CI).',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const platform = process.platform === 'darwin' || process.platform === 'linux' ? process.platform : null;
|
|
186
|
+
if (!platform) {
|
|
187
|
+
throw new ExitError(
|
|
188
|
+
1,
|
|
189
|
+
`lint-workflow-shellcheck: unsupported platform '${process.platform}' for ShellCheck auto-download ` +
|
|
190
|
+
`(supported: linux, darwin).`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const arch = ARCH_MAP[process.arch];
|
|
194
|
+
if (!arch) {
|
|
195
|
+
throw new ExitError(
|
|
196
|
+
1,
|
|
197
|
+
`lint-workflow-shellcheck: unsupported architecture '${process.arch}' for ShellCheck auto-download ` +
|
|
198
|
+
`(supported: x86_64 [node arch 'x64'], aarch64 [node arch 'arm64']).`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const assetName = `shellcheck-${SHELLCHECK_VERSION}.${platform}.${arch}.tar.gz`;
|
|
203
|
+
const url = `https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/${assetName}`;
|
|
204
|
+
|
|
205
|
+
let gz;
|
|
206
|
+
try {
|
|
207
|
+
gz = await httpsGetFollowingRedirects(url);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
throw new ExitError(1, `lint-workflow-shellcheck: failed to download ShellCheck (${url}): ${e.message}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let tarBuf;
|
|
213
|
+
try {
|
|
214
|
+
tarBuf = zlib.gunzipSync(gz);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
throw new ExitError(1, `lint-workflow-shellcheck: failed to gunzip downloaded ShellCheck archive: ${e.message}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const entry = extractFileFromTar(tarBuf, 'shellcheck');
|
|
220
|
+
if (!entry) {
|
|
221
|
+
throw new ExitError(
|
|
222
|
+
1,
|
|
223
|
+
`lint-workflow-shellcheck: could not find a 'shellcheck' entry inside downloaded archive ${assetName}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
228
|
+
// Write to a per-process temp path and rename into place — avoids any
|
|
229
|
+
// other concurrent invocation observing (and trying to execute) a
|
|
230
|
+
// partially-written binary at the real cache path.
|
|
231
|
+
const tmpPath = path.join(CACHE_DIR, `.shellcheck.tmp-${process.pid}`);
|
|
232
|
+
fs.writeFileSync(tmpPath, entry);
|
|
233
|
+
fs.chmodSync(tmpPath, 0o755);
|
|
234
|
+
fs.renameSync(tmpPath, CACHED_BIN_PATH);
|
|
235
|
+
|
|
236
|
+
return CACHED_BIN_PATH;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
SHELLCHECK_VERSION,
|
|
241
|
+
CACHED_BIN_PATH,
|
|
242
|
+
MAX_REDIRECTS,
|
|
243
|
+
DOWNLOAD_TIMEOUT_MS,
|
|
244
|
+
extractFileFromTar,
|
|
245
|
+
httpsGetFollowingRedirects,
|
|
246
|
+
resolveShellcheckBin,
|
|
247
|
+
};
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"tests/code-review-pipeline-regression.test.cjs :: source-text-is-the-product",
|
|
25
25
|
"tests/code-review.test.cjs :: source-text-is-the-product",
|
|
26
26
|
"tests/codebuddy-install.test.cjs :: source-text-is-the-product",
|
|
27
|
-
"tests/codex-config.test.cjs :: source-text-is-the-product",
|
|
28
27
|
"tests/command-contract.test.cjs :: source-text-is-the-product",
|
|
29
28
|
"tests/commands.test.cjs :: source-text-is-the-product",
|
|
30
29
|
"tests/config-field-docs.test.cjs :: docs-parity",
|
|
@@ -52,7 +51,6 @@
|
|
|
52
51
|
"tests/frontmatter-cli.test.cjs :: source-text-is-the-product",
|
|
53
52
|
"tests/gates-taxonomy.test.cjs :: source-text-is-the-product",
|
|
54
53
|
"tests/git-base-branch.test.cjs :: source-text-is-the-product",
|
|
55
|
-
"tests/gsd-check-update-worker-platform-gate.test.cjs :: structural-regression-guard",
|
|
56
54
|
"tests/gsd-researcher-app-aware.test.cjs :: source-text-is-the-product",
|
|
57
55
|
"tests/gsd-researcher-flow-diagram.test.cjs :: source-text-is-the-product",
|
|
58
56
|
"tests/gsd-settings-advanced.test.cjs :: source-text-is-the-product",
|
|
@@ -74,13 +72,11 @@
|
|
|
74
72
|
"tests/next-safety-gates.test.cjs :: source-text-is-the-product",
|
|
75
73
|
"tests/next-up-clear-order.test.cjs :: source-text-is-the-product",
|
|
76
74
|
"tests/no-hardcoded-home-gsd-tools.test.cjs :: source-text-is-the-product",
|
|
77
|
-
"tests/orphaned-hooks.test.cjs :: structural-regression-guard",
|
|
78
75
|
"tests/package-legitimacy-gate.test.cjs :: source-text-is-the-product",
|
|
79
76
|
"tests/parallel-dependent-plans.test.cjs :: source-text-is-the-product",
|
|
80
77
|
"tests/path-replacement.test.cjs :: source-text-is-the-product",
|
|
81
78
|
"tests/phase.test.cjs :: source-text-is-the-product",
|
|
82
79
|
"tests/phase6-capability-docs.test.cjs :: source-text-is-the-product",
|
|
83
|
-
"tests/phase6-capstone-conformance.test.cjs :: source-text-is-the-product",
|
|
84
80
|
"tests/phase6-planning-capabilities.test.cjs :: source-text-is-the-product",
|
|
85
81
|
"tests/plan-bounce.test.cjs :: source-text-is-the-product",
|
|
86
82
|
"tests/plan-phase-drift-guard.test.cjs :: source-text-is-the-product",
|
|
@@ -105,7 +101,6 @@
|
|
|
105
101
|
"tests/research-agent-profiles.test.cjs :: source-text-is-the-product",
|
|
106
102
|
"tests/roadmap.test.cjs :: source-text-is-the-product",
|
|
107
103
|
"tests/runtime-launcher-parity.test.cjs :: structural-regression-guard",
|
|
108
|
-
"tests/runtime-name-policy.test.cjs :: source-text-is-the-product",
|
|
109
104
|
"tests/scan-command.test.cjs :: source-text-is-the-product",
|
|
110
105
|
"tests/secret-scan-lint.security.test.cjs :: source-text-is-the-product",
|
|
111
106
|
"tests/secure-phase.test.cjs :: source-text-is-the-product",
|
|
@@ -113,7 +108,6 @@
|
|
|
113
108
|
"tests/security-scan.security.test.cjs :: source-text-is-the-product",
|
|
114
109
|
"tests/seed-scan-new-milestone.test.cjs :: source-text-is-the-product",
|
|
115
110
|
"tests/settings-integrations.test.cjs :: source-text-is-the-product",
|
|
116
|
-
"tests/settings-jsonc.test.cjs :: structural-regression-guard",
|
|
117
111
|
"tests/skill-frontmatter-contract.test.cjs :: source-text-is-the-product",
|
|
118
112
|
"tests/spawn-liveness-banner.test.cjs :: source-text-is-the-product",
|
|
119
113
|
"tests/state.test.cjs :: source-text-is-the-product",
|