@opengsd/gsd-core 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -84,9 +84,13 @@
|
|
|
84
84
|
* shape as `collectDiskPhases` and the `audit.cts` scanners; it must see
|
|
85
85
|
* every phase directory regardless of milestone window to catch a
|
|
86
86
|
* naming/duplicate defect wherever it lives.
|
|
87
|
-
* - `src/verify.cts` `
|
|
87
|
+
* - `src/verify.cts` `resolvePhaseDirByToken`: resolves ONE caller-supplied
|
|
88
88
|
* `phase` argument to its directory (falling back to an exact-name
|
|
89
89
|
* match) — a single-phase LOOKUP, not a current-milestone enumeration.
|
|
90
|
+
* Originally `cmdVerifySchemaDrift`'s own inline block; #3348 lifted it
|
|
91
|
+
* into this shared helper (also used by the new `cmdVerifyContextDrift`)
|
|
92
|
+
* without changing what question it asks, so the exemption moved with
|
|
93
|
+
* the call site rather than multiplying.
|
|
90
94
|
* - `src/init.cts` `detectHasPriorPhases`: answers "has this project EVER
|
|
91
95
|
* completed a phase", explicitly excluding the current one. A history
|
|
92
96
|
* probe across all milestones, not a current-milestone enumeration.
|
|
@@ -128,8 +132,10 @@
|
|
|
128
132
|
* `--include-archived` merge are phase LOCATION and archive
|
|
129
133
|
* enumeration, not current-milestone enumeration; both legitimately
|
|
130
134
|
* read the physical set. Its ENUMERATION path routes through the owner.
|
|
131
|
-
* - `src/roadmap-parser.cts` `getMilestonePhaseFilter` and its
|
|
132
|
-
* set-building
|
|
135
|
+
* - `src/roadmap-parser.cts` `getMilestonePhaseFilter` and its shared
|
|
136
|
+
* set-building implementation `scanMilestonePhaseIdSets` (the public
|
|
137
|
+
* `scanMilestonePhaseIds` wrapper remains a directly iterable Set; the
|
|
138
|
+
* same two heading/
|
|
133
139
|
* #3577 `collectTablePhaseRows` — the table-scan sibling feeding the same
|
|
134
140
|
* membership set; its local 999-only exclusion mirrors the owner's
|
|
135
141
|
* deliberate NOT-isSentinelPhaseId choice (a leading 0 is a real decimal
|
|
@@ -142,6 +148,18 @@
|
|
|
142
148
|
* ("00.1" is a real phase, not milestone 0). This scan asks a narrower
|
|
143
149
|
* question — "which phase ids does this milestone's window declare" —
|
|
144
150
|
* where only the 999 icebox range is excluded.
|
|
151
|
+
* - `src/state.cts` `countRoadmapPhaseHeadings`: its bracket branch composes
|
|
152
|
+
* bracket-milestone sentinel detection through `isSentinelPhaseId`, but
|
|
153
|
+
* retains a separate bare-token `999` rule. That rule intentionally does
|
|
154
|
+
* NOT use the convention-blind canonical predicate: the latter also reads
|
|
155
|
+
* leading `0`/`00.1` as milestone-0 sentinels, while a legacy-spelled bare
|
|
156
|
+
* zero token inside an opted-in bracket project is a real mid-migration
|
|
157
|
+
* phase. This is the state-counter twin of the roadmap-parser exemption.
|
|
158
|
+
* The guard has no per-detector exemption scope: it ORs the enumeration
|
|
159
|
+
* and sentinel detectors before consulting this function-only map. The
|
|
160
|
+
* whole-function exemption is therefore accepted; it is bounded because
|
|
161
|
+
* this counter performs no phases-directory `readdirSync` and needs the
|
|
162
|
+
* exemption only for its intentional bare-token `999` sentinel literal.
|
|
145
163
|
* - `src/state.cts` `cmdStateValidate` ("Gate 1: Validate STATE.md against
|
|
146
164
|
* filesystem"): resolves ONE directory — the disk match for STATE.md's
|
|
147
165
|
* own `Current Phase` field — by prefix, a single-phase LOOKUP, not an
|
|
@@ -291,7 +309,7 @@ const OWNER_FILES = new Set([
|
|
|
291
309
|
// `lint-milestone-window-drift.cjs`'s FUNCTION_SCOPED_EXEMPTIONS mechanism.
|
|
292
310
|
// See the header comment for the full written reason behind each entry.
|
|
293
311
|
const FUNCTION_SCOPED_EXEMPTIONS = new Map([
|
|
294
|
-
[path.join('src', 'verify.cts'), new Set(['cmdValidateHealth', '
|
|
312
|
+
[path.join('src', 'verify.cts'), new Set(['cmdValidateHealth', 'resolvePhaseDirByToken'])],
|
|
295
313
|
[path.join('src', 'init.cts'), new Set(['detectHasPriorPhases', 'detectUiPhaseActive'])],
|
|
296
314
|
[path.join('src', 'milestone.cts'), new Set(['archivePhaseDirectories', 'cmdMilestoneComplete', 'cmdPhasesClear'])],
|
|
297
315
|
// #3849: collectSiblingWorktreePhaseNums reads a SIBLING worktree's phases dir —
|
|
@@ -300,10 +318,10 @@ const FUNCTION_SCOPED_EXEMPTIONS = new Map([
|
|
|
300
318
|
[path.join('src', 'phase.cts'), new Set(['cmdPhasesList', 'cmdPhaseNextDecimal', 'cmdPhasePlanIndex', 'cmdPhaseInsert', 'renameDecimalPhases', 'renameIntegerPhases', 'collectSiblingWorktreePhaseNums'])],
|
|
301
319
|
[path.join('src', 'audit.cts'), new Set(['listAuditPhaseTargets'])],
|
|
302
320
|
[path.join('src', 'commands.cts'), new Set(['cmdHistoryDigest'])],
|
|
303
|
-
[path.join('src', 'state.cts'), new Set(['cmdStateValidate', 'cmdStateSync', 'cmdStateRebuild'])],
|
|
321
|
+
[path.join('src', 'state.cts'), new Set(['cmdStateValidate', 'cmdStateSync', 'cmdStateRebuild', 'countRoadmapPhaseHeadings'])],
|
|
304
322
|
[path.join('src', 'roadmap-upgrade.cts'), new Set(['computeMigrationPlan'])],
|
|
305
323
|
[path.join('src', 'smart-entry.cts'), new Set(['detectVerifyFailed'])],
|
|
306
|
-
[path.join('src', 'roadmap-parser.cts'), new Set(['getMilestonePhaseFilter', '
|
|
324
|
+
[path.join('src', 'roadmap-parser.cts'), new Set(['getMilestonePhaseFilter', 'scanMilestonePhaseIdSets', 'collectTablePhaseRows'])],
|
|
307
325
|
[path.join('src', 'planning-snapshot.cts'), new Set(['buildAllPhaseDirNamesField'])],
|
|
308
326
|
]);
|
|
309
327
|
|
|
@@ -20,6 +20,34 @@
|
|
|
20
20
|
* directly above). Sites that build their regex from `PHASE_NUMBER_TOKEN_SOURCE`
|
|
21
21
|
* carry no literal grammar and pass automatically.
|
|
22
22
|
*
|
|
23
|
+
* #4634 extends the same pattern with two more detectors:
|
|
24
|
+
*
|
|
25
|
+
* - name-validity-guard drift: `hasNameableContent(s)` in `src/roadmap-parser.cts`
|
|
26
|
+
* is the sole owner of the "does this string have nameable content" predicate
|
|
27
|
+
* (`/[\p{L}\p{N}]/u.test(s)`). Any other `src/**` file re-deriving that exact
|
|
28
|
+
* character class (regex-literal or `new RegExp` template form) instead of
|
|
29
|
+
* calling `hasNameableContent` is drift, sanctioned the same way as the token
|
|
30
|
+
* and bracket rules (`// phase-id-owner:` on the nearest preceding non-blank
|
|
31
|
+
* line), with a line-level escape for a line that already calls
|
|
32
|
+
* `hasNameableContent(`.
|
|
33
|
+
*
|
|
34
|
+
* - shell phase-number-arithmetic ban: `$((10#...))` base-10-forced arithmetic
|
|
35
|
+
* inside `gsd-core/workflows/**\/*.md` and `gsd-core/references/**\/*.md` breaks
|
|
36
|
+
* on decimal or multi-segment phase ids and is banned outright. This scan runs
|
|
37
|
+
* over markdown, not `.cts` source, so its sanction is an HTML comment on the
|
|
38
|
+
* nearest preceding non-blank line: `<!-- phase-id-owner: <reason> -->`.
|
|
39
|
+
*
|
|
40
|
+
* - branch-slug fallback drift: a `.replace('{slug}', ... || 'phase')` call
|
|
41
|
+
* silently substitutes the literal string `'phase'` when a phase's slug
|
|
42
|
+
* can't be derived, producing a non-identifying branch name like
|
|
43
|
+
* `gsd/phase-08-phase` (#4126, now fixed via the shared renderPhaseBranchName
|
|
44
|
+
* owner in phase-id.cts, consumed by both prior call sites). Sanctioned
|
|
45
|
+
* the same way as the token/bracket/name-validity rules (`// phase-id-owner:`
|
|
46
|
+
* on the nearest preceding non-blank line), with a line-level escape for a
|
|
47
|
+
* line that already calls `renderPhaseBranchName(`. Unlike the other
|
|
48
|
+
* `.cts`-scanning rules, this one has no per-file exemption — it is a banned
|
|
49
|
+
* anti-pattern everywhere, not a grammar with one legitimate owner site.
|
|
50
|
+
*
|
|
23
51
|
* Detection is intentionally NARROW: only the contiguous canonical token
|
|
24
52
|
* (`\d+[A-Z]?(?:\.\d+)*`, its `[A-Za-z]` and `[.-]` near-variants, in both
|
|
25
53
|
* regex-literal `\d` and `new RegExp` template `\\d` escaping) is drift. Bare
|
|
@@ -53,6 +81,47 @@ const TOKEN_DRIFT_RE = /(?:\\{1,2}d|\[0-9\])\+\[A-Z(?:a-z)?\]\??\(\?:(?:\\{1,2}\
|
|
|
53
81
|
const OWNER_RE = /^\s*\/\/.*phase-id-owner:/;
|
|
54
82
|
const CANON_REF = 'PHASE_NUMBER_TOKEN_SOURCE';
|
|
55
83
|
|
|
84
|
+
// #2761 M3 (trek-e review): the SECOND grammar this seam owns — the BRACKET
|
|
85
|
+
// project-code class of `[CODE.MM]`, spelled `[A-Z][A-Z0-9_]*` (with its
|
|
86
|
+
// case-widened `[A-Za-z]`/`[A-Za-z0-9_]` variant tolerated so a trivial rewrite
|
|
87
|
+
// does not evade the rule). The token guard above only ever knew the phase-
|
|
88
|
+
// NUMBER grammar, so three files re-typed this class verbatim — roadmap-parser's
|
|
89
|
+
// bracket-fallback selector, state's `isMilestoneBounded`, verify's
|
|
90
|
+
// `checkBracketCoherence` — and `check:phase-id-drift` reported clean the whole
|
|
91
|
+
// time. That is the blind spot which let #2761's own "no token literal outside
|
|
92
|
+
// src/phase-id.cts" gate pass while being violated. Build from
|
|
93
|
+
// `BRACKET_PROJECT_CODE_SRC`, `BRACKET_ID_SRC`, `bracketMilestoneIntroSrcFor`
|
|
94
|
+
// or `BRACKET_MILESTONE_INTRO_CAPTURING_SRC` instead.
|
|
95
|
+
const BRACKET_CODE_DRIFT_RE = /\[A-Z(?:a-z)?\]\[A-Z(?:a-z)?0-9_\]\*/;
|
|
96
|
+
|
|
97
|
+
// This rule has NO counterpart to the token rule's `line.includes(CANON_REF)`
|
|
98
|
+
// escape, and that omission is the point.
|
|
99
|
+
//
|
|
100
|
+
// That escape is LINE-level: a line naming the canonical source anywhere on it
|
|
101
|
+
// is taken as built-from-the-owner. verify.cts's copy read
|
|
102
|
+
//
|
|
103
|
+
// new RegExp(`^\\[[A-Z][A-Z0-9_]*\\.(${BRACKET_MILESTONE_NUMERIC_SRC})\\]`, 'i')
|
|
104
|
+
//
|
|
105
|
+
// — an owner reference for the MILESTONE field sharing a line with a re-typed
|
|
106
|
+
// PROJECT-CODE class. A line-level escape waves that through, so a bracket rule
|
|
107
|
+
// that copied it would have kept reporting clean on the very site under review.
|
|
108
|
+
// Partial ownership IS the drift. Only a `// phase-id-owner:` sanction
|
|
109
|
+
// suppresses this rule, and a sanction has to state which half is deliberate.
|
|
110
|
+
const BRACKET_OWNER_HINT =
|
|
111
|
+
'BRACKET_PROJECT_CODE_SRC / BRACKET_ID_SRC / bracketMilestoneIntroSrcFor / BRACKET_MILESTONE_INTRO_CAPTURING_SRC';
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Pure: true if the nearest preceding non-blank line to `lines[i]` is a
|
|
115
|
+
* dedicated sanction comment matching `ownerRe`. Shared by every detector in
|
|
116
|
+
* this file so the "how do you sanction a finding" walk has one owner instead
|
|
117
|
+
* of four independent copies that could silently diverge.
|
|
118
|
+
*/
|
|
119
|
+
function isSanctionedByPrecedingComment(lines, i, ownerRe) {
|
|
120
|
+
let j = i - 1;
|
|
121
|
+
while (j >= 0 && lines[j].trim() === '') j--; // nearest preceding non-blank line
|
|
122
|
+
return j >= 0 && ownerRe.test(lines[j]);
|
|
123
|
+
}
|
|
124
|
+
|
|
56
125
|
/**
|
|
57
126
|
* Pure: find every literal re-derivation of the canonical phase-number token in
|
|
58
127
|
* `text` that is NOT sanctioned. A site is sanctioned when the nearest preceding
|
|
@@ -71,21 +140,269 @@ function findPhaseIdRegexDrift(text) {
|
|
|
71
140
|
const m = TOKEN_DRIFT_RE.exec(line);
|
|
72
141
|
if (!m) continue;
|
|
73
142
|
if (line.includes(CANON_REF)) continue;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
143
|
+
if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
|
|
144
|
+
out.push({ line: i + 1, found: m[0] });
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Pure: find every literal re-derivation of the BRACKET project-code grammar in
|
|
151
|
+
* `text` that is NOT sanctioned. Same sanction mechanism as the token rule — a
|
|
152
|
+
* dedicated `// phase-id-owner:` comment on the nearest preceding non-blank
|
|
153
|
+
* line — but deliberately WITHOUT its line-level owner-reference escape, so a
|
|
154
|
+
* site that references the owner for one field while re-typing the other is
|
|
155
|
+
* still reported (see BRACKET_CODE_DRIFT_RE's note). Returns [{ line, found }].
|
|
156
|
+
*/
|
|
157
|
+
function findBracketGrammarDrift(text) {
|
|
158
|
+
const out = [];
|
|
159
|
+
const lines = text.split('\n');
|
|
160
|
+
for (let i = 0; i < lines.length; i++) {
|
|
161
|
+
const m = BRACKET_CODE_DRIFT_RE.exec(lines[i]);
|
|
162
|
+
if (!m) continue;
|
|
163
|
+
if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
|
|
164
|
+
out.push({ line: i + 1, found: m[0] });
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// #4634: the name-validity-guard grammar — `hasNameableContent(s)` in
|
|
170
|
+
// `src/roadmap-parser.cts` is `/[\p{L}\p{N}]/u.test(s)`. Tolerates both the
|
|
171
|
+
// regex-literal single-backslash form and the doubled-backslash template
|
|
172
|
+
// form (`new RegExp('[\\p{L}\\p{N}]'`), mirroring how TOKEN_DRIFT_RE tolerates
|
|
173
|
+
// both escapings.
|
|
174
|
+
const NAME_VALIDITY_DRIFT_RE = /\[\\{1,2}p\{L\}\\{1,2}p\{N\}\]/;
|
|
175
|
+
const NAME_VALIDITY_CANON_REF = 'hasNameableContent(';
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Pure: find every literal re-derivation of the canonical name-validity
|
|
179
|
+
* character class in `text` that is NOT sanctioned. Same sanction mechanism
|
|
180
|
+
* as the token/bracket rules — a dedicated `// phase-id-owner:` comment on
|
|
181
|
+
* the nearest preceding non-blank line — plus a line-level escape for a line
|
|
182
|
+
* that already calls `hasNameableContent(`. Returns [{ line, found }].
|
|
183
|
+
*/
|
|
184
|
+
function findNameValidityDrift(text) {
|
|
185
|
+
const out = [];
|
|
186
|
+
const lines = text.split('\n');
|
|
187
|
+
for (let i = 0; i < lines.length; i++) {
|
|
188
|
+
const line = lines[i];
|
|
189
|
+
const m = NAME_VALIDITY_DRIFT_RE.exec(line);
|
|
190
|
+
if (!m) continue;
|
|
191
|
+
if (line.includes(NAME_VALIDITY_CANON_REF)) continue;
|
|
192
|
+
if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
|
|
193
|
+
out.push({ line: i + 1, found: m[0] });
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// #4634: the branch-slug fallback anti-pattern (#4126) — a
|
|
199
|
+
// `.replace('{slug}', ... || 'phase')` call silently falls back to the
|
|
200
|
+
// literal string `'phase'` when a phase's slug can't be derived, producing a
|
|
201
|
+
// non-identifying branch name like `gsd/phase-08-phase`. Now fixed at both
|
|
202
|
+
// prior call sites (commands.cts, init.cts) via the shared
|
|
203
|
+
// `renderPhaseBranchName` owner in phase-id.cts; this rule is the ratchet
|
|
204
|
+
// against a THIRD site reintroducing the inline fallback. Deliberately
|
|
205
|
+
// narrow: it requires the literal `'phase'` fallback on the same line as the
|
|
206
|
+
// `{slug}` template token, so it does NOT match the sibling milestone-branch
|
|
207
|
+
// fallback (`|| 'milestone'`), which is a different, correct-as-is case.
|
|
208
|
+
const BRANCH_SLUG_FALLBACK_DRIFT_RE = /\{slug\}'.*\|\|\s*'phase'/;
|
|
209
|
+
|
|
210
|
+
// The canonical fix is `renderPhaseBranchName(...)`. There is no "owner file"
|
|
211
|
+
// for this rule the way there is for the token/bracket/name-validity
|
|
212
|
+
// grammars above — it is a banned anti-pattern everywhere, so no per-file
|
|
213
|
+
// exemption exists.
|
|
214
|
+
const BRANCH_SLUG_FALLBACK_CANON_REF = 'renderPhaseBranchName(';
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Pure: find every unsanctioned branch-slug `|| 'phase'` fallback in `text`.
|
|
218
|
+
* Same sanction mechanism as the token/bracket/name-validity rules — a
|
|
219
|
+
* dedicated `// phase-id-owner:` comment on the nearest preceding non-blank
|
|
220
|
+
* line — plus a line-level escape for a line that already calls
|
|
221
|
+
* `renderPhaseBranchName(`. Returns [{ line, found }].
|
|
222
|
+
*/
|
|
223
|
+
function findBranchSlugFallbackDrift(text) {
|
|
224
|
+
const out = [];
|
|
225
|
+
const lines = text.split('\n');
|
|
226
|
+
for (let i = 0; i < lines.length; i++) {
|
|
227
|
+
const line = lines[i];
|
|
228
|
+
const m = BRANCH_SLUG_FALLBACK_DRIFT_RE.exec(line);
|
|
229
|
+
if (!m) continue;
|
|
230
|
+
if (line.includes(BRANCH_SLUG_FALLBACK_CANON_REF)) continue;
|
|
231
|
+
if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
|
|
232
|
+
out.push({ line: i + 1, found: m[0] });
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// #4634: ban base-10-forced shell arithmetic (`$((10#...))`) on a variable
|
|
238
|
+
// that still carries a possibly-decimal/multi-segment phase id — this
|
|
239
|
+
// construct is exactly the pattern that breaks on a value like `08.5`. The
|
|
240
|
+
// capture group grabs the token immediately inside the parens (after an
|
|
241
|
+
// optional `$` and/or `{`, stripping a trailing `}`) so callers can inspect
|
|
242
|
+
// *which* variable is being coerced, not merely that the substring occurred.
|
|
243
|
+
//
|
|
244
|
+
// Refined post-#4619: the original blunt "ban `$((10#` outright" version
|
|
245
|
+
// over-fired on three false-positive classes once #4619's fix landed:
|
|
246
|
+
// 1. Prose mentioning the literal pattern in a full-line `#`-comment
|
|
247
|
+
// (filtered by the caller, not this regex — see below).
|
|
248
|
+
// 2. `$((10#$PHASE_INT))` / `$((10#$SPOT_PHASE_INT))` — arithmetic on the
|
|
249
|
+
// NOW-safe variable the #4619 fix produces via `PHASE_INT=${PHASE_NUMBER%%.*}`;
|
|
250
|
+
// a `%%.*`-stripped value can never contain a dot, so base-10 arithmetic
|
|
251
|
+
// on it can never hit the #4619 syntax-error class. Any name ending in
|
|
252
|
+
// `_INT` (case-insensitive) is that established "already reduced to a
|
|
253
|
+
// safe integer" convention.
|
|
254
|
+
// 3. `$((10#{plan_padded}))` / `$((10#${PLAN_ID}))` — plan ids are plain
|
|
255
|
+
// integers and were never in scope; this rule only polices variables
|
|
256
|
+
// that carry a *phase* id.
|
|
257
|
+
// So a match is only a violation when the captured name contains `phase`
|
|
258
|
+
// case-insensitively (it is phase-carrying) AND does not end in `_int`
|
|
259
|
+
// case-insensitively (it has not already been reduced to a safe integer).
|
|
260
|
+
const SHELL_PHASE_ARITH_DRIFT_RE = /\$\(\(\s*10#\$?\{?([A-Za-z0-9_]+)\}?/;
|
|
261
|
+
|
|
262
|
+
// A markdown comment can't easily carry a `//` line, so the sanction for the
|
|
263
|
+
// shell-arithmetic rule is an HTML comment on the nearest preceding non-blank
|
|
264
|
+
// line: `<!-- phase-id-owner: <reason> -->`.
|
|
265
|
+
const MD_OWNER_RE = /^\s*<!--.*phase-id-owner:/;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Pure: find every unsanctioned `$((10#...))` base-10-forced shell arithmetic
|
|
269
|
+
* site in `text` that still coerces an un-reduced phase-carrying variable.
|
|
270
|
+
* Skips full-line `#` comments outright (pure prose mentioning the pattern,
|
|
271
|
+
* not executable code), and skips any captured variable name that either
|
|
272
|
+
* doesn't contain `phase` (never in scope — e.g. plan ids) or already ends
|
|
273
|
+
* in `_int` (the #4619-fix convention for "safely stripped to an integer").
|
|
274
|
+
* Sanctioned by an HTML comment `<!-- phase-id-owner: ... -->` on the
|
|
275
|
+
* nearest preceding non-blank line. Returns [{ line, found }].
|
|
276
|
+
*/
|
|
277
|
+
function findShellPhaseArithDrift(text) {
|
|
278
|
+
const out = [];
|
|
279
|
+
const lines = text.split('\n');
|
|
280
|
+
for (let i = 0; i < lines.length; i++) {
|
|
281
|
+
const line = lines[i];
|
|
282
|
+
if (/^\s*#/.test(line)) continue;
|
|
283
|
+
const m = SHELL_PHASE_ARITH_DRIFT_RE.exec(line);
|
|
284
|
+
if (!m) continue;
|
|
285
|
+
const name = m[1];
|
|
286
|
+
if (!/phase/i.test(name)) continue;
|
|
287
|
+
if (/_int$/i.test(name)) continue;
|
|
288
|
+
if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
|
|
77
289
|
out.push({ line: i + 1, found: m[0] });
|
|
78
290
|
}
|
|
79
291
|
return out;
|
|
80
292
|
}
|
|
81
293
|
|
|
294
|
+
// #4634: the markdown scan roots — shell embedded in workflow/reference docs.
|
|
295
|
+
const MD_SCAN_DIRS = [path.join('gsd-core', 'workflows'), path.join('gsd-core', 'references')];
|
|
296
|
+
|
|
297
|
+
// #4568 (epic #4634): the single-segment phase regex ban scans a THIRD root,
|
|
298
|
+
// `agents/**/*.md`, that the #4619 shell-arithmetic extension above never
|
|
299
|
+
// touched — the gsd-code-fixer agent prompts re-derive the phase-number
|
|
300
|
+
// grammar too. Reuses the same `walkMd` walker as the shell-arith scan.
|
|
301
|
+
const SINGLE_SEGMENT_SCAN_DIRS = [...MD_SCAN_DIRS, 'agents'];
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Scan `gsd-core/workflows/**\/*.md` and `gsd-core/references/**\/*.md` for
|
|
305
|
+
* unsanctioned `$((10#...))` shell arithmetic. Returns [{ file, line, found }]
|
|
306
|
+
* with repo-relative paths.
|
|
307
|
+
*/
|
|
308
|
+
function scanMarkdownShellArith(root) {
|
|
309
|
+
const violations = [];
|
|
310
|
+
for (const dir of MD_SCAN_DIRS) {
|
|
311
|
+
for (const file of walkMd(path.join(root, dir), [])) {
|
|
312
|
+
const rel = path.relative(root, file);
|
|
313
|
+
let text;
|
|
314
|
+
try {
|
|
315
|
+
text = fs.readFileSync(file, 'utf8');
|
|
316
|
+
} catch {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
for (const d of findShellPhaseArithDrift(text)) {
|
|
320
|
+
violations.push({ file: rel, kind: 'shell-arith', ...d });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return violations;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// #4568 (epic #4634): ban the single-optional-dotted-segment phase regex
|
|
328
|
+
// shape `[0-9]+(\.[0-9]+)?` (and its `\d`/doubled-backslash near-variants)
|
|
329
|
+
// outright — this is exactly the grammar that hard-rejects or silently
|
|
330
|
+
// truncates a 3-or-more-segment phase id like `23.1.2`. The canonical
|
|
331
|
+
// grammar (`src/phase-id.cts`) uses the unbounded `(?:\.\d+)*` form; shell
|
|
332
|
+
// snippets embedded in markdown can't import that module, so textual parity
|
|
333
|
+
// (`*` in place of `?`) is the fix, and this rule is the ratchet against a
|
|
334
|
+
// future site re-deriving the bounded form. Deliberately narrow to the
|
|
335
|
+
// bounded ONE-optional-segment shape — the fixed `*`-form is not flagged.
|
|
336
|
+
const SINGLE_SEGMENT_PHASE_DRIFT_RE =
|
|
337
|
+
/(?:\\{1,2}d|\[0-9\])\+\(\\{1,2}\.(?:\\{1,2}d|\[0-9\])\+\)\?/;
|
|
338
|
+
|
|
339
|
+
// A single-segment shape like `[0-9]+(\.[0-9]+)?` is not inherently
|
|
340
|
+
// phase-specific (e.g. it could describe a version number), so the rule
|
|
341
|
+
// only fires on a line whose text plausibly carries a phase-number
|
|
342
|
+
// variable — a case-insensitive `phase` substring anywhere on the line,
|
|
343
|
+
// mirroring the phase-carrying filter `findShellPhaseArithDrift` already
|
|
344
|
+
// applies to its own variable-name capture.
|
|
345
|
+
const PHASE_CARRYING_LINE_RE = /phase/i;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Pure: find every unsanctioned single-optional-dotted-segment phase regex
|
|
349
|
+
* in `text`, restricted to lines that plausibly carry a phase-number
|
|
350
|
+
* variable. Sanctioned by an HTML comment `<!-- phase-id-owner: ... -->` on
|
|
351
|
+
* the nearest preceding non-blank line (same convention as the shell-arith
|
|
352
|
+
* rule). Returns [{ line, found }].
|
|
353
|
+
*/
|
|
354
|
+
function findSingleSegmentPhaseRegexDrift(text) {
|
|
355
|
+
const out = [];
|
|
356
|
+
const lines = text.split('\n');
|
|
357
|
+
for (let i = 0; i < lines.length; i++) {
|
|
358
|
+
const line = lines[i];
|
|
359
|
+
const m = SINGLE_SEGMENT_PHASE_DRIFT_RE.exec(line);
|
|
360
|
+
if (!m) continue;
|
|
361
|
+
if (!PHASE_CARRYING_LINE_RE.test(line)) continue;
|
|
362
|
+
if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
|
|
363
|
+
out.push({ line: i + 1, found: m[0] });
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Scan `gsd-core/workflows/**\/*.md`, `gsd-core/references/**\/*.md`, and
|
|
370
|
+
* `agents/**\/*.md` for unsanctioned single-optional-dotted-segment phase
|
|
371
|
+
* regexes. Returns [{ file, line, found }] with repo-relative paths.
|
|
372
|
+
*/
|
|
373
|
+
function scanMarkdownSingleSegmentPhaseRegex(root) {
|
|
374
|
+
const violations = [];
|
|
375
|
+
for (const dir of SINGLE_SEGMENT_SCAN_DIRS) {
|
|
376
|
+
for (const file of walkMd(path.join(root, dir), [])) {
|
|
377
|
+
const rel = path.relative(root, file);
|
|
378
|
+
let text;
|
|
379
|
+
try {
|
|
380
|
+
text = fs.readFileSync(file, 'utf8');
|
|
381
|
+
} catch {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
for (const d of findSingleSegmentPhaseRegexDrift(text)) {
|
|
385
|
+
violations.push({ file: rel, kind: 'single-segment-phase-regex', ...d });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return violations;
|
|
390
|
+
}
|
|
391
|
+
|
|
82
392
|
// Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
|
|
83
393
|
const SCAN_DIRS = ['src'];
|
|
84
394
|
const SCAN_EXT = new Set(['.cts', '.ts', '.mts']);
|
|
85
395
|
// The canonical owner defines the grammar; it is exempt by construction.
|
|
86
396
|
const EXEMPT = new Set([path.join('src', 'phase-id.cts')]);
|
|
87
397
|
|
|
88
|
-
|
|
398
|
+
// #4634: the name-validity-guard rule owns a DIFFERENT file (roadmap-parser.cts
|
|
399
|
+
// defines `hasNameableContent`), so it needs its own exemption set — the
|
|
400
|
+
// token/bracket rules above must NOT start exempting roadmap-parser.cts too,
|
|
401
|
+
// since it is not their owner.
|
|
402
|
+
const NAME_VALIDITY_EXEMPT = new Set([path.join('src', 'roadmap-parser.cts')]);
|
|
403
|
+
|
|
404
|
+
function walk(dir, acc, ext) {
|
|
405
|
+
const extSet = ext || SCAN_EXT;
|
|
89
406
|
let entries;
|
|
90
407
|
try {
|
|
91
408
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -96,14 +413,83 @@ function walk(dir, acc) {
|
|
|
96
413
|
const full = path.join(dir, entry.name);
|
|
97
414
|
if (entry.isDirectory()) {
|
|
98
415
|
if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') continue;
|
|
99
|
-
walk(full, acc);
|
|
100
|
-
} else if (entry.isFile() &&
|
|
416
|
+
walk(full, acc, extSet);
|
|
417
|
+
} else if (entry.isFile() && extSet.has(path.extname(entry.name))) {
|
|
101
418
|
acc.push(full);
|
|
102
419
|
}
|
|
103
420
|
}
|
|
104
421
|
return acc;
|
|
105
422
|
}
|
|
106
423
|
|
|
424
|
+
// #4634: markdown scan for the shell phase-arithmetic ban walks a disjoint set
|
|
425
|
+
// of roots/extensions from the src/**/*.cts scan above, so it gets its own thin
|
|
426
|
+
// wrapper over the same `walk` rather than a parallel tree-walker.
|
|
427
|
+
const MD_EXT = new Set(['.md']);
|
|
428
|
+
function walkMd(dir, acc) {
|
|
429
|
+
return walk(dir, acc, MD_EXT);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ─── #2761 M4: the heading-baseline selector census ────────────────────────
|
|
433
|
+
//
|
|
434
|
+
// `phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.<MODE>)` is the other half
|
|
435
|
+
// of this seam: it decides which intro grammar a call site compiles, and the
|
|
436
|
+
// MODE argument is a fact about that site's history that no behavioural test
|
|
437
|
+
// can recover — flipping verify's milestone-complete site from LABEL_ONLY to
|
|
438
|
+
// ANY_BRACKET grants a tolerance it has never had, and every behavioural test
|
|
439
|
+
// still passes. Pinning it therefore requires reading the authored source.
|
|
440
|
+
//
|
|
441
|
+
// That reading lives HERE, not in the test suite. `tests/**` runs
|
|
442
|
+
// `local/no-source-grep` at ERROR, and its documented exemption
|
|
443
|
+
// (CONTEXT.md: RULESET.TESTS.no-source-grep.exemption) is reserved for tests
|
|
444
|
+
// whose subject is a runtime CONTRACT FILE — STATE.md, config.toml,
|
|
445
|
+
// hooks.json, agent .md — which `src/*.cts` is not. The suite had claimed that
|
|
446
|
+
// exemption anyway. Scripts are the sanctioned home for source scanning (the
|
|
447
|
+
// rule runs at `warn` in `scripts/**`, and this file already scans src/ for the
|
|
448
|
+
// grammar rules above), so the scan is exported as structured data and the test
|
|
449
|
+
// asserts on the returned census instead of on file text.
|
|
450
|
+
const SELECTOR_CALL_RE = /phaseHeadingPrefixSrcFor\(/g;
|
|
451
|
+
const SELECTOR_BASELINE_RE = /phaseHeadingPrefixSrcFor\(\s*PHASE_HEADING_BASELINE\.(ANY_BRACKET|LABEL_ONLY)/g;
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Pure: census the heading-baseline selector calls in `text`.
|
|
455
|
+
*
|
|
456
|
+
* `total` counts EVERY invocation, so a call that does not name a
|
|
457
|
+
* `PHASE_HEADING_BASELINE` member shows up as `total > ANY_BRACKET +
|
|
458
|
+
* LABEL_ONLY` — a hole in the pin rather than a silently uncounted site.
|
|
459
|
+
* Returns { ANY_BRACKET, LABEL_ONLY, total }.
|
|
460
|
+
*/
|
|
461
|
+
function countSelectorBaselines(text) {
|
|
462
|
+
const out = { ANY_BRACKET: 0, LABEL_ONLY: 0, total: 0 };
|
|
463
|
+
for (const m of text.matchAll(SELECTOR_BASELINE_RE)) out[m[1]] += 1;
|
|
464
|
+
out.total = (text.match(SELECTOR_CALL_RE) || []).length;
|
|
465
|
+
return out;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Scan the authored source tree and return the selector census keyed by
|
|
470
|
+
* repo-relative path, for every file that consumes the selector at least once.
|
|
471
|
+
* `phase-id.cts` is excluded: it DEFINES the selector, so its own occurrences
|
|
472
|
+
* are the declaration, not a consumer's choice of baseline.
|
|
473
|
+
*/
|
|
474
|
+
function scanSelectorBaselines(root) {
|
|
475
|
+
const census = {};
|
|
476
|
+
for (const dir of SCAN_DIRS) {
|
|
477
|
+
for (const file of walk(path.join(root, dir), [])) {
|
|
478
|
+
const rel = path.relative(root, file);
|
|
479
|
+
if (EXEMPT.has(rel)) continue;
|
|
480
|
+
let text;
|
|
481
|
+
try {
|
|
482
|
+
text = fs.readFileSync(file, 'utf8');
|
|
483
|
+
} catch {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const counts = countSelectorBaselines(text);
|
|
487
|
+
if (counts.total > 0) census[path.basename(file)] = counts;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return census;
|
|
491
|
+
}
|
|
492
|
+
|
|
107
493
|
/**
|
|
108
494
|
* Scan the authored source tree and return every unsanctioned phase-token
|
|
109
495
|
* re-derivation, each annotated with the repo-relative file path.
|
|
@@ -121,30 +507,94 @@ function scanRepo(root) {
|
|
|
121
507
|
continue;
|
|
122
508
|
}
|
|
123
509
|
for (const d of findPhaseIdRegexDrift(text)) {
|
|
124
|
-
violations.push({ file: rel, ...d });
|
|
510
|
+
violations.push({ file: rel, kind: 'token', ...d });
|
|
511
|
+
}
|
|
512
|
+
// #2761 M3: the bracket grammar is the second thing this seam owns.
|
|
513
|
+
for (const d of findBracketGrammarDrift(text)) {
|
|
514
|
+
violations.push({ file: rel, kind: 'bracket', ...d });
|
|
515
|
+
}
|
|
516
|
+
// #4634: name-validity-guard drift, exempting only its own owner file.
|
|
517
|
+
if (!NAME_VALIDITY_EXEMPT.has(rel)) {
|
|
518
|
+
for (const d of findNameValidityDrift(text)) {
|
|
519
|
+
violations.push({ file: rel, kind: 'name-validity', ...d });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// #4634: branch-slug fallback anti-pattern, exempt nowhere.
|
|
523
|
+
for (const d of findBranchSlugFallbackDrift(text)) {
|
|
524
|
+
violations.push({ file: rel, kind: 'branch-slug-fallback', ...d });
|
|
125
525
|
}
|
|
126
526
|
}
|
|
127
527
|
}
|
|
128
528
|
return violations;
|
|
129
529
|
}
|
|
130
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Scan EVERYTHING this seam guards: the `src/**\/*.cts` token/bracket/
|
|
533
|
+
* name-validity invariants (`scanRepo`) plus the markdown shell
|
|
534
|
+
* phase-arithmetic ban (`scanMarkdownShellArith`). This is what the CLI runs;
|
|
535
|
+
* `scanRepo` alone stays narrowly scoped to its original src/** contract so
|
|
536
|
+
* a live, separately-tracked markdown defect (#4619) cannot make the
|
|
537
|
+
* pinned-clean `scanRepo` test spuriously fail.
|
|
538
|
+
*/
|
|
539
|
+
function scanAll(root) {
|
|
540
|
+
return [
|
|
541
|
+
...scanRepo(root),
|
|
542
|
+
...scanMarkdownShellArith(root),
|
|
543
|
+
...scanMarkdownSingleSegmentPhaseRegex(root),
|
|
544
|
+
];
|
|
545
|
+
}
|
|
546
|
+
|
|
131
547
|
function main() {
|
|
132
548
|
const root = path.join(__dirname, '..');
|
|
133
|
-
const violations =
|
|
549
|
+
const violations = scanAll(root);
|
|
134
550
|
if (violations.length === 0) {
|
|
135
|
-
process.stdout.write(
|
|
551
|
+
process.stdout.write(
|
|
552
|
+
'ok phase-id-drift: no unsanctioned phase-token, bracket-grammar, name-validity, ' +
|
|
553
|
+
'branch-slug-fallback, or shell phase-arithmetic re-derivations found\n',
|
|
554
|
+
);
|
|
136
555
|
return;
|
|
137
556
|
}
|
|
138
|
-
process.stderr.write('phase-id-drift: literal re-derivation(s) of
|
|
139
|
-
process.stderr.write(
|
|
140
|
-
process.stderr.write(
|
|
141
|
-
process.stderr.write('
|
|
557
|
+
process.stderr.write('phase-id-drift: literal re-derivation(s) of a canonical grammar found.\n');
|
|
558
|
+
process.stderr.write(`Build the regex from phase-id.cjs \`${CANON_REF}\` (or phaseMarkdownRegexSource for a\n`);
|
|
559
|
+
process.stderr.write(`known number) for the phase-number token, or from ${BRACKET_OWNER_HINT}\n`);
|
|
560
|
+
process.stderr.write('for the bracket grammar, or call `hasNameableContent(` (src/roadmap-parser.cts) for\n');
|
|
561
|
+
process.stderr.write('the name-validity predicate — or sanction the site with a dedicated\n');
|
|
562
|
+
process.stderr.write('`// phase-id-owner: <reason>` comment on the line directly above the regex.\n');
|
|
563
|
+
process.stderr.write('`$((10#...))` base-10-forced shell arithmetic is banned outright in\n');
|
|
564
|
+
process.stderr.write('gsd-core/workflows/**/*.md and gsd-core/references/**/*.md — sanction with\n');
|
|
565
|
+
process.stderr.write('`<!-- phase-id-owner: <reason> -->` on the line directly above.\n');
|
|
566
|
+
process.stderr.write('The single-optional-dotted-segment phase regex `[0-9]+(\\.[0-9]+)?` (or its \\d\n');
|
|
567
|
+
process.stderr.write('near-variant) is banned outright in gsd-core/workflows/**/*.md,\n');
|
|
568
|
+
process.stderr.write('gsd-core/references/**/*.md, and agents/**/*.md — widen it to `*` (unbounded\n');
|
|
569
|
+
process.stderr.write('segments) or sanction with `<!-- phase-id-owner: <reason> -->`.\n');
|
|
570
|
+
process.stderr.write('A `.replace(\'{slug}\', ... || \'phase\')` fallback is banned outright (#4126) —\n');
|
|
571
|
+
process.stderr.write('use `renderPhaseBranchName(` or sanction with\n');
|
|
572
|
+
process.stderr.write('`// phase-id-owner: <reason>` on the line directly above:\n');
|
|
142
573
|
for (const d of violations) {
|
|
143
|
-
process.stderr.write(` ${d.file}:${d.line} ${d.found}\n`);
|
|
574
|
+
process.stderr.write(` [${d.kind}] ${d.file}:${d.line} ${d.found}\n`);
|
|
144
575
|
}
|
|
145
576
|
process.exitCode = 1;
|
|
146
577
|
}
|
|
147
578
|
|
|
148
579
|
if (require.main === module) main();
|
|
149
580
|
|
|
150
|
-
module.exports = {
|
|
581
|
+
module.exports = {
|
|
582
|
+
findPhaseIdRegexDrift,
|
|
583
|
+
findBracketGrammarDrift,
|
|
584
|
+
findNameValidityDrift,
|
|
585
|
+
findBranchSlugFallbackDrift,
|
|
586
|
+
findShellPhaseArithDrift,
|
|
587
|
+
findSingleSegmentPhaseRegexDrift,
|
|
588
|
+
scanMarkdownShellArith,
|
|
589
|
+
scanMarkdownSingleSegmentPhaseRegex,
|
|
590
|
+
scanRepo,
|
|
591
|
+
scanAll,
|
|
592
|
+
countSelectorBaselines,
|
|
593
|
+
scanSelectorBaselines,
|
|
594
|
+
TOKEN_DRIFT_RE,
|
|
595
|
+
BRACKET_CODE_DRIFT_RE,
|
|
596
|
+
NAME_VALIDITY_DRIFT_RE,
|
|
597
|
+
BRANCH_SLUG_FALLBACK_DRIFT_RE,
|
|
598
|
+
SHELL_PHASE_ARITH_DRIFT_RE,
|
|
599
|
+
SINGLE_SEGMENT_PHASE_DRIFT_RE,
|
|
600
|
+
};
|