@opengsd/gsd-core 1.14.0 → 1.16.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 +85 -5
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +3 -3
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -12
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +15 -9
- package/agents/gsd-planner.md +15 -11
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +166 -37
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +37 -14
- package/bin/install.js +764 -287
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +7 -2
- package/commands/gsd/capture.md +9 -5
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +12 -4
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +11 -7
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +7 -2
- package/commands/gsd/quick-batch.md +21 -9
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +7 -4
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +477 -78
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +144 -42
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +197 -222
- package/gsd-core/bin/lib/capability-validator.cjs +16 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +343 -207
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +76 -19
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +426 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +364 -132
- package/gsd-core/bin/lib/install-engine.cjs +30 -33
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +37 -6
- package/gsd-core/bin/lib/model-resolver.cjs +171 -62
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +110 -8
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +489 -88
- package/gsd-core/bin/lib/plan-document.cjs +142 -20
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +692 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
- package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
- package/gsd-core/bin/lib/roadmap.cjs +405 -88
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
- package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +256 -105
- package/gsd-core/bin/lib/surface.cjs +19 -2
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
- package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
- package/gsd-core/bin/lib/uat.cjs +432 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1378 -274
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +357 -531
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +12 -3
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +37 -8
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +4 -3
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +13 -4
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +43 -23
- package/gsd-core/workflows/check-todos.md +7 -6
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +112 -25
- package/gsd-core/workflows/code-review.md +146 -135
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +13 -8
- package/gsd-core/workflows/debug.md +32 -7
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +14 -5
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +96 -178
- package/gsd-core/workflows/execute-plan.md +18 -23
- package/gsd-core/workflows/explore.md +4 -4
- package/gsd-core/workflows/extract-learnings.md +4 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +3 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +4 -3
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +6 -4
- package/gsd-core/workflows/map-codebase.md +5 -4
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +11 -11
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +50 -24
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +62 -39
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +33 -17
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +13 -13
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +14 -13
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +48 -11
- package/gsd-core/workflows/ui-review.md +21 -6
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +12 -13
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +129 -55
- package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +161 -4
- package/hooks/dist/gsd-statusline.js +85 -20
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +32 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +161 -4
- package/hooks/gsd-statusline.js +85 -20
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +22 -4
- package/scripts/build-hooks.js +15 -6
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +15 -8
- package/scripts/docs-guard-registry.cjs +34 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +79 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
- package/scripts/lib/ndjson-reporter.cjs +31 -5
- package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- 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-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +319 -5
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +38 -9
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +7 -2
- package/skills/gsd-capture/SKILL.md +9 -5
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +12 -4
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +7 -2
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +21 -9
- package/skills/gsd-review/SKILL.md +7 -4
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `check decision-coverage-verify` — advisory verify-phase decision-coverage gate, as a gate
|
|
4
|
+
* module (#5139, epic #5056, ADR-5057 §4 first bullet): it returns a `GateResult`; the command
|
|
5
|
+
* router formats it. Always `blocking:false` — a soft warning, never a verification failure.
|
|
6
|
+
* Imports no io module and performs no direct console/stdout/stderr write (ESLint-enforced).
|
|
7
|
+
*
|
|
8
|
+
* Argv after the verb: `<phase-dir> <context-path>` (positional; no `--context` flag here).
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.evaluateDecisionCoverageVerify = evaluateDecisionCoverageVerify;
|
|
12
|
+
const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
|
|
13
|
+
const gate_phase_context_cjs_1 = require("./gate-phase-context.cjs");
|
|
14
|
+
const gate_config_cjs_1 = require("./gate-config.cjs");
|
|
15
|
+
const decision_coverage_support_cjs_1 = require("./decision-coverage-support.cjs");
|
|
16
|
+
/**
|
|
17
|
+
* The verdict when CONTEXT.md or a shipped artifact could not be read. The gate stays advisory
|
|
18
|
+
* (`block: false`, `blocking: false`); the outcome is `unreadable`, so the exit status is
|
|
19
|
+
* UNAVAILABLE and no decision is reported honored or not honored from content never seen (#5170).
|
|
20
|
+
*/
|
|
21
|
+
function unreadableVerify(readError) {
|
|
22
|
+
return (0, gate_verdict_cjs_1.gateUnreadable)(false, {
|
|
23
|
+
skipped: false,
|
|
24
|
+
blocking: false,
|
|
25
|
+
reason: 'unreadable evidence',
|
|
26
|
+
total: null,
|
|
27
|
+
honored: null,
|
|
28
|
+
not_honored: [],
|
|
29
|
+
readError,
|
|
30
|
+
message: `Decision coverage verify (warning): could not read its evidence (${readError}); no decision was checked.`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** The verdict when there is no CONTEXT.md: the legitimate green skip (authoritatively `none`, never `unreadable`). */
|
|
34
|
+
function contextMissingSkip() {
|
|
35
|
+
return (0, gate_verdict_cjs_1.gateVerdict)('skip', false, { skipped: true, blocking: false, reason: 'CONTEXT.md missing', total: 0, honored: 0, not_honored: [], message: 'No CONTEXT.md - nothing to check.' });
|
|
36
|
+
}
|
|
37
|
+
function evaluateDecisionCoverageVerify(input) {
|
|
38
|
+
const { projectDir, args } = input;
|
|
39
|
+
let phaseDir = '';
|
|
40
|
+
if (args[0]) {
|
|
41
|
+
const resolved = (0, gate_phase_context_cjs_1.resolveContainedPath)(args[0], projectDir);
|
|
42
|
+
if ((0, gate_verdict_cjs_1.isGateUsageFailure)(resolved))
|
|
43
|
+
return resolved;
|
|
44
|
+
phaseDir = resolved;
|
|
45
|
+
}
|
|
46
|
+
let contextPath = '';
|
|
47
|
+
if (args[1]) {
|
|
48
|
+
const resolved = (0, gate_phase_context_cjs_1.resolveContainedPath)(args[1], projectDir);
|
|
49
|
+
if ((0, gate_verdict_cjs_1.isGateUsageFailure)(resolved))
|
|
50
|
+
return resolved;
|
|
51
|
+
contextPath = resolved;
|
|
52
|
+
}
|
|
53
|
+
if (!(0, gate_config_cjs_1.isDecisionCoverageGateEnabled)(projectDir)) {
|
|
54
|
+
return (0, gate_verdict_cjs_1.gateVerdict)('skip', false, { skipped: true, blocking: false, reason: 'workflow.context_coverage_gate is false', total: 0, honored: 0, not_honored: [], message: 'Decision coverage gate disabled by config.' });
|
|
55
|
+
}
|
|
56
|
+
// No context argument is the only up-front skip: an absent file is `none` and an unreadable one is
|
|
57
|
+
// `unreadable` below, from the one read that loads the decisions (`fs.existsSync` answered `false`
|
|
58
|
+
// for an EACCES on a parent, skipping a CONTEXT.md the gate never saw — #5170).
|
|
59
|
+
if (!contextPath) {
|
|
60
|
+
return contextMissingSkip();
|
|
61
|
+
}
|
|
62
|
+
const extracted = (0, decision_coverage_support_cjs_1.loadDecisionExtraction)(contextPath);
|
|
63
|
+
if (extracted.kind === 'unreadable')
|
|
64
|
+
return unreadableVerify(`${extracted.span ?? contextPath}: ${extracted.reason}`);
|
|
65
|
+
if (extracted.kind === 'none') {
|
|
66
|
+
return contextMissingSkip();
|
|
67
|
+
}
|
|
68
|
+
const { trackable: decisions, outcome: decisionOutcome } = extracted.value;
|
|
69
|
+
// Mirror could-not-parse surface for verify (non-blocking advisory WARN).
|
|
70
|
+
// Fire independent of decisions.length — a parse-miss on any bullet must surface,
|
|
71
|
+
// even when some decisions were partially extracted (#1365 fix-parity with plan gate).
|
|
72
|
+
if (decisionOutcome === 'could-not-parse') {
|
|
73
|
+
const partialParse = decisions.length > 0;
|
|
74
|
+
return (0, gate_verdict_cjs_1.gateVerdict)('advisory', false, {
|
|
75
|
+
skipped: false,
|
|
76
|
+
blocking: false,
|
|
77
|
+
reason: 'could-not-parse',
|
|
78
|
+
total: decisions.length,
|
|
79
|
+
honored: 0,
|
|
80
|
+
not_honored: [],
|
|
81
|
+
message: partialParse
|
|
82
|
+
? 'Decision coverage verify (warning): decisions could not be fully parsed — one or more ' +
|
|
83
|
+
'`- **D-NN ...**` bullets appear malformed (missing `:` or ` — ` separator, or a phase ' +
|
|
84
|
+
'prefix that is not a digit run). Fix the bullet format in the CONTEXT.md decisions block.'
|
|
85
|
+
: 'Decision coverage verify (warning): could not parse decisions — possible format mismatch. ' +
|
|
86
|
+
'Check the formatting of the CONTEXT.md decisions block (accepted forms: `- **D-NN:** text`, ' +
|
|
87
|
+
'`- **D4-NN:** text` (phase-prefixed), `- **D-NN — title** body`).',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (decisions.length === 0) {
|
|
91
|
+
return (0, gate_verdict_cjs_1.gateVerdict)('skip', false, { skipped: true, blocking: false, reason: 'no trackable decisions', total: 0, honored: 0, not_honored: [], message: 'No trackable decisions in CONTEXT.md.' });
|
|
92
|
+
}
|
|
93
|
+
// Every file the haystack is built from is typed evidence (#5170): one that exists but cannot be
|
|
94
|
+
// read is `unreadable`, never an empty slice of the haystack that makes a decision look unhonored.
|
|
95
|
+
const planContents = (0, decision_coverage_support_cjs_1.loadPlanContents)(phaseDir);
|
|
96
|
+
if (planContents.kind === 'unreadable')
|
|
97
|
+
return unreadableVerify(`${planContents.span ?? phaseDir}: ${planContents.reason}`);
|
|
98
|
+
const summaryParts = (0, decision_coverage_support_cjs_1.loadSummaryContents)(phaseDir);
|
|
99
|
+
if (summaryParts.kind === 'unreadable')
|
|
100
|
+
return unreadableVerify(`${summaryParts.span ?? phaseDir}: ${summaryParts.reason}`);
|
|
101
|
+
const modifiedFiles = (0, decision_coverage_support_cjs_1.readModifiedFilesContent)(projectDir, summaryParts.value);
|
|
102
|
+
if (modifiedFiles.kind === 'unreadable')
|
|
103
|
+
return unreadableVerify(`${modifiedFiles.span ?? 'files_modified'}: ${modifiedFiles.reason}`);
|
|
104
|
+
const haystack = [
|
|
105
|
+
planContents.value.join('\n\n'),
|
|
106
|
+
summaryParts.value.join('\n\n'),
|
|
107
|
+
modifiedFiles.value,
|
|
108
|
+
(0, decision_coverage_support_cjs_1.phaseCommitMessages)(projectDir, phaseDir),
|
|
109
|
+
].join('\n\n');
|
|
110
|
+
const notHonored = [];
|
|
111
|
+
let honored = 0;
|
|
112
|
+
for (const decision of decisions) {
|
|
113
|
+
if ((0, decision_coverage_support_cjs_1.decisionMentioned)(haystack, decision))
|
|
114
|
+
honored++;
|
|
115
|
+
else
|
|
116
|
+
notHonored.push({ id: decision.id, text: decision.text, category: decision.category });
|
|
117
|
+
}
|
|
118
|
+
return (0, gate_verdict_cjs_1.gateVerdict)(notHonored.length === 0 ? 'pass' : 'advisory', false, {
|
|
119
|
+
skipped: false,
|
|
120
|
+
blocking: false,
|
|
121
|
+
total: decisions.length,
|
|
122
|
+
honored,
|
|
123
|
+
not_honored: notHonored,
|
|
124
|
+
message: (0, decision_coverage_support_cjs_1.buildVerifyMessage)(notHonored),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Evaluation-scope resolver (#5164, epic #5056, ADR-5057 §4 second bullet): the ONE answer to
|
|
4
|
+
* "which commits and which file set does this gate or workflow step evaluate for this plan,
|
|
5
|
+
* phase or quick task".
|
|
6
|
+
*
|
|
7
|
+
* Before this module every consumer derived its own answer — `HEAD~1..HEAD`, `DIFF_BASE..HEAD`,
|
|
8
|
+
* `git log --all --grep` — and all of them disagreed with each other and with the work they
|
|
9
|
+
* meant to scope. Three constraints from the design lock hold here:
|
|
10
|
+
*
|
|
11
|
+
* 1. The scope is the UNION of each commit's own file set, never a range. A range keeps every
|
|
12
|
+
* interleaved non-phase commit in its window (measured on a 9,676-commit repo: the range
|
|
13
|
+
* held 23, 194 and 602 files where the union of the phase's commits held 9, 42 and 69).
|
|
14
|
+
* 2. Only commits reachable from the evaluated ref count (`git log --all` is gone). A commit
|
|
15
|
+
* that lives only on another branch is NAMED in `unreachable` and contributes nothing.
|
|
16
|
+
* 3. An empty union never becomes an empty scope that reviews nothing and reports success. It
|
|
17
|
+
* degrades to wider evidence (the phase-directory range) and SAYS so (`status: 'degraded'`,
|
|
18
|
+
* `reason`). What the union drops is named by path, not counted: `outsideUnion` (range
|
|
19
|
+
* minus union) and `missingOnDisk` (scoped paths that no longer exist).
|
|
20
|
+
*
|
|
21
|
+
* A phase's commits are the `## Task Commits` rows of its SUMMARY files (path-anchored: no commit
|
|
22
|
+
* message grep, the class re-fixed in #2989/#3191/#3503/#3995). A plan's commits are the
|
|
23
|
+
* reachable commits whose SUBJECT is `<type>(<phase>-<plan>):`, anchored and zero-padding
|
|
24
|
+
* tolerant (#4003). The row parse is a port of the byte-parity model from the closed PR #4127.
|
|
25
|
+
*
|
|
26
|
+
* Every git call goes through the `execGit` seam, which bounds each subprocess (its default
|
|
27
|
+
* budget); a timeout or a missing git is `unresolvable`, never a throw and never an empty scope.
|
|
28
|
+
*
|
|
29
|
+
* A gate module: imports no io module and writes nothing; `evaluateEvaluationScope` returns a
|
|
30
|
+
* `GateResult` the command router formats (`check evaluation-scope`).
|
|
31
|
+
*/
|
|
32
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
33
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MAX_COMMITS_CEILING = exports.SCOPE_EXCLUSION_PATHSPECS = void 0;
|
|
37
|
+
exports.extractTaskCommitRefs = extractTaskCommitRefs;
|
|
38
|
+
exports.planSubjectPattern = planSubjectPattern;
|
|
39
|
+
exports.isSafeRefArgument = isSafeRefArgument;
|
|
40
|
+
exports.isSafeDateArgument = isSafeDateArgument;
|
|
41
|
+
exports.resolveEvaluationScope = resolveEvaluationScope;
|
|
42
|
+
exports.evaluateEvaluationScope = evaluateEvaluationScope;
|
|
43
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
44
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
45
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
46
|
+
const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
|
|
47
|
+
const gate_phase_context_cjs_1 = require("./gate-phase-context.cjs");
|
|
48
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
49
|
+
const gate_evidence_cjs_1 = require("./gate-evidence.cjs");
|
|
50
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
51
|
+
/** Paths that are never part of a code-review or UI scope (planning artifacts, lockfiles). */
|
|
52
|
+
exports.SCOPE_EXCLUSION_PATHSPECS = Object.freeze([
|
|
53
|
+
':!.planning/', ':!ROADMAP.md', ':!STATE.md',
|
|
54
|
+
':!*-SUMMARY.md', ':!*-VERIFICATION.md', ':!*-PLAN.md',
|
|
55
|
+
':!package-lock.json', ':!yarn.lock', ':!Gemfile.lock', ':!poetry.lock',
|
|
56
|
+
]);
|
|
57
|
+
/** 41 chars per sha; 400 of them stay well under the 28K Windows-safe argv chunk. */
|
|
58
|
+
const SHA_CHUNK = 400;
|
|
59
|
+
const DEFAULT_MAX_COMMITS = 200;
|
|
60
|
+
const MAX_PATHSPECS = 20;
|
|
61
|
+
// ─── SUMMARY `## Task Commits` extraction (port of the #4127 model) ───────────
|
|
62
|
+
const TASK_COMMITS_HEADING = /^## Task Commits[ \t\r]*$/;
|
|
63
|
+
const NEXT_HEADING = /^## /;
|
|
64
|
+
const TASK_ROW_PREFIX = /^[ \t]*(?:[0-9]+\.|[-*])?[ \t]*\*\*Task[ \t]+[0-9]+:/;
|
|
65
|
+
const HEX_TOKEN = /`[0-9a-f]{7,40}`/g;
|
|
66
|
+
/**
|
|
67
|
+
* The commit ids a SUMMARY names, in document order, duplicates included. Section-scoped (every
|
|
68
|
+
* `## Task Commits` section), row-scoped (an optional list marker, the `**Task N:` label closed by
|
|
69
|
+
* its FIRST `**`) and backtick-anchored: only backticked lowercase hex AFTER that closing bold
|
|
70
|
+
* counts, so a sha quoted in prose or on the `**Plan metadata:**` line is not a task commit.
|
|
71
|
+
*/
|
|
72
|
+
function extractTaskCommitRefs(text) {
|
|
73
|
+
const refs = [];
|
|
74
|
+
let inside = false;
|
|
75
|
+
for (const line of text.split('\n')) {
|
|
76
|
+
if (TASK_COMMITS_HEADING.test(line)) {
|
|
77
|
+
inside = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (NEXT_HEADING.test(line)) {
|
|
81
|
+
inside = false;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!inside)
|
|
85
|
+
continue;
|
|
86
|
+
const row = TASK_ROW_PREFIX.exec(line);
|
|
87
|
+
if (!row)
|
|
88
|
+
continue;
|
|
89
|
+
const rest = line.slice(row[0].length);
|
|
90
|
+
const close = rest.indexOf('**');
|
|
91
|
+
if (close === -1)
|
|
92
|
+
continue;
|
|
93
|
+
for (const token of rest.slice(close + 2).match(HEX_TOKEN) ?? [])
|
|
94
|
+
refs.push(token.slice(1, -1));
|
|
95
|
+
}
|
|
96
|
+
return refs;
|
|
97
|
+
}
|
|
98
|
+
// ─── Plan-id → anchored subject pattern ───────────────────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Tolerate zero padding in the leading integer of a phase or plan segment (#4003, #4619, #4748);
|
|
101
|
+
* everything after the integer (`A`, `.1.2`) is matched literally.
|
|
102
|
+
*/
|
|
103
|
+
function paddedPattern(value) {
|
|
104
|
+
const m = /^(\d+)(.*)$/.exec(value);
|
|
105
|
+
if (!m)
|
|
106
|
+
return (0, pattern_cjs_1.escapeEre)(value);
|
|
107
|
+
const intPart = (m[1] ?? '').replace(/^0+(?=\d)/, '');
|
|
108
|
+
return `0*${(0, pattern_cjs_1.escapeEre)(intPart)}${(0, pattern_cjs_1.escapeEre)(m[2] ?? '')}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The anchored subject pattern for a plan id, or null when `planId` is empty, over-long or carries a
|
|
112
|
+
* control / whitespace character.
|
|
113
|
+
*
|
|
114
|
+
* A `<phase>-<plan>` id (`03-01`) is zero-padding tolerant: `feat(03-01):`, `test(3-1):` and
|
|
115
|
+
* `fix(03-01)!:` match, `feat(03-010):` does not. Any other id (a plan FILE NAME that does not follow
|
|
116
|
+
* the numbering) is matched LITERALLY — every ERE metacharacter escaped — so `x.*` or `a[b]` can only
|
|
117
|
+
* match a commit that names that exact id, and never widens the pattern.
|
|
118
|
+
*/
|
|
119
|
+
function planSubjectPattern(planId) {
|
|
120
|
+
if (planId.length === 0 || planId.length > 200 || /[\s\x00-\x1f\x7f]/.test(planId))
|
|
121
|
+
return null;
|
|
122
|
+
const dash = planId.indexOf('-');
|
|
123
|
+
const phasePart = dash > 0 ? planId.slice(0, dash) : '';
|
|
124
|
+
const planPart = dash > 0 ? planId.slice(dash + 1) : '';
|
|
125
|
+
if (/^[0-9A-Za-z.]+$/.test(phasePart) && /^[0-9A-Za-z.]+$/.test(planPart)) {
|
|
126
|
+
return `^[a-z]+\\(${paddedPattern(phasePart)}-${paddedPattern(planPart)}\\)!?:`;
|
|
127
|
+
}
|
|
128
|
+
return `^[a-z]+\\(${(0, pattern_cjs_1.escapeEre)(planId)}\\)!?:`;
|
|
129
|
+
}
|
|
130
|
+
// ─── Git plumbing ─────────────────────────────────────────────────────────────
|
|
131
|
+
class ScopeUnreadable extends Error {
|
|
132
|
+
reason;
|
|
133
|
+
/** git's exit status when the failure was a plain non-zero exit (not a timeout or a missing git). */
|
|
134
|
+
exitCode;
|
|
135
|
+
constructor(reason, exitCode = null) {
|
|
136
|
+
super(reason);
|
|
137
|
+
this.reason = reason;
|
|
138
|
+
this.exitCode = exitCode;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function makeGit(projectDir, runner) {
|
|
142
|
+
return (args, cwd = projectDir) => {
|
|
143
|
+
const result = runner(args, { cwd });
|
|
144
|
+
if (result.timedOut)
|
|
145
|
+
throw new ScopeUnreadable('git-timeout');
|
|
146
|
+
if (result.exitCode === 127)
|
|
147
|
+
throw new ScopeUnreadable('git-unavailable');
|
|
148
|
+
if (result.exitCode !== 0)
|
|
149
|
+
throw new ScopeUnreadable(`git-failed:${args[0] ?? ''}`, result.exitCode);
|
|
150
|
+
return result.stdout;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Run a git question whose exit status 1 is a legitimate "no" (`merge-base --is-ancestor`,
|
|
155
|
+
* `rev-parse --verify --quiet`). Exit 1 returns `null`; every other failure — a timeout, a missing
|
|
156
|
+
* git, a bad object (exit 128) — still rethrows, so a hung or broken git is `unresolvable`, never a
|
|
157
|
+
* silent "no".
|
|
158
|
+
*/
|
|
159
|
+
function gitOrNo(git, args, cwd) {
|
|
160
|
+
try {
|
|
161
|
+
return git(args, cwd);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof ScopeUnreadable && error.exitCode === 1)
|
|
165
|
+
return null;
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function splitNul(output) {
|
|
170
|
+
return output.split('\0').filter((entry) => entry.length > 0);
|
|
171
|
+
}
|
|
172
|
+
function chunk(items, size) {
|
|
173
|
+
const out = [];
|
|
174
|
+
for (let i = 0; i < items.length; i += size)
|
|
175
|
+
out.push(items.slice(i, i + size));
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
function uniqueSorted(items) {
|
|
179
|
+
return [...new Set(items)].sort();
|
|
180
|
+
}
|
|
181
|
+
/** True when `candidate` cannot be read as a git option and carries no whitespace or control byte. */
|
|
182
|
+
function isSafeRefArgument(candidate) {
|
|
183
|
+
return candidate.length > 0 && candidate.length <= 256 && !candidate.startsWith('-') && !/[\s\x00-\x1f\x7f]/.test(candidate);
|
|
184
|
+
}
|
|
185
|
+
/** A git approxidate: words, digits and `- : . ,` only, so it can never read as an option or a pathspec. */
|
|
186
|
+
function isSafeDateArgument(candidate) {
|
|
187
|
+
return /^[A-Za-z0-9][A-Za-z0-9 .,:+-]{0,63}$/.test(candidate);
|
|
188
|
+
}
|
|
189
|
+
function isAncestor(git, sha, of) {
|
|
190
|
+
return gitOrNo(git, ['merge-base', '--is-ancestor', sha, of]) !== null;
|
|
191
|
+
}
|
|
192
|
+
// ─── The resolver ─────────────────────────────────────────────────────────────
|
|
193
|
+
function emptyScope(unit) {
|
|
194
|
+
return {
|
|
195
|
+
unit, status: 'resolved', source: 'none', reason: null, commits: [],
|
|
196
|
+
changedFiles: [], files: [], missingOnDisk: [], outsideUnion: [], unreachable: [], rangeBase: null,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function resolveEvaluationScope(projectDir, unit, options = {}) {
|
|
200
|
+
const scope = emptyScope(unit);
|
|
201
|
+
const git = makeGit(projectDir, options.execGit ?? shell_command_projection_cjs_1.execGit);
|
|
202
|
+
const ref = options.ref ?? 'HEAD';
|
|
203
|
+
try {
|
|
204
|
+
if (!isSafeRefArgument(ref))
|
|
205
|
+
throw new ScopeUnreadable('unsafe-ref');
|
|
206
|
+
if (options.since !== undefined && !isSafeRefArgument(options.since))
|
|
207
|
+
throw new ScopeUnreadable('unsafe-since');
|
|
208
|
+
git(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
|
|
209
|
+
const repoRoot = git(['rev-parse', '--show-toplevel']);
|
|
210
|
+
if (unit.kind === 'phase')
|
|
211
|
+
resolvePhase(projectDir, unit, scope, git, ref, options, repoRoot);
|
|
212
|
+
else
|
|
213
|
+
resolveBySubject(unit, scope, git, ref, options, repoRoot);
|
|
214
|
+
return scope;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const reason = error instanceof ScopeUnreadable ? error.reason : 'resolver-error';
|
|
218
|
+
return { ...emptyScope(unit), status: 'unresolvable', reason };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** A commit's own file set. `--first-parent`: a merge commit contributes its diff against parent 1. */
|
|
222
|
+
function commitFiles(git, sha) {
|
|
223
|
+
const out = git(['-c', 'core.quotepath=off', 'show', '--first-parent', '--pretty=format:', '--name-only', '-z', sha, '--', '.', ...exports.SCOPE_EXCLUSION_PATHSPECS]);
|
|
224
|
+
return splitNul(out);
|
|
225
|
+
}
|
|
226
|
+
function rangeFiles(git, base, tip) {
|
|
227
|
+
const out = git(['-c', 'core.quotepath=off', 'diff', '--name-only', '-z', `${base}..${tip}`, '--', '.', ...exports.SCOPE_EXCLUSION_PATHSPECS]);
|
|
228
|
+
return splitNul(out);
|
|
229
|
+
}
|
|
230
|
+
function finalizeFiles(scope, repoRoot, changed) {
|
|
231
|
+
scope.changedFiles = uniqueSorted(changed);
|
|
232
|
+
const existing = [];
|
|
233
|
+
const missing = [];
|
|
234
|
+
for (const file of scope.changedFiles) {
|
|
235
|
+
(node_fs_1.default.existsSync(node_path_1.default.join(repoRoot, file)) ? existing : missing).push(file);
|
|
236
|
+
}
|
|
237
|
+
scope.files = existing;
|
|
238
|
+
scope.missingOnDisk = missing;
|
|
239
|
+
}
|
|
240
|
+
function logFormat(withBody) {
|
|
241
|
+
return withBody ? '--format=%H%x1f%s%x1f%b%x1e' : '--format=%H%x1f%s%x1e';
|
|
242
|
+
}
|
|
243
|
+
function parseLog(out, withBody) {
|
|
244
|
+
const commits = [];
|
|
245
|
+
for (const record of out.split('\x1e')) {
|
|
246
|
+
const trimmed = record.replace(/^\n+/, '');
|
|
247
|
+
if (!trimmed)
|
|
248
|
+
continue;
|
|
249
|
+
const [sha, subject, body] = trimmed.split('\x1f');
|
|
250
|
+
if (!sha || !/^[0-9a-f]{40}$/.test(sha))
|
|
251
|
+
continue;
|
|
252
|
+
const commit = { sha, subject: subject ?? '' };
|
|
253
|
+
if (withBody)
|
|
254
|
+
commit.body = (body ?? '').trim();
|
|
255
|
+
commits.push(commit);
|
|
256
|
+
}
|
|
257
|
+
return commits;
|
|
258
|
+
}
|
|
259
|
+
function subjectsFor(git, shas, withBody, chunkSize) {
|
|
260
|
+
const out = [];
|
|
261
|
+
for (const group of chunk(shas, chunkSize)) {
|
|
262
|
+
out.push(...parseLog(git(['log', '--no-walk=unsorted', logFormat(withBody), ...group]), withBody));
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The tip of `shas`: topological order lists a descendant before its ancestors, so the first
|
|
268
|
+
* commit of a walk started at all of them is one no other listed commit descends from. (Commit
|
|
269
|
+
* DATES are not used — commits made within one second tie, and a tie picks an arbitrary tip.)
|
|
270
|
+
*/
|
|
271
|
+
function newestOf(git, shas, chunkSize) {
|
|
272
|
+
// More shas than fit one argv chunk are reduced chunk by chunk: the chunks' tips are themselves a
|
|
273
|
+
// (shorter) list, until one chunk holds them all. Nothing is silently dropped.
|
|
274
|
+
if (shas.length > chunkSize) {
|
|
275
|
+
const tips = chunk(shas, chunkSize).map((group) => newestOf(git, group, chunkSize));
|
|
276
|
+
return newestOf(git, tips, chunkSize);
|
|
277
|
+
}
|
|
278
|
+
const tip = git(['rev-list', '--topo-order', '-n', '1', ...shas]).split('\n').filter(Boolean)[0];
|
|
279
|
+
return tip ?? shas[0] ?? 'HEAD';
|
|
280
|
+
}
|
|
281
|
+
/** One clamp for every commit cap, so the CLI limit and the resolver's ceiling cannot diverge. */
|
|
282
|
+
exports.MAX_COMMITS_CEILING = 1000;
|
|
283
|
+
function clampMaxCommits(requested) {
|
|
284
|
+
return Math.max(1, Math.min(requested ?? DEFAULT_MAX_COMMITS, exports.MAX_COMMITS_CEILING));
|
|
285
|
+
}
|
|
286
|
+
/** git's empty tree (SHA-1 repositories): the base a range needs to include a root commit's own files. */
|
|
287
|
+
const EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
288
|
+
/**
|
|
289
|
+
* The phase-start anchor on `ref`: the parent of the commit that first added anything under the
|
|
290
|
+
* phase dir. A phase directory that arrived with the repository's root commit has no parent, so the
|
|
291
|
+
* anchor is the empty tree — `<empty>..<ref>` then includes the root commit's own files.
|
|
292
|
+
*/
|
|
293
|
+
function phaseRangeBase(git, phaseDir, ref) {
|
|
294
|
+
const first = git(['log', ref, '--format=%H', '--diff-filter=A', '--', '.'], phaseDir)
|
|
295
|
+
.split('\n').filter(Boolean).pop();
|
|
296
|
+
if (!first)
|
|
297
|
+
return null;
|
|
298
|
+
return gitOrNo(git, ['rev-parse', '--verify', '--quiet', `${first}^`]) ?? EMPTY_TREE;
|
|
299
|
+
}
|
|
300
|
+
function resolvePhase(projectDir, unit, scope, git, ref, options, repoRoot) {
|
|
301
|
+
let phaseDir = unit.phaseDir ?? '';
|
|
302
|
+
if (unit.phaseDir === undefined) {
|
|
303
|
+
const located = (0, gate_phase_context_cjs_1.resolvePhaseDir)(projectDir, unit.phase);
|
|
304
|
+
if (located.kind === 'unreadable')
|
|
305
|
+
throw new ScopeUnreadable(`phase-dir-lookup-failed:${located.reason}`);
|
|
306
|
+
if (located.kind === 'found')
|
|
307
|
+
phaseDir = located.value;
|
|
308
|
+
}
|
|
309
|
+
if (!phaseDir)
|
|
310
|
+
throw new ScopeUnreadable('phase-dir-not-found');
|
|
311
|
+
const refs = [];
|
|
312
|
+
let summaryCount = 0;
|
|
313
|
+
// The canonical LIVE summary set (root + nested, superseded excluded) from its single owner. A scan
|
|
314
|
+
// that did not see every summary (an existing nested plans/ that could not be read) is "could not
|
|
315
|
+
// look": a short summary set would narrow the phase's commits and report success (#5170).
|
|
316
|
+
const planScan = (0, gate_evidence_cjs_1.readPlanScanEvidence)(phaseDir);
|
|
317
|
+
if (planScan.kind === 'unreadable')
|
|
318
|
+
throw new ScopeUnreadable('phase-dir-unreadable');
|
|
319
|
+
const entries = [...planScan.value.summaryFiles].sort();
|
|
320
|
+
for (const name of entries) {
|
|
321
|
+
try {
|
|
322
|
+
refs.push(...extractTaskCommitRefs(node_fs_1.default.readFileSync(node_path_1.default.join(phaseDir, name), 'utf-8')));
|
|
323
|
+
summaryCount += 1;
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
throw new ScopeUnreadable(`summary-unreadable:${name}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Resolve each listed id to a full sha reachable from the ref (and, for wave scoping, newer than `since`).
|
|
330
|
+
const shas = [];
|
|
331
|
+
const unreachable = [];
|
|
332
|
+
let sinceDropped = 0;
|
|
333
|
+
for (const id of [...new Set(refs)]) {
|
|
334
|
+
// Exit 1 = no object answers to the id (rebased away); exit 128 = an ambiguous short id. Both
|
|
335
|
+
// are named below, per id, and neither poisons the phase. A timeout still rethrows.
|
|
336
|
+
let full = '';
|
|
337
|
+
try {
|
|
338
|
+
full = gitOrNo(git, ['rev-parse', '--verify', '--quiet', `${id}^{commit}`]) ?? '';
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
if (!(error instanceof ScopeUnreadable && error.exitCode === 128))
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
if (!full || !isAncestor(git, full, ref)) {
|
|
345
|
+
unreachable.push(id);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (options.since !== undefined && isAncestor(git, full, options.since)) {
|
|
349
|
+
sinceDropped += 1;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
shas.push(full);
|
|
353
|
+
}
|
|
354
|
+
scope.unreachable = unreachable.sort();
|
|
355
|
+
scope.rangeBase = options.since ?? phaseRangeBase(git, phaseDir, ref);
|
|
356
|
+
const union = new Set();
|
|
357
|
+
const commits = [];
|
|
358
|
+
const withFiles = options.includeFiles ?? true;
|
|
359
|
+
// At least 2: a chunk of one never shrinks the list `newestOf` reduces.
|
|
360
|
+
const chunkSize = Math.max(2, options.shaChunk ?? SHA_CHUNK);
|
|
361
|
+
for (const meta of subjectsFor(git, [...new Set(shas)], options.includeBody === true, chunkSize)) {
|
|
362
|
+
const files = commitFiles(git, meta.sha);
|
|
363
|
+
for (const file of files)
|
|
364
|
+
union.add(file);
|
|
365
|
+
commits.push(withFiles ? { ...meta, files: [...files].sort() } : meta);
|
|
366
|
+
}
|
|
367
|
+
if (union.size > 0) {
|
|
368
|
+
scope.source = 'task-commits';
|
|
369
|
+
scope.commits = commits;
|
|
370
|
+
finalizeFiles(scope, repoRoot, union);
|
|
371
|
+
if (scope.rangeBase) {
|
|
372
|
+
const window = rangeFiles(git, scope.rangeBase, newestOf(git, commits.map((c) => c.sha), chunkSize));
|
|
373
|
+
scope.outsideUnion = uniqueSorted(window.filter((f) => !union.has(f)));
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// Empty union: the planning-only phase, a phase with no SUMMARY, or one whose ids are all gone.
|
|
378
|
+
const reason = summaryCount === 0 ? 'no-summary'
|
|
379
|
+
: refs.length === 0 ? 'no-task-commit-rows'
|
|
380
|
+
: shas.length === 0 ? (sinceDropped > 0 ? 'no-task-commits-since' : 'no-reachable-task-commits')
|
|
381
|
+
: 'empty-after-exclusions';
|
|
382
|
+
if (!scope.rangeBase)
|
|
383
|
+
throw new ScopeUnreadable(`${reason}:no-phase-start-anchor`);
|
|
384
|
+
scope.status = 'degraded';
|
|
385
|
+
scope.reason = reason;
|
|
386
|
+
scope.source = 'phase-range';
|
|
387
|
+
const max = clampMaxCommits(options.maxCommits);
|
|
388
|
+
scope.commits = parseLog(git(['log', `${scope.rangeBase}..${ref}`, logFormat(options.includeBody === true), '-n', String(max)]), options.includeBody === true);
|
|
389
|
+
finalizeFiles(scope, repoRoot, rangeFiles(git, scope.rangeBase, ref));
|
|
390
|
+
}
|
|
391
|
+
function resolveBySubject(unit, scope, git, ref, options, repoRoot) {
|
|
392
|
+
const withBody = options.includeBody === true;
|
|
393
|
+
// Refuse rather than truncate: a silently dropped positive pathspec narrows the match.
|
|
394
|
+
const pathspecs = options.pathspecs ?? [];
|
|
395
|
+
if (pathspecs.length > MAX_PATHSPECS)
|
|
396
|
+
throw new ScopeUnreadable('too-many-pathspecs');
|
|
397
|
+
const max = clampMaxCommits(options.maxCommits);
|
|
398
|
+
let range = ref;
|
|
399
|
+
if (options.milestoneBound === true) {
|
|
400
|
+
let tag = '';
|
|
401
|
+
try {
|
|
402
|
+
tag = git(['describe', '--tags', '--abbrev=0', ref]);
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
// Exit 128 = no reachable tag → unbounded; a timeout or a missing git still rethrows.
|
|
406
|
+
if (!(error instanceof ScopeUnreadable && error.exitCode === 128))
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
if (tag)
|
|
410
|
+
range = `${tag}..${ref}`;
|
|
411
|
+
}
|
|
412
|
+
const grepArgs = [];
|
|
413
|
+
let anchored = null;
|
|
414
|
+
if (unit.kind === 'plan') {
|
|
415
|
+
const pattern = planSubjectPattern(unit.planId);
|
|
416
|
+
if (pattern === null)
|
|
417
|
+
throw new ScopeUnreadable('invalid-plan-id');
|
|
418
|
+
grepArgs.push('--extended-regexp', `--grep=${pattern}`);
|
|
419
|
+
anchored = new RegExp(pattern);
|
|
420
|
+
scope.source = 'plan-subjects';
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
if (!isSafeRefArgument(unit.id))
|
|
424
|
+
throw new ScopeUnreadable('invalid-quick-id');
|
|
425
|
+
grepArgs.push('--fixed-strings', `--grep=${unit.id}`);
|
|
426
|
+
scope.source = 'quick-subjects';
|
|
427
|
+
}
|
|
428
|
+
const sinceArgs = [];
|
|
429
|
+
if (options.committedSince !== undefined) {
|
|
430
|
+
if (!isSafeDateArgument(options.committedSince))
|
|
431
|
+
throw new ScopeUnreadable('unsafe-committed-since');
|
|
432
|
+
sinceArgs.push(`--since=${options.committedSince}`);
|
|
433
|
+
}
|
|
434
|
+
const out = git(['log', range, logFormat(withBody), '-n', String(max * 2), ...sinceArgs, ...grepArgs, ...(pathspecs.length > 0 ? ['--', ...pathspecs] : [])]);
|
|
435
|
+
// `--grep` matches ANY message line; a plan lookup is subject-anchored, so filter on the subject.
|
|
436
|
+
let commits = parseLog(out, withBody).filter((c) => anchored === null || anchored.test(c.subject)).slice(0, max);
|
|
437
|
+
const since = options.since;
|
|
438
|
+
if (since !== undefined)
|
|
439
|
+
commits = commits.filter((c) => !isAncestor(git, c.sha, since));
|
|
440
|
+
if (options.commitsOnly === true) {
|
|
441
|
+
scope.commits = commits;
|
|
442
|
+
if (commits.length === 0)
|
|
443
|
+
scope.reason = 'no-matching-commits';
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const withFiles = options.includeFiles ?? false;
|
|
447
|
+
const union = new Set();
|
|
448
|
+
const result = [];
|
|
449
|
+
for (const meta of commits) {
|
|
450
|
+
const files = commitFiles(git, meta.sha);
|
|
451
|
+
for (const file of files)
|
|
452
|
+
union.add(file);
|
|
453
|
+
result.push(withFiles ? { ...meta, files: [...files].sort() } : meta);
|
|
454
|
+
}
|
|
455
|
+
scope.commits = result;
|
|
456
|
+
finalizeFiles(scope, repoRoot, union);
|
|
457
|
+
// Found nothing: a truthful "none" (status stays `resolved` — git was read), said out loud so a
|
|
458
|
+
// caller can tell "no such commits" from "an empty scope that was never computed". Commits whose
|
|
459
|
+
// every path is excluded (planning-only) are an empty scope too, and say so.
|
|
460
|
+
if (result.length === 0)
|
|
461
|
+
scope.reason = 'no-matching-commits';
|
|
462
|
+
else if (scope.changedFiles.length === 0)
|
|
463
|
+
scope.reason = 'empty-after-exclusions';
|
|
464
|
+
}
|
|
465
|
+
// ─── The `check evaluation-scope` verb ────────────────────────────────────────
|
|
466
|
+
const VALUE_FLAGS = new Set(['--phase', '--phase-dir', '--plan', '--quick', '--since', '--ref', '--pathspec', '--committed-since', '--max-commits']);
|
|
467
|
+
const BOOLEAN_FLAGS = new Set(['--milestone-bound', '--include-body', '--include-files', '--commits-only']);
|
|
468
|
+
function parseScopeArgs(args) {
|
|
469
|
+
const values = new Map();
|
|
470
|
+
const flags = new Set();
|
|
471
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
472
|
+
const arg = args[i] ?? '';
|
|
473
|
+
if (BOOLEAN_FLAGS.has(arg)) {
|
|
474
|
+
flags.add(arg);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (!VALUE_FLAGS.has(arg))
|
|
478
|
+
return `unknown argument: ${arg}`;
|
|
479
|
+
const value = args[i + 1];
|
|
480
|
+
if (value === undefined)
|
|
481
|
+
return `${arg} requires a value`;
|
|
482
|
+
i += 1;
|
|
483
|
+
values.set(arg, [...(values.get(arg) ?? []), value]);
|
|
484
|
+
}
|
|
485
|
+
const one = (flag) => values.get(flag)?.[0];
|
|
486
|
+
const phase = one('--phase');
|
|
487
|
+
const phaseDir = one('--phase-dir');
|
|
488
|
+
const plan = one('--plan');
|
|
489
|
+
const quick = one('--quick');
|
|
490
|
+
const given = [phase, phaseDir, plan, quick].filter((v) => v !== undefined).length;
|
|
491
|
+
if (given !== 1)
|
|
492
|
+
return 'exactly one of --phase <phase>, --phase-dir <dir>, --plan <phase>-<plan> or --quick <id> is required';
|
|
493
|
+
let unit;
|
|
494
|
+
if (quick !== undefined)
|
|
495
|
+
unit = { kind: 'quick', id: quick };
|
|
496
|
+
else if (plan !== undefined)
|
|
497
|
+
unit = { kind: 'plan', planId: plan };
|
|
498
|
+
else if (phaseDir !== undefined)
|
|
499
|
+
unit = { kind: 'phase', phase: '', phaseDir };
|
|
500
|
+
else
|
|
501
|
+
unit = { kind: 'phase', phase: phase ?? '' };
|
|
502
|
+
const options = {};
|
|
503
|
+
const ref = one('--ref');
|
|
504
|
+
const since = one('--since');
|
|
505
|
+
const pathspecs = values.get('--pathspec');
|
|
506
|
+
if (ref !== undefined)
|
|
507
|
+
options.ref = ref;
|
|
508
|
+
if (since !== undefined)
|
|
509
|
+
options.since = since;
|
|
510
|
+
if (pathspecs !== undefined)
|
|
511
|
+
options.pathspecs = pathspecs;
|
|
512
|
+
const committedSince = one('--committed-since');
|
|
513
|
+
if (committedSince !== undefined)
|
|
514
|
+
options.committedSince = committedSince;
|
|
515
|
+
const maxCommits = one('--max-commits');
|
|
516
|
+
if (maxCommits !== undefined) {
|
|
517
|
+
if (!/^[1-9][0-9]{0,3}$/.test(maxCommits) || Number(maxCommits) > exports.MAX_COMMITS_CEILING) {
|
|
518
|
+
return `--max-commits must be an integer from 1 to ${exports.MAX_COMMITS_CEILING}, got: ${maxCommits}`;
|
|
519
|
+
}
|
|
520
|
+
options.maxCommits = Number(maxCommits);
|
|
521
|
+
}
|
|
522
|
+
if (flags.has('--milestone-bound'))
|
|
523
|
+
options.milestoneBound = true;
|
|
524
|
+
if (flags.has('--include-body'))
|
|
525
|
+
options.includeBody = true;
|
|
526
|
+
if (flags.has('--include-files'))
|
|
527
|
+
options.includeFiles = true;
|
|
528
|
+
if (flags.has('--commits-only'))
|
|
529
|
+
options.commitsOnly = true;
|
|
530
|
+
return { unit, options };
|
|
531
|
+
}
|
|
532
|
+
/** `check evaluation-scope` — argv after the verb. A resolver, not a policy: it never blocks. */
|
|
533
|
+
function evaluateEvaluationScope(input) {
|
|
534
|
+
const parsed = parseScopeArgs(input.args);
|
|
535
|
+
if (typeof parsed === 'string') {
|
|
536
|
+
return (0, gate_verdict_cjs_1.gateUsageFailure)(gate_verdict_cjs_1.GATE_FAILURE_CODE.USAGE, `check evaluation-scope: ${parsed}`);
|
|
537
|
+
}
|
|
538
|
+
const options = input.execGit ? { ...parsed.options, execGit: input.execGit } : parsed.options;
|
|
539
|
+
let unit = parsed.unit;
|
|
540
|
+
if (unit.kind === 'phase' && unit.phaseDir !== undefined) {
|
|
541
|
+
// `--phase-dir` is caller-supplied: it must stay inside the project (realpath containment).
|
|
542
|
+
const contained = (0, gate_phase_context_cjs_1.resolveContainedPath)(unit.phaseDir, input.projectDir);
|
|
543
|
+
if ((0, gate_verdict_cjs_1.isGateUsageFailure)(contained))
|
|
544
|
+
return contained;
|
|
545
|
+
unit = { kind: 'phase', phase: '', phaseDir: contained };
|
|
546
|
+
}
|
|
547
|
+
const scope = resolveEvaluationScope(input.projectDir, unit, options);
|
|
548
|
+
// An unresolvable scope is "could not look" (a missing git, a timeout): outcome `unreadable`, exit
|
|
549
|
+
// UNAVAILABLE, never a pass-shaped exit 0 (#5170). The payload is unchanged.
|
|
550
|
+
if (scope.status === 'unresolvable')
|
|
551
|
+
return (0, gate_verdict_cjs_1.gateUnreadable)(false, { ...scope });
|
|
552
|
+
// `pass` only for a scope with nothing to explain; an empty-but-resolved scope (it carries a reason) is advisory.
|
|
553
|
+
const outcome = scope.status === 'degraded' || scope.reason !== null ? 'advisory' : 'pass';
|
|
554
|
+
return (0, gate_verdict_cjs_1.gateVerdict)(outcome, false, { ...scope });
|
|
555
|
+
}
|