@opengsd/gsd-core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// ci-pr-mergeability.cjs — #3833 PR mergeability preflight.
|
|
5
|
+
//
|
|
6
|
+
// Risk asymmetry drives this design: a false positive (a clean PR wrongly
|
|
7
|
+
// classified CONFLICTED) skips every gated compute lane in all eight gated
|
|
8
|
+
// workflows on EVERY PR — a repo-wide outage. A false negative (a real
|
|
9
|
+
// conflict missed) is today's behavior; the per-job scripts/ci-rebase-check.cjs
|
|
10
|
+
// backstop still catches it there. So the weight is entirely on the
|
|
11
|
+
// false-positive surface: the single predicate `mergeable === false`, and
|
|
12
|
+
// every falsy-but-not-false value around it.
|
|
13
|
+
//
|
|
14
|
+
// GitHub's `mergeable` field on GET /repos/:owner/:repo/pulls/:number is a
|
|
15
|
+
// documented tri-state: `true`, `false`, or `null` while a background
|
|
16
|
+
// computation is still running. `null` is falsy — `if (!mergeable)` would
|
|
17
|
+
// misclassify "still computing" as "conflicted" and would have red every PR
|
|
18
|
+
// in the repo the moment this job started running. Only strict `=== false`
|
|
19
|
+
// counts as a conflict.
|
|
20
|
+
//
|
|
21
|
+
// `mergeable_state` is read only for the human-facing annotation, never for
|
|
22
|
+
// the verdict: its `blocked` value means "required checks have not passed",
|
|
23
|
+
// which is true for every PR while THIS preflight job itself is one of the
|
|
24
|
+
// required checks still running — classifying on it would self-deadlock the
|
|
25
|
+
// pipeline (the gate can never go green because it is itself ungreen).
|
|
26
|
+
//
|
|
27
|
+
// Because `mergeable` starts null and GitHub computes it asynchronously, this
|
|
28
|
+
// polls with linear backoff up to a small budget, then fails OPEN
|
|
29
|
+
// (INDETERMINATE => exit 0) rather than blocking the world on an API that
|
|
30
|
+
// hasn't finished thinking.
|
|
31
|
+
|
|
32
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
33
|
+
|
|
34
|
+
const VERDICT = Object.freeze({
|
|
35
|
+
MERGEABLE: 'MERGEABLE',
|
|
36
|
+
CONFLICTED: 'CONFLICTED',
|
|
37
|
+
INDETERMINATE: 'INDETERMINATE',
|
|
38
|
+
SKIPPED_NOT_A_PR: 'SKIPPED_NOT_A_PR',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const MAX_ATTEMPTS = 5;
|
|
42
|
+
const BASE_DELAY_MS = 2000;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pure, total classifier: payload -> VERDICT atom. Never throws.
|
|
46
|
+
* Deliberately reads ONLY `mergeable`, and only with strict equality — see
|
|
47
|
+
* the header comment for why `mergeable_state` and truthiness are excluded.
|
|
48
|
+
*/
|
|
49
|
+
function classifyMergeability(payload) {
|
|
50
|
+
if (payload === null || typeof payload !== 'object') return VERDICT.INDETERMINATE;
|
|
51
|
+
if (payload.mergeable === true) return VERDICT.MERGEABLE;
|
|
52
|
+
if (payload.mergeable === false) return VERDICT.CONFLICTED;
|
|
53
|
+
return VERDICT.INDETERMINATE;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Linear backoff: attempt 1..N-1 gives 1x..(N-1)x baseMs. Non-positive/NaN
|
|
58
|
+
* attempts clamp to one base interval rather than producing 0 or NaN delays.
|
|
59
|
+
*/
|
|
60
|
+
function nextDelayMs(attempt, { baseMs = BASE_DELAY_MS } = {}) {
|
|
61
|
+
const n = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 1;
|
|
62
|
+
return baseMs * n;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Dependency-injected poll loop. Resolves as soon as GitHub reports a
|
|
67
|
+
* definite mergeable/conflicted state, or gives up after maxAttempts and
|
|
68
|
+
* reports INDETERMINATE (fail-open).
|
|
69
|
+
*
|
|
70
|
+
* @returns {Promise<{verdict:string, attempts:number, payload:*, reason:string}>}
|
|
71
|
+
*/
|
|
72
|
+
async function resolveMergeability({
|
|
73
|
+
fetchPr,
|
|
74
|
+
sleep,
|
|
75
|
+
eventName,
|
|
76
|
+
prNumber,
|
|
77
|
+
maxAttempts = MAX_ATTEMPTS,
|
|
78
|
+
baseDelayMs = BASE_DELAY_MS,
|
|
79
|
+
} = {}) {
|
|
80
|
+
if (eventName !== 'pull_request') {
|
|
81
|
+
return { verdict: VERDICT.SKIPPED_NOT_A_PR, attempts: 0, payload: null, reason: 'not-a-pull-request' };
|
|
82
|
+
}
|
|
83
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
84
|
+
return { verdict: VERDICT.INDETERMINATE, attempts: 0, payload: null, reason: 'no-pr-number' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let attempts = 0;
|
|
88
|
+
let lastPayload = null;
|
|
89
|
+
let lastError = null;
|
|
90
|
+
|
|
91
|
+
for (let i = 1; i <= maxAttempts; i++) {
|
|
92
|
+
attempts = i;
|
|
93
|
+
let payload;
|
|
94
|
+
try {
|
|
95
|
+
payload = await fetchPr(prNumber);
|
|
96
|
+
lastPayload = payload;
|
|
97
|
+
lastError = null;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
lastError = err;
|
|
100
|
+
payload = null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const verdict = classifyMergeability(payload);
|
|
104
|
+
if (verdict === VERDICT.MERGEABLE || verdict === VERDICT.CONFLICTED) {
|
|
105
|
+
return { verdict, attempts, payload, reason: 'resolved' };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (i < maxAttempts) {
|
|
109
|
+
await sleep(nextDelayMs(i, { baseMs: baseDelayMs }));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
verdict: VERDICT.INDETERMINATE,
|
|
115
|
+
attempts,
|
|
116
|
+
payload: lastPayload,
|
|
117
|
+
reason: lastError ? 'fetch-failed' : 'mergeability-not-computed',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function usage() {
|
|
122
|
+
return [
|
|
123
|
+
'Usage:',
|
|
124
|
+
' node scripts/ci-pr-mergeability.cjs',
|
|
125
|
+
'',
|
|
126
|
+
'CI-only preflight: polls the GitHub API for a PR\'s mergeable state and',
|
|
127
|
+
'fails the job (exit 1) only on a definite conflict. Everything else',
|
|
128
|
+
'(unknown, unreadable, not a PR) fails open (exit 0).',
|
|
129
|
+
'',
|
|
130
|
+
'Environment variables read:',
|
|
131
|
+
' GITHUB_EVENT_NAME workflow trigger event; only "pull_request" is polled',
|
|
132
|
+
' GITHUB_REPOSITORY owner/repo',
|
|
133
|
+
' GITHUB_TOKEN optional bearer token for the API read',
|
|
134
|
+
' GITHUB_BASE_REF PR base branch name (default: next)',
|
|
135
|
+
' GITHUB_API_URL GitHub API base URL (default: https://api.github.com)',
|
|
136
|
+
' PR_NUMBER the pull request number',
|
|
137
|
+
' GITHUB_OUTPUT path to append verdict=/mergeable= step outputs to',
|
|
138
|
+
' GITHUB_STEP_SUMMARY path to append a human-readable summary to',
|
|
139
|
+
' GSD_PR_MERGEABILITY_BASE_DELAY_MS optional numeric override of the base backoff, for tests',
|
|
140
|
+
].join('\n');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseArgs(argv) {
|
|
144
|
+
for (const arg of argv) {
|
|
145
|
+
if (arg === '--help' || arg === '-h') {
|
|
146
|
+
process.stdout.write(`${usage()}\n`);
|
|
147
|
+
throw new ExitError(0);
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function writeOutput(lines) {
|
|
154
|
+
const outputPath = process.env.GITHUB_OUTPUT;
|
|
155
|
+
if (typeof outputPath !== 'string' || outputPath === '') return;
|
|
156
|
+
try {
|
|
157
|
+
const fs = require('node:fs');
|
|
158
|
+
fs.appendFileSync(outputPath, `${lines.join('\n')}\n`);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// A failure while REPORTING the verdict must never invert the gate.
|
|
161
|
+
process.stderr.write(`::warning::failed to write GITHUB_OUTPUT: ${err.message}\n`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function writeSummary(text) {
|
|
166
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
167
|
+
if (typeof summaryPath !== 'string' || summaryPath === '') return;
|
|
168
|
+
try {
|
|
169
|
+
const fs = require('node:fs');
|
|
170
|
+
fs.appendFileSync(summaryPath, `${text}\n`);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
process.stderr.write(`::warning::failed to write GITHUB_STEP_SUMMARY: ${err.message}\n`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// `argv` defaults to real CLI argv but is a parameter so tests can call
|
|
177
|
+
// main() in-process (e.g. main([])) without inheriting the test runner's own
|
|
178
|
+
// argv, which would otherwise trip parseArgs's "unknown argument" branch.
|
|
179
|
+
async function main(argv = process.argv.slice(2)) {
|
|
180
|
+
parseArgs(argv);
|
|
181
|
+
|
|
182
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
183
|
+
const repo = process.env.GITHUB_REPOSITORY || '';
|
|
184
|
+
const token = process.env.GITHUB_TOKEN || '';
|
|
185
|
+
const baseBranch = process.env.GITHUB_BASE_REF || 'next';
|
|
186
|
+
const apiBase = process.env.GITHUB_API_URL || 'https://api.github.com';
|
|
187
|
+
const prNumberRaw = process.env.PR_NUMBER;
|
|
188
|
+
const prNumber = Number.parseInt(prNumberRaw, 10);
|
|
189
|
+
|
|
190
|
+
const overrideDelay = Number(process.env.GSD_PR_MERGEABILITY_BASE_DELAY_MS);
|
|
191
|
+
const baseDelayMs = Number.isFinite(overrideDelay) && overrideDelay >= 0 ? overrideDelay : BASE_DELAY_MS;
|
|
192
|
+
|
|
193
|
+
const fetchPr = async (number) => {
|
|
194
|
+
const url = `${apiBase}/repos/${repo}/pulls/${number}`;
|
|
195
|
+
const headers = {
|
|
196
|
+
accept: 'application/vnd.github+json',
|
|
197
|
+
'x-github-api-version': '2022-11-28',
|
|
198
|
+
'user-agent': 'gsd-core-ci-pr-mergeability',
|
|
199
|
+
};
|
|
200
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
201
|
+
const response = await fetch(url, { headers, signal: AbortSignal.timeout(10000) });
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error(`GitHub API responded ${response.status}`);
|
|
204
|
+
}
|
|
205
|
+
const text = await response.text();
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(text);
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
214
|
+
|
|
215
|
+
const result = await resolveMergeability({
|
|
216
|
+
fetchPr,
|
|
217
|
+
sleep,
|
|
218
|
+
eventName,
|
|
219
|
+
prNumber: Number.isNaN(prNumber) ? NaN : prNumber,
|
|
220
|
+
maxAttempts: MAX_ATTEMPTS,
|
|
221
|
+
baseDelayMs,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const mergeable = result.verdict === VERDICT.CONFLICTED ? 'false' : 'true';
|
|
225
|
+
writeOutput([`verdict=${result.verdict}`, `mergeable=${mergeable}`]);
|
|
226
|
+
writeSummary(`PR mergeability preflight: ${result.verdict}`);
|
|
227
|
+
|
|
228
|
+
if (result.verdict === VERDICT.CONFLICTED) {
|
|
229
|
+
process.stderr.write(
|
|
230
|
+
`::error::This pull request cannot cleanly merge ${baseBranch}. Rebase onto current ${baseBranch} and push again; no CI will run until the conflict is resolved.\n`,
|
|
231
|
+
);
|
|
232
|
+
const state = result.payload && typeof result.payload === 'object' ? result.payload.mergeable_state : undefined;
|
|
233
|
+
if (state) {
|
|
234
|
+
process.stderr.write(`::error::mergeable_state: ${state}\n`);
|
|
235
|
+
}
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (result.verdict === VERDICT.INDETERMINATE) {
|
|
240
|
+
process.stderr.write(
|
|
241
|
+
'::warning::GitHub did not report mergeability within the polling budget; proceeding (fail-open). scripts/ci-rebase-check.cjs remains the in-job backstop.\n',
|
|
242
|
+
);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
process.stdout.write(`PR mergeability preflight: ${result.verdict}\n`);
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (require.main === module) {
|
|
251
|
+
runMain(main);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = {
|
|
255
|
+
VERDICT,
|
|
256
|
+
MAX_ATTEMPTS,
|
|
257
|
+
BASE_DELAY_MS,
|
|
258
|
+
classifyMergeability,
|
|
259
|
+
nextDelayMs,
|
|
260
|
+
resolveMergeability,
|
|
261
|
+
main,
|
|
262
|
+
};
|
|
@@ -39,6 +39,10 @@ const PROTECTED_WORKFLOWS = new Set([
|
|
|
39
39
|
'mutation.yml',
|
|
40
40
|
'security-scan.yml',
|
|
41
41
|
'release.yml',
|
|
42
|
+
// #3833: the reusable gate every `pull_request` compute lane depends on —
|
|
43
|
+
// marking it inert would let a change to the gate itself ship without ever
|
|
44
|
+
// running the full matrix it is responsible for enforcing.
|
|
45
|
+
'pr-mergeable-preflight.yml',
|
|
42
46
|
]);
|
|
43
47
|
for (const wf of PROTECTED_WORKFLOWS) {
|
|
44
48
|
if (INERT_WORKFLOWS.has(wf)) {
|
|
@@ -93,6 +97,10 @@ const RULES = [
|
|
|
93
97
|
tests: [
|
|
94
98
|
'tests/run-tests-harness.test.cjs',
|
|
95
99
|
'tests/workflow-shell-pinning.test.cjs',
|
|
100
|
+
// #4220: the run-scoped temp root + computeSweepProtectSet ancestor-walk
|
|
101
|
+
// termination coverage — was previously not re-selected by an edit to
|
|
102
|
+
// scripts/run-tests.cjs, the exact file that shipped the #4020 hang.
|
|
103
|
+
'tests/run-tests-temp-root.test.cjs',
|
|
96
104
|
],
|
|
97
105
|
},
|
|
98
106
|
{
|
|
@@ -298,6 +306,9 @@ const RULES = [
|
|
|
298
306
|
'tests/require-userprofile-with-home.rule.test.cjs',
|
|
299
307
|
'tests/normalize-path-in-content.rule.test.cjs',
|
|
300
308
|
'tests/require-fs-op-fallback.rule.test.cjs',
|
|
309
|
+
// #4244 (origin #4020/#4220 Windows CI hang) — see ADR-1703 amendment.
|
|
310
|
+
'tests/require-full-tmpdir-triad.rule.test.cjs',
|
|
311
|
+
'tests/no-unbounded-dirname-walk.rule.test.cjs',
|
|
301
312
|
],
|
|
302
313
|
},
|
|
303
314
|
{
|
|
@@ -341,19 +352,48 @@ function missingRuleTestFiles(rules) {
|
|
|
341
352
|
return [...referenced].filter(f => !existsSync(path.join(__dirname, '..', f))).sort();
|
|
342
353
|
}
|
|
343
354
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Every PROTECTED_WORKFLOWS member that does not exist on disk.
|
|
357
|
+
* The name list and the real filenames are two surfaces over one fact
|
|
358
|
+
* (#3833): without this, renaming or deleting a gating workflow silently
|
|
359
|
+
* un-protects it and CI stays green while the protection is gone. Same
|
|
360
|
+
* shape and rationale as missingRuleTestFiles() above.
|
|
361
|
+
*/
|
|
362
|
+
function missingProtectedWorkflows(names = PROTECTED_WORKFLOWS) {
|
|
363
|
+
return [...names]
|
|
364
|
+
.filter(name => !existsSync(path.join(__dirname, '..', '.github', 'workflows', name)))
|
|
365
|
+
.sort();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Shared module-load assertion for the two "this list names something that
|
|
370
|
+
* does not exist on disk" guards above. Both are silent-coverage-hole guards:
|
|
371
|
+
* a name that no longer resolves stops selecting/protecting anything while CI
|
|
372
|
+
* stays green, so both must fail loudly at load rather than at test time.
|
|
373
|
+
*/
|
|
374
|
+
function assertNoneMissing(missing, summary, issueRef) {
|
|
375
|
+
if (missing.length === 0) return;
|
|
376
|
+
throw new Error(`ci-test-scope: ${summary} (${issueRef}):\n ${missing.join('\n ')}`);
|
|
355
377
|
}
|
|
356
378
|
|
|
379
|
+
// Fail loudly at module load — this fires on EVERY invocation of the CLI
|
|
380
|
+
// (including the real `changes` job in .github/workflows/test.yml), not only
|
|
381
|
+
// when a test suite happens to run.
|
|
382
|
+
assertNoneMissing(
|
|
383
|
+
missingRuleTestFiles(RULES),
|
|
384
|
+
'RULES reference test file(s) that do not exist on disk (silent coverage hole)',
|
|
385
|
+
'see #2758',
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// A PROTECTED_WORKFLOWS entry naming a file that does not exist means the
|
|
389
|
+
// workflow was renamed or deleted without updating this list, silently
|
|
390
|
+
// un-protecting it while CI stays green.
|
|
391
|
+
assertNoneMissing(
|
|
392
|
+
missingProtectedWorkflows(PROTECTED_WORKFLOWS),
|
|
393
|
+
'PROTECTED_WORKFLOWS reference workflow file(s) that do not exist on disk (silent protection hole)',
|
|
394
|
+
'see #3833',
|
|
395
|
+
);
|
|
396
|
+
|
|
357
397
|
function usage() {
|
|
358
398
|
return [
|
|
359
399
|
'Usage:',
|
|
@@ -590,4 +630,4 @@ if (require.main === module) {
|
|
|
590
630
|
runMain(main);
|
|
591
631
|
}
|
|
592
632
|
|
|
593
|
-
module.exports = { RULES, missingRuleTestFiles };
|
|
633
|
+
module.exports = { RULES, missingRuleTestFiles, PROTECTED_WORKFLOWS, INERT_WORKFLOWS, missingProtectedWorkflows };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/ci-timeout-report.cjs
|
|
5
|
+
*
|
|
6
|
+
* Scheduled CI-timeout trending report (#4036). Polls GitHub's Actions REST
|
|
7
|
+
* API for recently completed jobs across test.yml, mutation.yml, and
|
|
8
|
+
* install-smoke.yml, resolves each job's declared `timeout-minutes` budget,
|
|
9
|
+
* computes elapsed-vs-cap via scripts/lib/ci-job-timing.cjs, and appends
|
|
10
|
+
* new (never-before-seen) records to a JSONL history file. Every record also
|
|
11
|
+
* carries the triggering `runEvent` (e.g. `push`/`pull_request`) so entries
|
|
12
|
+
* for jobs whose matrix genuinely differs by trigger (e.g. `smoke`'s
|
|
13
|
+
* push-only macOS row) can be told apart in the persisted trend — records
|
|
14
|
+
* are never filtered by event, only labeled.
|
|
15
|
+
*
|
|
16
|
+
* Invoked from a GitHub Actions workflow via actions/github-script, e.g.:
|
|
17
|
+
* const report = require(`${process.env.GITHUB_WORKSPACE}/scripts/ci-timeout-report.cjs`);
|
|
18
|
+
* const result = await report.main({ github, context, core });
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const yaml = require('js-yaml');
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const {
|
|
25
|
+
computeElapsedPct, isNearCap, formatNearCapNotice,
|
|
26
|
+
} = require('./lib/ci-job-timing.cjs');
|
|
27
|
+
|
|
28
|
+
const HISTORY_PATH = path.join(__dirname, '..', 'tests', 'ci-timeout-budget-history.jsonl');
|
|
29
|
+
const WORKFLOWS_DIR = path.join(__dirname, '..', '.github', 'workflows');
|
|
30
|
+
|
|
31
|
+
// Static job name → job-id rules, first match wins, checked in array order
|
|
32
|
+
// (test-inert before test, since both job names start with "test ").
|
|
33
|
+
const JOB_RULES = [
|
|
34
|
+
{ workflowFile: 'test.yml', jobKey: 'test-inert', test: (name) => name === 'test (inert CI)' },
|
|
35
|
+
{ workflowFile: 'test.yml', jobKey: 'test', test: (name) => name.startsWith('test (') && name !== 'test (inert CI)' },
|
|
36
|
+
{ workflowFile: 'test.yml', jobKey: 'test-full', test: (name) => name.startsWith('full test (') },
|
|
37
|
+
{ workflowFile: 'test.yml', jobKey: 'coverage-gate', test: (name) => name === 'Coverage gate (merged shards)' },
|
|
38
|
+
{ workflowFile: 'install-smoke.yml', jobKey: 'smoke', test: (name) => name.startsWith('smoke (') },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function resolveJobTimeoutMinutes({ jobName, workflowFile, workflowYamlText, covered }) {
|
|
42
|
+
if (workflowFile === 'mutation.yml') {
|
|
43
|
+
const m = jobName.match(/^Stryker \(([^)]+)\)$/);
|
|
44
|
+
if (!m) return null;
|
|
45
|
+
const moduleName = m[1];
|
|
46
|
+
if (!covered || !Object.prototype.hasOwnProperty.call(covered, moduleName)) return null;
|
|
47
|
+
return covered[moduleName].timeoutMinutes || 15;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const rule = JOB_RULES.find((r) => r.workflowFile === workflowFile && r.test(jobName));
|
|
51
|
+
if (!rule) return null;
|
|
52
|
+
|
|
53
|
+
const doc = yaml.load(workflowYamlText);
|
|
54
|
+
const budget = doc && doc.jobs && doc.jobs[rule.jobKey] ? doc.jobs[rule.jobKey]['timeout-minutes'] : undefined;
|
|
55
|
+
return typeof budget === 'number' ? budget : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {{job: object, workflowFile: string, workflowYamlText: ?string, covered: ?object}} args
|
|
60
|
+
* `job.runEvent` is the triggering event (e.g. `push`/`pull_request`) — carried through to
|
|
61
|
+
* the returned record so entries whose matrix genuinely differs by trigger (e.g. `smoke`'s
|
|
62
|
+
* push-only macOS row) can be distinguished in the persisted history.
|
|
63
|
+
*/
|
|
64
|
+
function parseJobRecord({ job, workflowFile, workflowYamlText, covered }) {
|
|
65
|
+
if (!job.completed_at) return null;
|
|
66
|
+
|
|
67
|
+
const timeoutMinutes = resolveJobTimeoutMinutes({ jobName: job.name, workflowFile, workflowYamlText, covered });
|
|
68
|
+
if (timeoutMinutes == null) return null;
|
|
69
|
+
|
|
70
|
+
const { elapsedMs, pct } = computeElapsedPct({
|
|
71
|
+
startedAt: job.started_at, completedAt: job.completed_at, timeoutMinutes,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
runId: job.run_id,
|
|
76
|
+
jobName: job.name,
|
|
77
|
+
workflowFile,
|
|
78
|
+
sha: job.head_sha,
|
|
79
|
+
runEvent: job.runEvent,
|
|
80
|
+
completedAt: job.completed_at,
|
|
81
|
+
elapsedMs,
|
|
82
|
+
timeoutMinutes,
|
|
83
|
+
pct,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildReportLines(runs, { workflowFile, workflowYamlText, covered }) {
|
|
88
|
+
const records = [];
|
|
89
|
+
for (const { run, jobs } of runs) {
|
|
90
|
+
for (const job of jobs) {
|
|
91
|
+
const rec = parseJobRecord({
|
|
92
|
+
job: {
|
|
93
|
+
...job, run_id: run.id, head_sha: run.head_sha, runEvent: run.event,
|
|
94
|
+
},
|
|
95
|
+
workflowFile,
|
|
96
|
+
workflowYamlText,
|
|
97
|
+
covered,
|
|
98
|
+
});
|
|
99
|
+
if (rec) records.push(rec);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return records;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function dedupeAgainstHistory(newRecords, historyText) {
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const line of String(historyText || '').split('\n')) {
|
|
108
|
+
if (!line.trim()) continue;
|
|
109
|
+
try {
|
|
110
|
+
const rec = JSON.parse(line);
|
|
111
|
+
seen.add(`${rec.runId}::${rec.jobName}`);
|
|
112
|
+
} catch {
|
|
113
|
+
// Malformed history line — skip it rather than crash the whole report.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return newRecords.filter((r) => !seen.has(`${r.runId}::${r.jobName}`));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function formatHistoryLine(record) {
|
|
120
|
+
return `${JSON.stringify(record)}\n`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const WORKFLOW_FILES = ['test.yml', 'mutation.yml', 'install-smoke.yml'];
|
|
124
|
+
const MAX_RUNS_PER_WORKFLOW = 15;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Orchestration entry point — impure, invoked from actions/github-script.
|
|
128
|
+
*
|
|
129
|
+
* @param {{github: object, context: object, core: object, historyPath?: string, fs?: object}} args
|
|
130
|
+
* @returns {Promise<{added: number, nearCap: number}>}
|
|
131
|
+
*/
|
|
132
|
+
async function main({
|
|
133
|
+
github, context, core, historyPath = HISTORY_PATH, fs: fsImpl = fs,
|
|
134
|
+
}) {
|
|
135
|
+
const { owner, repo } = context.repo;
|
|
136
|
+
const mutationMatrix = require('./mutation-matrix.cjs');
|
|
137
|
+
|
|
138
|
+
const allNewRecords = [];
|
|
139
|
+
|
|
140
|
+
for (const workflowFile of WORKFLOW_FILES) {
|
|
141
|
+
const covered = workflowFile === 'mutation.yml' ? mutationMatrix.COVERED : null;
|
|
142
|
+
const workflowYamlText = workflowFile === 'mutation.yml'
|
|
143
|
+
? null
|
|
144
|
+
: fsImpl.readFileSync(path.join(WORKFLOWS_DIR, workflowFile), 'utf8');
|
|
145
|
+
|
|
146
|
+
let runsList;
|
|
147
|
+
try {
|
|
148
|
+
runsList = await github.paginate(github.rest.actions.listWorkflowRuns, {
|
|
149
|
+
owner,
|
|
150
|
+
repo,
|
|
151
|
+
workflow_id: workflowFile,
|
|
152
|
+
status: 'completed',
|
|
153
|
+
per_page: 30,
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
core.warning(`ci-timeout-report: failed to list runs for ${workflowFile}: ${err.message}`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const runs = runsList.slice(0, MAX_RUNS_PER_WORKFLOW);
|
|
161
|
+
const runsWithJobs = [];
|
|
162
|
+
|
|
163
|
+
for (const run of runs) {
|
|
164
|
+
try {
|
|
165
|
+
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
|
|
166
|
+
owner,
|
|
167
|
+
repo,
|
|
168
|
+
run_id: run.id,
|
|
169
|
+
per_page: 50,
|
|
170
|
+
});
|
|
171
|
+
runsWithJobs.push({ run, jobs });
|
|
172
|
+
} catch (err) {
|
|
173
|
+
core.warning(`ci-timeout-report: failed to list jobs for ${workflowFile} run ${run.id}: ${err.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const records = buildReportLines(runsWithJobs, { workflowFile, workflowYamlText, covered });
|
|
178
|
+
allNewRecords.push(...records);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let historyText = '';
|
|
182
|
+
try {
|
|
183
|
+
historyText = fsImpl.readFileSync(historyPath, 'utf8');
|
|
184
|
+
} catch {
|
|
185
|
+
// First run — history file does not exist yet, treat as empty.
|
|
186
|
+
historyText = '';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const deduped = dedupeAgainstHistory(allNewRecords, historyText);
|
|
190
|
+
|
|
191
|
+
if (deduped.length > 0) {
|
|
192
|
+
const newLines = deduped.map(formatHistoryLine).join('');
|
|
193
|
+
fsImpl.appendFileSync(historyPath, newLines);
|
|
194
|
+
}
|
|
195
|
+
// First-run bootstrap when there is nothing new to append is handled by
|
|
196
|
+
// `git add` picking up whatever the history file already contains.
|
|
197
|
+
|
|
198
|
+
let nearCapCount = 0;
|
|
199
|
+
for (const record of deduped) {
|
|
200
|
+
if (!isNearCap(record.pct)) continue;
|
|
201
|
+
nearCapCount += 1;
|
|
202
|
+
|
|
203
|
+
const notice = formatNearCapNotice({
|
|
204
|
+
label: `${record.jobName} (run ${record.runId})`,
|
|
205
|
+
pct: record.pct,
|
|
206
|
+
elapsedMs: record.elapsedMs,
|
|
207
|
+
capMs: record.timeoutMinutes * 60000,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
core.warning(notice.warningLine.replace(/^::warning title=CI budget::/, ''));
|
|
211
|
+
|
|
212
|
+
if (core.summary) {
|
|
213
|
+
core.summary.addRaw(`${notice.summaryMarkdown}\n`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { added: deduped.length, nearCap: nearCapCount };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
HISTORY_PATH,
|
|
222
|
+
WORKFLOWS_DIR,
|
|
223
|
+
JOB_RULES,
|
|
224
|
+
resolveJobTimeoutMinutes,
|
|
225
|
+
parseJobRecord,
|
|
226
|
+
buildReportLines,
|
|
227
|
+
dedupeAgainstHistory,
|
|
228
|
+
formatHistoryLine,
|
|
229
|
+
main,
|
|
230
|
+
};
|