@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
|
@@ -30,7 +30,7 @@ const modelProfiles = require("./model-profiles.cjs");
|
|
|
30
30
|
const { VALID_PROFILES, getAgentToModelMapForProfile, formatAgentToModelMapAsTable } = modelProfiles;
|
|
31
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
32
|
const configSchema = require("./config-schema.cjs");
|
|
33
|
-
const { VALID_CONFIG_KEYS, isValidConfigKey, getCapabilityConfigSchema } = configSchema;
|
|
33
|
+
const { VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS, isValidConfigKey, getCapabilityConfigSchema } = configSchema;
|
|
34
34
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
35
35
|
const review_reviewer_selection_cjs_1 = require("./review-reviewer-selection.cjs");
|
|
36
36
|
const configuration_cjs_1 = require("./configuration.cjs");
|
|
@@ -66,6 +66,8 @@ const SHIP_PR_BODY_TEMPLATE_TOKENS = new Set([
|
|
|
66
66
|
'padded_phase',
|
|
67
67
|
]);
|
|
68
68
|
const SHIP_PR_BODY_SOURCE_RE = /^(ROADMAP|PLAN|SUMMARY|VERIFICATION|STATE|REQUIREMENTS|CONTEXT)\.md\s+##\s+[^\r\n#][^\r\n]*$/;
|
|
69
|
+
/** Allowed values of `git.repo_tagging` (#5706): `single` is the default. */
|
|
70
|
+
const VALID_REPO_TAGGING_MODES = Object.freeze(['single', 'per_repo']);
|
|
69
71
|
// ADR-612 PR-5: configuration accepts every convention the runtime can read.
|
|
70
72
|
// Keep this distinct from roadmap-upgrade's supported target set: sequential
|
|
71
73
|
// is valid project configuration but is not a migration destination.
|
|
@@ -87,6 +89,9 @@ const SCHEMA_DEFAULTS = {
|
|
|
87
89
|
'planner.stall_detect_interval_minutes': 5,
|
|
88
90
|
'planner.stall_threshold_minutes': 10,
|
|
89
91
|
'git.create_tag': true,
|
|
92
|
+
// #5706 (epic #3628): per-repository milestone tagging mode. `single` is
|
|
93
|
+
// exactly the pre-existing behavior; `per_repo` is opt-in.
|
|
94
|
+
'git.repo_tagging': 'single',
|
|
90
95
|
// #1689: per-plan agent_hint executor routing — default-on. A no-op for plans
|
|
91
96
|
// without an agent_hint field, so existing dispatch is byte-identical.
|
|
92
97
|
'workflow.agent_hint_routing': true,
|
|
@@ -191,6 +196,18 @@ function validateKnownConfigKeyPath(keyPath) {
|
|
|
191
196
|
error(`Unknown config key: ${keyPath}. Did you mean ${suggested}?`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
192
197
|
}
|
|
193
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* The key gate `config-set` and `config-new-project` share (#5268): a known
|
|
201
|
+
* misspelling gets its suggestion, and anything else outside the schema is
|
|
202
|
+
* refused with the valid-key list. One function, so the two verbs cannot
|
|
203
|
+
* disagree about which keys they accept or about the error text.
|
|
204
|
+
*/
|
|
205
|
+
function assertKnownConfigKey(cwd, kp) {
|
|
206
|
+
validateKnownConfigKeyPath(kp);
|
|
207
|
+
if (!isValidConfigKey(kp, cwd)) {
|
|
208
|
+
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>, phase_commit_docs.<phase-id>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
194
211
|
/**
|
|
195
212
|
* Is `value` an acceptable `git.protected_branches` list (#3552)?
|
|
196
213
|
*
|
|
@@ -271,9 +288,11 @@ function validateShipPrBodySections(value) {
|
|
|
271
288
|
}
|
|
272
289
|
});
|
|
273
290
|
}
|
|
274
|
-
// ─── Core config operations ───────────────────────────────────────────────────
|
|
275
291
|
/**
|
|
276
|
-
*
|
|
292
|
+
* Plan a fully-materialized config object for a new project. Writes nothing:
|
|
293
|
+
* the user defaults rewrite comes back as data, for the caller to persist once
|
|
294
|
+
* it has decided to write at all (#5268: a `--dry-run` never does, and a
|
|
295
|
+
* refused build never gets that far).
|
|
277
296
|
*
|
|
278
297
|
* Merges (increasing priority):
|
|
279
298
|
* 1. Hardcoded defaults — every key that loadConfig() resolves, plus mode/granularity
|
|
@@ -284,11 +303,10 @@ function validateShipPrBodySections(value) {
|
|
|
284
303
|
* Uses the canonical `git` namespace for branching keys (consistent with VALID_CONFIG_KEYS
|
|
285
304
|
* and the settings workflow). loadConfig() handles both flat and nested formats, so this
|
|
286
305
|
* is backward-compatible with existing projects that have flat keys.
|
|
287
|
-
*
|
|
288
|
-
* Returns a plain object — does NOT write any files.
|
|
289
306
|
*/
|
|
290
|
-
function
|
|
307
|
+
function planNewProjectConfig(userChoices) {
|
|
291
308
|
const choices = userChoices || {};
|
|
309
|
+
let defaultsRewrite = null;
|
|
292
310
|
// #4976: the GSD-owned store resolves exactly as the config loader resolves
|
|
293
311
|
// it (`GSD_HOME || homedir()`), so the defaults.json seeding this project is
|
|
294
312
|
// the one the loader and its #3532 shadow warning read in the same run.
|
|
@@ -319,10 +337,9 @@ function buildNewProjectConfig(userChoices) {
|
|
|
319
337
|
const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
|
|
320
338
|
userDefaults['granularity'] = depthToGranularity[userDefaults['depth']] || userDefaults['depth'];
|
|
321
339
|
delete userDefaults['depth'];
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
catch { /* intentionally empty */ }
|
|
340
|
+
// The migrated value is used for this build whether or not the
|
|
341
|
+
// rewrite is ever persisted.
|
|
342
|
+
defaultsRewrite = { path: globalDefaultsPath, contents: JSON.stringify(userDefaults, null, 2) };
|
|
326
343
|
}
|
|
327
344
|
}
|
|
328
345
|
}
|
|
@@ -344,6 +361,7 @@ function buildNewProjectConfig(userChoices) {
|
|
|
344
361
|
git: {
|
|
345
362
|
branching_strategy: CONFIG_DEFAULTS.branching_strategy,
|
|
346
363
|
create_tag: true,
|
|
364
|
+
repo_tagging: 'single',
|
|
347
365
|
phase_branch_template: CONFIG_DEFAULTS.phase_branch_template,
|
|
348
366
|
milestone_branch_template: CONFIG_DEFAULTS.milestone_branch_template,
|
|
349
367
|
quick_branch_template: CONFIG_DEFAULTS.quick_branch_template,
|
|
@@ -444,7 +462,134 @@ function buildNewProjectConfig(userChoices) {
|
|
|
444
462
|
},
|
|
445
463
|
};
|
|
446
464
|
validateShipPrBodySections(config['ship']['pr_body_sections']);
|
|
447
|
-
return config;
|
|
465
|
+
return { config, defaultsRewrite };
|
|
466
|
+
}
|
|
467
|
+
/** Persist a plan's user defaults rewrite, best-effort as it always was. */
|
|
468
|
+
function persistDefaultsRewrite(plan) {
|
|
469
|
+
if (plan.defaultsRewrite === null)
|
|
470
|
+
return;
|
|
471
|
+
try {
|
|
472
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(plan.defaultsRewrite.path, plan.defaultsRewrite.contents);
|
|
473
|
+
}
|
|
474
|
+
catch { /* intentionally empty */ }
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Build a new project's config and persist the user defaults rewrite its plan
|
|
478
|
+
* carries. For callers that write the config unconditionally; one that may not
|
|
479
|
+
* (a `--dry-run`) plans with `planNewProjectConfig` and persists only on write.
|
|
480
|
+
*/
|
|
481
|
+
function buildNewProjectConfig(userChoices) {
|
|
482
|
+
const plan = planNewProjectConfig(userChoices);
|
|
483
|
+
persistDefaultsRewrite(plan);
|
|
484
|
+
return plan.config;
|
|
485
|
+
}
|
|
486
|
+
function isPlainObject(value) {
|
|
487
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Does any schema key live under `keyPath`? True for a section (`workflow`),
|
|
491
|
+
* for the top level of a dynamic key family (`agent_skills`, whose keys are
|
|
492
|
+
* `agent_skills.<agent-type>`), and for the settings that are also parents
|
|
493
|
+
* (`review.max_prompt_tokens_per_reviewer` and its per-reviewer keys;
|
|
494
|
+
* `agent_tools`, a setting whose entries are the `agent_tools.<selector>`
|
|
495
|
+
* family).
|
|
496
|
+
*/
|
|
497
|
+
function hasChildConfigKeys(cwd, keyPath) {
|
|
498
|
+
const childPrefix = `${keyPath}.`;
|
|
499
|
+
for (const known of VALID_CONFIG_KEYS) {
|
|
500
|
+
if (known.startsWith(childPrefix))
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
if (DYNAMIC_KEY_PATTERNS.some((family) => family['topLevel'] === keyPath))
|
|
504
|
+
return true;
|
|
505
|
+
return Object.keys(getCapabilityConfigSchema(cwd)).some((known) => known.startsWith(childPrefix));
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Is `keyPath` a section a choice may nest settings under? The same test as
|
|
509
|
+
* `hasChildConfigKeys`. A dynamic family's deeper levels
|
|
510
|
+
* (`model_profile_overrides.<runtime>`) are not recognised; an empty object
|
|
511
|
+
* there is refused as an unknown key, and it would set nothing anyway.
|
|
512
|
+
*/
|
|
513
|
+
function isConfigSection(cwd, keyPath) {
|
|
514
|
+
return hasChildConfigKeys(cwd, keyPath);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Hold `config-new-project` choices to the rules `config-set` applies (#5268).
|
|
518
|
+
*
|
|
519
|
+
* Choices arrive nested (`{"workflow":{"research":true}}`) while the schema
|
|
520
|
+
* and `config-set` address dotted leaf paths (`workflow.research`), so the
|
|
521
|
+
* walk descends through section objects and checks each leaf under its dotted
|
|
522
|
+
* path: the key through `assertKnownConfigKey`, the value through
|
|
523
|
+
* `validateConfigValue`. An object whose own path is a settable key with
|
|
524
|
+
* nothing under it (`model_overrides.<agent>`) is a leaf, as it is for
|
|
525
|
+
* `config-set`.
|
|
526
|
+
*
|
|
527
|
+
* Returns the checked choices as a new object and leaves `choices` as given:
|
|
528
|
+
* each value is the one `config-set` would persist (a normalizing rule's
|
|
529
|
+
* result where one applies). An empty object is a section with nothing set:
|
|
530
|
+
* refused as an unknown key unless its name is a real section, and left out
|
|
531
|
+
* of the result either way.
|
|
532
|
+
*/
|
|
533
|
+
function validateNewProjectChoices(cwd, choices, prefix = '') {
|
|
534
|
+
const checked = {};
|
|
535
|
+
for (const key of Object.keys(choices)) {
|
|
536
|
+
const kp = prefix ? `${prefix}.${key}` : key;
|
|
537
|
+
// An empty key names no setting. Refused here because an empty section
|
|
538
|
+
// name would otherwise be walked as if its contents sat one level up.
|
|
539
|
+
if (key === '')
|
|
540
|
+
assertKnownConfigKey(cwd, kp);
|
|
541
|
+
// A key is one path segment. A dot inside it spells a deeper path, which
|
|
542
|
+
// would pass the key gate while the merge wrote it as one literal key no
|
|
543
|
+
// reader resolves. The exception is a dot inside an id: one dynamic key
|
|
544
|
+
// family admits both the path cut at the dot and the whole path, so the
|
|
545
|
+
// dot is part of what that family calls a single key (`phase_commit_docs`
|
|
546
|
+
// is a map keyed by phase id, and "2.1" is a phase id).
|
|
547
|
+
const dot = key.indexOf('.');
|
|
548
|
+
if (dot !== -1) {
|
|
549
|
+
const head = prefix ? `${prefix}.${key.slice(0, dot)}` : key.slice(0, dot);
|
|
550
|
+
const dotIsInsideAnId = prefix !== '' && DYNAMIC_KEY_PATTERNS.some((family) => family.test(head) && family.test(kp));
|
|
551
|
+
if (!dotIsInsideAnId) {
|
|
552
|
+
error(`Invalid choice key "${key}" for config-new-project. Nest it instead of using a dotted key, e.g. {"workflow":{"research":true}}.`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const value = choices[key];
|
|
556
|
+
if (isPlainObject(value) && (!isValidConfigKey(kp, cwd) || hasChildConfigKeys(cwd, kp))) {
|
|
557
|
+
// A non-empty section has its name checked through its leaves. An empty
|
|
558
|
+
// one has no leaves, so its name is checked here: config-set refuses
|
|
559
|
+
// `bogus '{}'` as an unknown key, and so does this.
|
|
560
|
+
if (Object.keys(value).length === 0 && !isValidConfigKey(kp, cwd) && !isConfigSection(cwd, kp)) {
|
|
561
|
+
assertKnownConfigKey(cwd, kp);
|
|
562
|
+
}
|
|
563
|
+
const section = validateNewProjectChoices(cwd, value, kp);
|
|
564
|
+
// Empty as given, or emptied by null choices: it sets nothing.
|
|
565
|
+
if (Object.keys(section).length > 0)
|
|
566
|
+
checked[key] = section;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
// A section given a value that is not an object. config-set would call
|
|
570
|
+
// the name unknown, which misdescribes a real section; say what is wrong.
|
|
571
|
+
if (!isValidConfigKey(kp, cwd) && isConfigSection(cwd, kp)) {
|
|
572
|
+
error(`Invalid choice "${kp}" for config-new-project: it is a section, so its value must be an object of settings, e.g. {"workflow":{"research":true}}.`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
573
|
+
}
|
|
574
|
+
assertKnownConfigKey(cwd, kp);
|
|
575
|
+
// `config-set <key> null` clears the key: it skips the typed rules and
|
|
576
|
+
// leaves the key to its default. A null choice is the same request, so it
|
|
577
|
+
// skips them too and is left unchosen, where it used to be written as a
|
|
578
|
+
// literal null that config-set can never store.
|
|
579
|
+
const checkedValue = value === null
|
|
580
|
+
? null
|
|
581
|
+
: validateConfigValue(cwd, kp, value, typeof value === 'string' ? value : JSON.stringify(value));
|
|
582
|
+
// config-set's setter refuses these segments after the key and value
|
|
583
|
+
// rules have run (`_setNestedValue`); same refusal, same place in the order.
|
|
584
|
+
// It also runs before the assignment below, so no such key reaches `checked`.
|
|
585
|
+
if (kp.split('.').some((segment) => segment === '__proto__' || segment === 'prototype' || segment === 'constructor')) {
|
|
586
|
+
error('Invalid config key (prototype pollution guard): ' + kp, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
587
|
+
}
|
|
588
|
+
if (value === null)
|
|
589
|
+
continue;
|
|
590
|
+
checked[key] = checkedValue;
|
|
591
|
+
}
|
|
592
|
+
return checked;
|
|
448
593
|
}
|
|
449
594
|
/**
|
|
450
595
|
* Command: create a fully-materialized .planning/config.json for a new project.
|
|
@@ -453,25 +598,54 @@ function buildNewProjectConfig(userChoices) {
|
|
|
453
598
|
* configured during /gsd:new-project). All remaining keys are filled from
|
|
454
599
|
* hardcoded defaults and optional $GSD_HOME/.gsd/defaults.json.
|
|
455
600
|
*
|
|
456
|
-
*
|
|
601
|
+
* Choices are validated with `config-set`'s key and value rules before
|
|
602
|
+
* anything is written (#5268). With `dryRun`, prints the config that would be
|
|
603
|
+
* created and writes nothing.
|
|
604
|
+
*
|
|
605
|
+
* Idempotent: if config.json already exists, returns { created: false } once
|
|
606
|
+
* the choices have been checked; a refused choice is refused either way.
|
|
457
607
|
*/
|
|
458
|
-
function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
608
|
+
function cmdConfigNewProject(cwd, choicesJson, raw, options = {}) {
|
|
609
|
+
const dryRun = options.dryRun === true;
|
|
459
610
|
const planningBase = planningDir(cwd);
|
|
460
611
|
const configPath = node_path_1.default.join(planningBase, 'config.json');
|
|
461
|
-
// Idempotent: don't overwrite existing config
|
|
462
|
-
if (node_fs_1.default.existsSync(configPath)) {
|
|
463
|
-
output({ created: false, reason: 'already_exists' }, raw, 'exists');
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
612
|
// Parse user choices
|
|
467
613
|
let userChoices = {};
|
|
468
614
|
if (choicesJson && choicesJson.trim() !== '') {
|
|
615
|
+
let parsedChoices;
|
|
469
616
|
try {
|
|
470
|
-
|
|
617
|
+
parsedChoices = JSON.parse(choicesJson);
|
|
471
618
|
}
|
|
472
619
|
catch (err) {
|
|
473
620
|
error('Invalid JSON for config-new-project: ' + err.message);
|
|
474
621
|
}
|
|
622
|
+
// #5268: an array or a string parses as JSON and was then spread into the
|
|
623
|
+
// config as index keys.
|
|
624
|
+
if (!isPlainObject(parsedChoices)) {
|
|
625
|
+
error('Invalid choices for config-new-project: expected a JSON object of config keys.', ERROR_REASON.USAGE);
|
|
626
|
+
}
|
|
627
|
+
userChoices = parsedChoices;
|
|
628
|
+
}
|
|
629
|
+
// #5268: refuse what config-set refuses, before the first write — and
|
|
630
|
+
// before the existing-config check, so a refused choice is reported
|
|
631
|
+
// whether or not the project already has a config.
|
|
632
|
+
const checkedChoices = validateNewProjectChoices(cwd, userChoices);
|
|
633
|
+
// Idempotent: don't overwrite existing config
|
|
634
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
635
|
+
output({ created: false, reason: 'already_exists' }, raw, 'exists');
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const plan = planNewProjectConfig(checkedChoices);
|
|
639
|
+
const config = plan.config;
|
|
640
|
+
if (dryRun) {
|
|
641
|
+
// The preview is CLI output, so secret keys are masked exactly as
|
|
642
|
+
// config-set and config-get mask them. See lib/secrets.cjs.
|
|
643
|
+
const preview = { ...config };
|
|
644
|
+
for (const key of Object.keys(preview)) {
|
|
645
|
+
preview[key] = (0, secrets_cjs_1.maskIfSecret)(key, preview[key]);
|
|
646
|
+
}
|
|
647
|
+
output({ dry_run: true, would_create: true, path: '.planning/config.json', config: preview }, raw, 'would create (dry run)');
|
|
648
|
+
return;
|
|
475
649
|
}
|
|
476
650
|
// Ensure .planning directory exists
|
|
477
651
|
try {
|
|
@@ -480,14 +654,16 @@ function cmdConfigNewProject(cwd, choicesJson, raw) {
|
|
|
480
654
|
catch (err) {
|
|
481
655
|
error('Failed to create .planning directory: ' + err.message);
|
|
482
656
|
}
|
|
483
|
-
const config = buildNewProjectConfig(userChoices);
|
|
484
657
|
try {
|
|
485
658
|
(0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(config, null, 2));
|
|
486
|
-
output({ created: true, path: '.planning/config.json' }, raw, 'created');
|
|
487
659
|
}
|
|
488
660
|
catch (err) {
|
|
489
661
|
error('Failed to write config.json: ' + err.message);
|
|
490
662
|
}
|
|
663
|
+
// The user defaults rewrite follows the config it was planned for: a
|
|
664
|
+
// creation that fails before here leaves the defaults file as it was.
|
|
665
|
+
persistDefaultsRewrite(plan);
|
|
666
|
+
output({ created: true, path: '.planning/config.json' }, raw, 'created');
|
|
491
667
|
}
|
|
492
668
|
/**
|
|
493
669
|
* Ensures the config file exists (creates it if needed).
|
|
@@ -772,10 +948,7 @@ function cmdConfigSet(cwd, keyPath, value, raw, options = {}) {
|
|
|
772
948
|
// TypeScript doesn't always infer never-return narrowing through error(), so we assert.
|
|
773
949
|
const kp = keyPath;
|
|
774
950
|
const val = value;
|
|
775
|
-
|
|
776
|
-
if (!isValidConfigKey(kp, cwd)) {
|
|
777
|
-
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>, phase_commit_docs.<phase-id>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
778
|
-
}
|
|
951
|
+
assertKnownConfigKey(cwd, kp);
|
|
779
952
|
// Parse value (handle booleans, numbers, and JSON arrays/objects)
|
|
780
953
|
let parsedValue = val;
|
|
781
954
|
if (val === 'true')
|
|
@@ -832,6 +1005,60 @@ function cmdConfigSet(cwd, keyPath, value, raw, options = {}) {
|
|
|
832
1005
|
if (kp === 'project_code') {
|
|
833
1006
|
parsedValue = val;
|
|
834
1007
|
}
|
|
1008
|
+
parsedValue = validateConfigValue(cwd, kp, parsedValue, val);
|
|
1009
|
+
if (dryRun) {
|
|
1010
|
+
const preview = previewConfigValue(cwd, kp, parsedValue);
|
|
1011
|
+
if ((0, secrets_cjs_1.isSecretKey)(kp)) {
|
|
1012
|
+
const masked = (0, secrets_cjs_1.maskSecret)(parsedValue);
|
|
1013
|
+
const maskedPrev = preview.previousValue === undefined
|
|
1014
|
+
? undefined
|
|
1015
|
+
: (0, secrets_cjs_1.maskSecret)(preview.previousValue);
|
|
1016
|
+
output({ dry_run: true, would_update: true, key: kp, value: masked, previousValue: maskedPrev, masked: true }, raw, `${kp}=${masked} (dry run)`);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
output({ dry_run: true, would_update: true, key: kp, value: parsedValue, previousValue: preview.previousValue }, raw, `${kp}=${String(parsedValue)} (dry run)`);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const setConfigValueResult = setConfigValue(cwd, kp, parsedValue);
|
|
1023
|
+
// Mask secrets in both JSON and text output. The plaintext is written
|
|
1024
|
+
// to config.json (that's where secrets live on disk); the CLI output
|
|
1025
|
+
// must never echo it. See lib/secrets.cjs.
|
|
1026
|
+
if ((0, secrets_cjs_1.isSecretKey)(kp)) {
|
|
1027
|
+
// parsedValue is unknown at this point; maskSecret accepts MaskableValue
|
|
1028
|
+
const masked = (0, secrets_cjs_1.maskSecret)(parsedValue);
|
|
1029
|
+
const maskedPrev = setConfigValueResult.previousValue === undefined
|
|
1030
|
+
? undefined
|
|
1031
|
+
: (0, secrets_cjs_1.maskSecret)(setConfigValueResult.previousValue);
|
|
1032
|
+
const maskedResult = {
|
|
1033
|
+
...setConfigValueResult,
|
|
1034
|
+
value: masked,
|
|
1035
|
+
previousValue: maskedPrev,
|
|
1036
|
+
masked: true,
|
|
1037
|
+
};
|
|
1038
|
+
output(maskedResult, raw, `${kp}=${masked}`);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
output(setConfigValueResult, raw, `${kp}=${String(parsedValue)}`);
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* The per-key VALUE rules, shared by `config-set` and `config-new-project`
|
|
1045
|
+
* (#5268). They were inline in `cmdConfigSet`, so `config-new-project` had no
|
|
1046
|
+
* way to apply them and wrote values `config-set` refuses for the same key.
|
|
1047
|
+
*
|
|
1048
|
+
* `parsedValue` is the typed value about to be persisted and `val` its text
|
|
1049
|
+
* form, used only in error messages. Returns the value to persist for `kp`:
|
|
1050
|
+
* the input itself, or its normalized form where a rule normalizes
|
|
1051
|
+
* (`review.default_reviewers`). A value the key does not accept has no value
|
|
1052
|
+
* to persist, so it is refused through `error()` instead.
|
|
1053
|
+
*/
|
|
1054
|
+
function validateConfigValue(cwd, kp, parsedValue, val) {
|
|
1055
|
+
// #1581: project_code is an identifier, and config-set stores it as the text
|
|
1056
|
+
// typed. Through config-set this never fires; it holds a JSON choice (#5268)
|
|
1057
|
+
// to the type config-set stores, rather than writing a number readers cast
|
|
1058
|
+
// to string.
|
|
1059
|
+
if (kp === 'project_code' && typeof parsedValue !== 'string') {
|
|
1060
|
+
error(`Invalid project_code '${val}'. Must be a string identifier, e.g. "PROJ".`);
|
|
1061
|
+
}
|
|
835
1062
|
const VALID_CONTEXT_VALUES = ['dev', 'research', 'review'];
|
|
836
1063
|
if (kp === 'context')
|
|
837
1064
|
assertEnumValue(parsedValue, val, VALID_CONTEXT_VALUES, 'context value');
|
|
@@ -912,6 +1139,9 @@ function cmdConfigSet(cwd, keyPath, value, raw, options = {}) {
|
|
|
912
1139
|
error(`Invalid git.create_tag '${val}'. Must be a boolean (true or false).`);
|
|
913
1140
|
}
|
|
914
1141
|
}
|
|
1142
|
+
// #5706 (epic #3628): per-repository milestone tagging mode, enum only.
|
|
1143
|
+
if (kp === 'git.repo_tagging')
|
|
1144
|
+
assertEnumValue(parsedValue, val, VALID_REPO_TAGGING_MODES, 'git.repo_tagging');
|
|
915
1145
|
if (kp === 'git.protected_branches') {
|
|
916
1146
|
if (!isValidProtectedBranches(parsedValue)) {
|
|
917
1147
|
error(`Invalid git.protected_branches '${val}'. Must be a non-empty array of non-empty branch names.`);
|
|
@@ -1076,39 +1306,7 @@ function cmdConfigSet(cwd, keyPath, value, raw, options = {}) {
|
|
|
1076
1306
|
}
|
|
1077
1307
|
}
|
|
1078
1308
|
}
|
|
1079
|
-
|
|
1080
|
-
const preview = previewConfigValue(cwd, kp, parsedValue);
|
|
1081
|
-
if ((0, secrets_cjs_1.isSecretKey)(kp)) {
|
|
1082
|
-
const masked = (0, secrets_cjs_1.maskSecret)(parsedValue);
|
|
1083
|
-
const maskedPrev = preview.previousValue === undefined
|
|
1084
|
-
? undefined
|
|
1085
|
-
: (0, secrets_cjs_1.maskSecret)(preview.previousValue);
|
|
1086
|
-
output({ dry_run: true, would_update: true, key: kp, value: masked, previousValue: maskedPrev, masked: true }, raw, `${kp}=${masked} (dry run)`);
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
output({ dry_run: true, would_update: true, key: kp, value: parsedValue, previousValue: preview.previousValue }, raw, `${kp}=${String(parsedValue)} (dry run)`);
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
const setConfigValueResult = setConfigValue(cwd, kp, parsedValue);
|
|
1093
|
-
// Mask secrets in both JSON and text output. The plaintext is written
|
|
1094
|
-
// to config.json (that's where secrets live on disk); the CLI output
|
|
1095
|
-
// must never echo it. See lib/secrets.cjs.
|
|
1096
|
-
if ((0, secrets_cjs_1.isSecretKey)(kp)) {
|
|
1097
|
-
// parsedValue is unknown at this point; maskSecret accepts MaskableValue
|
|
1098
|
-
const masked = (0, secrets_cjs_1.maskSecret)(parsedValue);
|
|
1099
|
-
const maskedPrev = setConfigValueResult.previousValue === undefined
|
|
1100
|
-
? undefined
|
|
1101
|
-
: (0, secrets_cjs_1.maskSecret)(setConfigValueResult.previousValue);
|
|
1102
|
-
const maskedResult = {
|
|
1103
|
-
...setConfigValueResult,
|
|
1104
|
-
value: masked,
|
|
1105
|
-
previousValue: maskedPrev,
|
|
1106
|
-
masked: true,
|
|
1107
|
-
};
|
|
1108
|
-
output(maskedResult, raw, `${kp}=${masked}`);
|
|
1109
|
-
return;
|
|
1110
|
-
}
|
|
1111
|
-
output(setConfigValueResult, raw, `${kp}=${String(parsedValue)}`);
|
|
1309
|
+
return parsedValue;
|
|
1112
1310
|
}
|
|
1113
1311
|
function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
1114
1312
|
const configPath = node_path_1.default.join(planningDir(cwd), 'config.json');
|
|
@@ -40,6 +40,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
40
40
|
const node_path_1 = __importDefault(require("node:path"));
|
|
41
41
|
// frontmatter-fence.cjs is a leaf module (no imports), so this adds no cycle.
|
|
42
42
|
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
43
|
+
// pattern.cjs is a leaf module (escapeRegex only), so this adds no cycle.
|
|
44
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
43
45
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
46
|
const phaseIdModule = require("./phase-id.cjs");
|
|
45
47
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -521,6 +523,36 @@ function findOrphanSummaries(planFiles, summaryFiles) {
|
|
|
521
523
|
}
|
|
522
524
|
return summaryFiles.filter((s) => !claimed.has(s));
|
|
523
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* #5542: does `summaryFile` pair with ANY of `planFiles`, by the same
|
|
528
|
+
* `summaryCandidates` rule the counts use? The ROADMAP plan-checkbox writers
|
|
529
|
+
* tick a row only for a summary that does — never by id prefix, which let a
|
|
530
|
+
* leftover `47-1` summary survive because a live `47-10` plan starts with `47-1`.
|
|
531
|
+
*/
|
|
532
|
+
function summaryPairsWithAnyPlan(planFiles, summaryFile) {
|
|
533
|
+
return planFiles.some((plan) => countMatchedSummaries([plan], [summaryFile]) > 0);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* #5542: lookahead that ends a plan id inside a ROADMAP checkbox row, so `47-1`
|
|
537
|
+
* never matches the start of `47-10-PLAN.md`. The id ends at `.md` or at any
|
|
538
|
+
* character that is not a word character or a `.`/`-` followed by a digit: a
|
|
539
|
+
* dotted sub-id (`01-01.1`) or a longer numeric id (`05-01-02`) is a distinct
|
|
540
|
+
* plan, not a continuation of `01-01`/`05-01`, while sentence punctuation
|
|
541
|
+
* (`01-01. Setup`) and a slug (`05-01-scaffold`) still end the id.
|
|
542
|
+
*/
|
|
543
|
+
const PLAN_ID_ROW_BOUNDARY_SOURCE = '(?=\\.md|(?!\\w|[.-]\\d))';
|
|
544
|
+
/**
|
|
545
|
+
* #5542: the checkbox-row regex for one plan id. By default it matches an
|
|
546
|
+
* UNCHECKED row (the tick target); `anyState` matches either box state, for
|
|
547
|
+
* presence detection; `anchored` pins the row to a line start (the
|
|
548
|
+
* `phase complete` bullet flip). Capture group 1 is the opening `- [`, group 2
|
|
549
|
+
* the rest after the box, so a caller ticks with `$1x$2`.
|
|
550
|
+
*/
|
|
551
|
+
function planRowPattern(planId, opts = {}) {
|
|
552
|
+
const box = opts.anyState ? '[x ]' : ' ';
|
|
553
|
+
const lead = opts.anchored ? '^(\\s*-\\s*\\[)' : '(-\\s*\\[)';
|
|
554
|
+
return new RegExp(`${lead}${box}(\\]\\s*(?:\\*\\*)?${(0, pattern_cjs_1.escapeRegex)(planId)}${PLAN_ID_ROW_BOUNDARY_SOURCE}(?:\\*\\*)?)`, 'i');
|
|
555
|
+
}
|
|
524
556
|
module.exports = {
|
|
525
557
|
toPosixPath,
|
|
526
558
|
normalizeLineEndings,
|
|
@@ -536,4 +568,6 @@ module.exports = {
|
|
|
536
568
|
countMatchedSummaries,
|
|
537
569
|
findUnsummarizedPlans,
|
|
538
570
|
findOrphanSummaries,
|
|
571
|
+
summaryPairsWithAnyPlan,
|
|
572
|
+
planRowPattern,
|
|
539
573
|
};
|
|
@@ -20,11 +20,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
20
20
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
21
|
};
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.TRACKER_RESOLVE_BUDGET_MS = void 0;
|
|
23
24
|
exports.decisionMentioned = decisionMentioned;
|
|
25
|
+
exports.phaseDirProblem = phaseDirProblem;
|
|
24
26
|
exports.loadPlanContents = loadPlanContents;
|
|
25
27
|
exports.loadSummaryContents = loadSummaryContents;
|
|
26
28
|
exports.loadDecisionExtraction = loadDecisionExtraction;
|
|
27
29
|
exports.extractPlanDesignatedSections = extractPlanDesignatedSections;
|
|
30
|
+
exports.resolveTrackerBodies = resolveTrackerBodies;
|
|
28
31
|
exports.buildPlanMessage = buildPlanMessage;
|
|
29
32
|
exports.buildVerifyMessage = buildVerifyMessage;
|
|
30
33
|
exports.phaseCommitMessages = phaseCommitMessages;
|
|
@@ -45,6 +48,12 @@ const { scanPhasePlans } = planScanMod;
|
|
|
45
48
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
46
49
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
47
50
|
const { SCOPE } = planningScopeMod;
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
|
+
const planDocumentMod = require("./plan-document.cjs");
|
|
53
|
+
const { parsePlanDocument } = planDocumentMod;
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
|
+
const taskContentResolutionMod = require("./task-content-resolution.cjs");
|
|
56
|
+
const { resolveTaskContent, splitTrackerId, loadResolverCapabilities, isResolverFailure, contentTexts, } = taskContentResolutionMod;
|
|
48
57
|
// ─── Decision matching ────────────────────────────────────────────────────────
|
|
49
58
|
function normalizePhrase(text) {
|
|
50
59
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
@@ -70,6 +79,24 @@ function decisionMentioned(haystack, decision) {
|
|
|
70
79
|
return phrase ? normalizePhrase(haystack).includes(phrase) : false;
|
|
71
80
|
}
|
|
72
81
|
// ─── File reading ─────────────────────────────────────────────────────────────
|
|
82
|
+
// #4939: loadPlanContents answers no files for a path that is not there, and that reads
|
|
83
|
+
// as "no plan cites anything" — so a phase-dir argument that names no directory
|
|
84
|
+
// (the phase NUMBER in the phase-dir slot) must be told apart BEFORE the scan.
|
|
85
|
+
// Answers null for a directory, else what is wrong with the path and what to do
|
|
86
|
+
// about it. Like the plan gate's contextKind (#4794), the message names the real
|
|
87
|
+
// stat outcome: a permission or I/O failure (EACCES, ELOOP, EIO) is reported as
|
|
88
|
+
// unreadable, not as a missing directory. Only ENOENT/ENOTDIR mean the path is not there.
|
|
89
|
+
function phaseDirProblem(dirPath) {
|
|
90
|
+
const passDirectory = 'Pass the phase DIRECTORY (e.g. .planning/phases/01-slug), not the phase number.';
|
|
91
|
+
const st = (0, gate_evidence_cjs_1.statEvidence)(dirPath);
|
|
92
|
+
if (st.kind === 'none')
|
|
93
|
+
return { reason: 'phase directory not found', what: 'does not exist', hint: passDirectory, unreadable: false };
|
|
94
|
+
if (st.kind === 'unreadable')
|
|
95
|
+
return { reason: 'phase directory unreadable', what: `could not be read (${st.reason})`, hint: 'Check that the directory is readable.', unreadable: true };
|
|
96
|
+
if (st.value.isDirectory())
|
|
97
|
+
return null;
|
|
98
|
+
return { reason: 'phase path is not a directory', what: st.value.isFile() ? 'is a file, not a directory' : 'is not a directory', hint: passDirectory, unreadable: false };
|
|
99
|
+
}
|
|
73
100
|
/**
|
|
74
101
|
* The contents of the phase files `pick` selects (#5170, ADR-5057 §4). An ABSENT phase directory
|
|
75
102
|
* is "no files" (`found []`); a directory or file that exists but cannot be read is `unreadable` —
|
|
@@ -170,6 +197,72 @@ function extractPlanDesignatedSections(planContent) {
|
|
|
170
197
|
parts.push((0, markdown_sectionizer_cjs_1.extractXmlTagBodies)(cleaned));
|
|
171
198
|
return parts.join('\n\n');
|
|
172
199
|
}
|
|
200
|
+
/** Aggregate wall-clock budget for all resolver spawns in one gate run; registry load and each spawn's timeout are inside it (spawn timeouts are clamped to the remainder), so this is the true bound for resolver work. */
|
|
201
|
+
exports.TRACKER_RESOLVE_BUDGET_MS = 120_000;
|
|
202
|
+
const unreadable = (reason, span) => ({ kind: 'unreadable', reason, span });
|
|
203
|
+
/**
|
|
204
|
+
* Extra scannable text per plan (same index) from tracker-resolved task content; '' for plans without `tracker-id=`.
|
|
205
|
+
* `missingResolver` names the first tracker id with no installed resolver: coverage is monotone (a mention anywhere counts), so
|
|
206
|
+
* that can only cause a false "uncovered" — callers go `unreadable` only when a decision would otherwise be uncovered.
|
|
207
|
+
*/
|
|
208
|
+
function resolveTrackerBodies(projectDir, planContents, deps = {}) {
|
|
209
|
+
let missingResolver = null;
|
|
210
|
+
let capabilities = null;
|
|
211
|
+
const seen = new Map();
|
|
212
|
+
const resolveFn = deps.resolveTaskContentFn ?? resolveTaskContent;
|
|
213
|
+
const bodies = [];
|
|
214
|
+
const now = deps.now ?? (() => performance.now());
|
|
215
|
+
const startedAt = now();
|
|
216
|
+
for (const raw of planContents) {
|
|
217
|
+
const content = stripCommentsAndFences(raw);
|
|
218
|
+
if (!/\btracker-id\s*=/i.test(content)) {
|
|
219
|
+
bodies.push('');
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const parts = [];
|
|
223
|
+
for (const task of parsePlanDocument(content).tasks) {
|
|
224
|
+
const trackerId = task.trackerId;
|
|
225
|
+
if (trackerId === null)
|
|
226
|
+
continue;
|
|
227
|
+
const cached = seen.get(trackerId);
|
|
228
|
+
if (cached) {
|
|
229
|
+
parts.push(...cached);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (capabilities === null) {
|
|
233
|
+
try {
|
|
234
|
+
capabilities = (deps.loadCapabilities ?? loadResolverCapabilities)(projectDir);
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
return unreadable(`capability registry unreadable: ${err instanceof Error ? err.message : String(err)}`, trackerId);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Floor: spawnSync rejects a fractional timeout, and a 0 timeout means unbounded, so a sub-ms remainder is a rejection.
|
|
241
|
+
const remainingMs = Math.floor(exports.TRACKER_RESOLVE_BUDGET_MS - (now() - startedAt));
|
|
242
|
+
if (remainingMs <= 0) {
|
|
243
|
+
return unreadable(`tracker resolution exceeded the ${exports.TRACKER_RESOLVE_BUDGET_MS} ms aggregate budget`, trackerId);
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const result = resolveFn({ trackerId, capabilities, timeoutOverrideMs: remainingMs });
|
|
247
|
+
if (result.kind === 'no-resolver') {
|
|
248
|
+
missingResolver ??= { span: trackerId, reason: `no task-content resolver installed for tracker prefix "${splitTrackerId(trackerId)?.prefix}"` };
|
|
249
|
+
}
|
|
250
|
+
// Per field: an unclosed fence in one field must not swallow the rest.
|
|
251
|
+
const texts = result.kind === 'resolved' ? contentTexts(result.content).map(stripCommentsAndFences) : [];
|
|
252
|
+
seen.set(trackerId, texts);
|
|
253
|
+
parts.push(...texts);
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
if (isResolverFailure(err)) {
|
|
257
|
+
return unreadable(`tracker task content unresolved: ${err.message}`, trackerId);
|
|
258
|
+
}
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
bodies.push(parts.join('\n'));
|
|
263
|
+
}
|
|
264
|
+
return (0, gate_evidence_cjs_1.evidenceFound)({ bodies, missingResolver });
|
|
265
|
+
}
|
|
173
266
|
function buildPlanMessage(uncovered) {
|
|
174
267
|
if (uncovered.length === 0)
|
|
175
268
|
return 'All trackable CONTEXT.md decisions are covered by plans.';
|
|
@@ -34,7 +34,10 @@ const gate_phase_context_cjs_1 = require("./gate-phase-context.cjs");
|
|
|
34
34
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
35
35
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- verification.cjs is an export= CommonJS module
|
|
36
36
|
const verificationMod = require("./verification.cjs");
|
|
37
|
-
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
38
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
39
|
+
const { scopeToPhase } = phaseIdMod;
|
|
40
|
+
const { planningDir, isPhaseSpecFile, findPhaseSpecIn } = planningWorkspace;
|
|
38
41
|
const { defaultPhaseCleanCommitTimesMs } = verificationMod;
|
|
39
42
|
/**
|
|
40
43
|
* Pure comparator: which of `entries` have an effective last-changed time
|
|
@@ -54,7 +57,7 @@ function buildContextDriftMessage(staleArtifacts, phaseArg, projectDir) {
|
|
|
54
57
|
if (staleArtifacts.some((f) => f.endsWith('-PATTERNS.md'))) {
|
|
55
58
|
parts.push(`Regenerate patterns: delete the PATTERNS.md file, then re-run ${(0, runtime_slash_cjs_1.runtimeCommand)(projectDir, 'plan-phase')}.`);
|
|
56
59
|
}
|
|
57
|
-
if (staleArtifacts.some((f) => f.endsWith('-VALIDATION.md') || (f
|
|
60
|
+
if (staleArtifacts.some((f) => f.endsWith('-VALIDATION.md') || isPhaseSpecFile(f))) {
|
|
58
61
|
parts.push('Regenerate or manually reconcile VALIDATION.md / SPEC.md against the current decisions.');
|
|
59
62
|
}
|
|
60
63
|
parts.push('Do not hand-inject the newer decisions into a prompt as a substitute for regenerating — that carries the staleness forward.');
|
|
@@ -98,7 +101,9 @@ function runContextDriftGate(cwd, phaseArg) {
|
|
|
98
101
|
const researchFile = phaseFiles.find((f) => f.endsWith('-RESEARCH.md'));
|
|
99
102
|
const patternsFile = phaseFiles.find((f) => f.endsWith('-PATTERNS.md'));
|
|
100
103
|
const validationFile = phaseFiles.find((f) => f.endsWith('-VALIDATION.md'));
|
|
101
|
-
|
|
104
|
+
// #5629: the shared phase-SPEC predicate (the init payloads' `has_spec` / `spec_path` use the same one).
|
|
105
|
+
// Scoped to this phase's own artifacts, as init scopes it, so a misfiled `02-SPEC.md` agrees in both.
|
|
106
|
+
const specFile = findPhaseSpecIn(scopeToPhase(phaseFiles, node_path_1.default.basename(phaseDir)));
|
|
102
107
|
const upstreamFiles = [researchFile, patternsFile, validationFile, specFile].filter((f) => !!f);
|
|
103
108
|
if (upstreamFiles.length === 0) {
|
|
104
109
|
return skip('no-upstream-artifacts');
|