@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
|
@@ -18,19 +18,32 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
20
|
const frontmatter = require("./frontmatter.cjs");
|
|
21
|
-
const { extractFrontmatter } = frontmatter;
|
|
21
|
+
const { extractFrontmatter, frontmatterBlock } = frontmatter;
|
|
22
22
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
23
|
const markdownSectionizer = require("./markdown-sectionizer.cjs");
|
|
24
24
|
const { stripFencedCode } = markdownSectionizer;
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
26
|
const verification = require("./verification.cjs");
|
|
27
|
-
const { readVerificationStatus } = verification;
|
|
27
|
+
const { readVerificationStatus, reportStatusOf, isReportContained, VERIFICATION_STATUS } = verification;
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const phaseIdMod = require("./phase-id.cjs");
|
|
30
30
|
const { scopeToPhase } = phaseIdMod;
|
|
31
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
32
|
const coreUtils = require("./core-utils.cjs");
|
|
33
|
-
const { normalizeLineEndings } = coreUtils;
|
|
33
|
+
const { normalizeLineEndings, countMatchedSummaries } = coreUtils;
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
|
+
const planScanMod = require("./plan-scan.cjs");
|
|
36
|
+
const { isRootPlanFile } = planScanMod;
|
|
37
|
+
// Two CommonMark-legal line terminators (LINE SEPARATOR, PARAGRAPH SEPARATOR)
|
|
38
|
+
// that a naive `split('\n')`-only scan would not treat as line breaks.
|
|
39
|
+
// Built via String.fromCharCode rather than written as a regex-literal escape
|
|
40
|
+
// sequence, deliberately: this source file is round-tripped through tooling
|
|
41
|
+
// that decodes a literal backslash-u escape into the real character, which
|
|
42
|
+
// would leave an ACTUAL U+2028/U+2029 sitting inside a regex literal — and
|
|
43
|
+
// both are themselves JS/TS source line terminators, so the regex literal
|
|
44
|
+
// containing one would be truncated at that point and fail to parse at all.
|
|
45
|
+
const EXOTIC_LINE_SEPARATORS = String.fromCharCode(0x2028) + String.fromCharCode(0x2029);
|
|
46
|
+
const LINE_SPLIT_RE = new RegExp('[\\n' + EXOTIC_LINE_SEPARATORS + ']');
|
|
34
47
|
// ─── Blocking state sets (documented for maintainability) ─────────────────────
|
|
35
48
|
// UAT file frontmatter `status` values that indicate the file is not fully done
|
|
36
49
|
const BLOCKING_UAT_FM_STATUSES = new Set([
|
|
@@ -38,15 +51,41 @@ const BLOCKING_UAT_FM_STATUSES = new Set([
|
|
|
38
51
|
]);
|
|
39
52
|
// UAT file frontmatter `result` values that indicate failure
|
|
40
53
|
const BLOCKING_UAT_FM_RESULTS = new Set(['pending', 'blocked', 'failed']);
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'failed', 'in_progress',
|
|
47
|
-
]);
|
|
54
|
+
// #5118: the VERIFICATION report's `status` vocabulary is the owner's closed
|
|
55
|
+
// enum (src/verification.cts) — this module no longer keeps its own passing /
|
|
56
|
+
// blocking sets. `pending|blocked|partial|failed|in_progress` were never
|
|
57
|
+
// written by the verifier; a report carrying one (or any other out-of-set
|
|
58
|
+
// value) is now a VerificationStatusError from the owner, not a blocker.
|
|
48
59
|
// UAT test-item `result` values that count as passing
|
|
49
60
|
const PASSING_RESULTS = new Set(['passed', 'pass']);
|
|
61
|
+
// #4546 — a `skipped` test-item whose reason carries the verify-work writer's
|
|
62
|
+
// deferral template prefix ("Deferred follow-up: …", #1921) is a deliberately
|
|
63
|
+
// deferred follow-up: non-blocking. Quote-tolerant (the writer wraps the value
|
|
64
|
+
// in double quotes) and case-insensitive (human-edited files vary). Anything
|
|
65
|
+
// else — a reasonless skip, a non-deferral reason — still blocks. PARITY: this
|
|
66
|
+
// matcher and the writer template in gsd-core/workflows/verify-work.md
|
|
67
|
+
// (process_response) are two halves of one contract, pinned together by
|
|
68
|
+
// tests/verify-work-deferred-promotion.test.cjs.
|
|
69
|
+
const DEFERRED_REASON_RE = /^["']?deferred follow-up\b/i;
|
|
70
|
+
// Trust note (#4546 review): the reason line is user-authored state — an
|
|
71
|
+
// author could equally write `result: passed` — so this prefix is an
|
|
72
|
+
// AUTHORING contract with the verify-work writer, not a security boundary.
|
|
73
|
+
// A hand-written deferral that skips the UAT file's ## Deferred Follow-Ups
|
|
74
|
+
// section also bypasses the complete_session promotion offer; the section is
|
|
75
|
+
// the durable project-level record. Variant spellings that do not match
|
|
76
|
+
// ("Deferred follow-ups:", "followup") block — fail-closed by design.
|
|
77
|
+
// #4983 — the sibling case #4546 left unfixed: a `result: issue` test whose
|
|
78
|
+
// `## Gaps` entry (templates/UAT.md) has been reconciled to `status: resolved`
|
|
79
|
+
// by an executed gap-closure plan (verify-work.md's reconcile_gaps step,
|
|
80
|
+
// #1921) is a genuinely fixed-and-verified issue: non-blocking. Unlike the
|
|
81
|
+
// deferred-skip case above, this signal is NOT trusted on the entry's
|
|
82
|
+
// `status:`/`resolved_by:` text alone — `isTestGapResolved` below also
|
|
83
|
+
// requires `resolved_by` to name a `*-PLAN.md` file that actually exists in
|
|
84
|
+
// this phase directory with a matching `*-SUMMARY.md` (mirroring
|
|
85
|
+
// reconcile_gaps' own criterion: a plan whose `gap_ids` names the gap AND has
|
|
86
|
+
// a SUMMARY was actually executed) — so a `resolved_by` naming no executed
|
|
87
|
+
// plan still blocks, closing the false-green risk a text-only trust model
|
|
88
|
+
// would leave open for a status this predicate gate is not free to bypass.
|
|
50
89
|
// ─── stripFalsePositiveContexts ───────────────────────────────────────────────
|
|
51
90
|
/**
|
|
52
91
|
* Remove contexts that can contain `result: ...` lines that are NOT real test results:
|
|
@@ -59,8 +98,10 @@ const PASSING_RESULTS = new Set(['passed', 'pass']);
|
|
|
59
98
|
* Returns surviving lines joined by '\n'. Robust to CRLF input.
|
|
60
99
|
*/
|
|
61
100
|
function stripFalsePositiveContexts(content) {
|
|
62
|
-
// Step (a): strip leading frontmatter block
|
|
63
|
-
|
|
101
|
+
// Step (a): strip the leading frontmatter block — the one `frontmatterBlock` (the one fence
|
|
102
|
+
// owner) finds — with its closing fence line's line ending.
|
|
103
|
+
const block = frontmatterBlock(content);
|
|
104
|
+
let stripped = block ? block.rest.replace(/^\r?\n/, '') : content;
|
|
64
105
|
// Step (b): remove HTML comments anywhere; unterminated comment swallows to EOF
|
|
65
106
|
stripped = stripped.replace(/<!--[\s\S]*?(?:-->|$)/g, '');
|
|
66
107
|
// Step (c): remove fenced code blocks via the canonical seam (ADR-1372 T5)
|
|
@@ -119,19 +160,19 @@ function parseUatResultItems(cleanContent) {
|
|
|
119
160
|
// #3078-CR MEDIUM (security review follow-up): STRUCTURE and ATTRIBUTION
|
|
120
161
|
// need different split frames. This is a STRUCTURE scan — finding where a
|
|
121
162
|
// heading block begins — and there is no attribution distinction to
|
|
122
|
-
// preserve, so split on any of
|
|
123
|
-
//
|
|
124
|
-
// a naive `split('\n')`-only
|
|
125
|
-
//
|
|
126
|
-
// `\n`-delimited one. Contrast the
|
|
127
|
-
// ATTRIBUTION scan and must NOT do this.
|
|
163
|
+
// preserve, so split on any of the two exotic line separators alongside
|
|
164
|
+
// ordinary `\n` (LINE_SPLIT_RE, above): a heading delimited by one of them
|
|
165
|
+
// (origin/next's `/m`-anchored scan found these; a naive `split('\n')`-only
|
|
166
|
+
// port silently stopped finding them, making the gate MORE permissive than
|
|
167
|
+
// origin/next) is found exactly like a `\n`-delimited one. Contrast the
|
|
168
|
+
// `result:` scan below, which is an ATTRIBUTION scan and must NOT do this.
|
|
128
169
|
const HEADING_LINE_RE = /^###\s*(\d+)\.\s*(.+)$/;
|
|
129
170
|
const headings = [];
|
|
130
171
|
{
|
|
131
172
|
// All three separators are exactly one UTF-16 code unit, so the
|
|
132
173
|
// `line.length + 1` offset arithmetic below stays valid regardless of
|
|
133
174
|
// which separator terminated a given line.
|
|
134
|
-
const lines = cleanContent.split(
|
|
175
|
+
const lines = cleanContent.split(LINE_SPLIT_RE);
|
|
135
176
|
let offset = 0;
|
|
136
177
|
for (const line of lines) {
|
|
137
178
|
const hMatch = line.match(HEADING_LINE_RE);
|
|
@@ -152,8 +193,8 @@ function parseUatResultItems(cleanContent) {
|
|
|
152
193
|
// A block spans until the START of the next heading's line (tracked
|
|
153
194
|
// directly from the same split-frame scan above), not a re-search for a
|
|
154
195
|
// literal '\n###' over unsplit text -- the latter would silently miss a
|
|
155
|
-
// next heading delimited by
|
|
156
|
-
// every subsequent block into this one.
|
|
196
|
+
// next heading delimited by an exotic separator instead of '\n' and
|
|
197
|
+
// swallow every subsequent block into this one.
|
|
157
198
|
const blockContent = i + 1 < headings.length
|
|
158
199
|
? cleanContent.slice(blockStart, headings[i + 1].lineStart)
|
|
159
200
|
: cleanContent.slice(blockStart);
|
|
@@ -161,21 +202,33 @@ function parseUatResultItems(cleanContent) {
|
|
|
161
202
|
// the heading scan above): test each already-split line individually
|
|
162
203
|
// against a single-line (no `/m` anchor) pattern instead of anchoring
|
|
163
204
|
// over unsplit `blockContent`, so a `result:`-shaped line reachable only
|
|
164
|
-
// via
|
|
165
|
-
//
|
|
166
|
-
//
|
|
205
|
+
// via an exotic separator inside an `expected: |` scalar body can never
|
|
206
|
+
// register as a fake column-0 match. FIRST MATCH WINS — no ambiguity
|
|
207
|
+
// counting, matching src/uat.cts's contract.
|
|
167
208
|
// Uses [ \t]* (not \s*) so the captured value must sit on the SAME line as result:.
|
|
168
209
|
// A result: key with the value on a subsequent line yields no match → 'missing' (blocker).
|
|
210
|
+
// #4546: the `reason:` line is captured with the same frame and FIRST-match
|
|
211
|
+
// rule — it is the deferral signal the evaluator needs (a `skipped` item
|
|
212
|
+
// whose reason is the verify-work writer's "Deferred follow-up:" template
|
|
213
|
+
// is non-blocking). Quoted values are captured with their quotes so the
|
|
214
|
+
// evaluator's matcher can tolerate them exactly as written.
|
|
169
215
|
const RESULT_LINE_RE = /^result:[ \t]*\[?([\w-]+)\]?/i;
|
|
170
216
|
const resultMatch = blockContent
|
|
171
217
|
.split('\n')
|
|
172
218
|
.map((line) => line.match(RESULT_LINE_RE))
|
|
173
219
|
.find((m) => m !== null) ?? null;
|
|
220
|
+
const REASON_LINE_RE = /^reason:[ \t]*(.*)$/i;
|
|
221
|
+
const reasonMatch = blockContent
|
|
222
|
+
.split('\n')
|
|
223
|
+
.map((line) => line.match(REASON_LINE_RE))
|
|
224
|
+
.find((m) => m !== null) ?? null;
|
|
225
|
+
const reason = reasonMatch ? reasonMatch[1].trim() : '';
|
|
174
226
|
if (resultMatch) {
|
|
175
227
|
items.push({
|
|
176
228
|
test: h.test,
|
|
177
229
|
name: h.name,
|
|
178
230
|
result: resultMatch[1].toLowerCase(),
|
|
231
|
+
reason,
|
|
179
232
|
});
|
|
180
233
|
}
|
|
181
234
|
else {
|
|
@@ -184,11 +237,240 @@ function parseUatResultItems(cleanContent) {
|
|
|
184
237
|
test: h.test,
|
|
185
238
|
name: h.name,
|
|
186
239
|
result: 'missing',
|
|
240
|
+
reason,
|
|
187
241
|
});
|
|
188
242
|
}
|
|
189
243
|
}
|
|
190
244
|
return items;
|
|
191
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* #4983: parse the UAT file's `## Gaps` bullet list (templates/UAT.md) for
|
|
248
|
+
* the four scalar fields the predicate needs to decide whether a resolved
|
|
249
|
+
* `result: issue` test still blocks — `test`, `status`, `resolved_by`,
|
|
250
|
+
* `gap_id`.
|
|
251
|
+
*
|
|
252
|
+
* Deliberately a separate, minimal parser from src/uat.cts's `parseGapsItems`
|
|
253
|
+
* (the read-side/audit parser): this module hardens independently, on the
|
|
254
|
+
* ALREADY-CLEANED body (stripFalsePositiveContexts has already removed
|
|
255
|
+
* frontmatter/HTML comments/fenced code/blockquotes), and only needs these
|
|
256
|
+
* four fields — never the nested `artifacts:`/`missing:` sub-lists the
|
|
257
|
+
* read-side parser also handles.
|
|
258
|
+
*
|
|
259
|
+
* Entries are split on column-0 `- ` bullet openers (the template's shape);
|
|
260
|
+
* a nested sub-list item (e.g. a ` artifacts:` key followed by an indented
|
|
261
|
+
* ` - path: "..."` line) is indented and never matches the column-0
|
|
262
|
+
* opener, so it can never be mistaken for a new top-level entry.
|
|
263
|
+
*
|
|
264
|
+
* Fields are matched per line, first-match-wins, bounded to the entry's OWN
|
|
265
|
+
* top-level indentation — either the bullet-opener line itself (0 leading
|
|
266
|
+
* spaces once `BULLET_OPENER_RE` has stripped the `"- "` marker) or a
|
|
267
|
+
* continuation line indented by exactly the template's 2 spaces (each field
|
|
268
|
+
* regex below is anchored `^[ ]{0,2}key:`). A candidate line indented deeper
|
|
269
|
+
* than that (4+ spaces) is a nested sub-list's body or a multi-line scalar
|
|
270
|
+
* VALUE, never a sibling
|
|
271
|
+
* field declaration, and is deliberately NOT trimmed-and-matched the way an
|
|
272
|
+
* earlier revision of this function did (#4983 review round 1, MEDIUM): a
|
|
273
|
+
* block-scalar value line that happens to read "status: resolved" after
|
|
274
|
+
* trimming (e.g. embedded free text) could otherwise be mistaken for a real
|
|
275
|
+
* field at the wrong indentation, the same false-positive class
|
|
276
|
+
* `parseUatResultItems` above already guards against via column-0 anchoring
|
|
277
|
+
* for `result:`/`reason:`.
|
|
278
|
+
*/
|
|
279
|
+
function parseGapsEntries(cleanContent) {
|
|
280
|
+
const GAPS_HEADING_RE = /^##\s*Gaps\s*$/i;
|
|
281
|
+
const OTHER_HEADING_RE = /^##\s+\S/;
|
|
282
|
+
const lines = cleanContent.split('\n');
|
|
283
|
+
let sectionStart = -1;
|
|
284
|
+
let sectionEnd = lines.length;
|
|
285
|
+
for (let i = 0; i < lines.length; i++) {
|
|
286
|
+
if (sectionStart === -1) {
|
|
287
|
+
if (GAPS_HEADING_RE.test(lines[i]))
|
|
288
|
+
sectionStart = i + 1;
|
|
289
|
+
}
|
|
290
|
+
else if (OTHER_HEADING_RE.test(lines[i])) {
|
|
291
|
+
sectionEnd = i;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (sectionStart === -1)
|
|
296
|
+
return [];
|
|
297
|
+
// Split the section body into entries at column-0 `- ` bullet openers.
|
|
298
|
+
// The opener line's own field (if any) is stored with a synthetic 0-space
|
|
299
|
+
// prefix removed — see FIELD_INDENT_RE below, which accepts both 0 and 2
|
|
300
|
+
// leading spaces as "this entry's own top level".
|
|
301
|
+
const BULLET_OPENER_RE = /^-\s?(.*)$/;
|
|
302
|
+
const entryBlocks = [];
|
|
303
|
+
for (const line of lines.slice(sectionStart, sectionEnd)) {
|
|
304
|
+
const m = line.match(BULLET_OPENER_RE);
|
|
305
|
+
if (m) {
|
|
306
|
+
entryBlocks.push([m[1]]);
|
|
307
|
+
}
|
|
308
|
+
else if (entryBlocks.length > 0) {
|
|
309
|
+
entryBlocks[entryBlocks.length - 1].push(line);
|
|
310
|
+
}
|
|
311
|
+
// Lines before the first bullet (blank lines, the template's own
|
|
312
|
+
// `<!-- YAML format ... -->` comment — already stripped by the caller)
|
|
313
|
+
// carry no entry and are simply ignored.
|
|
314
|
+
}
|
|
315
|
+
const stripQuotes = (v) => v.trim().replace(/^["']+|["']+$/g, '').trim();
|
|
316
|
+
// Bounded to 0-2 leading spaces: the bullet-opener line's stored text has
|
|
317
|
+
// 0 (its `- ` marker already stripped), and a real continuation line in
|
|
318
|
+
// the template's own two-space-indented shape has exactly 2. Anything
|
|
319
|
+
// indented deeper (a scalar body, a nested `artifacts:`/`missing:` list
|
|
320
|
+
// item) is excluded by construction, not merely by accident of key name.
|
|
321
|
+
const TEST_FIELD_RE = /^[ ]{0,2}test:[ \t]*(.*)$/i;
|
|
322
|
+
const STATUS_FIELD_RE = /^[ ]{0,2}status:[ \t]*(.*)$/i;
|
|
323
|
+
const RESOLVED_BY_FIELD_RE = /^[ ]{0,2}resolved_by:[ \t]*(.*)$/i;
|
|
324
|
+
const GAP_ID_FIELD_RE = /^[ ]{0,2}gap_id:[ \t]*(.*)$/i;
|
|
325
|
+
const entries = [];
|
|
326
|
+
for (const block of entryBlocks) {
|
|
327
|
+
let test = null;
|
|
328
|
+
let status = '';
|
|
329
|
+
let resolvedBy = '';
|
|
330
|
+
let gapId = '';
|
|
331
|
+
for (const rawLine of block) {
|
|
332
|
+
if (test === null) {
|
|
333
|
+
const tm = rawLine.match(TEST_FIELD_RE);
|
|
334
|
+
if (tm) {
|
|
335
|
+
const n = parseInt(stripQuotes(tm[1]).replace(/[[\]]/g, ''), 10);
|
|
336
|
+
if (!Number.isNaN(n))
|
|
337
|
+
test = n;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (!status) {
|
|
342
|
+
const sm = rawLine.match(STATUS_FIELD_RE);
|
|
343
|
+
if (sm) {
|
|
344
|
+
status = stripQuotes(sm[1]).toLowerCase();
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!resolvedBy) {
|
|
349
|
+
const rm = rawLine.match(RESOLVED_BY_FIELD_RE);
|
|
350
|
+
if (rm) {
|
|
351
|
+
resolvedBy = stripQuotes(rm[1]);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (!gapId) {
|
|
356
|
+
const gm = rawLine.match(GAP_ID_FIELD_RE);
|
|
357
|
+
if (gm) {
|
|
358
|
+
gapId = stripQuotes(gm[1]);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
entries.push({ test, status, resolvedBy, gapId });
|
|
364
|
+
}
|
|
365
|
+
return entries;
|
|
366
|
+
}
|
|
367
|
+
// ─── isTestGapResolved ────────────────────────────────────────────────────────
|
|
368
|
+
/**
|
|
369
|
+
* #4983: true only when EVERY `## Gaps` entry recorded against `testNum` is a
|
|
370
|
+
* verified resolution — never on the presence of just one resolved entry,
|
|
371
|
+
* since a later regression re-opens the SAME test number with a fresh
|
|
372
|
+
* `gap_id` (per verify-work.md's reconcile_gaps contract) while a prior entry
|
|
373
|
+
* for it stays `resolved`; an unresolved regression must still block.
|
|
374
|
+
*
|
|
375
|
+
* A single entry only counts as a verified resolution when ALL of:
|
|
376
|
+
* - `status` is `resolved` (an entry with no gap recorded, or `status:
|
|
377
|
+
* failed`/blank, is not a resolution — fails closed);
|
|
378
|
+
* - `resolved_by` is non-empty (a partial resolution missing the writer's
|
|
379
|
+
* attribution fails closed, per #4983's acceptance criteria);
|
|
380
|
+
* - `resolved_by`, taken as a bare basename (rejecting any path separator —
|
|
381
|
+
* no directory traversal), names a `*-PLAN.md` file that actually exists
|
|
382
|
+
* in this phase directory;
|
|
383
|
+
* - that plan has a matching `*-SUMMARY.md` sibling in the same directory —
|
|
384
|
+
* the same "plan executed" evidence verify-work.md's own reconcile_gaps
|
|
385
|
+
* step requires (elaboration.md §1) before it ever writes `resolved_by`;
|
|
386
|
+
* - the entry's own `gap_id` is present, AND the named plan's frontmatter
|
|
387
|
+
* `gap_ids` array includes it — proof the plan resolved THIS gap, not
|
|
388
|
+
* some other one.
|
|
389
|
+
*
|
|
390
|
+
* The `gap_id` cross-check (#4983 review round 1, CRITICAL) closes a false-
|
|
391
|
+
* green a plan-existence-only check leaves wide open: `resolved_by` naming a
|
|
392
|
+
* real, executed plan is not proof that plan addressed THIS gap. Concretely,
|
|
393
|
+
* a phase with a legitimately-resolved gap on test 3 (`resolved_by:
|
|
394
|
+
* 05-02-PLAN.md`, `05-02-PLAN.md`'s own `gap_ids: [G-05-3]`) could otherwise
|
|
395
|
+
* have an UNRELATED, still-broken test 7 marked `status: resolved,
|
|
396
|
+
* resolved_by: 05-02-PLAN.md` (copy-pasted or mis-attributed) and the prior
|
|
397
|
+
* checks alone would accept it — `05-02-PLAN.md` and its SUMMARY genuinely
|
|
398
|
+
* exist, they just never touched gap 7. Reading the plan's own `gap_ids` and
|
|
399
|
+
* requiring it to name this entry's `gap_id` closes that: a resolution claim
|
|
400
|
+
* this gate cannot independently verify against the phase directory's own
|
|
401
|
+
* files, cross-referenced back to the SPECIFIC gap, is not trusted. An entry
|
|
402
|
+
* with no `gap_id` at all (an older, hand-written entry predating the
|
|
403
|
+
* `gap_id` convention) fails closed — there is nothing to cross-reference,
|
|
404
|
+
* so it cannot be verified.
|
|
405
|
+
*
|
|
406
|
+
* This is deliberately MORE than a text-only trust check (contrast the
|
|
407
|
+
* `DEFERRED_REASON_RE` deferred-skip case above, an accepted authoring
|
|
408
|
+
* contract, not a security boundary): #4983's acceptance criteria explicitly
|
|
409
|
+
* requires that "a `resolved_by` that names no executed plan still blocks" —
|
|
410
|
+
* reading this as "no plan that executed AND resolved THIS gap" is the
|
|
411
|
+
* reading that actually closes the false-green risk, not merely "some plan
|
|
412
|
+
* ran somewhere in this phase."
|
|
413
|
+
*/
|
|
414
|
+
function isTestGapResolved(entries, testNum, dirEntries, phaseFullDir) {
|
|
415
|
+
const forTest = entries.filter((e) => e.test === testNum);
|
|
416
|
+
if (forTest.length === 0)
|
|
417
|
+
return false;
|
|
418
|
+
return forTest.every((e) => {
|
|
419
|
+
if (e.status !== 'resolved')
|
|
420
|
+
return false;
|
|
421
|
+
if (!e.resolvedBy)
|
|
422
|
+
return false;
|
|
423
|
+
if (node_path_1.default.basename(e.resolvedBy) !== e.resolvedBy)
|
|
424
|
+
return false;
|
|
425
|
+
// Canonical plan-filename predicate (src/plan-scan.cts) rather than a
|
|
426
|
+
// hand-rolled `-PLAN.md$` regex — one owner for "what is a plan file"
|
|
427
|
+
// across the codebase (lint-plan-count-drift.cjs enforces this).
|
|
428
|
+
if (!isRootPlanFile(e.resolvedBy))
|
|
429
|
+
return false;
|
|
430
|
+
if (!dirEntries.includes(e.resolvedBy))
|
|
431
|
+
return false;
|
|
432
|
+
// Canonical plan→summary pairing (src/core-utils.cts), same reason: a
|
|
433
|
+
// single-plan/single-candidate-set query reuses the exact matching rules
|
|
434
|
+
// scanPhasePlans's own summaryCount is built from, rather than a
|
|
435
|
+
// hand-rolled `-PLAN.md` → `-SUMMARY.md` suffix swap.
|
|
436
|
+
if (countMatchedSummaries([e.resolvedBy], dirEntries) !== 1)
|
|
437
|
+
return false;
|
|
438
|
+
// Fail closed on an entry with no gap_id to cross-reference — there is
|
|
439
|
+
// nothing to verify the named plan actually resolved THIS gap against.
|
|
440
|
+
if (!e.gapId)
|
|
441
|
+
return false;
|
|
442
|
+
return planClaimsGapId(phaseFullDir, e.resolvedBy, e.gapId);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* #4983 (review round 1, CRITICAL follow-up): read the named plan's own
|
|
447
|
+
* frontmatter `gap_ids` array and confirm it includes `gapId` — the
|
|
448
|
+
* cross-reference `isTestGapResolved` needs to confirm a plan that exists
|
|
449
|
+
* and has a SUMMARY actually claims to resolve THIS gap, not merely that it
|
|
450
|
+
* ran. Any failure to read or parse the plan (missing file, unreadable,
|
|
451
|
+
* malformed frontmatter, `gap_ids` absent or not an array) returns `false` —
|
|
452
|
+
* fail-closed, matching this module's convention throughout.
|
|
453
|
+
*/
|
|
454
|
+
function planClaimsGapId(phaseFullDir, planBasename, gapId) {
|
|
455
|
+
let raw;
|
|
456
|
+
try {
|
|
457
|
+
raw = node_fs_1.default.readFileSync(node_path_1.default.join(phaseFullDir, planBasename), 'utf-8');
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
let fm;
|
|
463
|
+
try {
|
|
464
|
+
fm = extractFrontmatter(raw, planBasename);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
const gapIds = fm['gap_ids'];
|
|
470
|
+
if (!Array.isArray(gapIds))
|
|
471
|
+
return false;
|
|
472
|
+
return gapIds.some((id) => typeof id === 'string' && id.trim() === gapId.trim());
|
|
473
|
+
}
|
|
192
474
|
// ─── evaluateUatPassed ────────────────────────────────────────────────────────
|
|
193
475
|
/**
|
|
194
476
|
* Evaluate all UAT/VERIFICATION files in a phase directory.
|
|
@@ -198,7 +480,13 @@ function parseUatResultItems(cleanContent) {
|
|
|
198
480
|
* Pass requires at least one real passing check AND no blockers.
|
|
199
481
|
*/
|
|
200
482
|
function evaluateUatPassed(phaseFullDir, opts) {
|
|
201
|
-
|
|
483
|
+
// uatOnly (#4663) takes precedence: it evaluates the UAT rows ONLY, skipping
|
|
484
|
+
// the VERIFICATION-file blockers entirely. The verify-work canonicalize
|
|
485
|
+
// pre-check needs exactly that — it runs while the report still reads
|
|
486
|
+
// `human_needed`, which is itself a blocking verification status, so the
|
|
487
|
+
// full predicate could never pass there and the flip would deadlock.
|
|
488
|
+
const uatOnly = opts?.policy?.uatOnly === true;
|
|
489
|
+
const requireVerification = !uatOnly && opts?.policy?.requireVerification === true;
|
|
202
490
|
const blockers = [];
|
|
203
491
|
const checks = [];
|
|
204
492
|
const uatFiles = [];
|
|
@@ -221,7 +509,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
221
509
|
checks: [],
|
|
222
510
|
blockers,
|
|
223
511
|
no_uat_artifacts,
|
|
224
|
-
policy: { require_verification: requireVerification },
|
|
512
|
+
policy: { require_verification: requireVerification, uat_only: uatOnly },
|
|
225
513
|
// readVerificationStatus was never reached on this early-return path.
|
|
226
514
|
verification_stale_check_indeterminate: false,
|
|
227
515
|
};
|
|
@@ -234,8 +522,15 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
234
522
|
const verFileNames = scopeToPhase(dirEntries.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md')), phaseDirBaseName);
|
|
235
523
|
// ── Process UAT files ──────────────────────────────────────────────────────
|
|
236
524
|
for (const file of uatFileNames) {
|
|
237
|
-
uatFiles.push(file);
|
|
238
525
|
const uatFilePath = node_path_1.default.join(phaseFullDir, file);
|
|
526
|
+
// #5118 security review (SEC-1): containment BEFORE the read, exactly as
|
|
527
|
+
// for the VERIFICATION loop below — a `*-UAT.md` / `*-HUMAN-UAT.md` whose
|
|
528
|
+
// real path escapes the phase directory is treated as absent (not listed,
|
|
529
|
+
// no blocker naming it), so the `### N. <name>` text it points at never
|
|
530
|
+
// reaches a blocker or a check row.
|
|
531
|
+
if (!isReportContained(phaseFullDir, uatFilePath))
|
|
532
|
+
continue;
|
|
533
|
+
uatFiles.push(file);
|
|
239
534
|
let raw = '';
|
|
240
535
|
try {
|
|
241
536
|
// #3078-CR MEDIUM: normalize line endings at the read boundary — the
|
|
@@ -268,14 +563,29 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
268
563
|
// Parse test items from the cleaned body (hardened against false positives)
|
|
269
564
|
const cleanContent = stripFalsePositiveContexts(raw);
|
|
270
565
|
const items = parseUatResultItems(cleanContent);
|
|
566
|
+
// #4983: parsed once per file — `evaluateUatPassed` already has `dirEntries`
|
|
567
|
+
// from the readdir above, so `isTestGapResolved` can verify `resolved_by`
|
|
568
|
+
// against this phase directory's own files.
|
|
569
|
+
const gapEntries = parseGapsEntries(cleanContent);
|
|
271
570
|
for (const item of items) {
|
|
272
|
-
|
|
571
|
+
// #4546: a `skipped` item whose reason matches the verify-work writer's
|
|
572
|
+
// "Deferred follow-up:" template is a deliberately deferred follow-up
|
|
573
|
+
// (#1921) — non-blocking. Quote-tolerant because the writer emits the
|
|
574
|
+
// reason WITH its wrapping quotes. Everything else — pending, blocked,
|
|
575
|
+
// issue, missing, and a plain or non-deferral skipped — still blocks.
|
|
576
|
+
const deferred = item.result === 'skipped' && DEFERRED_REASON_RE.test(item.reason);
|
|
577
|
+
// #4983: an `issue` item whose `## Gaps` entry has been verified-resolved
|
|
578
|
+
// (see isTestGapResolved) is a genuinely fixed issue — non-blocking.
|
|
579
|
+
const resolved = item.result === 'issue' && isTestGapResolved(gapEntries, item.test, dirEntries, phaseFullDir);
|
|
580
|
+
const passing = PASSING_RESULTS.has(item.result) || deferred || resolved;
|
|
273
581
|
checks.push({
|
|
274
582
|
file,
|
|
275
583
|
test: item.test,
|
|
276
584
|
name: item.name,
|
|
277
585
|
result: item.result,
|
|
278
586
|
passing,
|
|
587
|
+
deferred,
|
|
588
|
+
resolved,
|
|
279
589
|
});
|
|
280
590
|
if (!passing) {
|
|
281
591
|
blockers.push(`${file}: test ${item.test} (${item.result})`);
|
|
@@ -284,9 +594,14 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
284
594
|
}
|
|
285
595
|
// ── Process VERIFICATION files ─────────────────────────────────────────────
|
|
286
596
|
let hasPassingVerification = false;
|
|
287
|
-
for (const file of verFileNames) {
|
|
597
|
+
for (const file of uatOnly ? [] : verFileNames) {
|
|
288
598
|
verificationFiles.push(file);
|
|
289
599
|
const verificationFilePath = node_path_1.default.join(phaseFullDir, file);
|
|
600
|
+
// #5118 security review (S1): containment BEFORE the read — a report whose
|
|
601
|
+
// real path escapes the planning root reads `missing` (no status counts,
|
|
602
|
+
// no blocker) and not a byte of it reaches any message.
|
|
603
|
+
if (!isReportContained(phaseFullDir, verificationFilePath))
|
|
604
|
+
continue;
|
|
290
605
|
let raw = '';
|
|
291
606
|
try {
|
|
292
607
|
// #3078-CR MEDIUM: same read-boundary normalization as the UAT loop above.
|
|
@@ -296,16 +611,17 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
296
611
|
blockers.push(`${file}: could not read verification file`);
|
|
297
612
|
continue;
|
|
298
613
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
614
|
+
// #5118: judged by the owner's report reader — a status outside the
|
|
615
|
+
// closed writer set throws VerificationStatusError (no silent pass-through).
|
|
616
|
+
const vStatus = reportStatusOf(extractFrontmatter(raw, verificationFilePath), verificationFilePath);
|
|
617
|
+
if (vStatus === VERIFICATION_STATUS.HUMAN_NEEDED || vStatus === VERIFICATION_STATUS.GAPS_FOUND) {
|
|
302
618
|
blockers.push(`${file}: verification status=${vStatus}`);
|
|
303
619
|
}
|
|
304
|
-
else if (vStatus
|
|
305
|
-
// Allowlist: only explicitly-passing
|
|
620
|
+
else if (vStatus === VERIFICATION_STATUS.PASSED) {
|
|
621
|
+
// Allowlist: only an explicitly-passing status counts
|
|
306
622
|
hasPassingVerification = true;
|
|
307
623
|
}
|
|
308
|
-
//
|
|
624
|
+
// No status: does NOT count as passing, does NOT push a blocker
|
|
309
625
|
// (handled by the requireVerification policy check below if needed)
|
|
310
626
|
}
|
|
311
627
|
// ── Policy: requireVerification ───────────────────────────────────────────
|
|
@@ -318,10 +634,10 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
318
634
|
const verificationResult = readVerificationStatus(phaseFullDir);
|
|
319
635
|
const verificationStatus = verificationResult.status;
|
|
320
636
|
verificationStaleCheckIndeterminate = verificationResult.staleCheckIndeterminate === true;
|
|
321
|
-
if (verificationStatus ===
|
|
322
|
-
blockers.push(
|
|
637
|
+
if (verificationStatus === VERIFICATION_STATUS.STALE) {
|
|
638
|
+
blockers.push(`policy: verification status=${VERIFICATION_STATUS.STALE}`);
|
|
323
639
|
}
|
|
324
|
-
else if (verificationStatus !==
|
|
640
|
+
else if (verificationStatus !== VERIFICATION_STATUS.PASSED || !hasPassingVerification) {
|
|
325
641
|
blockers.push('policy: verification required but no passing *-VERIFICATION.md found');
|
|
326
642
|
}
|
|
327
643
|
}
|
|
@@ -340,6 +656,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
340
656
|
no_uat_artifacts,
|
|
341
657
|
policy: {
|
|
342
658
|
require_verification: requireVerification,
|
|
659
|
+
uat_only: uatOnly,
|
|
343
660
|
},
|
|
344
661
|
verification_stale_check_indeterminate: verificationStaleCheckIndeterminate,
|
|
345
662
|
};
|
|
@@ -349,4 +666,8 @@ module.exports = {
|
|
|
349
666
|
parseUatResultItems,
|
|
350
667
|
analyzeMarkdown,
|
|
351
668
|
evaluateUatPassed,
|
|
669
|
+
// #4983: exported for direct unit-testability of the Gaps-resolution
|
|
670
|
+
// parser/validator, independent of a full evaluateUatPassed round-trip.
|
|
671
|
+
parseGapsEntries,
|
|
672
|
+
isTestGapResolved,
|
|
352
673
|
};
|