@opengsd/gsd-core 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- 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-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -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 +268 -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 +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- 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-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- 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 +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -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-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -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 +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +44 -26
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- 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 +17 -11
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +13 -20
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- 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 +24 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +53 -13
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +162 -46
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +162 -46
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -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 +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -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 +471 -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 +488 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- 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/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Anti-divergence drift guard for the PROMPT-LAYER plan/summary-COUNTING seam
|
|
6
|
+
* (epic #3180, ADR-3180 "Planning Semantic Model Single Owner", Decision 4(e)).
|
|
7
|
+
*
|
|
8
|
+
* `scripts/lint-plan-count-drift.cjs` and `scripts/lint-milestone-window-drift.cjs`
|
|
9
|
+
* scan `src/` only — but the `.planning/` semantic derivations they own are ALSO
|
|
10
|
+
* re-derived a second time, in the PROMPT layer: the workflow markdown that
|
|
11
|
+
* ships to every runtime, authored as raw shell rather than TypeScript. Issue
|
|
12
|
+
* #1762's second reproduction traced a wrong `30 plans, 24 summaries` figure to
|
|
13
|
+
* a `ls -1 ... *-PLAN.md | wc -l` snippet in `gsd-core/workflows/progress.md` —
|
|
14
|
+
* a re-derivation no `.cts`-scoped guard can see, because it is markdown, not
|
|
15
|
+
* source. ADR-3180 Decision 4(a) requires whole-repo discovery; this guard
|
|
16
|
+
* extends that requirement from "the whole `src/` tree" to "every authored
|
|
17
|
+
* surface that can carry a derivation", covering the prompt layer the two
|
|
18
|
+
* sibling guards structurally cannot reach.
|
|
19
|
+
*
|
|
20
|
+
* Detection is intentionally NARROW, mirroring the sibling guards' precedent:
|
|
21
|
+
* a line is a re-derivation when it carries BOTH, in ONE source line:
|
|
22
|
+
* (a) a plan/summary SET GLOB — a `*` followed by a run of
|
|
23
|
+
* `[-A-Za-z0-9_.{}$]` characters and then the literal `PLAN.md` or
|
|
24
|
+
* `SUMMARY.md`. The leading `*` is load-bearing: it is what makes the
|
|
25
|
+
* line enumerate a SET of files rather than name one specific plan.
|
|
26
|
+
* `gsd-core/workflows/execute-plan.md`'s
|
|
27
|
+
* `grep -cE '^\s*<task[[:space:]>]' .../{phase}-{plan}-PLAN.md` counts
|
|
28
|
+
* TASKS *inside* one already-named plan file — it has no glob token
|
|
29
|
+
* (no `*` anywhere near `PLAN.md`), so it is not a plan-count
|
|
30
|
+
* re-derivation and correctly never matches (a).
|
|
31
|
+
* (b) a COUNTING operation on that same line — `wc -l`, or `grep -c`
|
|
32
|
+
* (optionally with bundled short flags, e.g. `grep -cE`). Reading,
|
|
33
|
+
* globbing, or merely LISTING plan/summary files (`ls *-PLAN.md`,
|
|
34
|
+
* `cat *-PLAN.md`, `--files ".../*-PLAN.md"`) without counting them is
|
|
35
|
+
* not this derivation and must not be flagged — every non-counting
|
|
36
|
+
* `*-PLAN.md`/`*-SUMMARY.md` glob in `gsd-core/workflows/plan-phase.md`
|
|
37
|
+
* (backup, `--files`, `cat`, cross-reference prose) is exactly this
|
|
38
|
+
* shape and is deliberately left alone.
|
|
39
|
+
* `*-UAT.md` never matches (a) — UAT artifacts are a different derivation
|
|
40
|
+
* this guard does not own — so `gsd-core/workflows/progress.md`'s
|
|
41
|
+
* `... *-UAT.md ... | wc -l` line correctly never fires even though it sits
|
|
42
|
+
* one line below two lines that DO.
|
|
43
|
+
*
|
|
44
|
+
* Both regexes are small, bounded, and non-backtracking by construction (a
|
|
45
|
+
* single fixed character class with no nested quantifiers) — `npm run
|
|
46
|
+
* lint:ci` runs CodeQL js/redos over this repo, the same discipline the
|
|
47
|
+
* sibling guards document in their own headers.
|
|
48
|
+
*
|
|
49
|
+
* Surfaces scanned (SCAN_DIRS): `gsd-core/workflows`, `commands`, `agents`,
|
|
50
|
+
* `skills` — the prompt-layer markdown that ships to runtimes. SCAN_EXT:
|
|
51
|
+
* `.md` only. The tree-walk / root-confinement / symlink / sanitizer
|
|
52
|
+
* machinery is SHARED with the two sibling guards via `scripts/lib/drift-scan.cjs`
|
|
53
|
+
* (ADR-3180 Decision 4's own "Rejected: let the new drift guard copy Phase 1's
|
|
54
|
+
* tree-walk / root-confinement / sanitizer") — see that module for the
|
|
55
|
+
* `isInsideRoot` case-sensitivity note, the `walk` symlink-confinement
|
|
56
|
+
* rationale, and the ReDoS-avoidance rationale for its regex-literal reader
|
|
57
|
+
* (unused by this guard's own regexes, which need no literal tokenizer, but
|
|
58
|
+
* shared for the tree walk and report sanitization).
|
|
59
|
+
*
|
|
60
|
+
* RATCHET, not an allowlist. Per ADR-3180 Decision 4(e) this guard's baseline
|
|
61
|
+
* (`scripts/baselines/planning-prompt-drift-baseline.json`) mirrors
|
|
62
|
+
* `scripts/qa-smell-ratchet.cjs`'s precedent exactly: a violation whose
|
|
63
|
+
* `(file, text)` pair is already RECORDED in the baseline is KNOWN and never
|
|
64
|
+
* fails; a violation whose pair is NOT recorded is NEW and fails, telling the
|
|
65
|
+
* author to route the count through the `gsd-core` CLI instead of re-deriving
|
|
66
|
+
* it in shell; a recorded pair that no longer fires in this run is STALE and
|
|
67
|
+
* ALSO fails, forcing `--update` (run by a maintainer after a migration) to
|
|
68
|
+
* prune it — this is what makes the baseline SHRINK-ONLY as call sites
|
|
69
|
+
* migrate off the shell re-derivation, rather than a list that only ever
|
|
70
|
+
* grows. Matching is keyed on the pair (`file`, TRIMMED source `text`), never
|
|
71
|
+
* the line number: a workflow markdown file's line numbers churn on every
|
|
72
|
+
* unrelated edit (a new paragraph, a reworded step) and a number-keyed
|
|
73
|
+
* baseline would need hand-maintenance on changes that have nothing to do
|
|
74
|
+
* with this derivation at all.
|
|
75
|
+
*
|
|
76
|
+
* COUNT, not duplicate rows. Two DIFFERENT source lines can carry the exact
|
|
77
|
+
* same (file, TRIMMED text) pair — `gsd-core/workflows/plan-phase.md` has two
|
|
78
|
+
* byte-identical `DISK_PLANS=$(ls "${PHASE_DIR}"/*-PLAN.md 2>/dev/null | wc -l
|
|
79
|
+
* | tr -d ' ')` sites. Keying on (file, text) alone with one baseline row per
|
|
80
|
+
* OCCURRENCE made a partial migration invisible: migrating ONE of the two
|
|
81
|
+
* sites still leaves a violation matching the row, so nothing goes fresh and
|
|
82
|
+
* nothing goes stale — the remaining, unmigrated copy is silently covered by
|
|
83
|
+
* the row meant to acknowledge the pair NO LONGER MIGRATING. Each baseline
|
|
84
|
+
* entry therefore carries a `count` — the number of byte-identical
|
|
85
|
+
* occurrences of that (file, text) pair acknowledged at this site, not a
|
|
86
|
+
* duplicated row per occurrence:
|
|
87
|
+
* - actual occurrences this run < entry.count -> STALE as a PARTIAL
|
|
88
|
+
* migration: some but not all acknowledged copies are gone, so the entry
|
|
89
|
+
* no longer describes reality and must be re-recorded via `--update`;
|
|
90
|
+
* - actual occurrences this run > entry.count -> the occurrences beyond
|
|
91
|
+
* the acknowledged count are FRESH: a new copy landed next to one that
|
|
92
|
+
* was already acknowledged;
|
|
93
|
+
* - actual occurrences this run === 0 -> fully STALE, the
|
|
94
|
+
* existing "site was migrated, delete the row" case;
|
|
95
|
+
* - actual occurrences this run === entry.count -> fully acknowledged, no
|
|
96
|
+
* failure.
|
|
97
|
+
* Line numbers stay OUT of the key even with counting — that is still what
|
|
98
|
+
* keeps the baseline immune to unrelated churn; `count` answers "how many",
|
|
99
|
+
* never "which lines".
|
|
100
|
+
*
|
|
101
|
+
* KNOWN, ACCEPTED limits of a per-line textual scan (same tradeoff the
|
|
102
|
+
* sibling guards document): a re-derivation whose glob and counting operator
|
|
103
|
+
* are split across two DIFFERENT lines (e.g. a variable holding the glob,
|
|
104
|
+
* counted via `wc -l` on the next line) is not caught by this narrow shape.
|
|
105
|
+
* That is left to code review, not this regex.
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
const fs = require('node:fs');
|
|
109
|
+
const path = require('node:path');
|
|
110
|
+
const driftScan = require('./lib/drift-scan.cjs');
|
|
111
|
+
const { sanitizeForReport, scanTree } = driftScan;
|
|
112
|
+
|
|
113
|
+
// (a) A plan/summary SET GLOB: a `*` followed by a bounded run of path/brace/
|
|
114
|
+
// var-interpolation characters and then the literal `PLAN.md` or
|
|
115
|
+
// `SUMMARY.md`. The character class is fixed and the quantifier is a single
|
|
116
|
+
// `*` (regex "zero or more", not the shell glob character being matched) over
|
|
117
|
+
// that one class — no nesting, no alternation inside a repeated group, so
|
|
118
|
+
// there is nothing here for a backtracking engine to explore more than once.
|
|
119
|
+
const PLAN_SUMMARY_GLOB_RE = /\*[-A-Za-z0-9_.{}$]*(?:PLAN|SUMMARY)\.md/;
|
|
120
|
+
|
|
121
|
+
// `scanTree` (scripts/lib/drift-scan.cjs) builds its repo-relative path via
|
|
122
|
+
// `path.relative()`, which uses NATIVE separators: on Windows that is
|
|
123
|
+
// `gsd-core\workflows\execute-plan.md`, while the committed baseline
|
|
124
|
+
// (`scripts/baselines/planning-prompt-drift-baseline.json`) stores POSIX
|
|
125
|
+
// paths (`gsd-core/workflows/execute-plan.md`). Every baseline lookup in this
|
|
126
|
+
// guard is keyed on that path, so an un-normalized Windows path silently
|
|
127
|
+
// fails to match ANY baseline entry — every real violation reports as FRESH
|
|
128
|
+
// and every baseline entry reports as STALE (100% failure rate on Windows,
|
|
129
|
+
// caught by GitHub Actions' Windows CI lane on PR #3223; the remote runner
|
|
130
|
+
// this repo otherwise gates on is Linux-only and cannot see this class).
|
|
131
|
+
// Normalized UNCONDITIONALLY — never gated on `process.platform` — because a
|
|
132
|
+
// platform-conditional normalizer is itself the bug: it makes the POSIX path
|
|
133
|
+
// the tested case and leaves the Windows branch exercised only on Windows.
|
|
134
|
+
// Applied at the single seam `findPromptDrift` owns (the only place a
|
|
135
|
+
// repo-relative path enters this guard's violation objects), so ONE
|
|
136
|
+
// normalized value flows into all four consumers: the baseline key
|
|
137
|
+
// (`diffAgainstBaseline`), the `--update` writer (`writeBaseline` via
|
|
138
|
+
// `dedupeViolationsForBaseline`), the violation report (`main`), and the
|
|
139
|
+
// tests.
|
|
140
|
+
function toPosixRel(relPath) {
|
|
141
|
+
return relPath.replace(/\\/g, '/');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// (b) A counting operation: `wc -l`, or `grep -c` optionally followed by
|
|
145
|
+
// bundled short flags before the next space (e.g. `grep -cE`, `grep -cE`).
|
|
146
|
+
// `[A-Za-z]{0,4}` bounds the bundled-flag run so the alternative branch is
|
|
147
|
+
// exactly as fixed-width-bounded as `wc -l` — no unbounded quantifier chained
|
|
148
|
+
// to another, so nothing to backtrack.
|
|
149
|
+
const COUNTING_OP_RE = /wc -l|grep -c[A-Za-z]{0,4}\b/;
|
|
150
|
+
|
|
151
|
+
// Prompt-layer markdown that ships to every runtime.
|
|
152
|
+
const SCAN_DIRS = ['gsd-core/workflows', 'commands', 'agents', 'skills'];
|
|
153
|
+
const SCAN_EXT = new Set(['.md']);
|
|
154
|
+
|
|
155
|
+
const BASELINE_REL_PATH = path.join('scripts', 'baselines', 'planning-prompt-drift-baseline.json');
|
|
156
|
+
|
|
157
|
+
// ADR-3180 Decision 4(e): a baseline entry is "acknowledged, in writing, with
|
|
158
|
+
// the issue that owns its removal" — that is Phase 8 (#3218, "the prompt
|
|
159
|
+
// layer": give the workflow layer a CLI surface to ask for plan and phase
|
|
160
|
+
// counts, and burn this ratchet baseline to zero), NOT the epic (#3180)
|
|
161
|
+
// itself. #3180 is the scope authority for the whole consolidation; #3218 is
|
|
162
|
+
// the phase that actually deletes these shell re-derivations.
|
|
163
|
+
const RATCHET_OWNER_ISSUE = '#3218';
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Pure: find every plan/summary-count re-derivation line in `text`.
|
|
167
|
+
* `relPath` is the repo-relative path (native separators or POSIX, either
|
|
168
|
+
* is accepted) — normalized via `toPosixRel` and attached as `file` on every
|
|
169
|
+
* result; this function applies no per-file exemption, so `relPath` is not
|
|
170
|
+
* otherwise consulted for detection.
|
|
171
|
+
* Returns [{ file, line, found, text }] — `file` is always POSIX-separated,
|
|
172
|
+
* `text` is the TRIMMED source line, the same value the baseline keys on.
|
|
173
|
+
*/
|
|
174
|
+
function findPromptDrift(text, relPath) {
|
|
175
|
+
const file = toPosixRel(relPath);
|
|
176
|
+
const out = [];
|
|
177
|
+
const lines = text.split('\n');
|
|
178
|
+
for (let i = 0; i < lines.length; i++) {
|
|
179
|
+
const line = lines[i];
|
|
180
|
+
const globMatch = PLAN_SUMMARY_GLOB_RE.exec(line);
|
|
181
|
+
if (!globMatch) continue;
|
|
182
|
+
if (!COUNTING_OP_RE.test(line)) continue;
|
|
183
|
+
out.push({ file, line: i + 1, found: globMatch[0], text: line.trim() });
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Scan the prompt-layer markdown tree and return every re-derivation, each
|
|
190
|
+
* annotated with the repo-relative file path (POSIX-normalized — see
|
|
191
|
+
* `toPosixRel`).
|
|
192
|
+
*/
|
|
193
|
+
function scanRepo(root) {
|
|
194
|
+
return scanTree({
|
|
195
|
+
root,
|
|
196
|
+
scanDirs: SCAN_DIRS,
|
|
197
|
+
scanExt: SCAN_EXT,
|
|
198
|
+
onFile(rel, text) {
|
|
199
|
+
return findPromptDrift(text, rel);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Read and parse the ratchet baseline. Returns `{ entries, errors }` —
|
|
206
|
+
* `entries` is `[]` and `errors` names the problem when the file is missing,
|
|
207
|
+
* empty, invalid JSON, or malformed; callers in check mode treat a non-empty
|
|
208
|
+
* `errors` as a hard failure (mirrors `qa-smell-ratchet.cjs`'s `readBaseline`).
|
|
209
|
+
*/
|
|
210
|
+
function loadBaseline(root) {
|
|
211
|
+
const baselinePath = path.join(root, BASELINE_REL_PATH);
|
|
212
|
+
if (!fs.existsSync(baselinePath)) {
|
|
213
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} is missing — run \`node scripts/lint-planning-prompt-drift.cjs --update\` to generate it`] };
|
|
214
|
+
}
|
|
215
|
+
const raw = fs.readFileSync(baselinePath, 'utf8');
|
|
216
|
+
if (raw.trim() === '') {
|
|
217
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} is present but empty`] };
|
|
218
|
+
}
|
|
219
|
+
let doc;
|
|
220
|
+
try {
|
|
221
|
+
doc = JSON.parse(raw);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} is not valid JSON: ${err.message}`] };
|
|
224
|
+
}
|
|
225
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
226
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH} must be a JSON object, got ${Array.isArray(doc) ? 'array' : typeof doc}`] };
|
|
227
|
+
}
|
|
228
|
+
if (!Array.isArray(doc.entries)) {
|
|
229
|
+
return { entries: [], errors: [`${BASELINE_REL_PATH}: "entries" must be an array, got ${JSON.stringify(doc.entries)}`] };
|
|
230
|
+
}
|
|
231
|
+
const errors = [];
|
|
232
|
+
const entries = [];
|
|
233
|
+
doc.entries.forEach((entry, i) => {
|
|
234
|
+
const where = `${BASELINE_REL_PATH}.entries[${i}]`;
|
|
235
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
236
|
+
errors.push(`${where} must be an object, got ${JSON.stringify(entry)}`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (typeof entry.file !== 'string' || entry.file === '') {
|
|
240
|
+
errors.push(`${where}.file must be a non-empty string, got ${JSON.stringify(entry.file)}`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (typeof entry.text !== 'string' || entry.text === '') {
|
|
244
|
+
errors.push(`${where}.text must be a non-empty string, got ${JSON.stringify(entry.text)}`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// `count` is optional on read (diffAgainstBaseline defaults an absent
|
|
248
|
+
// count to 1) but when present must be a positive integer — the number
|
|
249
|
+
// of byte-identical (file, text) occurrences this entry acknowledges.
|
|
250
|
+
if (entry.count !== undefined && !(Number.isInteger(entry.count) && entry.count >= 1)) {
|
|
251
|
+
errors.push(`${where}.count must be a positive integer when present, got ${JSON.stringify(entry.count)}`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
entries.push(entry);
|
|
255
|
+
});
|
|
256
|
+
return { entries, errors };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Diff scanned `violations` (from `scanRepo`) against baseline `entries`,
|
|
261
|
+
* matched by the pair (`file`, TRIMMED `text`) — never the line
|
|
262
|
+
* number — and COUNT-aware: an entry acknowledges `entry.count`
|
|
263
|
+
* (default 1 when absent) byte-identical occurrences of that pair, not
|
|
264
|
+
* merely its presence. Returns `{ fresh, stale }`:
|
|
265
|
+
* - `fresh`: violations whose (file, text) pair is NOT in the baseline at
|
|
266
|
+
* all (a brand new site), PLUS any occurrences of a KNOWN pair beyond
|
|
267
|
+
* its acknowledged `count` (a new copy landed next to an
|
|
268
|
+
* already-acknowledged one) — both fail the build as NEW.
|
|
269
|
+
* - `stale`: baseline entries whose actual occurrence count this run is
|
|
270
|
+
* LESS than their acknowledged `count` — zero actual
|
|
271
|
+
* occurrences is the fully-migrated case ("site was migrated, delete
|
|
272
|
+
* the row"); a positive but short count is a PARTIAL migration (some
|
|
273
|
+
* but not all acknowledged copies are gone). Both fail the build,
|
|
274
|
+
* forcing `--update` to re-record the pair (this is what keeps the
|
|
275
|
+
* baseline shrink-only and what makes a partial migration visible
|
|
276
|
+
* instead of silently covered by the still-present sibling
|
|
277
|
+
* occurrence).
|
|
278
|
+
*/
|
|
279
|
+
function diffAgainstBaseline(violations, baseline) {
|
|
280
|
+
const key = (file, text) => `${file}${text}`;
|
|
281
|
+
|
|
282
|
+
// Group this run's violations by (file, text) so a duplicated pair's
|
|
283
|
+
// occurrence COUNT — not merely its presence — can be
|
|
284
|
+
// compared against what the baseline entry acknowledges.
|
|
285
|
+
const actualByKey = new Map();
|
|
286
|
+
for (const v of violations) {
|
|
287
|
+
const k = key(v.file, v.text);
|
|
288
|
+
let vs = actualByKey.get(k);
|
|
289
|
+
if (!vs) { vs = []; actualByKey.set(k, vs); }
|
|
290
|
+
vs.push(v);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const knownKeys = new Set(baseline.map((e) => key(e.file, e.text)));
|
|
294
|
+
|
|
295
|
+
const fresh = [];
|
|
296
|
+
const stale = [];
|
|
297
|
+
|
|
298
|
+
// Every occurrence of a pair the baseline has never recorded at all is NEW.
|
|
299
|
+
for (const [k, vs] of actualByKey) {
|
|
300
|
+
if (!knownKeys.has(k)) fresh.push(...vs);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// For every RECORDED pair, compare its acknowledged count against how
|
|
304
|
+
// many occurrences this run actually found.
|
|
305
|
+
for (const entry of baseline) {
|
|
306
|
+
const k = key(entry.file, entry.text);
|
|
307
|
+
const expected = entry.count ?? 1;
|
|
308
|
+
const vs = actualByKey.get(k) || [];
|
|
309
|
+
const actual = vs.length;
|
|
310
|
+
if (actual < expected) {
|
|
311
|
+
// Zero actual occurrences is the fully-stale case; 0 < actual <
|
|
312
|
+
// expected is a partial migration — both are STALE, and
|
|
313
|
+
// both carry the expected/actual counts so the caller can name the
|
|
314
|
+
// mismatch.
|
|
315
|
+
stale.push({ ...entry, count: expected, actualCount: actual });
|
|
316
|
+
} else if (actual > expected) {
|
|
317
|
+
// Occurrences beyond the acknowledged count are a NEW copy landing
|
|
318
|
+
// next to one that was already acknowledged.
|
|
319
|
+
fresh.push(...vs.slice(expected));
|
|
320
|
+
}
|
|
321
|
+
// actual === expected: fully acknowledged, no failure.
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { fresh, stale };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Stable sort: by `file`, then by `text`. */
|
|
328
|
+
function sortEntries(entries) {
|
|
329
|
+
return [...entries].sort((a, b) => {
|
|
330
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
331
|
+
if (a.text !== b.text) return a.text < b.text ? -1 : 1;
|
|
332
|
+
return 0;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Collapse `violations` into one baseline row per distinct (file, text) pair,
|
|
338
|
+
* carrying a `count` of how many occurrences that pair has in THIS run — see
|
|
339
|
+
* the module header's "COUNT, not duplicate rows" note. Pure; no I/O.
|
|
340
|
+
*/
|
|
341
|
+
function dedupeViolationsForBaseline(violations) {
|
|
342
|
+
const order = [];
|
|
343
|
+
const byKey = new Map();
|
|
344
|
+
for (const v of violations) {
|
|
345
|
+
const k = `${v.file}${v.text}`;
|
|
346
|
+
let entry = byKey.get(k);
|
|
347
|
+
if (!entry) {
|
|
348
|
+
entry = { file: v.file, text: v.text, derivation: 'plan-count', owner_issue: RATCHET_OWNER_ISSUE, count: 0 };
|
|
349
|
+
byKey.set(k, entry);
|
|
350
|
+
order.push(entry);
|
|
351
|
+
}
|
|
352
|
+
entry.count += 1;
|
|
353
|
+
}
|
|
354
|
+
return order;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function writeBaseline(root, violations) {
|
|
358
|
+
const entries = sortEntries(dedupeViolationsForBaseline(violations));
|
|
359
|
+
const doc = {
|
|
360
|
+
$comment:
|
|
361
|
+
'ADR-3180 Decision 4(e) ratchet, owned by Phase 8 (#3218). See scripts/lint-planning-prompt-drift.cjs. '
|
|
362
|
+
+ 'SHRINK-ONLY: entries are removed as sites migrate to the gsd-core CLI; new or changed entries fail '
|
|
363
|
+
+ 'lint:ci. `count` is the number of byte-identical (file, text) occurrences acknowledged at this site '
|
|
364
|
+
+ '— a run producing fewer fails as a partial migration, more fails as an unacknowledged new copy.',
|
|
365
|
+
entries,
|
|
366
|
+
};
|
|
367
|
+
const baselinePath = path.join(root, BASELINE_REL_PATH);
|
|
368
|
+
fs.mkdirSync(path.dirname(baselinePath), { recursive: true });
|
|
369
|
+
fs.writeFileSync(baselinePath, `${JSON.stringify(doc, null, 2)}\n`, 'utf8');
|
|
370
|
+
return entries;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Resolve the scan root for a CLI run. Default: this repo — exactly what
|
|
375
|
+
* `lint:ci` invokes. `--root <dir>` overrides it (#3640) so the CLI
|
|
376
|
+
* end-to-end test can prove the guard's FAIL path against an isolated temp
|
|
377
|
+
* tree instead of writing its fixture into the shared `gsd-core/workflows/`
|
|
378
|
+
* that parallel test chunks observe mid-lifecycle (the emitted-provenance
|
|
379
|
+
* manifest build copies the transient file into all 19 runtime manifests;
|
|
380
|
+
* the #3333 TOCTOU ENOENT crash was the same writer's first symptom).
|
|
381
|
+
*
|
|
382
|
+
* Usage errors exit 2 — a code distinct from the guard's own findings exit
|
|
383
|
+
* (1), so tests and CI can tell "bad invocation" from "drift found" without
|
|
384
|
+
* parsing stderr. Fail-closed on every bad shape, because a wrong root is
|
|
385
|
+
* not harmless: `--root --update` would otherwise resolve the flag itself
|
|
386
|
+
* into a cwd-relative path and --update would happily `mkdir -p` a stray
|
|
387
|
+
* baseline tree inside the shared source directory (#3640's write class).
|
|
388
|
+
* A value that starts with `-` (another flag) or does not name an existing
|
|
389
|
+
* directory is therefore a usage error, never a scan root. With repeated
|
|
390
|
+
* `--root` flags the first wins (same first-match convention as the boolean
|
|
391
|
+
* `--update` parse).
|
|
392
|
+
*/
|
|
393
|
+
function resolveScanRoot() {
|
|
394
|
+
const rootIdx = process.argv.indexOf('--root');
|
|
395
|
+
if (rootIdx === -1) return path.join(__dirname, '..');
|
|
396
|
+
const value = process.argv[rootIdx + 1];
|
|
397
|
+
const isDir = value !== undefined
|
|
398
|
+
&& !value.startsWith('-')
|
|
399
|
+
&& fs.existsSync(value)
|
|
400
|
+
&& fs.statSync(value).isDirectory();
|
|
401
|
+
if (!isDir) {
|
|
402
|
+
process.stderr.write('planning-prompt-drift: --root requires an existing directory argument (usage: --root <dir>)\n');
|
|
403
|
+
process.exitCode = 2;
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return path.resolve(value);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function main() {
|
|
410
|
+
const root = resolveScanRoot();
|
|
411
|
+
if (root === null) return;
|
|
412
|
+
const update = process.argv.includes('--update');
|
|
413
|
+
const violations = scanRepo(root);
|
|
414
|
+
|
|
415
|
+
if (update) {
|
|
416
|
+
const entries = writeBaseline(root, violations);
|
|
417
|
+
process.stdout.write(`ok planning-prompt-drift: baseline regenerated with ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'}\n`);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const { entries: baseline, errors } = loadBaseline(root);
|
|
422
|
+
if (errors.length > 0) {
|
|
423
|
+
process.stderr.write('planning-prompt-drift: baseline load error(s):\n');
|
|
424
|
+
for (const e of errors) process.stderr.write(` ${e}\n`);
|
|
425
|
+
process.exitCode = 1;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const { fresh, stale } = diffAgainstBaseline(violations, baseline);
|
|
430
|
+
|
|
431
|
+
if (fresh.length === 0 && stale.length === 0) {
|
|
432
|
+
process.stdout.write(`ok planning-prompt-drift: no unacknowledged plan/summary count re-derivations in the prompt layer (${baseline.length} known)\n`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (fresh.length > 0) {
|
|
437
|
+
process.stderr.write('planning-prompt-drift: NEW plan/summary count re-derivation(s) found in the prompt layer.\n');
|
|
438
|
+
process.stderr.write('Route the count through the gsd-core CLI instead of re-deriving it in shell (ls .../*-PLAN.md | wc -l\n');
|
|
439
|
+
process.stderr.write('or grep -c on a *-PLAN.md/*-SUMMARY.md glob), or add an acknowledged entry to\n');
|
|
440
|
+
process.stderr.write(`${BASELINE_REL_PATH} via --update:\n`);
|
|
441
|
+
for (const v of fresh) {
|
|
442
|
+
process.stderr.write(` ${sanitizeForReport(v.file)}:${v.line} ${sanitizeForReport(v.found)} ${sanitizeForReport(v.text)}\n`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (stale.length > 0) {
|
|
447
|
+
process.stderr.write('\nplanning-prompt-drift: STALE baseline entr' + (stale.length === 1 ? 'y' : 'ies') + " (fully migrated, or a PARTIAL migration — fewer occurrences found than acknowledged; delete or re-record the row):\n");
|
|
448
|
+
for (const e of stale) {
|
|
449
|
+
process.stderr.write(` ${sanitizeForReport(e.file)} ${sanitizeForReport(e.text)} (found ${e.actualCount}/${e.count} acknowledged occurrence${e.count === 1 ? '' : 's'})\n`);
|
|
450
|
+
}
|
|
451
|
+
process.stderr.write(`\n remedy: node scripts/lint-planning-prompt-drift.cjs --update\n`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
process.exitCode = 1;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (require.main === module) main();
|
|
458
|
+
|
|
459
|
+
module.exports = {
|
|
460
|
+
findPromptDrift,
|
|
461
|
+
scanRepo,
|
|
462
|
+
toPosixRel,
|
|
463
|
+
loadBaseline,
|
|
464
|
+
diffAgainstBaseline,
|
|
465
|
+
writeBaseline,
|
|
466
|
+
PLAN_SUMMARY_GLOB_RE,
|
|
467
|
+
COUNTING_OP_RE,
|
|
468
|
+
SCAN_DIRS,
|
|
469
|
+
SCAN_EXT,
|
|
470
|
+
BASELINE_REL_PATH,
|
|
471
|
+
};
|