@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,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Health Diagnostic — config.json validation rules (Phase 11, #3309,
|
|
4
|
+
* ADR-3180 §8.2/§8.3/§8.5).
|
|
5
|
+
*
|
|
6
|
+
* Group: "config.json validation" (design doc, "Rule table organization"
|
|
7
|
+
* table) — W003, W004, W022 (one rule, three internal conditions), E005,
|
|
8
|
+
* W008, W016, W012, W013, W014, W015.
|
|
9
|
+
*
|
|
10
|
+
* Ported behavior-preserving from `cmdValidateHealth`'s config.json blocks
|
|
11
|
+
* (`src/verify.cts:1777-1835` for W003/W004/W022/E005,
|
|
12
|
+
* `src/verify.cts:1837-1865` for W008/W016,
|
|
13
|
+
* `src/verify.cts:2136-2191` for W012/W013/W014/W015). Every rule here reads
|
|
14
|
+
* ONLY `snapshot.config` (`{value, scope, exists}`, `src/planning-snapshot.cts`'s
|
|
15
|
+
* `buildConfigField`).
|
|
16
|
+
*
|
|
17
|
+
* W022 stays a SINGLE code across its three call sites per the design doc's
|
|
18
|
+
* "Rejected alternatives" §3: all three are variations on one question ("is
|
|
19
|
+
* `models` well-formed"), not a genuine multi-subject conflation. This rule's
|
|
20
|
+
* `checkW022` mirrors the original's exact if / else-if control flow
|
|
21
|
+
* (`verify.cts:1799-1824`): the object-shaped branch loops every `models`
|
|
22
|
+
* entry (0-N diagnostics, one per malformed entry); the non-object branch
|
|
23
|
+
* fires independently and ONLY when the object-shaped branch did not run —
|
|
24
|
+
* `models` is never checked against both.
|
|
25
|
+
*
|
|
26
|
+
* Two disclosed fidelity reductions, forced by `snapshot.config`'s shape
|
|
27
|
+
* (neither is available without violating §8.1 rule 1's "no ambient I/O in a
|
|
28
|
+
* rule's `check`"):
|
|
29
|
+
*
|
|
30
|
+
* - E005's original message interpolates the live `JSON.parse` error text
|
|
31
|
+
* (`config.json: JSON parse error - ${err.message}`, `verify.cts:1829`).
|
|
32
|
+
* `buildConfigField` (`src/planning-snapshot.cts:268-281`) catches and
|
|
33
|
+
* discards that error, collapsing an unparseable config.json to
|
|
34
|
+
* `{value: null, scope: UNREADABLE, exists: true}` with no error text
|
|
35
|
+
* anywhere in the snapshot. This rule's message drops the interpolated
|
|
36
|
+
* suffix rather than fabricate error text the snapshot never carried.
|
|
37
|
+
* - W016's original message interpolates `${slash('ai-integration-phase')}`
|
|
38
|
+
* (`verify.cts:1856`), a per-project runtime-resolved value
|
|
39
|
+
* (`formatGsdSlash`, `src/runtime-slash.cts`) this rule's `(snapshot) =>
|
|
40
|
+
* Diagnostic[]` signature has no access to. Hardcodes the canonical
|
|
41
|
+
* `/gsd-ai-integration-phase` hyphen form instead, mirroring the sibling
|
|
42
|
+
* "Phase directory structure" group's W009 rule
|
|
43
|
+
* (`src/health-diagnostic-rules/phase-structure.cts`), which hardcodes
|
|
44
|
+
* `/gsd-plan-phase` the same way for the identical reason.
|
|
45
|
+
*
|
|
46
|
+
* Design: .gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md
|
|
47
|
+
*
|
|
48
|
+
* ADR-457 build-at-publish: source in
|
|
49
|
+
* src/health-diagnostic-rules/config-validation.cts, compiled to
|
|
50
|
+
* gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs (gitignored).
|
|
51
|
+
*/
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- health-diagnostic-types.cjs is an export= CommonJS module
|
|
53
|
+
const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
54
|
+
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK, adviseRemedy } = healthDiagnosticMod;
|
|
55
|
+
const model_catalog_cjs_1 = require("../model-catalog.cjs");
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
57
|
+
const planningScopeMod = require("../planning-scope.cjs");
|
|
58
|
+
const { SCOPE } = planningScopeMod;
|
|
59
|
+
// verify.cts:2141 — inlined literal, not exported from anywhere; same list.
|
|
60
|
+
const VALID_BRANCHING_STRATEGIES = ['none', 'phase', 'milestone'];
|
|
61
|
+
// ─── W003 — config.json not found (verify.cts:1777-1785) ───────────────────
|
|
62
|
+
function checkW003(snapshot) {
|
|
63
|
+
if (snapshot.config.exists)
|
|
64
|
+
return [];
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
code: 'W003',
|
|
68
|
+
severity: SEVERITY.WARNING,
|
|
69
|
+
message: 'config.json not found',
|
|
70
|
+
remedy: { action: REMEDY_ACTION.CREATE_CONFIG, risk: REMEDY_RISK.NONE, args: {} },
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
// ─── E005 — config.json JSON parse error (verify.cts:1825-1834) ────────────
|
|
75
|
+
//
|
|
76
|
+
// `exists: true, value: null` is exactly `buildConfigField`'s "present but
|
|
77
|
+
// unparseable" contract (planning-snapshot.cts:268-281) — the same
|
|
78
|
+
// discriminator that separates this from W003's "absent" case.
|
|
79
|
+
function checkE005(snapshot) {
|
|
80
|
+
if (!snapshot.config.exists || snapshot.config.value !== null)
|
|
81
|
+
return [];
|
|
82
|
+
return [
|
|
83
|
+
{
|
|
84
|
+
code: 'E005',
|
|
85
|
+
severity: SEVERITY.ERROR,
|
|
86
|
+
message: 'config.json: JSON parse error',
|
|
87
|
+
remedy: { action: REMEDY_ACTION.RESET_CONFIG, risk: REMEDY_RISK.DESTRUCTIVE, args: {} },
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
// ─── W004 — invalid model_profile (verify.cts:1790-1797) ───────────────────
|
|
92
|
+
function checkW004(snapshot) {
|
|
93
|
+
const value = snapshot.config.value;
|
|
94
|
+
if (!value)
|
|
95
|
+
return [];
|
|
96
|
+
const profile = value['model_profile'];
|
|
97
|
+
if (profile && !model_catalog_cjs_1.VALID_PROFILES.includes(profile)) {
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
code: 'W004',
|
|
101
|
+
severity: SEVERITY.WARNING,
|
|
102
|
+
message: `config.json: invalid model_profile "${profile}"`,
|
|
103
|
+
remedy: adviseRemedy(`Valid values: ${model_catalog_cjs_1.VALID_PROFILES.join(', ')}`),
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
// ─── W008 — workflow.nyquist_validation absent (verify.cts:1841-1851) ──────
|
|
110
|
+
function checkW008(snapshot) {
|
|
111
|
+
const value = snapshot.config.value;
|
|
112
|
+
const workflow = value ? value['workflow'] : undefined;
|
|
113
|
+
if (workflow && workflow['nyquist_validation'] === undefined) {
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
code: 'W008',
|
|
117
|
+
severity: SEVERITY.WARNING,
|
|
118
|
+
message: 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)',
|
|
119
|
+
remedy: { action: REMEDY_ACTION.ADD_NYQUIST_KEY, risk: REMEDY_RISK.NONE, args: {} },
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
// ─── W016 — workflow.ai_integration_phase absent (verify.cts:1852-1861) ────
|
|
126
|
+
function checkW016(snapshot) {
|
|
127
|
+
const value = snapshot.config.value;
|
|
128
|
+
const workflow = value ? value['workflow'] : undefined;
|
|
129
|
+
if (workflow && workflow['ai_integration_phase'] === undefined) {
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
code: 'W016',
|
|
133
|
+
severity: SEVERITY.WARNING,
|
|
134
|
+
message: 'config.json: workflow.ai_integration_phase absent (defaults to enabled — run /gsd-ai-integration-phase before planning AI system phases)',
|
|
135
|
+
remedy: { action: REMEDY_ACTION.ADD_AI_INTEGRATION_PHASE_KEY, risk: REMEDY_RISK.NONE, args: {} },
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
// ─── W012 — invalid branching_strategy (verify.cts:2141-2152) ──────────────
|
|
142
|
+
function checkW012(snapshot) {
|
|
143
|
+
const value = snapshot.config.value;
|
|
144
|
+
if (!value)
|
|
145
|
+
return [];
|
|
146
|
+
const strategy = value['branching_strategy'];
|
|
147
|
+
if (strategy && !VALID_BRANCHING_STRATEGIES.includes(strategy)) {
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
code: 'W012',
|
|
151
|
+
severity: SEVERITY.WARNING,
|
|
152
|
+
message: `config.json: invalid branching_strategy "${strategy}"`,
|
|
153
|
+
remedy: adviseRemedy(`Valid values: ${VALID_BRANCHING_STRATEGIES.join(', ')}`),
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
// ─── W013 — context_window not a positive integer (verify.cts:2154-2164) ───
|
|
160
|
+
function checkW013(snapshot) {
|
|
161
|
+
const value = snapshot.config.value;
|
|
162
|
+
if (!value)
|
|
163
|
+
return [];
|
|
164
|
+
const cw = value['context_window'];
|
|
165
|
+
if (cw !== undefined && (typeof cw !== 'number' || cw <= 0 || !Number.isInteger(cw))) {
|
|
166
|
+
return [
|
|
167
|
+
{
|
|
168
|
+
code: 'W013',
|
|
169
|
+
severity: SEVERITY.WARNING,
|
|
170
|
+
message: `config.json: context_window should be a positive integer, got "${cw}"`,
|
|
171
|
+
remedy: adviseRemedy('Set to 200000 (default) or 1000000 (for 1M models)'),
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
// ─── W014 — phase_branch_template missing {phase} (verify.cts:2166-2176) ───
|
|
178
|
+
function checkW014(snapshot) {
|
|
179
|
+
const value = snapshot.config.value;
|
|
180
|
+
if (!value)
|
|
181
|
+
return [];
|
|
182
|
+
const tmpl = value['phase_branch_template'];
|
|
183
|
+
if (tmpl && !tmpl.includes('{phase}')) {
|
|
184
|
+
return [
|
|
185
|
+
{
|
|
186
|
+
code: 'W014',
|
|
187
|
+
severity: SEVERITY.WARNING,
|
|
188
|
+
message: 'config.json: phase_branch_template missing {phase} placeholder',
|
|
189
|
+
remedy: adviseRemedy('Template must include {phase} for phase number substitution'),
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
// ─── W015 — milestone_branch_template missing {milestone} (verify.cts:2177-2187) ─
|
|
196
|
+
function checkW015(snapshot) {
|
|
197
|
+
const value = snapshot.config.value;
|
|
198
|
+
if (!value)
|
|
199
|
+
return [];
|
|
200
|
+
const tmpl = value['milestone_branch_template'];
|
|
201
|
+
if (tmpl && !tmpl.includes('{milestone}')) {
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
code: 'W015',
|
|
205
|
+
severity: SEVERITY.WARNING,
|
|
206
|
+
message: 'config.json: milestone_branch_template missing {milestone} placeholder',
|
|
207
|
+
remedy: adviseRemedy('Template must include {milestone} for version substitution'),
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
// ─── W022 — models malformed, 3 internal conditions (verify.cts:1798-1824) ─
|
|
214
|
+
//
|
|
215
|
+
// Mirrors the original if / else-if chain exactly: the object-shaped branch
|
|
216
|
+
// (a: unknown phase type, b: invalid tier value) loops every `models` entry,
|
|
217
|
+
// pushing 0-N diagnostics; the non-object branch (c) is an ELSE-IF, so it
|
|
218
|
+
// only runs when `models` is truthy but did NOT satisfy "object, not array" —
|
|
219
|
+
// (a)/(b) are never evaluated against a non-object `models`.
|
|
220
|
+
function checkW022(snapshot) {
|
|
221
|
+
const value = snapshot.config.value;
|
|
222
|
+
if (!value)
|
|
223
|
+
return [];
|
|
224
|
+
const diagnostics = [];
|
|
225
|
+
const configModels = value['models'];
|
|
226
|
+
if (configModels && typeof configModels === 'object' && !Array.isArray(configModels)) {
|
|
227
|
+
for (const [phaseType, tierValue] of Object.entries(configModels)) {
|
|
228
|
+
if (!model_catalog_cjs_1.VALID_PHASE_TYPES.has(phaseType)) {
|
|
229
|
+
diagnostics.push({
|
|
230
|
+
code: 'W022',
|
|
231
|
+
severity: SEVERITY.WARNING,
|
|
232
|
+
message: `config.json: models has an unknown phase type "${phaseType}" which will be ignored`,
|
|
233
|
+
remedy: adviseRemedy(`Valid phase types: ${[...model_catalog_cjs_1.VALID_PHASE_TYPES].join(', ')}`),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
else if (typeof tierValue !== 'string' || !model_catalog_cjs_1.VALID_TIERS.has(tierValue)) {
|
|
237
|
+
diagnostics.push({
|
|
238
|
+
code: 'W022',
|
|
239
|
+
severity: SEVERITY.WARNING,
|
|
240
|
+
message: `config.json: models.${phaseType} has an invalid tier value ${JSON.stringify(tierValue)} which will be ignored`,
|
|
241
|
+
remedy: adviseRemedy(`Valid tiers: ${[...model_catalog_cjs_1.VALID_TIERS].join(', ')}`),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (configModels !== undefined && configModels !== null) {
|
|
247
|
+
diagnostics.push({
|
|
248
|
+
code: 'W022',
|
|
249
|
+
severity: SEVERITY.WARNING,
|
|
250
|
+
message: `config.json: models is set to ${JSON.stringify(configModels)}, but must be an object mapping phase types to tiers — this value will be ignored`,
|
|
251
|
+
remedy: adviseRemedy('Set models to an object like {"planning": "sonnet"}, or remove the key to use profile defaults'),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return diagnostics;
|
|
255
|
+
}
|
|
256
|
+
// ─── W029 — `.planning/` ignored but still tracked (#3586, epic #2292) ─────
|
|
257
|
+
//
|
|
258
|
+
// Not a config.json-sourced check (unlike every other rule in this file) —
|
|
259
|
+
// registered here per the phase brief rather than in a new file, reading
|
|
260
|
+
// only `snapshot.planningTracked` (`src/planning-snapshot.cts`'s
|
|
261
|
+
// `buildPlanningTrackedField`). Fires ONLY when the probe's scope is
|
|
262
|
+
// COMPLETE and both `ignored` and `tracked` are true: `.gitignore` has no
|
|
263
|
+
// effect on files git already tracks, so a project that committed
|
|
264
|
+
// `.planning/` before ignoring it keeps staging those files forever even
|
|
265
|
+
// though `commit_docs` correctly auto-resolves `false`. Any other COMPLETE
|
|
266
|
+
// combination (most importantly `ignored: false, tracked: true` — the
|
|
267
|
+
// default, healthy state of every normal project) is silent; a degraded
|
|
268
|
+
// (non-COMPLETE) scope is ALSO silent — a probe that could not run must
|
|
269
|
+
// never manufacture a finding. ADVISE-only: auto-untracking is destructive
|
|
270
|
+
// and outside this phase's stated boundary (design doc, "Rejected" #3).
|
|
271
|
+
function checkW029(snapshot) {
|
|
272
|
+
const field = snapshot.planningTracked;
|
|
273
|
+
if (!field || field.scope !== SCOPE.COMPLETE)
|
|
274
|
+
return [];
|
|
275
|
+
if (!field.value.ignored || !field.value.tracked)
|
|
276
|
+
return [];
|
|
277
|
+
return [
|
|
278
|
+
{
|
|
279
|
+
code: 'W029',
|
|
280
|
+
severity: SEVERITY.WARNING,
|
|
281
|
+
message: '.planning/ matches a gitignore rule but is still tracked by git — .gitignore has no effect on already-tracked files, so staging keeps picking them up',
|
|
282
|
+
remedy: adviseRemedy('Run `git rm -r --cached .planning/` to untrack it (the ignore rule then takes effect); this does not delete the files on disk'),
|
|
283
|
+
},
|
|
284
|
+
];
|
|
285
|
+
}
|
|
286
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
287
|
+
const RULES = [
|
|
288
|
+
{ code: 'W003', severity: SEVERITY.WARNING, description: 'config.json not found', repairable: true, check: checkW003 },
|
|
289
|
+
{ code: 'E005', severity: SEVERITY.ERROR, description: 'config.json parse error', repairable: false, check: checkE005 },
|
|
290
|
+
{ code: 'W004', severity: SEVERITY.WARNING, description: 'config.json invalid field value', repairable: false, check: checkW004 },
|
|
291
|
+
{
|
|
292
|
+
code: 'W008',
|
|
293
|
+
severity: SEVERITY.WARNING,
|
|
294
|
+
description: 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)',
|
|
295
|
+
repairable: true,
|
|
296
|
+
check: checkW008,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
code: 'W016',
|
|
300
|
+
severity: SEVERITY.WARNING,
|
|
301
|
+
description: 'config.json: workflow.ai_integration_phase absent (defaults to enabled but agents may skip AI-integration-phase planning)',
|
|
302
|
+
repairable: true,
|
|
303
|
+
check: checkW016,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
code: 'W012',
|
|
307
|
+
severity: SEVERITY.WARNING,
|
|
308
|
+
description: 'config.json invalid branching_strategy value',
|
|
309
|
+
repairable: false,
|
|
310
|
+
check: checkW012,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
code: 'W013',
|
|
314
|
+
severity: SEVERITY.WARNING,
|
|
315
|
+
description: 'config.json context_window not a positive integer',
|
|
316
|
+
repairable: false,
|
|
317
|
+
check: checkW013,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
code: 'W014',
|
|
321
|
+
severity: SEVERITY.WARNING,
|
|
322
|
+
description: 'config.json phase_branch_template missing {phase} placeholder',
|
|
323
|
+
repairable: false,
|
|
324
|
+
check: checkW014,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
code: 'W015',
|
|
328
|
+
severity: SEVERITY.WARNING,
|
|
329
|
+
description: 'config.json milestone_branch_template missing {milestone} placeholder',
|
|
330
|
+
repairable: false,
|
|
331
|
+
check: checkW015,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
code: 'W022',
|
|
335
|
+
severity: SEVERITY.WARNING,
|
|
336
|
+
description: 'config.json models entry malformed (unknown phase type, invalid tier, or non-object value)',
|
|
337
|
+
repairable: false,
|
|
338
|
+
check: checkW022,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
code: 'W029',
|
|
342
|
+
severity: SEVERITY.WARNING,
|
|
343
|
+
description: '.planning/ matches a gitignore rule but is still tracked by git (gitignore has no effect on already-tracked files)',
|
|
344
|
+
repairable: false,
|
|
345
|
+
check: checkW029,
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
module.exports = { RULES };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Health Diagnostic — `validate.consistency`-only rules (Phase 12, #3310,
|
|
4
|
+
* ADR-3180 §8.4).
|
|
5
|
+
*
|
|
6
|
+
* Group: "consistency" — a NEW `C0NN` code namespace, parallel to (not part
|
|
7
|
+
* of) `validate.health`'s `E`/`W`/`I` space Phase 11 minted. These four
|
|
8
|
+
* subjects are `cmdValidateConsistency`'s (`src/verify.cts:1466-1612`)
|
|
9
|
+
* own findings, not `validate.health` findings — see the design doc's
|
|
10
|
+
* "Code namespace" section for why they get their own prefix instead of
|
|
11
|
+
* competing for the next free `W0NN` number.
|
|
12
|
+
*
|
|
13
|
+
* Ported behavior-preserving from `cmdValidateConsistency`
|
|
14
|
+
* (`src/verify.cts:1504-1519` for C001, `:1570-1576` for C002, `:1584-1587`
|
|
15
|
+
* for C003, `:1596-1602` for C004) — relocated, not reinvented. The
|
|
16
|
+
* duplicate subjects `cmdValidateConsistency` also computed (phase-in-
|
|
17
|
+
* roadmap-no-dir / dir-on-disk-not-in-roadmap) are NOT here: those are
|
|
18
|
+
* W006/W007, reused verbatim from
|
|
19
|
+
* `src/health-diagnostic-rules/roadmap-disk-consistency.cts` via
|
|
20
|
+
* `health-diagnostic.cts`'s `CONSISTENCY_RULES` composition, not
|
|
21
|
+
* reimplemented in this file (design doc, "Which rules run where").
|
|
22
|
+
*
|
|
23
|
+
* All four remedies are `ADVISE` — none of these four subjects had a repair
|
|
24
|
+
* path in the pre-migration code either, matching Phase 11's own convention
|
|
25
|
+
* for message-only findings with no automated fix.
|
|
26
|
+
*
|
|
27
|
+
* Design: .gsd/phase/feat-3310-enhance-3180-the-sibling-validators-shar/40-design.md
|
|
28
|
+
* Test matrix: .gsd/phase/feat-3310-enhance-3180-the-sibling-validators-shar/50-test-matrix.md
|
|
29
|
+
*
|
|
30
|
+
* ADR-457 build-at-publish: source in src/health-diagnostic-rules/consistency.cts,
|
|
31
|
+
* compiled to gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs (gitignored).
|
|
32
|
+
*/
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
35
|
+
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
|
+
const phaseIdMod = require("../phase-id.cjs");
|
|
38
|
+
const { isSentinelPhaseId } = phaseIdMod;
|
|
39
|
+
// ─── C001 — gap in disk phase numbering (integer sequence) ────────────────
|
|
40
|
+
// (verify.cts:1504-1519)
|
|
41
|
+
function checkC001(snapshot) {
|
|
42
|
+
// verify.cts:1505 — `if (config.phase_naming !== 'custom')`; the
|
|
43
|
+
// integer-sequence gap check is skipped entirely under custom phase
|
|
44
|
+
// naming, since a custom naming scheme has no integer sequence to have a
|
|
45
|
+
// gap in.
|
|
46
|
+
if (snapshot.config.value?.['phase_naming'] === 'custom')
|
|
47
|
+
return [];
|
|
48
|
+
const integerPhases = snapshot.allPhaseDirNames.value
|
|
49
|
+
// #3225: exclude sentinel phase ids (999.x/0.x) — never part of the
|
|
50
|
+
// sequential numbering, mirrors verify.cts:1510 verbatim.
|
|
51
|
+
.filter((p) => !p.includes('.') && !isSentinelPhaseId(p))
|
|
52
|
+
.map((p) => parseInt(p, 10))
|
|
53
|
+
.filter((n) => !Number.isNaN(n))
|
|
54
|
+
.sort((a, b) => a - b);
|
|
55
|
+
const diagnostics = [];
|
|
56
|
+
for (let i = 1; i < integerPhases.length; i++) {
|
|
57
|
+
if (integerPhases[i] !== integerPhases[i - 1] + 1) {
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
code: 'C001',
|
|
60
|
+
severity: SEVERITY.WARNING,
|
|
61
|
+
message: `Gap in phase numbering: ${integerPhases[i - 1]} → ${integerPhases[i]}`,
|
|
62
|
+
remedy: adviseRemedy('Create the missing phase directory or renumber to close the gap'),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return diagnostics;
|
|
67
|
+
}
|
|
68
|
+
// ─── C002 — gap in plan numbering within a phase ───────────────────────────
|
|
69
|
+
// (verify.cts:1570-1576)
|
|
70
|
+
// Fidelity note: the original's `phaseLabel` (verify.cts:1532,
|
|
71
|
+
// `posixNormalize(path.relative(planBase, phasePath))`) is a
|
|
72
|
+
// `.planning/`-relative posix path, which for the flat `phases/<dir>` case
|
|
73
|
+
// this rule scans reduces to the bare directory name (`phaseDir` below) —
|
|
74
|
+
// same string, not a narrower one, since `perPhasePlanNumbering` only
|
|
75
|
+
// enumerates the flat `phases/` root (see `PlanningSnapshot`'s own doc
|
|
76
|
+
// comment on this field for the one disclosed scope reduction, an active
|
|
77
|
+
// archived milestone's second phase root, which is out of scope here too).
|
|
78
|
+
function checkC002(snapshot) {
|
|
79
|
+
const diagnostics = [];
|
|
80
|
+
for (const { phaseDir, planNums } of snapshot.perPhasePlanNumbering.value) {
|
|
81
|
+
for (let i = 1; i < planNums.length; i++) {
|
|
82
|
+
if (planNums[i] !== planNums[i - 1] + 1) {
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
code: 'C002',
|
|
85
|
+
severity: SEVERITY.WARNING,
|
|
86
|
+
message: `Gap in plan numbering in ${phaseDir}: plan ${planNums[i - 1]} → ${planNums[i]}`,
|
|
87
|
+
remedy: adviseRemedy('Create the missing plan or renumber to close the gap'),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return diagnostics;
|
|
93
|
+
}
|
|
94
|
+
// ─── C003 — orphan SUMMARY with no matching live PLAN ──────────────────────
|
|
95
|
+
// (verify.cts:1584-1587)
|
|
96
|
+
function checkC003(snapshot) {
|
|
97
|
+
return snapshot.perPhaseOrphanSummaries.value.map(({ phaseDir, orphanSummary }) => ({
|
|
98
|
+
code: 'C003',
|
|
99
|
+
severity: SEVERITY.WARNING,
|
|
100
|
+
message: `Summary ${orphanSummary} in ${phaseDir} has no matching PLAN.md`,
|
|
101
|
+
remedy: adviseRemedy('Create the matching PLAN.md or remove the orphan summary'),
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
// ─── C004 — PLAN missing `wave` frontmatter ────────────────────────────────
|
|
105
|
+
// (verify.cts:1596-1602)
|
|
106
|
+
function checkC004(snapshot) {
|
|
107
|
+
return snapshot.perPhaseWaveMissingPlans.value.map(({ phaseDir, plan }) => ({
|
|
108
|
+
code: 'C004',
|
|
109
|
+
severity: SEVERITY.WARNING,
|
|
110
|
+
message: `${phaseDir}/${plan}: missing 'wave' in frontmatter`,
|
|
111
|
+
remedy: adviseRemedy("Add a 'wave' key to the plan's frontmatter"),
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
115
|
+
const RULES = [
|
|
116
|
+
{
|
|
117
|
+
code: 'C001',
|
|
118
|
+
severity: SEVERITY.WARNING,
|
|
119
|
+
description: 'Gap in disk phase numbering (integer sequence)',
|
|
120
|
+
repairable: false,
|
|
121
|
+
check: checkC001,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
code: 'C002',
|
|
125
|
+
severity: SEVERITY.WARNING,
|
|
126
|
+
description: 'Gap in plan numbering within a phase',
|
|
127
|
+
repairable: false,
|
|
128
|
+
check: checkC002,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
code: 'C003',
|
|
132
|
+
severity: SEVERITY.WARNING,
|
|
133
|
+
description: 'Orphan SUMMARY with no matching live PLAN',
|
|
134
|
+
repairable: false,
|
|
135
|
+
check: checkC003,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
code: 'C004',
|
|
139
|
+
severity: SEVERITY.WARNING,
|
|
140
|
+
description: "PLAN missing 'wave' in frontmatter",
|
|
141
|
+
repairable: false,
|
|
142
|
+
check: checkC004,
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
module.exports = { RULES };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Install Surface Shadowing rule (#2873, epic #2866 Phase 4a — governed by
|
|
4
|
+
* `.gsd/phase/feat-2873-cross-scope-shadowing/40-design.md`).
|
|
5
|
+
*
|
|
6
|
+
* One code, W028, surfacing `install-shadow-report.cts`'s `ShadowReport` as
|
|
7
|
+
* a `/gsd-health` diagnostic — the design doc's row #4 ("one diagnostic,
|
|
8
|
+
* severity WARNING, same projection" as the install-time report). WARNING
|
|
9
|
+
* severity, ADVISE remedy with `risk: NONE`: shadowing is never auto-fixable
|
|
10
|
+
* (there is no single correct scope to remove) so this is advisory only,
|
|
11
|
+
* mirroring `agent-install.cts`'s W010 shape.
|
|
12
|
+
*
|
|
13
|
+
* Reaches OUTSIDE the planning snapshot the same way `agent-install.cts`
|
|
14
|
+
* does for W010: `resolveRuntime(cwd)` (`runtime-slash.cjs`) resolves the
|
|
15
|
+
* runtime id from `process.env.GSD_RUNTIME` / `config.runtime` / the
|
|
16
|
+
* `'claude'` default (never throws), using `snapshot.cwd`
|
|
17
|
+
* (`planning-snapshot.cts`'s `buildPlanningSnapshot` — `path.resolve(cwd)`)
|
|
18
|
+
* as the project directory. `buildShadowReport` is then called with that
|
|
19
|
+
* `cwd` so the local scope resolves against the project actually being
|
|
20
|
+
* health-checked, while `home` is left un-injected so the resolver defaults
|
|
21
|
+
* to `os.homedir()` — the real machine, same production call shape the
|
|
22
|
+
* installer uses.
|
|
23
|
+
*
|
|
24
|
+
* `check(snapshot)` degrades to `[]` (never throws) whenever there is
|
|
25
|
+
* nothing installable to report: `buildShadowReport` itself already
|
|
26
|
+
* degrades an unresolvable runtime (`configHome.kind === 'none'`, e.g.
|
|
27
|
+
* vscode — design row #7) to `reason: RESOLVER_UNAVAILABLE`, which
|
|
28
|
+
* `renderShadowReport` renders as `[]`; the try/catch around both calls
|
|
29
|
+
* below additionally absorbs any other unexpected throw (design row D5),
|
|
30
|
+
* since this rule is advisory and must never make `/gsd-health` itself
|
|
31
|
+
* fail.
|
|
32
|
+
*
|
|
33
|
+
* Design: .gsd/phase/feat-2873-cross-scope-shadowing/40-design.md
|
|
34
|
+
*
|
|
35
|
+
* ADR-457 build-at-publish: source in
|
|
36
|
+
* src/health-diagnostic-rules/install-surface-shadowing.cts, compiled to
|
|
37
|
+
* gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs
|
|
38
|
+
* (gitignored).
|
|
39
|
+
*/
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
|
+
const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
42
|
+
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
|
+
const installShadowReportMod = require("../install-shadow-report.cjs");
|
|
45
|
+
const { buildShadowReport, renderShadowReport } = installShadowReportMod;
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
const runtimeSlashMod = require("../runtime-slash.cjs");
|
|
48
|
+
const { resolveRuntime } = runtimeSlashMod;
|
|
49
|
+
const package_identity_cjs_1 = require("../package-identity.cjs");
|
|
50
|
+
/**
|
|
51
|
+
* `check(snapshot)` for W028 — see module header for the degrade-to-`[]`
|
|
52
|
+
* cases and the runtime-resolution mechanism reused from `agent-install.cts`.
|
|
53
|
+
*/
|
|
54
|
+
function checkInstallSurfaceShadowing(snapshot) {
|
|
55
|
+
let lines;
|
|
56
|
+
let runtime;
|
|
57
|
+
try {
|
|
58
|
+
runtime = resolveRuntime(snapshot.cwd);
|
|
59
|
+
const report = buildShadowReport(runtime, { cwd: snapshot.cwd });
|
|
60
|
+
// `renderShadowReport` returns `[]` for every reason other than
|
|
61
|
+
// SCOPE_SHADOWED (design rows #1, #2, #6, #7, #8, #11), and sanitizes
|
|
62
|
+
// every `declaredRuntime` it interpolates via `sanitizeForRender`
|
|
63
|
+
// internally (`install-shadow-report.cts`) — this rule's `message`
|
|
64
|
+
// reuses that render path rather than re-sanitizing, so the
|
|
65
|
+
// sanitize-at-the-render-seam guarantee (design row #13) holds here too.
|
|
66
|
+
// Trigger names are already SAFE_STEM-gated upstream
|
|
67
|
+
// (`installed-surface-resolver.cts`'s `deriveStemsForKindEntry`) before
|
|
68
|
+
// they ever reach a rendered line — no re-gating needed here either.
|
|
69
|
+
lines = renderShadowReport(report);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Advisory rule: an unresolvable runtime, a `configHome.kind === 'none'`
|
|
73
|
+
// runtime, or any other unexpected failure degrades to "no finding",
|
|
74
|
+
// never a thrown exception that would break `/gsd-health` itself
|
|
75
|
+
// (design row D5).
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
if (lines.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
code: 'W028',
|
|
83
|
+
severity: SEVERITY.WARNING,
|
|
84
|
+
message: lines.join(' '),
|
|
85
|
+
remedy: adviseRemedy(`Review install scopes for ${runtime}: npx ${package_identity_cjs_1.PACKAGE_NAME}@latest`),
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
const RULES = [
|
|
90
|
+
{
|
|
91
|
+
code: 'W028',
|
|
92
|
+
severity: SEVERITY.WARNING,
|
|
93
|
+
description: 'A GSD-owned install scope shadows another on this machine',
|
|
94
|
+
repairable: false,
|
|
95
|
+
check: checkInstallSurfaceShadowing,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
module.exports = { RULES };
|