@opengsd/gsd-core 1.18.0 → 1.20.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-code-fixer.compact.md +7 -7
- package/agents/gsd-code-fixer.md +13 -7
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +179 -30
- 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-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +376 -103
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +32 -0
- package/gsd-core/bin/lib/commands.cjs +27 -17
- 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 +45 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -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 +245 -68
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-profiles.cjs +9 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- 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 +61 -51
- 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-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +629 -38
- package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +3066 -653
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
- package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
- package/gsd-core/bin/lib/roadmap.cjs +383 -123
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
- package/gsd-core/bin/lib/smart-entry.cjs +35 -11
- package/gsd-core/bin/lib/state-contract.cjs +39 -15
- package/gsd-core/bin/lib/state-document.cjs +344 -27
- package/gsd-core/bin/lib/state-transition.cjs +156 -32
- package/gsd-core/bin/lib/state.cjs +527 -186
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +5 -25
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +4 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
- 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/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/necessity-review.md +48 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +2 -2
- 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/audit-fix.md +2 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +64 -7
- 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 +37 -150
- package/gsd-core/workflows/diagnose-issues.md +29 -5
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +6 -10
- 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 +7 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +10 -6
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +14 -14
- package/gsd-core/workflows/pr-branch.md +2 -0
- 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.md +1 -1
- package/gsd-core/workflows/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +70 -154
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +15 -4
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/verify-work.md +4 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-graphify-update.sh +6 -0
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +172 -39
- package/hooks/dist/gsd-statusline.js +37 -3
- package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
- package/hooks/dist/gsd-workflow-guard.js +3 -2
- package/hooks/dist/gsd-worktree-path-guard.js +29 -2
- package/hooks/dist/lib/git-probe.js +16 -0
- 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 +11 -1
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-graphify-update.sh +6 -0
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +37 -3
- package/hooks/gsd-windsurf-pre-write.js +29 -2
- package/hooks/gsd-workflow-guard.js +3 -2
- package/hooks/gsd-worktree-path-guard.js +29 -2
- package/hooks/hooks.json +1 -0
- package/hooks/lib/git-probe.js +16 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +15 -8
- 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 +774 -0
- package/pi/package-agents/gsd-code-reviewer.md +441 -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 +898 -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 +54 -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/build-hooks.js +4 -0
- package/scripts/ci-next-health.cjs +246 -23
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-features.cjs +14 -2
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/mutation-matrix.cjs +23 -1
- package/scripts/npm-audit-baseline.cjs +41 -7
- 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/scripts/sync-next-version.cjs +38 -6
- package/skills/gsd-update/SKILL.md +5 -2
- package/vscode/package.json +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
- package/scripts/diagnose-validate-commit-phases.cjs +0 -348
- package/scripts/xtrace-phase-timing.cjs +0 -205
|
@@ -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.');
|
|
@@ -82,7 +85,7 @@ function runContextDriftGate(cwd, phaseArg) {
|
|
|
82
85
|
}
|
|
83
86
|
// Same phase-directory resolution rule the schema-drift gate uses (#1571, #2528):
|
|
84
87
|
// matchPhaseDirs, never a naive substring test.
|
|
85
|
-
const phaseDir = (0, gate_phase_context_cjs_1.resolvePhaseDirByToken)(phasesDir, phaseArg);
|
|
88
|
+
const phaseDir = (0, gate_phase_context_cjs_1.resolvePhaseDirByToken)(cwd, phasesDir, phaseArg);
|
|
86
89
|
if (!phaseDir) {
|
|
87
90
|
return unresolvable();
|
|
88
91
|
}
|
|
@@ -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');
|
|
@@ -163,7 +163,10 @@ function evaluateDecisionCoveragePlan(input) {
|
|
|
163
163
|
if (phaseDir && planContents.value.length === 0) {
|
|
164
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
165
|
}
|
|
166
|
-
const
|
|
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] : ''));
|
|
167
170
|
const uncovered = [];
|
|
168
171
|
let covered = 0;
|
|
169
172
|
for (const decision of decisions) {
|
|
@@ -172,6 +175,8 @@ function evaluateDecisionCoveragePlan(input) {
|
|
|
172
175
|
else
|
|
173
176
|
uncovered.push({ id: decision.id, text: decision.text, category: decision.category });
|
|
174
177
|
}
|
|
178
|
+
if (uncovered.length > 0 && tracker.value.missingResolver)
|
|
179
|
+
return unreadableDecisionGate(`${tracker.value.missingResolver.span}: ${tracker.value.missingResolver.reason}`);
|
|
175
180
|
const passed = uncovered.length === 0;
|
|
176
181
|
return (0, gate_verdict_cjs_1.gateVerdict)(passed ? 'pass' : 'block', !passed, {
|
|
177
182
|
passed,
|
|
@@ -103,6 +103,9 @@ function evaluateDecisionCoverageVerify(input) {
|
|
|
103
103
|
const planContents = (0, decision_coverage_support_cjs_1.loadPlanContents)(phaseDir);
|
|
104
104
|
if (planContents.kind === 'unreadable')
|
|
105
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}`);
|
|
106
109
|
const summaryParts = (0, decision_coverage_support_cjs_1.loadSummaryContents)(phaseDir);
|
|
107
110
|
if (summaryParts.kind === 'unreadable')
|
|
108
111
|
return unreadableVerify(`${summaryParts.span ?? phaseDir}: ${summaryParts.reason}`);
|
|
@@ -115,7 +118,7 @@ function evaluateDecisionCoverageVerify(input) {
|
|
|
115
118
|
if (modifiedFiles.kind === 'unreadable')
|
|
116
119
|
return unreadableVerify(`${modifiedFiles.span ?? 'files_modified'}: ${modifiedFiles.reason}`);
|
|
117
120
|
const haystack = [
|
|
118
|
-
planContents.value.join('\n\n'),
|
|
121
|
+
planContents.value.map((c, i) => c + (tracker.value.bodies[i] ? '\n' + tracker.value.bodies[i] : '')).join('\n\n'),
|
|
119
122
|
summaryParts.value.join('\n\n'),
|
|
120
123
|
modifiedFiles.value,
|
|
121
124
|
(0, decision_coverage_support_cjs_1.phaseCommitMessages)(projectDir, phaseDir),
|
|
@@ -128,6 +131,8 @@ function evaluateDecisionCoverageVerify(input) {
|
|
|
128
131
|
else
|
|
129
132
|
notHonored.push({ id: decision.id, text: decision.text, category: decision.category });
|
|
130
133
|
}
|
|
134
|
+
if (notHonored.length > 0 && tracker.value.missingResolver)
|
|
135
|
+
return unreadableVerify(`${tracker.value.missingResolver.span}: ${tracker.value.missingResolver.reason}`);
|
|
131
136
|
return (0, gate_verdict_cjs_1.gateVerdict)(notHonored.length === 0 ? 'pass' : 'advisory', false, {
|
|
132
137
|
skipped: false,
|
|
133
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}`);
|
|
@@ -34,10 +34,7 @@ const planningWorkspaceMod = require("./planning-workspace.cjs");
|
|
|
34
34
|
const { planningDir } = planningWorkspaceMod;
|
|
35
35
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
36
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
37
|
-
const { findPhaseInternal } = phaseLocatorMod;
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
39
|
-
const phaseIdMod = require("./phase-id.cjs");
|
|
40
|
-
const { normalizePhaseName, matchPhaseDirs } = phaseIdMod;
|
|
37
|
+
const { phaseDirectoryOptions, findPhaseInternal, resolvePhaseDirectoryLookup, matchPhaseDirsForLookup } = phaseLocatorMod;
|
|
41
38
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
39
|
const roadmapModule = require("./roadmap.cjs");
|
|
43
40
|
const { getRoadmapPhaseWithFallback } = roadmapModule;
|
|
@@ -78,7 +75,7 @@ function unresolvableProbeVerdict(readError) {
|
|
|
78
75
|
function resolvePhaseDir(projectDir, phase) {
|
|
79
76
|
let result;
|
|
80
77
|
try {
|
|
81
|
-
result = findPhaseInternal(projectDir, phase);
|
|
78
|
+
result = findPhaseInternal(projectDir, phase, undefined, phaseDirectoryOptions(projectDir));
|
|
82
79
|
}
|
|
83
80
|
catch (err) {
|
|
84
81
|
return (0, gate_evidence_cjs_1.evidenceFromError)(err, `phase ${phase}`);
|
|
@@ -109,11 +106,13 @@ function resolvePhaseDir(projectDir, phase) {
|
|
|
109
106
|
* `resolvePhaseDir` would widen what those gates inspect, so the two stay apart and each states its
|
|
110
107
|
* scope. Throws when `phasesDir` cannot be listed; both callers sit inside their non-blocking catch.
|
|
111
108
|
*/
|
|
112
|
-
function resolvePhaseDirByToken(phasesDir, phaseArg) {
|
|
113
|
-
const
|
|
109
|
+
function resolvePhaseDirByToken(cwd, phasesDir, phaseArg, directoryContext = phaseDirectoryOptions(cwd)) {
|
|
110
|
+
const lookup = resolvePhaseDirectoryLookup(cwd, phaseArg, undefined, directoryContext);
|
|
114
111
|
const dirEntries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
115
112
|
const dirNames = dirEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
116
|
-
|
|
113
|
+
// #4304: both drift gates resolve a directory in the current checkout.
|
|
114
|
+
// Bracket is the only opt-in grammar; all other conventions remain legacy.
|
|
115
|
+
const matched = matchPhaseDirsForLookup(dirNames, lookup, undefined, directoryContext).matches[0];
|
|
117
116
|
if (matched)
|
|
118
117
|
return node_path_1.default.join(phasesDir, matched);
|
|
119
118
|
const contained = (0, security_cjs_1.tryWithinRoot)(phaseArg, phasesDir);
|
|
@@ -68,7 +68,7 @@ function runSchemaDriftGate(cwd, phaseArg, skipFlag) {
|
|
|
68
68
|
// matching "11-expansion"), making the drift gate inspect the wrong phase.
|
|
69
69
|
// This shares the one selection rule with find-phase / verify
|
|
70
70
|
// phase-completeness rather than restating it. (#1571, #2528)
|
|
71
|
-
const phaseDir = (0, gate_phase_context_cjs_1.resolvePhaseDirByToken)(phasesDir, phaseArg);
|
|
71
|
+
const phaseDir = (0, gate_phase_context_cjs_1.resolvePhaseDirByToken)(cwd, phasesDir, phaseArg);
|
|
72
72
|
if (!phaseDir) {
|
|
73
73
|
// An unresolvable phase is "could not look" (#5170): there is no phase directory to evaluate.
|
|
74
74
|
return schemaDriftUnreadable(`Phase directory not found: ${phaseArg}`);
|
|
@@ -29,6 +29,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
29
29
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
30
30
|
};
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.TDD_ROW_REASON = void 0;
|
|
32
33
|
exports.evaluateTddReviewCheckpoint = evaluateTddReviewCheckpoint;
|
|
33
34
|
const node_path_1 = __importDefault(require("node:path"));
|
|
34
35
|
const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
|
|
@@ -38,6 +39,9 @@ const gate_evaluation_scope_cjs_1 = require("./gate-evaluation-scope.cjs");
|
|
|
38
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
39
40
|
const frontmatterMod = require("./frontmatter.cjs");
|
|
40
41
|
const { frontmatterKeyHasValue, extractFrontmatter } = frontmatterMod;
|
|
42
|
+
exports.TDD_ROW_REASON = {
|
|
43
|
+
GREEN_BEFORE_RED: 'green_before_red',
|
|
44
|
+
};
|
|
41
45
|
/**
|
|
42
46
|
* True when the plan's frontmatter declares `type: tdd` (CRLF included, #2449). The block is the
|
|
43
47
|
* one fence owner's, read as RAW text — not through the YAML parser — so a block the parser refuses
|
|
@@ -78,19 +82,39 @@ function transitiveDependents(planId, dependents) {
|
|
|
78
82
|
return [...seen].sort();
|
|
79
83
|
}
|
|
80
84
|
/**
|
|
81
|
-
* The
|
|
82
|
-
*
|
|
83
|
-
* unresolvable scope (git unavailable, an id that is not `<phase>-<plan>`) is "no commits".
|
|
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".
|
|
84
88
|
*/
|
|
85
|
-
function
|
|
89
|
+
function planCommits(projectDir, planId) {
|
|
86
90
|
const scope = (0, gate_evaluation_scope_cjs_1.resolveEvaluationScope)(projectDir, { kind: 'plan', planId }, { pathspecs: ['.'], commitsOnly: true });
|
|
87
|
-
const
|
|
91
|
+
const commits = [];
|
|
88
92
|
for (const commit of scope.commits) {
|
|
89
93
|
const kind = /^([a-z]+)\(/.exec(commit.subject)?.[1];
|
|
90
94
|
if (kind)
|
|
91
|
-
|
|
95
|
+
commits.push({ sha: commit.sha, kind });
|
|
92
96
|
}
|
|
93
|
-
return
|
|
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
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return unanswered ? null : false;
|
|
94
118
|
}
|
|
95
119
|
/**
|
|
96
120
|
* The verdict when the review could not read a plan or the phase directory. Advisory policy is
|
|
@@ -177,26 +201,41 @@ function evaluateTddReviewCheckpoint(input) {
|
|
|
177
201
|
const rows = [];
|
|
178
202
|
for (const planPath of tddPlanFiles) {
|
|
179
203
|
const planId = node_path_1.default.basename(planPath, '-PLAN.md');
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const
|
|
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);
|
|
183
213
|
// #4867: a RED-only probe plan is satisfied by the GREEN of a plan that (transitively) declares
|
|
184
|
-
// `depends_on` it. Only a real `feat(...)` commit under such a plan counts
|
|
185
|
-
// still required, and a plan nothing depends on is scored
|
|
186
|
-
|
|
187
|
-
const
|
|
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
|
|
188
218
|
? []
|
|
189
|
-
: transitiveDependents(planId, dependents).
|
|
190
|
-
const
|
|
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);
|
|
191
223
|
const missing = [];
|
|
192
224
|
if (!red)
|
|
193
225
|
missing.push('RED');
|
|
194
|
-
if (!
|
|
226
|
+
if (!anyGreenCommit)
|
|
195
227
|
missing.push('GREEN');
|
|
196
|
-
const status = missing.length === 0 ? 'Pass' : 'FAIL';
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
});
|
|
200
239
|
}
|
|
201
240
|
const violations = rows.filter(r => r.status === 'FAIL').length;
|
|
202
241
|
// Build review table
|
|
@@ -218,7 +257,9 @@ function evaluateTddReviewCheckpoint(input) {
|
|
|
218
257
|
if (violations > 0) {
|
|
219
258
|
table += '\n\n⚠ Gate violations are advisory — review before advancing.';
|
|
220
259
|
for (const r of rows.filter(row => row.status === 'FAIL')) {
|
|
221
|
-
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).`;
|
|
222
263
|
table += `\n Expected commit pattern: test(${r.planId}): ... → feat(${r.planId}): ...`;
|
|
223
264
|
}
|
|
224
265
|
}
|
|
@@ -21,6 +21,8 @@ const gate_phase_context_cjs_1 = require("./gate-phase-context.cjs");
|
|
|
21
21
|
const gate_evaluation_scope_cjs_1 = require("./gate-evaluation-scope.cjs");
|
|
22
22
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
23
23
|
const ui_safety_gate_cjs_1 = require("./ui-safety-gate.cjs");
|
|
24
|
+
const ui_frontend_evidence_cjs_1 = require("./ui-frontend-evidence.cjs");
|
|
25
|
+
const gate_evidence_cjs_1 = require("./gate-evidence.cjs");
|
|
24
26
|
const UI_FILE_EXTENSIONS_RE = /\.(tsx|jsx|css|scss|sass|less|vue|svelte|html)$/i;
|
|
25
27
|
const UI_PATH_PATTERNS_RE = /\/(components|pages|views|screens|layouts|ui|frontend)\//i;
|
|
26
28
|
/**
|
|
@@ -32,7 +34,10 @@ const UI_PATH_PATTERNS_RE = /\/(components|pages|views|screens|layouts|ui|fronte
|
|
|
32
34
|
* git call is bounded by the resolver's seam; an unreadable scope is reported, not "clean").
|
|
33
35
|
* (d) Phase directory → `*-UI-SPEC.md`.
|
|
34
36
|
*
|
|
35
|
-
*
|
|
37
|
+
* (c') Static frontend evidence (`readStaticFrontendEvidence`, the definition `computeUiPlanGate` uses),
|
|
38
|
+
* probed only for a frontend phase that changed UI files (#5646).
|
|
39
|
+
*
|
|
40
|
+
* `block = frontend && hasUiFiles && hasFrontendEvidence && !hasUiSpec`.
|
|
36
41
|
*/
|
|
37
42
|
function computeUiSafetyGate(projectDir, phase) {
|
|
38
43
|
// (a) phase section text (same two-pass lookup as computeUiPlanGate)
|
|
@@ -43,14 +48,27 @@ function computeUiSafetyGate(projectDir, phase) {
|
|
|
43
48
|
// (c) any UI files in the phase's own commits? (deleted paths count: a removed component is a UI change)
|
|
44
49
|
const scope = (0, gate_evaluation_scope_cjs_1.resolveEvaluationScope)(projectDir, { kind: 'phase', phase });
|
|
45
50
|
const hasUiFiles = scope.changedFiles.some((f) => f.trim() && (UI_FILE_EXTENSIONS_RE.test(f) || UI_PATH_PATTERNS_RE.test(f)));
|
|
51
|
+
// (c') #5646 — static structural corroboration, the same evidence `computeUiPlanGate` requires (#3312):
|
|
52
|
+
// `hasUiFiles` is a weak signal (an `.html` fixture, or any file under a `ui/` directory in any
|
|
53
|
+
// language), so on its own it halted projects with no frontend that the plan gate had cleared. Only
|
|
54
|
+
// probed when it could change the answer. `unreadable` (#5170) is a manifest or tree the probe could
|
|
55
|
+
// not look at: absence of evidence is then not established, and the verdict says so (below).
|
|
56
|
+
const frontendEvidence = frontend && hasUiFiles ? (0, ui_frontend_evidence_cjs_1.readStaticFrontendEvidence)(projectDir) : (0, gate_evidence_cjs_1.evidenceNone)();
|
|
57
|
+
const hasFrontendEvidence = frontendEvidence.kind === 'found';
|
|
46
58
|
// (d) phase directory and *-UI-SPEC.md
|
|
47
59
|
// `none` is "no spec"; `unreadable` is "could not look" (#5170) and is carried to the verdict.
|
|
48
60
|
const uiSpec = (0, gate_phase_context_cjs_1.locateUiSpec)(projectDir, phase);
|
|
49
61
|
const hasUiSpec = uiSpec.kind === 'found';
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
const
|
|
53
|
-
|
|
62
|
+
// Unreadable frontend evidence only matters when it could flip the verdict: with a UI-SPEC present the
|
|
63
|
+
// gate does not block whatever the tree holds.
|
|
64
|
+
const frontendReadError = frontendEvidence.kind === 'unreadable' && !hasUiSpec
|
|
65
|
+
? `static frontend evidence could not be read (${frontendEvidence.reason})`
|
|
66
|
+
: undefined;
|
|
67
|
+
const readError = roadmapReadError ?? (uiSpec.kind === 'unreadable' ? uiSpec.reason : undefined) ?? frontendReadError;
|
|
68
|
+
// block only when: this is a frontend phase AND UI files were changed AND the project has static
|
|
69
|
+
// frontend evidence AND no UI-SPEC exists
|
|
70
|
+
const block = frontend && hasUiFiles && hasFrontendEvidence && !hasUiSpec;
|
|
71
|
+
const result = { frontend, hasUiFiles, hasFrontendEvidence, hasUiSpec, block };
|
|
54
72
|
if (block) {
|
|
55
73
|
result.message = `UI files changed in this wave but no UI-SPEC.md exists for Phase ${phase}. ` +
|
|
56
74
|
`Run ${(0, runtime_slash_cjs_1.runtimeCommand)(projectDir, `ui-phase ${phase}`)} to generate the design contract before continuing.`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Keep a machine-local file out of `git status` without touching any tracked
|
|
4
|
+
* file: append an anchored entry to the enclosing repo's `info/exclude` (#5551).
|
|
5
|
+
*
|
|
6
|
+
* Used for files GSD writes under `.planning/` that are per-machine state, not
|
|
7
|
+
* project content (the milestone claim). A copy that is already tracked is left
|
|
8
|
+
* alone — an exclude entry never untracks, and migrating it is the user's call.
|
|
9
|
+
*
|
|
10
|
+
* Best-effort by contract: never throws. Ignoring is optional, so no failure
|
|
11
|
+
* here (no git, read-only `.git`, odd path) may perturb the caller.
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.excludeFromGit = excludeFromGit;
|
|
18
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
21
|
+
const text_lines_cjs_1 = require("./text-lines.cjs");
|
|
22
|
+
/** Escape gitignore metacharacters so a directory or file name matches literally. */
|
|
23
|
+
function escapeGitignore(s) {
|
|
24
|
+
return s.replace(/[\\*?[]/g, '\\$&');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Exclude `<dir>/<filename>` from git via the repo-local `info/exclude`.
|
|
28
|
+
* `dir` must already exist (it is the spawn cwd).
|
|
29
|
+
*/
|
|
30
|
+
function excludeFromGit(dir, filename) {
|
|
31
|
+
try {
|
|
32
|
+
// gitignore cannot express a newline in a pattern, and git's line output could not be split.
|
|
33
|
+
if (/[\r\n]/.test(dir))
|
|
34
|
+
return;
|
|
35
|
+
// The prefix goes last: stdout is trimmed, and a non-empty prefix ends in `/`, so only an
|
|
36
|
+
// empty prefix (dir is the git root) loses its line; a leading space survives.
|
|
37
|
+
const revParse = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--git-path', 'info/exclude', '--show-prefix'], { cwd: dir });
|
|
38
|
+
const [excludeRel, prefix = ''] = (0, text_lines_cjs_1.splitLines)(String(revParse.stdout));
|
|
39
|
+
if (revParse.exitCode !== 0 || !excludeRel)
|
|
40
|
+
return;
|
|
41
|
+
const exclude = node_path_1.default.resolve(dir, excludeRel);
|
|
42
|
+
const entry = `/${escapeGitignore(prefix)}${escapeGitignore(filename)}`;
|
|
43
|
+
const existing = node_fs_1.default.existsSync(exclude) ? node_fs_1.default.readFileSync(exclude, 'utf8') : '';
|
|
44
|
+
if ((0, text_lines_cjs_1.splitLines)(existing).includes(entry))
|
|
45
|
+
return;
|
|
46
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(exclude), { recursive: true });
|
|
47
|
+
node_fs_1.default.appendFileSync(exclude, (existing && !existing.endsWith('\n') ? '\n' : '') + entry + '\n');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// advisory: see the module header.
|
|
51
|
+
}
|
|
52
|
+
}
|