@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,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Anti-divergence drift guard for the milestone-WINDOWING seam
|
|
6
|
+
* (epic #3180, issue #3184, ADR-3180 "Planning Semantic Model Single Owner").
|
|
7
|
+
*
|
|
8
|
+
* `src/roadmap-parser.cts` is the SINGLE canonical owner of "where does a
|
|
9
|
+
* given milestone's ROADMAP section begin and end" — `computeMilestoneSectionEnd`,
|
|
10
|
+
* `locateMilestoneHeadings`, and `isMilestoneBoundedInRoadmap`. Every other
|
|
11
|
+
* module that hand-rolls a heading-level quantifier together with the
|
|
12
|
+
* milestone-boundary shape (a non-Phase heading carrying a version token or a
|
|
13
|
+
* shipped/active marker) is a re-derivation that can silently drift from the
|
|
14
|
+
* owner — the exact defect class #2562 fixed in one copy and never reached
|
|
15
|
+
* the other two (design doc: `currentMilestoneRawRanges::computeSectionEnd`
|
|
16
|
+
* carried a "keep in sync" comment that was already evidence the risk was
|
|
17
|
+
* known, not controlled).
|
|
18
|
+
*
|
|
19
|
+
* Per ADR-3180 Decision 4(a) this guard discovers call sites by SCANNING THE
|
|
20
|
+
* WHOLE `src/` TREE, not by consulting an allowlist of known files — an
|
|
21
|
+
* allowlist only measures re-derivations in files someone remembered to
|
|
22
|
+
* list. Exemptions below are FUNCTION-SCOPED with a written reason, never a
|
|
23
|
+
* bare file allowlist, mirroring `lint-plan-count-drift.cjs`'s precedent.
|
|
24
|
+
*
|
|
25
|
+
* Detection is intentionally NARROW, mirroring the plan-count-drift and
|
|
26
|
+
* phase-id-drift precedents: a line is a re-derivation when it carries BOTH,
|
|
27
|
+
* in ONE source line:
|
|
28
|
+
* (a) a markdown heading-level quantifier token — `#{N,M}`, e.g. `#{1,3}`,
|
|
29
|
+
* `#{2,3}`, `#{2,4}` — inside either a regex literal or a
|
|
30
|
+
* quoted/backticked string, AND
|
|
31
|
+
* (b) a milestone-window token: either the negative-lookahead phase
|
|
32
|
+
* exclusion (`(?!Phase` / `(?!Phase\s+\S)`) or the milestone
|
|
33
|
+
* boundary-marker set — a `v\d+\.\d+`-shaped version token appearing
|
|
34
|
+
* together with any of the ✅ 📋 🚧 shipped/active markers.
|
|
35
|
+
* Token (b) is deliberately narrow: a PHASE-heading regex (`#{2,4}\s*Phase`)
|
|
36
|
+
* carries (a) alone, constantly, throughout this codebase (phase-numbering,
|
|
37
|
+
* plan-index, wave-scheduling call sites) and must NOT be flagged — it asks
|
|
38
|
+
* "where is phase N's heading", a different, already-single-owned question
|
|
39
|
+
* (#2121). Only a line that ALSO carries the milestone-boundary shape — the
|
|
40
|
+
* one `computeMilestoneSectionEnd`/`locateMilestoneHeadings` compute — is a
|
|
41
|
+
* candidate re-derivation of THIS derivation.
|
|
42
|
+
*
|
|
43
|
+
* Both `(a)` and `(b)` must be readable through JS regex-literal AND
|
|
44
|
+
* string/template-literal escaping: the two pre-#3184 `state.cts`
|
|
45
|
+
* re-derivations this design is modelled on were
|
|
46
|
+
* `new RegExp(\`^#{1,3}\\s+(?!Phase\\s+\\S)...\`)` — i.e. the SAME source
|
|
47
|
+
* text as a real `/.../ ` regex literal, just doubly backslash-escaped
|
|
48
|
+
* because it lives inside a template literal. `HEADING_QUANTIFIER_RE` and
|
|
49
|
+
* `PHASE_LOOKAHEAD_RE` match either escaping level unchanged (no backslash
|
|
50
|
+
* appears inside `#{`/`}`/`(?!Phase`'s literal characters); `VERSION_TOKEN_RE`
|
|
51
|
+
* explicitly tolerates ONE or TWO backslashes before each `d`/`.` for exactly
|
|
52
|
+
* this reason. Regex-LITERAL boundaries (used only to extract a reportable
|
|
53
|
+
* `found` fragment, never for detection itself, which tests the raw line) are
|
|
54
|
+
* located via the shared `readRegexLiteralAt` tokenizer
|
|
55
|
+
* (`scripts/lib/drift-scan.cjs`) — a single left-to-right, no-backtracking
|
|
56
|
+
* pass — never a backtracking "find the regex literal" regex (CodeQL js/redos
|
|
57
|
+
* runs on `lint:ci`; see that module's own header for the full rationale).
|
|
58
|
+
* `readStringLiteralAt` below is the same style, written locally for
|
|
59
|
+
* quoted/backticked strings (not shared — `lint-plan-count-drift.cjs` has no
|
|
60
|
+
* equivalent need, since its own literal-bearing shape is regex-only).
|
|
61
|
+
*
|
|
62
|
+
* Owner file: `src/roadmap-parser.cts` DEFINES this grammar, but it is NOT
|
|
63
|
+
* exempt as a whole file — that was the original design (a bare per-file
|
|
64
|
+
* allowlist) and it closed off exactly the blind spot ADR-3180 Decision 4(a)
|
|
65
|
+
* warns about: `getMilestoneInfo`, added later in this same owner file,
|
|
66
|
+
* hand-rolled its own milestone-heading regex (issues #3171, #3197) and the
|
|
67
|
+
* whole-file exemption made it invisible to this guard. The owner file is now
|
|
68
|
+
* scanned like every other file in SCAN_DIRS; only its named canonical
|
|
69
|
+
* functions are exempt (`FUNCTION_SCOPED_EXEMPTIONS`, keyed on `OWNER_FILE`),
|
|
70
|
+
* each with a written reason — `isMilestoneShippedInRoadmap`,
|
|
71
|
+
* `locateMilestoneHeadings`, `hasMilestoneSectioning`, and
|
|
72
|
+
* `extractCurrentMilestoneScoped` (whose `anyMilestonePattern`/
|
|
73
|
+
* `anyMilestoneOrDetails` locals compose `#{1,3}` with the `(?!Phase...)`/
|
|
74
|
+
* marker alternations as part of the canonical implementation, not a copy of
|
|
75
|
+
* it). `computeMilestoneSectionEnd` carries (a) and (b) on two DIFFERENT
|
|
76
|
+
* lines (the heading-quantifier match and the version/marker test are two
|
|
77
|
+
* separate statements) rather than one line carrying both, so this guard's
|
|
78
|
+
* own documented per-line-scan limit means it never fires there and it needs
|
|
79
|
+
* no listed exemption. An unrelated re-derivation added anywhere else
|
|
80
|
+
* in this file — including inside a function added after this guard, such as
|
|
81
|
+
* a future `getMilestoneInfo`-shaped one — is still caught.
|
|
82
|
+
*
|
|
83
|
+
* The tree-walk / root-confinement / regex-literal-tokenizer / sanitizer
|
|
84
|
+
* machinery is SHARED with `scripts/lint-plan-count-drift.cjs` via
|
|
85
|
+
* `scripts/lib/drift-scan.cjs` (ADR-3180 Decision 4, design doc's own
|
|
86
|
+
* "Rejected: let the new drift guard copy Phase 1's tree-walk /
|
|
87
|
+
* root-confinement / sanitizer") — see that module for the `isInsideRoot`
|
|
88
|
+
* case-sensitivity note, the `walk` symlink-confinement rationale, and the
|
|
89
|
+
* `readRegexLiteralAt` ReDoS-avoidance rationale.
|
|
90
|
+
*
|
|
91
|
+
* KNOWN, ACCEPTED limits of a per-line textual scan (same tradeoff the
|
|
92
|
+
* sibling drift guards document): a re-derivation whose `(a)`/`(b)` tokens
|
|
93
|
+
* are split across two DIFFERENT lines with no single line carrying both is
|
|
94
|
+
* not caught by this narrow shape, nor is one routed through dynamic
|
|
95
|
+
* dispatch. That is left to code review and the design's identity test
|
|
96
|
+
* (ADR-3180 Decision 4b/4c), not this regex.
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
const path = require('node:path');
|
|
100
|
+
const driftScan = require('./lib/drift-scan.cjs');
|
|
101
|
+
const { readRegexLiteralAt, MAX_REGEX_LITERAL_LEN, sanitizeForReport, scanTree } = driftScan;
|
|
102
|
+
|
|
103
|
+
// (a) A markdown heading-level quantifier: `#{N,M}` — e.g. `#{1,3}`,
|
|
104
|
+
// `#{2,3}`, `#{2,4}`. Bounded to 1-2 digit levels (real Markdown headings
|
|
105
|
+
// never exceed level 6) so this stays a small, fixed, linear test — no
|
|
106
|
+
// unbounded quantifier, nothing for CodeQL js/redos to flag.
|
|
107
|
+
const HEADING_QUANTIFIER_RE = /#\{\d{1,2},\d{1,2}\}/;
|
|
108
|
+
|
|
109
|
+
// (b1) The negative-lookahead phase exclusion `computeMilestoneSectionEnd`/
|
|
110
|
+
// `locateMilestoneHeadings` use to skip `### Phase N: …` headings while
|
|
111
|
+
// scanning for the NEXT milestone boundary.
|
|
112
|
+
const PHASE_LOOKAHEAD_RE = /\(\?!Phase\b/;
|
|
113
|
+
|
|
114
|
+
// (b2) A `v\d+\.\d+`-shaped version token, tolerant of ONE or TWO backslash
|
|
115
|
+
// escaping levels (a bare regex literal carries `\d`/`\.` with a single
|
|
116
|
+
// backslash; a template-literal regex SOURCE string carries the SAME source
|
|
117
|
+
// text doubly-escaped, `\\d`/`\\.`, because the template literal's own
|
|
118
|
+
// backslash must itself be escaped in the .cts source) and an OPTIONAL
|
|
119
|
+
// capturing group immediately around the digit run (`v(\d+)\.\d+`, the shape
|
|
120
|
+
// `roadmap-command-router.cts`'s `MILESTONE_RE` actually uses to capture the
|
|
121
|
+
// major version number).
|
|
122
|
+
const VERSION_TOKEN_RE = /v\(?\\{1,2}d\+\)?\\{1,2}\.\\{1,2}d\+/;
|
|
123
|
+
|
|
124
|
+
// (b2) The milestone shipped/active marker set `isClosedMilestoneHeading`/
|
|
125
|
+
// `computeMilestoneSectionEnd` test for. `(b)` fires when this appears on the
|
|
126
|
+
// SAME line as a VERSION_TOKEN_RE match — a version token alone is not
|
|
127
|
+
// milestone-boundary-specific (plenty of non-heading code compares version
|
|
128
|
+
// strings), and a marker alone is not either (it can appear in unrelated
|
|
129
|
+
// prose-matching code); together, on one line, they are the boundary shape.
|
|
130
|
+
const MARKER_EMOJI_RE = /[✅📋🚧]/u;
|
|
131
|
+
|
|
132
|
+
// (a-bis) #3216: a LITERAL markdown heading anchor — `##`, `###`, … — as
|
|
133
|
+
// opposed to the `#{N,M}` quantifier token (a) above. `getMilestoneInfo`
|
|
134
|
+
// hand-rolled its milestone-heading match with a literal `^##` / `## ` rather
|
|
135
|
+
// than a quantifier, so token (a) alone reported a clean zero on a file that
|
|
136
|
+
// carried two live re-derivations (#3171, #3197). The negative lookahead for
|
|
137
|
+
// `{` keeps this from double-matching the quantifier form.
|
|
138
|
+
//
|
|
139
|
+
// A `#` run is far more common in source than `#{N,M}` (private-field sigils,
|
|
140
|
+
// colour literals, fragment URLs, prose), so this token is admitted ONLY
|
|
141
|
+
// inside a heading-MATCHER literal — a regex literal, or a string/template
|
|
142
|
+
// literal handed to `new RegExp(` — never a bare line match. A template that
|
|
143
|
+
// BUILDS a heading for output (`## ${version}`) is not a re-derivation of
|
|
144
|
+
// where a milestone's section begins, and conflating the two would flag every
|
|
145
|
+
// heading writer in the tree.
|
|
146
|
+
const LITERAL_HEADING_RUN_RE = /#{2,6}(?!\{)/;
|
|
147
|
+
|
|
148
|
+
// A line that constructs a regex from a string/template literal, which is what
|
|
149
|
+
// admits the `new RegExp(`^##…${escapedVer}…`)` shape while leaving ordinary
|
|
150
|
+
// heading-building templates alone.
|
|
151
|
+
const NEW_REGEXP_RE = /new\s+RegExp\s*\(/;
|
|
152
|
+
|
|
153
|
+
// (b2-bis) #3216: the `v(\d+(?:\.\d+)+)` version shape — a capturing group
|
|
154
|
+
// around the major, then a NON-capturing `(?:\.\d+)+` repeat. VERSION_TOKEN_RE
|
|
155
|
+
// cannot see it: after `v(` + `\d+` it requires a backslash next, and this
|
|
156
|
+
// shape has `(` there instead.
|
|
157
|
+
const VERSION_TOKEN_GROUPED_RE = /v\(\\{1,2}d\+\(\?:\\{1,2}\.\\{1,2}d\+\)\+\)/;
|
|
158
|
+
|
|
159
|
+
// (b3) #3216: an INTERPOLATED version placeholder — `${escapedVer}`,
|
|
160
|
+
// `${escapedVersion}`, `${version}`. A regex that interpolates its version
|
|
161
|
+
// spells no literal `v\d+\.\d+` anywhere, so (b) could never fire. Inside a
|
|
162
|
+
// heading-matcher literal, "a heading anchor plus an interpolated version" IS
|
|
163
|
+
// the milestone-heading shape the canonical `locateMilestoneHeadings`
|
|
164
|
+
// composes — and so is a copy of it.
|
|
165
|
+
const INTERPOLATED_VERSION_RE = /\$\{[A-Za-z0-9_.]*[Vv]er[A-Za-z0-9_.]*\}/;
|
|
166
|
+
|
|
167
|
+
// Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
|
|
168
|
+
const SCAN_DIRS = ['src'];
|
|
169
|
+
const SCAN_EXT = new Set(['.cts', '.ts', '.mts']);
|
|
170
|
+
|
|
171
|
+
// The canonical owner defines the grammar; it is exempt by construction (see
|
|
172
|
+
// header comment for why its OWN internal composition of these tokens is not
|
|
173
|
+
// a re-derivation).
|
|
174
|
+
const OWNER_FILE = path.join('src', 'roadmap-parser.cts');
|
|
175
|
+
|
|
176
|
+
// Per ADR-3180 Decision 4(a): NOT a bare file allowlist — each entry below is
|
|
177
|
+
// scoped to the SPECIFIC function asking a documented, DIFFERENT question, so
|
|
178
|
+
// an unrelated re-derivation added anywhere else in these same files is still
|
|
179
|
+
// caught. Mirrors `lint-plan-count-drift.cjs`'s FUNCTION_SCOPED_EXEMPTIONS
|
|
180
|
+
// mechanism.
|
|
181
|
+
//
|
|
182
|
+
// - roadmap-command-router.cts checkW021: `MILESTONE_RE` CLASSIFIES a
|
|
183
|
+
// single heading LINE as "is this a milestone heading, and if so what is
|
|
184
|
+
// its major version" for the W021 phase/milestone-prefix-mismatch check
|
|
185
|
+
// — it is a per-line classifier consumed one line at a time via
|
|
186
|
+
// `content.split('\n')`, with no concept of a section END at all. It
|
|
187
|
+
// never computes "where does this milestone's content stop" — the
|
|
188
|
+
// question `computeMilestoneSectionEnd` answers — so it cannot diverge
|
|
189
|
+
// from that computation; it answers a narrower, different question this
|
|
190
|
+
// derivation does not own.
|
|
191
|
+
// - (Phase 11, #3309: `verify.cts`'s pre-migration `checkMilestonePrefixMismatches`
|
|
192
|
+
// — formerly exempted here — was DELETED when `cmdValidateHealth` migrated
|
|
193
|
+
// onto the rule table; its `sectionRx` walk relocated verbatim into
|
|
194
|
+
// `planning-snapshot.cts`'s `buildRoadmapDeclaredPhasesField`, which needs
|
|
195
|
+
// no exemption of its own: like the deleted function, its `sectionRx`
|
|
196
|
+
// never carries token (b) as this guard defines it — no `(?!Phase`
|
|
197
|
+
// lookahead, no marker-emoji pairing — so it was never a live match.)
|
|
198
|
+
// - roadmap-parser.cts isMilestoneShippedInRoadmap: composes the heading
|
|
199
|
+
// quantifier with the shipped/active MARKER check (via
|
|
200
|
+
// isClosedMilestoneHeading) to answer "is THIS milestone version marked
|
|
201
|
+
// shipped by the ROADMAP" — a documented, narrower question than
|
|
202
|
+
// computeMilestoneSectionEnd/locateMilestoneHeadings' "where does it
|
|
203
|
+
// end"/"which heading is it", not a copy of either.
|
|
204
|
+
// - roadmap-parser.cts locateMilestoneHeadings: this literally IS the
|
|
205
|
+
// canonical heading-locator this guard exists to protect (see the
|
|
206
|
+
// function's own header comment) — every other module's heading lookup
|
|
207
|
+
// is expected to call it, not re-express it.
|
|
208
|
+
// - roadmap-parser.cts hasMilestoneSectioning: the canonical "does this
|
|
209
|
+
// ROADMAP use milestone sectioning at all" predicate — a deliberately
|
|
210
|
+
// WEAKER, version-agnostic composition of the same two tokens, owned
|
|
211
|
+
// here per its own header comment so the milestone-heading vocabulary
|
|
212
|
+
// has one home rather than a third hand-rolled copy in state.cts.
|
|
213
|
+
// - roadmap-parser.cts extractCurrentMilestoneScoped: its
|
|
214
|
+
// `anyMilestoneOrDetails`/`anyMilestonePattern` locals are the two
|
|
215
|
+
// internal call sites the header comment already names as part of the
|
|
216
|
+
// canonical implementation (composing `#{1,3}` with the
|
|
217
|
+
// `(?!Phase...)`/marker alternations to find "the next milestone
|
|
218
|
+
// boundary" while assembling the current-milestone window) — not
|
|
219
|
+
// re-derivations of a question answered elsewhere.
|
|
220
|
+
// - roadmap-parser.cts listMilestoneHeadings: #3216 (epic #3180 §7.2's
|
|
221
|
+
// Scope amendment) — the version-AGNOSTIC sibling of
|
|
222
|
+
// `locateMilestoneHeadings`, and the function that textually DEFINES
|
|
223
|
+
// `MILESTONE_HEADING_LINE_SOURCE` (the one shared grammar constant both
|
|
224
|
+
// it and `locateMilestoneHeadings` build their pattern from) in its own
|
|
225
|
+
// source span. It is a named canonical function defining the grammar,
|
|
226
|
+
// not a copy of it — replacing the third independent re-derivation the
|
|
227
|
+
// widened guard found at `roadmap.cts:454`.
|
|
228
|
+
// - planning-snapshot.cts buildMilestoneArchiveStatusField (Phase 11,
|
|
229
|
+
// #3309): its `## <version>` heading scan reads `MILESTONES.md` — a
|
|
230
|
+
// FLAT version registry, not `ROADMAP.md` — asking "which versions does
|
|
231
|
+
// the registry already document", never "where does THIS milestone's
|
|
232
|
+
// ROADMAP section begin/end" (`computeMilestoneSectionEnd`/
|
|
233
|
+
// `locateMilestoneHeadings`'s own question). A different document, a
|
|
234
|
+
// different question; not a re-derivation of ROADMAP windowing.
|
|
235
|
+
// - health-diagnostic.cts computeMissingMilestoneVersions (Phase 11,
|
|
236
|
+
// #3309): `applyRepairs` is not a `Rule` and is not handed a
|
|
237
|
+
// `PlanningSnapshot` (see that file's header comment), so
|
|
238
|
+
// `backfillMilestones` recomputes the IDENTICAL `MILESTONES.md`
|
|
239
|
+
// heading-membership check `buildMilestoneArchiveStatusField` already
|
|
240
|
+
// performs for the W018 rule's read side — same non-ROADMAP-windowing
|
|
241
|
+
// question as that function, for the same reason.
|
|
242
|
+
const FUNCTION_SCOPED_EXEMPTIONS = new Map([
|
|
243
|
+
[path.join('src', 'roadmap-command-router.cts'), new Set(['checkW021'])],
|
|
244
|
+
[
|
|
245
|
+
OWNER_FILE,
|
|
246
|
+
new Set([
|
|
247
|
+
'isMilestoneShippedInRoadmap',
|
|
248
|
+
'locateMilestoneHeadings',
|
|
249
|
+
'listMilestoneHeadings',
|
|
250
|
+
'hasMilestoneSectioning',
|
|
251
|
+
'extractCurrentMilestoneScoped',
|
|
252
|
+
]),
|
|
253
|
+
],
|
|
254
|
+
[path.join('src', 'planning-snapshot.cts'), new Set(['buildMilestoneArchiveStatusField'])],
|
|
255
|
+
[path.join('src', 'health-diagnostic.cts'), new Set(['computeMissingMilestoneVersions'])],
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
// Optional `export ` modifier, mirroring `lint-plan-count-drift.cjs`'s
|
|
259
|
+
// TOP_LEVEL_FUNCTION_RE — only a column-0 top-level `function` declaration
|
|
260
|
+
// updates the current-function tracker; a nested/arrow function does not
|
|
261
|
+
// reset it, matching every FUNCTION_SCOPED_EXEMPTIONS entry above (all
|
|
262
|
+
// top-level `function` declarations).
|
|
263
|
+
const TOP_LEVEL_FUNCTION_RE = /^(?:export\s+)?function\s+([A-Za-z0-9_]+)\s*\(/;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Read the quoted or backtick-delimited string/template literal starting at
|
|
267
|
+
* `line[start]` (which must be `'`, `"`, or `` ` ``). Returns `{ text, end }`
|
|
268
|
+
* — `text` includes both delimiters, `end` is the index one past the literal
|
|
269
|
+
* — or null if no matching close quote is found within MAX_REGEX_LITERAL_LEN
|
|
270
|
+
* characters. Same single left-to-right, no-backtracking, escape-aware style
|
|
271
|
+
* as the shared `readRegexLiteralAt` (`\x` escapes consume both characters,
|
|
272
|
+
* so an escaped quote never terminates the literal early) — written locally
|
|
273
|
+
* because `lint-plan-count-drift.cjs` has no equivalent need (its literal
|
|
274
|
+
* shape is regex-only), so it does not belong in the shared module.
|
|
275
|
+
*/
|
|
276
|
+
function readStringLiteralAt(line, start) {
|
|
277
|
+
const quote = line[start];
|
|
278
|
+
if (quote !== "'" && quote !== '"' && quote !== '`') return null;
|
|
279
|
+
const limit = Math.min(line.length, start + MAX_REGEX_LITERAL_LEN);
|
|
280
|
+
for (let i = start + 1; i < limit; i++) {
|
|
281
|
+
const ch = line[i];
|
|
282
|
+
if (ch === '\\') {
|
|
283
|
+
i++; // escape consumes the next character, whatever it is
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (ch === '\r' || ch === '\n') return null; // a literal cannot span lines in this per-line scan
|
|
287
|
+
if (ch === quote) return { text: line.slice(start, i + 1), end: i + 1 };
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Strip comment text from a line before detection. A guard that fires on a
|
|
294
|
+
* COMMENT — including a comment documenting that the code below uses the
|
|
295
|
+
* canonical owner, or prose quoting this guard's own detector shapes — reports
|
|
296
|
+
* prose as drift and trains readers to add exemptions for documentation.
|
|
297
|
+
* Handles the three shapes that appear in this codebase: a whole-line
|
|
298
|
+
* block-comment continuation (`*` or `/*` leading), a `//` line comment, and
|
|
299
|
+
* a trailing `//` after code. Mirrors `lint-phase-enumeration-drift.cjs`'s
|
|
300
|
+
* own copy (not shared — each guard applies it at a slightly different point
|
|
301
|
+
* in its detection pipeline).
|
|
302
|
+
*
|
|
303
|
+
* Deliberately simple and conservative: it does not attempt full block-comment
|
|
304
|
+
* state tracking across lines (this is a per-line scan, same tradeoff the
|
|
305
|
+
* sibling guards document). A `//` inside a string literal would be stripped
|
|
306
|
+
* early — accepted, because the effect is to UNDER-report on a pathological
|
|
307
|
+
* line, never to over-report prose as drift.
|
|
308
|
+
*/
|
|
309
|
+
function stripComments(line) {
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
// Whole-line block comment or JSDoc continuation.
|
|
312
|
+
if (trimmed.startsWith('*') || trimmed.startsWith('/*') || trimmed.startsWith('//')) return '';
|
|
313
|
+
// Trailing line comment after code.
|
|
314
|
+
const idx = line.indexOf('//');
|
|
315
|
+
return idx === -1 ? line : line.slice(0, idx);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The first literal (regex OR quoted/backtick string) on `line` whose text
|
|
320
|
+
* contains a HEADING_QUANTIFIER_RE match — the "smoking gun" fragment worth
|
|
321
|
+
* reporting, mirroring `findRegexLiteralMdMatch`'s role in the sibling guard.
|
|
322
|
+
* Falls back to a bounded, trimmed slice of the raw line when the tokens are
|
|
323
|
+
* not both inside one located literal (not currently reachable against this
|
|
324
|
+
* repo — see the header comment's per-file audit — but a fail-safe rather
|
|
325
|
+
* than a thrown error if a future line splits them). Takes the RAW `line`
|
|
326
|
+
* (not comment-stripped) so a reported fragment still shows the actual source
|
|
327
|
+
* text — comment-stripping is applied only to the detection decision, never
|
|
328
|
+
* to the reported fragment.
|
|
329
|
+
*/
|
|
330
|
+
/**
|
|
331
|
+
* All heading-MATCHER literals on `line` — a regex literal always counts; a
|
|
332
|
+
* quoted/backtick string literal counts only when `code` (the comment-stripped
|
|
333
|
+
* line passed in from the caller) constructs a regex via `new RegExp(`. A
|
|
334
|
+
* plain string/template literal that is not fed to `new RegExp(` is not a
|
|
335
|
+
* matcher — most commonly a heading BUILT for output, not one matched against.
|
|
336
|
+
*/
|
|
337
|
+
function headingMatcherLiterals(line, code) {
|
|
338
|
+
const out = [];
|
|
339
|
+
const allowStrings = NEW_REGEXP_RE.test(code);
|
|
340
|
+
for (let i = 0; i < line.length; i++) {
|
|
341
|
+
const ch = line[i];
|
|
342
|
+
let literal = null;
|
|
343
|
+
let isRegex = false;
|
|
344
|
+
if (ch === '/') {
|
|
345
|
+
literal = readRegexLiteralAt(line, i);
|
|
346
|
+
isRegex = true;
|
|
347
|
+
} else if (ch === "'" || ch === '"' || ch === '`') {
|
|
348
|
+
literal = readStringLiteralAt(line, i);
|
|
349
|
+
}
|
|
350
|
+
if (!literal) continue;
|
|
351
|
+
if (isRegex || allowStrings) out.push(literal.text);
|
|
352
|
+
i = literal.end - 1;
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function extractFragment(line) {
|
|
358
|
+
for (let i = 0; i < line.length; i++) {
|
|
359
|
+
const ch = line[i];
|
|
360
|
+
let literal = null;
|
|
361
|
+
if (ch === '/') literal = readRegexLiteralAt(line, i);
|
|
362
|
+
else if (ch === "'" || ch === '"' || ch === '`') literal = readStringLiteralAt(line, i);
|
|
363
|
+
if (!literal) continue;
|
|
364
|
+
if (HEADING_QUANTIFIER_RE.test(literal.text) || LITERAL_HEADING_RUN_RE.test(literal.text)) return literal.text;
|
|
365
|
+
i = literal.end - 1; // resume scanning just past this literal
|
|
366
|
+
}
|
|
367
|
+
return line.trim().slice(0, MAX_REGEX_LITERAL_LEN);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Pure: find every unsanctioned milestone-window re-derivation in `text`.
|
|
372
|
+
* `relPath` is the repo-relative path, used both to report file:line and to
|
|
373
|
+
* apply the narrow, function-scoped exemptions above.
|
|
374
|
+
* Returns [{ line, found }].
|
|
375
|
+
*/
|
|
376
|
+
function findMilestoneWindowDrift(text, relPath) {
|
|
377
|
+
const out = [];
|
|
378
|
+
const lines = text.split('\n');
|
|
379
|
+
const exemptFunctions = FUNCTION_SCOPED_EXEMPTIONS.get(relPath) || null;
|
|
380
|
+
let currentFunction = null;
|
|
381
|
+
for (let i = 0; i < lines.length; i++) {
|
|
382
|
+
const line = lines[i];
|
|
383
|
+
const fnMatch = TOP_LEVEL_FUNCTION_RE.exec(line);
|
|
384
|
+
if (fnMatch) currentFunction = fnMatch[1];
|
|
385
|
+
|
|
386
|
+
const code = stripComments(line);
|
|
387
|
+
if (!code.trim()) continue;
|
|
388
|
+
|
|
389
|
+
const matcherLiterals = headingMatcherLiterals(line, code);
|
|
390
|
+
const hasQuantifier = HEADING_QUANTIFIER_RE.test(code);
|
|
391
|
+
const hasLiteralHeading = matcherLiterals.some((t) => LITERAL_HEADING_RUN_RE.test(t));
|
|
392
|
+
if (!hasQuantifier && !hasLiteralHeading) continue;
|
|
393
|
+
|
|
394
|
+
const anyVersionToken = VERSION_TOKEN_RE.test(code) || VERSION_TOKEN_GROUPED_RE.test(code);
|
|
395
|
+
const isMilestoneWindowToken =
|
|
396
|
+
PHASE_LOOKAHEAD_RE.test(code)
|
|
397
|
+
|| (anyVersionToken && MARKER_EMOJI_RE.test(code))
|
|
398
|
+
|| (hasLiteralHeading && (anyVersionToken || INTERPOLATED_VERSION_RE.test(code)));
|
|
399
|
+
if (!isMilestoneWindowToken) continue;
|
|
400
|
+
|
|
401
|
+
if (exemptFunctions && exemptFunctions.has(currentFunction)) continue;
|
|
402
|
+
|
|
403
|
+
out.push({ line: i + 1, found: extractFragment(line) });
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Scan the authored source tree and return every unsanctioned re-derivation,
|
|
410
|
+
* each annotated with the repo-relative file path.
|
|
411
|
+
*/
|
|
412
|
+
function scanRepo(root) {
|
|
413
|
+
return scanTree({
|
|
414
|
+
root,
|
|
415
|
+
scanDirs: SCAN_DIRS,
|
|
416
|
+
scanExt: SCAN_EXT,
|
|
417
|
+
onFile(rel, text) {
|
|
418
|
+
// `rel` is already the REAL (canonical) path (scanTree resolves
|
|
419
|
+
// symlinks before calling onFile), so this — and
|
|
420
|
+
// FUNCTION_SCOPED_EXEMPTIONS above, also keyed on `rel` — match
|
|
421
|
+
// consistently regardless of which symlink reached the file. The owner
|
|
422
|
+
// file is NOT short-circuited here; it is scanned like every other
|
|
423
|
+
// file, and only its named canonical functions are exempt (see
|
|
424
|
+
// FUNCTION_SCOPED_EXEMPTIONS).
|
|
425
|
+
return findMilestoneWindowDrift(text, rel).map((d) => ({ file: rel, ...d }));
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function main() {
|
|
431
|
+
const root = path.join(__dirname, '..');
|
|
432
|
+
const violations = scanRepo(root);
|
|
433
|
+
if (violations.length === 0) {
|
|
434
|
+
process.stdout.write('ok milestone-window-drift: no unsanctioned milestone-window re-derivations outside roadmap-parser.cts\n');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
process.stderr.write('milestone-window-drift: independent re-derivation(s) of milestone-window bounding found.\n');
|
|
438
|
+
process.stderr.write('Use src/roadmap-parser.cjs `computeMilestoneSectionEnd` / `locateMilestoneHeadings` /\n');
|
|
439
|
+
process.stderr.write('`isMilestoneBoundedInRoadmap` instead of re-deriving the milestone heading/boundary regex:\n');
|
|
440
|
+
for (const d of violations) {
|
|
441
|
+
// `d.file` is exactly as attacker-controlled as `d.found`: a repo can
|
|
442
|
+
// legally track a filename containing control bytes / bidi overrides,
|
|
443
|
+
// and it is a fork-PR-authored value reaching a CI log the same way the
|
|
444
|
+
// matched literal does — sanitize it at the same reporting boundary.
|
|
445
|
+
process.stderr.write(` ${sanitizeForReport(d.file)}:${d.line} ${sanitizeForReport(d.found)}\n`);
|
|
446
|
+
}
|
|
447
|
+
process.exitCode = 1;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (require.main === module) main();
|
|
451
|
+
|
|
452
|
+
module.exports = {
|
|
453
|
+
findMilestoneWindowDrift,
|
|
454
|
+
scanRepo,
|
|
455
|
+
HEADING_QUANTIFIER_RE,
|
|
456
|
+
PHASE_LOOKAHEAD_RE,
|
|
457
|
+
VERSION_TOKEN_RE,
|
|
458
|
+
MARKER_EMOJI_RE,
|
|
459
|
+
OWNER_FILE,
|
|
460
|
+
FUNCTION_SCOPED_EXEMPTIONS,
|
|
461
|
+
readStringLiteralAt,
|
|
462
|
+
extractFragment,
|
|
463
|
+
stripComments,
|
|
464
|
+
LITERAL_HEADING_RUN_RE,
|
|
465
|
+
VERSION_TOKEN_GROUPED_RE,
|
|
466
|
+
INTERPOLATED_VERSION_RE,
|
|
467
|
+
headingMatcherLiterals,
|
|
468
|
+
};
|