@opengsd/gsd-core 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +3 -3
- package/agents/gsd-code-fixer.md +5 -4
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +460 -105
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +124 -18
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +41 -8
- package/gsd-core/bin/lib/capability-registry.cjs +162 -74
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +20 -11
- package/gsd-core/bin/lib/capability-validator.cjs +23 -4
- package/gsd-core/bin/lib/commands.cjs +5 -1
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +93 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +21 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +19 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +114 -18
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +76 -5
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -7
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +235 -37
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +89 -30
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +428 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +123 -16
- package/gsd-core/bin/lib/smart-entry.cjs +37 -16
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +47 -5
- package/gsd-core/bin/lib/state-transition.cjs +150 -34
- package/gsd-core/bin/lib/state.cjs +140 -88
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +7 -28
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +38 -1
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +14 -3
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +5 -4
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +66 -8
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +64 -157
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +34 -6
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +44 -5
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +11 -11
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +19 -8
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +21 -3
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +7 -3
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +46 -11
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +6 -2
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +7 -3
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +54 -86
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +23 -8
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +5 -1
- package/gsd-core/workflows/review.md +22 -15
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +4 -3
- package/gsd-core/workflows/ship.md +16 -14
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +15 -6
- package/hooks/dist/gsd-agent-isolation-guard.js +14 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -3
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +244 -53
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +14 -2
- package/hooks/gsd-cursor-subagent-start.js +8 -3
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +244 -53
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +19 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +41 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/release-tarball-smoke.cjs +1 -0
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -145,10 +145,28 @@ function evaluateDecisionCoveragePlan(input) {
|
|
|
145
145
|
if (decisions.length === 0) {
|
|
146
146
|
return (0, gate_verdict_cjs_1.gateVerdict)('skip', false, { passed: true, skipped: true, reason: 'no trackable decisions', total: 0, covered: 0, uncovered: [], message: 'No trackable decisions in CONTEXT.md.' });
|
|
147
147
|
}
|
|
148
|
+
// #4939: a phase-dir argument that was GIVEN but is not a directory is a caller
|
|
149
|
+
// error like #2770's empty context argument and #4794's non-file context path:
|
|
150
|
+
// no plan was scanned, so nothing was measured. Fail closed with total/covered
|
|
151
|
+
// null and `uncovered` omitted. An OMITTED phase dir is left to the scan below —
|
|
152
|
+
// the #4130 `--context <path>` alone tests pin that as a coverage gap.
|
|
153
|
+
const phaseDirIssue = phaseDir ? (0, decision_coverage_support_cjs_1.phaseDirProblem)(phaseDir) : null;
|
|
154
|
+
if (phaseDirIssue) {
|
|
155
|
+
const phaseDirResult = { passed: false, skipped: false, reason: phaseDirIssue.reason, total: null, covered: null, message: `Decision coverage gate: the phase directory "${positionals[0]}" ${phaseDirIssue.what}, so no plans were scanned. ${phaseDirIssue.hint}` };
|
|
156
|
+
return phaseDirIssue.unreadable ? (0, gate_verdict_cjs_1.gateUnreadable)(true, phaseDirResult) : (0, gate_verdict_cjs_1.gateVerdict)('block', true, phaseDirResult);
|
|
157
|
+
}
|
|
148
158
|
const planContents = (0, decision_coverage_support_cjs_1.loadPlanContents)(phaseDir);
|
|
149
159
|
if (planContents.kind === 'unreadable')
|
|
150
160
|
return unreadableDecisionGate(`${planContents.span ?? phaseDir}: ${planContents.reason}`);
|
|
151
|
-
|
|
161
|
+
// #4939: a phase directory that holds no PLAN file is the same caller error: zero plans
|
|
162
|
+
// loaded is not zero coverage.
|
|
163
|
+
if (phaseDir && planContents.value.length === 0) {
|
|
164
|
+
return (0, gate_verdict_cjs_1.gateVerdict)('block', true, { passed: false, skipped: false, reason: 'phase directory holds no plans', total: null, covered: null, message: `Decision coverage gate: the phase directory "${positionals[0]}" holds no PLAN files, so no plans were scanned. Pass the phase DIRECTORY that holds the plans (e.g. .planning/phases/01-slug), not the phase number.` });
|
|
165
|
+
}
|
|
166
|
+
const tracker = (0, decision_coverage_support_cjs_1.resolveTrackerBodies)(projectDir, planContents.value, input.deps);
|
|
167
|
+
if (tracker.kind === 'unreadable')
|
|
168
|
+
return unreadableDecisionGate(`${tracker.span}: ${tracker.reason}`);
|
|
169
|
+
const sections = planContents.value.map((c, i) => (0, decision_coverage_support_cjs_1.extractPlanDesignatedSections)(c) + (tracker.value.bodies[i] ? '\n' + tracker.value.bodies[i] : ''));
|
|
152
170
|
const uncovered = [];
|
|
153
171
|
let covered = 0;
|
|
154
172
|
for (const decision of decisions) {
|
|
@@ -157,6 +175,8 @@ function evaluateDecisionCoveragePlan(input) {
|
|
|
157
175
|
else
|
|
158
176
|
uncovered.push({ id: decision.id, text: decision.text, category: decision.category });
|
|
159
177
|
}
|
|
178
|
+
if (uncovered.length > 0 && tracker.value.missingResolver)
|
|
179
|
+
return unreadableDecisionGate(`${tracker.value.missingResolver.span}: ${tracker.value.missingResolver.reason}`);
|
|
160
180
|
const passed = uncovered.length === 0;
|
|
161
181
|
return (0, gate_verdict_cjs_1.gateVerdict)(passed ? 'pass' : 'block', !passed, {
|
|
162
182
|
passed,
|
|
@@ -90,19 +90,35 @@ function evaluateDecisionCoverageVerify(input) {
|
|
|
90
90
|
if (decisions.length === 0) {
|
|
91
91
|
return (0, gate_verdict_cjs_1.gateVerdict)('skip', false, { skipped: true, blocking: false, reason: 'no trackable decisions', total: 0, honored: 0, not_honored: [], message: 'No trackable decisions in CONTEXT.md.' });
|
|
92
92
|
}
|
|
93
|
+
// #4939: mirror the plan gate. A phase-dir argument that names no directory
|
|
94
|
+
// means no plan or summary was scanned, so no decision can be reported
|
|
95
|
+
// not-honored. Warn (verify stays non-blocking) rather than list every decision.
|
|
96
|
+
const phaseDirIssue = phaseDir ? (0, decision_coverage_support_cjs_1.phaseDirProblem)(phaseDir) : null;
|
|
97
|
+
if (phaseDirIssue) {
|
|
98
|
+
const phaseDirResult = { skipped: false, blocking: false, reason: phaseDirIssue.reason, total: null, honored: null, not_honored: [], message: `Decision coverage verify (warning): the phase directory "${args[0]}" ${phaseDirIssue.what}, so no plans or summaries were scanned. ${phaseDirIssue.hint}` };
|
|
99
|
+
return phaseDirIssue.unreadable ? (0, gate_verdict_cjs_1.gateUnreadable)(false, phaseDirResult) : (0, gate_verdict_cjs_1.gateVerdict)('advisory', false, phaseDirResult);
|
|
100
|
+
}
|
|
93
101
|
// Every file the haystack is built from is typed evidence (#5170): one that exists but cannot be
|
|
94
102
|
// read is `unreadable`, never an empty slice of the haystack that makes a decision look unhonored.
|
|
95
103
|
const planContents = (0, decision_coverage_support_cjs_1.loadPlanContents)(phaseDir);
|
|
96
104
|
if (planContents.kind === 'unreadable')
|
|
97
105
|
return unreadableVerify(`${planContents.span ?? phaseDir}: ${planContents.reason}`);
|
|
106
|
+
const tracker = (0, decision_coverage_support_cjs_1.resolveTrackerBodies)(projectDir, planContents.value, input.deps);
|
|
107
|
+
if (tracker.kind === 'unreadable')
|
|
108
|
+
return unreadableVerify(`${tracker.span}: ${tracker.reason}`);
|
|
98
109
|
const summaryParts = (0, decision_coverage_support_cjs_1.loadSummaryContents)(phaseDir);
|
|
99
110
|
if (summaryParts.kind === 'unreadable')
|
|
100
111
|
return unreadableVerify(`${summaryParts.span ?? phaseDir}: ${summaryParts.reason}`);
|
|
112
|
+
// #4939: mirror the plan gate. A phase directory with no PLAN and no SUMMARY file gave the
|
|
113
|
+
// verifier nothing to read, which is a caller error, not a phase whose decisions were all missed.
|
|
114
|
+
if (phaseDir && planContents.value.length === 0 && summaryParts.value.length === 0) {
|
|
115
|
+
return (0, gate_verdict_cjs_1.gateVerdict)('advisory', false, { skipped: false, blocking: false, reason: 'phase directory holds no plans', total: null, honored: null, not_honored: [], message: `Decision coverage verify (warning): the phase directory "${args[0]}" holds no PLAN or SUMMARY files, so nothing was scanned. Pass the phase DIRECTORY that holds the plans (e.g. .planning/phases/01-slug), not the phase number.` });
|
|
116
|
+
}
|
|
101
117
|
const modifiedFiles = (0, decision_coverage_support_cjs_1.readModifiedFilesContent)(projectDir, summaryParts.value);
|
|
102
118
|
if (modifiedFiles.kind === 'unreadable')
|
|
103
119
|
return unreadableVerify(`${modifiedFiles.span ?? 'files_modified'}: ${modifiedFiles.reason}`);
|
|
104
120
|
const haystack = [
|
|
105
|
-
planContents.value.join('\n\n'),
|
|
121
|
+
planContents.value.map((c, i) => c + (tracker.value.bodies[i] ? '\n' + tracker.value.bodies[i] : '')).join('\n\n'),
|
|
106
122
|
summaryParts.value.join('\n\n'),
|
|
107
123
|
modifiedFiles.value,
|
|
108
124
|
(0, decision_coverage_support_cjs_1.phaseCommitMessages)(projectDir, phaseDir),
|
|
@@ -115,6 +131,8 @@ function evaluateDecisionCoverageVerify(input) {
|
|
|
115
131
|
else
|
|
116
132
|
notHonored.push({ id: decision.id, text: decision.text, category: decision.category });
|
|
117
133
|
}
|
|
134
|
+
if (notHonored.length > 0 && tracker.value.missingResolver)
|
|
135
|
+
return unreadableVerify(`${tracker.value.missingResolver.span}: ${tracker.value.missingResolver.reason}`);
|
|
118
136
|
return (0, gate_verdict_cjs_1.gateVerdict)(notHonored.length === 0 ? 'pass' : 'advisory', false, {
|
|
119
137
|
skipped: false,
|
|
120
138
|
blocking: false,
|
|
@@ -35,9 +35,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.MAX_COMMITS_CEILING = exports.SCOPE_EXCLUSION_PATHSPECS = void 0;
|
|
37
37
|
exports.extractTaskCommitRefs = extractTaskCommitRefs;
|
|
38
|
+
exports.qualifiedPlanSubjectPattern = qualifiedPlanSubjectPattern;
|
|
38
39
|
exports.planSubjectPattern = planSubjectPattern;
|
|
39
40
|
exports.isSafeRefArgument = isSafeRefArgument;
|
|
40
41
|
exports.isSafeDateArgument = isSafeDateArgument;
|
|
42
|
+
exports.commitIsAncestor = commitIsAncestor;
|
|
41
43
|
exports.resolveEvaluationScope = resolveEvaluationScope;
|
|
42
44
|
exports.evaluateEvaluationScope = evaluateEvaluationScope;
|
|
43
45
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -46,6 +48,9 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
46
48
|
const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
|
|
47
49
|
const gate_phase_context_cjs_1 = require("./gate-phase-context.cjs");
|
|
48
50
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
|
+
const planningWorkspaceMod = require("./planning-workspace.cjs");
|
|
53
|
+
const { readScopedConfigValue } = planningWorkspaceMod;
|
|
49
54
|
const gate_evidence_cjs_1 = require("./gate-evidence.cjs");
|
|
50
55
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
51
56
|
/** Paths that are never part of a code-review or UI scope (planning artifacts, lockfiles). */
|
|
@@ -107,25 +112,124 @@ function paddedPattern(value) {
|
|
|
107
112
|
const intPart = (m[1] ?? '').replace(/^0+(?=\d)/, '');
|
|
108
113
|
return `0*${(0, pattern_cjs_1.escapeEre)(intPart)}${(0, pattern_cjs_1.escapeEre)(m[2] ?? '')}`;
|
|
109
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* The `<phase>-<plan>` fragment of a plan subject pattern, or null when `id` is not that shape.
|
|
117
|
+
*
|
|
118
|
+
* Zero-padding tolerant in both halves: `03-01` yields `0*3-0*1`, so `feat(03-01):`, `test(3-1):`
|
|
119
|
+
* and `fix(03-01)!:` match while `feat(03-010):` does not (#4003, #4619, #4748).
|
|
120
|
+
*/
|
|
121
|
+
function phasePlanPattern(id) {
|
|
122
|
+
const dash = id.indexOf('-');
|
|
123
|
+
const phasePart = dash > 0 ? id.slice(0, dash) : '';
|
|
124
|
+
const planPart = dash > 0 ? id.slice(dash + 1) : '';
|
|
125
|
+
if (/^[0-9A-Za-z.]+$/.test(phasePart) && /^[0-9A-Za-z.]+$/.test(planPart)) {
|
|
126
|
+
return `${paddedPattern(phasePart)}-${paddedPattern(planPart)}`;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The anchored subject pattern for a `<phase>-<plan>` ERE fragment, optionally behind one scope
|
|
132
|
+
* qualifier as the group `(<qualifier>-)?`.
|
|
133
|
+
*
|
|
134
|
+
* Every spelling of a plan id goes through this one builder: the bare id, the id carrying the
|
|
135
|
+
* configured project code (#5271), and the workstream-qualified id `<ws>-<phase>-<plan>` that #5177
|
|
136
|
+
* proposes. The qualifier is ERE-escaped, so a workstream name (a path segment, not a project-code
|
|
137
|
+
* token) is matched literally. The group is a real capture group, never `(?:…)`: the pattern goes
|
|
138
|
+
* to `git log --extended-regexp`, and POSIX ERE has no non-capturing group.
|
|
139
|
+
*/
|
|
140
|
+
function qualifiedPlanSubjectPattern(fragment, qualifier = null) {
|
|
141
|
+
const group = qualifier === null ? '' : `(${(0, pattern_cjs_1.escapeEre)(qualifier)}-)?`;
|
|
142
|
+
return `^[a-z]+\\(${group}${fragment}\\)!?:`;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The `<CODE>-` prefix of `planId` when it is EXACTLY the project's configured `project_code`
|
|
146
|
+
* followed by a dash — the separator included — else null.
|
|
147
|
+
*
|
|
148
|
+
* The match is CASE-SENSITIVE and anchored to the configured value. The phase-id module's
|
|
149
|
+
* `stripProjectCodePrefix` grammar (case-insensitive, any code before a digit) recognises ANY
|
|
150
|
+
* letter prefix, which is deliberately wider than the project: `AUTH-01-02` and `setup-1-2` are
|
|
151
|
+
* ids this project never assigned a code to, and stripping their prefixes made them resolve
|
|
152
|
+
* another plan's commits (#5271). Only the code the project actually configures may be
|
|
153
|
+
* stripped, so an id that merely LOOKS coded keeps its literal-match guarantee.
|
|
154
|
+
*/
|
|
155
|
+
function configuredCodePrefix(planId, projectCode) {
|
|
156
|
+
if (projectCode === null || !isUsableProjectCode(projectCode))
|
|
157
|
+
return null;
|
|
158
|
+
const prefix = `${projectCode}-`;
|
|
159
|
+
return planId.startsWith(prefix) ? prefix : null;
|
|
160
|
+
}
|
|
110
161
|
/**
|
|
111
162
|
* The anchored subject pattern for a plan id, or null when `planId` is empty, over-long or carries a
|
|
112
163
|
* control / whitespace character.
|
|
113
164
|
*
|
|
114
165
|
* A `<phase>-<plan>` id (`03-01`) is zero-padding tolerant: `feat(03-01):`, `test(3-1):` and
|
|
115
|
-
* `fix(03-01)!:` match, `feat(03-010):` does not.
|
|
116
|
-
*
|
|
117
|
-
*
|
|
166
|
+
* `fix(03-01)!:` match, `feat(03-010):` does not. An id carrying the CONFIGURED project code
|
|
167
|
+
* (`PRJ-01-01` with `project_code: "PRJ"`, #5271) matches either spelling of the SAME plan —
|
|
168
|
+
* `feat(PRJ-01-01):` and `feat(01-01):` — because the two init verbs hand out different phase
|
|
169
|
+
* tokens for one plan. The code is stripped ONLY when it is the configured one: an id like
|
|
170
|
+
* `AUTH-01-02` or `setup-1-2`, which merely looks coded, is matched LITERALLY — every ERE
|
|
171
|
+
* metacharacter escaped — so it can only match a commit naming that exact id and never widens the
|
|
172
|
+
* pattern to another plan's commits. Any other id (a plan FILE NAME that does not follow the
|
|
173
|
+
* numbering) is matched literally for the same reason.
|
|
174
|
+
*
|
|
175
|
+
* `projectCode` is the project's configured `project_code`; with none (no config, no `planningDir`),
|
|
176
|
+
* every id is matched literally, which is the pre-#5271 behaviour.
|
|
118
177
|
*/
|
|
119
|
-
function planSubjectPattern(planId) {
|
|
178
|
+
function planSubjectPattern(planId, projectCode = null) {
|
|
120
179
|
if (planId.length === 0 || planId.length > 200 || /[\s\x00-\x1f\x7f]/.test(planId))
|
|
121
180
|
return null;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
181
|
+
const direct = phasePlanPattern(planId);
|
|
182
|
+
if (direct)
|
|
183
|
+
return qualifiedPlanSubjectPattern(direct);
|
|
184
|
+
// #5271: `init.execute-phase` returns `phase_number: "PRJ-01"` where `init.plan-phase` returns
|
|
185
|
+
// `padded_phase: "01"`, and every gate builds its `--plan` from the former. Splitting `PRJ-01-01`
|
|
186
|
+
// at its FIRST dash leaves `01-01` as the plan half, which is not a plan number, so the id fell
|
|
187
|
+
// through to the literal pattern that no executor commit can ever carry — an empty scope at exit
|
|
188
|
+
// 0, which is the correct answer for a plan that has NOT started and the wrong one for one that
|
|
189
|
+
// has. Retry on the CONFIGURED project-code-stripped id, with the code OPTIONAL so the commit the
|
|
190
|
+
// executor actually wrote still matches.
|
|
191
|
+
//
|
|
192
|
+
// The `(PRJ-)?` group comes from `qualifiedPlanSubjectPattern`, and it rides ONLY on a remainder
|
|
193
|
+
// that could be a plan number, so an empty or non-numeric remainder keeps the whole id literal —
|
|
194
|
+
// never WIDER than the pre-#5271 pattern.
|
|
195
|
+
const prefix = configuredCodePrefix(planId, projectCode);
|
|
196
|
+
if (prefix !== null) {
|
|
197
|
+
const stripped = planId.slice(prefix.length);
|
|
198
|
+
const coded = phasePlanPattern(stripped);
|
|
199
|
+
// The only remainder `phasePlanPattern` cannot split is the N-segment milestone shape
|
|
200
|
+
// (`01-02-03`), so require that whole shape: a bare `1`, `01-` or a trailing dash would
|
|
201
|
+
// otherwise widen the optional group onto `feat(1):`.
|
|
202
|
+
const planShaped = coded !== null || /^\d[0-9A-Za-z.]*(-[0-9A-Za-z.]+){2,}$/.test(stripped);
|
|
203
|
+
if (!planShaped)
|
|
204
|
+
return qualifiedPlanSubjectPattern((0, pattern_cjs_1.escapeEre)(planId));
|
|
205
|
+
return qualifiedPlanSubjectPattern(coded ?? (0, pattern_cjs_1.escapeEre)(stripped), projectCode);
|
|
127
206
|
}
|
|
128
|
-
return
|
|
207
|
+
return qualifiedPlanSubjectPattern((0, pattern_cjs_1.escapeEre)(planId));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Whether a configured `project_code` can safely be stripped from a plan id and put in a pattern.
|
|
211
|
+
*
|
|
212
|
+
* `config-set` stores `project_code` unvalidated and docs/CONFIGURATION.md calls it "any short
|
|
213
|
+
* string", so this is a SAFETY check, not the phase-id module's uppercase grammar: a letter first,
|
|
214
|
+
* then letters, digits, `_`, `.` or `-`, at most 32 characters. That admits `gsd` and `AUTH-X`
|
|
215
|
+
* (which the uppercase grammar left in the #5271 symptom) while excluding whitespace, control
|
|
216
|
+
* characters and anything long enough to be a sentence. The value is ERE-escaped by the pattern
|
|
217
|
+
* builder, so no metacharacter it admits can change the pattern.
|
|
218
|
+
*/
|
|
219
|
+
function isUsableProjectCode(code) {
|
|
220
|
+
return /^[A-Za-z][A-Za-z0-9_.-]{0,31}$/.test(code);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The project's configured `project_code`, or null when it is absent, non-string or unusable
|
|
224
|
+
* (`isUsableProjectCode`). An unusable value reads as ABSENT: the coded id keeps its LITERAL
|
|
225
|
+
* spelling, which is never WIDER than the pre-#5271 pattern, since a literal id can only match a
|
|
226
|
+
* subject naming that exact id.
|
|
227
|
+
*/
|
|
228
|
+
function configuredProjectCode(projectDir) {
|
|
229
|
+
const { present, value } = readScopedConfigValue(projectDir, ['project_code']);
|
|
230
|
+
if (!present || typeof value !== 'string')
|
|
231
|
+
return null;
|
|
232
|
+
return isUsableProjectCode(value) ? value : null;
|
|
129
233
|
}
|
|
130
234
|
// ─── Git plumbing ─────────────────────────────────────────────────────────────
|
|
131
235
|
class ScopeUnreadable extends Error {
|
|
@@ -189,6 +293,23 @@ function isSafeDateArgument(candidate) {
|
|
|
189
293
|
function isAncestor(git, sha, of) {
|
|
190
294
|
return gitOrNo(git, ['merge-base', '--is-ancestor', sha, of]) !== null;
|
|
191
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Whether commit `sha` is an ancestor of commit `of` (#5275): ancestry, never log position or commit
|
|
298
|
+
* date, so a history merged from worktree branches is judged by what it descends from. `null` means
|
|
299
|
+
* git could not answer (missing, hung, a bad object): the caller must not read that as "no".
|
|
300
|
+
*/
|
|
301
|
+
function commitIsAncestor(projectDir, sha, of, execGit) {
|
|
302
|
+
if (!isSafeRefArgument(sha) || !isSafeRefArgument(of))
|
|
303
|
+
return null;
|
|
304
|
+
try {
|
|
305
|
+
return isAncestor(makeGit(projectDir, execGit ?? shell_command_projection_cjs_1.execGit), sha, of);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (error instanceof ScopeUnreadable)
|
|
309
|
+
return null;
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
192
313
|
// ─── The resolver ─────────────────────────────────────────────────────────────
|
|
193
314
|
function emptyScope(unit) {
|
|
194
315
|
return {
|
|
@@ -210,7 +331,7 @@ function resolveEvaluationScope(projectDir, unit, options = {}) {
|
|
|
210
331
|
if (unit.kind === 'phase')
|
|
211
332
|
resolvePhase(projectDir, unit, scope, git, ref, options, repoRoot);
|
|
212
333
|
else
|
|
213
|
-
resolveBySubject(unit, scope, git, ref, options, repoRoot);
|
|
334
|
+
resolveBySubject(projectDir, unit, scope, git, ref, options, repoRoot);
|
|
214
335
|
return scope;
|
|
215
336
|
}
|
|
216
337
|
catch (error) {
|
|
@@ -388,7 +509,7 @@ function resolvePhase(projectDir, unit, scope, git, ref, options, repoRoot) {
|
|
|
388
509
|
scope.commits = parseLog(git(['log', `${scope.rangeBase}..${ref}`, logFormat(options.includeBody === true), '-n', String(max)]), options.includeBody === true);
|
|
389
510
|
finalizeFiles(scope, repoRoot, rangeFiles(git, scope.rangeBase, ref));
|
|
390
511
|
}
|
|
391
|
-
function resolveBySubject(unit, scope, git, ref, options, repoRoot) {
|
|
512
|
+
function resolveBySubject(projectDir, unit, scope, git, ref, options, repoRoot) {
|
|
392
513
|
const withBody = options.includeBody === true;
|
|
393
514
|
// Refuse rather than truncate: a silently dropped positive pathspec narrows the match.
|
|
394
515
|
const pathspecs = options.pathspecs ?? [];
|
|
@@ -412,7 +533,7 @@ function resolveBySubject(unit, scope, git, ref, options, repoRoot) {
|
|
|
412
533
|
const grepArgs = [];
|
|
413
534
|
let anchored = null;
|
|
414
535
|
if (unit.kind === 'plan') {
|
|
415
|
-
const pattern = planSubjectPattern(unit.planId);
|
|
536
|
+
const pattern = planSubjectPattern(unit.planId, configuredProjectCode(projectDir));
|
|
416
537
|
if (pattern === null)
|
|
417
538
|
throw new ScopeUnreadable('invalid-plan-id');
|
|
418
539
|
grepArgs.push('--extended-regexp', `--grep=${pattern}`);
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
* This is a leaf pure module: no fs, no child_process, no config — the subprocess
|
|
27
27
|
* seam is injected so the evaluator is trivially testable without spawning.
|
|
28
28
|
*/
|
|
29
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
30
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
|
+
};
|
|
32
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
29
33
|
// ─── Public constants ─────────────────────────────────────────────────────────
|
|
30
34
|
/** Default command timeout for `command-exit-zero` (30s). Matches execTool default. */
|
|
31
35
|
const COMMAND_EXIT_ZERO_DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -116,15 +120,31 @@ function evaluateArtifactFrontmatterEquals(predicate, ctx, deps) {
|
|
|
116
120
|
throw new Error('artifact-frontmatter-equals predicate requires an "equals" key');
|
|
117
121
|
}
|
|
118
122
|
const targetDir = isNonEmptyString(ctx.phaseDir) ? ctx.phaseDir : ctx.cwd;
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
// Plain `.sort()` (no locale) so the outcome never depends on directory listing order (#5313).
|
|
124
|
+
// Set: finders return realpaths, so a symlink to the same file is one artifact, not two.
|
|
125
|
+
const found = deps.findPhaseArtifact(targetDir, artifactSuffix);
|
|
126
|
+
// A pre-#5313 dep returned `string | null`; a silent null -> "not found" block or per-character spread would mislead.
|
|
127
|
+
if (!Array.isArray(found)) {
|
|
128
|
+
throw new Error('predicate deps "findPhaseArtifact" must return an array of paths');
|
|
129
|
+
}
|
|
130
|
+
const candidates = [...new Set(found)].sort();
|
|
131
|
+
if (candidates.length === 0) {
|
|
121
132
|
return {
|
|
122
133
|
block: true,
|
|
123
134
|
message: `Artifact matching ${artifactSuffix} not found in ${targetDir}`,
|
|
124
135
|
details: { kind: 'artifact-frontmatter-equals', artifactNotFound: true },
|
|
125
136
|
};
|
|
126
137
|
}
|
|
127
|
-
|
|
138
|
+
// #5313: never pick one of several matches (listing order is OS-dependent); a throw would fail open via onError.
|
|
139
|
+
if (candidates.length > 1) {
|
|
140
|
+
const names = candidates.map((c) => node_path_1.default.relative(targetDir, c).split(node_path_1.default.sep).join('/')).join(', ');
|
|
141
|
+
return {
|
|
142
|
+
block: true,
|
|
143
|
+
message: `Multiple artifacts match ${artifactSuffix} in ${targetDir}: ${names}; remove or rename all but one`,
|
|
144
|
+
details: { kind: 'artifact-frontmatter-equals', ambiguous: true, candidates },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const fm = deps.readFrontmatter(candidates[0]);
|
|
128
148
|
const actualValue = fm[field];
|
|
129
149
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
130
150
|
const expectedStr = typeof expectedValue === 'object' ? JSON.stringify(expectedValue) : String(expectedValue);
|
|
@@ -80,31 +80,27 @@ function buildPredicateDeps() {
|
|
|
80
80
|
if (phaseDirStat.kind === 'unreadable')
|
|
81
81
|
return unreadable(phaseDir, phaseDirStat.reason);
|
|
82
82
|
if (phaseDirStat.kind === 'none')
|
|
83
|
-
return
|
|
83
|
+
return [];
|
|
84
84
|
if (artifactSuffix === '.' ||
|
|
85
85
|
artifactSuffix === '..' ||
|
|
86
86
|
artifactSuffix.includes('\0') ||
|
|
87
87
|
node_path_1.default.basename(artifactSuffix) !== artifactSuffix ||
|
|
88
88
|
node_path_1.default.win32.basename(artifactSuffix) !== artifactSuffix) {
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
const directContained = (0, security_cjs_1.tryWithinRoot)(artifactSuffix, phaseDir);
|
|
92
|
-
if (directContained !== null) {
|
|
93
|
-
const direct = regularFileOrNull(directContained);
|
|
94
|
-
if (direct !== null)
|
|
95
|
-
return direct;
|
|
89
|
+
return [];
|
|
96
90
|
}
|
|
91
|
+
// #5313: collect EVERY distinct match; the evaluator fails closed on >1 instead of trusting listing order.
|
|
92
|
+
const found = [];
|
|
97
93
|
const planningContained = (0, security_cjs_1.tryWithinRoot)(node_path_1.default.join('.planning', artifactSuffix), phaseDir);
|
|
98
94
|
if (planningContained !== null) {
|
|
99
95
|
const planning = regularFileOrNull(planningContained);
|
|
100
96
|
if (planning !== null)
|
|
101
|
-
|
|
97
|
+
found.push(planning);
|
|
102
98
|
}
|
|
103
99
|
const listing = (0, gate_evidence_cjs_1.readDirEvidence)(phaseDir);
|
|
104
100
|
if (listing.kind === 'unreadable')
|
|
105
101
|
return unreadable(phaseDir, listing.reason);
|
|
106
102
|
if (listing.kind === 'none')
|
|
107
|
-
return
|
|
103
|
+
return [];
|
|
108
104
|
for (const f of listing.value) {
|
|
109
105
|
if (f.endsWith('-' + artifactSuffix) || f === artifactSuffix) {
|
|
110
106
|
const candidateContained = (0, security_cjs_1.tryWithinRoot)(f, phaseDir);
|
|
@@ -112,10 +108,10 @@ function buildPredicateDeps() {
|
|
|
112
108
|
continue;
|
|
113
109
|
const candidate = regularFileOrNull(candidateContained);
|
|
114
110
|
if (candidate !== null)
|
|
115
|
-
|
|
111
|
+
found.push(candidate);
|
|
116
112
|
}
|
|
117
113
|
}
|
|
118
|
-
return
|
|
114
|
+
return found;
|
|
119
115
|
},
|
|
120
116
|
readFrontmatter(filePath) {
|
|
121
117
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(filePath);
|
|
@@ -17,12 +17,19 @@
|
|
|
17
17
|
* tolerant. A plan id that is not `<phase>-<plan>` (a plan named `.*-PLAN.md`) matches nothing,
|
|
18
18
|
* and a git failure is "no commits" exactly as before.
|
|
19
19
|
*
|
|
20
|
+
* Cross-plan GREEN (#4867): a gap closure is planned as a RED-only probe plan plus the plan that
|
|
21
|
+
* `depends_on` it and carries the fix. A `type: tdd` plan with its own RED but no GREEN is credited
|
|
22
|
+
* with a `feat(...)` commit under any plan that lists it in `depends_on`, directly or through a
|
|
23
|
+
* chain; the row carries `greenFrom` and the report names the supplier. Its own RED is still
|
|
24
|
+
* required, and a plan nothing depends on is scored on its own commits alone.
|
|
25
|
+
*
|
|
20
26
|
* Argv after the verb: `<phase>` (a number; an unresolvable phase reports zero plans).
|
|
21
27
|
*/
|
|
22
28
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
29
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
30
|
};
|
|
25
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.TDD_ROW_REASON = void 0;
|
|
26
33
|
exports.evaluateTddReviewCheckpoint = evaluateTddReviewCheckpoint;
|
|
27
34
|
const node_path_1 = __importDefault(require("node:path"));
|
|
28
35
|
const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
|
|
@@ -31,7 +38,10 @@ const gate_evidence_cjs_1 = require("./gate-evidence.cjs");
|
|
|
31
38
|
const gate_evaluation_scope_cjs_1 = require("./gate-evaluation-scope.cjs");
|
|
32
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
40
|
const frontmatterMod = require("./frontmatter.cjs");
|
|
34
|
-
const { frontmatterKeyHasValue } = frontmatterMod;
|
|
41
|
+
const { frontmatterKeyHasValue, extractFrontmatter } = frontmatterMod;
|
|
42
|
+
exports.TDD_ROW_REASON = {
|
|
43
|
+
GREEN_BEFORE_RED: 'green_before_red',
|
|
44
|
+
};
|
|
35
45
|
/**
|
|
36
46
|
* True when the plan's frontmatter declares `type: tdd` (CRLF included, #2449). The block is the
|
|
37
47
|
* one fence owner's, read as RAW text — not through the YAML parser — so a block the parser refuses
|
|
@@ -42,20 +52,69 @@ const { frontmatterKeyHasValue } = frontmatterMod;
|
|
|
42
52
|
function isTddPlan(content) {
|
|
43
53
|
return frontmatterKeyHasValue(content, 'type', 'tdd');
|
|
44
54
|
}
|
|
55
|
+
/** A `depends_on` token as a plan id: `01.1-20`, `01.1-20-PLAN` and `01.1-20-PLAN.md` all name plan `01.1-20`. */
|
|
56
|
+
function toPlanId(token) {
|
|
57
|
+
return token.trim().replace(/-PLAN(?:\.md)?$/i, '');
|
|
58
|
+
}
|
|
45
59
|
/**
|
|
46
|
-
* The
|
|
47
|
-
*
|
|
48
|
-
*
|
|
60
|
+
* The plan ids a plan lists in `depends_on` (#4867). A plan whose frontmatter does not parse, or has
|
|
61
|
+
* no `depends_on`, depends on nothing: the pairing below only ever ADDS credit, so an unreadable
|
|
62
|
+
* link can never turn a failing plan into a passing one.
|
|
49
63
|
*/
|
|
50
|
-
function
|
|
64
|
+
function dependsOnOf(content) {
|
|
65
|
+
// `extractFrontmatter` does not throw: an unparseable block is an empty result, so `depends_on` is absent.
|
|
66
|
+
const value = extractFrontmatter(content)['depends_on'];
|
|
67
|
+
const tokens = Array.isArray(value) ? value.map(String) : typeof value === 'string' ? value.split(',') : [];
|
|
68
|
+
return tokens.map(toPlanId).filter((id) => id !== '');
|
|
69
|
+
}
|
|
70
|
+
/** Every plan that depends on `planId`, directly or through a chain (#4867); a cycle is walked once. */
|
|
71
|
+
function transitiveDependents(planId, dependents) {
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
const queue = [planId];
|
|
74
|
+
while (queue.length > 0) {
|
|
75
|
+
for (const next of dependents.get(queue.shift()) ?? []) {
|
|
76
|
+
if (next !== planId && !seen.has(next)) {
|
|
77
|
+
seen.add(next);
|
|
78
|
+
queue.push(next);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return [...seen].sort();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The plan's own commits (`test`, `feat`, `refactor`, …) — those that touched at least one path
|
|
86
|
+
* (`pathspecs: ['.']`, as the old `git log -- .` lookup did) — with the sha the ordering check needs.
|
|
87
|
+
* An unresolvable scope (git unavailable, an id that is not `<phase>-<plan>`) is "no commits".
|
|
88
|
+
*/
|
|
89
|
+
function planCommits(projectDir, planId) {
|
|
51
90
|
const scope = (0, gate_evaluation_scope_cjs_1.resolveEvaluationScope)(projectDir, { kind: 'plan', planId }, { pathspecs: ['.'], commitsOnly: true });
|
|
52
|
-
const
|
|
91
|
+
const commits = [];
|
|
53
92
|
for (const commit of scope.commits) {
|
|
54
93
|
const kind = /^([a-z]+)\(/.exec(commit.subject)?.[1];
|
|
55
94
|
if (kind)
|
|
56
|
-
|
|
95
|
+
commits.push({ sha: commit.sha, kind });
|
|
96
|
+
}
|
|
97
|
+
return commits;
|
|
98
|
+
}
|
|
99
|
+
const shasOfKind = (commits, kind) => commits.filter((c) => c.kind === kind).map((c) => c.sha);
|
|
100
|
+
/**
|
|
101
|
+
* Whether some GREEN descends from some RED (#5275). Decided by ancestry only — never log position or
|
|
102
|
+
* commit date — so a history merged from worktree branches is judged by what each commit descends from.
|
|
103
|
+
* `true` as soon as one pair is in order; `null` when git could not answer for a pair and none was
|
|
104
|
+
* in order (an environment fault is not a violation, so the caller keeps the credit); else `false`.
|
|
105
|
+
*/
|
|
106
|
+
function greenFollowsRed(projectDir, reds, greens) {
|
|
107
|
+
let unanswered = false;
|
|
108
|
+
for (const red of reds) {
|
|
109
|
+
for (const green of greens) {
|
|
110
|
+
const redIsAncestor = (0, gate_evaluation_scope_cjs_1.commitIsAncestor)(projectDir, red, green);
|
|
111
|
+
if (redIsAncestor === true)
|
|
112
|
+
return true;
|
|
113
|
+
if (redIsAncestor === null)
|
|
114
|
+
unanswered = true;
|
|
115
|
+
}
|
|
57
116
|
}
|
|
58
|
-
return
|
|
117
|
+
return unanswered ? null : false;
|
|
59
118
|
}
|
|
60
119
|
/**
|
|
61
120
|
* The verdict when the review could not read a plan or the phase directory. Advisory policy is
|
|
@@ -91,6 +150,9 @@ function evaluateTddReviewCheckpoint(input) {
|
|
|
91
150
|
const phaseDir = located.kind === 'found' ? located.value : '';
|
|
92
151
|
// Find all PLAN.md files with type: tdd in frontmatter
|
|
93
152
|
const tddPlanFiles = [];
|
|
153
|
+
// #4867: plan id -> the plans that list it in `depends_on`, over EVERY plan of the phase (a
|
|
154
|
+
// dependent GREEN plan is usually `type: execute`, not `tdd`).
|
|
155
|
+
const dependents = new Map();
|
|
94
156
|
const unreadable = [];
|
|
95
157
|
if (phaseDir) {
|
|
96
158
|
const entries = (0, gate_evidence_cjs_1.readDirEvidence)(phaseDir);
|
|
@@ -110,8 +172,13 @@ function evaluateTddReviewCheckpoint(input) {
|
|
|
110
172
|
const plan = (0, gate_evidence_cjs_1.readTextEvidence)(planPath);
|
|
111
173
|
if (plan.kind === 'unreadable')
|
|
112
174
|
unreadable.push({ source: planPath, reason: plan.reason });
|
|
113
|
-
else if (plan.kind === 'found'
|
|
114
|
-
|
|
175
|
+
else if (plan.kind === 'found') {
|
|
176
|
+
if (isTddPlan(plan.value))
|
|
177
|
+
tddPlanFiles.push(planPath);
|
|
178
|
+
const planId = node_path_1.default.basename(planPath, '-PLAN.md');
|
|
179
|
+
for (const dep of dependsOnOf(plan.value))
|
|
180
|
+
dependents.set(dep, [...(dependents.get(dep) ?? []), planId]);
|
|
181
|
+
}
|
|
115
182
|
}
|
|
116
183
|
}
|
|
117
184
|
}
|
|
@@ -134,17 +201,41 @@ function evaluateTddReviewCheckpoint(input) {
|
|
|
134
201
|
const rows = [];
|
|
135
202
|
for (const planPath of tddPlanFiles) {
|
|
136
203
|
const planId = node_path_1.default.basename(planPath, '-PLAN.md');
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const refactor =
|
|
204
|
+
const commits = planCommits(projectDir, planId);
|
|
205
|
+
const reds = shasOfKind(commits, 'test');
|
|
206
|
+
const red = reds.length > 0;
|
|
207
|
+
const refactor = commits.some((c) => c.kind === 'refactor');
|
|
208
|
+
// #5275: with a RED present, a GREEN counts only when it descends from a RED. Without one there is
|
|
209
|
+
// nothing to order against and the plan is already missing RED, so a GREEN commit stays credited.
|
|
210
|
+
const inOrder = (greens) => greens.length > 0 && (!red || greenFollowsRed(projectDir, reds, greens) !== false);
|
|
211
|
+
const ownGreens = shasOfKind(commits, 'feat');
|
|
212
|
+
const ownOk = inOrder(ownGreens);
|
|
213
|
+
// #4867: a RED-only probe plan is satisfied by the GREEN of a plan that (transitively) declares
|
|
214
|
+
// `depends_on` it. Only a real `feat(...)` commit under such a plan counts, and it must follow the
|
|
215
|
+
// RED too (#5275); the plan's own RED is still required, and a plan nothing depends on is scored
|
|
216
|
+
// on its own commits alone.
|
|
217
|
+
const dependentGreens = ownOk
|
|
218
|
+
? []
|
|
219
|
+
: transitiveDependents(planId, dependents).map((id) => ({ id, greens: shasOfKind(planCommits(projectDir, id), 'feat') }));
|
|
220
|
+
const greenFrom = dependentGreens.filter((d) => inOrder(d.greens)).map((d) => d.id);
|
|
221
|
+
const green = ownOk || greenFrom.length > 0;
|
|
222
|
+
const anyGreenCommit = ownGreens.length > 0 || dependentGreens.some((d) => d.greens.length > 0);
|
|
141
223
|
const missing = [];
|
|
142
224
|
if (!red)
|
|
143
225
|
missing.push('RED');
|
|
144
|
-
if (!
|
|
226
|
+
if (!anyGreenCommit)
|
|
145
227
|
missing.push('GREEN');
|
|
146
|
-
const status = missing.length === 0 ? 'Pass' : 'FAIL';
|
|
147
|
-
|
|
228
|
+
const status = missing.length === 0 && green ? 'Pass' : 'FAIL';
|
|
229
|
+
// Both commit kinds exist but no GREEN follows a RED: its own reason. A row that fails for a
|
|
230
|
+
// missing commit keeps the exact shape it always had (`missing` already says why).
|
|
231
|
+
const reason = status === 'FAIL' && missing.length === 0
|
|
232
|
+
? exports.TDD_ROW_REASON.GREEN_BEFORE_RED
|
|
233
|
+
: undefined;
|
|
234
|
+
rows.push({
|
|
235
|
+
planId, red, green, refactor, status, missing,
|
|
236
|
+
...(reason ? { reason } : {}),
|
|
237
|
+
...(greenFrom.length > 0 ? { greenFrom } : {}),
|
|
238
|
+
});
|
|
148
239
|
}
|
|
149
240
|
const violations = rows.filter(r => r.status === 'FAIL').length;
|
|
150
241
|
// Build review table
|
|
@@ -160,10 +251,15 @@ function evaluateTddReviewCheckpoint(input) {
|
|
|
160
251
|
tableDivider,
|
|
161
252
|
...tableRows,
|
|
162
253
|
].join('\n');
|
|
254
|
+
for (const r of rows.filter(row => row.greenFrom)) {
|
|
255
|
+
table += `\n\nPlan ${r.planId} GREEN is supplied by dependent plan ${(r.greenFrom ?? []).join(', ')}.`;
|
|
256
|
+
}
|
|
163
257
|
if (violations > 0) {
|
|
164
258
|
table += '\n\n⚠ Gate violations are advisory — review before advancing.';
|
|
165
259
|
for (const r of rows.filter(row => row.status === 'FAIL')) {
|
|
166
|
-
table +=
|
|
260
|
+
table += r.reason === exports.TDD_ROW_REASON.GREEN_BEFORE_RED
|
|
261
|
+
? `\n Plan ${r.planId} GREEN precedes RED: no feat(...) commit descends from a test(...) commit.`
|
|
262
|
+
: `\n Plan ${r.planId} missing: ${r.missing.join(', ')} gate commit(s).`;
|
|
167
263
|
table += `\n Expected commit pattern: test(${r.planId}): ... → feat(${r.planId}): ...`;
|
|
168
264
|
}
|
|
169
265
|
}
|