@opengsd/gsd-core 1.9.1 → 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 +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- 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 +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.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 +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -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 +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- 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 +608 -148
- 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 +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- 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 +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- 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-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- 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 +160 -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-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- 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 +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- 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 +60 -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/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- 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/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -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 +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- 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 +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- 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 +33 -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 +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- 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/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- 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/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- 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/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- 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 +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- 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 +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- 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/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- 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/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- 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 +607 -26
- 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 +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- 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 +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- 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 +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- 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/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- 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-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- 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 +15 -54
- 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-compiled-artifact-sync.cjs +6 -1
- 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-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -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 +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- 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 +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- 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 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Anti-divergence drift guard for the DIAGNOSTIC-RULE raw-`.planning/`-read
|
|
6
|
+
* bypass of the planning snapshot single owner (epic #3180, ADR-3180
|
|
7
|
+
* "Planning Semantic Model Single Owner", §8.1 rule 2).
|
|
8
|
+
*
|
|
9
|
+
* ADR-3180 §8.1 rule 2: a diagnostic rule may see only PARSED values coming
|
|
10
|
+
* off `src/planning-snapshot.cts`, never raw `.planning/` document text —
|
|
11
|
+
* every diagnostic rule is expected to consume the snapshot's already-parsed
|
|
12
|
+
* fields (via the ADR-3180 §7 owner functions: `getMilestoneInfo`,
|
|
13
|
+
* `listMilestonePhaseDirs`, `isPhaseComplete`, `scanPhasePlans`,
|
|
14
|
+
* `stateFieldValue`, etc.), not re-derive its own view of the filesystem with
|
|
15
|
+
* `platformReadSync(`/`readFileSync(`/`readdirSync(`.
|
|
16
|
+
*
|
|
17
|
+
* `cmdValidateHealth` (`src/verify.cts`) is the one diagnostic-rule-shaped
|
|
18
|
+
* function in the repo today that has NOT yet been migrated onto the
|
|
19
|
+
* snapshot — it predates ADR-3180 and still does its own raw reads for
|
|
20
|
+
* roughly thirty W0xx/W1xx diagnostic codes. Migrating it is Phase 11
|
|
21
|
+
* (#3309), not this phase (#3308) — this guard's job is only to make that
|
|
22
|
+
* acknowledged debt VISIBLE and SHRINK-ONLY via a ratchet baseline, exactly
|
|
23
|
+
* like `scripts/lint-planning-prompt-drift.cjs` and
|
|
24
|
+
* `scripts/lint-state-field-drift.cjs` do for their own re-derivations, so it
|
|
25
|
+
* cannot silently grow while Phase 11 is pending.
|
|
26
|
+
*
|
|
27
|
+
* FUNCTION-SCOPED, not whole-file or whole-repo. `DIAGNOSTIC_RULE_FUNCTIONS`
|
|
28
|
+
* (a `Map<repo-relative file, Set<function name>>`) names the exact functions
|
|
29
|
+
* this rule applies to — the semantic inverse of
|
|
30
|
+
* `lint-completion-ratio-drift.cjs`'s `FUNCTION_SCOPED_EXEMPTIONS` (which
|
|
31
|
+
* names functions a rule does NOT apply to), but the identical data shape and
|
|
32
|
+
* lookup pattern. A raw-read primitive anywhere OUTSIDE a registered function
|
|
33
|
+
* — including elsewhere in the very same file — is not this derivation and is
|
|
34
|
+
* never flagged; `src/verify.cts` itself is full of legitimate raw reads
|
|
35
|
+
* outside `cmdValidateHealth` (health-check plumbing, non-diagnostic-rule
|
|
36
|
+
* helpers) that this guard must not see.
|
|
37
|
+
*
|
|
38
|
+
* Line-to-enclosing-function attribution is a TRIMMED copy of
|
|
39
|
+
* `lint-state-field-drift.cjs`'s `buildFunctionInfo` — the same
|
|
40
|
+
* comment/string-stripping tokenizer (`scanCode`) feeding the same
|
|
41
|
+
* brace-depth function-frame stack, producing the same `innermostAt[line] ->
|
|
42
|
+
* function name | null` attribution — with that guard's LADDER WINDOW
|
|
43
|
+
* co-occurrence logic dropped entirely: this guard only ever needs "which
|
|
44
|
+
* function encloses this line", never a multi-line pattern within one
|
|
45
|
+
* function body.
|
|
46
|
+
*
|
|
47
|
+
* RATCHET, not an allowlist — mirrors `lint-planning-prompt-drift.cjs`'s
|
|
48
|
+
* `diffAgainstBaseline`/`writeBaseline`/`dedupeViolationsForBaseline`/
|
|
49
|
+
* `sortEntries` machinery verbatim (baseline path, owner issue, and
|
|
50
|
+
* `derivation` label are the only differences): a violation whose (file,
|
|
51
|
+
* text) pair is already RECORDED in the baseline is KNOWN and never fails; an
|
|
52
|
+
* unrecorded pair is FRESH and fails; a recorded pair that no longer fires is
|
|
53
|
+
* STALE and ALSO fails, forcing `--update` (run by a maintainer, expected to
|
|
54
|
+
* run to completion — zero remaining entries — once #3309 lands) to prune it.
|
|
55
|
+
*
|
|
56
|
+
* Tree-walk / root-confinement / symlink / sanitizer machinery is shared via
|
|
57
|
+
* `scripts/lib/drift-scan.cjs`, exactly like every sibling guard.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
const fs = require('node:fs');
|
|
61
|
+
const path = require('node:path');
|
|
62
|
+
const driftScan = require('./lib/drift-scan.cjs');
|
|
63
|
+
const { sanitizeForReport, scanTree } = driftScan;
|
|
64
|
+
|
|
65
|
+
// Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
|
|
66
|
+
const SCAN_DIRS = ['src'];
|
|
67
|
+
const SCAN_EXT = new Set(['.cts']);
|
|
68
|
+
|
|
69
|
+
const BASELINE_REL_PATH = path.join('scripts', 'baselines', 'planning-snapshot-bypass-baseline.json');
|
|
70
|
+
|
|
71
|
+
// ADR-3180 §8.1 rule 2's acknowledged debt is owned by Phase 11 (#3309, "give
|
|
72
|
+
// cmdValidateHealth the snapshot"), NOT the epic (#3180) itself and NOT
|
|
73
|
+
// Phase 8 (#3218, the sibling prompt-layer guard's owner issue — a different
|
|
74
|
+
// derivation entirely).
|
|
75
|
+
const RATCHET_OWNER_ISSUE = '#3309';
|
|
76
|
+
|
|
77
|
+
// Per ADR-3180 §8.1 rule 2: function-scoped registry of exactly which
|
|
78
|
+
// diagnostic-rule-shaped functions this guard applies to. Not a bare file
|
|
79
|
+
// allowlist (ADR-3180 Decision 4(a) forbids that) — every other function in
|
|
80
|
+
// `src/verify.cts`, and every function in every other file, is scanned like
|
|
81
|
+
// normal code and simply never matches because it is not in this Map.
|
|
82
|
+
const DIAGNOSTIC_RULE_FUNCTIONS = new Map([[path.join('src', 'verify.cts'), new Set(['cmdValidateHealth'])]]);
|
|
83
|
+
|
|
84
|
+
// `scanTree` builds its repo-relative path via `path.relative()`, which uses
|
|
85
|
+
// NATIVE separators: on Windows that is `src\verify.cts`, while
|
|
86
|
+
// `DIAGNOSTIC_RULE_FUNCTIONS` above and the committed baseline both store
|
|
87
|
+
// POSIX paths (`src/verify.cts`). Normalized UNCONDITIONALLY — never gated on
|
|
88
|
+
// `process.platform` — so the POSIX path is never the only tested case (see
|
|
89
|
+
// `lint-planning-prompt-drift.cjs`'s `toPosixRel` for the full rationale;
|
|
90
|
+
// applied here at the same single seam: `findSnapshotBypassDrift` is the only
|
|
91
|
+
// place a repo-relative path enters this guard's violation objects).
|
|
92
|
+
function toPosixRel(relPath) {
|
|
93
|
+
return relPath.replace(/\\/g, '/');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Looks up `DIAGNOSTIC_RULE_FUNCTIONS` by POSIX-normalizing BOTH the incoming
|
|
97
|
+
// `relPath` and each registered key before comparing, so a native-separator
|
|
98
|
+
// caller (Windows) and a POSIX-separator caller (every other platform, and
|
|
99
|
+
// every test in this repo) resolve to the same registered `Set` — see
|
|
100
|
+
// `toPosixRel` above.
|
|
101
|
+
function lookupRegisteredFunctions(relPath) {
|
|
102
|
+
const posix = toPosixRel(relPath);
|
|
103
|
+
for (const [key, fns] of DIAGNOSTIC_RULE_FUNCTIONS) {
|
|
104
|
+
if (toPosixRel(key) === posix) return fns;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A raw `.planning/` filesystem read primitive. ADR-3180 §7 owner functions
|
|
110
|
+
// (`getMilestoneInfo`, `listMilestonePhaseDirs`, `isPhaseComplete`,
|
|
111
|
+
// `scanPhasePlans`, `stateFieldValue`, ...) never match this — none of their
|
|
112
|
+
// names or call shapes contain `platformReadSync(`, `readFileSync(`, or
|
|
113
|
+
// `readdirSync(`, so a registered function that has already been migrated
|
|
114
|
+
// onto the snapshot correctly stops producing violations without needing any
|
|
115
|
+
// separate allowlist of "safe" calls.
|
|
116
|
+
const RAW_READ_RE = /platformReadSync\(|readFileSync\(|readdirSync\(/;
|
|
117
|
+
|
|
118
|
+
// `function NAME(` — top-level or nested, matches the DECLARATION line
|
|
119
|
+
// itself (mirrors `lint-state-field-drift.cjs`'s `FUNCTION_DECL_RE`).
|
|
120
|
+
const FUNCTION_DECL_RE = /\bfunction\s+([A-Za-z_$][\w$]*)\s*\(/;
|
|
121
|
+
|
|
122
|
+
// `const NAME = (...): ReturnType => {` — an arrow function assigned to a
|
|
123
|
+
// const, whose own line already carries `=>\s*\{` (mirrors
|
|
124
|
+
// `lint-state-field-drift.cjs`'s `ARROW_CONST_RE`).
|
|
125
|
+
const ARROW_CONST_RE = /\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*\([^)]*\)\s*(?::\s*[^=]+)?=>\s*\{/;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Strip line-comments, block comments, and the CONTENTS of string/template
|
|
129
|
+
* literals (excluded from brace-depth counting so a brace inside a string
|
|
130
|
+
* never desyncs `depth`) while keeping quoted text verbatim in `detect` (so
|
|
131
|
+
* declaration/call regexes can still see identifiers that happen to sit
|
|
132
|
+
* inside a template literal's interpolation-free text). Trimmed, unchanged
|
|
133
|
+
* copy of `lint-state-field-drift.cjs`'s `scanCode` — this guard needs the
|
|
134
|
+
* identical comment/string-safety, not a domain-specific variant.
|
|
135
|
+
* Returns `{ detect, braces }`, one string per input line.
|
|
136
|
+
*/
|
|
137
|
+
function scanCode(lines) {
|
|
138
|
+
const detect = new Array(lines.length);
|
|
139
|
+
const braces = new Array(lines.length);
|
|
140
|
+
let inBlockComment = false;
|
|
141
|
+
let inTemplate = false;
|
|
142
|
+
for (let li = 0; li < lines.length; li++) {
|
|
143
|
+
const line = lines[li];
|
|
144
|
+
let outDetect = '';
|
|
145
|
+
let outBraces = '';
|
|
146
|
+
let i = 0;
|
|
147
|
+
if (inTemplate) {
|
|
148
|
+
const start = i;
|
|
149
|
+
while (i < line.length) {
|
|
150
|
+
if (line[i] === '\\') {
|
|
151
|
+
i += 2;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (line[i] === '`') {
|
|
155
|
+
i++;
|
|
156
|
+
inTemplate = false;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
i++;
|
|
160
|
+
}
|
|
161
|
+
outDetect += line.slice(start, i);
|
|
162
|
+
if (inTemplate) {
|
|
163
|
+
detect[li] = outDetect;
|
|
164
|
+
braces[li] = '';
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
while (i < line.length) {
|
|
169
|
+
if (inBlockComment) {
|
|
170
|
+
const close = line.indexOf('*/', i);
|
|
171
|
+
if (close === -1) {
|
|
172
|
+
i = line.length;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
i = close + 2;
|
|
176
|
+
inBlockComment = false;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const ch = line[i];
|
|
180
|
+
if (ch === '/' && line[i + 1] === '/') {
|
|
181
|
+
i = line.length;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
if (ch === '/' && line[i + 1] === '*') {
|
|
185
|
+
inBlockComment = true;
|
|
186
|
+
i += 2;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (ch === "'" || ch === '"') {
|
|
190
|
+
const quote = ch;
|
|
191
|
+
const start = i;
|
|
192
|
+
let j = i + 1;
|
|
193
|
+
while (j < line.length) {
|
|
194
|
+
if (line[j] === '\\') {
|
|
195
|
+
j += 2;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (line[j] === quote) {
|
|
199
|
+
j++;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
j++;
|
|
203
|
+
}
|
|
204
|
+
outDetect += line.slice(start, j);
|
|
205
|
+
i = j;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (ch === '`') {
|
|
209
|
+
const start = i;
|
|
210
|
+
let j = i + 1;
|
|
211
|
+
let closed = false;
|
|
212
|
+
while (j < line.length) {
|
|
213
|
+
if (line[j] === '\\') {
|
|
214
|
+
j += 2;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (line[j] === '`') {
|
|
218
|
+
j++;
|
|
219
|
+
closed = true;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
j++;
|
|
223
|
+
}
|
|
224
|
+
if (!closed) {
|
|
225
|
+
outDetect += line.slice(start);
|
|
226
|
+
inTemplate = true;
|
|
227
|
+
i = line.length;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
outDetect += line.slice(start, j);
|
|
231
|
+
i = j;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
outDetect += ch;
|
|
235
|
+
outBraces += ch;
|
|
236
|
+
i++;
|
|
237
|
+
}
|
|
238
|
+
detect[li] = outDetect;
|
|
239
|
+
braces[li] = outBraces;
|
|
240
|
+
}
|
|
241
|
+
return { detect, braces };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Trimmed copy of `lint-state-field-drift.cjs`'s `buildFunctionInfo`: a
|
|
246
|
+
* single pass over `lines` maintaining a brace-depth stack of open named
|
|
247
|
+
* function frames, producing `innermostAt[lineIndex] -> function name |
|
|
248
|
+
* null` — which function frame is innermost at each source line. The LADDER
|
|
249
|
+
* WINDOW co-occurrence tracking from the sibling guard is dropped entirely;
|
|
250
|
+
* this guard needs only line-to-enclosing-function attribution.
|
|
251
|
+
*/
|
|
252
|
+
function buildFunctionInfo(lines) {
|
|
253
|
+
const { detect, braces } = scanCode(lines);
|
|
254
|
+
const innermostAt = new Array(lines.length).fill(null);
|
|
255
|
+
const stack = []; // { name, openDepth }
|
|
256
|
+
let depth = 0;
|
|
257
|
+
let pendingDeclName = null;
|
|
258
|
+
for (let i = 0; i < lines.length; i++) {
|
|
259
|
+
const detectCode = detect[i];
|
|
260
|
+
const braceCode = braces[i];
|
|
261
|
+
|
|
262
|
+
let immediateName = null;
|
|
263
|
+
if (detectCode.trim()) {
|
|
264
|
+
const arrowMatch = ARROW_CONST_RE.exec(detectCode);
|
|
265
|
+
if (arrowMatch) {
|
|
266
|
+
immediateName = arrowMatch[1];
|
|
267
|
+
} else {
|
|
268
|
+
const declMatch = FUNCTION_DECL_RE.exec(detectCode);
|
|
269
|
+
if (declMatch) pendingDeclName = declMatch[1];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const opens = (braceCode.match(/\{/g) || []).length;
|
|
274
|
+
const closes = (braceCode.match(/\}/g) || []).length;
|
|
275
|
+
depth += opens - closes;
|
|
276
|
+
|
|
277
|
+
if (immediateName) stack.push({ name: immediateName, openDepth: depth });
|
|
278
|
+
|
|
279
|
+
if (pendingDeclName) {
|
|
280
|
+
if (opens > 0) {
|
|
281
|
+
stack.push({ name: pendingDeclName, openDepth: depth });
|
|
282
|
+
pendingDeclName = null;
|
|
283
|
+
} else if (detectCode.includes(';')) {
|
|
284
|
+
pendingDeclName = null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
while (stack.length > 0 && depth < stack[stack.length - 1].openDepth) stack.pop();
|
|
289
|
+
|
|
290
|
+
innermostAt[i] = stack.length > 0 ? stack[stack.length - 1].name : null;
|
|
291
|
+
}
|
|
292
|
+
return { innermostAt };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Pure: find every raw `.planning/`-read line inside a
|
|
297
|
+
* `DIAGNOSTIC_RULE_FUNCTIONS`-registered function in `text`. `relPath` is the
|
|
298
|
+
* repo-relative path (native separators or POSIX, either is accepted) — used
|
|
299
|
+
* both as the (POSIX-normalized) `file` on every result and to look up the
|
|
300
|
+
* registered function set for this file. A file with no registered entry
|
|
301
|
+
* short-circuits to `[]` immediately, before any line is scanned.
|
|
302
|
+
* Returns [{ file, line, found, text }] — `file` is always POSIX-separated,
|
|
303
|
+
* `text` is the TRIMMED source line, the same value the baseline keys on.
|
|
304
|
+
*/
|
|
305
|
+
function findSnapshotBypassDrift(text, relPath) {
|
|
306
|
+
const registeredFns = lookupRegisteredFunctions(relPath);
|
|
307
|
+
if (!registeredFns) return [];
|
|
308
|
+
|
|
309
|
+
const file = toPosixRel(relPath);
|
|
310
|
+
const lines = text.split('\n');
|
|
311
|
+
const { innermostAt } = buildFunctionInfo(lines);
|
|
312
|
+
const out = [];
|
|
313
|
+
for (let i = 0; i < lines.length; i++) {
|
|
314
|
+
const fn = innermostAt[i];
|
|
315
|
+
if (!fn || !registeredFns.has(fn)) continue;
|
|
316
|
+
const line = lines[i];
|
|
317
|
+
const match = RAW_READ_RE.exec(line);
|
|
318
|
+
if (!match) continue;
|
|
319
|
+
out.push({ file, line: i + 1, found: match[0], text: line.trim() });
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Scan the authored source tree and return every registered-function raw-read
|
|
326
|
+
* bypass, each annotated with the (POSIX-normalized) repo-relative file path.
|
|
327
|
+
*/
|
|
328
|
+
function scanRepo(root) {
|
|
329
|
+
return scanTree({
|
|
330
|
+
root,
|
|
331
|
+
scanDirs: SCAN_DIRS,
|
|
332
|
+
scanExt: SCAN_EXT,
|
|
333
|
+
onFile(rel, text) {
|
|
334
|
+
return findSnapshotBypassDrift(text, rel);
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Read and parse the ratchet baseline. Returns `{ entries, errors }` —
|
|
341
|
+
* mirrors `lint-planning-prompt-drift.cjs`'s `loadBaseline` verbatim, adapted
|
|
342
|
+
* to this guard's baseline path.
|
|
343
|
+
*/
|
|
344
|
+
function loadBaseline(root) {
|
|
345
|
+
const baselinePath = path.join(root, BASELINE_REL_PATH);
|
|
346
|
+
if (!fs.existsSync(baselinePath)) {
|
|
347
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} is missing — run \`node scripts/lint-planning-snapshot-bypass-drift.cjs --update\` to generate it`] };
|
|
348
|
+
}
|
|
349
|
+
const raw = fs.readFileSync(baselinePath, 'utf8');
|
|
350
|
+
if (raw.trim() === '') {
|
|
351
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} is present but empty`] };
|
|
352
|
+
}
|
|
353
|
+
let doc;
|
|
354
|
+
try {
|
|
355
|
+
doc = JSON.parse(raw);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} is not valid JSON: ${err.message}`] };
|
|
358
|
+
}
|
|
359
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
360
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} must be a JSON object, got ${Array.isArray(doc) ? 'array' : typeof doc}`] };
|
|
361
|
+
}
|
|
362
|
+
if (!Array.isArray(doc.entries)) {
|
|
363
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH}: "entries" must be an array, got ${JSON.stringify(doc.entries)}`] };
|
|
364
|
+
}
|
|
365
|
+
const errors = [];
|
|
366
|
+
const entries = [];
|
|
367
|
+
doc.entries.forEach((entry, i) => {
|
|
368
|
+
const where = `${BASELINE_REL_PATH}.entries[${i}]`;
|
|
369
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
370
|
+
errors.push(`${where} must be an object, got ${JSON.stringify(entry)}`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (typeof entry.file !== 'string' || entry.file === '') {
|
|
374
|
+
errors.push(`${where}.file must be a non-empty string, got ${JSON.stringify(entry.file)}`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (typeof entry.text !== 'string' || entry.text === '') {
|
|
378
|
+
errors.push(`${where}.text must be a non-empty string, got ${JSON.stringify(entry.text)}`);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (entry.count !== undefined && !(Number.isInteger(entry.count) && entry.count >= 1)) {
|
|
382
|
+
errors.push(`${where}.count must be a positive integer when present, got ${JSON.stringify(entry.count)}`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
entries.push(entry);
|
|
386
|
+
});
|
|
387
|
+
return { entries, errors };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Diff scanned `violations` against baseline `entries`, matched by the pair
|
|
392
|
+
* (`file`, TRIMMED `text`), count-aware — mirrors
|
|
393
|
+
* `lint-planning-prompt-drift.cjs`'s `diffAgainstBaseline` verbatim. See that
|
|
394
|
+
* module's header for the full "COUNT, not duplicate rows" rationale.
|
|
395
|
+
*/
|
|
396
|
+
function diffAgainstBaseline(violations, baseline) {
|
|
397
|
+
const key = (file, text) => `${file} ${text}`;
|
|
398
|
+
|
|
399
|
+
const actualByKey = new Map();
|
|
400
|
+
for (const v of violations) {
|
|
401
|
+
const k = key(v.file, v.text);
|
|
402
|
+
let vs = actualByKey.get(k);
|
|
403
|
+
if (!vs) { vs = []; actualByKey.set(k, vs); }
|
|
404
|
+
vs.push(v);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const knownKeys = new Set(baseline.map((e) => key(e.file, e.text)));
|
|
408
|
+
|
|
409
|
+
const fresh = [];
|
|
410
|
+
const stale = [];
|
|
411
|
+
|
|
412
|
+
for (const [k, vs] of actualByKey) {
|
|
413
|
+
if (!knownKeys.has(k)) fresh.push(...vs);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (const entry of baseline) {
|
|
417
|
+
const k = key(entry.file, entry.text);
|
|
418
|
+
const expected = entry.count ?? 1;
|
|
419
|
+
const vs = actualByKey.get(k) || [];
|
|
420
|
+
const actual = vs.length;
|
|
421
|
+
if (actual < expected) {
|
|
422
|
+
stale.push({ ...entry, count: expected, actualCount: actual });
|
|
423
|
+
} else if (actual > expected) {
|
|
424
|
+
fresh.push(...vs.slice(expected));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return { fresh, stale };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Stable sort: by `file`, then by `text`. */
|
|
432
|
+
function sortEntries(entries) {
|
|
433
|
+
return [...entries].sort((a, b) => {
|
|
434
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
435
|
+
if (a.text !== b.text) return a.text < b.text ? -1 : 1;
|
|
436
|
+
return 0;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Collapse `violations` into one baseline row per distinct (file, text) pair,
|
|
442
|
+
* carrying a `count` of how many occurrences that pair has in THIS run. Pure;
|
|
443
|
+
* no I/O.
|
|
444
|
+
*/
|
|
445
|
+
function dedupeViolationsForBaseline(violations) {
|
|
446
|
+
const order = [];
|
|
447
|
+
const byKey = new Map();
|
|
448
|
+
for (const v of violations) {
|
|
449
|
+
const k = `${v.file} ${v.text}`;
|
|
450
|
+
let entry = byKey.get(k);
|
|
451
|
+
if (!entry) {
|
|
452
|
+
entry = { file: v.file, text: v.text, derivation: 'planning-snapshot-bypass', owner_issue: RATCHET_OWNER_ISSUE, count: 0 };
|
|
453
|
+
byKey.set(k, entry);
|
|
454
|
+
order.push(entry);
|
|
455
|
+
}
|
|
456
|
+
entry.count += 1;
|
|
457
|
+
}
|
|
458
|
+
return order;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function writeBaseline(root, violations) {
|
|
462
|
+
const entries = sortEntries(dedupeViolationsForBaseline(violations));
|
|
463
|
+
const doc = {
|
|
464
|
+
$comment:
|
|
465
|
+
'ADR-3180 §8.1 rule 2 ratchet, owned by Phase 11 (#3309). See scripts/lint-planning-snapshot-bypass-drift.cjs. '
|
|
466
|
+
+ 'SHRINK-ONLY: entries are removed as cmdValidateHealth migrates onto src/planning-snapshot.cts; new or '
|
|
467
|
+
+ 'changed entries fail lint:ci. `count` is the number of byte-identical (file, text) occurrences '
|
|
468
|
+
+ 'acknowledged at this site — a run producing fewer fails as a partial migration, more fails as an '
|
|
469
|
+
+ 'unacknowledged new copy.',
|
|
470
|
+
entries,
|
|
471
|
+
};
|
|
472
|
+
const baselinePath = path.join(root, BASELINE_REL_PATH);
|
|
473
|
+
fs.mkdirSync(path.dirname(baselinePath), { recursive: true });
|
|
474
|
+
fs.writeFileSync(baselinePath, `${JSON.stringify(doc, null, 2)}\n`, 'utf8');
|
|
475
|
+
return entries;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function main() {
|
|
479
|
+
const root = path.join(__dirname, '..');
|
|
480
|
+
const update = process.argv.includes('--update');
|
|
481
|
+
const violations = scanRepo(root);
|
|
482
|
+
|
|
483
|
+
if (update) {
|
|
484
|
+
const entries = writeBaseline(root, violations);
|
|
485
|
+
process.stdout.write(`ok planning-snapshot-bypass: baseline regenerated with ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'}\n`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const { entries: baseline, errors } = loadBaseline(root);
|
|
490
|
+
if (errors.length > 0) {
|
|
491
|
+
process.stderr.write('planning-snapshot-bypass: baseline load error(s):\n');
|
|
492
|
+
for (const e of errors) process.stderr.write(` ${e}\n`);
|
|
493
|
+
process.exitCode = 1;
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const { fresh, stale } = diffAgainstBaseline(violations, baseline);
|
|
498
|
+
|
|
499
|
+
if (fresh.length === 0 && stale.length === 0) {
|
|
500
|
+
process.stdout.write(`ok planning-snapshot-bypass: no unacknowledged raw .planning/ reads in registered diagnostic-rule functions (${baseline.length} known)\n`);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (fresh.length > 0) {
|
|
505
|
+
process.stderr.write('planning-snapshot-bypass: NEW raw .planning/ read(s) found inside a DIAGNOSTIC_RULE_FUNCTIONS-registered function.\n');
|
|
506
|
+
process.stderr.write('Route the read through src/planning-snapshot.cts (ADR-3180 §7 owner functions: getMilestoneInfo,\n');
|
|
507
|
+
process.stderr.write('listMilestonePhaseDirs, isPhaseComplete, scanPhasePlans, stateFieldValue, ...) instead of calling\n');
|
|
508
|
+
process.stderr.write(`platformReadSync(/readFileSync(/readdirSync( directly, or add an acknowledged entry to ${BASELINE_REL_PATH}\n`);
|
|
509
|
+
process.stderr.write('via --update:\n');
|
|
510
|
+
for (const v of fresh) {
|
|
511
|
+
process.stderr.write(` ${sanitizeForReport(v.file)}:${v.line} ${sanitizeForReport(v.found)} ${sanitizeForReport(v.text)}\n`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (stale.length > 0) {
|
|
516
|
+
process.stderr.write('\nplanning-snapshot-bypass: STALE baseline entr' + (stale.length === 1 ? 'y' : 'ies') + " (fully migrated, or a PARTIAL migration — fewer occurrences found than acknowledged; delete or re-record the row):\n");
|
|
517
|
+
for (const e of stale) {
|
|
518
|
+
process.stderr.write(` ${sanitizeForReport(e.file)} ${sanitizeForReport(e.text)} (found ${e.actualCount}/${e.count} acknowledged occurrence${e.count === 1 ? '' : 's'})\n`);
|
|
519
|
+
}
|
|
520
|
+
process.stderr.write(`\n remedy: node scripts/lint-planning-snapshot-bypass-drift.cjs --update\n`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
process.exitCode = 1;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (require.main === module) main();
|
|
527
|
+
|
|
528
|
+
module.exports = {
|
|
529
|
+
findSnapshotBypassDrift,
|
|
530
|
+
scanRepo,
|
|
531
|
+
toPosixRel,
|
|
532
|
+
loadBaseline,
|
|
533
|
+
diffAgainstBaseline,
|
|
534
|
+
writeBaseline,
|
|
535
|
+
dedupeViolationsForBaseline,
|
|
536
|
+
sortEntries,
|
|
537
|
+
buildFunctionInfo,
|
|
538
|
+
DIAGNOSTIC_RULE_FUNCTIONS,
|
|
539
|
+
RAW_READ_RE,
|
|
540
|
+
SCAN_DIRS,
|
|
541
|
+
SCAN_EXT,
|
|
542
|
+
BASELINE_REL_PATH,
|
|
543
|
+
RATCHET_OWNER_ISSUE,
|
|
544
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* lint-regression-test-names.cjs — ban NEW top-level bug-NNNN test files.
|
|
5
|
+
* lint-regression-test-names.cjs — ban NEW top-level bug-NNNN/fix-NNNN/issue-NNNN test files.
|
|
6
6
|
*
|
|
7
7
|
* ## Why
|
|
8
8
|
*
|
|
@@ -17,20 +17,22 @@
|
|
|
17
17
|
* ## What this enforces
|
|
18
18
|
*
|
|
19
19
|
* Identity ratchet (scripts/lib/allowlist-ratchet.cjs) over basenames matching
|
|
20
|
-
* /^bug-\d+.*\.test\.cjs$/ in tests/:
|
|
21
|
-
* - A NEW bug-* file (not in the allowlist) fails: fold
|
|
22
|
-
* the owning module's test file instead.
|
|
23
|
-
* - A REMOVED bug-* file with a stale allowlist entry
|
|
24
|
-
*
|
|
25
|
-
* baseline only
|
|
20
|
+
* /^(?:bug|fix|issue)-\d+.*\.test\.cjs$/ in tests/:
|
|
21
|
+
* - A NEW bug-*, fix-*, or issue-* file (not in the allowlist) fails: fold
|
|
22
|
+
* the regression into the owning module's test file instead.
|
|
23
|
+
* - A REMOVED bug-*, fix-*, or issue-* file with a stale allowlist entry
|
|
24
|
+
* also fails: prune the entry from
|
|
25
|
+
* scripts/lint-regression-test-names.allowlist.json so the baseline only
|
|
26
|
+
* ever shrinks.
|
|
26
27
|
*
|
|
27
28
|
* ## --update (allowlist drift repair)
|
|
28
29
|
*
|
|
29
30
|
* `node scripts/lint-regression-test-names.cjs --update` regenerates the
|
|
30
31
|
* allowlist from the files currently in tests/ and reports what changed.
|
|
31
32
|
* Use it when the failure is INHERITED drift, not your own new file — e.g.
|
|
32
|
-
* the base branch merged bug-* files without feeding the
|
|
33
|
-
* #947/#948/#950 race after the ratchet landed), or after a
|
|
33
|
+
* the base branch merged bug-*, fix-*, or issue-* files without feeding the
|
|
34
|
+
* allowlist (the #947/#948/#950 race after the ratchet landed), or after a
|
|
35
|
+
* rebase. The
|
|
34
36
|
* allowlist is a snapshot artifact: regenerate it AFTER rebasing, never
|
|
35
37
|
* carry a pre-rebase copy through.
|
|
36
38
|
*
|
|
@@ -49,7 +51,7 @@ const ALLOWLIST_PATH =
|
|
|
49
51
|
process.env.GSD_LINT_REGRESSION_ALLOWLIST ||
|
|
50
52
|
path.join(__dirname, 'lint-regression-test-names.allowlist.json');
|
|
51
53
|
|
|
52
|
-
const BUG_FILE_RE = /^bug-\d+.*\.test\.cjs$/;
|
|
54
|
+
const BUG_FILE_RE = /^(?:bug|fix|issue)-\d+.*\.test\.cjs$/;
|
|
53
55
|
|
|
54
56
|
function main() {
|
|
55
57
|
const args = process.argv.slice(2);
|
|
@@ -97,8 +99,8 @@ function main() {
|
|
|
97
99
|
for (const msg of failures) console.error(msg);
|
|
98
100
|
if (novel.length > 0) {
|
|
99
101
|
console.error(
|
|
100
|
-
'\nIf this PR added the file(s) above: new bug-NNNN
|
|
101
|
-
"accepted — add the regression case to the owning module's test file " +
|
|
102
|
+
'\nIf this PR added the file(s) above: new bug-NNNN/fix-NNNN/issue-NNNN ' +
|
|
103
|
+
"test files are not accepted — add the regression case to the owning module's test file " +
|
|
102
104
|
"(e.g. a describe('regressions') block in tests/<module>.test.cjs) instead.\n" +
|
|
103
105
|
'If the file(s) came from the base branch (inherited allowlist drift, ' +
|
|
104
106
|
'e.g. after a rebase): run ' +
|
|
@@ -110,7 +112,7 @@ function main() {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
console.log(
|
|
113
|
-
`ok lint-regression-test-names: ${current.length} grandfathered bug-* file(s), no novel offenders`
|
|
115
|
+
`ok lint-regression-test-names: ${current.length} grandfathered bug-*/fix-*/issue-* file(s), no novel offenders`
|
|
114
116
|
);
|
|
115
117
|
}
|
|
116
118
|
|