@opengsd/gsd-core 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +608 -148
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +160 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +33 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gen-prompt-budget-parity-corpus.cjs
|
|
4
|
+
*
|
|
5
|
+
* Captures a characterization corpus of `prompt-budget.applyBudget`'s observable
|
|
6
|
+
* output, for issue #2929 (epic #1671 Phase 2 — the `context-composer` seam).
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS EXISTS
|
|
9
|
+
* ---------------
|
|
10
|
+
* Phase 2 generalizes `applyBudget`'s trim ladder into a shared composer seam.
|
|
11
|
+
* Its success condition is that `prompt-budget`'s output does not change. The only
|
|
12
|
+
* authority on "did not change" is the behavior that shipped BEFORE the refactor,
|
|
13
|
+
* so this corpus is generated against the pre-refactor implementation and committed
|
|
14
|
+
* as a frozen fixture. `tests/prompt-budget-parity.test.cjs` then replays every case
|
|
15
|
+
* and asserts byte-identical `prompt` plus field-identical `metadata`.
|
|
16
|
+
*
|
|
17
|
+
* FIXTURE PROVENANCE (CONTRIBUTING.md "Fixture provenance (#2371)")
|
|
18
|
+
* -----------------------------------------------------------------
|
|
19
|
+
* That rule forbids fixtures derived from the gate's own writer, grammar, or the
|
|
20
|
+
* author's mental model, because such a fixture can only confirm what the author
|
|
21
|
+
* already believed. This corpus is expressly NOT hand-authored: every `expected`
|
|
22
|
+
* value is computed by executing the shipped implementation, which predates the
|
|
23
|
+
* composer and knows nothing about it. What IS author-chosen is the set of INPUTS —
|
|
24
|
+
* so the inputs are derived from the design's parity-critical behavior table rather
|
|
25
|
+
* than from intuition, and `tests/prompt-budget-parity.test.cjs` additionally proves
|
|
26
|
+
* the corpus is non-vacuous by mutating the ladder and requiring the corpus to fail.
|
|
27
|
+
* Disclosed here rather than assumed.
|
|
28
|
+
*
|
|
29
|
+
* USAGE
|
|
30
|
+
* node scripts/gen-prompt-budget-parity-corpus.cjs --write # regenerate
|
|
31
|
+
* node scripts/gen-prompt-budget-parity-corpus.cjs --check # drift-guard (exit 1 on drift)
|
|
32
|
+
*
|
|
33
|
+
* `--check` is the intentional-change gate: after Phase 2 lands, any diff here means
|
|
34
|
+
* review-prompt output moved, which is a user-visible change requiring justification.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
'use strict';
|
|
38
|
+
|
|
39
|
+
const fs = require('node:fs');
|
|
40
|
+
const path = require('node:path');
|
|
41
|
+
|
|
42
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
43
|
+
const OUT_PATH = path.join(REPO_ROOT, 'tests', 'fixtures', 'prompt-budget-parity', 'corpus.json');
|
|
44
|
+
const IMPL_PATH = path.join(REPO_ROOT, 'gsd-core', 'bin', 'lib', 'prompt-budget.cjs');
|
|
45
|
+
|
|
46
|
+
/** Corpus schema version. Bump only on a deliberate shape change. */
|
|
47
|
+
const CORPUS_VERSION = 1;
|
|
48
|
+
|
|
49
|
+
// ─── Deterministic content builders ──────────────────────────────────────────
|
|
50
|
+
// estimateTokens() is Math.ceil(len / 4), so a string of length 4*N measures
|
|
51
|
+
// exactly N tokens. Every size below is therefore exact, not approximate — which
|
|
52
|
+
// is what makes the cap-1 / cap / cap+1 rows land ON the boundary rather than near it.
|
|
53
|
+
|
|
54
|
+
/** A filler string measuring exactly `tokens` tokens under chars/4. */
|
|
55
|
+
function tokensOf(tokens, fill = 'x') {
|
|
56
|
+
return fill.repeat(tokens * 4);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** `lines` newline-separated lines, each `width` chars wide. */
|
|
60
|
+
function linesOf(lines, width = 19) {
|
|
61
|
+
return Array.from({ length: lines }, (_, i) => String(i).padEnd(width, '-')).join('\n');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Case definitions ────────────────────────────────────────────────────────
|
|
65
|
+
// Each case is { name, why, sections, budget, options }.
|
|
66
|
+
// `why` names the design/test-matrix row the case exists to pin.
|
|
67
|
+
|
|
68
|
+
function buildCases() {
|
|
69
|
+
const cases = [];
|
|
70
|
+
const add = (c) => cases.push(c);
|
|
71
|
+
|
|
72
|
+
// Base shape reused by the boundary family. With safetyMarginPct 0 the effective
|
|
73
|
+
// budget equals the budget, so totals can be placed exactly on the cap.
|
|
74
|
+
// staticBase = est(instructions) + est('## Roadmap\n\n') + est(roadmap)
|
|
75
|
+
// + est('## Plans\n\n') + est('### a.md\n\n')
|
|
76
|
+
const boundaryBase = (planTokens) => ({
|
|
77
|
+
instructions: tokensOf(10, 'I'),
|
|
78
|
+
roadmap: tokensOf(10, 'R'),
|
|
79
|
+
plans: [{ file: 'a.md', content: tokensOf(planTokens, 'P') }],
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ── A. Budget boundary (RULESET.TESTS.boundary-coverage.fixtures a/b/c) ────
|
|
83
|
+
add({
|
|
84
|
+
name: 'A1-exact-cap',
|
|
85
|
+
why: 'A1/row 3 — total measures exactly effectiveBudget; must NOT be treated as pressure',
|
|
86
|
+
sections: boundaryBase(71),
|
|
87
|
+
budget: 100,
|
|
88
|
+
options: { safetyMarginPct: 0 },
|
|
89
|
+
});
|
|
90
|
+
add({
|
|
91
|
+
name: 'A2-one-under-cap',
|
|
92
|
+
why: 'A2/row 5 — one token under; no pressure, no reserve deducted',
|
|
93
|
+
sections: boundaryBase(70),
|
|
94
|
+
budget: 100,
|
|
95
|
+
options: { safetyMarginPct: 0 },
|
|
96
|
+
});
|
|
97
|
+
add({
|
|
98
|
+
name: 'A3-one-over-cap',
|
|
99
|
+
why: 'A3/row 4 — one token over; pressure engages the ladder',
|
|
100
|
+
sections: boundaryBase(72),
|
|
101
|
+
budget: 100,
|
|
102
|
+
options: { safetyMarginPct: 0 },
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// A1 pins exact-cap as "no pressure", but with only a floored plan group present that
|
|
106
|
+
// assertion is not load-bearing: the 1024-char floor absorbs any trim, so relaxing the
|
|
107
|
+
// pressure test from `>` to `>=` produces byte-identical output and the corpus cannot
|
|
108
|
+
// see the difference. This case gives the ladder something DROPPABLE at exactly the cap,
|
|
109
|
+
// so `>` vs `>=` becomes observable as context surviving vs being omitted.
|
|
110
|
+
// staticBase 29 + plan 20 = 49, context wrapper 3, so est(context) 148 puts the
|
|
111
|
+
// baseline at exactly 200 = effectiveBudget.
|
|
112
|
+
add({
|
|
113
|
+
name: 'A3b-exact-cap-with-droppable-present',
|
|
114
|
+
why: 'exact-cap discriminator — baseline lands exactly ON effectiveBudget with a droppable available. Pins that `baseline > effectiveBudget` is STRICT: at equality nothing is dropped. Mutating the comparison to >= flips context from kept to omitted, which A1 alone cannot detect.',
|
|
115
|
+
sections: {
|
|
116
|
+
instructions: tokensOf(10, 'I'),
|
|
117
|
+
roadmap: tokensOf(10, 'R'),
|
|
118
|
+
plans: [{ file: 'a.md', content: tokensOf(20, 'P') }],
|
|
119
|
+
context: tokensOf(148, 'C'),
|
|
120
|
+
},
|
|
121
|
+
budget: 200,
|
|
122
|
+
options: { safetyMarginPct: 0 },
|
|
123
|
+
});
|
|
124
|
+
add({
|
|
125
|
+
name: 'A3c-one-under-cap-with-droppable-present',
|
|
126
|
+
why: 'companion to A3b — one token under the cap with the same droppable available. Together they bracket the strict-inequality boundary from below.',
|
|
127
|
+
sections: {
|
|
128
|
+
instructions: tokensOf(10, 'I'),
|
|
129
|
+
roadmap: tokensOf(10, 'R'),
|
|
130
|
+
plans: [{ file: 'a.md', content: tokensOf(20, 'P') }],
|
|
131
|
+
context: tokensOf(147, 'C'),
|
|
132
|
+
},
|
|
133
|
+
budget: 200,
|
|
134
|
+
options: { safetyMarginPct: 0 },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Row (d) of RULESET.TESTS.boundary-coverage.fixtures: baseline within
|
|
138
|
+
// reserve-distance of the limit. NOTE_RESERVE_TOKENS is 80, and this is the exact
|
|
139
|
+
// band (effectiveBudget - 80, effectiveBudget] that PR #3708 shipped two
|
|
140
|
+
// regressions in (LEARNING.prompt-budget.boundary-gap). Swept token by token.
|
|
141
|
+
// Budget 200 (not 100) is required here, not incidental: the band is
|
|
142
|
+
// NOTE_RESERVE_TOKENS (80) wide, so the baseline needs >=81 tokens of headroom
|
|
143
|
+
// beneath the cap to be swept. staticBase is 29 tokens, so planTokens = 171 - delta
|
|
144
|
+
// places the baseline exactly `delta` under effectiveBudget and stays positive
|
|
145
|
+
// across the whole sweep. delta 80 and 81 sit just OUTSIDE the band deliberately —
|
|
146
|
+
// they are the negative controls that prove the band's edge is where it is claimed.
|
|
147
|
+
for (const delta of [0, 1, 2, 39, 40, 79, 80, 81]) {
|
|
148
|
+
add({
|
|
149
|
+
name: `A4-reserve-band-minus-${delta}`,
|
|
150
|
+
why: `boundary-coverage.fixtures row (d) — baseline at effectiveBudget-${delta}. Pins the NEGATIVE control: within NOTE_RESERVE_TOKENS distance of the cap, NOTHING may be trimmed. Early-pressure firing here is the exact PR #3708 regression.`,
|
|
151
|
+
sections: boundaryBase(171 - delta),
|
|
152
|
+
budget: 200,
|
|
153
|
+
options: { safetyMarginPct: 0 },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Mirror of A4 on the OTHER side of the cap. A4 proves the reserve is NOT deducted
|
|
158
|
+
// at or below the cap; this proves it IS deducted above it, and that the ladder
|
|
159
|
+
// relieves pressure by dropping. Without both sides the family pins only half the
|
|
160
|
+
// boundary, and an "always reserve" regression would still pass A4.
|
|
161
|
+
// staticBase 29 + planContent 20 = 49, and the context wrapper costs 3, so
|
|
162
|
+
// est(context) = 148 + delta places the baseline exactly `delta` OVER effectiveBudget.
|
|
163
|
+
for (const delta of [1, 2, 40, 79, 80, 81]) {
|
|
164
|
+
add({
|
|
165
|
+
name: `A10-pressure-band-plus-${delta}`,
|
|
166
|
+
why: `boundary-coverage.fixtures row (d), above-cap mirror — baseline at effectiveBudget+${delta}; pressure fires, NOTE_RESERVE_TOKENS is deducted, and context is dropped`,
|
|
167
|
+
sections: {
|
|
168
|
+
instructions: tokensOf(10, 'I'),
|
|
169
|
+
roadmap: tokensOf(10, 'R'),
|
|
170
|
+
plans: [{ file: 'a.md', content: tokensOf(20, 'P') }],
|
|
171
|
+
context: tokensOf(148 + delta, 'C'),
|
|
172
|
+
},
|
|
173
|
+
budget: 200,
|
|
174
|
+
options: { safetyMarginPct: 0 },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
add({
|
|
179
|
+
name: 'A5-safety-margin-default',
|
|
180
|
+
why: 'A4 — default 10% margin; effectiveBudget = floor(budget * 0.9)',
|
|
181
|
+
sections: boundaryBase(60),
|
|
182
|
+
budget: 100,
|
|
183
|
+
options: {},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// The rounding mode of effectiveBudget was unpinned: every other case in this file
|
|
187
|
+
// uses a (budget, safetyMarginPct) pair whose product is a whole number, so
|
|
188
|
+
// Math.floor and Math.round agree and mutating one to the other changed nothing.
|
|
189
|
+
// These three straddle .5 in both directions so the mode is observable.
|
|
190
|
+
// 95 * 0.90 = 85.5 -> floor 85, round 86 (disagree)
|
|
191
|
+
// 97 * 0.90 = 87.3 -> floor 87, round 87 (agree; guards against a ceil mutation)
|
|
192
|
+
// 93 * 0.85 = 79.05 -> floor 79, round 79 (agree; second ceil guard, different margin)
|
|
193
|
+
add({
|
|
194
|
+
name: 'A11-fractional-effective-budget-half',
|
|
195
|
+
why: 'pins the ROUNDING MODE of effectiveBudget. 95 * (1 - 10/100) = 85.5, where Math.floor (85) and Math.round (86) disagree. Without a fractional case the mode is unpinned and floor->round is a byte-invisible mutation, which an isolated review confirmed against the 47-case corpus.',
|
|
196
|
+
sections: boundaryBase(56),
|
|
197
|
+
budget: 95,
|
|
198
|
+
options: { safetyMarginPct: 10 },
|
|
199
|
+
});
|
|
200
|
+
add({
|
|
201
|
+
name: 'A12-fractional-effective-budget-below-half',
|
|
202
|
+
why: 'companion to A11 below the .5 point: 97 * 0.90 = 87.3, where floor and round agree but Math.ceil (88) would not. Guards the mutation A11 does not cover.',
|
|
203
|
+
sections: boundaryBase(58),
|
|
204
|
+
budget: 97,
|
|
205
|
+
options: { safetyMarginPct: 10 },
|
|
206
|
+
});
|
|
207
|
+
add({
|
|
208
|
+
name: 'A13-fractional-effective-budget-odd-margin',
|
|
209
|
+
why: 'third rounding guard at a non-multiple-of-10 margin: 93 * (1 - 15/100) = 79.05. Exercises the margin arithmetic itself, not just the budget, since safetyMarginPct is caller-supplied.',
|
|
210
|
+
sections: boundaryBase(50),
|
|
211
|
+
budget: 93,
|
|
212
|
+
options: { safetyMarginPct: 15 },
|
|
213
|
+
});
|
|
214
|
+
add({
|
|
215
|
+
name: 'A6-safety-margin-zero',
|
|
216
|
+
why: 'A5 — 0% margin means the full budget is usable',
|
|
217
|
+
sections: boundaryBase(60),
|
|
218
|
+
budget: 100,
|
|
219
|
+
options: { safetyMarginPct: 0 },
|
|
220
|
+
});
|
|
221
|
+
add({
|
|
222
|
+
name: 'A7-safety-margin-hundred',
|
|
223
|
+
why: 'A5 — 100% margin drives effectiveBudget to 0, forcing the min-set hard fail',
|
|
224
|
+
sections: boundaryBase(10),
|
|
225
|
+
budget: 100,
|
|
226
|
+
options: { safetyMarginPct: 100 },
|
|
227
|
+
});
|
|
228
|
+
add({
|
|
229
|
+
name: 'A8-budget-zero',
|
|
230
|
+
why: 'A6 — non-positive budget must hard-fail cleanly, never negative-length slice',
|
|
231
|
+
sections: boundaryBase(10),
|
|
232
|
+
budget: 0,
|
|
233
|
+
options: {},
|
|
234
|
+
});
|
|
235
|
+
add({
|
|
236
|
+
name: 'A9-budget-negative',
|
|
237
|
+
why: 'A6 — negative budget must not crash',
|
|
238
|
+
sections: boundaryBase(10),
|
|
239
|
+
budget: -50,
|
|
240
|
+
options: {},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// ── B. Shrink strategies ──────────────────────────────────────────────────
|
|
244
|
+
add({
|
|
245
|
+
name: 'B3-head-shrink-engaged',
|
|
246
|
+
why: 'B3/row 7 — projectMd longer than projectMdHeadLines under pressure; shrunk flag true',
|
|
247
|
+
sections: {
|
|
248
|
+
instructions: tokensOf(10, 'I'),
|
|
249
|
+
roadmap: tokensOf(10, 'R'),
|
|
250
|
+
plans: [{ file: 'a.md', content: tokensOf(40, 'P') }],
|
|
251
|
+
projectMd: linesOf(120),
|
|
252
|
+
},
|
|
253
|
+
budget: 200,
|
|
254
|
+
options: { safetyMarginPct: 0 },
|
|
255
|
+
});
|
|
256
|
+
add({
|
|
257
|
+
name: 'B4-head-shrink-noop',
|
|
258
|
+
why: 'B4/row 7+9 — fewer lines than the cap; text unchanged and shrunk flag MUST stay false',
|
|
259
|
+
sections: {
|
|
260
|
+
instructions: tokensOf(10, 'I'),
|
|
261
|
+
roadmap: tokensOf(10, 'R'),
|
|
262
|
+
plans: [{ file: 'a.md', content: tokensOf(40, 'P') }],
|
|
263
|
+
projectMd: linesOf(5),
|
|
264
|
+
},
|
|
265
|
+
budget: 60,
|
|
266
|
+
options: { safetyMarginPct: 0 },
|
|
267
|
+
});
|
|
268
|
+
add({
|
|
269
|
+
name: 'B5-head-shrink-zero-lines',
|
|
270
|
+
why: 'B5/row 8 — projectMdHeadLines <= 0 yields the empty string',
|
|
271
|
+
sections: {
|
|
272
|
+
instructions: tokensOf(10, 'I'),
|
|
273
|
+
roadmap: tokensOf(10, 'R'),
|
|
274
|
+
plans: [{ file: 'a.md', content: tokensOf(40, 'P') }],
|
|
275
|
+
projectMd: linesOf(120),
|
|
276
|
+
},
|
|
277
|
+
budget: 60,
|
|
278
|
+
options: { safetyMarginPct: 0, projectMdHeadLines: 0 },
|
|
279
|
+
});
|
|
280
|
+
add({
|
|
281
|
+
name: 'B6-plan-truncate-share-above-floor',
|
|
282
|
+
why: 'B6/row 13 — proportional share ABOVE the floor, so the share (not the floor) decides. Budget 700: min-set is 5+5+256+256=522 which fits, so this reaches the truncate step instead of hard-failing the way the original budget-400 version did. groupBudget 598 of 1000 => charsBudget 2392, share 1196 per plan > floor 1024.',
|
|
283
|
+
sections: {
|
|
284
|
+
instructions: tokensOf(5, 'I'),
|
|
285
|
+
roadmap: tokensOf(5, 'R'),
|
|
286
|
+
plans: [
|
|
287
|
+
{ file: 'a.md', content: tokensOf(500, 'A') },
|
|
288
|
+
{ file: 'b.md', content: tokensOf(500, 'B') },
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
budget: 700,
|
|
292
|
+
options: { safetyMarginPct: 0 },
|
|
293
|
+
});
|
|
294
|
+
add({
|
|
295
|
+
name: 'B7-plan-truncate-floor-binding',
|
|
296
|
+
why: 'B7/row 10 — share falls BELOW the 1024-char floor so the floor wins, and the resulting total deliberately EXCEEDS the group budget. Budget 1100: min-set 5+5+4*256=1034 fits; groupBudget 992 => charsBudget 3968 => share 992 < 1024, so every plan is held at the floor. This is the case that proves a floor is a per-fragment guarantee, not a budget cap.',
|
|
297
|
+
sections: {
|
|
298
|
+
instructions: tokensOf(5, 'I'),
|
|
299
|
+
roadmap: tokensOf(5, 'R'),
|
|
300
|
+
plans: [
|
|
301
|
+
{ file: 'a.md', content: tokensOf(500, 'A') },
|
|
302
|
+
{ file: 'b.md', content: tokensOf(500, 'B') },
|
|
303
|
+
{ file: 'c.md', content: tokensOf(500, 'C') },
|
|
304
|
+
{ file: 'd.md', content: tokensOf(500, 'D') },
|
|
305
|
+
],
|
|
306
|
+
},
|
|
307
|
+
budget: 1100,
|
|
308
|
+
options: { safetyMarginPct: 0 },
|
|
309
|
+
});
|
|
310
|
+
add({
|
|
311
|
+
name: 'B8-zero-sized-plans',
|
|
312
|
+
why: 'B8/row 11 — total original chars 0; no divide-by-zero, pct stays 0',
|
|
313
|
+
sections: {
|
|
314
|
+
instructions: tokensOf(10, 'I'),
|
|
315
|
+
roadmap: tokensOf(10, 'R'),
|
|
316
|
+
plans: [
|
|
317
|
+
{ file: 'a.md', content: '' },
|
|
318
|
+
{ file: 'b.md', content: '' },
|
|
319
|
+
],
|
|
320
|
+
context: tokensOf(200, 'C'),
|
|
321
|
+
},
|
|
322
|
+
budget: 60,
|
|
323
|
+
options: { safetyMarginPct: 0 },
|
|
324
|
+
});
|
|
325
|
+
add({
|
|
326
|
+
name: 'B9-nonpositive-plan-budget',
|
|
327
|
+
why: 'B9/row 12 — computed plan budget <= 0 skips the truncate step ENTIRELY rather than clamping',
|
|
328
|
+
sections: boundaryBase(72),
|
|
329
|
+
budget: 100,
|
|
330
|
+
options: { safetyMarginPct: 0 },
|
|
331
|
+
});
|
|
332
|
+
add({
|
|
333
|
+
name: 'B11-drop-order-all-three',
|
|
334
|
+
why: 'B11/row 14 — droppables leave in declared order: context, research, requirements',
|
|
335
|
+
sections: {
|
|
336
|
+
instructions: tokensOf(10, 'I'),
|
|
337
|
+
roadmap: tokensOf(10, 'R'),
|
|
338
|
+
plans: [{ file: 'a.md', content: tokensOf(30, 'P') }],
|
|
339
|
+
context: tokensOf(300, 'C'),
|
|
340
|
+
research: tokensOf(300, 'S'),
|
|
341
|
+
requirements: tokensOf(300, 'Q'),
|
|
342
|
+
},
|
|
343
|
+
budget: 200,
|
|
344
|
+
options: { safetyMarginPct: 0 },
|
|
345
|
+
});
|
|
346
|
+
add({
|
|
347
|
+
name: 'B11b-drop-context-only',
|
|
348
|
+
why: 'B11 — pressure relieved after the first drop; research and requirements survive',
|
|
349
|
+
sections: {
|
|
350
|
+
instructions: tokensOf(10, 'I'),
|
|
351
|
+
roadmap: tokensOf(10, 'R'),
|
|
352
|
+
plans: [{ file: 'a.md', content: tokensOf(30, 'P') }],
|
|
353
|
+
context: tokensOf(300, 'C'),
|
|
354
|
+
research: tokensOf(10, 'S'),
|
|
355
|
+
requirements: tokensOf(10, 'Q'),
|
|
356
|
+
},
|
|
357
|
+
budget: 300,
|
|
358
|
+
options: { safetyMarginPct: 0 },
|
|
359
|
+
});
|
|
360
|
+
add({
|
|
361
|
+
name: 'B12-droppables-absent',
|
|
362
|
+
why: 'B12/row 15 — null droppables are not dropped and never appear in omitted. Budget 400 (not 120) so this actually RENDERS: at 120 the min-set pre-check hard-fails and the case would compare two empty strings.',
|
|
363
|
+
sections: {
|
|
364
|
+
instructions: tokensOf(10, 'I'),
|
|
365
|
+
roadmap: tokensOf(10, 'R'),
|
|
366
|
+
plans: [{ file: 'a.md', content: tokensOf(300, 'P') }],
|
|
367
|
+
context: null,
|
|
368
|
+
research: null,
|
|
369
|
+
requirements: null,
|
|
370
|
+
},
|
|
371
|
+
budget: 400,
|
|
372
|
+
options: { safetyMarginPct: 0 },
|
|
373
|
+
});
|
|
374
|
+
add({
|
|
375
|
+
name: 'B13-droppables-empty-string',
|
|
376
|
+
why: 'B13/row 23 — empty-string droppables. Paired with B12 to pin whether "" and null are distinguished. They are NOT: applyBudget uses truthy checks throughout, so "" behaves as absent. This case records that fact rather than the design doc\'s claim that they differ.',
|
|
377
|
+
sections: {
|
|
378
|
+
instructions: tokensOf(10, 'I'),
|
|
379
|
+
roadmap: tokensOf(10, 'R'),
|
|
380
|
+
plans: [{ file: 'a.md', content: tokensOf(300, 'P') }],
|
|
381
|
+
context: '',
|
|
382
|
+
research: '',
|
|
383
|
+
requirements: '',
|
|
384
|
+
},
|
|
385
|
+
budget: 400,
|
|
386
|
+
options: { safetyMarginPct: 0 },
|
|
387
|
+
});
|
|
388
|
+
add({
|
|
389
|
+
name: 'B13b-empty-string-droppable-under-pressure',
|
|
390
|
+
why: 'B13 companion — an empty-string droppable while the ladder is ACTIVELY running. Pins that "" is never recorded in omitted even under real pressure, which B12/B13 (no pressure) cannot show. research is real and IS dropped, so the ladder is demonstrably engaged.',
|
|
391
|
+
sections: {
|
|
392
|
+
instructions: tokensOf(10, 'I'),
|
|
393
|
+
roadmap: tokensOf(10, 'R'),
|
|
394
|
+
plans: [{ file: 'a.md', content: tokensOf(20, 'P') }],
|
|
395
|
+
context: '',
|
|
396
|
+
research: tokensOf(200, 'S'),
|
|
397
|
+
requirements: '',
|
|
398
|
+
},
|
|
399
|
+
budget: 200,
|
|
400
|
+
options: { safetyMarginPct: 0 },
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// ── C. Note reserve + hard-fail paths ─────────────────────────────────────
|
|
404
|
+
add({
|
|
405
|
+
name: 'C1-no-trim-no-note',
|
|
406
|
+
why: 'C1/row 1 — everything fits; no note, noteInjected false',
|
|
407
|
+
sections: {
|
|
408
|
+
instructions: tokensOf(10, 'I'),
|
|
409
|
+
roadmap: tokensOf(10, 'R'),
|
|
410
|
+
plans: [{ file: 'a.md', content: tokensOf(10, 'P') }],
|
|
411
|
+
projectMd: linesOf(3),
|
|
412
|
+
context: tokensOf(5, 'C'),
|
|
413
|
+
research: tokensOf(5, 'S'),
|
|
414
|
+
requirements: tokensOf(5, 'Q'),
|
|
415
|
+
},
|
|
416
|
+
budget: 1000,
|
|
417
|
+
options: {},
|
|
418
|
+
});
|
|
419
|
+
add({
|
|
420
|
+
name: 'C4-min-set-hard-fail',
|
|
421
|
+
why: 'C4/row 16 — min-set pre-check exceeds budget; empty output and estimatedTokens 0',
|
|
422
|
+
sections: {
|
|
423
|
+
instructions: tokensOf(500, 'I'),
|
|
424
|
+
roadmap: tokensOf(500, 'R'),
|
|
425
|
+
plans: [{ file: 'a.md', content: tokensOf(500, 'P') }],
|
|
426
|
+
},
|
|
427
|
+
budget: 100,
|
|
428
|
+
options: {},
|
|
429
|
+
});
|
|
430
|
+
add({
|
|
431
|
+
name: 'C5-post-assembly-hard-fail',
|
|
432
|
+
why: 'C5/row 17 — survives the min-set check but the assembled prompt still overflows; real measured size reported',
|
|
433
|
+
sections: {
|
|
434
|
+
instructions: tokensOf(80, 'I'),
|
|
435
|
+
roadmap: tokensOf(80, 'R'),
|
|
436
|
+
plans: [{ file: 'a.md', content: tokensOf(2000, 'P') }],
|
|
437
|
+
},
|
|
438
|
+
budget: 200,
|
|
439
|
+
options: { safetyMarginPct: 0 },
|
|
440
|
+
});
|
|
441
|
+
add({
|
|
442
|
+
name: 'C7-custom-note-template',
|
|
443
|
+
why: 'note template placeholders {budget} {omittedList} {planTruncationPct} all substitute',
|
|
444
|
+
sections: {
|
|
445
|
+
instructions: tokensOf(10, 'I'),
|
|
446
|
+
roadmap: tokensOf(10, 'R'),
|
|
447
|
+
plans: [{ file: 'a.md', content: tokensOf(30, 'P') }],
|
|
448
|
+
context: tokensOf(300, 'C'),
|
|
449
|
+
},
|
|
450
|
+
budget: 200,
|
|
451
|
+
options: {
|
|
452
|
+
safetyMarginPct: 0,
|
|
453
|
+
noteTemplate: 'TRIMMED b={budget} o={omittedList} p={planTruncationPct}',
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
add({
|
|
457
|
+
name: 'C8-omitted-list-none',
|
|
458
|
+
why: 'renderNote emits the literal "none" when a trim occurred but nothing was dropped',
|
|
459
|
+
sections: {
|
|
460
|
+
instructions: tokensOf(10, 'I'),
|
|
461
|
+
roadmap: tokensOf(10, 'R'),
|
|
462
|
+
plans: [{ file: 'a.md', content: tokensOf(30, 'P') }],
|
|
463
|
+
projectMd: linesOf(120),
|
|
464
|
+
},
|
|
465
|
+
budget: 120,
|
|
466
|
+
options: { safetyMarginPct: 0 },
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// ── D. Shape + encoding ───────────────────────────────────────────────────
|
|
470
|
+
add({
|
|
471
|
+
name: 'D1-all-sections-present',
|
|
472
|
+
why: 'full assembly order: instructions, note, roadmap, project, plans, context, research, requirements',
|
|
473
|
+
sections: {
|
|
474
|
+
instructions: tokensOf(10, 'I'),
|
|
475
|
+
roadmap: tokensOf(10, 'R'),
|
|
476
|
+
plans: [
|
|
477
|
+
{ file: 'one.md', content: tokensOf(10, '1') },
|
|
478
|
+
{ file: 'two.md', content: tokensOf(10, '2') },
|
|
479
|
+
],
|
|
480
|
+
projectMd: linesOf(4),
|
|
481
|
+
context: tokensOf(10, 'C'),
|
|
482
|
+
research: tokensOf(10, 'S'),
|
|
483
|
+
requirements: tokensOf(10, 'Q'),
|
|
484
|
+
},
|
|
485
|
+
budget: 5000,
|
|
486
|
+
options: {},
|
|
487
|
+
});
|
|
488
|
+
add({
|
|
489
|
+
name: 'D2-no-optional-sections',
|
|
490
|
+
why: 'minimum viable shape — only the two never-droppables plus one plan',
|
|
491
|
+
sections: {
|
|
492
|
+
instructions: tokensOf(10, 'I'),
|
|
493
|
+
roadmap: tokensOf(10, 'R'),
|
|
494
|
+
plans: [{ file: 'a.md', content: tokensOf(10, 'P') }],
|
|
495
|
+
},
|
|
496
|
+
budget: 5000,
|
|
497
|
+
options: {},
|
|
498
|
+
});
|
|
499
|
+
add({
|
|
500
|
+
name: 'D3-zero-plans',
|
|
501
|
+
why: 'empty plans array — headers still assembled, reduce over [] is 0',
|
|
502
|
+
sections: {
|
|
503
|
+
instructions: tokensOf(10, 'I'),
|
|
504
|
+
roadmap: tokensOf(10, 'R'),
|
|
505
|
+
plans: [],
|
|
506
|
+
},
|
|
507
|
+
budget: 5000,
|
|
508
|
+
options: {},
|
|
509
|
+
});
|
|
510
|
+
add({
|
|
511
|
+
name: 'D4-crlf-content',
|
|
512
|
+
why: 'row 25 — CRLF measured and sliced identically to LF under the token unit',
|
|
513
|
+
sections: {
|
|
514
|
+
instructions: 'alpha\r\nbeta\r\ngamma',
|
|
515
|
+
roadmap: 'one\r\ntwo\r\nthree',
|
|
516
|
+
plans: [{ file: 'a.md', content: Array.from({ length: 60 }, (_, i) => `line ${i}`).join('\r\n') }],
|
|
517
|
+
projectMd: Array.from({ length: 80 }, (_, i) => `p${i}`).join('\r\n'),
|
|
518
|
+
},
|
|
519
|
+
budget: 120,
|
|
520
|
+
options: { safetyMarginPct: 0 },
|
|
521
|
+
});
|
|
522
|
+
add({
|
|
523
|
+
name: 'D5-unicode-content',
|
|
524
|
+
why: 'row 26 — multibyte and astral content; chars/4 counts UTF-16 units, and slice may split a surrogate pair (pinned as current behavior)',
|
|
525
|
+
sections: {
|
|
526
|
+
instructions: 'héllo wörld ünicode',
|
|
527
|
+
roadmap: '日本語のロードマップ',
|
|
528
|
+
plans: [{ file: 'ünï.md', content: '🎉'.repeat(400) }],
|
|
529
|
+
context: 'Ω'.repeat(200),
|
|
530
|
+
},
|
|
531
|
+
budget: 150,
|
|
532
|
+
options: { safetyMarginPct: 0 },
|
|
533
|
+
});
|
|
534
|
+
add({
|
|
535
|
+
name: 'D6-plan-filename-affects-budget',
|
|
536
|
+
why: 'S5 — per-plan wrapper cost "### <file>\\n\\n" counts toward staticBaseTokens',
|
|
537
|
+
sections: {
|
|
538
|
+
instructions: tokensOf(10, 'I'),
|
|
539
|
+
roadmap: tokensOf(10, 'R'),
|
|
540
|
+
plans: [{ file: 'a-very-long-plan-filename-that-costs-tokens.md', content: tokensOf(10, 'P') }],
|
|
541
|
+
},
|
|
542
|
+
budget: 5000,
|
|
543
|
+
options: {},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
return cases;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ─── Capture ─────────────────────────────────────────────────────────────────
|
|
550
|
+
|
|
551
|
+
function capture() {
|
|
552
|
+
if (!fs.existsSync(IMPL_PATH)) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
`prompt-budget.cjs not found at ${IMPL_PATH}. Run "npm run build:lib" first ` +
|
|
555
|
+
'(the .cjs is a gitignored tsc output of src/prompt-budget.cts).'
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
const { applyBudget } = require(IMPL_PATH);
|
|
559
|
+
|
|
560
|
+
const cases = buildCases();
|
|
561
|
+
|
|
562
|
+
const seen = new Set();
|
|
563
|
+
for (const c of cases) {
|
|
564
|
+
if (seen.has(c.name)) throw new Error(`duplicate corpus case name: ${c.name}`);
|
|
565
|
+
seen.add(c.name);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
version: CORPUS_VERSION,
|
|
570
|
+
description:
|
|
571
|
+
'Characterization corpus of prompt-budget.applyBudget captured from the pre-refactor ' +
|
|
572
|
+
'implementation. Oracle for issue #2929 (epic #1671 Phase 2). Regenerate with ' +
|
|
573
|
+
'node scripts/gen-prompt-budget-parity-corpus.cjs --write',
|
|
574
|
+
cases: cases.map((c) => {
|
|
575
|
+
const result = applyBudget({
|
|
576
|
+
sections: c.sections,
|
|
577
|
+
budget: c.budget,
|
|
578
|
+
options: c.options,
|
|
579
|
+
});
|
|
580
|
+
return {
|
|
581
|
+
name: c.name,
|
|
582
|
+
why: c.why,
|
|
583
|
+
input: { sections: c.sections, budget: c.budget, options: c.options },
|
|
584
|
+
expected: { prompt: result.prompt, metadata: result.metadata },
|
|
585
|
+
};
|
|
586
|
+
}),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function serialize(corpus) {
|
|
591
|
+
return `${JSON.stringify(corpus, null, 2)}\n`;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
function main(argv) {
|
|
597
|
+
const write = argv.includes('--write');
|
|
598
|
+
const check = argv.includes('--check');
|
|
599
|
+
|
|
600
|
+
if (write === check) {
|
|
601
|
+
process.stderr.write('usage: gen-prompt-budget-parity-corpus.cjs (--write | --check)\n');
|
|
602
|
+
return 2;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const serialized = serialize(capture());
|
|
606
|
+
|
|
607
|
+
if (write) {
|
|
608
|
+
fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true });
|
|
609
|
+
fs.writeFileSync(OUT_PATH, serialized);
|
|
610
|
+
const n = JSON.parse(serialized).cases.length;
|
|
611
|
+
process.stdout.write(`wrote ${path.relative(REPO_ROOT, OUT_PATH)} (${n} cases)\n`);
|
|
612
|
+
return 0;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (!fs.existsSync(OUT_PATH)) {
|
|
616
|
+
process.stderr.write(
|
|
617
|
+
`MISSING ${path.relative(REPO_ROOT, OUT_PATH)}\n` +
|
|
618
|
+
'Run: node scripts/gen-prompt-budget-parity-corpus.cjs --write\n'
|
|
619
|
+
);
|
|
620
|
+
return 1;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const committed = fs.readFileSync(OUT_PATH, 'utf8');
|
|
624
|
+
if (committed !== serialized) {
|
|
625
|
+
process.stderr.write(
|
|
626
|
+
`DRIFT in ${path.relative(REPO_ROOT, OUT_PATH)}\n\n` +
|
|
627
|
+
"prompt-budget's observable output no longer matches the committed corpus.\n" +
|
|
628
|
+
'This corpus is the parity oracle for #2929: a diff here means review-prompt\n' +
|
|
629
|
+
'output MOVED. That is user-visible.\n\n' +
|
|
630
|
+
'If the change was NOT intended, fix the code — do not regenerate.\n' +
|
|
631
|
+
'If it WAS intended, regenerate and justify the diff in the PR:\n' +
|
|
632
|
+
' node scripts/gen-prompt-budget-parity-corpus.cjs --write\n'
|
|
633
|
+
);
|
|
634
|
+
return 1;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
process.stdout.write(`ok ${path.relative(REPO_ROOT, OUT_PATH)}\n`);
|
|
638
|
+
return 0;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (require.main === module) {
|
|
642
|
+
process.exitCode = main(process.argv.slice(2));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
module.exports = { buildCases, capture, serialize, OUT_PATH, CORPUS_VERSION };
|
package/scripts/gen-registry.cjs
CHANGED
|
@@ -35,6 +35,7 @@ const path = require('node:path');
|
|
|
35
35
|
|
|
36
36
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
37
37
|
const { renderMarkdown } = require('./registry-schema.cjs');
|
|
38
|
+
const { normalizeEol } = require('../gsd-core/bin/lib/text-lines.cjs');
|
|
38
39
|
|
|
39
40
|
const SOURCES = [
|
|
40
41
|
{ type: 'capability', jsonFile: 'capabilities.json', mdFile: 'capability-registry.md' },
|
|
@@ -42,18 +43,6 @@ const SOURCES = [
|
|
|
42
43
|
{ type: 'reviewer', jsonFile: 'reviewers.json', mdFile: 'reviewer-registry.md', optional: true },
|
|
43
44
|
];
|
|
44
45
|
|
|
45
|
-
/**
|
|
46
|
-
* The generator always writes LF; a Windows checkout (autocrlf) may present
|
|
47
|
-
* committed files with CRLF. Normalize before comparing so `--check` only
|
|
48
|
-
* fails on real content drift, not checkout-introduced line-ending noise.
|
|
49
|
-
*
|
|
50
|
-
* @param {string} content
|
|
51
|
-
* @returns {string}
|
|
52
|
-
*/
|
|
53
|
-
function normalizeLineEndings(content) {
|
|
54
|
-
return content.replace(/\r/g, '');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
46
|
function getRegistriesDir() {
|
|
58
47
|
return path.join(process.cwd(), 'docs', 'registries');
|
|
59
48
|
}
|
|
@@ -126,7 +115,7 @@ function main() {
|
|
|
126
115
|
continue;
|
|
127
116
|
}
|
|
128
117
|
const committed = fs.readFileSync(mdPath, 'utf8');
|
|
129
|
-
if (
|
|
118
|
+
if (normalizeEol(committed) !== normalizeEol(rendered)) {
|
|
130
119
|
process.stderr.write(`${mdFile} is stale. Run:\n node scripts/gen-registry.cjs --write\n`);
|
|
131
120
|
anyDrift = true;
|
|
132
121
|
}
|
|
@@ -149,4 +138,4 @@ function main() {
|
|
|
149
138
|
|
|
150
139
|
if (require.main === module) runMain(main);
|
|
151
140
|
|
|
152
|
-
module.exports = { main, renderFor, SOURCES, normalizeLineEndings };
|
|
141
|
+
module.exports = { main, renderFor, SOURCES, normalizeLineEndings: normalizeEol };
|