@opengsd/gsd-core 1.15.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/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- 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 +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- 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 +1 -1
- package/agents/gsd-plan-checker.md +7 -2
- package/agents/gsd-planner.md +3 -3
- 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 +42 -51
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +31 -9
- package/bin/install.js +126 -196
- 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 +5 -0
- package/commands/gsd/capture.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +6 -1
- package/commands/gsd/quick-batch.md +20 -8
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +5 -1
- 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 +142 -56
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +78 -44
- 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 +183 -103
- package/gsd-core/bin/lib/capability-validator.cjs +16 -0
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
- 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 +11 -8
- 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 +173 -170
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config.cjs +43 -12
- 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/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 +413 -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/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/init.cjs +162 -96
- package/gsd-core/bin/lib/install-engine.cjs +19 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- 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 +19 -1
- package/gsd-core/bin/lib/model-resolver.cjs +18 -18
- 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 +11 -4
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id.cjs +1 -1
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +280 -80
- package/gsd-core/bin/lib/plan-document.cjs +93 -19
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +273 -40
- package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- 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/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/roadmap-command-router.cjs +13 -15
- package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
- package/gsd-core/bin/lib/roadmap.cjs +169 -72
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +91 -157
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +307 -591
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- 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.cjs +214 -105
- package/gsd-core/bin/lib/surface.cjs +2 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
- package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
- package/gsd-core/bin/lib/uat.cjs +424 -7
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
- 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 +1165 -314
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +286 -567
- 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/shared/config-defaults.manifest.json +5 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -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/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +10 -4
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- 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 +3 -2
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +3 -2
- 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.md +28 -18
- package/gsd-core/workflows/check-todos.md +6 -5
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +5 -4
- package/gsd-core/workflows/code-review.md +84 -90
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +12 -7
- package/gsd-core/workflows/debug.md +30 -5
- 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 +1 -1
- package/gsd-core/workflows/docs-update.md +4 -3
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +5 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
- 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 +1 -1
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- 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/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.md +78 -170
- package/gsd-core/workflows/execute-plan.md +13 -19
- package/gsd-core/workflows/explore.md +2 -2
- package/gsd-core/workflows/extract-learnings.md +3 -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 +2 -1
- package/gsd-core/workflows/import.md +3 -2
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- 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 +5 -3
- package/gsd-core/workflows/map-codebase.md +4 -3
- 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 +10 -10
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.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 +1 -1
- 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 +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -18
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
- 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 +42 -31
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +3 -2
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +11 -12
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +12 -12
- 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 +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +5 -4
- package/gsd-core/workflows/ui-review.md +4 -3
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/validate-phase.md +10 -12
- 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 +68 -59
- package/hooks/dist/gsd-agent-isolation-guard.js +8 -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 +147 -16
- package/hooks/dist/gsd-statusline.js +15 -7
- package/hooks/dist/gsd-update-banner.js +8 -0
- 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 +8 -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 +147 -16
- package/hooks/gsd-statusline.js +15 -7
- package/hooks/gsd-update-banner.js +8 -0
- 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 +21 -4
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +12 -8
- package/scripts/docs-guard-registry.cjs +6 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +10 -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 +20 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +34 -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 +10 -0
- 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-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +29 -0
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-test-file-count.allowlist.json +11 -1
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
- package/scripts/mutation-matrix.cjs +50 -5
- 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 +5 -0
- package/skills/gsd-capture/SKILL.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +6 -1
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +20 -8
- package/skills/gsd-review/SKILL.md +5 -1
- 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/workflows/execute-phase/steps/stale-reverification.md +0 -24
|
@@ -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,13 +51,11 @@ 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']);
|
|
50
61
|
// #4546 — a `skipped` test-item whose reason carries the verify-work writer's
|
|
@@ -63,6 +74,18 @@ const DEFERRED_REASON_RE = /^["']?deferred follow-up\b/i;
|
|
|
63
74
|
// section also bypasses the complete_session promotion offer; the section is
|
|
64
75
|
// the durable project-level record. Variant spellings that do not match
|
|
65
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.
|
|
66
89
|
// ─── stripFalsePositiveContexts ───────────────────────────────────────────────
|
|
67
90
|
/**
|
|
68
91
|
* Remove contexts that can contain `result: ...` lines that are NOT real test results:
|
|
@@ -75,8 +98,10 @@ const DEFERRED_REASON_RE = /^["']?deferred follow-up\b/i;
|
|
|
75
98
|
* Returns surviving lines joined by '\n'. Robust to CRLF input.
|
|
76
99
|
*/
|
|
77
100
|
function stripFalsePositiveContexts(content) {
|
|
78
|
-
// Step (a): strip leading frontmatter block
|
|
79
|
-
|
|
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;
|
|
80
105
|
// Step (b): remove HTML comments anywhere; unterminated comment swallows to EOF
|
|
81
106
|
stripped = stripped.replace(/<!--[\s\S]*?(?:-->|$)/g, '');
|
|
82
107
|
// Step (c): remove fenced code blocks via the canonical seam (ADR-1372 T5)
|
|
@@ -135,19 +160,19 @@ function parseUatResultItems(cleanContent) {
|
|
|
135
160
|
// #3078-CR MEDIUM (security review follow-up): STRUCTURE and ATTRIBUTION
|
|
136
161
|
// need different split frames. This is a STRUCTURE scan — finding where a
|
|
137
162
|
// heading block begins — and there is no attribution distinction to
|
|
138
|
-
// preserve, so split on any of
|
|
139
|
-
//
|
|
140
|
-
// a naive `split('\n')`-only
|
|
141
|
-
//
|
|
142
|
-
// `\n`-delimited one. Contrast the
|
|
143
|
-
// 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.
|
|
144
169
|
const HEADING_LINE_RE = /^###\s*(\d+)\.\s*(.+)$/;
|
|
145
170
|
const headings = [];
|
|
146
171
|
{
|
|
147
172
|
// All three separators are exactly one UTF-16 code unit, so the
|
|
148
173
|
// `line.length + 1` offset arithmetic below stays valid regardless of
|
|
149
174
|
// which separator terminated a given line.
|
|
150
|
-
const lines = cleanContent.split(
|
|
175
|
+
const lines = cleanContent.split(LINE_SPLIT_RE);
|
|
151
176
|
let offset = 0;
|
|
152
177
|
for (const line of lines) {
|
|
153
178
|
const hMatch = line.match(HEADING_LINE_RE);
|
|
@@ -168,8 +193,8 @@ function parseUatResultItems(cleanContent) {
|
|
|
168
193
|
// A block spans until the START of the next heading's line (tracked
|
|
169
194
|
// directly from the same split-frame scan above), not a re-search for a
|
|
170
195
|
// literal '\n###' over unsplit text -- the latter would silently miss a
|
|
171
|
-
// next heading delimited by
|
|
172
|
-
// 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.
|
|
173
198
|
const blockContent = i + 1 < headings.length
|
|
174
199
|
? cleanContent.slice(blockStart, headings[i + 1].lineStart)
|
|
175
200
|
: cleanContent.slice(blockStart);
|
|
@@ -177,9 +202,9 @@ function parseUatResultItems(cleanContent) {
|
|
|
177
202
|
// the heading scan above): test each already-split line individually
|
|
178
203
|
// against a single-line (no `/m` anchor) pattern instead of anchoring
|
|
179
204
|
// over unsplit `blockContent`, so a `result:`-shaped line reachable only
|
|
180
|
-
// via
|
|
181
|
-
//
|
|
182
|
-
//
|
|
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.
|
|
183
208
|
// Uses [ \t]* (not \s*) so the captured value must sit on the SAME line as result:.
|
|
184
209
|
// A result: key with the value on a subsequent line yields no match → 'missing' (blocker).
|
|
185
210
|
// #4546: the `reason:` line is captured with the same frame and FIRST-match
|
|
@@ -218,6 +243,234 @@ function parseUatResultItems(cleanContent) {
|
|
|
218
243
|
}
|
|
219
244
|
return items;
|
|
220
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
|
+
}
|
|
221
474
|
// ─── evaluateUatPassed ────────────────────────────────────────────────────────
|
|
222
475
|
/**
|
|
223
476
|
* Evaluate all UAT/VERIFICATION files in a phase directory.
|
|
@@ -269,8 +522,15 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
269
522
|
const verFileNames = scopeToPhase(dirEntries.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md')), phaseDirBaseName);
|
|
270
523
|
// ── Process UAT files ──────────────────────────────────────────────────────
|
|
271
524
|
for (const file of uatFileNames) {
|
|
272
|
-
uatFiles.push(file);
|
|
273
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);
|
|
274
534
|
let raw = '';
|
|
275
535
|
try {
|
|
276
536
|
// #3078-CR MEDIUM: normalize line endings at the read boundary — the
|
|
@@ -303,6 +563,10 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
303
563
|
// Parse test items from the cleaned body (hardened against false positives)
|
|
304
564
|
const cleanContent = stripFalsePositiveContexts(raw);
|
|
305
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);
|
|
306
570
|
for (const item of items) {
|
|
307
571
|
// #4546: a `skipped` item whose reason matches the verify-work writer's
|
|
308
572
|
// "Deferred follow-up:" template is a deliberately deferred follow-up
|
|
@@ -310,7 +574,10 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
310
574
|
// reason WITH its wrapping quotes. Everything else — pending, blocked,
|
|
311
575
|
// issue, missing, and a plain or non-deferral skipped — still blocks.
|
|
312
576
|
const deferred = item.result === 'skipped' && DEFERRED_REASON_RE.test(item.reason);
|
|
313
|
-
|
|
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;
|
|
314
581
|
checks.push({
|
|
315
582
|
file,
|
|
316
583
|
test: item.test,
|
|
@@ -318,6 +585,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
318
585
|
result: item.result,
|
|
319
586
|
passing,
|
|
320
587
|
deferred,
|
|
588
|
+
resolved,
|
|
321
589
|
});
|
|
322
590
|
if (!passing) {
|
|
323
591
|
blockers.push(`${file}: test ${item.test} (${item.result})`);
|
|
@@ -329,6 +597,11 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
329
597
|
for (const file of uatOnly ? [] : verFileNames) {
|
|
330
598
|
verificationFiles.push(file);
|
|
331
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;
|
|
332
605
|
let raw = '';
|
|
333
606
|
try {
|
|
334
607
|
// #3078-CR MEDIUM: same read-boundary normalization as the UAT loop above.
|
|
@@ -338,16 +611,17 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
338
611
|
blockers.push(`${file}: could not read verification file`);
|
|
339
612
|
continue;
|
|
340
613
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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) {
|
|
344
618
|
blockers.push(`${file}: verification status=${vStatus}`);
|
|
345
619
|
}
|
|
346
|
-
else if (vStatus
|
|
347
|
-
// Allowlist: only explicitly-passing
|
|
620
|
+
else if (vStatus === VERIFICATION_STATUS.PASSED) {
|
|
621
|
+
// Allowlist: only an explicitly-passing status counts
|
|
348
622
|
hasPassingVerification = true;
|
|
349
623
|
}
|
|
350
|
-
//
|
|
624
|
+
// No status: does NOT count as passing, does NOT push a blocker
|
|
351
625
|
// (handled by the requireVerification policy check below if needed)
|
|
352
626
|
}
|
|
353
627
|
// ── Policy: requireVerification ───────────────────────────────────────────
|
|
@@ -360,10 +634,10 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
360
634
|
const verificationResult = readVerificationStatus(phaseFullDir);
|
|
361
635
|
const verificationStatus = verificationResult.status;
|
|
362
636
|
verificationStaleCheckIndeterminate = verificationResult.staleCheckIndeterminate === true;
|
|
363
|
-
if (verificationStatus ===
|
|
364
|
-
blockers.push(
|
|
637
|
+
if (verificationStatus === VERIFICATION_STATUS.STALE) {
|
|
638
|
+
blockers.push(`policy: verification status=${VERIFICATION_STATUS.STALE}`);
|
|
365
639
|
}
|
|
366
|
-
else if (verificationStatus !==
|
|
640
|
+
else if (verificationStatus !== VERIFICATION_STATUS.PASSED || !hasPassingVerification) {
|
|
367
641
|
blockers.push('policy: verification required but no passing *-VERIFICATION.md found');
|
|
368
642
|
}
|
|
369
643
|
}
|
|
@@ -392,4 +666,8 @@ module.exports = {
|
|
|
392
666
|
parseUatResultItems,
|
|
393
667
|
analyzeMarkdown,
|
|
394
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,
|
|
395
673
|
};
|