@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
package/gsd-core/bin/lib/uat.cjs
CHANGED
|
@@ -16,10 +16,10 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
16
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
18
|
const io = require("./io.cjs");
|
|
19
|
-
const { output, error } = io;
|
|
19
|
+
const { output, error, captureStdoutSyncWrites, resolveAtFileOutput } = io;
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
21
|
const markdownSectionizer = require("./markdown-sectionizer.cjs");
|
|
22
|
-
const { collectSection, tokenizeHeadings, stripFencedCode, scanFencedBlocks } = markdownSectionizer;
|
|
22
|
+
const { collectSection, withSection, tokenizeHeadings, stripFencedCode, scanFencedBlocks } = markdownSectionizer;
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
24
|
const markdownTable = require("./markdown-table.cjs");
|
|
25
25
|
const { splitTableRow, isDelimiterRow } = markdownTable;
|
|
@@ -31,7 +31,8 @@ const planningWorkspace = require("./planning-workspace.cjs");
|
|
|
31
31
|
const { planningDir } = planningWorkspace;
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
33
|
const frontmatter = require("./frontmatter.cjs");
|
|
34
|
-
const { extractFrontmatter, frontmatterListEntries, flattenObjectListItem } = frontmatter;
|
|
34
|
+
const { extractFrontmatter, spliceFrontmatter, frontmatterListEntries, flattenObjectListItem, isFrontmatterWriteRefusal } = frontmatter;
|
|
35
|
+
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
35
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
37
|
const phaseIdMod = require("./phase-id.cjs");
|
|
37
38
|
const { PHASE_NUMBER_TOKEN_SOURCE, scopeToPhase } = phaseIdMod;
|
|
@@ -41,6 +42,13 @@ const { listMilestonePhaseDirs, getAllArchivedPhaseDirs } = phaseLocator;
|
|
|
41
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
43
|
const auditMod = require("./audit.cjs");
|
|
43
44
|
const { isAuditItemAcknowledged, deriveUatGapSnapshotValue } = auditMod;
|
|
45
|
+
// #5118: the verification-status owner's report reader and closed enum.
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
const verificationMod = require("./verification.cjs");
|
|
48
|
+
const { reportStatusOf, isReportContained, VERIFICATION_STATUS } = verificationMod;
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
50
|
+
const pristineBaseline = require("./pristine-baseline.cjs");
|
|
51
|
+
const { gitExec } = pristineBaseline;
|
|
44
52
|
const security_cjs_1 = require("./security.cjs");
|
|
45
53
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
46
54
|
const configLoader = require("./config-loader.cjs");
|
|
@@ -133,6 +141,13 @@ function cmdAuditUat(cwd, raw) {
|
|
|
133
141
|
// the reason scopeToPhase has no unfiltered fallback.
|
|
134
142
|
for (const file of selectPhaseUatFiles(files, dir)) {
|
|
135
143
|
const uatFilePath = node_path_1.default.join(phaseDir, file);
|
|
144
|
+
// #5118 security review (SEC-1): containment BEFORE the read, exactly as
|
|
145
|
+
// for the VERIFICATION loop below — a `*-UAT.md` / `*-HUMAN-UAT.md`
|
|
146
|
+
// symlinked outside the phase directory is treated as absent, so the
|
|
147
|
+
// `### N. <name>` and `expected:` text it points at never reaches the
|
|
148
|
+
// audit output.
|
|
149
|
+
if (!isReportContained(phaseDir, uatFilePath))
|
|
150
|
+
continue;
|
|
136
151
|
const content = readNormalizedDocument(uatFilePath);
|
|
137
152
|
const { items, headingsSeen } = parseUatItemsWithStats(content);
|
|
138
153
|
const uatFm = extractFrontmatter(content, uatFilePath);
|
|
@@ -202,14 +217,20 @@ function cmdAuditUat(cwd, raw) {
|
|
|
202
217
|
// for the same reason as the UAT loop above.
|
|
203
218
|
for (const file of scopeToPhase(files.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md')), dir)) {
|
|
204
219
|
const verificationFilePath = node_path_1.default.join(phaseDir, file);
|
|
220
|
+
// #5118 security review (S1): containment BEFORE the read — an escaping
|
|
221
|
+
// report reads `missing` and never reaches `reportStatusOf`'s message.
|
|
222
|
+
if (!isReportContained(phaseDir, verificationFilePath))
|
|
223
|
+
continue;
|
|
205
224
|
const content = readNormalizedDocument(verificationFilePath);
|
|
206
225
|
const verFm = extractFrontmatter(content, verificationFilePath);
|
|
207
|
-
|
|
226
|
+
// #5118: the owner's report reader judges `status` (exact match, no case
|
|
227
|
+
// folding); an out-of-set value throws VerificationStatusError.
|
|
228
|
+
const status = reportStatusOf(verFm, verificationFilePath);
|
|
208
229
|
// #3805: same marker, same 'status' snapshot key as scanVerificationGaps,
|
|
209
230
|
// and the same ORDERING — the open-status gate runs FIRST (a marker on
|
|
210
231
|
// a file that would never surface is not a suppressed item), then the
|
|
211
232
|
// acknowledgement suppresses what the gate surfaced.
|
|
212
|
-
if (status ===
|
|
233
|
+
if (status === VERIFICATION_STATUS.HUMAN_NEEDED || status === VERIFICATION_STATUS.GAPS_FOUND) {
|
|
213
234
|
if (isAuditItemAcknowledged(verFm, { snapshotKey: 'status', currentValue: status })) {
|
|
214
235
|
acknowledgedFiles++;
|
|
215
236
|
continue;
|
|
@@ -341,6 +362,360 @@ function cmdRenderCheckpoint(cwd, options = {}, raw) {
|
|
|
341
362
|
checkpoint,
|
|
342
363
|
}, raw, checkpoint);
|
|
343
364
|
}
|
|
365
|
+
// ─── completeUatSession ─────────────────────────────────────────────────────
|
|
366
|
+
// #5105 R1: the ONE owner for "does completing this UAT session change
|
|
367
|
+
// anything material". Pure core; the CLI handler below writes/commits only
|
|
368
|
+
// when `changed` is true (ADR-5057 §3, #4981 UAT self-stale offender C1/C2).
|
|
369
|
+
//
|
|
370
|
+
// #5105 review S7/S8: section edits go through the shared markdown-sectionizer
|
|
371
|
+
// primitives (`withSection`/`collectSection`) rather than a hand-rolled `^## `
|
|
372
|
+
// scanner — a `## ` occurrence inside a fenced code block must not end the
|
|
373
|
+
// section, and `tokenizeHeadings` (which both are built on) is fence-aware.
|
|
374
|
+
// Frontmatter reads/writes are scoped to the `---`…`---` block only, never a
|
|
375
|
+
// whole-document regex — a body line that happens to start with `status:` or
|
|
376
|
+
// `updated:` must never be touched. `status` goes through
|
|
377
|
+
// `extractFrontmatter`/`spliceFrontmatter` (safe: `complete`/`partial` never
|
|
378
|
+
// contain a `:`); `updated` does NOT (see `setFrontmatterUpdated`'s doc
|
|
379
|
+
// comment for why an ISO-8601 timestamp can't round-trip through the same
|
|
380
|
+
// re-serializer without picking up a spurious quoting change).
|
|
381
|
+
/**
|
|
382
|
+
* Replace the `## Current Test` section body with `[testing complete]`,
|
|
383
|
+
* leaving every other line of the document untouched. Mirrors the section
|
|
384
|
+
* boundary convention `parseCurrentTest` reads (a level-2 `## Current Test`
|
|
385
|
+
* heading; the section body runs to the next `## ` heading or EOF) via the
|
|
386
|
+
* shared `withSection` primitive, which is fence-aware (a `## `-looking line
|
|
387
|
+
* inside a fenced code block is not a heading and cannot end the section).
|
|
388
|
+
*
|
|
389
|
+
* A document missing the heading altogether is returned unchanged (bounded
|
|
390
|
+
* no-op, `withSection`'s own contract on a heading miss) — that is a
|
|
391
|
+
* malformed-input case `completeUatSession`'s caller already never produces,
|
|
392
|
+
* and this function fails soft rather than throwing so the pure core stays
|
|
393
|
+
* total.
|
|
394
|
+
*/
|
|
395
|
+
function setCurrentTestComplete(content) {
|
|
396
|
+
// `'\n[testing complete]'` (no trailing newline) mirrors the trimmed shape
|
|
397
|
+
// `collectSection` itself produces for an ALREADY-canonical section body —
|
|
398
|
+
// required for idempotence: when the section already reads exactly this,
|
|
399
|
+
// `withSection`'s own `newBody === section.body` no-op guard must fire, or
|
|
400
|
+
// every already-complete document would gain a spurious extra blank line on
|
|
401
|
+
// each pass (a real, if invisible-looking, material byte change). The line
|
|
402
|
+
// break is the document's own (its first line's terminator), so a CRLF
|
|
403
|
+
// document's already-canonical section is recognized as unchanged and a
|
|
404
|
+
// rewritten one never gains a bare-LF line.
|
|
405
|
+
const eol = /^[^\n]*\r\n/.test(content) ? '\r\n' : '\n';
|
|
406
|
+
return withSection(content, (h) => /^current\s+test$/i.test(h.text) && h.level === 2, () => `${eol}[testing complete]`, { levelBounded: true });
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Set the frontmatter `status:` value, preserving every other key's raw text
|
|
410
|
+
* verbatim (`spliceFrontmatter`'s per-key fidelity) — scoped strictly to the
|
|
411
|
+
* `---`…`---` block, so a body line that happens to start with `status:` is
|
|
412
|
+
* never touched (#5105 review S8). `status` values here (`complete`/`partial`)
|
|
413
|
+
* never contain a `:`, so `reconstructFrontmatter`'s double-quoting rule for
|
|
414
|
+
* colon-bearing scalars never fires — see `withFrontmatterUpdated`'s doc
|
|
415
|
+
* comment for why `updated:` (an ISO-8601 timestamp, which ALWAYS contains a
|
|
416
|
+
* `:`) is deliberately NOT routed through this same round-trip.
|
|
417
|
+
*
|
|
418
|
+
* Throws `spliceFrontmatter`'s write refusal (`isFrontmatterWriteRefusal`)
|
|
419
|
+
* when the frontmatter block is not parseable YAML — a block like
|
|
420
|
+
* `status:complete` (no space) is never "repaired" by regeneration;
|
|
421
|
+
* `cmdUatCompleteSession` surfaces the refusal and writes/commits nothing.
|
|
422
|
+
*/
|
|
423
|
+
function setFrontmatterStatus(content, status) {
|
|
424
|
+
const fm = extractFrontmatter(content);
|
|
425
|
+
return spliceFrontmatter(content, { ...fm, status });
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The `---`…`---` frontmatter block's `[start, end)` character bounds in
|
|
429
|
+
* `content`, and where its closing fence line starts — located by
|
|
430
|
+
* `locateFrontmatterFence`, the one fence owner `spliceFrontmatter` and every
|
|
431
|
+
* reader use — or `null` if there is none.
|
|
432
|
+
*/
|
|
433
|
+
function frontmatterBlockBounds(content) {
|
|
434
|
+
// A leading BOM (#2977) is not content before the fence — it sits before
|
|
435
|
+
// `start`, carried through unchanged.
|
|
436
|
+
const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
|
|
437
|
+
return fence?.closed ? { start: fence.bom.length, end: fence.closingFenceEnd, closingStart: fence.closingStart } : null;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* `line` inserted into `block` (the bounds' slice) as a new last line, just
|
|
441
|
+
* before its closing fence line — whatever that line is (`---`, `--- `, the
|
|
442
|
+
* lenient `----`) — with the line ending of the line before it.
|
|
443
|
+
*/
|
|
444
|
+
function insertBeforeClosingFence(block, bounds, line) {
|
|
445
|
+
const closingOffset = bounds.closingStart - bounds.start;
|
|
446
|
+
const eol = block[closingOffset - 2] === '\r' ? '\r\n' : '\n';
|
|
447
|
+
return `${block.slice(0, closingOffset)}${line}${eol}${block.slice(closingOffset)}`;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Write the frontmatter `updated:` line, SCOPED to the matched
|
|
451
|
+
* `---`…`---` block only (#5105 review S8) — deliberately NOT routed through
|
|
452
|
+
* `extractFrontmatter`/`spliceFrontmatter`'s generic re-serializer the way
|
|
453
|
+
* `setFrontmatterStatus` is: `reconstructFrontmatter` double-quotes any
|
|
454
|
+
* scalar containing a `:`, and an ISO-8601 timestamp always contains one —
|
|
455
|
+
* round-tripping `updated` through it would silently reformat every
|
|
456
|
+
* `updated:`/`started:`-shaped timestamp the first material change touches,
|
|
457
|
+
* from `updated: 2026-01-01T00:00:00Z` to `updated: "2026-01-01T00:00:00Z"`,
|
|
458
|
+
* breaking every OTHER reader in the ecosystem that pattern-matches the
|
|
459
|
+
* historically-unquoted form. A key-line replace confined to the matched
|
|
460
|
+
* frontmatter block gets the same "body text is never touched" guarantee
|
|
461
|
+
* without that regression.
|
|
462
|
+
*
|
|
463
|
+
* Returns the whole document with `updated:` set to `value` — appending the
|
|
464
|
+
* key just before the closing `---` (with the block's own line ending) when
|
|
465
|
+
* the block exists but the key does not (S8: "a frontmatter lacking
|
|
466
|
+
* `updated:` gains one when changed"), or returning `content` unchanged when
|
|
467
|
+
* there is no frontmatter block at all (never manufacturing one here).
|
|
468
|
+
*/
|
|
469
|
+
function setFrontmatterUpdated(content, value) {
|
|
470
|
+
const bounds = frontmatterBlockBounds(content);
|
|
471
|
+
if (!bounds)
|
|
472
|
+
return content;
|
|
473
|
+
const block = content.slice(bounds.start, bounds.end);
|
|
474
|
+
const keyLineRe = /^updated:.*$/m;
|
|
475
|
+
const line = `updated: ${value}`;
|
|
476
|
+
// A function replacer: the line is inserted literally, never read as a `$&`/`$'`/`` $` ``
|
|
477
|
+
// replacement pattern.
|
|
478
|
+
const newBlock = keyLineRe.test(block)
|
|
479
|
+
? block.replace(keyLineRe, () => line)
|
|
480
|
+
: insertBeforeClosingFence(block, bounds, line);
|
|
481
|
+
return content.slice(0, bounds.start) + newBlock + content.slice(bounds.end);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* The MATERIAL projection of a document for #5105 R1's comparison: every byte
|
|
485
|
+
* except the frontmatter `updated:` line (deny-by-default — everything else
|
|
486
|
+
* counts). The whole line, terminator included, is dropped, so a document
|
|
487
|
+
* LACKING an `updated:` line projects the same as one carrying any `updated:`
|
|
488
|
+
* value — presence is not material either, and "differs only in `updated`"
|
|
489
|
+
* holds when one side has no such line. A document with no frontmatter block,
|
|
490
|
+
* or frontmatter with no `updated` key, is returned unchanged.
|
|
491
|
+
*/
|
|
492
|
+
function stripUpdatedForCompare(content) {
|
|
493
|
+
const bounds = frontmatterBlockBounds(content);
|
|
494
|
+
if (!bounds)
|
|
495
|
+
return content;
|
|
496
|
+
const block = content.slice(bounds.start, bounds.end);
|
|
497
|
+
// The block always ends with `<eol>---`, so an `updated:` line always has
|
|
498
|
+
// its own terminator to drop with it.
|
|
499
|
+
const newBlock = block.replace(/^updated:.*\r?\n/m, '');
|
|
500
|
+
return content.slice(0, bounds.start) + newBlock + content.slice(bounds.end);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* #5105 R1 — pure core of `uat.complete-session`.
|
|
504
|
+
*
|
|
505
|
+
* Computes the resulting `status` using verify-work.md's `complete_session`
|
|
506
|
+
* criterion (the workflow step this command replaces, pinned at
|
|
507
|
+
* 518ccb0061:gsd-core/workflows/verify-work.md:522-545): `partial` when ANY
|
|
508
|
+
* row is `result: [pending]` (including a genuine parse gap — a row missing
|
|
509
|
+
* its `result:` line entirely counts the same as an explicit pending token),
|
|
510
|
+
* `result: blocked`, or a `result: skipped` row with no `reason:` field;
|
|
511
|
+
* `complete` otherwise. An `issue` row — resolved or not — is a DEFINITIVE
|
|
512
|
+
* result and never blocks completion on its own ("All tests have a
|
|
513
|
+
* definitive result (pass, issue, or skipped-with-reason)"); this is a
|
|
514
|
+
* deliberately looser criterion than `uat-predicate.cjs`'s `evaluateUatPassed`
|
|
515
|
+
* (the `phase uat-passed` gate), which does still block on an unresolved
|
|
516
|
+
* issue. Sets `## Current Test` to `[testing complete]`.
|
|
517
|
+
*
|
|
518
|
+
* #5105 R1 redesign (grilling-pass DESIGN DEFECT): the prior contract compared
|
|
519
|
+
* the candidate against the LIVE file, which never sees the UAT rows a
|
|
520
|
+
* verify-work session writes to the file (with the Write tool, uncommitted)
|
|
521
|
+
* DURING the session — `complete_session` is where those rows were meant to
|
|
522
|
+
* be committed, so a live-vs-result compare could report `changed: false` and
|
|
523
|
+
* never commit them. `baseline` is now an explicit second input — the file's
|
|
524
|
+
* content at git HEAD (`null` when untracked/absent; the caller resolves it,
|
|
525
|
+
* this core stays pure) — and the comparison is candidate-vs-baseline, not
|
|
526
|
+
* candidate-vs-live: `changed` means "does the candidate MATERIALLY differ
|
|
527
|
+
* from what is already committed", where MATERIAL excludes only the
|
|
528
|
+
* frontmatter `updated:` line — its value and its presence
|
|
529
|
+
* (`stripUpdatedForCompare`, deny-by-default,
|
|
530
|
+
* #5105 R1 "F10"). No material change → `{ changed: false, content }` returns
|
|
531
|
+
* the ORIGINAL `content` UNTOUCHED (not the candidate) — even a live document
|
|
532
|
+
* that differs from baseline ONLY in `updated:` is never written, and the
|
|
533
|
+
* caller performs zero writes and zero commits (the stated #5105 R1 decision:
|
|
534
|
+
* a live UAT that differs from the committed baseline ONLY in the frontmatter
|
|
535
|
+
* `updated:` value is never written). A material change → this function ALSO
|
|
536
|
+
* stamps `updated` from `clock()` on the candidate; the caller writes + commits.
|
|
537
|
+
*
|
|
538
|
+
* #5105 review finding 2 / M1: when the candidate is NOT materially changed
|
|
539
|
+
* from `baseline`, but the LIVE `content` differs from that candidate in
|
|
540
|
+
* something OTHER than `updated:` (e.g. a re-opened session left
|
|
541
|
+
* `status: testing` plus a pending `## Current Test` with no row changes),
|
|
542
|
+
* the byte-exact `baseline` form is restored (`restored: true`) — `content`
|
|
543
|
+
* IS `baseline` itself: `changed === false` means candidate and baseline differ
|
|
544
|
+
* at most in the `updated:` line, so the exact committed bytes (whatever the
|
|
545
|
+
* `updated:` line's spacing, position, or absence) are the restore target and
|
|
546
|
+
* no splice is performed. This restore path is DENIED (no write, `content`
|
|
547
|
+
* is the untouched live bytes, no `restored` flag) when live differs from the
|
|
548
|
+
* candidate ONLY in `updated:` — that case is covered by the decision above.
|
|
549
|
+
*/
|
|
550
|
+
function completeUatSession(content, options = {}) {
|
|
551
|
+
const clock = options.clock || (() => new Date());
|
|
552
|
+
const baseline = options.baseline ?? null;
|
|
553
|
+
// Scoped to the `## Tests` section body ONLY — the `## Current Test`
|
|
554
|
+
// section can itself contain a `### N. Name` heading with no `result:`
|
|
555
|
+
// line (a still-pending test's own description), which would otherwise be
|
|
556
|
+
// mis-parsed as a 'missing' (blocking) item and poison the status
|
|
557
|
+
// computation for a session that is, in fact, fully complete. Reuses the
|
|
558
|
+
// module's own row parser (`parseUatItemsWithStats`) rather than
|
|
559
|
+
// `uat-predicate.cjs`'s independent one — `uat-predicate.cjs` encodes a
|
|
560
|
+
// DIFFERENT (stricter) passing criterion for a different gate, so re-using
|
|
561
|
+
// its parser without its criterion would still be re-deriving.
|
|
562
|
+
const testsSection = collectSection(content, (h) => /^tests$/i.test(h.text) && h.level === 2, { levelBounded: true });
|
|
563
|
+
const { items, headingsSeen } = parseUatItemsWithStats(testsSection ? testsSection.body : '');
|
|
564
|
+
const hasBlockingRow = items.some((item) => {
|
|
565
|
+
const result = item.result.toLowerCase();
|
|
566
|
+
if (result === 'pending' || result === 'blocked')
|
|
567
|
+
return true;
|
|
568
|
+
if (result === 'skipped')
|
|
569
|
+
return !item.reason;
|
|
570
|
+
return false;
|
|
571
|
+
});
|
|
572
|
+
const status = (headingsSeen > 0 || hasBlockingRow) ? 'partial' : 'complete';
|
|
573
|
+
let candidate = setFrontmatterStatus(content, status);
|
|
574
|
+
candidate = setCurrentTestComplete(candidate);
|
|
575
|
+
const changed = stripUpdatedForCompare(candidate) !== stripUpdatedForCompare(baseline ?? '');
|
|
576
|
+
if (!changed) {
|
|
577
|
+
// #5105 review finding 2 / M1: a material match against `baseline` does
|
|
578
|
+
// not mean `content` (the LIVE file) is already clean — a re-opened
|
|
579
|
+
// session can leave `status: testing` plus a pending `## Current Test`
|
|
580
|
+
// with no row changes, which normalizes to the SAME material candidate
|
|
581
|
+
// but is not byte-identical to what is already committed. Restore the
|
|
582
|
+
// byte-exact committed form ONLY when the live document differs from the
|
|
583
|
+
// candidate in something OTHER than `updated:` — a live document that
|
|
584
|
+
// differs from baseline/candidate ONLY in `updated:` must never be
|
|
585
|
+
// written (the stated decision above), so that case falls through to the
|
|
586
|
+
// untouched-`content` return below instead.
|
|
587
|
+
if (baseline !== null && stripUpdatedForCompare(content) !== stripUpdatedForCompare(candidate)) {
|
|
588
|
+
// `changed === false` means candidate and baseline differ at most in the `updated:` line,
|
|
589
|
+
// so the byte-exact restore target is `baseline` itself — no splice needed.
|
|
590
|
+
const restoredContent = baseline;
|
|
591
|
+
if (restoredContent !== content) {
|
|
592
|
+
return { changed: false, content: restoredContent, status, restored: true };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return { changed: false, content, status };
|
|
596
|
+
}
|
|
597
|
+
const updatedIso = clock().toISOString();
|
|
598
|
+
candidate = setFrontmatterUpdated(candidate, updatedIso);
|
|
599
|
+
return { changed: true, content: candidate, status };
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Bounded, byte-exact read of `absPath` at git HEAD — the #5105 R1 `baseline`
|
|
603
|
+
* input. Routes through `pristine-baseline.cts`'s shared `gitExec` (not the
|
|
604
|
+
* trimming `execGit` shell projection, and not a second private
|
|
605
|
+
* `execFileSync` wrapper — #5105 review finding 4): the comparison this
|
|
606
|
+
* feeds is byte-sensitive (a trailing newline IS material), so the read must
|
|
607
|
+
* not trim anything.
|
|
608
|
+
*
|
|
609
|
+
* The pathspec is git-native: `git show HEAD:./<basename>` run from the file's
|
|
610
|
+
* own directory. A `./`-prefixed tree path is resolved by git relative to the
|
|
611
|
+
* command's cwd (the form `verify.cts` uses via `rev-parse --show-prefix`), so
|
|
612
|
+
* git — not JS — canonicalizes it: a symlinked `--cwd`, a project root in a
|
|
613
|
+
* subdirectory of the repository, and Windows 8.3 / drive-case / forward-slash
|
|
614
|
+
* toplevel shapes all resolve with no realpath or `path.relative` derivation,
|
|
615
|
+
* and git itself contains the path inside the repository.
|
|
616
|
+
*
|
|
617
|
+
* Returns `null` on any failure (git absent, not a repository, unborn HEAD,
|
|
618
|
+
* the path untracked/absent at HEAD) — never throws, matching the "no
|
|
619
|
+
* baseline" posture every other git-history reader in this codebase uses.
|
|
620
|
+
*/
|
|
621
|
+
function readBaselineAtHead(absPath) {
|
|
622
|
+
try {
|
|
623
|
+
return gitExec(node_path_1.default.dirname(absPath), ['show', `HEAD:./${node_path_1.default.basename(absPath)}`]);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* CLI command handler (#5105 R1): `uat.complete-session <uatPath> [--message <m>]`.
|
|
631
|
+
* Deny-by-default no-op on an unchanged session — zero writes, zero commits
|
|
632
|
+
* (closes C1 in the #5105 census); a live document differing from the
|
|
633
|
+
* committed baseline ONLY in the frontmatter `updated:` value is one such
|
|
634
|
+
* no-op and is never written. On a material change, writes the file and
|
|
635
|
+
* commits through the existing `cmdCommit` internals (honoring `commit_docs`),
|
|
636
|
+
* reading `cmdCommit`'s own result back via the shared `captureStdoutSyncWrites`
|
|
637
|
+
* helper (#5105 review S9 — the ONE such helper, also used by gsd-tools.cjs)
|
|
638
|
+
* instead of a hand-rolled `fs.writeSync` monkeypatch, so this command can
|
|
639
|
+
* report the real `committed`/`reason` outcome rather than assuming success.
|
|
640
|
+
* A third `changed:false, restored:true` outcome (M1) writes the file — a
|
|
641
|
+
* byte-exact restore to the committed baseline — but never commits: this is
|
|
642
|
+
* still a no-op from git's perspective (see `completeUatSession`'s own doc).
|
|
643
|
+
*/
|
|
644
|
+
async function cmdUatCompleteSession(cwd, uatPathArg, options = {}, raw) {
|
|
645
|
+
if (!uatPathArg) {
|
|
646
|
+
error('UAT file required: use uat.complete-session <uatPath>');
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const resolvedPath = (0, security_cjs_1.requireSafePath)(uatPathArg, cwd, 'UAT file', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
650
|
+
if (!node_fs_1.default.existsSync(resolvedPath)) {
|
|
651
|
+
error(`UAT file not found: ${uatPathArg}`);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const content = node_fs_1.default.readFileSync(resolvedPath, 'utf-8');
|
|
655
|
+
const relPath = toPosixPath(node_path_1.default.relative(cwd, resolvedPath));
|
|
656
|
+
const baseline = readBaselineAtHead(resolvedPath);
|
|
657
|
+
let result;
|
|
658
|
+
try {
|
|
659
|
+
result = completeUatSession(content, { baseline });
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
// An unparseable (or key-unreconcilable) frontmatter block: fail closed —
|
|
663
|
+
// nothing is written and nothing is committed (`spliceFrontmatter` owns
|
|
664
|
+
// the refusal decision; this surfaces it).
|
|
665
|
+
if (!isFrontmatterWriteRefusal(err))
|
|
666
|
+
throw err;
|
|
667
|
+
error(`uat.complete-session: ${uatPathArg} — ${err.message}`);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (!result.changed) {
|
|
671
|
+
// #5105 review finding 2: a `restored` result still writes (byte-exact
|
|
672
|
+
// restore to HEAD's own committed form) but never commits — this is a
|
|
673
|
+
// no-op from git's perspective, not a new change.
|
|
674
|
+
if (result.restored) {
|
|
675
|
+
node_fs_1.default.writeFileSync(resolvedPath, result.content);
|
|
676
|
+
}
|
|
677
|
+
output({ changed: false, status: result.status, ...(result.restored ? { restored: true } : {}) }, raw);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
node_fs_1.default.writeFileSync(resolvedPath, result.content);
|
|
681
|
+
const message = options.message || `test: complete UAT session (${relPath})`;
|
|
682
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
683
|
+
const commandsMod = require('./commands.cjs');
|
|
684
|
+
// `raw: false` — cmdCommit's `raw: true` shape emits only a bare hash/reason
|
|
685
|
+
// STRING (its own `output(result, raw, hash || 'committed')` rawValue arm),
|
|
686
|
+
// not the structured `{ committed, reason }` object this command needs to
|
|
687
|
+
// read back; the full JSON envelope is captured here and never reaches the
|
|
688
|
+
// real stdout (captureStdoutSyncWrites), so suppressing it via `raw` would
|
|
689
|
+
// only destroy the information this command needs.
|
|
690
|
+
const captured = await captureStdoutSyncWrites(() => {
|
|
691
|
+
commandsMod.cmdCommit(cwd, message, [relPath], false, false, false);
|
|
692
|
+
});
|
|
693
|
+
let committed = false;
|
|
694
|
+
let reason;
|
|
695
|
+
try {
|
|
696
|
+
// io.cjs's output() redirects a >50KB JSON payload to a tmpfile and emits
|
|
697
|
+
// `@file:<path>` instead (never the case for a commit result in practice,
|
|
698
|
+
// but resolved defensively rather than assumed) — via the shared
|
|
699
|
+
// `resolveAtFileOutput` helper (#5105 review finding 7), the same one
|
|
700
|
+
// gsd-tools.cjs uses for its own `@file:` resolution.
|
|
701
|
+
const capturedJson = resolveAtFileOutput(captured);
|
|
702
|
+
const commitResult = JSON.parse(capturedJson);
|
|
703
|
+
committed = commitResult.committed === true;
|
|
704
|
+
if (!committed && typeof commitResult.reason === 'string')
|
|
705
|
+
reason = commitResult.reason;
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
reason = 'commit_output_unparseable';
|
|
709
|
+
}
|
|
710
|
+
const envelope = {
|
|
711
|
+
changed: true,
|
|
712
|
+
status: result.status,
|
|
713
|
+
committed,
|
|
714
|
+
};
|
|
715
|
+
if (reason)
|
|
716
|
+
envelope.reason = reason;
|
|
717
|
+
output(envelope, raw);
|
|
718
|
+
}
|
|
344
719
|
// ─── parseCurrentTest ─────────────────────────────────────────────────────────
|
|
345
720
|
function parseCurrentTest(content) {
|
|
346
721
|
// #3707-CR: this is the render-checkpoint path's own independent ingress
|
|
@@ -1418,6 +1793,19 @@ function parseUatItemsWithStats(content) {
|
|
|
1418
1793
|
const headingParts = parseTestRowHeadingText(current.text);
|
|
1419
1794
|
const testNumber = headingParts.number;
|
|
1420
1795
|
const testName = headingParts.name;
|
|
1796
|
+
// #4983: an `issue` test whose `## Gaps` entry has been reconciled to
|
|
1797
|
+
// `status: resolved` (verify-work.md's reconcile_gaps step, #1921) is a
|
|
1798
|
+
// fixed-and-verified issue — skip it exactly like a PASS token above so
|
|
1799
|
+
// the read-side/audit view stops surfacing it as an open row forever.
|
|
1800
|
+
// Scoped to `result === 'issue'` only, mirroring the #4983 gate fix's own
|
|
1801
|
+
// scope (src/uat-predicate.cts's isTestGapResolved). Trusts the `status:`
|
|
1802
|
+
// text the same way `parseGapsItems` below already trusts it for a Gaps
|
|
1803
|
+
// entry viewed on its own (this is the informational read layer, not the
|
|
1804
|
+
// gate — the filesystem-verified "resolved_by names an executed plan"
|
|
1805
|
+
// check lives only in the GATE, which is what actually blocks
|
|
1806
|
+
// `phase uat-passed`).
|
|
1807
|
+
if (result === 'issue' && isTestIssueResolvedInGaps(content, testNumber))
|
|
1808
|
+
continue;
|
|
1421
1809
|
// Reuse the existing block-scalar/inline `expected:` grammar rather than
|
|
1422
1810
|
// re-deriving a second one (#3707 defect 2). #3078 blocker: the block is
|
|
1423
1811
|
// CLIPPED at its first top-level fence opener first — still raw text (a
|
|
@@ -1541,6 +1929,33 @@ function parseGapsItems(content) {
|
|
|
1541
1929
|
items.push(...parseGapsTableItems(gapsSection.body));
|
|
1542
1930
|
return items;
|
|
1543
1931
|
}
|
|
1932
|
+
/**
|
|
1933
|
+
* #4983: true when the UAT file's `## Gaps` section records `testNum` as
|
|
1934
|
+
* `status: resolved` (case-insensitive). Reuses the same `collectSection` /
|
|
1935
|
+
* `splitGapsEntries` / `extractGapEntryFields` seam `parseGapsItems` above
|
|
1936
|
+
* already uses to find and read a Gaps entry, so this reader stays in
|
|
1937
|
+
* lockstep with that entry's own fence/nesting/quoting hardening rather than
|
|
1938
|
+
* re-deriving it.
|
|
1939
|
+
*
|
|
1940
|
+
* This is the READ-SIDE / audit view: it trusts the entry's `status:` text
|
|
1941
|
+
* exactly the way `parseGapsItems` already trusts it to skip a resolved Gaps
|
|
1942
|
+
* entry on its own — no filesystem check that `resolved_by` names an
|
|
1943
|
+
* executed plan. That stricter, filesystem-verified check belongs to the
|
|
1944
|
+
* GATE that actually blocks `phase uat-passed` (src/uat-predicate.cts's
|
|
1945
|
+
* `isTestGapResolved`), not to this informational listing.
|
|
1946
|
+
*/
|
|
1947
|
+
function isTestIssueResolvedInGaps(content, testNum) {
|
|
1948
|
+
const gapsSection = collectSection(content, (h) => /^gaps$/i.test(h.text) && h.level === 2, { levelBounded: true });
|
|
1949
|
+
if (!gapsSection)
|
|
1950
|
+
return false;
|
|
1951
|
+
return splitGapsEntries(gapsSection.body).some((entryLines) => {
|
|
1952
|
+
const fields = extractGapEntryFields(entryLines);
|
|
1953
|
+
if (!fields.status || fields.status.toLowerCase() !== 'resolved')
|
|
1954
|
+
return false;
|
|
1955
|
+
const entryTest = fields.test;
|
|
1956
|
+
return !!entryTest && /^\d+$/.test(entryTest) && parseInt(entryTest, 10) === testNum;
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1544
1959
|
/**
|
|
1545
1960
|
* Split a section body into its GFM pipe tables, one entry per table (#2766).
|
|
1546
1961
|
*
|
|
@@ -3333,12 +3748,12 @@ function parseVerificationGapsItems(content) {
|
|
|
3333
3748
|
*/
|
|
3334
3749
|
function parseVerificationItems(content, status, sourcePath) {
|
|
3335
3750
|
const items = [];
|
|
3336
|
-
if (status ===
|
|
3751
|
+
if (status === VERIFICATION_STATUS.GAPS_FOUND) {
|
|
3337
3752
|
items.push(...parseHumanVerificationItems(content, sourcePath));
|
|
3338
3753
|
items.push(...parseVerificationGapsItems(content));
|
|
3339
3754
|
return items;
|
|
3340
3755
|
}
|
|
3341
|
-
if (status ===
|
|
3756
|
+
if (status === VERIFICATION_STATUS.HUMAN_NEEDED) {
|
|
3342
3757
|
return parseHumanVerificationItems(content, sourcePath);
|
|
3343
3758
|
}
|
|
3344
3759
|
return items;
|
|
@@ -3579,6 +3994,14 @@ function categorizeItem(rawResult, reason, blockedBy) {
|
|
|
3579
3994
|
return 'blocked';
|
|
3580
3995
|
}
|
|
3581
3996
|
if (result === 'skipped') {
|
|
3997
|
+
// #4546 — a deliberately deferred follow-up (the verify-work writer's
|
|
3998
|
+
// "Deferred follow-up:" template, #1921) is its own category, checked
|
|
3999
|
+
// BEFORE the keyword families so e.g. "…on the release build next
|
|
4000
|
+
// version" is not misfiled as build_needed. Must agree with
|
|
4001
|
+
// uat-predicate.cts's DEFERRED_REASON_RE (gate/audit agreement,
|
|
4002
|
+
// #3078-CR), pinned by tests/uat-predicate.test.cjs.
|
|
4003
|
+
if (reason && /^["']?deferred follow-up\b/i.test(reason))
|
|
4004
|
+
return 'deferred';
|
|
3582
4005
|
if (reason) {
|
|
3583
4006
|
if (/server|not running|not available/i.test(reason))
|
|
3584
4007
|
return 'server_blocked';
|
|
@@ -3604,6 +4027,8 @@ function categorizeItem(rawResult, reason, blockedBy) {
|
|
|
3604
4027
|
module.exports = {
|
|
3605
4028
|
cmdAuditUat,
|
|
3606
4029
|
cmdRenderCheckpoint,
|
|
4030
|
+
completeUatSession,
|
|
4031
|
+
cmdUatCompleteSession,
|
|
3607
4032
|
parseCurrentTest,
|
|
3608
4033
|
parseUatItems,
|
|
3609
4034
|
parseUatItemsWithStats,
|
|
@@ -117,6 +117,15 @@ function validateRequirement(element) {
|
|
|
117
117
|
if (r.elements == null && !(typeof r.text === 'string' && r.text.trim())) {
|
|
118
118
|
throw new Error(`element ${element.id} text must be a non-empty string when no elements override is provided`);
|
|
119
119
|
}
|
|
120
|
+
// text_en (#4657) is optional, but when present it must be a non-empty string. An empty
|
|
121
|
+
// string is NOT caught by `??` (only null/undefined are), so an unvalidated `text_en: ''`
|
|
122
|
+
// would silently win `text_en ?? text` and classify against '' — the same fail-open shape
|
|
123
|
+
// #1110/#2773 already exist to eliminate, just moved one field over. Validated
|
|
124
|
+
// unconditionally (not gated on whether `elements` will make it unused) so bad data fails
|
|
125
|
+
// closed even when it happens to be dead for this particular call.
|
|
126
|
+
if (r.text_en != null && !(typeof r.text_en === 'string' && r.text_en.trim())) {
|
|
127
|
+
throw new Error(`element ${element.id} text_en must be a non-empty string when present`);
|
|
128
|
+
}
|
|
120
129
|
}
|
|
121
130
|
/** Validate a UI-consideration resolution against the UI verification vocabulary (delegated, D-06). */
|
|
122
131
|
function validateResolution(resolution) {
|
|
@@ -143,7 +152,11 @@ function proposeConsiderations(element) {
|
|
|
143
152
|
kinds = element.elements;
|
|
144
153
|
}
|
|
145
154
|
else {
|
|
146
|
-
|
|
155
|
+
// #4657: prefer the English translation when present — UI_CUES are English-only
|
|
156
|
+
// word-boundary patterns, so a non-English `text` (e.g. response_language projects)
|
|
157
|
+
// would otherwise classify to zero kinds. validateRequirement (called above) has
|
|
158
|
+
// already guaranteed text_en, if present, is a non-empty string.
|
|
159
|
+
kinds = classifyElement((element.text_en ?? element.text));
|
|
147
160
|
if (kinds.length === 0) {
|
|
148
161
|
// Prose present but no element cue matched. Do NOT silently drop it (#1110): a UI element
|
|
149
162
|
// whose phrasing missed every cue would otherwise vanish from coverage with no signal — the
|
|
@@ -210,7 +223,7 @@ function proposeElements(elements) {
|
|
|
210
223
|
const considerations = proposeConsiderations(el);
|
|
211
224
|
const kinds = Array.isArray(el.elements)
|
|
212
225
|
? el.elements // already validated inside proposeConsiderations
|
|
213
|
-
: classifyElement(el.text);
|
|
226
|
+
: classifyElement((el.text_en ?? el.text)); // #4657: text_en ?? text
|
|
214
227
|
const unclassified = !Array.isArray(el.elements) && kinds.length === 0;
|
|
215
228
|
const categories = unclassified ? [] : applicableCategories(kinds);
|
|
216
229
|
return { id: el.id, kinds, categories, considerations, unclassified };
|