@opengsd/gsd-core 1.9.1 → 1.11.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 +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +608 -148
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +160 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +33 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -1,554 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { execFileSync } = require('node:child_process');
|
|
4
|
-
const { readdirSync, readFileSync, existsSync } = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
|
|
7
|
-
const { ExitError } = require('./lib/cli-exit.cjs');
|
|
8
|
-
const { suiteOf } = require('./run-tests.cjs');
|
|
9
|
-
|
|
10
|
-
const CRITICAL_PATHS = [
|
|
11
|
-
'.github/workflows/',
|
|
12
|
-
'package.json',
|
|
13
|
-
'package-lock.json',
|
|
14
|
-
'scripts/run-tests.cjs',
|
|
15
|
-
'scripts/affected-tests-lib.cjs',
|
|
16
|
-
'scripts/run-affected-tests.cjs',
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
// Suites that are push-only. PRs must never select or run these.
|
|
20
|
-
const PR_EXCLUDED_SUITES = new Set(['install', 'slow']);
|
|
21
|
-
|
|
22
|
-
// Suites run on every PR cell when the critical-path fallback fires.
|
|
23
|
-
const PR_FULL_SUITES = ['unit', 'integration', 'security'];
|
|
24
|
-
|
|
25
|
-
// Source trees to walk when building the forward graph (in addition to tests/).
|
|
26
|
-
// Relative to repoRoot. We walk these to discover SUT-internal requires so that
|
|
27
|
-
// a change to a deep helper propagates through re-export chains to tests.
|
|
28
|
-
const SOURCE_TREES = [
|
|
29
|
-
'gsd-core/bin/lib',
|
|
30
|
-
'bin/lib',
|
|
31
|
-
'bin',
|
|
32
|
-
'scripts',
|
|
33
|
-
'commands',
|
|
34
|
-
'hooks',
|
|
35
|
-
'agents',
|
|
36
|
-
'eslint-rules',
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
function toPosixPath(input) {
|
|
40
|
-
return input.split(path.sep).join('/');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function parseRelativeSpecifiers(source) {
|
|
44
|
-
const specifiers = [];
|
|
45
|
-
const requireRe = /require\((['"])(.+?)\1\)/g;
|
|
46
|
-
const importFromRe = /from\s+(['"])(.+?)\1/g;
|
|
47
|
-
let match;
|
|
48
|
-
|
|
49
|
-
while ((match = requireRe.exec(source)) !== null) {
|
|
50
|
-
specifiers.push(match[2]);
|
|
51
|
-
}
|
|
52
|
-
while ((match = importFromRe.exec(source)) !== null) {
|
|
53
|
-
specifiers.push(match[2]);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return specifiers.filter(specifier => specifier.startsWith('.'));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Extended candidate list now includes .ts/.cts/.mts/.json as well as the
|
|
60
|
-
// standard .js/.cjs/.mjs and index variants.
|
|
61
|
-
function resolveRelativeDependency(repoRoot, fromAbs, specifier) {
|
|
62
|
-
const base = path.resolve(path.dirname(fromAbs), specifier);
|
|
63
|
-
const candidates = [
|
|
64
|
-
base,
|
|
65
|
-
`${base}.js`,
|
|
66
|
-
`${base}.cjs`,
|
|
67
|
-
`${base}.mjs`,
|
|
68
|
-
`${base}.ts`,
|
|
69
|
-
`${base}.cts`,
|
|
70
|
-
`${base}.mts`,
|
|
71
|
-
`${base}.json`,
|
|
72
|
-
path.join(base, 'index.js'),
|
|
73
|
-
path.join(base, 'index.cjs'),
|
|
74
|
-
path.join(base, 'index.mjs'),
|
|
75
|
-
path.join(base, 'index.ts'),
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
for (const candidate of candidates) {
|
|
79
|
-
if (existsSync(candidate)) {
|
|
80
|
-
return toPosixPath(path.relative(repoRoot, candidate));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
// Source-file walker
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Collect all .cjs / .mjs / .js / .ts / .cts / .mts / .json files under a
|
|
93
|
-
* directory tree, returned as repo-relative POSIX paths. Silently skips
|
|
94
|
-
* trees that don't exist.
|
|
95
|
-
*/
|
|
96
|
-
function walkTree(repoRoot, relDir) {
|
|
97
|
-
const absDir = path.join(repoRoot, relDir);
|
|
98
|
-
if (!existsSync(absDir)) return [];
|
|
99
|
-
|
|
100
|
-
const results = [];
|
|
101
|
-
const queue = [absDir];
|
|
102
|
-
|
|
103
|
-
while (queue.length > 0) {
|
|
104
|
-
const cur = queue.shift();
|
|
105
|
-
let entries;
|
|
106
|
-
try {
|
|
107
|
-
entries = readdirSync(cur, { withFileTypes: true });
|
|
108
|
-
} catch {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
for (const entry of entries) {
|
|
112
|
-
const abs = path.join(cur, entry.name);
|
|
113
|
-
if (entry.isDirectory()) {
|
|
114
|
-
// Skip node_modules
|
|
115
|
-
if (entry.name === 'node_modules') continue;
|
|
116
|
-
queue.push(abs);
|
|
117
|
-
} else if (entry.isFile()) {
|
|
118
|
-
const ext = path.extname(entry.name);
|
|
119
|
-
if (['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.json'].includes(ext)) {
|
|
120
|
-
results.push(toPosixPath(path.relative(repoRoot, abs)));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return results;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
// Forward graph: Map<fileRel, Set<depRel>>
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Build a forward dependency graph over test files PLUS source trees.
|
|
135
|
-
* For each file: read, parseRelativeSpecifiers, resolve each specifier.
|
|
136
|
-
* Returns Map<fileRel, Set<depRel>>.
|
|
137
|
-
*/
|
|
138
|
-
function buildForwardGraph(repoRoot, testFiles) {
|
|
139
|
-
// Collect all files to index: test files + source files
|
|
140
|
-
const sourceFiles = [];
|
|
141
|
-
for (const tree of SOURCE_TREES) {
|
|
142
|
-
for (const f of walkTree(repoRoot, tree)) {
|
|
143
|
-
sourceFiles.push(f);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const allFiles = [...new Set([...testFiles, ...sourceFiles])];
|
|
148
|
-
const forward = new Map();
|
|
149
|
-
|
|
150
|
-
for (const fileRel of allFiles) {
|
|
151
|
-
const absFile = path.join(repoRoot, fileRel);
|
|
152
|
-
let source;
|
|
153
|
-
try {
|
|
154
|
-
source = readFileSync(absFile, 'utf8');
|
|
155
|
-
} catch {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const specs = parseRelativeSpecifiers(source);
|
|
160
|
-
const deps = new Set();
|
|
161
|
-
|
|
162
|
-
for (const specifier of specs) {
|
|
163
|
-
const dep = resolveRelativeDependency(repoRoot, absFile, specifier);
|
|
164
|
-
if (dep) deps.add(dep);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
forward.set(fileRel, deps);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return forward;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
174
|
-
// Reverse-transitive index: Map<depRel, Set<testRel>>
|
|
175
|
-
// ---------------------------------------------------------------------------
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Build the TRANSITIVE reverse index: Map<depRel, Set<testRel>>.
|
|
179
|
-
*
|
|
180
|
-
* Algorithm:
|
|
181
|
-
* 1. Build forward graph over all test + source files.
|
|
182
|
-
* 2. Invert to direct reverse edges: Map<depRel, Set<dependentRel>>.
|
|
183
|
-
* 3. For each test file, BFS backwards through all direct reverse edges
|
|
184
|
-
* to find every ancestor. Map each ancestor → the test.
|
|
185
|
-
*
|
|
186
|
-
* Cycle safety: visited set per BFS — each node is enqueued at most once.
|
|
187
|
-
*
|
|
188
|
-
* @param {string} repoRoot
|
|
189
|
-
* @param {string[]} testFiles repo-relative posix paths (e.g. ['tests/foo.test.cjs'])
|
|
190
|
-
* @returns {Map<string, Set<string>>}
|
|
191
|
-
*/
|
|
192
|
-
function buildTransitiveReverseIndex(repoRoot, testFiles) {
|
|
193
|
-
const forward = buildForwardGraph(repoRoot, testFiles);
|
|
194
|
-
|
|
195
|
-
// Build direct reverse edges: dep → Set of files that directly require dep
|
|
196
|
-
const directReverse = new Map();
|
|
197
|
-
for (const [fileRel, deps] of forward) {
|
|
198
|
-
for (const dep of deps) {
|
|
199
|
-
if (!directReverse.has(dep)) directReverse.set(dep, new Set());
|
|
200
|
-
directReverse.get(dep).add(fileRel);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// For each test file, BFS through direct reverse edges to collect all
|
|
205
|
-
// ancestors, then invert: ancestor → test.
|
|
206
|
-
// We do this test-file-first (not dep-first) so we know which test reached
|
|
207
|
-
// each ancestor.
|
|
208
|
-
const transitiveReverse = new Map();
|
|
209
|
-
|
|
210
|
-
for (const testFile of testFiles) {
|
|
211
|
-
// BFS from testFile following reverse edges (files that point TO testFile,
|
|
212
|
-
// then files that point to THOSE files, etc.).
|
|
213
|
-
// We want: "if X changed, would that eventually pull in testFile?"
|
|
214
|
-
// So we walk the FORWARD graph starting from testFile to find all deps,
|
|
215
|
-
// then any of those deps maps back to testFile.
|
|
216
|
-
|
|
217
|
-
// Actually simpler: for each test we do a forward BFS to find ALL files
|
|
218
|
-
// the test transitively depends on. Then we record testFile as a
|
|
219
|
-
// dependent of each of those files.
|
|
220
|
-
const visited = new Set();
|
|
221
|
-
visited.add(testFile);
|
|
222
|
-
const queue = [testFile];
|
|
223
|
-
|
|
224
|
-
while (queue.length > 0) {
|
|
225
|
-
const current = queue.shift();
|
|
226
|
-
const deps = forward.get(current);
|
|
227
|
-
if (!deps) continue;
|
|
228
|
-
for (const dep of deps) {
|
|
229
|
-
if (visited.has(dep)) continue;
|
|
230
|
-
visited.add(dep);
|
|
231
|
-
queue.push(dep);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Every file in `visited` (except testFile itself) is a transitive dep.
|
|
236
|
-
// Record testFile as a dependent of each.
|
|
237
|
-
for (const dep of visited) {
|
|
238
|
-
if (dep === testFile) continue;
|
|
239
|
-
if (!transitiveReverse.has(dep)) transitiveReverse.set(dep, new Set());
|
|
240
|
-
transitiveReverse.get(dep).add(testFile);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return transitiveReverse;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
// Legacy shim — kept so that runAffectedTests can call buildTransitiveReverseIndex
|
|
249
|
-
// and existing call sites that still call buildReverseIndex still work.
|
|
250
|
-
// ---------------------------------------------------------------------------
|
|
251
|
-
function buildReverseIndex(repoRoot, testFiles) {
|
|
252
|
-
return buildTransitiveReverseIndex(repoRoot, testFiles);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function shouldRunFullSuite(changedFiles) {
|
|
256
|
-
return changedFiles.some(file =>
|
|
257
|
-
CRITICAL_PATHS.some(critical => file === critical || file.startsWith(critical)),
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function listTestFiles(repoRoot) {
|
|
262
|
-
const results = [];
|
|
263
|
-
function walk(dir, relBase) {
|
|
264
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
265
|
-
if (entry.isDirectory()) {
|
|
266
|
-
if (entry.name === 'node_modules') continue;
|
|
267
|
-
const nextRel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
268
|
-
walk(path.join(dir, entry.name), nextRel);
|
|
269
|
-
} else if (entry.name.endsWith('.test.cjs')) {
|
|
270
|
-
const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
271
|
-
results.push(`tests/${rel}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
walk(path.join(repoRoot, 'tests'), '');
|
|
276
|
-
return results.sort();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Select the affected tests given a set of changed files and a reverse index.
|
|
281
|
-
*
|
|
282
|
-
* Options:
|
|
283
|
-
* detectWiden {boolean} — when true, attach `._widenRequired = true` to the
|
|
284
|
-
* returned array when a changed source file has zero transitive test
|
|
285
|
-
* dependents. The caller (runAffectedTests) uses this to widen to unit/all.
|
|
286
|
-
*
|
|
287
|
-
* The returned array is sorted and may have `._widenRequired` attached.
|
|
288
|
-
*/
|
|
289
|
-
function pickAffectedTests(changedFiles, allTests, reverseIndex, options = {}) {
|
|
290
|
-
const { detectWiden = false } = options;
|
|
291
|
-
const selected = new Set();
|
|
292
|
-
let widenRequired = false;
|
|
293
|
-
|
|
294
|
-
// Build a fast lookup of currently-existing test files (from readdirSync — deleted files absent).
|
|
295
|
-
const allTestsSet = new Set(allTests);
|
|
296
|
-
|
|
297
|
-
// (a) directly-changed test files + (b) transitive test dependents
|
|
298
|
-
// Deleted test files are filtered out — they no longer exist and cannot be run.
|
|
299
|
-
// A deleted test file also must NOT trigger widen (the test is simply gone).
|
|
300
|
-
for (const file of changedFiles) {
|
|
301
|
-
if (file.startsWith('tests/') && file.endsWith('.test.cjs')) {
|
|
302
|
-
// Only select if the test file still exists (i.e. is present in allTests from readdirSync).
|
|
303
|
-
if (allTestsSet.has(file)) {
|
|
304
|
-
selected.add(file);
|
|
305
|
-
}
|
|
306
|
-
// Deleted test file — do not add to selected; do not look up reverse index.
|
|
307
|
-
} else {
|
|
308
|
-
const dependents = reverseIndex.get(file);
|
|
309
|
-
if (dependents) {
|
|
310
|
-
for (const testFile of dependents) selected.add(testFile);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// (c) stem heuristic — kept as secondary mechanism
|
|
316
|
-
for (const file of changedFiles) {
|
|
317
|
-
const stem = path.basename(file).replace(/\.[^.]+$/, '').toLowerCase();
|
|
318
|
-
if (!stem) continue;
|
|
319
|
-
for (const testFile of allTests) {
|
|
320
|
-
if (testFile.toLowerCase().includes(stem)) selected.add(testFile);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Widen backstop: if a changed file is a non-test, non-CRITICAL_PATH source file
|
|
325
|
-
// (recognised extension) under a SOURCE_TREE, AND it is either deleted (no longer
|
|
326
|
-
// on disk — so never in the forward graph and has no static dependents) OR it
|
|
327
|
-
// exists with ZERO transitive test dependents — signal a widen.
|
|
328
|
-
// NOTE: we deliberately do NOT skip deleted files here; a deleted source file's
|
|
329
|
-
// absence from the forward graph means dependents===undefined, which is the same
|
|
330
|
-
// as zero static dependents, and is itself the widen trigger.
|
|
331
|
-
if (detectWiden) {
|
|
332
|
-
for (const file of changedFiles) {
|
|
333
|
-
// Only care about source files, not test files or docs
|
|
334
|
-
if (file.startsWith('tests/')) continue;
|
|
335
|
-
if (shouldRunFullSuite([file])) continue; // critical path already triggers full suite
|
|
336
|
-
// Check: is this a source file (has a recognised extension)?
|
|
337
|
-
const ext = path.extname(file);
|
|
338
|
-
const isSourceFile = ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.json'].includes(ext);
|
|
339
|
-
if (!isSourceFile) continue;
|
|
340
|
-
// Check: is this file under a recognised source tree?
|
|
341
|
-
const isUnderSourceTree = SOURCE_TREES.some(
|
|
342
|
-
tree => file === tree || file.startsWith(tree + '/'),
|
|
343
|
-
);
|
|
344
|
-
if (!isUnderSourceTree) continue;
|
|
345
|
-
// Does it have any test dependents?
|
|
346
|
-
// A deleted file will have undefined here (not in the graph) — that is
|
|
347
|
-
// treated as zero static dependents and triggers widen conservatively.
|
|
348
|
-
const dependents = reverseIndex.get(file);
|
|
349
|
-
const hasStaticDependents = dependents && dependents.size > 0;
|
|
350
|
-
if (!hasStaticDependents) {
|
|
351
|
-
widenRequired = true;
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Drop any file whose suite is push-only. This is the single chokepoint —
|
|
358
|
-
// it catches direct-change, reverse-index, AND stem-match selections.
|
|
359
|
-
for (const file of selected) {
|
|
360
|
-
const suite = suiteOf(path.basename(file));
|
|
361
|
-
if (PR_EXCLUDED_SUITES.has(suite)) selected.delete(file);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// When nothing maps, return an empty array. The caller decides the fallback.
|
|
365
|
-
const result = [...selected].sort();
|
|
366
|
-
if (widenRequired) result._widenRequired = true;
|
|
367
|
-
return result;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function changedFilesSinceBase(repoRoot, baseRef) {
|
|
371
|
-
const out = execFileSync(
|
|
372
|
-
'git',
|
|
373
|
-
['diff', '--name-only', '--no-renames', '--diff-filter=ACMRD', `${baseRef}...HEAD`],
|
|
374
|
-
{ cwd: repoRoot, encoding: 'utf8' },
|
|
375
|
-
).trim();
|
|
376
|
-
if (!out) return [];
|
|
377
|
-
return out.split('\n').map(line => line.trim()).filter(Boolean);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function runNodeTestFiles(repoRoot, files) {
|
|
381
|
-
const defaultConcurrency = process.platform === 'win32' ? 2 : 4;
|
|
382
|
-
const concurrency = process.env.TEST_CONCURRENCY
|
|
383
|
-
? `--test-concurrency=${process.env.TEST_CONCURRENCY}`
|
|
384
|
-
: `--test-concurrency=${defaultConcurrency}`;
|
|
385
|
-
const absoluteFiles = files.map(file => path.join(repoRoot, file));
|
|
386
|
-
|
|
387
|
-
// Keep chunks bounded for Windows CreateProcess command-length limits.
|
|
388
|
-
const maxChars = process.env.RUN_TESTS_MAX_CMDLINE_CHARS
|
|
389
|
-
? Number(process.env.RUN_TESTS_MAX_CMDLINE_CHARS)
|
|
390
|
-
: 28000;
|
|
391
|
-
const fixed = process.execPath.length + '--test'.length + concurrency.length + 8;
|
|
392
|
-
const chunks = [];
|
|
393
|
-
let current = [];
|
|
394
|
-
let currentLen = fixed;
|
|
395
|
-
|
|
396
|
-
for (const file of absoluteFiles) {
|
|
397
|
-
const add = file.length + 1;
|
|
398
|
-
if (current.length > 0 && currentLen + add > maxChars) {
|
|
399
|
-
chunks.push(current);
|
|
400
|
-
current = [];
|
|
401
|
-
currentLen = fixed;
|
|
402
|
-
}
|
|
403
|
-
current.push(file);
|
|
404
|
-
currentLen += add;
|
|
405
|
-
}
|
|
406
|
-
if (current.length > 0) chunks.push(current);
|
|
407
|
-
|
|
408
|
-
let firstFailure = 0;
|
|
409
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
410
|
-
if (chunks.length > 1) {
|
|
411
|
-
console.error(`affected-tests: chunk ${i + 1}/${chunks.length} (${chunks[i].length} files)`);
|
|
412
|
-
}
|
|
413
|
-
try {
|
|
414
|
-
execFileSync(process.execPath, ['--test', concurrency, ...chunks[i]], {
|
|
415
|
-
cwd: repoRoot,
|
|
416
|
-
stdio: 'inherit',
|
|
417
|
-
env: { ...process.env },
|
|
418
|
-
});
|
|
419
|
-
} catch (error) {
|
|
420
|
-
const code = error.status || 1;
|
|
421
|
-
if (firstFailure === 0) firstFailure = code;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
if (firstFailure !== 0) throw new ExitError(firstFailure);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function runSuite(repoRoot, suite) {
|
|
428
|
-
execFileSync(process.execPath, ['scripts/run-tests.cjs', '--suite', suite], {
|
|
429
|
-
cwd: repoRoot,
|
|
430
|
-
stdio: 'inherit',
|
|
431
|
-
env: { ...process.env },
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function resolveBaseRef() {
|
|
436
|
-
if (process.env.GSD_AFFECTED_BASE) return process.env.GSD_AFFECTED_BASE;
|
|
437
|
-
if (process.env.GITHUB_BASE_REF) return `origin/${process.env.GITHUB_BASE_REF}`;
|
|
438
|
-
return 'origin/main';
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Pure function: given the outputs of the selection phase, return a run plan
|
|
443
|
-
* describing what should be executed. No I/O is performed here.
|
|
444
|
-
*
|
|
445
|
-
* Return shapes:
|
|
446
|
-
* { mode: 'suite', suite: 'unit' } — no changed files
|
|
447
|
-
* { mode: 'suites', suites: PR_FULL_SUITES } — critical path triggered
|
|
448
|
-
* { mode: 'suites', suites: PR_FULL_SUITES } — widen required (orphan src file)
|
|
449
|
-
* { mode: 'suite', suite: 'unit' } — selection empty after widen=false
|
|
450
|
-
* { mode: 'files', files: string[] } — concrete selection, no widen
|
|
451
|
-
*
|
|
452
|
-
* Invariant: when widenRequired is true the executed set is ALWAYS ⊇ selected,
|
|
453
|
-
* because PR_FULL_SUITES covers every PR-eligible suite (unit + integration +
|
|
454
|
-
* security), so every concrete match that pickAffectedTests put into `selected`
|
|
455
|
-
* belongs to one of those suites and will be exercised by running all three.
|
|
456
|
-
*/
|
|
457
|
-
function resolveRunPlan({ changedFiles: _changedFiles, selected, widenRequired, criticalPath, noChanges }) {
|
|
458
|
-
if (noChanges) {
|
|
459
|
-
return { mode: 'suite', suite: 'unit' };
|
|
460
|
-
}
|
|
461
|
-
if (criticalPath) {
|
|
462
|
-
return { mode: 'suites', suites: PR_FULL_SUITES };
|
|
463
|
-
}
|
|
464
|
-
if (widenRequired) {
|
|
465
|
-
return { mode: 'suites', suites: PR_FULL_SUITES };
|
|
466
|
-
}
|
|
467
|
-
if (selected.length === 0) {
|
|
468
|
-
return { mode: 'suite', suite: 'unit' };
|
|
469
|
-
}
|
|
470
|
-
return { mode: 'files', files: selected };
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function runAffectedTests(options = {}) {
|
|
474
|
-
const repoRoot = options.repoRoot || path.resolve(__dirname, '..');
|
|
475
|
-
const baseRef = options.baseRef || resolveBaseRef();
|
|
476
|
-
const changed = changedFilesSinceBase(repoRoot, baseRef);
|
|
477
|
-
|
|
478
|
-
if (changed.length === 0) {
|
|
479
|
-
console.error(`affected-tests: no changed files against ${baseRef}; running unit suite`);
|
|
480
|
-
runSuite(repoRoot, 'unit');
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (shouldRunFullSuite(changed)) {
|
|
485
|
-
console.error('affected-tests: critical CI/runtime files changed; running PR suites (unit, integration, security)');
|
|
486
|
-
for (const suite of PR_FULL_SUITES) {
|
|
487
|
-
runSuite(repoRoot, suite);
|
|
488
|
-
}
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const allTests = listTestFiles(repoRoot);
|
|
493
|
-
const reverseIndex = buildTransitiveReverseIndex(repoRoot, allTests);
|
|
494
|
-
const selected = pickAffectedTests(changed, allTests, reverseIndex, { detectWiden: true });
|
|
495
|
-
|
|
496
|
-
console.error(`affected-tests: base=${baseRef} changed=${changed.length} selected=${selected.length}`);
|
|
497
|
-
console.error(`affected-tests: ${selected.join(' ')}`);
|
|
498
|
-
|
|
499
|
-
const plan = resolveRunPlan({
|
|
500
|
-
changedFiles: changed,
|
|
501
|
-
selected,
|
|
502
|
-
widenRequired: selected._widenRequired === true,
|
|
503
|
-
criticalPath: false,
|
|
504
|
-
noChanges: false,
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
if (plan.mode === 'suites') {
|
|
508
|
-
// Widen backstop: a source file changed that has no static test dependents.
|
|
509
|
-
// Run all PR suites (unit + integration + security) — a strict superset of
|
|
510
|
-
// the concretely-selected tests — so no integration/security match is lost.
|
|
511
|
-
for (const file of changed) {
|
|
512
|
-
const ext = path.extname(file);
|
|
513
|
-
const isSourceFile = ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.json'].includes(ext);
|
|
514
|
-
if (!isSourceFile || file.startsWith('tests/') || shouldRunFullSuite([file])) continue;
|
|
515
|
-
const dependents = reverseIndex.get(file);
|
|
516
|
-
if (!dependents || dependents.size === 0) {
|
|
517
|
-
console.error(
|
|
518
|
-
`affected-tests: ${file} has no static test dependents; widening to PR suites (unit+integration+security)`,
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
for (const suite of plan.suites) {
|
|
523
|
-
runSuite(repoRoot, suite);
|
|
524
|
-
}
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (plan.mode === 'suite') {
|
|
529
|
-
console.error('affected-tests: no affected tests found; running unit suite as smoke');
|
|
530
|
-
runSuite(repoRoot, plan.suite);
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// plan.mode === 'files'
|
|
535
|
-
runNodeTestFiles(repoRoot, plan.files);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
module.exports = {
|
|
539
|
-
CRITICAL_PATHS,
|
|
540
|
-
PR_EXCLUDED_SUITES,
|
|
541
|
-
PR_FULL_SUITES,
|
|
542
|
-
buildForwardGraph,
|
|
543
|
-
buildReverseIndex,
|
|
544
|
-
buildTransitiveReverseIndex,
|
|
545
|
-
listTestFiles,
|
|
546
|
-
parseRelativeSpecifiers,
|
|
547
|
-
pickAffectedTests,
|
|
548
|
-
resolveBaseRef,
|
|
549
|
-
resolveRelativeDependency,
|
|
550
|
-
resolveRunPlan,
|
|
551
|
-
shouldRunFullSuite,
|
|
552
|
-
toPosixPath,
|
|
553
|
-
runAffectedTests,
|
|
554
|
-
};
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* gen-emitted-baseline.cjs — publishes the emitted-baseline artifact the
|
|
6
|
-
* differential attribution check reads via `resolveBaseline()` (#2724, ADR-2719 §5).
|
|
7
|
-
*
|
|
8
|
-
* ## Why this exists
|
|
9
|
-
*
|
|
10
|
-
* Phase 3 (#2723) built `resolveBaseline()` (tests/helpers/emitted-baseline.cjs) and its
|
|
11
|
-
* cache-key staleness discipline, but nothing produced the artifact it reads — the
|
|
12
|
-
* dual-run window instead read the baseline straight off the committed golden fixtures
|
|
13
|
-
* at the base ref (`baselineManifestsAtRef`). Phase 4 deletes those fixtures, so
|
|
14
|
-
* something has to build and publish `{version, sha, manifests, sizes}` for real.
|
|
15
|
-
*
|
|
16
|
-
* ## What it does
|
|
17
|
-
*
|
|
18
|
-
* Builds the emitted manifest set (19 real installer spawns) and the workflow/agent size
|
|
19
|
-
* maps for the tree at `--dir <path>` (default: THIS script's own checkout — i.e.
|
|
20
|
-
* whatever commit is currently checked out where `gen-emitted-baseline.cjs` itself
|
|
21
|
-
* lives). Writes the result to `--out <path>` (default `.gsd-cache/emitted-baseline.json`).
|
|
22
|
-
*
|
|
23
|
-
* `--dir` decouples "which copy of this script runs" from "which tree gets measured"
|
|
24
|
-
* (#2767). The MEASUREMENT SCHEMA — this script, and the `currentManifests`/
|
|
25
|
-
* `currentSizes`/`buildParityManifest` functions it calls — always comes from wherever
|
|
26
|
-
* `gen-emitted-baseline.cjs` itself is being run from (relative `require`s resolve
|
|
27
|
-
* there); only the tree being measured (which `bin/install.js` gets spawned, which
|
|
28
|
-
* `hooks/`/`gsd-core/workflows/`/`agents/` get read) moves to `--dir`. That is what lets
|
|
29
|
-
* a differential apply ONE definition of "the emitted manifest" to two different
|
|
30
|
-
* commits and stay comparable even as that definition evolves — see
|
|
31
|
-
* `tests/helpers/emitted-runtime.cjs`'s `buildBaselineAtRef` for the caller that
|
|
32
|
-
* exercises this for real, and why it must NOT run the `--dir` tree's own copy of this
|
|
33
|
-
* script (which may be an older version, or — for any base ref that predates the PR
|
|
34
|
-
* that added this script at all — may not exist there yet).
|
|
35
|
-
*
|
|
36
|
-
* ## Callers
|
|
37
|
-
*
|
|
38
|
-
* 1. CI's push-to-`next` job runs this straight after `next` advances (no `--dir`,
|
|
39
|
-
* measuring its own checkout), then uploads `.gsd-cache/emitted-baseline.json` as a
|
|
40
|
-
* cache entry keyed on the merge sha (.github/workflows/test.yml,
|
|
41
|
-
* `publish-emitted-baseline` job).
|
|
42
|
-
* 2. `tests/emitted-attribution.test.cjs`'s real-tree test passes
|
|
43
|
-
* `buildBaselineAtRef` (tests/helpers/emitted-runtime.cjs) to `resolveBaseline()`
|
|
44
|
-
* as the `buildFallback` for a cache miss: it checks out `base` into a throwaway
|
|
45
|
-
* `git worktree`, symlinks in `node_modules` and runs `npm run build:lib` there
|
|
46
|
-
* (this script and the test helpers are Node-builtins-only per CONTRIBUTING.md's
|
|
47
|
-
* "No external dependencies in core", but `tests/helpers/install-shared.cjs`
|
|
48
|
-
* requires the TSC-compiled, gitignored `gsd-core/bin/lib/*.cjs`, so that one build
|
|
49
|
-
* step is unavoidable), then spawns THIS repo's OWN `gen-emitted-baseline.cjs`
|
|
50
|
-
* with `--dir <worktree> --out <tmp>` and reads the artifact back.
|
|
51
|
-
*
|
|
52
|
-
* Every git subprocess is bounded (CLAUDE.md → KNOWN DEFECTS: unbounded subprocesses
|
|
53
|
-
* hang CI silently).
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
const fs = require('node:fs');
|
|
57
|
-
const path = require('node:path');
|
|
58
|
-
const { execFileSync } = require('node:child_process');
|
|
59
|
-
|
|
60
|
-
const { runMain, ExitError } = require('./lib/cli-exit.cjs');
|
|
61
|
-
const { BUILD_SCRIPT } = require('../tests/helpers/install-shared.cjs');
|
|
62
|
-
const { currentManifests, currentSizes, git } = require('../tests/helpers/emitted-runtime.cjs');
|
|
63
|
-
const { BASELINE_VERSION } = require('../tests/helpers/emitted-baseline.cjs');
|
|
64
|
-
|
|
65
|
-
const REPO_ROOT = path.join(__dirname, '..');
|
|
66
|
-
const DEFAULT_OUT = path.join(REPO_ROOT, '.gsd-cache', 'emitted-baseline.json');
|
|
67
|
-
|
|
68
|
-
// Reuses emitted-runtime.cjs's `git()` (not a local execFileSync) so the
|
|
69
|
-
// `-c safe.directory=<dir>` fix for the remote runner's dubious-ownership
|
|
70
|
-
// mount (#2767) has exactly one source of truth — see that module's
|
|
71
|
-
// `safeDirArgs` doc comment.
|
|
72
|
-
function resolveHeadSha(cwd) {
|
|
73
|
-
return git(['rev-parse', 'HEAD'], { cwd }).trim();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function parseArgs(argv) {
|
|
77
|
-
let out = DEFAULT_OUT;
|
|
78
|
-
let dir = REPO_ROOT;
|
|
79
|
-
for (let i = 0; i < argv.length; i++) {
|
|
80
|
-
if (argv[i] === '--out') {
|
|
81
|
-
out = argv[i + 1];
|
|
82
|
-
i++;
|
|
83
|
-
} else if (argv[i] === '--dir') {
|
|
84
|
-
dir = argv[i + 1];
|
|
85
|
-
i++;
|
|
86
|
-
} else {
|
|
87
|
-
throw new ExitError(2, `gen-emitted-baseline: unknown argument "${argv[i]}"`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (!out || typeof out !== 'string') {
|
|
91
|
-
throw new ExitError(2, 'gen-emitted-baseline: --out requires a path');
|
|
92
|
-
}
|
|
93
|
-
if (!dir || typeof dir !== 'string') {
|
|
94
|
-
throw new ExitError(2, 'gen-emitted-baseline: --dir requires a path');
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
out: path.isAbsolute(out) ? out : path.join(process.cwd(), out),
|
|
98
|
-
dir: path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async function main() {
|
|
103
|
-
const { out, dir } = parseArgs(process.argv.slice(2));
|
|
104
|
-
|
|
105
|
-
if (!fs.existsSync(dir)) {
|
|
106
|
-
throw new ExitError(1, `gen-emitted-baseline: --dir "${dir}" does not exist`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const sha = resolveHeadSha(dir);
|
|
110
|
-
if (!/^[0-9a-f]{40}$/.test(sha)) {
|
|
111
|
-
throw new ExitError(1, `gen-emitted-baseline: HEAD of "${dir}" did not resolve to a 40-hex sha: ${sha}`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// hooks/dist is gitignored and built; a scoped CI checkout may not have run
|
|
115
|
-
// build:hooks yet. Idempotent, mirrors the real-tree test. Built INSIDE `dir` — the
|
|
116
|
-
// MEASURED tree's own hooks/ source, not this script's — so a `--dir` pointed at a
|
|
117
|
-
// different checkout (e.g. a base-ref worktree) reflects that checkout's hooks, not
|
|
118
|
-
// the caller's. `dir === REPO_ROOT` (the default, no `--dir`) collapses to the
|
|
119
|
-
// pre-#2767 behavior of building this own repo's hooks/dist.
|
|
120
|
-
const buildScript = dir === REPO_ROOT ? BUILD_SCRIPT : path.join(dir, 'scripts', 'build-hooks.js');
|
|
121
|
-
execFileSync(process.execPath, [buildScript], { cwd: dir, encoding: 'utf-8', stdio: 'pipe', timeout: 120_000 });
|
|
122
|
-
|
|
123
|
-
// The measurement SCHEMA (currentManifests/currentSizes/buildParityManifest) is
|
|
124
|
-
// always THIS script's own — only the tree being measured moves with `repoRoot`.
|
|
125
|
-
const manifests = currentManifests({ repoRoot: dir });
|
|
126
|
-
const sizes = currentSizes({ repoRoot: dir });
|
|
127
|
-
|
|
128
|
-
const artifact = { version: BASELINE_VERSION, sha, manifests, sizes };
|
|
129
|
-
|
|
130
|
-
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
131
|
-
fs.writeFileSync(out, `${JSON.stringify(artifact, null, 2)}\n`, 'utf8');
|
|
132
|
-
|
|
133
|
-
const familyCount = Object.keys(manifests).length;
|
|
134
|
-
const sizeCount = Object.keys(sizes).length;
|
|
135
|
-
process.stdout.write(
|
|
136
|
-
`gen-emitted-baseline: wrote ${out} (dir=${dir}, sha=${sha.slice(0, 12)}, ${familyCount} families, ${sizeCount} sized files)\n`,
|
|
137
|
-
);
|
|
138
|
-
return 0;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (require.main === module) {
|
|
142
|
-
runMain(main);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
module.exports = { parseArgs, resolveHeadSha, DEFAULT_OUT, REPO_ROOT };
|