@opengsd/gsd-core 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +575 -101
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +134 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +24 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Health Diagnostic — Milestone archive + root hygiene rules (Phase 11,
|
|
4
|
+
* #3309, ADR-3180 §8.2/§8.3/§8.5).
|
|
5
|
+
*
|
|
6
|
+
* Group: "Milestone archive + root hygiene" (design doc, "Rule table
|
|
7
|
+
* organization" table) — W018, W019.
|
|
8
|
+
*
|
|
9
|
+
* Ported behavior-preserving from `cmdValidateHealth`
|
|
10
|
+
* (`src/verify.cts:2301-2354`), the exact call sites for W018/W019.
|
|
11
|
+
*
|
|
12
|
+
* Design: .gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md
|
|
13
|
+
*
|
|
14
|
+
* ADR-457 build-at-publish: source in
|
|
15
|
+
* src/health-diagnostic-rules/milestone-archive-hygiene.cts, compiled to
|
|
16
|
+
* gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs
|
|
17
|
+
* (gitignored).
|
|
18
|
+
*/
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
|
+
const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
21
|
+
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK, adviseRemedy } = healthDiagnosticMod;
|
|
22
|
+
const artifacts_cjs_1 = require("../artifacts.cjs");
|
|
23
|
+
// ─── W018 — MILESTONES.md missing archived milestone(s) (verify.cts:2301-2335) ──
|
|
24
|
+
//
|
|
25
|
+
// Condition: `snapshot.milestoneArchiveStatus.value.archivedVersions` (list of
|
|
26
|
+
// versions with a `.planning/milestones/<ver>-ROADMAP.md` snapshot file) minus
|
|
27
|
+
// `.documentedVersions` (list of `## <version>` headings already present in
|
|
28
|
+
// MILESTONES.md) — versions present in the archive but not documented in the
|
|
29
|
+
// registry. ONE aggregate `Diagnostic` listing every missing version, exactly
|
|
30
|
+
// mirroring the original's single `addIssue` call
|
|
31
|
+
// (`verify.cts:2321-2330`, `` `MILESTONES.md missing ${missingFromRegistry.length}
|
|
32
|
+
// archived milestone(s): ${missingFromRegistry.join(', ')}` ``) — the original
|
|
33
|
+
// computes the FULL list first (`missingFromRegistry`), then fires exactly one
|
|
34
|
+
// `addIssue` after the loop, not once per version. No diagnostic at all if the
|
|
35
|
+
// archive dir has zero recognized `-ROADMAP.md` snapshots (mirrors the
|
|
36
|
+
// original's `if (archivedVersions.length > 0)` guard) or if nothing is
|
|
37
|
+
// missing.
|
|
38
|
+
function checkW018(snapshot) {
|
|
39
|
+
const { archivedVersions, documentedVersions } = snapshot.milestoneArchiveStatus.value;
|
|
40
|
+
if (archivedVersions.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
const documented = new Set(documentedVersions);
|
|
43
|
+
const missingFromRegistry = archivedVersions.filter((ver) => !documented.has(ver));
|
|
44
|
+
if (missingFromRegistry.length === 0)
|
|
45
|
+
return [];
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
code: 'W018',
|
|
49
|
+
severity: SEVERITY.WARNING,
|
|
50
|
+
message: `MILESTONES.md missing ${missingFromRegistry.length} archived milestone(s): ${missingFromRegistry.join(', ')}`,
|
|
51
|
+
remedy: {
|
|
52
|
+
action: REMEDY_ACTION.BACKFILL_MILESTONES,
|
|
53
|
+
risk: REMEDY_RISK.NONE,
|
|
54
|
+
args: {},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
// ─── W019 — Unrecognized .planning/ root file (verify.cts:2337-2354) ───────
|
|
60
|
+
//
|
|
61
|
+
// Condition: for each filename in `snapshot.planningRootFiles.value` ending in
|
|
62
|
+
// `.md`, call `isCanonicalPlanningFile(filename)` (bare basename, not a path —
|
|
63
|
+
// `src/artifacts.cts:43`); flag any that return false. One `Diagnostic` PER
|
|
64
|
+
// unrecognized file (array return), mirroring the original's `addIssue` call
|
|
65
|
+
// INSIDE the `for` loop (`verify.cts:2343-2349`) — unlike W018 this is not an
|
|
66
|
+
// aggregate. Fix text copied verbatim from `verify.cts:2347`.
|
|
67
|
+
function checkW019(snapshot) {
|
|
68
|
+
const diagnostics = [];
|
|
69
|
+
for (const filename of snapshot.planningRootFiles.value) {
|
|
70
|
+
if (!filename.endsWith('.md'))
|
|
71
|
+
continue;
|
|
72
|
+
if ((0, artifacts_cjs_1.isCanonicalPlanningFile)(filename))
|
|
73
|
+
continue;
|
|
74
|
+
diagnostics.push({
|
|
75
|
+
code: 'W019',
|
|
76
|
+
severity: SEVERITY.WARNING,
|
|
77
|
+
message: `Unrecognized .planning/ file: ${filename} — not a canonical GSD artifact`,
|
|
78
|
+
remedy: adviseRemedy('Move to .planning/milestones/ archive subdir or delete if stale. See templates/README.md for the canonical artifact list.'),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return diagnostics;
|
|
82
|
+
}
|
|
83
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
84
|
+
const RULES = [
|
|
85
|
+
{
|
|
86
|
+
code: 'W018',
|
|
87
|
+
severity: SEVERITY.WARNING,
|
|
88
|
+
description: 'MILESTONES.md missing entry for archived milestone snapshot',
|
|
89
|
+
repairable: true,
|
|
90
|
+
check: checkW018,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
code: 'W019',
|
|
94
|
+
severity: SEVERITY.WARNING,
|
|
95
|
+
description: 'Unrecognized .planning/ root file — not a canonical GSD artifact',
|
|
96
|
+
repairable: false,
|
|
97
|
+
check: checkW019,
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
module.exports = { RULES };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Health Diagnostic — Phase directory structure rules (Phase 11, #3309,
|
|
4
|
+
* ADR-3180 §8.2/§8.3/§8.5).
|
|
5
|
+
*
|
|
6
|
+
* Group: "Phase directory structure" (design doc, "Rule table organization"
|
|
7
|
+
* table) — W005, W023, I001, W009.
|
|
8
|
+
*
|
|
9
|
+
* Ported behavior-preserving from `cmdValidateHealth`
|
|
10
|
+
* (`src/verify.cts:1893-1990`, the exact call sites for W005/W023/I001/W009),
|
|
11
|
+
* with two disclosed fidelity reductions forced by `PlanningSnapshot`'s
|
|
12
|
+
* current shape (see each rule's own comment below):
|
|
13
|
+
*
|
|
14
|
+
* - I001 cannot name the individual unsummarized PLAN filename (`snapshot.
|
|
15
|
+
* phases.value[i]` exposes only `planCount`/`summaryCount`, not per-plan
|
|
16
|
+
* filenames) — this rule reports a coarser per-PHASE message instead.
|
|
17
|
+
* - W023's original "described" list called `determinePhaseStatus`
|
|
18
|
+
* (`commands.cts:154`), a SIX-way status string ('Not Started'/'Planned'/
|
|
19
|
+
* 'In Progress'/'Executed'/'Needs Review'/'Complete') computed from its own
|
|
20
|
+
* raw `readdirSync` + `*-VERIFICATION.md` frontmatter read of `phaseDir`.
|
|
21
|
+
* This rule cannot re-run that raw read (§8.1 rule 1 forbids ambient I/O in
|
|
22
|
+
* `check`), so `derivePhaseStatusLabel` below reconstructs the same
|
|
23
|
+
* six-way label from fields `PlanningSnapshot` already exposes —
|
|
24
|
+
* `PhaseSnapshot.planCount`/`summaryCount` (the plan/no-plan and
|
|
25
|
+
* in-progress/planned branches, identical inputs to the original) and
|
|
26
|
+
* `PhaseSnapshot.complete`/`verificationStatus` (`isPhaseComplete`'s own
|
|
27
|
+
* §7.4 disk-strict routing of the SAME `*-VERIFICATION.md` file) for the
|
|
28
|
+
* verification-gated branches. This is a disclosed fidelity reduction, not
|
|
29
|
+
* a byte-for-byte guarantee: `verificationStatus` is the DIFFERENT,
|
|
30
|
+
* `readVerificationStatus`-routed vocabulary ('passed'/'gaps_found'/
|
|
31
|
+
* 'human_needed'/'stale'/'unknown'/'missing') and can disagree with a raw
|
|
32
|
+
* frontmatter re-read in edge cases (e.g. a stale-but-frontmatter-"passed"
|
|
33
|
+
* VERIFICATION.md routes to 'stale', not 'passed', under §7.4's staleness
|
|
34
|
+
* handling — this rule reports 'Executed' there, not 'Complete'). No new
|
|
35
|
+
* ambient I/O and no new `PlanningSnapshot` field were needed — every input
|
|
36
|
+
* was already on `PhaseSnapshot`.
|
|
37
|
+
*
|
|
38
|
+
* W009's original message interpolates `${slash('plan-phase')}`
|
|
39
|
+
* (`verify.cts:1982`, ``Re-run ${slash('plan-phase')} with --research to
|
|
40
|
+
* regenerate``), a per-project runtime-resolved value (`formatGsdSlash`,
|
|
41
|
+
* `src/runtime-slash.cts`) this rule's `(snapshot) => Diagnostic[]`
|
|
42
|
+
* signature has no access to. Hardcodes the canonical `/gsd-plan-phase`
|
|
43
|
+
* hyphen form instead, mirroring the sibling "config.json validation"
|
|
44
|
+
* group's W016 rule (`src/health-diagnostic-rules/config-validation.cts`),
|
|
45
|
+
* which hardcodes `/gsd-ai-integration-phase` the same way for the
|
|
46
|
+
* identical reason.
|
|
47
|
+
*
|
|
48
|
+
* Design: .gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md
|
|
49
|
+
*
|
|
50
|
+
* ADR-457 build-at-publish: source in
|
|
51
|
+
* src/health-diagnostic-rules/phase-structure.cts, compiled to
|
|
52
|
+
* gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs (gitignored).
|
|
53
|
+
*/
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
|
+
const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
56
|
+
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
|
+
const validateMod = require("../validate.cjs");
|
|
59
|
+
const { phaseDirNameRe } = validateMod;
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
61
|
+
const phaseIdMod = require("../phase-id.cjs");
|
|
62
|
+
const { extractPhaseToken, normalizePhaseName, comparePhaseNum } = phaseIdMod;
|
|
63
|
+
// ─── W005 — phase directory doesn't follow NN-name format (verify.cts:1893-1902) ─
|
|
64
|
+
function checkW005(snapshot) {
|
|
65
|
+
const diagnostics = [];
|
|
66
|
+
for (const name of snapshot.phaseDirs.value) {
|
|
67
|
+
if (!name.match(phaseDirNameRe)) {
|
|
68
|
+
diagnostics.push({
|
|
69
|
+
code: 'W005',
|
|
70
|
+
severity: SEVERITY.WARNING,
|
|
71
|
+
message: `Phase directory "${name}" doesn't follow NN-name format`,
|
|
72
|
+
remedy: adviseRemedy('Rename to match pattern (e.g., 01-setup)'),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return diagnostics;
|
|
77
|
+
}
|
|
78
|
+
// ─── W023 — phase directories collide on normalized key (verify.cts:1904-1950) ─
|
|
79
|
+
//
|
|
80
|
+
// Groups `snapshot.phaseDirs.value` by `normalizePhaseName(extractPhaseToken(name))`
|
|
81
|
+
// — the exact same two owners (`phase-id.cjs`) the original `verify.cts:1917-1918`
|
|
82
|
+
// call site uses, relocated verbatim rather than reimplemented. Sorted with
|
|
83
|
+
// `comparePhaseNum` + a `localeCompare` tiebreak, mirroring
|
|
84
|
+
// `verify.cts:1930-1932`'s deterministic-output rationale. See the file-level
|
|
85
|
+
// comment for the disclosed "described" fidelity reduction.
|
|
86
|
+
/**
|
|
87
|
+
* Reconstruct `commands.cts:154`'s `determinePhaseStatus` six-way label from
|
|
88
|
+
* fields `PhaseSnapshot` already exposes (no ambient I/O, no new snapshot
|
|
89
|
+
* field — see file-level comment). `complete`/`verificationStatus` come from
|
|
90
|
+
* `isPhaseComplete`'s §7.4 disk-strict routing of the same `*-VERIFICATION.md`
|
|
91
|
+
* file the original raw read targeted.
|
|
92
|
+
*/
|
|
93
|
+
function derivePhaseStatusLabel(planCount, summaryCount, complete, verificationStatus) {
|
|
94
|
+
if (planCount === 0)
|
|
95
|
+
return 'Not Started';
|
|
96
|
+
if (summaryCount < planCount && summaryCount > 0)
|
|
97
|
+
return 'In Progress';
|
|
98
|
+
if (summaryCount < planCount)
|
|
99
|
+
return 'Planned';
|
|
100
|
+
// summaryCount >= planCount > 0 — verification-gated, same as the original's
|
|
101
|
+
// post-count fall-through.
|
|
102
|
+
if (complete)
|
|
103
|
+
return 'Complete';
|
|
104
|
+
if (verificationStatus === 'human_needed')
|
|
105
|
+
return 'Needs Review';
|
|
106
|
+
// gaps_found / stale / missing / unknown all land here, same as the
|
|
107
|
+
// original's "verification exists but unrecognized" and "no verification
|
|
108
|
+
// file" branches both returning 'Executed'.
|
|
109
|
+
return 'Executed';
|
|
110
|
+
}
|
|
111
|
+
function checkW023(snapshot) {
|
|
112
|
+
const groups = new Map();
|
|
113
|
+
for (const name of snapshot.phaseDirs.value) {
|
|
114
|
+
const token = extractPhaseToken(name);
|
|
115
|
+
const key = normalizePhaseName(token);
|
|
116
|
+
const list = groups.get(key);
|
|
117
|
+
if (list)
|
|
118
|
+
list.push(name);
|
|
119
|
+
else
|
|
120
|
+
groups.set(key, [name]);
|
|
121
|
+
}
|
|
122
|
+
const phaseByDir = new Map(snapshot.phases.value.map((p) => [p.dir, p]));
|
|
123
|
+
const diagnostics = [];
|
|
124
|
+
for (const [key, dirs] of groups) {
|
|
125
|
+
if (dirs.length < 2)
|
|
126
|
+
continue;
|
|
127
|
+
const described = dirs
|
|
128
|
+
.slice()
|
|
129
|
+
.sort((a, b) => comparePhaseNum(a, b) || String(a).localeCompare(String(b)))
|
|
130
|
+
.map((d) => {
|
|
131
|
+
const phase = phaseByDir.get(d);
|
|
132
|
+
const plans = phase ? phase.planCount : 0;
|
|
133
|
+
const summaries = phase ? phase.summaryCount : 0;
|
|
134
|
+
const complete = phase ? phase.complete : false;
|
|
135
|
+
const verificationStatus = phase ? phase.verificationStatus : 'missing';
|
|
136
|
+
const status = derivePhaseStatusLabel(plans, summaries, complete, verificationStatus);
|
|
137
|
+
return `${d} (${status})`;
|
|
138
|
+
})
|
|
139
|
+
.join(', ');
|
|
140
|
+
diagnostics.push({
|
|
141
|
+
code: 'W023',
|
|
142
|
+
severity: SEVERITY.WARNING,
|
|
143
|
+
message: `Phase directories collide on normalized key "${key}": ${described}`,
|
|
144
|
+
remedy: adviseRemedy('Inspect each directory; rename or remove the duplicate so only one directory maps to this phase key'),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return diagnostics;
|
|
148
|
+
}
|
|
149
|
+
// ─── I001 — plan(s) without a matching SUMMARY.md (verify.cts:1952-1965) ───
|
|
150
|
+
//
|
|
151
|
+
// GENUINE FIDELITY GAP (see file-level comment): the original is PER-PLAN
|
|
152
|
+
// (`${e.name}/${plan} has no SUMMARY.md`, `plan` an individual PLAN.md
|
|
153
|
+
// filename from `findUnsummarizedPlans`). `PlanningSnapshot`'s
|
|
154
|
+
// `phases.value[i]` carries only `planCount`/`summaryCount` NUMBERS per
|
|
155
|
+
// phase — no per-plan filenames — so this rule cannot name which plan lacks
|
|
156
|
+
// a summary without reading the phase directory directly inside `check`
|
|
157
|
+
// (forbidden by §8.1 rule 1). This rule instead reports one coarser
|
|
158
|
+
// per-PHASE diagnostic naming the deficit count, not the individual
|
|
159
|
+
// filename(s).
|
|
160
|
+
function checkI001(snapshot) {
|
|
161
|
+
const diagnostics = [];
|
|
162
|
+
for (const phase of snapshot.phases.value) {
|
|
163
|
+
const deficit = phase.planCount - phase.summaryCount;
|
|
164
|
+
if (deficit > 0) {
|
|
165
|
+
diagnostics.push({
|
|
166
|
+
code: 'I001',
|
|
167
|
+
severity: SEVERITY.INFO,
|
|
168
|
+
message: `Phase ${phase.dir} has ${deficit} plan(s) without a matching summary`,
|
|
169
|
+
remedy: adviseRemedy('May be in progress'),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return diagnostics;
|
|
174
|
+
}
|
|
175
|
+
// ─── W009 — Validation Architecture in RESEARCH.md but no VALIDATION.md ────
|
|
176
|
+
// (verify.cts:1967-1990)
|
|
177
|
+
function checkW009(snapshot) {
|
|
178
|
+
const diagnostics = [];
|
|
179
|
+
for (const entry of snapshot.researchValidationStatus.value) {
|
|
180
|
+
if (entry.hasValidationArchitecture && !entry.hasValidationMd) {
|
|
181
|
+
diagnostics.push({
|
|
182
|
+
code: 'W009',
|
|
183
|
+
severity: SEVERITY.WARNING,
|
|
184
|
+
message: `Phase ${entry.dir}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`,
|
|
185
|
+
remedy: adviseRemedy('Re-run /gsd-plan-phase with --research to regenerate'),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return diagnostics;
|
|
190
|
+
}
|
|
191
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
192
|
+
const RULES = [
|
|
193
|
+
{
|
|
194
|
+
code: 'W005',
|
|
195
|
+
severity: SEVERITY.WARNING,
|
|
196
|
+
description: 'Phase directory naming mismatch',
|
|
197
|
+
repairable: false,
|
|
198
|
+
check: checkW005,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
code: 'W023',
|
|
202
|
+
severity: SEVERITY.WARNING,
|
|
203
|
+
description: 'Phase directories collide on normalized key',
|
|
204
|
+
repairable: false,
|
|
205
|
+
check: checkW023,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
code: 'I001',
|
|
209
|
+
severity: SEVERITY.INFO,
|
|
210
|
+
description: 'Plan without SUMMARY (may be in progress)',
|
|
211
|
+
repairable: false,
|
|
212
|
+
check: checkI001,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
code: 'W009',
|
|
216
|
+
severity: SEVERITY.WARNING,
|
|
217
|
+
description: 'Phase has Validation Architecture in RESEARCH.md but no VALIDATION.md',
|
|
218
|
+
repairable: false,
|
|
219
|
+
check: checkW009,
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
module.exports = { RULES };
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Health Diagnostic — ROADMAP/disk consistency rules (Phase 11, #3309,
|
|
4
|
+
* ADR-3180 §8.2/§8.3/§8.5).
|
|
5
|
+
*
|
|
6
|
+
* Group: "ROADMAP/disk consistency" (design doc, "Rule table organization"
|
|
7
|
+
* table) — W006, W007.
|
|
8
|
+
*
|
|
9
|
+
* Ported behavior-preserving from `cmdValidateHealth`
|
|
10
|
+
* (`src/verify.cts:2029-2101`, the exact call sites for W006/W007).
|
|
11
|
+
*
|
|
12
|
+
* Both rules share ONE matcher — `matchPhaseDirs` + `normalizePhaseName`
|
|
13
|
+
* (`src/phase-id.cts`), the same canonical directory-resolution owner
|
|
14
|
+
* `verify.cts:2060/2073` already calls (its own #2528 comment explains why:
|
|
15
|
+
* pairing roadmap phases against disk by intersecting independently-derived
|
|
16
|
+
* TOKEN SETS mislabels digit-leading slugs like `05-80-20-cleanup` in BOTH
|
|
17
|
+
* directions at once — phase 5 reads as missing a directory (W006) AND that
|
|
18
|
+
* directory reads as not in the roadmap (W007) — so both rules here resolve
|
|
19
|
+
* through `matchPhaseDirs`, never a hand-rolled string/token comparison, and
|
|
20
|
+
* `dirsForPhase` below is the single call site both go through, so they
|
|
21
|
+
* cannot independently drift on what "matches" means (#2528's own bug
|
|
22
|
+
* class).
|
|
23
|
+
*
|
|
24
|
+
* DISK-SIDE SOURCE — `allPhaseDirNames`, NOT `phaseDirs` (found while
|
|
25
|
+
* implementing this file, fixed inline rather than deferred).
|
|
26
|
+
* `snapshot.phaseDirs` (Phase 10, `listMilestonePhaseDirs`) is WINDOWED: its
|
|
27
|
+
* `inWindow` filter (`getMilestonePhaseFilter`, `src/roadmap-parser.cts:1220`)
|
|
28
|
+
* admits a directory only when its phase id is a MEMBER of the roadmap's
|
|
29
|
+
* current-milestone-declared phase set (`isDirInMilestone`). That makes
|
|
30
|
+
* `phaseDirs.value` a subset that, by construction, can never contain a
|
|
31
|
+
* directory the roadmap does NOT declare — exactly the directory W007 exists
|
|
32
|
+
* to find. Sourced from `phaseDirs`, W007 would be structurally inert: every
|
|
33
|
+
* member of the set is already provably claimable. Verified empirically
|
|
34
|
+
* (`node -e` trace against a real `buildPlanningSnapshot`): a genuine orphan
|
|
35
|
+
* directory (`04-extra`, no roadmap entry) was silently absent from
|
|
36
|
+
* `phaseDirs.value` and W007 fired zero diagnostics. `phaseDirs`'s windowing
|
|
37
|
+
* also risks a W006 false positive for a phase declared in a NON-current
|
|
38
|
+
* milestone section (`roadmapDeclaredPhases` is built from the FULL raw
|
|
39
|
+
* ROADMAP, all milestones — `src/planning-snapshot.cts:398-436` — while
|
|
40
|
+
* `phaseDirs` is scoped to the current milestone only), so both rules here
|
|
41
|
+
* use the new, additive `allPhaseDirNames` field
|
|
42
|
+
* (`src/planning-snapshot.cts`) instead: every directory actually present
|
|
43
|
+
* under the active `phases/` root, unfiltered by roadmap declaration.
|
|
44
|
+
* Archived-milestone directory names (`verify.cts:2050`,
|
|
45
|
+
* `collectArchivedPhaseDirNames`) are still not exposed as directory NAMES
|
|
46
|
+
* on `PlanningSnapshot`, but the equivalent TOKEN set is: `checkW006` below
|
|
47
|
+
* additionally consults `snapshot.archivedPhaseTokens` (added for the
|
|
48
|
+
* W002/state-consistency group's #3652 fix, reused verbatim here — see that
|
|
49
|
+
* field's own doc comment) so a phase whose only directory lives in a
|
|
50
|
+
* milestone archive (shipped OR the current milestone's own archive layout)
|
|
51
|
+
* no longer reads as W006-missing (Bug 1, found post-migration: the archived
|
|
52
|
+
* fixtures under `tests/milestone-archive.test.cjs` and
|
|
53
|
+
* `tests/verify-health.test.cjs` regressed against the pre-migration
|
|
54
|
+
* `verify.cts` behavior). W007 still never scans archived dirs (its loop is
|
|
55
|
+
* `allPhaseDirNames.value`, the active `phases/` root only), so an archived
|
|
56
|
+
* dir still cannot spuriously read as W007-orphaned either — unchanged.
|
|
57
|
+
*
|
|
58
|
+
* Bug 2 (found alongside Bug 1): `dirsForPhase` below also runs a
|
|
59
|
+
* `phaseVariants()`-based fallback when `matchPhaseDirs` finds nothing — see
|
|
60
|
+
* its own doc comment. Pre-migration, `verify.cts:2071-2073`/`2092-2093` ran
|
|
61
|
+
* this as a SECOND, independent check the migrated matchPhaseDirs-only path
|
|
62
|
+
* had dropped, causing a false W006/W007 whenever ROADMAP and disk spelled
|
|
63
|
+
* the same phase with a different zero-padding (e.g. ROADMAP "01A" vs disk
|
|
64
|
+
* "1A-...").
|
|
65
|
+
*
|
|
66
|
+
* Not-started exclusion (verify.cts:2065/2075-2076,
|
|
67
|
+
* `buildNotStartedPhaseVariants`, `src/validate.cts:160`): the design doc's
|
|
68
|
+
* field table assigns this group only `roadmapDeclaredPhases`/`phaseDirs`,
|
|
69
|
+
* and `roadmapDeclaredPhases` (`src/planning-snapshot.cts:398-436`) does
|
|
70
|
+
* NOT filter not-started phases out — it returns every heading- and
|
|
71
|
+
* checklist-declared phase id regardless of checked state (confirmed by
|
|
72
|
+
* direct read: its `buildRoadmapPhaseVariants` call includes BOTH `[x]` and
|
|
73
|
+
* `[ ]` checklist entries). Omitting the exclusion here would regress a
|
|
74
|
+
* COMMON case: `gsd-core/templates/roadmap.md`'s "Initial Roadmap" shape
|
|
75
|
+
* declares every phase as an unchecked `- [ ] **Phase N: [Name]**` checklist
|
|
76
|
+
* item before any phase directory exists, so a freshly created ROADMAP.md
|
|
77
|
+
* would immediately spam one W006 per phase. `snapshot.roadmapPhaseCheckboxes`
|
|
78
|
+
* (`src/planning-snapshot.cts:457-480`, backs W011 in the STATE.md-consistency
|
|
79
|
+
* group) already parses exactly this `[x]`/`[ ]` state — `check(snapshot)`'s
|
|
80
|
+
* signature grants the full snapshot, not just this group's assigned column,
|
|
81
|
+
* so `isPhaseNotStarted` below reads it directly rather than re-deriving a
|
|
82
|
+
* third independent regex over raw ROADMAP text (forbidden by §8.1 rule 2).
|
|
83
|
+
* KNOWN GAP, disclosed rather than silently dropped: `roadmapPhaseCheckboxes`
|
|
84
|
+
* is keyed by `PHASE_NUMBER_TOKEN_SOURCE` (`phase-id.cts:54`, no dash), so a
|
|
85
|
+
* milestone-dash-prefixed phase id ("2-01") can never match a checkbox key —
|
|
86
|
+
* unlike the original `buildNotStartedPhaseVariants`, which captures the
|
|
87
|
+
* fuller `[\w][\w.-]*` grammar (dashes included). For that id shape only,
|
|
88
|
+
* this rule's not-started exclusion silently no-ops (never excludes), which
|
|
89
|
+
* is the conservative direction (a possible false W006, not a suppressed
|
|
90
|
+
* true one).
|
|
91
|
+
*
|
|
92
|
+
* Design: .gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md
|
|
93
|
+
*
|
|
94
|
+
* ADR-457 build-at-publish: source in
|
|
95
|
+
* src/health-diagnostic-rules/roadmap-disk-consistency.cts, compiled to
|
|
96
|
+
* gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs
|
|
97
|
+
* (gitignored).
|
|
98
|
+
*/
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
100
|
+
const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
101
|
+
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
103
|
+
const planningScopeMod = require("../planning-scope.cjs");
|
|
104
|
+
const { SCOPE } = planningScopeMod;
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
106
|
+
const phaseIdMod = require("../phase-id.cjs");
|
|
107
|
+
const { matchPhaseDirs, normalizePhaseName, extractPhaseToken, isSentinelPhaseId } = phaseIdMod;
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
109
|
+
const validateMod = require("../validate.cjs");
|
|
110
|
+
const { phaseVariants } = validateMod;
|
|
111
|
+
// ─── Shared matcher — the single call site both W006 and W007 go through ───
|
|
112
|
+
/**
|
|
113
|
+
* Every on-disk directory (from `allPhaseDirNames.value`) that `phaseId`
|
|
114
|
+
* resolves to via the canonical `matchPhaseDirs` selection. Both `checkW006`
|
|
115
|
+
* (does ANY directory resolve) and `computeClaimedDirs` (which directories
|
|
116
|
+
* does the roadmap claim, for W007) call this — one matcher, reused, per the
|
|
117
|
+
* file-level comment.
|
|
118
|
+
*/
|
|
119
|
+
function dirsForPhase(dirs, phaseId) {
|
|
120
|
+
const canonical = matchPhaseDirs(dirs, normalizePhaseName(phaseId)).matches;
|
|
121
|
+
if (canonical.length > 0)
|
|
122
|
+
return canonical;
|
|
123
|
+
// Bug 2 (#3309 W006/W007 migration cluster, found while fixing the
|
|
124
|
+
// originally-reported archived-directory gap): `matchPhaseDirs`'s
|
|
125
|
+
// `phaseTokenMatches` compares `extractPhaseToken(dir)` (the directory's
|
|
126
|
+
// LITERAL, un-normalized digit run — e.g. "1A" for `1A-suffix-phase`)
|
|
127
|
+
// against `normalizePhaseName(phaseId)` (which PADS — "01A") case-
|
|
128
|
+
// insensitively, but never unifies a padding/letter-suffix mismatch
|
|
129
|
+
// BETWEEN the two sides: "1A" !== "01A" even though they name the same
|
|
130
|
+
// phase. Pre-migration, `verify.cts:2071-2073`/`2092-2093` ran a SECOND,
|
|
131
|
+
// independent check here — `[...phaseVariants(p)].some((v) =>
|
|
132
|
+
// diskPhases.has(v))` — that the migrated matchPhaseDirs-only path
|
|
133
|
+
// dropped. `phaseVariants` (`validate.cts:101`) is symmetric
|
|
134
|
+
// (padded<->unpadded, letter-suffix preserved both ways), so generating
|
|
135
|
+
// variants from `phaseId` and checking raw-disk-token membership is
|
|
136
|
+
// equivalent to intersecting `phaseVariants(phaseId)` with
|
|
137
|
+
// `phaseVariants(diskToken)` — variants always include their own input
|
|
138
|
+
// verbatim, so this fallback catches exactly the cases `matchPhaseDirs`
|
|
139
|
+
// alone misses without re-deriving a second matcher.
|
|
140
|
+
const variants = phaseVariants(phaseId);
|
|
141
|
+
return dirs.filter((d) => {
|
|
142
|
+
const token = extractPhaseToken(d).toUpperCase();
|
|
143
|
+
for (const variant of variants) {
|
|
144
|
+
if (token === variant.toUpperCase())
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* True when `phaseId` has an unchecked (`[ ]`) checklist entry in
|
|
152
|
+
* `roadmapPhaseCheckboxes` under any of its padding/case variants
|
|
153
|
+
* (`phaseVariants`, `src/validate.cts:101` — the same variant-expansion
|
|
154
|
+
* owner `verify.cts:2071/2075` uses for this exact exclusion). See the
|
|
155
|
+
* file-level comment for the KNOWN GAP on dash-shaped ids.
|
|
156
|
+
*/
|
|
157
|
+
function isPhaseNotStarted(phaseId, checkboxes) {
|
|
158
|
+
for (const variant of phaseVariants(phaseId)) {
|
|
159
|
+
if (Object.prototype.hasOwnProperty.call(checkboxes, variant) && checkboxes[variant] === false) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
// ─── W006 — ROADMAP.md declares a phase with no directory on disk ─────────
|
|
166
|
+
// (verify.cts:2067-2084)
|
|
167
|
+
function checkW006(snapshot) {
|
|
168
|
+
// Mirrors verify.cts:2029's `if (fs.existsSync(roadmapPath))` guard: ROADMAP.md
|
|
169
|
+
// absent or unreadable means the field degrades to `{value: [], scope:
|
|
170
|
+
// UNREADABLE}` (`src/planning-snapshot.cts:401-403/407-409`) and NEITHER
|
|
171
|
+
// W006 nor W007 evaluates — an empty declared-phase list must not be
|
|
172
|
+
// mistaken for "the roadmap legitimately declares zero phases" here.
|
|
173
|
+
if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
|
|
174
|
+
return [];
|
|
175
|
+
const dirs = snapshot.allPhaseDirNames.value;
|
|
176
|
+
const archivedTokens = new Set(snapshot.archivedPhaseTokens.value);
|
|
177
|
+
const checkboxes = snapshot.roadmapPhaseCheckboxes.value;
|
|
178
|
+
const diagnostics = [];
|
|
179
|
+
for (const { phaseId } of snapshot.roadmapDeclaredPhases.value) {
|
|
180
|
+
// #3225: sentinel phase ids (999.x/0.x) are never-on-roadmap by
|
|
181
|
+
// convention; a sentinel heading shouldn't demand a directory.
|
|
182
|
+
if (isSentinelPhaseId(phaseId))
|
|
183
|
+
continue;
|
|
184
|
+
if (dirsForPhase(dirs, phaseId).length > 0)
|
|
185
|
+
continue;
|
|
186
|
+
// Bug 1 (#3309 W006/W007 migration cluster): a phase whose ONLY
|
|
187
|
+
// directory lives under a milestone archive
|
|
188
|
+
// (`.planning/milestones/vX.Y-phases/<phase>/`, shipped OR the current
|
|
189
|
+
// milestone's own archive layout) must not read as "no directory on
|
|
190
|
+
// disk" — mirrors `verify.cts:2038`'s
|
|
191
|
+
// `forEachArchivedPhaseToken(planBase, (token) => diskPhases.add(token))`
|
|
192
|
+
// feeding the archived-phase token set into this exact existence check.
|
|
193
|
+
// `snapshot.archivedPhaseTokens` (`src/planning-snapshot.cts`) is the
|
|
194
|
+
// same token set, reused verbatim from the W002/state-consistency
|
|
195
|
+
// group's own fix for the analogous gap — not a re-derivation.
|
|
196
|
+
if ([...phaseVariants(phaseId)].some((v) => archivedTokens.has(v)))
|
|
197
|
+
continue;
|
|
198
|
+
if (isPhaseNotStarted(phaseId, checkboxes))
|
|
199
|
+
continue;
|
|
200
|
+
diagnostics.push({
|
|
201
|
+
code: 'W006',
|
|
202
|
+
severity: SEVERITY.WARNING,
|
|
203
|
+
message: `Phase ${phaseId} in ROADMAP.md but no directory on disk`,
|
|
204
|
+
remedy: adviseRemedy('Create phase directory or remove from roadmap'),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return diagnostics;
|
|
208
|
+
}
|
|
209
|
+
// ─── W007 — an on-disk phase directory has no matching ROADMAP entry ──────
|
|
210
|
+
// (verify.cts:2086-2101)
|
|
211
|
+
/** Every directory in `dirs` that ANY declared roadmap phase resolves to. */
|
|
212
|
+
function computeClaimedDirs(dirs, declaredPhases) {
|
|
213
|
+
const claimed = new Set();
|
|
214
|
+
for (const { phaseId } of declaredPhases) {
|
|
215
|
+
for (const dir of dirsForPhase(dirs, phaseId))
|
|
216
|
+
claimed.add(dir);
|
|
217
|
+
}
|
|
218
|
+
return claimed;
|
|
219
|
+
}
|
|
220
|
+
function checkW007(snapshot) {
|
|
221
|
+
// Same guard as W006 — see its comment.
|
|
222
|
+
if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
|
|
223
|
+
return [];
|
|
224
|
+
const dirs = snapshot.allPhaseDirNames.value;
|
|
225
|
+
const claimedDirs = computeClaimedDirs(dirs, snapshot.roadmapDeclaredPhases.value);
|
|
226
|
+
const diagnostics = [];
|
|
227
|
+
for (const dirName of dirs) {
|
|
228
|
+
// `extractPhaseToken` is the phase-id.cts owner `PHASE_TOKEN_FROM_DIR_RE`
|
|
229
|
+
// (`src/validate.cts:73-76`) is documented to match exactly
|
|
230
|
+
// (verify.cts's original `p` key from `collectDiskPhaseEntries`,
|
|
231
|
+
// `verify.cts:1373-1397`) — same token, relocated read, not reinvented.
|
|
232
|
+
const token = extractPhaseToken(dirName);
|
|
233
|
+
// #3225: a sentinel dir on disk (999-interim, 0-drafts) is defined as
|
|
234
|
+
// never-on-roadmap; it must not trigger W007.
|
|
235
|
+
if (isSentinelPhaseId(token))
|
|
236
|
+
continue;
|
|
237
|
+
if (claimedDirs.has(dirName))
|
|
238
|
+
continue;
|
|
239
|
+
diagnostics.push({
|
|
240
|
+
code: 'W007',
|
|
241
|
+
severity: SEVERITY.WARNING,
|
|
242
|
+
message: `Phase ${token} exists on disk but not in ROADMAP.md`,
|
|
243
|
+
remedy: adviseRemedy('Add to roadmap or remove directory'),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return diagnostics;
|
|
247
|
+
}
|
|
248
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
249
|
+
const RULES = [
|
|
250
|
+
{
|
|
251
|
+
code: 'W006',
|
|
252
|
+
severity: SEVERITY.WARNING,
|
|
253
|
+
description: 'Phase in ROADMAP but no directory',
|
|
254
|
+
repairable: false,
|
|
255
|
+
check: checkW006,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
code: 'W007',
|
|
259
|
+
severity: SEVERITY.WARNING,
|
|
260
|
+
description: 'Phase on disk but not in ROADMAP',
|
|
261
|
+
repairable: false,
|
|
262
|
+
check: checkW007,
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
module.exports = { RULES };
|