@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
|
@@ -290,6 +290,56 @@ function _resetRuntimeWarningCacheForTests() {
|
|
|
290
290
|
_warnedConfigKeys.clear();
|
|
291
291
|
_warnedUnknownConfigKeys.clear();
|
|
292
292
|
_warnedUnusableConfig.clear();
|
|
293
|
+
_warnedShadowedGlobalKeys.clear();
|
|
294
|
+
}
|
|
295
|
+
// ─── #3532 (10b): shadowed global-defaults diagnostic ────────────────────────
|
|
296
|
+
// The keys Branch D's `_globalBaseCfg` demonstrably honors from
|
|
297
|
+
// ~/.gsd/defaults.json when no project config exists. Under a project
|
|
298
|
+
// .planning/config.json (Branch A — every real project) the global file is
|
|
299
|
+
// never opened, so each of these set globally is silently inert for resolution.
|
|
300
|
+
// `effort` is in Branch D's honored set but is EXCLUDED from the shadow warning:
|
|
301
|
+
// the install-time effort sync (readGsdEffectiveEffortConfig) DOES merge the
|
|
302
|
+
// global file, so warning on it would be false for the channel users actually
|
|
303
|
+
// control via `effort sync`. Keep this list in lockstep with `_globalBaseCfg`
|
|
304
|
+
// below — the per-key canary in tests/config-loader.test.cjs fails first on
|
|
305
|
+
// drift in either direction.
|
|
306
|
+
const GLOBAL_DEFAULTS_RESOLUTION_KEYS = [
|
|
307
|
+
'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
|
|
308
|
+
'nyquist_validation', 'post_planning_gaps', 'parallelization', 'text_mode',
|
|
309
|
+
'resolve_model_ids', 'context_window', 'subagent_timeout', 'model_overrides',
|
|
310
|
+
'models', 'granularity', 'granularities', 'planning', 'dynamic_routing',
|
|
311
|
+
'effort', 'fast_mode', 'agent_skills', 'response_language', 'runtime',
|
|
312
|
+
'model_profile_overrides', 'model_policy',
|
|
313
|
+
];
|
|
314
|
+
// Module-level dedup keyed on the SORTED shadowed-key set: a later call with
|
|
315
|
+
// the same shadowed set stays quiet, while a config that grows a new shadowed
|
|
316
|
+
// key re-arms the warning. Stronger than _warnedUnknownConfigKeys (which keys
|
|
317
|
+
// on insertion order) — same discipline, order-independent key.
|
|
318
|
+
const _warnedShadowedGlobalKeys = new Set();
|
|
319
|
+
function _warnShadowedGlobalDefaults(globalDefaults, globalPath) {
|
|
320
|
+
const shadowed = GLOBAL_DEFAULTS_RESOLUTION_KEYS.filter(k => k !== 'effort' && Object.prototype.hasOwnProperty.call(globalDefaults, k));
|
|
321
|
+
// Branch D also honors the nested alias workflow.post_planning_gaps (the
|
|
322
|
+
// `?? globalDefaults['workflow']?.['post_planning_gaps']` fallback in
|
|
323
|
+
// _globalBaseCfg) — a global file using only the nested form is equally
|
|
324
|
+
// shadowed, so it reports under its dotted name.
|
|
325
|
+
if (!shadowed.includes('post_planning_gaps')) {
|
|
326
|
+
const wf = globalDefaults['workflow'];
|
|
327
|
+
if (wf && typeof wf === 'object' && !Array.isArray(wf) &&
|
|
328
|
+
Object.prototype.hasOwnProperty.call(wf, 'post_planning_gaps')) {
|
|
329
|
+
shadowed.push('workflow.post_planning_gaps');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (shadowed.length === 0)
|
|
333
|
+
return;
|
|
334
|
+
const dedupKey = shadowed.slice().sort().join(',');
|
|
335
|
+
if (_warnedShadowedGlobalKeys.has(dedupKey))
|
|
336
|
+
return;
|
|
337
|
+
_warnedShadowedGlobalKeys.add(dedupKey);
|
|
338
|
+
try {
|
|
339
|
+
process.stderr.write(`gsd-tools: warning: ${globalPath} sets ${shadowed.join(', ')} but a project config ` +
|
|
340
|
+
`takes precedence here — those global keys are ignored for model resolution. (#3532)\n`);
|
|
341
|
+
}
|
|
342
|
+
catch { /* stderr might be closed in some test harnesses */ }
|
|
293
343
|
}
|
|
294
344
|
// ─── FIX 2: Federated overlay helpers ────────────────────────────────────────
|
|
295
345
|
/**
|
|
@@ -751,10 +801,18 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
751
801
|
fast_mode: (parsed['fast_mode']) || null,
|
|
752
802
|
agent_skills: (parsed['agent_skills']) || {},
|
|
753
803
|
agent_skills_security: (parsed['agent_skills_security']) || null,
|
|
804
|
+
// #3587: phase_commit_docs.<phase-id> — a dynamic-key family shaped like
|
|
805
|
+
// agent_skills above (`{ "<phase-id>": boolean }`). Must be threaded here
|
|
806
|
+
// explicitly: `_baseConfig` is a hand-maintained allowlist, so a key that
|
|
807
|
+
// is only in config-schema.manifest.json's dynamicKeyPatterns (and not
|
|
808
|
+
// projected here) is silently dropped on read — the exact `features`-key
|
|
809
|
+
// failure mode this module's own A3 test guards against.
|
|
810
|
+
phase_commit_docs: (parsed['phase_commit_docs']) || {},
|
|
754
811
|
manager: (parsed['manager']) || {},
|
|
755
812
|
response_language: get('response_language') || null,
|
|
756
813
|
claude_md_path: get('claude_md_path') || null,
|
|
757
814
|
claude_md_assembly: (parsed['claude_md_assembly']) || null,
|
|
815
|
+
phase_id_convention: get('phase_id_convention') ?? null,
|
|
758
816
|
};
|
|
759
817
|
// ADR-857 phase 3b: federated config overlay
|
|
760
818
|
try {
|
|
@@ -776,6 +834,22 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
776
834
|
// A1 vs A2: disambiguate by whether a real workstream was requested.
|
|
777
835
|
// Fix 4: empty-string ws ('') resolves the root path → source:'root'.
|
|
778
836
|
const source = wsRequested ? 'workstream' : 'root';
|
|
837
|
+
// #3532 (10b): a parsed project config means Branch D never runs, so every
|
|
838
|
+
// key ~/.gsd/defaults.json sets that Branch D would honor is silently inert
|
|
839
|
+
// here. Observation only — one deduped stderr warning; precedence is
|
|
840
|
+
// untouched. Faults in the global file stay silent in this branch (the
|
|
841
|
+
// project config governs; the nearer file is the actionable one).
|
|
842
|
+
try {
|
|
843
|
+
const shadowHome = process.env['GSD_HOME'] || node_os_1.default.homedir();
|
|
844
|
+
const shadowPath = node_path_1.default.join(shadowHome, '.gsd', 'defaults.json');
|
|
845
|
+
const shadowRead = _readConfigFile(shadowPath);
|
|
846
|
+
if (shadowRead.kind === 'ok') {
|
|
847
|
+
_warnShadowedGlobalDefaults(shadowRead.data, shadowPath);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch {
|
|
851
|
+
// Observation only — never let the diagnostic perturb resolution.
|
|
852
|
+
}
|
|
779
853
|
// This config parsed — but a DIFFERENT file on the resolution path may not
|
|
780
854
|
// have. A workstream config that loads cleanly while the root config it
|
|
781
855
|
// inherits from is corrupt is still a degraded resolution: the root's
|
|
@@ -907,6 +981,8 @@ module.exports = {
|
|
|
907
981
|
_getNestedConfigDefault,
|
|
908
982
|
_deepMergeConfig,
|
|
909
983
|
_warnedUnknownConfigKeys,
|
|
984
|
+
_warnedShadowedGlobalKeys,
|
|
985
|
+
GLOBAL_DEFAULTS_RESOLUTION_KEYS,
|
|
910
986
|
_warnUnknownProfileOverrides,
|
|
911
987
|
_resetRuntimeWarningCacheForTests,
|
|
912
988
|
_warnedConfigKeys,
|
|
@@ -65,7 +65,12 @@ const SCHEMA_DEFAULTS = {
|
|
|
65
65
|
'context_window': 200000,
|
|
66
66
|
'executor.stall_detect_interval_minutes': 5,
|
|
67
67
|
'executor.stall_threshold_minutes': 10,
|
|
68
|
+
'planner.stall_detect_interval_minutes': 5,
|
|
69
|
+
'planner.stall_threshold_minutes': 10,
|
|
68
70
|
'git.create_tag': true,
|
|
71
|
+
// #1689: per-plan agent_hint executor routing — default-on. A no-op for plans
|
|
72
|
+
// without an agent_hint field, so existing dispatch is byte-identical.
|
|
73
|
+
'workflow.agent_hint_routing': true,
|
|
69
74
|
// Derived from the defaults manifest rather than restated, so the manifest
|
|
70
75
|
// stays the single source of truth for the smart-zone budget (#2630).
|
|
71
76
|
'workflow.smart_zone_tokens': CONFIG_DEFAULTS.smart_zone_tokens,
|
|
@@ -289,10 +294,19 @@ function buildNewProjectConfig(userChoices) {
|
|
|
289
294
|
const ud = userDefaults;
|
|
290
295
|
const ch = choices;
|
|
291
296
|
const hd = hardcoded;
|
|
297
|
+
// #2840: `runtime` is host-specific (written by the installer for whichever
|
|
298
|
+
// runtime's install ran last). On a machine with 2+ runtimes, it poisons every
|
|
299
|
+
// new project config — e.g. a Codex install's `runtime:"codex"` leaks into
|
|
300
|
+
// Claude Code projects, resolving agents to wrong model IDs. `resolve_model_ids`
|
|
301
|
+
// already has a per-install guard (#2297); `runtime` gets the same treatment
|
|
302
|
+
// by excluding it from the defaults spread. Projects detect the runtime from
|
|
303
|
+
// the install path / .gsd-runtime marker, not from a copied config key.
|
|
304
|
+
const safeDefaults = { ...userDefaults };
|
|
305
|
+
delete safeDefaults['runtime'];
|
|
292
306
|
// Three-level deep merge: hardcoded <- userDefaults <- choices
|
|
293
307
|
const config = {
|
|
294
308
|
...hardcoded,
|
|
295
|
-
...
|
|
309
|
+
...safeDefaults,
|
|
296
310
|
...choices,
|
|
297
311
|
git: {
|
|
298
312
|
...hd['git'],
|
|
@@ -641,7 +655,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
641
655
|
const val = value;
|
|
642
656
|
validateKnownConfigKeyPath(kp);
|
|
643
657
|
if (!isValidConfigKey(kp, cwd)) {
|
|
644
|
-
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
658
|
+
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>, phase_commit_docs.<phase-id>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
645
659
|
}
|
|
646
660
|
// Parse value (handle booleans, numbers, and JSON arrays/objects)
|
|
647
661
|
let parsedValue = val;
|
|
@@ -725,6 +739,12 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
725
739
|
error(`Invalid workflow.post_planning_gaps '${val}'. Must be a boolean (true or false).`);
|
|
726
740
|
}
|
|
727
741
|
}
|
|
742
|
+
// Per-plan executor routing via agent_hint frontmatter (#1689)
|
|
743
|
+
if (kp === 'workflow.agent_hint_routing') {
|
|
744
|
+
if (typeof parsedValue !== 'boolean') {
|
|
745
|
+
error(`Invalid workflow.agent_hint_routing '${val}'. Must be a boolean (true or false).`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
728
748
|
// #3086 — git.create_tag: boolean only
|
|
729
749
|
if (kp === 'git.create_tag') {
|
|
730
750
|
if (typeof parsedValue !== 'boolean') {
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Context Composer — shared budget-trim seam (ADR-1671 Decision item 2).
|
|
4
|
+
*
|
|
5
|
+
* Extracted from the five-step trim ladder in `prompt-budget.cts` by issue
|
|
6
|
+
* #2929 (epic #1671 Phase 2) so a second consumer — per-runtime artifact
|
|
7
|
+
* emission, in a later phase — can reuse the exact same trimming behavior
|
|
8
|
+
* without re-deriving it. `prompt-budget.applyBudget` is routed through this
|
|
9
|
+
* module in a follow-up commit, which asserts byte-identical output against
|
|
10
|
+
* a committed corpus; the algorithm here is a characterization of that
|
|
11
|
+
* existing behavior, not a redesign.
|
|
12
|
+
*
|
|
13
|
+
* THE COMPOSER DECIDES, THE CALLER RENDERS. `composeWithinBudget` never
|
|
14
|
+
* joins fragments and never returns a rendered string — it returns a plan of
|
|
15
|
+
* surviving fragments with their (possibly trimmed) content, plus metadata
|
|
16
|
+
* describing what happened. Rendering is prompt-specific: the review-prompt
|
|
17
|
+
* consumer assembles Markdown sections with blank-line separators; the
|
|
18
|
+
* per-runtime emission consumer renders differently. Keeping the decision
|
|
19
|
+
* (what survives, in what shape) separate from the render (how it is joined)
|
|
20
|
+
* is what lets both consumers share this module.
|
|
21
|
+
*
|
|
22
|
+
* The budget unit is injected via `measure(text) => number` rather than
|
|
23
|
+
* hardcoded to a token estimator: `prompt-budget` passes its chars/4
|
|
24
|
+
* estimator, per-runtime emission passes a byte counter. `charsPerUnit` in
|
|
25
|
+
* `ComposeOptions` is the inverse of `measure` (4 for chars/4, 1 for a byte
|
|
26
|
+
* counter) and is what lets `proportional-truncate`'s char-budget math stay
|
|
27
|
+
* unit-agnostic.
|
|
28
|
+
*
|
|
29
|
+
* ADR-1671's literal contract describes "priority + binary-search cutoff"
|
|
30
|
+
* trimming. That description cannot express this module's actual shape:
|
|
31
|
+
* head-shrink (structural, line-bounded) and proportional-truncate-with-floor
|
|
32
|
+
* (per-fragment guaranteed minimum, not a single global cutoff) are not
|
|
33
|
+
* binary-search cutoffs. `ShrinkStrategy` — not a numeric priority field — is
|
|
34
|
+
* the core abstraction here, and a `cutoff` strategy is expected to join this
|
|
35
|
+
* closed set once per-runtime emission needs binary-search behavior. Relatedly,
|
|
36
|
+
* `verbatim` and `drop` fragments are never reordered by importance: survival
|
|
37
|
+
* and drop order both follow DECLARATION order (the order fragments appear in
|
|
38
|
+
* `ComposeInput.fragments`), not a numeric priority.
|
|
39
|
+
*/
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.headShrink = headShrink;
|
|
42
|
+
exports.tailTruncate = tailTruncate;
|
|
43
|
+
exports.composeWithinBudget = composeWithinBudget;
|
|
44
|
+
/**
|
|
45
|
+
* Head-shrink a string to at most `maxLines` lines.
|
|
46
|
+
*
|
|
47
|
+
* Copied verbatim from `prompt-budget.cts` (formerly private) — do not
|
|
48
|
+
* "improve" it; a committed corpus depends on this exact behavior.
|
|
49
|
+
*/
|
|
50
|
+
function headShrink(text, maxLines) {
|
|
51
|
+
if (maxLines <= 0)
|
|
52
|
+
return '';
|
|
53
|
+
let idx = -1;
|
|
54
|
+
let seen = 0;
|
|
55
|
+
while (seen < maxLines) {
|
|
56
|
+
idx = text.indexOf('\n', idx + 1);
|
|
57
|
+
if (idx === -1)
|
|
58
|
+
return text;
|
|
59
|
+
seen += 1;
|
|
60
|
+
}
|
|
61
|
+
return text.slice(0, idx);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Tail-truncate a string to at most `maxChars` characters.
|
|
65
|
+
*
|
|
66
|
+
* Copied verbatim from `prompt-budget.cts` (formerly private) — do not
|
|
67
|
+
* "improve" it; a committed corpus depends on this exact behavior.
|
|
68
|
+
*/
|
|
69
|
+
function tailTruncate(text, maxChars) {
|
|
70
|
+
if (text.length <= maxChars)
|
|
71
|
+
return text;
|
|
72
|
+
return text.slice(0, maxChars);
|
|
73
|
+
}
|
|
74
|
+
const isPresent = (w) => w.required || Boolean(w.content);
|
|
75
|
+
const costOf = (w, measure) => isPresent(w) ? measure(w.wrapper) + measure(w.content) : 0;
|
|
76
|
+
const totalOf = (working, measure) => working.reduce((sum, w) => sum + costOf(w, measure), 0);
|
|
77
|
+
/**
|
|
78
|
+
* Trim a set of fragments to fit within `budget`, following the exact
|
|
79
|
+
* five-step ladder characterized from `prompt-budget.applyBudget`:
|
|
80
|
+
* head-shrink, then group proportional-truncate, then declaration-order
|
|
81
|
+
* drops — each pass individually guarded by the running total against the
|
|
82
|
+
* content budget, walked once in declaration order.
|
|
83
|
+
*
|
|
84
|
+
* Returns a plan (surviving fragment content + metadata); it never renders.
|
|
85
|
+
*/
|
|
86
|
+
function composeWithinBudget(input) {
|
|
87
|
+
const { fragments, budget, measure, options = {} } = input;
|
|
88
|
+
const { safetyMarginPct = 0, reserve = 0, charsPerUnit = 1, minimumFor } = options;
|
|
89
|
+
const effectiveBudget = Math.floor(budget * (1 - safetyMarginPct / 100));
|
|
90
|
+
const seenIds = new Set();
|
|
91
|
+
const working = [];
|
|
92
|
+
let sawNonIsolate = false;
|
|
93
|
+
let isolatePrefix = '';
|
|
94
|
+
for (const f of fragments) {
|
|
95
|
+
if (seenIds.has(f.id)) {
|
|
96
|
+
throw new TypeError(`composeWithinBudget: duplicate fragment id "${f.id}"`);
|
|
97
|
+
}
|
|
98
|
+
seenIds.add(f.id);
|
|
99
|
+
const isolate = f.isolate === true;
|
|
100
|
+
if (isolate) {
|
|
101
|
+
if (sawNonIsolate) {
|
|
102
|
+
throw new TypeError(`composeWithinBudget: isolate fragment "${f.id}" declared after a non-isolate fragment; ` +
|
|
103
|
+
'isolate fragments must form a leading, contiguous prefix of ComposeInput.fragments');
|
|
104
|
+
}
|
|
105
|
+
isolatePrefix += (f.wrapper ?? '') + f.content;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
sawNonIsolate = true;
|
|
109
|
+
}
|
|
110
|
+
working.push({
|
|
111
|
+
id: f.id,
|
|
112
|
+
wrapper: f.wrapper ?? '',
|
|
113
|
+
strategy: f.strategy,
|
|
114
|
+
required: f.required === true,
|
|
115
|
+
group: f.group,
|
|
116
|
+
originalLength: f.content.length,
|
|
117
|
+
flexReserve: f.flexReserve ?? 0,
|
|
118
|
+
isolate,
|
|
119
|
+
content: f.content,
|
|
120
|
+
shrunk: false,
|
|
121
|
+
truncated: false,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Minimum-set pre-check. Wrappers are deliberately NOT counted here, and
|
|
125
|
+
// the reserve is deliberately EXCLUDED — this checks the floor, not the
|
|
126
|
+
// steady-state budget.
|
|
127
|
+
if (minimumFor) {
|
|
128
|
+
const minTotal = fragments.reduce((sum, f) => sum + measure(minimumFor(f) ?? ''), 0);
|
|
129
|
+
if (minTotal > effectiveBudget) {
|
|
130
|
+
return {
|
|
131
|
+
fragments: [],
|
|
132
|
+
metadata: {
|
|
133
|
+
budget,
|
|
134
|
+
effectiveBudget,
|
|
135
|
+
contentBudget: effectiveBudget,
|
|
136
|
+
underPressure: false,
|
|
137
|
+
omitted: [],
|
|
138
|
+
shrunk: [],
|
|
139
|
+
floored: [],
|
|
140
|
+
truncationPct: 0,
|
|
141
|
+
hardFailed: true,
|
|
142
|
+
hardFailReason: 'minimum-set',
|
|
143
|
+
isolatePrefix,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const total = () => totalOf(working, measure);
|
|
149
|
+
const baseline = total();
|
|
150
|
+
const underPressure = baseline > effectiveBudget;
|
|
151
|
+
// The reserve is deducted ONLY when under pressure — deducting it
|
|
152
|
+
// unconditionally drops sections `reserve` units early
|
|
153
|
+
// (CONTEXT.md LEARNING.prompt-budget.boundary-gap, PR #3708).
|
|
154
|
+
const contentBudget = underPressure ? effectiveBudget - reserve : effectiveBudget;
|
|
155
|
+
const omitted = [];
|
|
156
|
+
const shrunk = [];
|
|
157
|
+
const floored = [];
|
|
158
|
+
const truncation = { pct: 0 };
|
|
159
|
+
const processedGroups = new Set();
|
|
160
|
+
for (const w of working) {
|
|
161
|
+
// Isolate fragments are the byte-stable canonical prefix (ADR-1671):
|
|
162
|
+
// never trimmed, shrunk, or dropped, regardless of declared strategy.
|
|
163
|
+
// They still count toward the budget via total()/costOf above.
|
|
164
|
+
if (w.isolate) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
switch (w.strategy.kind) {
|
|
168
|
+
case 'verbatim': {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
case 'head-shrink': {
|
|
172
|
+
if (total() > contentBudget && isPresent(w)) {
|
|
173
|
+
const next = headShrink(w.content, w.strategy.maxLines);
|
|
174
|
+
if (next !== w.content) {
|
|
175
|
+
if (w.flexReserve > 0 && measure(next) < w.flexReserve) {
|
|
176
|
+
// Shrinking would undershoot the floor — leave untouched.
|
|
177
|
+
floored.push(w.id);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
w.content = next;
|
|
181
|
+
w.shrunk = true;
|
|
182
|
+
shrunk.push(w.id);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
case 'proportional-truncate': {
|
|
189
|
+
const groupKey = w.group ?? w.id;
|
|
190
|
+
if (processedGroups.has(groupKey))
|
|
191
|
+
continue;
|
|
192
|
+
processedGroups.add(groupKey);
|
|
193
|
+
if (total() > contentBudget) {
|
|
194
|
+
const members = working.filter((m) => m.strategy.kind === 'proportional-truncate' && (m.group ?? m.id) === groupKey && !m.isolate);
|
|
195
|
+
const memberContentTotal = members.reduce((sum, m) => sum + measure(m.content), 0);
|
|
196
|
+
const overhead = total() - memberContentTotal;
|
|
197
|
+
const groupBudget = contentBudget - overhead;
|
|
198
|
+
const groupContentTotal = memberContentTotal;
|
|
199
|
+
if (groupBudget > 0 && groupBudget < groupContentTotal) {
|
|
200
|
+
const originalChars = members.reduce((sum, m) => sum + m.originalLength, 0);
|
|
201
|
+
const charsBudget = groupBudget * charsPerUnit;
|
|
202
|
+
for (const m of members) {
|
|
203
|
+
const floorChars = m.strategy.kind === 'proportional-truncate' ? m.strategy.floorChars : 0;
|
|
204
|
+
const share = originalChars > 0 ? Math.floor((m.originalLength / originalChars) * charsBudget) : 0;
|
|
205
|
+
const charsForReserve = m.flexReserve * charsPerUnit;
|
|
206
|
+
const capWithoutReserve = Math.max(share, floorChars);
|
|
207
|
+
const maxChars = Math.max(capWithoutReserve, charsForReserve);
|
|
208
|
+
const beforeTruncate = m.content;
|
|
209
|
+
const next = tailTruncate(m.content, maxChars);
|
|
210
|
+
if (next !== beforeTruncate) {
|
|
211
|
+
m.content = next;
|
|
212
|
+
m.truncated = true;
|
|
213
|
+
}
|
|
214
|
+
// flexReserve raised the cap above what the unreserved cap
|
|
215
|
+
// would have allowed, and that raised cap actually mattered
|
|
216
|
+
// (the unreserved cap would have cut into the content) —
|
|
217
|
+
// this holds whether the reserve prevented a trim outright
|
|
218
|
+
// (next === beforeTruncate) or merely shrank the trim below
|
|
219
|
+
// what the unreserved cap would have produced. Either way a
|
|
220
|
+
// cut was prevented, so it belongs in `floored`.
|
|
221
|
+
if (m.flexReserve > 0 &&
|
|
222
|
+
maxChars > capWithoutReserve &&
|
|
223
|
+
capWithoutReserve < beforeTruncate.length &&
|
|
224
|
+
!floored.includes(m.id)) {
|
|
225
|
+
floored.push(m.id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const newChars = members.reduce((sum, m) => sum + m.content.length, 0);
|
|
229
|
+
if (originalChars > 0) {
|
|
230
|
+
truncation.pct = ((originalChars - newChars) / originalChars) * 100;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
case 'drop': {
|
|
237
|
+
if (total() > contentBudget && isPresent(w) && Boolean(w.content)) {
|
|
238
|
+
// Dropping always takes a fragment to 0, and 0 is below any
|
|
239
|
+
// positive floor — so ANY flexReserve > 0 disables drop entirely
|
|
240
|
+
// for this fragment, not just when its current size already sits
|
|
241
|
+
// at/under the floor.
|
|
242
|
+
if (w.flexReserve > 0) {
|
|
243
|
+
floored.push(w.id);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
w.content = '';
|
|
247
|
+
omitted.push(w.id);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const composedFragments = working.map((w) => ({
|
|
255
|
+
id: w.id,
|
|
256
|
+
content: w.content,
|
|
257
|
+
wrapper: w.wrapper,
|
|
258
|
+
present: isPresent(w),
|
|
259
|
+
shrunk: w.shrunk,
|
|
260
|
+
truncated: w.truncated,
|
|
261
|
+
}));
|
|
262
|
+
return {
|
|
263
|
+
fragments: composedFragments,
|
|
264
|
+
metadata: {
|
|
265
|
+
budget,
|
|
266
|
+
effectiveBudget,
|
|
267
|
+
contentBudget,
|
|
268
|
+
underPressure,
|
|
269
|
+
omitted,
|
|
270
|
+
shrunk,
|
|
271
|
+
floored,
|
|
272
|
+
truncationPct: truncation.pct,
|
|
273
|
+
hardFailed: false,
|
|
274
|
+
hardFailReason: null,
|
|
275
|
+
isolatePrefix,
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|