@opengsd/gsd-core 1.13.0 → 1.15.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/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +459 -0
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +360 -0
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +848 -163
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +8 -3
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/review.md +2 -3
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +672 -146
- package/gsd-core/bin/lib/adr-parser.cjs +4 -2
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +119 -34
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +96 -189
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -2
- package/gsd-core/bin/lib/check-command-router.cjs +213 -49
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +823 -112
- package/gsd-core/bin/lib/config-loader.cjs +66 -4
- package/gsd-core/bin/lib/config.cjs +186 -45
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +164 -45
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +614 -86
- package/gsd-core/bin/lib/install-engine.cjs +29 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +37 -13
- package/gsd-core/bin/lib/model-resolver.cjs +253 -53
- package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +268 -27
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +393 -88
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +356 -42
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +181 -30
- package/gsd-core/bin/lib/state.cjs +265 -27
- package/gsd-core/bin/lib/surface.cjs +77 -3
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +9 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +315 -30
- package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
- package/gsd-core/bin/lib/verify.cjs +320 -48
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +13 -4
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +14 -2
- package/gsd-core/references/tdd.md +30 -4
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +433 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +29 -16
- package/gsd-core/workflows/check-todos.md +6 -4
- package/gsd-core/workflows/cleanup.md +5 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +216 -73
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +41 -264
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +17 -158
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +83 -172
- package/gsd-core/workflows/execute-plan.md +24 -10
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +52 -5
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +56 -14
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +39 -209
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +45 -187
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +132 -20
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +29 -10
- package/gsd-core/workflows/reapply-patches.md +86 -6
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +22 -9
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +14 -12
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +101 -196
- package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +71 -19
- package/hooks/dist/gsd-statusline.js +81 -20
- package/hooks/dist/gsd-validate-commit.sh +97 -8
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +66 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +71 -19
- package/hooks/gsd-statusline.js +81 -20
- package/hooks/gsd-validate-commit.sh +97 -8
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +11 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +131 -12
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +35 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +623 -13
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +29 -9
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
- package/scripts/prompt-injection-scan.sh +18 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +8 -3
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review/SKILL.md +2 -3
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
package/scripts/build-hooks.js
CHANGED
|
@@ -11,16 +11,25 @@ const path = require('path');
|
|
|
11
11
|
const vm = require('vm');
|
|
12
12
|
|
|
13
13
|
const HOOKS_DIR = path.join(__dirname, '..', 'hooks');
|
|
14
|
+
const REPO_ROOT = path.join(HOOKS_DIR, '..');
|
|
14
15
|
const DIST_DIR = path.join(HOOKS_DIR, 'dist');
|
|
15
16
|
// Per-process staging directory for atomic writes. Using process.pid in the
|
|
16
17
|
// name eliminates all contention between concurrent builders: each process
|
|
17
18
|
// owns its own staging dir and never races with another builder's cleanup.
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
19
|
+
// A SIBLING of hooks/ (not inside it) — still on the same filesystem as
|
|
20
|
+
// DIST_DIR (both live under the repo checkout), so rename(2) stays atomic —
|
|
21
|
+
// but this way nothing that walks or copies hooks/ as a whole can ever
|
|
22
|
+
// observe this directory being created/populated/deleted concurrently.
|
|
23
|
+
// tests/gsd-validate-commit-sigpipe.test.cjs's fixture setup does exactly
|
|
24
|
+
// that (fs.cpSync(HOOKS_DIR, ..., {recursive: true})), and when its walk
|
|
25
|
+
// entered a staging dir that a concurrent build-hooks.js run deleted
|
|
26
|
+
// mid-iteration, Node's native recursive-copy (std::filesystem under the
|
|
27
|
+
// hood) threw an uncaught C++ exception and aborted the whole process with
|
|
28
|
+
// SIGABRT — not a catchable JS error. Moving the staging dir out of hooks/
|
|
29
|
+
// closes that race for every current and future hooks/-tree walker, not
|
|
30
|
+
// just that one call site (which also gained its own defensive filter).
|
|
31
|
+
// The parent pattern .dist-staging-*/ (repo root) is gitignored.
|
|
32
|
+
const STAGE_DIR = path.join(REPO_ROOT, `.dist-staging-${process.pid}`);
|
|
24
33
|
|
|
25
34
|
// Hooks to copy (pure Node.js, no bundling needed)
|
|
26
35
|
const HOOKS_TO_COPY = [
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
* 7. Reverse direction: a workflow/command matching a quoted `## TOKEN`
|
|
28
28
|
* no agent declares or emits is dispatch-on-phantom
|
|
29
29
|
* (unmatched_consumer_token) — F9's shape from the consumer side.
|
|
30
|
+
* 8. Every reference pointer an agent file carries either resolves to
|
|
31
|
+
* exactly the regular file it names, contained under
|
|
32
|
+
* gsd-core/references/, or is reported (unresolved_reference_include) —
|
|
33
|
+
* a pointer the follower declines is never silently dropped (#4930).
|
|
30
34
|
*
|
|
31
35
|
* Exit 0 = clean. Exit 1 = violations (with diagnostics on stderr).
|
|
32
36
|
*/
|
|
@@ -35,6 +39,9 @@
|
|
|
35
39
|
|
|
36
40
|
const fs = require('fs');
|
|
37
41
|
const path = require('path');
|
|
42
|
+
// The repo's ONE containment decision (ADR-4650, src/security.cts) — realpath-resolved, so an
|
|
43
|
+
// intermediate component referring outside the root is refused before this loop reads the file.
|
|
44
|
+
const { tryWithinRoot } = require('../gsd-core/bin/lib/security.cjs');
|
|
38
45
|
|
|
39
46
|
function resolveRoot(argv) {
|
|
40
47
|
const idx = argv.indexOf('--root');
|
|
@@ -92,8 +99,9 @@ function toRepoRelative(absPath) {
|
|
|
92
99
|
/**
|
|
93
100
|
* referenceIncludes(content)
|
|
94
101
|
*
|
|
95
|
-
* Plain scan for `@~/.claude/gsd-core/references/*.md` tokens
|
|
96
|
-
*
|
|
102
|
+
* Plain scan for `@~/.claude/gsd-core/references/*.md` tokens (and the bare
|
|
103
|
+
* `@gsd-core/references/*.md` spelling, #4841) anywhere in an agent file's
|
|
104
|
+
* content -- inside an `<execution_context>` block (already
|
|
97
105
|
* covered structurally by `executionContextRefs` in command-contract-helpers,
|
|
98
106
|
* but a raw regex over the whole string picks those up too) and, just as
|
|
99
107
|
* importantly, OUTSIDE one: agents frequently point at a reference doc from
|
|
@@ -105,17 +113,94 @@ function toRepoRelative(absPath) {
|
|
|
105
113
|
* lives in `gsd-core/references/planner-guidance.md`, which the agent only
|
|
106
114
|
* `@`-includes -- so the producer scan below must follow these includes to
|
|
107
115
|
* see markers an agent's contract legitimately delegates to a reference doc.
|
|
108
|
-
* Returns
|
|
116
|
+
* Returns `{ includes, declined }`: `includes` are ROOT-relative paths
|
|
117
|
+
* (`gsd-core/references/foo.md`), de-duplicated; `declined` are the pointers
|
|
118
|
+
* this scan refused to follow, as written, each with its reason (#4930).
|
|
109
119
|
*/
|
|
110
120
|
function referenceIncludes(content) {
|
|
111
121
|
const seen = new Set();
|
|
112
|
-
const
|
|
122
|
+
const declined = new Map();
|
|
123
|
+
// Both spellings the agent corpus has carried: the installed-path form the
|
|
124
|
+
// installer rewrites per profile, and the bare repo-relative `@gsd-core/…`
|
|
125
|
+
// form (#4841) that it does not. The bare form is now refused in agents/ by
|
|
126
|
+
// tests/shipped-reference-cites.test.cjs; it is followed here so a pointer
|
|
127
|
+
// that slips past that gate is still scanned rather than silently dropped.
|
|
128
|
+
//
|
|
129
|
+
// THE TOKEN IS CAPTURED WHOLE AND THE NAME GRAMMAR IS ANCHORED AT BOTH ENDS.
|
|
130
|
+
// Nested names are allowed (`few-shot-examples/verifier.md`); every segment
|
|
131
|
+
// starts with `[A-Za-z0-9_-]` — narrower than "non-dot", since `+x.md` and
|
|
132
|
+
// `é.md` do not match either — so a `.` or `..` segment is never a name.
|
|
133
|
+
// Anchoring is what makes that a statement about the whole pointer rather
|
|
134
|
+
// than about its first few segments: an earlier form of this pattern ended
|
|
135
|
+
// at `\.md` with no following boundary, so `…/references/tdd.md/xx/yy` was
|
|
136
|
+
// followed as `tdd.md`, and THIS FUNCTION'S CALLER READS THE PATH IT
|
|
137
|
+
// RETURNS — `fs.readFileSync` in main()'s agent loop — so a truncated prefix
|
|
138
|
+
// folded the wrong file's text into the scanned corpus. Matching the whole
|
|
139
|
+
// whitespace-delimited token and requiring it to satisfy the grammar end to
|
|
140
|
+
// end closes that by construction, and for any separator spelling rather
|
|
141
|
+
// than the ones a boundary lookahead happens to enumerate.
|
|
142
|
+
//
|
|
143
|
+
// ANCHORING DOES NOT ESTABLISH CONTAINMENT, and an earlier revision of this
|
|
144
|
+
// comment said it did. The grammar's refusal of `.`/`..` segments covers the
|
|
145
|
+
// TEXTUAL half only: an intermediate component that refers outside the tree
|
|
146
|
+
// is resolved transparently on the way to the file. Containment is decided
|
|
147
|
+
// separately, below, by `tryWithinRoot` — and it has to be, because this
|
|
148
|
+
// loop READS what it resolves.
|
|
149
|
+
//
|
|
150
|
+
// A token that does not parse WHOLE is declined rather than truncated — the
|
|
151
|
+
// conservative half of the same rule — and the decline is RETURNED, not
|
|
152
|
+
// dropped (#4930): main() reports it as unresolved_reference_include. A
|
|
153
|
+
// skipped pointer is a scan this tool did not perform, and the verdict it
|
|
154
|
+
// prints depends on that scan, so the skip has to be visible in the tool's
|
|
155
|
+
// own output. tests/shipped-reference-cites.test.cjs reports the same token
|
|
156
|
+
// for the shipped agents/ tree; this is the half that holds for any --root.
|
|
157
|
+
// FOUR spellings. `@$HOME/.claude/` is a second installed-path form the installer rewrites
|
|
158
|
+
// explicitly (applyAgentPathRewritesInner's `/\$HOME\/\.claude\//g` replace, beside the `~/.claude/`
|
|
159
|
+
// one). The fourth is a FAMILY rather than a string: `--relative-includes` (#4377) makes a local
|
|
160
|
+
// install emit project-relative includes whose prefix is DERIVED from the resolved config dir, so it
|
|
161
|
+
// is matched by SHAPE — one or MORE leading segments before `gsd-core` (a config dir nested under
|
|
162
|
+
// the project root emits `@config/nested/gsd-core/…`), none of them `.` or `..`. The `+` keeps the
|
|
163
|
+
// bare form the bare form by construction, since it needs a segment BEFORE `gsd-core`.
|
|
164
|
+
//
|
|
165
|
+
// THE SHAPE IS NARROWER THAN THE FAMILY, and saying otherwise would be the overstatement this
|
|
166
|
+
// function has already had to retract once. `_computePathPrefix` can emit a prefix this character
|
|
167
|
+
// class does not match — a config dir named `config+nested` or `ümlaut` — and widening the class to
|
|
168
|
+
// arbitrary directory names is what would turn every `@scope/…` token in prose into a pointer. So a
|
|
169
|
+
// prefix outside the class is not followed, which is where this follower was for ALL
|
|
170
|
+
// project-relative spellings before #4841. The gate's own comment states the same bound; the two
|
|
171
|
+
// must not drift apart, because between them they are the only record of it.
|
|
172
|
+
const re = /@(?:(?:~|\$HOME)\/\.claude\/|(?:(?!\.\.?\/)[A-Za-z0-9._-]+\/)+)?gsd-core\/references\/(\S+)/g;
|
|
173
|
+
const name = /^(?:[A-Za-z0-9_-][A-Za-z0-9._-]*\/)*[A-Za-z0-9_-][A-Za-z0-9._-]*\.md$/;
|
|
174
|
+
// Trailing prose punctuation is not part of a filename — a pointer may end a
|
|
175
|
+
// sentence or sit inside backticks, parentheses or bold markers. The class
|
|
176
|
+
// cannot eat into `.md`, which ends at `d`.
|
|
177
|
+
// START-ANCHORED: this tests a CUT SUFFIX, so it must be punctuation END TO END. An unanchored
|
|
178
|
+
// `/…$/` answers true for `/xx?`, which would let the loop cut a separator and call the remainder a
|
|
179
|
+
// name — `tdd.md/xx?` following as `tdd.md`, the defect this whole function was rewritten to close.
|
|
180
|
+
const trailingProseOnly = /^[.,;:!?)\]}>"'`*]+$/;
|
|
113
181
|
let m;
|
|
114
182
|
while ((m = re.exec(content)) !== null) {
|
|
115
|
-
const
|
|
116
|
-
|
|
183
|
+
const raw = m[1];
|
|
184
|
+
// MINIMAL strip, same rule as the gate: shortest trailing run whose removal yields a valid name.
|
|
185
|
+
let candidate = null;
|
|
186
|
+
for (let cut = 0; cut <= raw.length; cut++) {
|
|
187
|
+
const probe = raw.slice(0, raw.length - cut);
|
|
188
|
+
if (cut > 0 && !trailingProseOnly.test(raw.slice(raw.length - cut))) break;
|
|
189
|
+
if (name.test(probe)) { candidate = probe; break; }
|
|
190
|
+
}
|
|
191
|
+
if (candidate === null) {
|
|
192
|
+
declined.set(m[0], 'does not name a reference whole — the text continues past the name, so nothing is followed');
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// AMBIGUITY, mirrored from the gate. If the UNSTRIPPED token also names something, the strip would
|
|
196
|
+
// pick one of two readings — and this loop READS what it picks, so it declines rather than guess.
|
|
197
|
+
if (candidate !== raw && fs.existsSync(path.join(ROOT, 'gsd-core', 'references', raw))) {
|
|
198
|
+
declined.set(m[0], `is ambiguous — \`${raw}\` itself names something on disk, so stripping it to \`${candidate}\` would pick one of two readings; neither is followed`);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
seen.add('gsd-core/references/' + candidate);
|
|
117
202
|
}
|
|
118
|
-
return [...seen];
|
|
203
|
+
return { includes: [...seen], declined: [...declined].map(([pointer, reason]) => ({ pointer, reason })) };
|
|
119
204
|
}
|
|
120
205
|
|
|
121
206
|
function remedyFor(kind) {
|
|
@@ -158,9 +243,13 @@ function main() {
|
|
|
158
243
|
const candidateMarkers = new Map();
|
|
159
244
|
const agentTexts = new Map();
|
|
160
245
|
const unclosedFenceViolations = [];
|
|
246
|
+
const includeViolations = [];
|
|
161
247
|
|
|
248
|
+
// #4407: .compact.md variant siblings are an alternate rendering of their
|
|
249
|
+
// canonical agent's SAME contract, not a distinct one — excluded so they
|
|
250
|
+
// don't need (and can't drift from) their own registry row.
|
|
162
251
|
const agentFiles = fs.existsSync(AGENTS_DIR)
|
|
163
|
-
? fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md'))
|
|
252
|
+
? fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md') && !f.endsWith('.compact.md'))
|
|
164
253
|
: [];
|
|
165
254
|
|
|
166
255
|
for (const file of agentFiles) {
|
|
@@ -170,12 +259,41 @@ function main() {
|
|
|
170
259
|
// Single pass over the agent's @-included references: each file is read
|
|
171
260
|
// once and feeds BOTH the read-tag fold (agentTexts) and marker
|
|
172
261
|
// extraction (producer/candidate attribution).
|
|
262
|
+
// #4930: every include this loop does NOT fold in is reported, never
|
|
263
|
+
// swallowed. An unfollowed include is text the marker verdict below was
|
|
264
|
+
// computed without, so a decline that leaves no trace in the output makes
|
|
265
|
+
// that verdict unexplainable. (lint-command-contract's @-ref existence rule
|
|
266
|
+
// covers commands' <execution_context> only, not agents.)
|
|
173
267
|
const includeTexts = [];
|
|
174
|
-
|
|
268
|
+
const reportInclude = (pointer, reason) => includeViolations.push({
|
|
269
|
+
kind: 'unresolved_reference_include',
|
|
270
|
+
agent,
|
|
271
|
+
marker: null,
|
|
272
|
+
detail: `${toRepoRelative(abs)}: ${pointer} ${reason}`,
|
|
273
|
+
});
|
|
274
|
+
const { includes, declined } = referenceIncludes(content);
|
|
275
|
+
for (const { pointer, reason } of declined) reportInclude(pointer, reason);
|
|
276
|
+
for (const refRelPath of includes) {
|
|
277
|
+
// CONTAIN BEFORE READ. The name grammar refuses `.`/`..` segments, which covers the TEXTUAL
|
|
278
|
+
// half of containment and nothing else: an intermediate path component that refers outside
|
|
279
|
+
// the tree is resolved transparently on the way to the file, so a textually-clean name can
|
|
280
|
+
// still address something outside `references/`. This loop READS what it resolves and folds
|
|
281
|
+
// the text into the scanned corpus, so the real path is what has to be checked.
|
|
282
|
+
const refsDir = path.join(ROOT, 'gsd-core', 'references');
|
|
283
|
+
const contained = tryWithinRoot(refRelPath.slice('gsd-core/references/'.length), refsDir);
|
|
284
|
+
if (contained === null) {
|
|
285
|
+
reportInclude(refRelPath, 'does not resolve inside gsd-core/references/');
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
175
288
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
289
|
+
// Read the ContainedPath the predicate returned, never a re-joined path (ADR-4650).
|
|
290
|
+
if (!fs.lstatSync(contained).isFile()) {
|
|
291
|
+
reportInclude(refRelPath, 'is not a regular file');
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
includeTexts.push(fs.readFileSync(contained, 'utf-8'));
|
|
295
|
+
} catch (e) {
|
|
296
|
+
reportInclude(refRelPath, e && e.code === 'ENOENT' ? 'does not exist' : `could not be read (${e && e.code})`);
|
|
179
297
|
}
|
|
180
298
|
}
|
|
181
299
|
agentTexts.set(agent, [content, ...includeTexts].join('\n'));
|
|
@@ -234,6 +352,7 @@ function main() {
|
|
|
234
352
|
const allViolations = [
|
|
235
353
|
...parseViolations,
|
|
236
354
|
...unclosedFenceViolations,
|
|
355
|
+
...includeViolations,
|
|
237
356
|
...contractViolationsList,
|
|
238
357
|
...readTagViolationsList,
|
|
239
358
|
...reverseViolationsList,
|
package/scripts/check-env.cjs
CHANGED
|
@@ -30,7 +30,17 @@ const path = require('path');
|
|
|
30
30
|
const { spawnSync } = require('child_process');
|
|
31
31
|
|
|
32
32
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
33
|
+
const { describeNpmVersionCheckFailure } = require('./lib/npm-version-check-diagnosis.cjs');
|
|
33
34
|
|
|
35
|
+
// #4460 follow-up: check-env.cjs runs as its own standalone CI step BEFORE
|
|
36
|
+
// `npm ci` / `npm run build:lib` (a deliberate pre-flight, run before there
|
|
37
|
+
// is even a node_modules to build with) -- confirmed the hard way, by a
|
|
38
|
+
// MODULE_NOT_FOUND crash on every real CI platform after a first attempt at
|
|
39
|
+
// this fix routed the npm-version check through the canonical execNpm seam
|
|
40
|
+
// (gsd-core/bin/lib/shell-command-projection.cjs), a tsc-compiled artifact
|
|
41
|
+
// that plain does not exist yet at that point in the pipeline. This file
|
|
42
|
+
// must stay self-contained: no requires reaching into gsd-core/bin/lib.
|
|
43
|
+
//
|
|
34
44
|
// On Windows, npm ships as npm.cmd (a batch wrapper); spawnSync without
|
|
35
45
|
// shell:true requires the exact filename including extension.
|
|
36
46
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
@@ -180,18 +190,36 @@ function main() {
|
|
|
180
190
|
// Check 2: npm version vs engines.npm (skip if field absent)
|
|
181
191
|
// ---------------------------------------------------------------------------
|
|
182
192
|
const enginesNpm = pkgField('engines.npm', PROJECT_ROOT);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
// #4460: 15_000ms, not the original 10_000 -- matches the default this
|
|
194
|
+
// repo's canonical (but here unusable, see the file-header note above)
|
|
195
|
+
// execNpm seam already uses for npm subprocess calls generally, rather
|
|
196
|
+
// than inventing a new number. Confirmed via real Windows CI: a 10s
|
|
197
|
+
// window was insufficient twice under ~51-file concurrent test load.
|
|
198
|
+
const NPM_VERSION_TIMEOUT_MS = 15_000;
|
|
199
|
+
// No try/catch needed: spawnSync's documented contract routes ENOENT and a
|
|
200
|
+
// timeout-triggered kill through the RETURNED result's `.error` field, not
|
|
201
|
+
// a thrown exception -- there is nothing here for a catch to intercept.
|
|
202
|
+
const npmVersionSpawn = spawnSync(npmCmd, ['--version'], { encoding: 'utf8', timeout: NPM_VERSION_TIMEOUT_MS, shell: process.platform === 'win32' });
|
|
203
|
+
const npmVersionResult = {
|
|
204
|
+
exitCode: npmVersionSpawn.status ?? 1,
|
|
205
|
+
stdout: (npmVersionSpawn.stdout || '').toString().trim(),
|
|
206
|
+
signal: npmVersionSpawn.signal ?? null,
|
|
207
|
+
error: npmVersionSpawn.error ?? null,
|
|
208
|
+
// Canonical cross-platform timeout predicate (matches this repo's
|
|
209
|
+
// execNpm/isSpawnTimeout convention, src/shell-command-projection.cts):
|
|
210
|
+
// error.code === 'ETIMEDOUT', which Node's spawnSync guarantees when its
|
|
211
|
+
// own `timeout` option fires. Checking `signal === 'SIGTERM'` instead
|
|
212
|
+
// (what an earlier version of this fix did) is platform-fragile -- that
|
|
213
|
+
// module's own docstring flags a Windows-specific false-negative risk,
|
|
214
|
+
// the exact platform this bug was discovered on.
|
|
215
|
+
timedOut: (npmVersionSpawn.error && npmVersionSpawn.error.code === 'ETIMEDOUT') === true,
|
|
216
|
+
};
|
|
217
|
+
const currentNpm = npmVersionResult.exitCode === 0 && npmVersionResult.stdout ? npmVersionResult.stdout : '';
|
|
190
218
|
|
|
191
219
|
if (!enginesNpm) {
|
|
192
220
|
addCheck('npm-version', 'skip', 'engines.npm not set in package.json — skipping');
|
|
193
221
|
} else if (!currentNpm) {
|
|
194
|
-
addCheck('npm-version', 'fail',
|
|
222
|
+
addCheck('npm-version', 'fail', describeNpmVersionCheckFailure(npmVersionResult));
|
|
195
223
|
} else {
|
|
196
224
|
if (satisfiesConstraint(currentNpm, enginesNpm)) {
|
|
197
225
|
addCheck('npm-version', 'pass', `npm ${currentNpm} satisfies ${enginesNpm}`);
|
|
@@ -37,6 +37,7 @@ const fs = require('node:fs');
|
|
|
37
37
|
const path = require('node:path');
|
|
38
38
|
|
|
39
39
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
40
|
+
const { tryWithinRoot, PathAcceptance } = require('../gsd-core/bin/lib/security.cjs');
|
|
40
41
|
|
|
41
42
|
const ROOT = path.resolve(__dirname, '..');
|
|
42
43
|
const CONTEXT_PATH = path.join(ROOT, 'CONTEXT.md');
|
|
@@ -131,20 +132,6 @@ function isTracked(token) {
|
|
|
131
132
|
return TRACKED_EXACT.has(token) || TRACKED_PREFIXES.some((prefix) => token.startsWith(prefix));
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
/**
|
|
135
|
-
* True if joining `token` to ROOT stays inside ROOT. `PATH_TOKEN_RE` admits `.`
|
|
136
|
-
* inside a segment, so a token like `src/../../../etc/passwd` matches and (via
|
|
137
|
-
* the `src/` prefix) reads as "tracked" — `path.join(ROOT, token)` would then
|
|
138
|
-
* normalize to an out-of-tree absolute path and `fs.existsSync` would probe it,
|
|
139
|
-
* turning a doc lint into a filesystem-existence oracle on the CI host. A
|
|
140
|
-
* CONTEXT.md reference is always a plain in-repo path, so a `..` escape is never
|
|
141
|
-
* legitimate: confine to ROOT and drop anything that climbs out.
|
|
142
|
-
*/
|
|
143
|
-
function isWithinRoot(token) {
|
|
144
|
-
const resolved = path.resolve(ROOT, token);
|
|
145
|
-
return resolved === ROOT || resolved.startsWith(ROOT + path.sep);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
135
|
/**
|
|
149
136
|
* Every distinct, trackable file-path token referenced in `text`, with any
|
|
150
137
|
* trailing `:<line>` suffix stripped.
|
|
@@ -167,7 +154,11 @@ function isWithinRoot(token) {
|
|
|
167
154
|
* (CONTRIBUTING's "Do not compute a next number locally"), never a real path.
|
|
168
155
|
*/
|
|
169
156
|
function extractTrackedRefs(text) {
|
|
170
|
-
|
|
157
|
+
// Maps token -> the ContainedPath tryWithinRoot returned for it. ADR-4650:
|
|
158
|
+
// the value that was validated for containment must be the exact value
|
|
159
|
+
// that gets probed later — never a path re-derived (e.g. re-joined) from
|
|
160
|
+
// the token, which could diverge from what was actually checked.
|
|
161
|
+
const tokens = new Map();
|
|
171
162
|
const add = (raw) => {
|
|
172
163
|
if (!PATH_TOKEN_RE.test(raw)) return;
|
|
173
164
|
const token = raw.replace(/:\d+$/, '');
|
|
@@ -177,8 +168,18 @@ function extractTrackedRefs(text) {
|
|
|
177
168
|
if (!/[A-Za-z0-9_]$/.test(token)) return;
|
|
178
169
|
if (token.includes('NNNN')) return;
|
|
179
170
|
if (!isTracked(token)) return;
|
|
180
|
-
|
|
181
|
-
|
|
171
|
+
// Containment decision is the canonical predicate's, per ADR-4650. Carry
|
|
172
|
+
// the returned ContainedPath forward so checkFileRefs stats the SAME
|
|
173
|
+
// value that was validated, instead of re-joining `token` onto ROOT.
|
|
174
|
+
//
|
|
175
|
+
// The containment ANSWER is unchanged from the retired lexical-only
|
|
176
|
+
// `isWithinRoot`, but the canonical predicate resolves symlinks, so a
|
|
177
|
+
// rejected token is now realpath-resolved before being rejected rather
|
|
178
|
+
// than rejected by string comparison alone; the result is still never
|
|
179
|
+
// surfaced and the token is never stat'd unless it is contained.
|
|
180
|
+
const contained = tryWithinRoot(token, ROOT, PathAcceptance.AbsoluteInsideRoot);
|
|
181
|
+
if (contained === null) return;
|
|
182
|
+
tokens.set(token, contained);
|
|
182
183
|
};
|
|
183
184
|
const subTokenRe = /[\w.-]+(?:\/[\w.-]+)*/g;
|
|
184
185
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -199,14 +200,17 @@ function extractTrackedRefs(text) {
|
|
|
199
200
|
|
|
200
201
|
/** Check A: every tracked reference must resolve on disk. */
|
|
201
202
|
function checkFileRefs(contextText) {
|
|
202
|
-
const
|
|
203
|
+
const entries = [...extractTrackedRefs(contextText)].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
203
204
|
const findings = [];
|
|
204
|
-
for (const token of
|
|
205
|
-
|
|
205
|
+
for (const [token, contained] of entries) {
|
|
206
|
+
// Stat the ContainedPath returned by tryWithinRoot — NOT a re-joined
|
|
207
|
+
// path.join(ROOT, token) — so the path that was validated for
|
|
208
|
+
// containment is the path that is probed (ADR-4650).
|
|
209
|
+
if (!fs.existsSync(contained)) {
|
|
206
210
|
findings.push(`CONTEXT.md references \`${token}\` which does not exist in the repo.`);
|
|
207
211
|
}
|
|
208
212
|
}
|
|
209
|
-
return { findings, checked:
|
|
213
|
+
return { findings, checked: entries.length };
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
/** The glossary's own claim: `Runtime enum: `allRuntimes` (N values: a, b, c)`. */
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// ci-next-health.cjs — #4422 base-branch health gate.
|
|
5
|
+
//
|
|
6
|
+
// Risk asymmetry drives this design, same shape as scripts/ci-pr-mergeability.cjs
|
|
7
|
+
// but pointed the other direction: on 2026-09-06 three unrelated PRs merged on
|
|
8
|
+
// top of an already-broken `next` before anyone noticed, because nothing checked
|
|
9
|
+
// the base branch's OWN health before letting a PR land on it. A false positive
|
|
10
|
+
// here (a healthy `next` wrongly reported RED) blocks every PR merge in the repo
|
|
11
|
+
// until a human notices and applies the `fix-next` bypass label — annoying, but
|
|
12
|
+
// loud and immediately actionable. A false negative (a broken `next` reported
|
|
13
|
+
// healthy) reproduces the #4422 incident exactly. So, unlike the mergeability
|
|
14
|
+
// preflight, this gate does NOT fail open on a definite red signal — it fails
|
|
15
|
+
// open only when the signal itself is unavailable or inapplicable (wrong event,
|
|
16
|
+
// no resolvable base ref, an API read that throws). Once GitHub actually answers
|
|
17
|
+
// with a non-success conclusion for the base branch's last push-triggered Tests
|
|
18
|
+
// run, that is treated as authoritative and blocks — with one explicit, visible,
|
|
19
|
+
// human-operated escape hatch (the `fix-next` label) for the PR that is itself
|
|
20
|
+
// the fix-forward.
|
|
21
|
+
//
|
|
22
|
+
// Every non-success conclusion (`failure`, `cancelled`, `timed_out`,
|
|
23
|
+
// `action_required`, ...) is treated as RED, not just `failure` — a cancelled or
|
|
24
|
+
// timed-out run on the base branch is not evidence the branch is healthy, it is
|
|
25
|
+
// evidence nobody knows yet.
|
|
26
|
+
|
|
27
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
28
|
+
|
|
29
|
+
const VERDICT = Object.freeze({
|
|
30
|
+
CLEAN: 'CLEAN',
|
|
31
|
+
RED: 'RED',
|
|
32
|
+
BYPASSED: 'BYPASSED',
|
|
33
|
+
SKIPPED_NOT_APPLICABLE: 'SKIPPED_NOT_APPLICABLE',
|
|
34
|
+
INDETERMINATE: 'INDETERMINATE',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const APPLICABLE_EVENTS = new Set(['pull_request', 'merge_group']);
|
|
38
|
+
const BYPASS_LABEL = 'fix-next';
|
|
39
|
+
const TESTS_WORKFLOW_FILE = 'test.yml';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Pure, total classifier: the GitHub "list workflow runs" response payload ->
|
|
43
|
+
* CLEAN | RED | INDETERMINATE. Never throws.
|
|
44
|
+
*
|
|
45
|
+
* - Not an object, or `workflow_runs` isn't an array -> INDETERMINATE: the
|
|
46
|
+
* shape we depend on is not present, so nothing can be concluded.
|
|
47
|
+
* - No completed push runs found yet (e.g. a brand-new `release/**`/`hotfix/**`
|
|
48
|
+
* branch with no Tests history) -> CLEAN: absence of evidence of red is not
|
|
49
|
+
* evidence of red, and a brand-new branch must not be permanently unmergeable.
|
|
50
|
+
* - The most recent run's `conclusion === 'success'` -> CLEAN.
|
|
51
|
+
* - Anything else (failure, cancelled, timed_out, action_required, ...) -> RED.
|
|
52
|
+
*/
|
|
53
|
+
function classifyRunConclusion(payload) {
|
|
54
|
+
if (payload === null || typeof payload !== 'object') return VERDICT.INDETERMINATE;
|
|
55
|
+
if (!Array.isArray(payload.workflow_runs)) return VERDICT.INDETERMINATE;
|
|
56
|
+
if (payload.workflow_runs.length === 0) return VERDICT.CLEAN;
|
|
57
|
+
const [latest] = payload.workflow_runs;
|
|
58
|
+
if (latest && latest.conclusion === 'success') return VERDICT.CLEAN;
|
|
59
|
+
return VERDICT.RED;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Dependency-injected orchestrator. `fetchLatestRun` is an async function
|
|
64
|
+
* taking the resolved base branch name and returning the parsed API payload
|
|
65
|
+
* (or throwing) — injected so tests never touch the network.
|
|
66
|
+
*
|
|
67
|
+
* @returns {Promise<{verdict:string, payload:*, reason:string}>}
|
|
68
|
+
*/
|
|
69
|
+
async function resolveNextHealth({ fetchLatestRun, eventName, baseRef, labels } = {}) {
|
|
70
|
+
if (!APPLICABLE_EVENTS.has(eventName)) {
|
|
71
|
+
return { verdict: VERDICT.SKIPPED_NOT_APPLICABLE, payload: null, reason: 'not-applicable-event' };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof baseRef !== 'string' || baseRef.trim() === '') {
|
|
75
|
+
return { verdict: VERDICT.INDETERMINATE, payload: null, reason: 'no-base-ref' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let payload = null;
|
|
79
|
+
try {
|
|
80
|
+
payload = await fetchLatestRun(baseRef);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return { verdict: VERDICT.INDETERMINATE, payload: null, reason: `fetch-failed: ${err.message}` };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const classified = classifyRunConclusion(payload);
|
|
86
|
+
if (classified !== VERDICT.RED) {
|
|
87
|
+
return { verdict: classified, payload, reason: 'resolved' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Escape hatch. `labels` is empty/absent for merge_group — that event has no
|
|
91
|
+
// label surface today, which means a queued merge-group commit cannot use
|
|
92
|
+
// this bypass. Known gap, not solved here: a maintainer must land the
|
|
93
|
+
// fix-forward as a direct pull_request merge (where the label IS readable)
|
|
94
|
+
// rather than through the merge queue, until GitHub exposes an equivalent
|
|
95
|
+
// signal for merge_group.
|
|
96
|
+
const labelList = Array.isArray(labels) ? labels : [];
|
|
97
|
+
if (labelList.includes(BYPASS_LABEL)) {
|
|
98
|
+
return { verdict: VERDICT.BYPASSED, payload, reason: 'bypass-label' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { verdict: VERDICT.RED, payload, reason: 'red' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function usage() {
|
|
105
|
+
return [
|
|
106
|
+
'Usage:',
|
|
107
|
+
' node scripts/ci-next-health.cjs',
|
|
108
|
+
'',
|
|
109
|
+
'CI-only gate: checks whether the PR/merge-group\'s base branch\'s own last',
|
|
110
|
+
'push-triggered Tests run is red, and fails the job (exit 1) when it is —',
|
|
111
|
+
'unless the PR carries the "fix-next" bypass label. Every other case',
|
|
112
|
+
'(wrong event, no resolvable base ref, an unreadable API read) fails open',
|
|
113
|
+
'(exit 0).',
|
|
114
|
+
'',
|
|
115
|
+
'Environment variables read:',
|
|
116
|
+
' GITHUB_EVENT_NAME workflow trigger event; only "pull_request" and',
|
|
117
|
+
' "merge_group" are checked',
|
|
118
|
+
' GITHUB_REPOSITORY owner/repo',
|
|
119
|
+
' GITHUB_TOKEN optional bearer token for the API read',
|
|
120
|
+
' GITHUB_BASE_REF PR base branch name (pull_request events; set',
|
|
121
|
+
' automatically by GitHub Actions)',
|
|
122
|
+
' MERGE_GROUP_BASE_REF merge-group base ref (merge_group events; the',
|
|
123
|
+
' caller workflow must populate this from',
|
|
124
|
+
' github.event.merge_group.base_ref — a leading',
|
|
125
|
+
' "refs/heads/" prefix is stripped if present)',
|
|
126
|
+
' GITHUB_API_URL GitHub API base URL (default: https://api.github.com)',
|
|
127
|
+
' PR_LABELS comma-separated PR label names (pull_request events;',
|
|
128
|
+
' the caller workflow must populate this from',
|
|
129
|
+
' github.event.pull_request.labels.*.name); checked',
|
|
130
|
+
' for the literal "fix-next" bypass label',
|
|
131
|
+
' GITHUB_OUTPUT path to append verdict= step output to',
|
|
132
|
+
' GITHUB_STEP_SUMMARY path to append a human-readable summary to',
|
|
133
|
+
].join('\n');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseArgs(argv) {
|
|
137
|
+
for (const arg of argv) {
|
|
138
|
+
if (arg === '--help' || arg === '-h') {
|
|
139
|
+
process.stdout.write(`${usage()}\n`);
|
|
140
|
+
throw new ExitError(0);
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function writeOutput(lines) {
|
|
147
|
+
const outputPath = process.env.GITHUB_OUTPUT;
|
|
148
|
+
if (typeof outputPath !== 'string' || outputPath === '') return;
|
|
149
|
+
try {
|
|
150
|
+
const fs = require('node:fs');
|
|
151
|
+
fs.appendFileSync(outputPath, `${lines.join('\n')}\n`);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
// A failure while REPORTING the verdict must never invert the gate.
|
|
154
|
+
process.stderr.write(`::warning::failed to write GITHUB_OUTPUT: ${err.message}\n`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function writeSummary(text) {
|
|
159
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
160
|
+
if (typeof summaryPath !== 'string' || summaryPath === '') return;
|
|
161
|
+
try {
|
|
162
|
+
const fs = require('node:fs');
|
|
163
|
+
fs.appendFileSync(summaryPath, `${text}\n`);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
process.stderr.write(`::warning::failed to write GITHUB_STEP_SUMMARY: ${err.message}\n`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function resolveBaseRef(eventName) {
|
|
170
|
+
if (eventName === 'pull_request') {
|
|
171
|
+
return process.env.GITHUB_BASE_REF || '';
|
|
172
|
+
}
|
|
173
|
+
if (eventName === 'merge_group') {
|
|
174
|
+
const raw = process.env.MERGE_GROUP_BASE_REF || '';
|
|
175
|
+
return raw.startsWith('refs/heads/') ? raw.slice('refs/heads/'.length) : raw;
|
|
176
|
+
}
|
|
177
|
+
return '';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function parseLabels(raw) {
|
|
181
|
+
if (typeof raw !== 'string' || raw.trim() === '') return [];
|
|
182
|
+
return raw.split(',').map((s) => s.trim()).filter((s) => s !== '');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function runUrlOf(payload) {
|
|
186
|
+
const run = payload && Array.isArray(payload.workflow_runs) ? payload.workflow_runs[0] : undefined;
|
|
187
|
+
return run && typeof run.html_url === 'string' ? run.html_url : '(unknown run URL)';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// `argv` defaults to real CLI argv but is a parameter so tests can call
|
|
191
|
+
// main() in-process (e.g. main([])) without inheriting the test runner's own
|
|
192
|
+
// argv, which would otherwise trip parseArgs's "unknown argument" branch.
|
|
193
|
+
async function main(argv = process.argv.slice(2)) {
|
|
194
|
+
parseArgs(argv);
|
|
195
|
+
|
|
196
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
197
|
+
const repo = process.env.GITHUB_REPOSITORY || '';
|
|
198
|
+
const token = process.env.GITHUB_TOKEN || '';
|
|
199
|
+
const apiBase = process.env.GITHUB_API_URL || 'https://api.github.com';
|
|
200
|
+
const baseRef = resolveBaseRef(eventName);
|
|
201
|
+
const labels = parseLabels(process.env.PR_LABELS);
|
|
202
|
+
|
|
203
|
+
const fetchLatestRun = async (branch) => {
|
|
204
|
+
const url = `${apiBase}/repos/${repo}/actions/workflows/${TESTS_WORKFLOW_FILE}/runs`
|
|
205
|
+
+ `?branch=${encodeURIComponent(branch)}&event=push&status=completed&per_page=1`;
|
|
206
|
+
const headers = {
|
|
207
|
+
accept: 'application/vnd.github+json',
|
|
208
|
+
'x-github-api-version': '2022-11-28',
|
|
209
|
+
'user-agent': 'gsd-core-ci-next-health',
|
|
210
|
+
};
|
|
211
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
212
|
+
const response = await fetch(url, { headers, signal: AbortSignal.timeout(10000) });
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
throw new Error(`GitHub API responded ${response.status}`);
|
|
215
|
+
}
|
|
216
|
+
const text = await response.text();
|
|
217
|
+
try {
|
|
218
|
+
return JSON.parse(text);
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const result = await resolveNextHealth({ fetchLatestRun, eventName, baseRef, labels });
|
|
225
|
+
|
|
226
|
+
writeOutput([`verdict=${result.verdict}`]);
|
|
227
|
+
writeSummary(`Base branch health: ${result.verdict}`);
|
|
228
|
+
|
|
229
|
+
if (result.verdict === VERDICT.RED) {
|
|
230
|
+
const runUrl = runUrlOf(result.payload);
|
|
231
|
+
process.stderr.write(
|
|
232
|
+
`::error::the base branch "${baseRef}"'s own last Tests run is red: ${runUrl} — `
|
|
233
|
+
+ 'wait for a fix-forward merge, or if THIS pull request is the fix, ask a '
|
|
234
|
+
+ `maintainer to apply the "${BYPASS_LABEL}" label to explicitly override this gate.\n`,
|
|
235
|
+
);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (result.verdict === VERDICT.BYPASSED) {
|
|
240
|
+
const runUrl = runUrlOf(result.payload);
|
|
241
|
+
process.stderr.write(
|
|
242
|
+
`::warning::the base branch "${baseRef}"'s own last Tests run is red: ${runUrl} — `
|
|
243
|
+
+ `a human applied the "${BYPASS_LABEL}" label to explicitly override this gate.\n`,
|
|
244
|
+
);
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (result.verdict === VERDICT.INDETERMINATE) {
|
|
249
|
+
process.stderr.write(
|
|
250
|
+
`::warning::could not determine "${baseRef || '(no base ref)'}"'s Tests health (${result.reason}); `
|
|
251
|
+
+ 'proceeding (fail-open).\n',
|
|
252
|
+
);
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
process.stdout.write(`Base branch health: ${result.verdict}\n`);
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
runMain(main);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = {
|
|
265
|
+
VERDICT,
|
|
266
|
+
BYPASS_LABEL,
|
|
267
|
+
TESTS_WORKFLOW_FILE,
|
|
268
|
+
classifyRunConclusion,
|
|
269
|
+
resolveNextHealth,
|
|
270
|
+
main,
|
|
271
|
+
};
|