@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
package/scripts/base64-scan.sh
CHANGED
|
@@ -9,12 +9,29 @@
|
|
|
9
9
|
# scripts/base64-scan.sh --file path/to/file # Scan a single file
|
|
10
10
|
# scripts/base64-scan.sh --dir agents/ # Scan all files in a directory
|
|
11
11
|
#
|
|
12
|
-
# Exit codes:
|
|
13
|
-
# 0
|
|
14
|
-
# 1
|
|
15
|
-
#
|
|
12
|
+
# Exit codes (ADR-3889, #3908 — registered in gsd-core/bin/shared/exit-codes.json):
|
|
13
|
+
# 0 = clean
|
|
14
|
+
# 1 = findings detected
|
|
15
|
+
# $EXIT_USAGE (64) = usage error (bad argv, missing --file/--dir target)
|
|
16
|
+
# $EXIT_NO_INPUT (66) = ran; scope established; zero files in scope (genuinely empty)
|
|
17
|
+
# $EXIT_UNAVAILABLE (69) = could not establish scope (bad ref, not a repo, unreadable dir)
|
|
16
18
|
set -euo pipefail
|
|
17
19
|
|
|
20
|
+
# ─── Exit-code registry (ADR-3889, #3908) ────────────────────────────────────
|
|
21
|
+
# Resolved relative to THIS script's location, not the caller's cwd. Loud,
|
|
22
|
+
# non-zero failure if the fragment is missing — never fall back to a guessed
|
|
23
|
+
# literal integer, and never let a missing registry silently degrade to
|
|
24
|
+
# exit 0.
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
EXIT_CODES_SH="$SCRIPT_DIR/../gsd-core/bin/shared/exit-codes.sh"
|
|
27
|
+
if [[ ! -f "$EXIT_CODES_SH" ]]; then
|
|
28
|
+
echo "base64-scan: FATAL: exit-code registry not found at $EXIT_CODES_SH" >&2
|
|
29
|
+
echo " Regenerate with: node scripts/gen-exit-code-registry.cjs --write" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
# shellcheck disable=SC1090
|
|
33
|
+
. "$EXIT_CODES_SH"
|
|
34
|
+
|
|
18
35
|
# ── Locale hardening (#116) ───────────────────────────────────────────────────
|
|
19
36
|
# BSD tr (macOS) treats input bytes as multi-byte characters under any UTF-8
|
|
20
37
|
# locale. When the input to `tr -cd '[:print:]'` contains bytes that are not
|
|
@@ -180,7 +197,31 @@ collect_files() {
|
|
|
180
197
|
case "$mode" in
|
|
181
198
|
--diff)
|
|
182
199
|
local base="${1:-origin/main}"
|
|
183
|
-
git
|
|
200
|
+
# Run git separately from the filter pipe so its OWN exit status (not
|
|
201
|
+
# grep's) decides whether the diff could be established. stdout and
|
|
202
|
+
# stderr are captured SEPARATELY (never merged with `2>&1`) so that a
|
|
203
|
+
# warning git writes to stderr on an otherwise successful diff can
|
|
204
|
+
# never be mistaken for a filename in the file list. On failure the
|
|
205
|
+
# captured stderr is emitted as the diagnostic; on success it is
|
|
206
|
+
# forwarded as a warning, never folded into the file list.
|
|
207
|
+
# `|| true` on the filter below is CORRECT (not gratuitous): `grep -v`
|
|
208
|
+
# exits 1 when every line is filtered out (e.g. an all-images diff),
|
|
209
|
+
# which is a legitimate empty result, not a failure to run.
|
|
210
|
+
local raw status err_file
|
|
211
|
+
err_file=$(mktemp)
|
|
212
|
+
raw=$(git diff --name-only --diff-filter=ACMR "$base"...HEAD 2>"$err_file")
|
|
213
|
+
status=$?
|
|
214
|
+
if (( status != 0 )); then
|
|
215
|
+
cat "$err_file" >&2
|
|
216
|
+
rm -f "$err_file"
|
|
217
|
+
exit "$EXIT_UNAVAILABLE"
|
|
218
|
+
fi
|
|
219
|
+
if [[ -s "$err_file" ]]; then
|
|
220
|
+
echo "Warning: git diff emitted stderr output:" >&2
|
|
221
|
+
cat "$err_file" >&2
|
|
222
|
+
fi
|
|
223
|
+
rm -f "$err_file"
|
|
224
|
+
printf '%s\n' "$raw" \
|
|
184
225
|
| grep -vE '\.(png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|otf|zip|tar|gz|pdf)$' || true
|
|
185
226
|
;;
|
|
186
227
|
--file)
|
|
@@ -188,24 +229,41 @@ collect_files() {
|
|
|
188
229
|
echo "$1"
|
|
189
230
|
else
|
|
190
231
|
echo "Error: file not found: $1" >&2
|
|
191
|
-
exit
|
|
232
|
+
exit "$EXIT_USAGE"
|
|
192
233
|
fi
|
|
193
234
|
;;
|
|
194
235
|
--dir)
|
|
195
236
|
local dir="$1"
|
|
196
237
|
if [[ ! -d "$dir" ]]; then
|
|
197
238
|
echo "Error: directory not found: $dir" >&2
|
|
198
|
-
exit
|
|
239
|
+
exit "$EXIT_USAGE"
|
|
240
|
+
fi
|
|
241
|
+
# Same treatment as --diff: a `find` that fails (e.g. permission
|
|
242
|
+
# denied) must not be reported as an empty directory, and stdout/stderr
|
|
243
|
+
# are captured separately so a stderr warning never enters the file list.
|
|
244
|
+
local raw status err_file
|
|
245
|
+
err_file=$(mktemp)
|
|
246
|
+
raw=$(find "$dir" -type f ! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/dist/*' \
|
|
247
|
+
! -name '*.png' ! -name '*.jpg' ! -name '*.gif' ! -name '*.woff*' 2>"$err_file")
|
|
248
|
+
status=$?
|
|
249
|
+
if (( status != 0 )); then
|
|
250
|
+
cat "$err_file" >&2
|
|
251
|
+
rm -f "$err_file"
|
|
252
|
+
exit "$EXIT_UNAVAILABLE"
|
|
253
|
+
fi
|
|
254
|
+
if [[ -s "$err_file" ]]; then
|
|
255
|
+
echo "Warning: find emitted stderr output:" >&2
|
|
256
|
+
cat "$err_file" >&2
|
|
199
257
|
fi
|
|
200
|
-
|
|
201
|
-
|
|
258
|
+
rm -f "$err_file"
|
|
259
|
+
printf '%s\n' "$raw"
|
|
202
260
|
;;
|
|
203
261
|
--stdin)
|
|
204
262
|
cat
|
|
205
263
|
;;
|
|
206
264
|
*)
|
|
207
265
|
echo "Usage: $0 --diff [base] | --file <path> | --dir <path> | --stdin" >&2
|
|
208
|
-
exit
|
|
266
|
+
exit "$EXIT_USAGE"
|
|
209
267
|
;;
|
|
210
268
|
esac
|
|
211
269
|
}
|
|
@@ -309,7 +367,7 @@ extract_and_check_blobs() {
|
|
|
309
367
|
main() {
|
|
310
368
|
if [[ $# -eq 0 ]]; then
|
|
311
369
|
echo "Usage: $0 --diff [base] | --file <path> | --dir <path>" >&2
|
|
312
|
-
exit
|
|
370
|
+
exit "$EXIT_USAGE"
|
|
313
371
|
fi
|
|
314
372
|
|
|
315
373
|
load_ignorelist
|
|
@@ -321,8 +379,12 @@ main() {
|
|
|
321
379
|
files=$(collect_files "$mode" "$@")
|
|
322
380
|
|
|
323
381
|
if [[ -z "$files" ]]; then
|
|
382
|
+
# collect_files already exited (UNAVAILABLE/USAGE) for anything that
|
|
383
|
+
# could not establish scope. Reaching here with an empty result means
|
|
384
|
+
# scope WAS established and is genuinely empty (e.g. an all-images
|
|
385
|
+
# diff) — that is NO_INPUT, not a silent clean pass.
|
|
324
386
|
echo "base64-scan: no files to scan"
|
|
325
|
-
exit
|
|
387
|
+
exit "$EXIT_NO_INPUT"
|
|
326
388
|
fi
|
|
327
389
|
|
|
328
390
|
local total=0
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "ADR-3180 Decision 4(e) ratchet, owned by Phase 8 (#3218). See scripts/lint-planning-prompt-drift.cjs. SHRINK-ONLY: entries are removed as sites migrate to the gsd-core CLI; new or changed entries fail lint:ci. `count` is the number of byte-identical (file, text) occurrences acknowledged at this site — a run producing fewer fails as a partial migration, more fails as an unacknowledged new copy. Emptied by #3218: all 6 recorded sites (7 occurrences) migrated to `gsd_run query find-phase` — see gsd-core/workflows/{execute-plan,plan-phase,plan-review-convergence,progress}.md.",
|
|
3
|
+
"entries": []
|
|
4
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "ADR-3180 §8.1 rule 2 ratchet, owned by Phase 11 (#3309). See scripts/lint-planning-snapshot-bypass-drift.cjs. SHRINK-ONLY: entries are removed as cmdValidateHealth migrates onto src/planning-snapshot.cts; new or changed entries fail lint:ci. `count` is the number of byte-identical (file, text) occurrences acknowledged at this site — a run producing fewer fails as a partial migration, more fails as an unacknowledged new copy.",
|
|
3
|
+
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"file": "src/verify.cts",
|
|
6
|
+
"text": "const stateContent = fs.readFileSync(statePath, 'utf-8');",
|
|
7
|
+
"derivation": "planning-snapshot-bypass",
|
|
8
|
+
"owner_issue": "#3309",
|
|
9
|
+
"count": 1
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "#3409 unreachable-shell-guard ratchet. See scripts/lint-unreachable-guard-drift.cjs. SHRINK-ONLY: entries are removed as sites migrate off the unreachable-arm shape; new or changed entries fail lint:ci. `count` is the number of byte-identical (file, text) occurrences acknowledged at this site — a run producing fewer fails as a partial migration, more fails as an unacknowledged new copy.",
|
|
3
|
+
"entries": []
|
|
4
|
+
}
|
package/scripts/build-hooks.js
CHANGED
|
@@ -71,6 +71,11 @@ const HOOKS_TO_COPY = [
|
|
|
71
71
|
'gsd-session-state.sh',
|
|
72
72
|
'gsd-validate-commit.sh',
|
|
73
73
|
'gsd-phase-boundary.sh',
|
|
74
|
+
// Portable node resolver (#3662). Not a registered hook itself: managed JS
|
|
75
|
+
// hook commands under --portable-hooks route through it (bash <resolver>
|
|
76
|
+
// <baked-node> <script>) so node resolves at hook-fire time in every
|
|
77
|
+
// environment sharing the config root. Staged verbatim — no templating.
|
|
78
|
+
'gsd-node-runner.sh',
|
|
74
79
|
// Graphify auto-update hook (#3347 / PR #3557 / #3579). Opt-in via
|
|
75
80
|
// .planning/config.json graphify.auto_update; off by default.
|
|
76
81
|
'gsd-graphify-update.sh'
|
|
@@ -22,6 +22,7 @@ const LINT_REASON = Object.freeze({
|
|
|
22
22
|
OK_NO_USER_FACING_CHANGES: 'ok_no_user_facing_changes',
|
|
23
23
|
FAIL_MISSING_FRAGMENT: 'fail_missing_fragment',
|
|
24
24
|
FAIL_INVALID_FRAGMENT: 'fail_invalid_fragment',
|
|
25
|
+
FAIL_PR_FIELD_DRIFT: 'fail_pr_field_drift',
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
const OPT_OUT_LABEL = 'no-changelog';
|
|
@@ -35,8 +36,6 @@ const USER_FACING_PREFIXES = [
|
|
|
35
36
|
'agents/',
|
|
36
37
|
'commands/',
|
|
37
38
|
'hooks/',
|
|
38
|
-
'sdk/src/',
|
|
39
|
-
'sdk/prompts/',
|
|
40
39
|
];
|
|
41
40
|
|
|
42
41
|
// Exact-match user-facing files. Any direct edit to one of these without a
|
|
@@ -53,10 +52,47 @@ function isFragment(file) {
|
|
|
53
52
|
return /^\.changeset\/[^/]+\.md$/.test(file) && !file.endsWith('/README.md');
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
/**
|
|
56
|
+
* DEFECT.CHANGESET-PR-FIELD-DRIFT (#3316, #3325): a fragment's `pr:` field is
|
|
57
|
+
* a guess (issue number, stacked-PR leftover) that never got backfilled to
|
|
58
|
+
* the real PR number after `gh api POST /pulls` returned it.
|
|
59
|
+
*
|
|
60
|
+
* Pure: given `prEntries` (`[{ file, pr }]`, one entry per successfully
|
|
61
|
+
* parsed changed fragment — `pr` is always a positive integer, since
|
|
62
|
+
* `parseFragment` already rejects `pr: 0` / non-numeric values as
|
|
63
|
+
* `invalid_pr` before a fragment ever reaches this function) and
|
|
64
|
+
* `realPrNumber` (the PR this run belongs to, or `null` when unknown —
|
|
65
|
+
* push/non-PR runs), returns every fragment whose `pr` disagrees with
|
|
66
|
+
* `realPrNumber`. `realPrNumber == null` always yields `[]`: with no PR
|
|
67
|
+
* event payload to compare against, there is nothing to drift-check.
|
|
68
|
+
*
|
|
69
|
+
* `pr === 0` is always silent regardless of `realPrNumber` — CONTRIBUTING.md
|
|
70
|
+
* documents `pr: 0` as the deliberate placeholder used "during initial
|
|
71
|
+
* commit" before `gh api POST /pulls` returns the real number, so it is not
|
|
72
|
+
* yet a drifted value, just an unbackfilled one. (In practice `parseFragment`
|
|
73
|
+
* already rejects `pr: 0` as `invalid_pr` before a fragment reaches this
|
|
74
|
+
* function via `main()`'s wiring — this guard documents and locks in the
|
|
75
|
+
* pure function's own contract independent of that upstream check.)
|
|
76
|
+
*/
|
|
77
|
+
function findPrFieldDrift(prEntries, realPrNumber) {
|
|
78
|
+
if (realPrNumber == null) return [];
|
|
79
|
+
const drift = [];
|
|
80
|
+
for (const { file, pr } of prEntries) {
|
|
81
|
+
if (pr === 0) continue;
|
|
82
|
+
if (pr !== realPrNumber) {
|
|
83
|
+
drift.push({ file, found: pr, expected: realPrNumber });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return drift;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function evaluateLint({ changedFiles, labels, fragmentFailures = [], prFieldDrift = [] }) {
|
|
57
90
|
if (fragmentFailures.length > 0) {
|
|
58
91
|
return { ok: false, reason: LINT_REASON.FAIL_INVALID_FRAGMENT, failures: fragmentFailures };
|
|
59
92
|
}
|
|
93
|
+
if (prFieldDrift.length > 0) {
|
|
94
|
+
return { ok: false, reason: LINT_REASON.FAIL_PR_FIELD_DRIFT, drift: prFieldDrift };
|
|
95
|
+
}
|
|
60
96
|
if (changedFiles.some(isFragment)) {
|
|
61
97
|
return { ok: true, reason: LINT_REASON.OK_FRAGMENT_PRESENT };
|
|
62
98
|
}
|
|
@@ -78,10 +114,18 @@ function main() {
|
|
|
78
114
|
// GitHub Actions event payload path
|
|
79
115
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
80
116
|
let labels = [];
|
|
117
|
+
// DEFECT.CHANGESET-PR-FIELD-DRIFT: the real PR number this run belongs to,
|
|
118
|
+
// read from the same event payload. `null` on a push / non-PR run (no
|
|
119
|
+
// `pull_request` in the payload, or no payload at all) — the drift check
|
|
120
|
+
// below is a no-op in that case, it never fails a push run.
|
|
121
|
+
let realPrNumber = null;
|
|
81
122
|
if (eventPath && fs.existsSync(eventPath)) {
|
|
82
123
|
try {
|
|
83
124
|
const event = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
|
|
84
125
|
labels = (event.pull_request?.labels || []).map((l) => l.name);
|
|
126
|
+
if (event.pull_request && Number.isInteger(event.pull_request.number)) {
|
|
127
|
+
realPrNumber = event.pull_request.number;
|
|
128
|
+
}
|
|
85
129
|
} catch { /* fall through */ }
|
|
86
130
|
}
|
|
87
131
|
// #2988: local fallback must match the repo's integration branch (`next`),
|
|
@@ -107,6 +151,7 @@ function main() {
|
|
|
107
151
|
|
|
108
152
|
// Validate the content of every changed fragment file.
|
|
109
153
|
const fragmentFailures = [];
|
|
154
|
+
const prEntries = [];
|
|
110
155
|
for (const file of changedFiles) {
|
|
111
156
|
if (!isFragment(file)) continue;
|
|
112
157
|
// A fragment path in the diff that no longer exists on disk was deleted in
|
|
@@ -125,10 +170,13 @@ function main() {
|
|
|
125
170
|
const result = parseFragment(src);
|
|
126
171
|
if (!result.ok) {
|
|
127
172
|
fragmentFailures.push({ file, reason: result.reason, detail: result.detail });
|
|
173
|
+
continue;
|
|
128
174
|
}
|
|
175
|
+
prEntries.push({ file, pr: result.fragment.pr });
|
|
129
176
|
}
|
|
130
177
|
|
|
131
|
-
const
|
|
178
|
+
const prFieldDrift = findPrFieldDrift(prEntries, realPrNumber);
|
|
179
|
+
const verdict = evaluateLint({ changedFiles, labels, fragmentFailures, prFieldDrift });
|
|
132
180
|
if (process.argv.includes('--json')) {
|
|
133
181
|
process.stdout.write(JSON.stringify({ ...verdict, changedFiles, labels }, null, 2) + '\n');
|
|
134
182
|
} else if (verdict.ok) {
|
|
@@ -141,6 +189,13 @@ function main() {
|
|
|
141
189
|
process.stderr.write(` ${f.file}: ${f.reason}${detail}\n`);
|
|
142
190
|
}
|
|
143
191
|
process.stderr.write(`Fix the fragment(s) above before merging.\n`);
|
|
192
|
+
} else if (verdict.reason === LINT_REASON.FAIL_PR_FIELD_DRIFT) {
|
|
193
|
+
process.stderr.write(`\nERROR changeset-lint: ${verdict.reason}\n`);
|
|
194
|
+
process.stderr.write(`The following .changeset fragment(s) have a stale \`pr:\` field (DEFECT.CHANGESET-PR-FIELD-DRIFT):\n`);
|
|
195
|
+
for (const d of verdict.drift) {
|
|
196
|
+
process.stderr.write(` ${d.file}: pr: ${d.found}, expected pr: ${d.expected}\n`);
|
|
197
|
+
}
|
|
198
|
+
process.stderr.write(`Backfill \`pr:\` with this PR's real number (see .changeset/README.md), then push again.\n`);
|
|
144
199
|
} else {
|
|
145
200
|
process.stderr.write(`\nERROR changeset-lint: ${verdict.reason}\n`);
|
|
146
201
|
process.stderr.write(`PR touches user-facing files but does not include a .changeset/*.md fragment.\n`);
|
|
@@ -152,4 +207,4 @@ function main() {
|
|
|
152
207
|
|
|
153
208
|
if (require.main === module) runMain(main);
|
|
154
209
|
|
|
155
|
-
module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment, DEFAULT_BASE };
|
|
210
|
+
module.exports = { evaluateLint, LINT_REASON, OPT_OUT_LABEL, isUserFacing, isFragment, DEFAULT_BASE, findPrFieldDrift };
|
|
@@ -4,9 +4,10 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
6
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
7
|
+
const { FAMILIES, ALIAS_TABLE } = require('./lib/alias-drift-families.cjs');
|
|
7
8
|
|
|
8
9
|
const ROOT = path.resolve(__dirname, '..');
|
|
9
|
-
const aliasesPath = path.join(ROOT, 'gsd-core', 'bin', 'lib',
|
|
10
|
+
const aliasesPath = path.join(ROOT, 'gsd-core', 'bin', 'lib', `${ALIAS_TABLE}.cjs`);
|
|
10
11
|
|
|
11
12
|
function fail(message) {
|
|
12
13
|
process.stderr.write(`${message}\n`);
|
|
@@ -36,48 +37,11 @@ function main() {
|
|
|
36
37
|
|
|
37
38
|
const aliases = require(aliasesPath);
|
|
38
39
|
|
|
39
|
-
const families =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
commandAliases: 'VERIFY_COMMAND_ALIASES',
|
|
47
|
-
subcommands: 'VERIFY_SUBCOMMANDS',
|
|
48
|
-
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'verify-command-router.cjs'),
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
commandAliases: 'INIT_COMMAND_ALIASES',
|
|
52
|
-
subcommands: 'INIT_SUBCOMMANDS',
|
|
53
|
-
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'init-command-router.cjs'),
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
commandAliases: 'PHASE_COMMAND_ALIASES',
|
|
57
|
-
subcommands: 'PHASE_SUBCOMMANDS',
|
|
58
|
-
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phase-command-router.cjs'),
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
commandAliases: 'PHASES_COMMAND_ALIASES',
|
|
62
|
-
subcommands: 'PHASES_SUBCOMMANDS',
|
|
63
|
-
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'phases-command-router.cjs'),
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
commandAliases: 'VALIDATE_COMMAND_ALIASES',
|
|
67
|
-
subcommands: 'VALIDATE_SUBCOMMANDS',
|
|
68
|
-
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'validate-command-router.cjs'),
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
commandAliases: 'ROADMAP_COMMAND_ALIASES',
|
|
72
|
-
subcommands: 'ROADMAP_SUBCOMMANDS',
|
|
73
|
-
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'roadmap-command-router.cjs'),
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
commandAliases: 'EVAL_COMMAND_ALIASES',
|
|
77
|
-
subcommands: 'EVAL_SUBCOMMANDS',
|
|
78
|
-
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'eval-command-router.cjs'),
|
|
79
|
-
},
|
|
80
|
-
];
|
|
40
|
+
const families = FAMILIES.map((family) => ({
|
|
41
|
+
commandAliases: family.commandAliases,
|
|
42
|
+
subcommands: family.subcommands,
|
|
43
|
+
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', `${family.router}.cjs`),
|
|
44
|
+
}));
|
|
81
45
|
|
|
82
46
|
for (const family of families) {
|
|
83
47
|
const commandAliases = aliases[family.commandAliases];
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-contract-drift.cjs
|
|
4
|
+
*
|
|
5
|
+
* Enforces that gsd-core/references/agent-contracts.md's `## Agent Registry`
|
|
6
|
+
* table stays in sync with reality:
|
|
7
|
+
*
|
|
8
|
+
* 1. The table itself must parse cleanly (no malformed rows).
|
|
9
|
+
* 2. Every agents/*.md file must have its fenced code blocks properly
|
|
10
|
+
* closed (an unclosed fence makes in-fence marker detection unreliable).
|
|
11
|
+
* 3. Every marker an agent actually emits in-fence, and every marker the
|
|
12
|
+
* registry declares for it, must agree (contractViolations' declared/
|
|
13
|
+
* emitted checks) -- unless the row opts out via `kind:
|
|
14
|
+
* artifact+query`/`structured-return`, in which case any emitted
|
|
15
|
+
* marker is itself a violation (vestigial_marker).
|
|
16
|
+
* 4. Every `sentinel-match` row's declared markers must have at least one
|
|
17
|
+
* exact-case consumer somewhere under gsd-core/workflows/, commands/,
|
|
18
|
+
* or agents/ (excluding the producing agent's own file).
|
|
19
|
+
* 5. Registry roster coverage: every agents/*.md file has exactly one
|
|
20
|
+
* row, every row names an agent file that exists
|
|
21
|
+
* (agent_without_contract / duplicate_registry_row / unknown_producer),
|
|
22
|
+
* and every file-shaped `Consumed by` entry resolves (unknown_consumer).
|
|
23
|
+
* 6. Read-tag arm: no `<files_to_read>` survives anywhere in the consumer
|
|
24
|
+
* corpus (legacy_read_tag), and whenever a declared consumer emits
|
|
25
|
+
* `<required_reading>` the producing agent's file must reference the
|
|
26
|
+
* gate (read_tag_gate_missing).
|
|
27
|
+
* 7. Reverse direction: a workflow/command matching a quoted `## TOKEN`
|
|
28
|
+
* no agent declares or emits is dispatch-on-phantom
|
|
29
|
+
* (unmatched_consumer_token) — F9's shape from the consumer side.
|
|
30
|
+
*
|
|
31
|
+
* Exit 0 = clean. Exit 1 = violations (with diagnostics on stderr).
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
'use strict';
|
|
35
|
+
|
|
36
|
+
const fs = require('fs');
|
|
37
|
+
const path = require('path');
|
|
38
|
+
|
|
39
|
+
function resolveRoot(argv) {
|
|
40
|
+
const idx = argv.indexOf('--root');
|
|
41
|
+
if (idx === -1) return path.join(__dirname, '..');
|
|
42
|
+
const value = argv[idx + 1];
|
|
43
|
+
if (!value) {
|
|
44
|
+
throw new Error('check-contract-drift: --root requires a directory argument');
|
|
45
|
+
}
|
|
46
|
+
return path.resolve(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ROOT = resolveRoot(process.argv.slice(2));
|
|
50
|
+
const CONTRACTS_FILE = path.join(ROOT, 'gsd-core', 'references', 'agent-contracts.md');
|
|
51
|
+
const AGENTS_DIR = path.join(ROOT, 'agents');
|
|
52
|
+
const WORKFLOWS_DIR = path.join(ROOT, 'gsd-core', 'workflows');
|
|
53
|
+
const COMMANDS_DIR = path.join(ROOT, 'commands');
|
|
54
|
+
|
|
55
|
+
const {
|
|
56
|
+
extractMarkers,
|
|
57
|
+
parseAgentContracts,
|
|
58
|
+
contractViolations,
|
|
59
|
+
readTagViolations,
|
|
60
|
+
parseConsumedByCell,
|
|
61
|
+
unmatchedConsumerTokens,
|
|
62
|
+
sanitizeEcho,
|
|
63
|
+
REMEDIES,
|
|
64
|
+
} = require('./command-contract-helpers.cjs');
|
|
65
|
+
|
|
66
|
+
const { runMain } = require('./lib/cli-exit.cjs');
|
|
67
|
+
|
|
68
|
+
// ─── helpers ────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
function walkMarkdownFiles(dir, acc) {
|
|
71
|
+
let entries;
|
|
72
|
+
try {
|
|
73
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
74
|
+
} catch {
|
|
75
|
+
return acc;
|
|
76
|
+
}
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const full = path.join(dir, entry.name);
|
|
79
|
+
if (entry.isDirectory()) {
|
|
80
|
+
walkMarkdownFiles(full, acc);
|
|
81
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
82
|
+
acc.push(full);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return acc;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toRepoRelative(absPath) {
|
|
89
|
+
return path.relative(ROOT, absPath).split(path.sep).join('/');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* referenceIncludes(content)
|
|
94
|
+
*
|
|
95
|
+
* Plain scan for `@~/.claude/gsd-core/references/*.md` tokens anywhere in an
|
|
96
|
+
* agent file's content -- inside an `<execution_context>` block (already
|
|
97
|
+
* covered structurally by `executionContextRefs` in command-contract-helpers,
|
|
98
|
+
* but a raw regex over the whole string picks those up too) and, just as
|
|
99
|
+
* importantly, OUTSIDE one: agents frequently point at a reference doc from
|
|
100
|
+
* plain prose (e.g. "See @~/.claude/gsd-core/references/planner-guidance.md
|
|
101
|
+
* for ...") rather than from the eager `<execution_context>` include list.
|
|
102
|
+
* An agent's completion-marker contract can be authored in such a reference
|
|
103
|
+
* file rather than the agent file itself -- gsd-planner declares
|
|
104
|
+
* `PLANNING COMPLETE` in its registry row, but the example heading itself
|
|
105
|
+
* lives in `gsd-core/references/planner-guidance.md`, which the agent only
|
|
106
|
+
* `@`-includes -- so the producer scan below must follow these includes to
|
|
107
|
+
* see markers an agent's contract legitimately delegates to a reference doc.
|
|
108
|
+
* Returns ROOT-relative paths (`gsd-core/references/foo.md`), de-duplicated.
|
|
109
|
+
*/
|
|
110
|
+
function referenceIncludes(content) {
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
const re = /@~\/\.claude\/gsd-core\/references\/[A-Za-z0-9._-]+\.md/g;
|
|
113
|
+
let m;
|
|
114
|
+
while ((m = re.exec(content)) !== null) {
|
|
115
|
+
const relPath = 'gsd-core/references/' + m[0].slice('@~/.claude/gsd-core/references/'.length);
|
|
116
|
+
seen.add(relPath);
|
|
117
|
+
}
|
|
118
|
+
return [...seen];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function remedyFor(kind) {
|
|
122
|
+
return REMEDIES[kind] || 'review the registry row and agent file for drift';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── run ─────────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
function main() {
|
|
128
|
+
if (!fs.existsSync(CONTRACTS_FILE)) {
|
|
129
|
+
process.stderr.write(`\nERROR check-contract-drift: contracts file not found at ${toRepoRelative(CONTRACTS_FILE)}\n\n`);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const contractsMd = fs.readFileSync(CONTRACTS_FILE, 'utf-8');
|
|
134
|
+
const { rows: registry, errors: parseErrors } = parseAgentContracts(contractsMd);
|
|
135
|
+
|
|
136
|
+
// knownMarkers: every marker string declared anywhere in the registry --
|
|
137
|
+
// extractMarkers only ever resolves a heading against this vocabulary, it
|
|
138
|
+
// never falls back to guessing a shape. Includes `(unconsumed: …)` entries
|
|
139
|
+
// so their declared↔emitted agreement is checked too.
|
|
140
|
+
const knownMarkers = new Set();
|
|
141
|
+
for (const row of registry) {
|
|
142
|
+
for (const m of row.completion_markers || []) knownMarkers.add(m);
|
|
143
|
+
for (const m of row.unconsumed_markers || []) knownMarkers.add(m);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// producerMarkers: agent -> [in-fence marker strings that matched knownMarkers]
|
|
147
|
+
// candidateMarkers: agent -> [in-fence marker-shaped headings NOT in knownMarkers,
|
|
148
|
+
// deduped per marker — "emitted but undeclared" is a fact about the
|
|
149
|
+
// marker, not about each line or file it appears in]
|
|
150
|
+
// agentTexts: agent -> file content CONCATENATED with every
|
|
151
|
+
// references/*.md file the agent @-includes, for the read-tag arm's gate
|
|
152
|
+
// check. The fold is load-bearing: planner/executor/phase-researcher
|
|
153
|
+
// deliver the MUST-Read gate via the shared mandatory-initial-read.md
|
|
154
|
+
// include, so the agent file alone would report a gate that actually
|
|
155
|
+
// arrives (the same producer-scope gap referenceIncludes() fixes for
|
|
156
|
+
// markers, one layer up).
|
|
157
|
+
const producerMarkers = new Map();
|
|
158
|
+
const candidateMarkers = new Map();
|
|
159
|
+
const agentTexts = new Map();
|
|
160
|
+
const unclosedFenceViolations = [];
|
|
161
|
+
|
|
162
|
+
const agentFiles = fs.existsSync(AGENTS_DIR)
|
|
163
|
+
? fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md'))
|
|
164
|
+
: [];
|
|
165
|
+
|
|
166
|
+
for (const file of agentFiles) {
|
|
167
|
+
const agent = file.replace(/\.md$/, '');
|
|
168
|
+
const abs = path.join(AGENTS_DIR, file);
|
|
169
|
+
const content = fs.readFileSync(abs, 'utf-8');
|
|
170
|
+
// Single pass over the agent's @-included references: each file is read
|
|
171
|
+
// once and feeds BOTH the read-tag fold (agentTexts) and marker
|
|
172
|
+
// extraction (producer/candidate attribution).
|
|
173
|
+
const includeTexts = [];
|
|
174
|
+
for (const refRelPath of referenceIncludes(content)) {
|
|
175
|
+
try {
|
|
176
|
+
includeTexts.push(fs.readFileSync(path.join(ROOT, refRelPath), 'utf-8'));
|
|
177
|
+
} catch {
|
|
178
|
+
// include miss — lint-command-contract rule 4 owns @-ref existence
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
agentTexts.set(agent, [content, ...includeTexts].join('\n'));
|
|
182
|
+
const { markers, candidates, unclosedFence } = extractMarkers(agentTexts.get(agent), knownMarkers);
|
|
183
|
+
const inFenceMarkers = markers.filter(m => m.inFence).map(m => m.marker);
|
|
184
|
+
const inFenceCandidates = candidates.filter(m => m.inFence).map(m => m.marker);
|
|
185
|
+
|
|
186
|
+
producerMarkers.set(agent, inFenceMarkers);
|
|
187
|
+
candidateMarkers.set(agent, [...new Set(inFenceCandidates)]);
|
|
188
|
+
|
|
189
|
+
if (unclosedFence) {
|
|
190
|
+
unclosedFenceViolations.push({
|
|
191
|
+
kind: 'unclosed_fence',
|
|
192
|
+
agent,
|
|
193
|
+
marker: null,
|
|
194
|
+
detail: `${toRepoRelative(abs)} has an unterminated code fence`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// consumerTexts: every *.md under gsd-core/workflows/, commands/, agents/
|
|
200
|
+
// — plus every file-shaped `Consumed by` entry that resolves on disk, so
|
|
201
|
+
// a row citing a reference doc or an ADR (e.g. intel-updater's
|
|
202
|
+
// docs/adr/22-plan-drift-guard.md) is validated against the real file and
|
|
203
|
+
// its text participates in consumer matching, not just workflows.
|
|
204
|
+
const consumerTexts = new Map();
|
|
205
|
+
const consumerDirs = [WORKFLOWS_DIR, COMMANDS_DIR, AGENTS_DIR];
|
|
206
|
+
for (const dir of consumerDirs) {
|
|
207
|
+
for (const abs of walkMarkdownFiles(dir, [])) {
|
|
208
|
+
consumerTexts.set(toRepoRelative(abs), fs.readFileSync(abs, 'utf-8'));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const row of registry) {
|
|
212
|
+
for (const rel of parseConsumedByCell(row.consumed_by)) {
|
|
213
|
+
if (consumerTexts.has(rel)) continue;
|
|
214
|
+
const abs = path.join(ROOT, rel);
|
|
215
|
+
try {
|
|
216
|
+
consumerTexts.set(rel, fs.readFileSync(abs, 'utf-8'));
|
|
217
|
+
} catch {
|
|
218
|
+
// absent — contractViolations reports it as unknown_consumer
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const contractViolationsList = contractViolations({ registry, producerMarkers, candidateMarkers, consumerTexts });
|
|
224
|
+
const readTagViolationsList = readTagViolations({ registry, agentTexts, consumerTexts });
|
|
225
|
+
const reverseViolationsList = unmatchedConsumerTokens({ consumerTexts, vocabulary: knownMarkers });
|
|
226
|
+
|
|
227
|
+
const parseViolations = parseErrors.map(e => ({
|
|
228
|
+
kind: 'parse_error',
|
|
229
|
+
agent: null,
|
|
230
|
+
marker: null,
|
|
231
|
+
detail: `agent-contracts.md:${e.line} — ${e.reason}`,
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
const allViolations = [
|
|
235
|
+
...parseViolations,
|
|
236
|
+
...unclosedFenceViolations,
|
|
237
|
+
...contractViolationsList,
|
|
238
|
+
...readTagViolationsList,
|
|
239
|
+
...reverseViolationsList,
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
const agentCount = registry.length;
|
|
243
|
+
const markerCount = registry.reduce((n, r) => n + (r.completion_markers || []).length, 0);
|
|
244
|
+
|
|
245
|
+
// --json: the typed surface tests consume (CONTRIBUTING "Raw Text
|
|
246
|
+
// Matching" rule — the human formatter below is for operators only).
|
|
247
|
+
if (process.argv.includes('--json')) {
|
|
248
|
+
console.log(
|
|
249
|
+
JSON.stringify({
|
|
250
|
+
check: 'check-contract-drift',
|
|
251
|
+
status: allViolations.length === 0 ? 'ok' : 'violations',
|
|
252
|
+
agents: agentCount,
|
|
253
|
+
markers: markerCount,
|
|
254
|
+
violations: allViolations.map((v) => ({
|
|
255
|
+
kind: v.kind,
|
|
256
|
+
agent: v.agent ?? null,
|
|
257
|
+
marker: v.marker ?? null,
|
|
258
|
+
detail: sanitizeEcho(v.detail),
|
|
259
|
+
})),
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
return allViolations.length === 0 ? 0 : 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (allViolations.length === 0) {
|
|
266
|
+
console.log(`ok check-contract-drift: ${agentCount} agents, ${markerCount} markers, 0 violations`);
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// group by violation kind
|
|
271
|
+
const byKind = new Map();
|
|
272
|
+
for (const v of allViolations) {
|
|
273
|
+
if (!byKind.has(v.kind)) byKind.set(v.kind, []);
|
|
274
|
+
byKind.get(v.kind).push(v);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
process.stderr.write(
|
|
278
|
+
`\nERROR check-contract-drift: ${allViolations.length} violation(s) across ${byKind.size} kind(s)\n\n`,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
for (const [kind, violations] of byKind) {
|
|
282
|
+
process.stderr.write(` ${kind} (${violations.length}):\n`);
|
|
283
|
+
for (const v of violations) {
|
|
284
|
+
const agentLabel = v.agent ? sanitizeEcho(v.agent) : '(registry)';
|
|
285
|
+
const markerLabel = v.marker ? ` marker "${sanitizeEcho(v.marker)}"` : '';
|
|
286
|
+
process.stderr.write(` - ${agentLabel}${markerLabel}: ${sanitizeEcho(v.detail)}\n`);
|
|
287
|
+
process.stderr.write(` remedy: ${remedyFor(kind)}\n`);
|
|
288
|
+
}
|
|
289
|
+
process.stderr.write('\n');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
process.stderr.write('See gsd-core/references/agent-contracts.md for the registry contract spec.\n\n');
|
|
293
|
+
|
|
294
|
+
return 1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
runMain(main);
|