@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
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* lint-allow-test-rule-refs.cjs — enforce that NEW `allow-test-rule:` exemption
|
|
6
|
-
* comments carry a tracking-issue reference.
|
|
7
|
-
*
|
|
8
|
-
* ## Why
|
|
9
|
-
*
|
|
10
|
-
* `allow-test-rule:` is an inline comment that disables the `no-source-grep`
|
|
11
|
-
* ESLint rule for a whole test file. Today many such comments exist with no
|
|
12
|
-
* issue reference, making it impossible to audit or revisit them. Per ADR-456
|
|
13
|
-
* (docs/adr/456-test-rigor-architecture.md) every NEW exemption must carry a
|
|
14
|
-
* `#NNN` issue reference or an https:// URL so the decision is traceable.
|
|
15
|
-
*
|
|
16
|
-
* ## What "compliant" means
|
|
17
|
-
*
|
|
18
|
-
* A compliant `allow-test-rule:` comment is one whose reason text (everything
|
|
19
|
-
* after the colon) contains either:
|
|
20
|
-
* - a `#\d+` token (e.g. `// allow-test-rule: see #1234`)
|
|
21
|
-
* - an https?:// URL
|
|
22
|
-
*
|
|
23
|
-
* Any other comment is an OFFENDER.
|
|
24
|
-
*
|
|
25
|
-
* ## Grandfathering
|
|
26
|
-
*
|
|
27
|
-
* All pre-existing untracked exemptions are recorded in
|
|
28
|
-
* scripts/lint-allow-test-rule-refs.allowlist.json (seeded at gate introduction
|
|
29
|
-
* time). The identity ratchet (scripts/lib/allowlist-ratchet.cjs) means:
|
|
30
|
-
* - A NEW non-compliant comment not in the allowlist → gate fails.
|
|
31
|
-
* - A previously-offending comment that is now compliant → allowlist entry is
|
|
32
|
-
* STALE and must be pruned (ratchet-down; the baseline only ever shrinks).
|
|
33
|
-
*
|
|
34
|
-
* ## Offender identifiers
|
|
35
|
-
*
|
|
36
|
-
* Identifiers are stable cross-rename-safe strings of the form:
|
|
37
|
-
* `<repo-relative-path> :: <trimmed-reason>`
|
|
38
|
-
*
|
|
39
|
-
* e.g. `tests/foo.test.cjs :: source-text-is-the-product`
|
|
40
|
-
*
|
|
41
|
-
* If a file has multiple non-compliant comments with the SAME reason text, only
|
|
42
|
-
* one identifier is recorded (deduped via Set).
|
|
43
|
-
*
|
|
44
|
-
* See docs/adr/456-test-rigor-architecture.md for the full policy.
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
const fs = require('fs');
|
|
48
|
-
const path = require('path');
|
|
49
|
-
const { assertWithinAllowlist } = require('./lib/allowlist-ratchet.cjs');
|
|
50
|
-
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
51
|
-
|
|
52
|
-
const ROOT = path.join(__dirname, '..');
|
|
53
|
-
const TESTS_DIR = process.env.GSD_LINT_ALLOW_TEST_RULE_TESTS_DIR || path.join(ROOT, 'tests');
|
|
54
|
-
const ALLOWLIST_PATH =
|
|
55
|
-
process.env.GSD_LINT_ALLOW_TEST_RULE_ALLOWLIST ||
|
|
56
|
-
path.join(__dirname, 'lint-allow-test-rule-refs.allowlist.json');
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Extracts the reason text after `allow-test-rule:` from a single line of source
|
|
60
|
-
* text in any comment form that the no-source-grep ESLint rule honours.
|
|
61
|
-
*
|
|
62
|
-
* The ESLint rule tests `c.value` (AST comment node value, delimiters stripped)
|
|
63
|
-
* with /allow-test-rule:\s*\S/, which fires on BOTH:
|
|
64
|
-
* // allow-test-rule: <reason> (line comment)
|
|
65
|
-
* /* allow-test-rule: <reason> * / (block comment, single-line)
|
|
66
|
-
*
|
|
67
|
-
* By scanning line-by-line and extracting everything after `allow-test-rule:` on
|
|
68
|
-
* each line, we cover both forms without a cross-line regex (which was previously
|
|
69
|
-
* matching arbitrary `/* ... * /` pairs spanning hundreds of lines, causing false
|
|
70
|
-
* positives).
|
|
71
|
-
*
|
|
72
|
-
* The trailing `*\/` and whitespace are stripped so block-comment closers don't
|
|
73
|
-
* bleed into the extracted reason.
|
|
74
|
-
*/
|
|
75
|
-
const ALLOW_TEST_RULE_LINE_RE = /allow-test-rule:\s*(.+)/;
|
|
76
|
-
/** Matches a compliant issue reference or URL */
|
|
77
|
-
const ISSUE_REF_RE = /#\d+|https?:\/\//;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Recursively collect offender identifiers from all *.test.cjs files under dir.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} dir absolute path to scan
|
|
83
|
-
* @returns {string[]} sorted, deduped list of `<relpath> :: <reason>` strings
|
|
84
|
-
*/
|
|
85
|
-
function collectOffenders(dir) {
|
|
86
|
-
const offenders = new Set();
|
|
87
|
-
|
|
88
|
-
function scan(current) {
|
|
89
|
-
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
90
|
-
const full = path.join(current, entry.name);
|
|
91
|
-
if (entry.isDirectory()) {
|
|
92
|
-
scan(full);
|
|
93
|
-
} else if (entry.isFile() && entry.name.endsWith('.test.cjs')) {
|
|
94
|
-
const relpath = path.relative(ROOT, full).split(path.sep).join('/');
|
|
95
|
-
let content;
|
|
96
|
-
try {
|
|
97
|
-
content = fs.readFileSync(full, 'utf8');
|
|
98
|
-
} catch {
|
|
99
|
-
// skip unreadable files (e.g. binary)
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
// Scan line-by-line. By testing each line for `allow-test-rule:` we
|
|
103
|
-
// cover BOTH comment forms without a cross-line regex:
|
|
104
|
-
// // allow-test-rule: <reason> ← line comment
|
|
105
|
-
// /* allow-test-rule: <reason> */ ← single-line block comment
|
|
106
|
-
//
|
|
107
|
-
// For each matching line we extract the reason (everything after the
|
|
108
|
-
// colon), then strip any trailing block-comment closer `*/` and
|
|
109
|
-
// whitespace so the identifier stays clean.
|
|
110
|
-
for (const line of content.split('\n')) {
|
|
111
|
-
const m = ALLOW_TEST_RULE_LINE_RE.exec(line);
|
|
112
|
-
if (!m) continue;
|
|
113
|
-
// Strip trailing block-comment closer and whitespace if present
|
|
114
|
-
const reason = m[1].replace(/\s*\*\/\s*$/, '').trim();
|
|
115
|
-
if (!reason) continue;
|
|
116
|
-
if (ISSUE_REF_RE.test(reason)) continue; // compliant — skip
|
|
117
|
-
offenders.add(`${relpath} :: ${reason}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
scan(dir);
|
|
124
|
-
return [...offenders].sort();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function main() {
|
|
128
|
-
const args = process.argv.slice(2);
|
|
129
|
-
const unknown = args.filter((a) => a !== '--help');
|
|
130
|
-
if (unknown.length > 0) {
|
|
131
|
-
throw new ExitError(2, `lint-allow-test-rule-refs: unknown argument(s): ${unknown.join(', ')}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const current = collectOffenders(TESTS_DIR);
|
|
135
|
-
const known = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
|
|
136
|
-
|
|
137
|
-
const failures = [];
|
|
138
|
-
const { novel } = assertWithinAllowlist({
|
|
139
|
-
label: 'allow-test-rule-refs',
|
|
140
|
-
current,
|
|
141
|
-
known,
|
|
142
|
-
fail: (msg) => failures.push(msg),
|
|
143
|
-
pruneHint: 'edit scripts/lint-allow-test-rule-refs.allowlist.json',
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (failures.length > 0) {
|
|
147
|
-
for (const msg of failures) process.stderr.write(`${msg}\n`);
|
|
148
|
-
if (novel.length > 0) {
|
|
149
|
-
process.stderr.write(
|
|
150
|
-
'\nNew allow-test-rule exemption without an issue ref — add `see #NNN` per ADR-456' +
|
|
151
|
-
' (docs/adr/456-test-rigor-architecture.md).\n'
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
throw new ExitError(1);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
console.log(
|
|
158
|
-
`ok lint-allow-test-rule-refs: ${current.length} grandfathered exemption(s) tracked, no novel untracked offenders`
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
runMain(main);
|
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* lint-emitted-drift-ack — refuse to merge a broken emitted-drift acknowledgment (#2789).
|
|
6
|
-
*
|
|
7
|
-
* The ack document is read from TWO sides: the working tree (`readAckFile`) and the BASE
|
|
8
|
-
* REF (`readAckFileAtRef`), because an entry already present at the base is spent and may
|
|
9
|
-
* no longer clear a delta. The base-side read fails LOUDLY on a document it cannot parse
|
|
10
|
-
* — it has to, since silently inheriting nothing would leave every entry able to consume
|
|
11
|
-
* a delta, which is the pre-#2789 gate.
|
|
12
|
-
*
|
|
13
|
-
* That makes a corrupt document ON THE BASE BRANCH unusually expensive: it reds every PR
|
|
14
|
-
* that carries an ack until someone repairs it. The real-tree test avoids an outright
|
|
15
|
-
* deadlock (a tree with no ack never consults the base, so the repair PR still lands),
|
|
16
|
-
* but the cheaper answer is to never let a broken document reach the base at all. This
|
|
17
|
-
* runs in `lint:ci`, so a PR carrying one cannot go green and cannot merge.
|
|
18
|
-
*
|
|
19
|
-
* This validator is DELIBERATELY STANDALONE. `scripts/` ships in the npm package and
|
|
20
|
-
* `tests/` does not (package.json `files`), so requiring the gate's own `parseAck` from
|
|
21
|
-
* here would be a MODULE_NOT_FOUND in the published package. The duplication is bounded
|
|
22
|
-
* by a parity test — `tests/emitted-attribution.test.cjs` runs both surfaces over one
|
|
23
|
-
* corpus and fails if they ever disagree about what is schema-valid.
|
|
24
|
-
*
|
|
25
|
-
* #2914: the single shared `tests/emitted-drift-ack.json` is replaced by per-PR
|
|
26
|
-
* fragments under `tests/emitted-drift-acks/` (kept alongside the legacy file, which is
|
|
27
|
-
* still honored). This validator now checks BOTH: every physical source is run through
|
|
28
|
-
* the same schema/policy rules below, and — because two sources are never allowed to
|
|
29
|
-
* name the same path (silent last-wins would resurrect exactly the silent-drift class
|
|
30
|
-
* the ack seam exists to end) — a cross-source duplicate key is ALSO a hard failure.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
const fs = require('node:fs');
|
|
34
|
-
const path = require('node:path');
|
|
35
|
-
|
|
36
|
-
const ACK_VERSION = 1;
|
|
37
|
-
const ACK_REPO_PATH = 'tests/emitted-drift-ack.json';
|
|
38
|
-
const ACK_DIR_REPO_PATH = 'tests/emitted-drift-acks';
|
|
39
|
-
const REPO_ROOT = path.join(__dirname, '..');
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Upper bound on how many fragment files `listFragmentFiles` may return in one
|
|
43
|
-
* `readdirSync` pass. Mirrors `MAX_ACK_FRAGMENTS` in `tests/helpers/emitted-diff.cjs` —
|
|
44
|
-
* DUPLICATED rather than imported, because `scripts/` ships in the npm package and
|
|
45
|
-
* `tests/` does not (requiring across that line would be MODULE_NOT_FOUND once
|
|
46
|
-
* published; see this file's top-of-file comment). The two are held to the same value
|
|
47
|
-
* by the schema-parity test in `tests/emitted-attribution.test.cjs`.
|
|
48
|
-
*
|
|
49
|
-
* Exceeding it throws rather than truncating: a truncated listing would silently drop
|
|
50
|
-
* acknowledgments from consideration, which is exactly the class of silent failure this
|
|
51
|
-
* whole ack seam exists to prevent.
|
|
52
|
-
*/
|
|
53
|
-
const MAX_ACK_FRAGMENTS = 500;
|
|
54
|
-
|
|
55
|
-
const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Key names that can never be a legitimate emitted path or bare workflow/agent filename
|
|
59
|
-
* (`__proto__`, `constructor`, `prototype`). Duplicated (not imported) in
|
|
60
|
-
* `tests/helpers/emitted-diff.cjs`'s `parseAck` for the same reason every other constant
|
|
61
|
-
* here is duplicated rather than required — `scripts/` ships, `tests/` does not. Held to
|
|
62
|
-
* the same set by the schema-parity test in `tests/emitted-attribution.test.cjs`, which
|
|
63
|
-
* must see BOTH surfaces reject a document naming one of these, never one silently
|
|
64
|
-
* accepting what the other errors on (#2914 review).
|
|
65
|
-
*/
|
|
66
|
-
const RESERVED_ACK_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Validate an ack document's raw text.
|
|
70
|
-
*
|
|
71
|
-
* `schemaErrors` are the ones that must agree with the gate's `parseAck` — the shape
|
|
72
|
-
* contract. `policyErrors` are lint-only rules that `parseAck` deliberately does NOT
|
|
73
|
-
* enforce, because they are about what may be COMMITTED rather than what may be parsed:
|
|
74
|
-
* a present-but-entryless document parses fine and signals nothing, so it must be deleted
|
|
75
|
-
* rather than left behind.
|
|
76
|
-
*
|
|
77
|
-
* @param {string|null} raw file contents, or null when the file is absent
|
|
78
|
-
* @param {object} [opts]
|
|
79
|
-
* @param {string} [opts.source] the path used in error messages (default: the legacy
|
|
80
|
-
* file). Generalized (#2914) so the same rules apply verbatim to a fragment under
|
|
81
|
-
* `ACK_DIR_REPO_PATH` — one definition of "valid", named per the file it is checking.
|
|
82
|
-
* @returns {{ schemaErrors: string[], policyErrors: string[], ok: boolean }}
|
|
83
|
-
*/
|
|
84
|
-
function validateAckText(raw, { source = ACK_REPO_PATH } = {}) {
|
|
85
|
-
const schemaErrors = [];
|
|
86
|
-
const policyErrors = [];
|
|
87
|
-
const done = () => ({ schemaErrors, policyErrors, ok: schemaErrors.length === 0 && policyErrors.length === 0 });
|
|
88
|
-
|
|
89
|
-
if (raw === null) return done(); // absent is the healthy steady state
|
|
90
|
-
|
|
91
|
-
if (raw.trim() === '') {
|
|
92
|
-
schemaErrors.push(`${source} is present but empty`);
|
|
93
|
-
return done();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let doc;
|
|
97
|
-
try {
|
|
98
|
-
doc = JSON.parse(raw);
|
|
99
|
-
} catch (err) {
|
|
100
|
-
schemaErrors.push(`${source} is not valid JSON: ${err.message}`);
|
|
101
|
-
return done();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// A document that is literally `null` is POLICY, not schema. The gate's `parseAck` uses
|
|
105
|
-
// `null` as its "absent == no acks" sentinel, so it reads such a file as legal and
|
|
106
|
-
// harmless — and the parity test holds us to that. It is still not something to commit:
|
|
107
|
-
// it declares nothing, so the remedy is the same as an entryless document.
|
|
108
|
-
if (doc === null) {
|
|
109
|
-
policyErrors.push(
|
|
110
|
-
`${source} contains "null" and declares no acknowledgments. Delete the file — `
|
|
111
|
-
+ 'the healthy steady state is no file at all.',
|
|
112
|
-
);
|
|
113
|
-
return done();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!isPlainObject(doc)) {
|
|
117
|
-
schemaErrors.push(
|
|
118
|
-
`${source}: must be a JSON object, got ${Array.isArray(doc) ? 'array' : typeof doc}`,
|
|
119
|
-
);
|
|
120
|
-
return done();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (doc.version !== undefined && doc.version !== ACK_VERSION) {
|
|
124
|
-
schemaErrors.push(
|
|
125
|
-
`${source}: unsupported version ${JSON.stringify(doc.version)} (expected ${ACK_VERSION})`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const paths = doc.paths;
|
|
130
|
-
if (paths !== undefined && !isPlainObject(paths)) {
|
|
131
|
-
schemaErrors.push(`${source}: "paths" must be an object of <emitted path> -> { reason }`);
|
|
132
|
-
return done();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const entries = paths === undefined ? [] : Object.entries(paths);
|
|
136
|
-
for (const [rel, value] of entries) {
|
|
137
|
-
if (RESERVED_ACK_KEYS.has(rel)) {
|
|
138
|
-
// Reject loudly rather than silently filter. Previously this key was excluded
|
|
139
|
-
// only from `declaredKeys`'s duplicate-detection view, so a document naming it
|
|
140
|
-
// passed validation here while the gate's `parseAck` (fed the JSON.parse'd
|
|
141
|
-
// document, where such a key is a genuine own property) either mishandled it or
|
|
142
|
-
// disagreed silently — two surfaces reaching different verdicts on the same
|
|
143
|
-
// document (#2914 review). Recognizably the same finding as `parseAck`'s.
|
|
144
|
-
schemaErrors.push(
|
|
145
|
-
`${source}: ack key "${rel}" is reserved and can never be a valid emitted path `
|
|
146
|
-
+ 'or workflow/agent filename — remove it',
|
|
147
|
-
);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const reason = isPlainObject(value) ? value.reason : value;
|
|
151
|
-
if (typeof reason !== 'string' || reason.trim() === '') {
|
|
152
|
-
schemaErrors.push(`${source}: ack for "${rel}" has no non-empty "reason"`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Lint-only. An entryless document parses cleanly and acknowledges nothing, so it is
|
|
157
|
-
// pure confusion on the base branch — and it is exactly what a contributor leaves
|
|
158
|
-
// behind after removing the last entry by hand.
|
|
159
|
-
if (entries.length === 0) {
|
|
160
|
-
policyErrors.push(
|
|
161
|
-
`${source} is present but declares no acknowledgments. Delete the file — an `
|
|
162
|
-
+ 'empty one signals nothing, and the healthy steady state is no file at all.',
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return done();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function readIfPresent(file) {
|
|
170
|
-
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Fragment filenames under `dir`, sorted. Absent directory == zero fragments.
|
|
175
|
-
*
|
|
176
|
-
* Fails loudly, naming `dir`, the cap, and the actual count, when the directory holds
|
|
177
|
-
* more than `MAX_ACK_FRAGMENTS` entries — never silently truncates the listing.
|
|
178
|
-
*/
|
|
179
|
-
function listFragmentFiles(dir) {
|
|
180
|
-
if (!fs.existsSync(dir)) return [];
|
|
181
|
-
const names = fs.readdirSync(dir).filter((name) => name.endsWith('.json')).sort();
|
|
182
|
-
if (names.length > MAX_ACK_FRAGMENTS) {
|
|
183
|
-
throw new Error(
|
|
184
|
-
`lint-emitted-drift-ack: ${dir} contains ${names.length} ack fragments, exceeding `
|
|
185
|
-
+ `the cap of ${MAX_ACK_FRAGMENTS}. Refusing to read only some of them — a truncated `
|
|
186
|
-
+ 'read would silently drop acknowledgments. Prune spent fragments from this directory.',
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
return names;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* The path keys a document declares, for cross-source collision detection — but ONLY
|
|
194
|
-
* when the document is itself trustworthy. A document that failed its own schema check
|
|
195
|
-
* must not also seed a bogus "collision" derived from garbage; its own error already
|
|
196
|
-
* blocks the merge, and reporting a fabricated collision on top would confuse rather
|
|
197
|
-
* than clarify. `RESERVED_ACK_KEYS` are also excluded here — they can never be a
|
|
198
|
-
* legitimate duplicate, since they can never be a legitimate key at all — but this is
|
|
199
|
-
* belt-and-suspenders, not the enforcement point: `validateAckText` above now rejects any
|
|
200
|
-
* document naming one outright, so `main()` only ever calls this on a document whose
|
|
201
|
-
* schema already checked out, making the exclusion below unreachable in practice.
|
|
202
|
-
*/
|
|
203
|
-
function declaredKeys(raw) {
|
|
204
|
-
if (raw === null) return [];
|
|
205
|
-
let doc;
|
|
206
|
-
try {
|
|
207
|
-
doc = JSON.parse(raw);
|
|
208
|
-
} catch {
|
|
209
|
-
return [];
|
|
210
|
-
}
|
|
211
|
-
if (!isPlainObject(doc)) return [];
|
|
212
|
-
const paths = doc.paths;
|
|
213
|
-
if (paths === undefined) return [];
|
|
214
|
-
if (!isPlainObject(paths)) return [];
|
|
215
|
-
return Object.keys(paths).filter((k) => !RESERVED_ACK_KEYS.has(k));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* assertAbsentOnNext — the `next`-lane guard (#2914), invoked only by the
|
|
220
|
-
* `guard-no-ack-on-next` workflow job on push to `next`, never in `lint:ci`.
|
|
221
|
-
*
|
|
222
|
-
* `validateAckText` lints SHAPE, because a PR's own working tree may legitimately carry
|
|
223
|
-
* a live, well-formed ack — that is the normal case a PR-lane check must allow. This
|
|
224
|
-
* function instead rejects PRESENCE outright, valid or not: per the ack-lifecycle law
|
|
225
|
-
* (#2789, `RULESET.EMITTED_ATTRIBUTION`), an entry already at the base is spent the
|
|
226
|
-
* moment it merges, so a document surviving on `next` is inert cruft by definition, not
|
|
227
|
-
* a thing to schema-check.
|
|
228
|
-
*
|
|
229
|
-
* This MUST NOT run as a PR-lane check comparing a PR against `next` — that is the #2768
|
|
230
|
-
* shape #2789 exists to prevent (a spent-but-present base ack would red every open PR the
|
|
231
|
-
* instant one landed). It is safe only because it runs on `next` itself, asserting a fact
|
|
232
|
-
* about `next`'s own tree, never about any PR's diff against it.
|
|
233
|
-
*
|
|
234
|
-
* @param {boolean} present whether ACK_REPO_PATH exists in the tree being checked
|
|
235
|
-
* @returns {{ ok: boolean, message: string }}
|
|
236
|
-
*/
|
|
237
|
-
function assertAbsentOnNext(present) {
|
|
238
|
-
if (!present) {
|
|
239
|
-
return { ok: true, message: `ok guard-no-ack-on-next: ${ACK_REPO_PATH} is absent (the healthy steady state)` };
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
ok: false,
|
|
243
|
-
message: [
|
|
244
|
-
`guard-no-ack-on-next: ${ACK_REPO_PATH} exists on next.`,
|
|
245
|
-
'',
|
|
246
|
-
'Every entry in this file is scoped to the diff that introduced it (#2789). Once merged '
|
|
247
|
-
+ 'to next it is, by definition, already at the base -- spent and inert, regardless of '
|
|
248
|
-
+ 'whether it is otherwise well-formed.',
|
|
249
|
-
'',
|
|
250
|
-
'#2914: acks now go in per-PR fragments under tests/emitted-drift-acks/, one file per '
|
|
251
|
-
+ 'PR, never this single shared file -- a persistent fragment there is harmless (every '
|
|
252
|
-
+ 'fragment is independently named, so it cannot conflict with any other PR), which is '
|
|
253
|
-
+ 'why only THIS legacy file is guarded here, never the fragment directory.',
|
|
254
|
-
'',
|
|
255
|
-
'CONTRIBUTING.md: "When you remove the last entry from tests/emitted-drift-ack.json, '
|
|
256
|
-
+ 'delete the file too -- its presence is the alarm."',
|
|
257
|
-
'',
|
|
258
|
-
`Remedy: git rm ${ACK_REPO_PATH}`,
|
|
259
|
-
].join('\n'),
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function main() {
|
|
264
|
-
const legacyFile = path.join(REPO_ROOT, ...ACK_REPO_PATH.split('/'));
|
|
265
|
-
|
|
266
|
-
if (process.argv.includes('--guard-next')) {
|
|
267
|
-
const result = assertAbsentOnNext(fs.existsSync(legacyFile));
|
|
268
|
-
console.log(result.message);
|
|
269
|
-
if (!result.ok) process.exitCode = 1;
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const fragmentsDir = path.join(REPO_ROOT, ...ACK_DIR_REPO_PATH.split('/'));
|
|
274
|
-
const sources = [
|
|
275
|
-
{ label: ACK_REPO_PATH, raw: readIfPresent(legacyFile) },
|
|
276
|
-
...listFragmentFiles(fragmentsDir).map((name) => ({
|
|
277
|
-
label: `${ACK_DIR_REPO_PATH}/${name}`,
|
|
278
|
-
raw: readIfPresent(path.join(fragmentsDir, name)),
|
|
279
|
-
})),
|
|
280
|
-
];
|
|
281
|
-
|
|
282
|
-
const problems = [];
|
|
283
|
-
const owner = new Map(); // path key -> the source label that already claimed it
|
|
284
|
-
let anyPresent = false;
|
|
285
|
-
|
|
286
|
-
for (const { label, raw } of sources) {
|
|
287
|
-
if (raw !== null) anyPresent = true;
|
|
288
|
-
|
|
289
|
-
// `validateAckText` already prefixes every message with `source` (== `label`), so
|
|
290
|
-
// these are pushed verbatim rather than re-prefixed — a second prefix would read as
|
|
291
|
-
// "tests/emitted-drift-acks/x.json: tests/emitted-drift-acks/x.json is not valid
|
|
292
|
-
// JSON", naming the same file twice for no reason.
|
|
293
|
-
const result = validateAckText(raw, { source: label });
|
|
294
|
-
problems.push(...result.schemaErrors, ...result.policyErrors);
|
|
295
|
-
|
|
296
|
-
// Only chase collisions across documents whose OWN schema already checked out —
|
|
297
|
-
// a document we could not trust must not also seed a fabricated collision.
|
|
298
|
-
if (result.schemaErrors.length === 0) {
|
|
299
|
-
for (const key of declaredKeys(raw)) {
|
|
300
|
-
if (owner.has(key)) {
|
|
301
|
-
problems.push(
|
|
302
|
-
`duplicate ack for "${key}": declared in both ${owner.get(key)} and ${label}. `
|
|
303
|
-
+ 'Two ack sources (fragments, or a fragment and the legacy file) may never '
|
|
304
|
-
+ 'name the same path — rename or merge them.',
|
|
305
|
-
);
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
owner.set(key, label);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (problems.length) {
|
|
314
|
-
console.error(`lint-emitted-drift-ack: ${problems.length} problem(s)\n`);
|
|
315
|
-
for (const e of problems) console.error(` - ${e}`);
|
|
316
|
-
console.error(
|
|
317
|
-
'\nThis blocks the merge on purpose. The base-side reader fails loudly on a document '
|
|
318
|
-
+ 'it cannot parse, so a broken one on the base branch reds every PR that carries an '
|
|
319
|
-
+ 'acknowledgment, and a duplicate across two sources is exactly the silent-drift class '
|
|
320
|
-
+ 'the ack seam exists to end. Fix or delete the offending source(s) here, where it is cheap.',
|
|
321
|
-
);
|
|
322
|
-
process.exitCode = 1;
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
console.log(
|
|
327
|
-
anyPresent
|
|
328
|
-
? 'ok lint-emitted-drift-ack: all acknowledgment sources are well-formed'
|
|
329
|
-
: 'ok lint-emitted-drift-ack: no acknowledgment sources present (the healthy steady state)',
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (require.main === module) main();
|
|
334
|
-
|
|
335
|
-
module.exports = {
|
|
336
|
-
validateAckText,
|
|
337
|
-
assertAbsentOnNext,
|
|
338
|
-
declaredKeys,
|
|
339
|
-
listFragmentFiles,
|
|
340
|
-
ACK_VERSION,
|
|
341
|
-
ACK_REPO_PATH,
|
|
342
|
-
ACK_DIR_REPO_PATH,
|
|
343
|
-
MAX_ACK_FRAGMENTS,
|
|
344
|
-
};
|