session-orchestrator 5.2.0 → 5.4.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/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +9 -30
- package/CHANGELOG.md +206 -0
- package/README.md +13 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +22 -7
- package/docs/ci-setup.md +57 -4
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +12 -10
- package/docs/events-schema.md +65 -15
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +84 -17
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +154 -12
- package/docs/session-config-template.md +41 -3
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +87 -6
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +101 -11
- package/hooks/on-stop.mjs +170 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +202 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
- package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +792 -74
- package/hooks/subagent-telemetry.mjs +111 -38
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +3 -3
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/navigator.md +12 -0
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +7 -2
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +6 -2
- package/rules/opt-in-stack/backend-data.md +10 -2
- package/rules/opt-in-stack/backend.md +10 -4
- package/rules/opt-in-stack/frontend.md +7 -2
- package/rules/opt-in-stack/security-web.md +10 -4
- package/rules/opt-in-stack/swift.md +6 -2
- package/scripts/apply-session-learnings.mjs +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +70 -53
- package/scripts/backfill-abandoned-sessions.mjs +118 -16
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +371 -31
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -0
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +18 -2
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +19 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +163 -0
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +85 -10
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +545 -7
- package/scripts/lib/fleet-protocol.mjs +270 -0
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +40 -5
- package/scripts/lib/gates/gate-full.mjs +35 -5
- package/scripts/lib/gates/gate-helpers.mjs +336 -23
- package/scripts/lib/gates/gate-incremental.mjs +49 -23
- package/scripts/lib/gates/gate-per-file.mjs +42 -8
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +208 -8
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +316 -0
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +950 -0
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +152 -33
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +141 -74
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +3 -4
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +56 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +118 -4
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +210 -84
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +306 -0
- package/scripts/release.mjs +40 -8
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +357 -197
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +16 -4
- package/scripts/validate-plugin.mjs +19 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +81 -47
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +47 -18
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +3 -3
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/learning-patterns.md +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +32 -60
- package/skills/session-plan/SKILL.md +17 -8
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +9 -5
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +3 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
|
@@ -47,6 +47,16 @@
|
|
|
47
47
|
* tier-gate conditionals `applyGates` already implements (the `context`
|
|
48
48
|
* param below is the exact mechanism rule-loader exposes for this).
|
|
49
49
|
*
|
|
50
|
+
* #1449: that membership now matches what Claude Code actually loads. The
|
|
51
|
+
* loader used to skip a leading provenance comment before `---`, so a rule
|
|
52
|
+
* Claude Code loads always-on (it reads frontmatter only from line 1) counted
|
|
53
|
+
* here as path-scoped, and every consumer's always-on total was short by the
|
|
54
|
+
* vendored rules carrying that header. Such a file now counts as always-on —
|
|
55
|
+
* its displaced frontmatter as body bytes, since that is how Claude Code
|
|
56
|
+
* delivers it — and is listed in `defects` and on its own banner line. The
|
|
57
|
+
* list is report-only: it is not an `overBudget` term, so a defect never turns
|
|
58
|
+
* a budget verdict red.
|
|
59
|
+
*
|
|
50
60
|
* Cross-references:
|
|
51
61
|
* - "2026-06-20 instruction-budget audit" (#668 / #687; archived in the private Meta-Vault)
|
|
52
62
|
* - scripts/lib/rule-loader.mjs (always-on classification + tier-gate SSOT)
|
|
@@ -78,7 +88,12 @@
|
|
|
78
88
|
|
|
79
89
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
80
90
|
import { basename, join } from 'node:path';
|
|
81
|
-
import {
|
|
91
|
+
import { scanEventsBackwards } from './events.mjs';
|
|
92
|
+
import {
|
|
93
|
+
FRONTMATTER_NOT_AT_TOP,
|
|
94
|
+
loadApplicableRules,
|
|
95
|
+
parseGlobsFrontmatter,
|
|
96
|
+
} from './rule-loader.mjs';
|
|
82
97
|
|
|
83
98
|
/** Default directive ceiling (operator-chosen growth ratchet just above the ~457 baseline). */
|
|
84
99
|
export const DEFAULT_CEILING = 480;
|
|
@@ -443,6 +458,35 @@ export const DEFAULT_GENERATED_BYTE_CEILING = 95000;
|
|
|
443
458
|
*/
|
|
444
459
|
export const DEFAULT_PATH_SCOPED_BYTE_CEILING = 124000;
|
|
445
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Share of {@link DEFAULT_PATH_SCOPED_BYTE_CEILING} at which the banner warns
|
|
463
|
+
* BEFORE the writer refuses (#1419).
|
|
464
|
+
*
|
|
465
|
+
* The defect #1419 records is not the ceiling but its TIMING: it becomes
|
|
466
|
+
* visible only inside `writeApprovedRules`, after the operator has already
|
|
467
|
+
* decided. Measured on that case (2026-09-20, /reconcile run): 117,973 of
|
|
468
|
+
* 124,000 B = 95.1 % — ~6 kB of headroom holding back `capped: 91`
|
|
469
|
+
* candidates, and `overPathScopedBudget` (a HARD `bytes > ceiling` test) did
|
|
470
|
+
* not fire, so nothing warned.
|
|
471
|
+
*
|
|
472
|
+
* 90 % is the distance of ~4 materialised rules: 10 % of 124,000 B = 12,400 B
|
|
473
|
+
* against the ~1.5 kB a generated rule file occupied on the #1419 tree — i.e.
|
|
474
|
+
* roughly four more accepted learnings until the hard break. A fixed fraction,
|
|
475
|
+
* deliberately not a per-repo calibration: it cannot know how large THIS
|
|
476
|
+
* repo's next rule is, and a second calibrated number would need its own
|
|
477
|
+
* falsification record.
|
|
478
|
+
*
|
|
479
|
+
* BV-004 ceiling + revisit trigger (HR-105): measure this line's firing rate
|
|
480
|
+
* after ~20 sessions from the probe records in
|
|
481
|
+
* `.orchestrator/metrics/events.jsonl`
|
|
482
|
+
* (`jq -c 'select(.event=="orchestrator.probes.completed")
|
|
483
|
+
* | .probes[] | select(.id=="instruction-budget")' …` — count `ran-warn`
|
|
484
|
+
* against the record total).
|
|
485
|
+
* Above ~10 % the instrument is RE-AIMED per HR-101 — neither lowered nor
|
|
486
|
+
* silenced; below it, leave it alone.
|
|
487
|
+
*/
|
|
488
|
+
export const PATH_SCOPED_NEAR_THRESHOLD = 0.9;
|
|
489
|
+
|
|
446
490
|
/**
|
|
447
491
|
* Read the `instruction-budget:` nested block from the `## Session Config`
|
|
448
492
|
* section of CLAUDE.md (or AGENTS.md) at `repoRoot`. Synchronous + never throws.
|
|
@@ -1025,12 +1069,18 @@ function measureRuleCorpora(rulesDir) {
|
|
|
1025
1069
|
* bySurface: { coordinator: number, wave: number, always: number,
|
|
1026
1070
|
* generated: {bytes: number, files: number},
|
|
1027
1071
|
* pathScoped: {bytes: number, files: number} },
|
|
1072
|
+
* defects: string[],
|
|
1028
1073
|
* }}
|
|
1029
|
-
* perFile is sorted DESC by count.
|
|
1074
|
+
* perFile is sorted DESC by count. `defects` (#1449) lists, sorted by name,
|
|
1075
|
+
* the rule files whose frontmatter does not start on line 1 — Claude Code
|
|
1076
|
+
* ignores it, so they load always-on and are counted as such. It is
|
|
1077
|
+
* independent of `context` (such a file has no tier to gate on) and never
|
|
1078
|
+
* feeds `overBudget`. On missing/unreadable dir →
|
|
1030
1079
|
* { totalDirectives: 0, totalBytes: 0, perFile: [], ceiling, byteCeiling,
|
|
1031
1080
|
* overDirectiveBudget: false, overByteBudget: false, overBudget: false,
|
|
1032
1081
|
* severity: 'ok', bySurface: { coordinator: 0, wave: 0, always: 0,
|
|
1033
|
-
* generated: {bytes:0,files:0}, pathScoped: {bytes:0,files:0} }
|
|
1082
|
+
* generated: {bytes:0,files:0}, pathScoped: {bytes:0,files:0} },
|
|
1083
|
+
* defects: [] }.
|
|
1034
1084
|
*
|
|
1035
1085
|
* #931a verdict rule — `overBudget` is the OR of the two axes
|
|
1036
1086
|
* (`overDirectiveBudget || overByteBudget`), NOT a per-axis severity split:
|
|
@@ -1135,6 +1185,7 @@ export function computeInstructionBudget(opts = {}) {
|
|
|
1135
1185
|
generated: { bytes: 0, files: 0 },
|
|
1136
1186
|
pathScoped: { bytes: 0, files: 0 },
|
|
1137
1187
|
},
|
|
1188
|
+
defects: [],
|
|
1138
1189
|
};
|
|
1139
1190
|
|
|
1140
1191
|
let allEntries;
|
|
@@ -1226,6 +1277,13 @@ export function computeInstructionBudget(opts = {}) {
|
|
|
1226
1277
|
const overPathScopedBudget = bySurface.pathScoped.bytes > pathScopedByteCeiling;
|
|
1227
1278
|
const overBudget = overDirectiveBudget || overByteBudget || overGeneratedBudget || overPathScopedBudget;
|
|
1228
1279
|
|
|
1280
|
+
// #1449: read off the untiered list — a defective file carries no meta, so
|
|
1281
|
+
// no tier gate drops it and `alwaysOnAll` holds every one of them.
|
|
1282
|
+
const defects = alwaysOnAll
|
|
1283
|
+
.filter((e) => e.defect === FRONTMATTER_NOT_AT_TOP)
|
|
1284
|
+
.map((e) => basename(e.path))
|
|
1285
|
+
.sort((a, b) => a.localeCompare(b));
|
|
1286
|
+
|
|
1229
1287
|
return {
|
|
1230
1288
|
totalDirectives,
|
|
1231
1289
|
totalBytes,
|
|
@@ -1241,9 +1299,102 @@ export function computeInstructionBudget(opts = {}) {
|
|
|
1241
1299
|
overBudget,
|
|
1242
1300
|
severity: overBudget ? 'warn' : 'ok',
|
|
1243
1301
|
bySurface,
|
|
1302
|
+
defects,
|
|
1244
1303
|
};
|
|
1245
1304
|
}
|
|
1246
1305
|
|
|
1306
|
+
/** Ledger event the backlog number below is read from (`reconcile/engine.mjs`). */
|
|
1307
|
+
const RECONCILE_EVENT = 'orchestrator.reconcile.completed';
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* Wall-clock budget for the backwards ledger walk. Deliberately small: this
|
|
1311
|
+
* runs inside a session-start probe whose own budget is 2 s, and the number it
|
|
1312
|
+
* fetches is DECORATION on a line that is already actionable without it.
|
|
1313
|
+
*/
|
|
1314
|
+
const RECONCILE_SCAN_BUDGET_MS = 150;
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* `capped` from the most recent `orchestrator.reconcile.completed` record —
|
|
1318
|
+
* how many rule-eligible learnings the last /reconcile run held back.
|
|
1319
|
+
*
|
|
1320
|
+
* Three-state on purpose (`feedback_missing_measurement_looks_like_zero`):
|
|
1321
|
+
* `null` means NOT MEASURED (no ledger, no such record, unreadable source, or
|
|
1322
|
+
* the walk ran out of budget), which the caller must render as ABSENCE — never
|
|
1323
|
+
* as `0`, which is a measured "nothing waiting".
|
|
1324
|
+
*
|
|
1325
|
+
* @param {string|undefined} repoRoot
|
|
1326
|
+
* @returns {number|null}
|
|
1327
|
+
*/
|
|
1328
|
+
function readLastReconcileCapped(repoRoot) {
|
|
1329
|
+
if (typeof repoRoot !== 'string' || repoRoot === '') return null;
|
|
1330
|
+
const filePath = join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl');
|
|
1331
|
+
let capped = null;
|
|
1332
|
+
let scan;
|
|
1333
|
+
try {
|
|
1334
|
+
scan = scanEventsBackwards({
|
|
1335
|
+
filePath,
|
|
1336
|
+
budgetMs: RECONCILE_SCAN_BUDGET_MS,
|
|
1337
|
+
// Cheap raw-line pre-filter before JSON.parse — same reason as
|
|
1338
|
+
// `maintenance-due-banner.mjs`: parsing every line of a multi-MB ledger
|
|
1339
|
+
// to answer one question is the cost this option exists to avoid.
|
|
1340
|
+
filter: RECONCILE_EVENT,
|
|
1341
|
+
onRecord: (rec) => {
|
|
1342
|
+
if (rec?.event !== RECONCILE_EVENT) return false;
|
|
1343
|
+
capped = Number.isFinite(rec.capped) ? rec.capped : null;
|
|
1344
|
+
return true; // newest record wins; stop the walk
|
|
1345
|
+
},
|
|
1346
|
+
});
|
|
1347
|
+
} catch {
|
|
1348
|
+
return null; // never throw out of the banner wrapper
|
|
1349
|
+
}
|
|
1350
|
+
// A walk that did not finish has not proven anything about the backlog.
|
|
1351
|
+
if (scan.truncated || scan.unreadable.length > 0) return null;
|
|
1352
|
+
return capped;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* The #1419 early-warning line: the path-scoped ceiling is close, not broken.
|
|
1357
|
+
*
|
|
1358
|
+
* Reports the numbers the RULE judged (HR-106) — the same `pathScoped.bytes`
|
|
1359
|
+
* and ceiling `writeApprovedRules` will compare later — plus, when the ledger
|
|
1360
|
+
* carries one, how many learnings the last /reconcile run already held back.
|
|
1361
|
+
* The percentage is formatted locale-free so the same corpus renders the same
|
|
1362
|
+
* string on every host.
|
|
1363
|
+
*
|
|
1364
|
+
* @param {ReturnType<typeof computeInstructionBudget>} budget
|
|
1365
|
+
* @param {string|undefined} repoRoot
|
|
1366
|
+
* @returns {string}
|
|
1367
|
+
*/
|
|
1368
|
+
function formatNearPathScopedLine(budget, repoRoot) {
|
|
1369
|
+
const used = budget.bySurface.pathScoped.bytes;
|
|
1370
|
+
const ceiling = budget.pathScopedByteCeiling;
|
|
1371
|
+
const pct = ((used / ceiling) * 100).toFixed(1);
|
|
1372
|
+
const capped = readLastReconcileCapped(repoRoot);
|
|
1373
|
+
// Only a POSITIVE measured backlog is worth a clause; `0` and `null` both
|
|
1374
|
+
// add nothing the operator can act on, and printing `0` for `null` would be
|
|
1375
|
+
// the false-negative this helper's three-state contract exists to prevent.
|
|
1376
|
+
const backlog = typeof capped === 'number' && capped > 0 ? ` — ${capped} Learnings warten` : '';
|
|
1377
|
+
return (
|
|
1378
|
+
`path-scoped Regel-Decke zu ${pct} % belegt (${used}/${ceiling} B)${backlog} — ` +
|
|
1379
|
+
'/reconcile wird Learnings zurueckhalten'
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* The #1449 defect line: rule files whose frontmatter Claude Code never reads.
|
|
1385
|
+
* Names every file — the fix is per file (move the text above `---` below the
|
|
1386
|
+
* closing fence), so a count alone would send the operator hunting.
|
|
1387
|
+
*
|
|
1388
|
+
* @param {string[]} defects - basenames from `computeInstructionBudget().defects`
|
|
1389
|
+
* @returns {string}
|
|
1390
|
+
*/
|
|
1391
|
+
function formatFrontmatterDefectLine(defects) {
|
|
1392
|
+
return (
|
|
1393
|
+
`⚠ ${defects.length} rule(s) carry frontmatter that Claude Code ignores ` +
|
|
1394
|
+
`(text before the opening ---): ${defects.join(', ')} — they load always-on.`
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1247
1398
|
/**
|
|
1248
1399
|
* Banner wrapper — session-start Phase 4 convention.
|
|
1249
1400
|
*
|
|
@@ -1260,7 +1411,9 @@ export function computeInstructionBudget(opts = {}) {
|
|
|
1260
1411
|
*
|
|
1261
1412
|
* The message names WHICH axis breached (#931a) — a banner that only said
|
|
1262
1413
|
* "over budget" would leave the operator guessing whether to prune bullets or
|
|
1263
|
-
* prose. It stays
|
|
1414
|
+
* prose. It stays short because it renders at every session start: the breach
|
|
1415
|
+
* block is three lines, plus one line per extra finding (near-threshold,
|
|
1416
|
+
* frontmatter defect).
|
|
1264
1417
|
*
|
|
1265
1418
|
* @param {object} [opts] forwarded to computeInstructionBudget.
|
|
1266
1419
|
* @param {string} [opts.repoRoot] project root for the config read.
|
|
@@ -1268,10 +1421,17 @@ export function computeInstructionBudget(opts = {}) {
|
|
|
1268
1421
|
* @param {number} [opts.byteCeiling] explicit byte-ceiling override (wins over config).
|
|
1269
1422
|
* @param {number} [opts.pathScopedByteCeiling] explicit path-scoped-ceiling override.
|
|
1270
1423
|
* @returns {{ severity: 'warn', message: string } | null}
|
|
1271
|
-
* null when disabled / off / every axis
|
|
1272
|
-
* read failure. Since #1316 each of the four axes — directives, bytes,
|
|
1424
|
+
* null when disabled / off / every axis comfortably under its ceiling OR on
|
|
1425
|
+
* any read failure. Since #1316 each of the four axes — directives, bytes,
|
|
1273
1426
|
* generated, path-scoped — raises the banner on its own (see
|
|
1274
|
-
* DEFAULT_PATH_SCOPED_BYTE_CEILING).
|
|
1427
|
+
* DEFAULT_PATH_SCOPED_BYTE_CEILING). Since #1419 the path-scoped axis ALSO
|
|
1428
|
+
* raises it at {@link PATH_SCOPED_NEAR_THRESHOLD} of its ceiling, before the
|
|
1429
|
+
* breach: that band used to be silent, and the ceiling then surfaced only
|
|
1430
|
+
* inside `writeApprovedRules` — after the operator had approved rules it
|
|
1431
|
+
* refused to write. Since #1449 a rule whose frontmatter is not on line 1
|
|
1432
|
+
* raises it too (one line naming the files), whether or not any axis is
|
|
1433
|
+
* close. The RETURN SHAPE is unchanged (`{severity:'warn', message}` |
|
|
1434
|
+
* null), so `session-start-probes.mjs` needs no new vocabulary.
|
|
1275
1435
|
*/
|
|
1276
1436
|
export function checkInstructionBudget(opts = {}) {
|
|
1277
1437
|
let cfg;
|
|
@@ -1331,7 +1491,42 @@ export function checkInstructionBudget(opts = {}) {
|
|
|
1331
1491
|
return null; // never throw out of the banner wrapper
|
|
1332
1492
|
}
|
|
1333
1493
|
|
|
1334
|
-
if (!budget
|
|
1494
|
+
if (!budget) return null;
|
|
1495
|
+
|
|
1496
|
+
// #1419 near-threshold stage. Strictly BELOW the hard test (a breach is
|
|
1497
|
+
// reported as a breach, never twice), and computed before the early return
|
|
1498
|
+
// so a corpus at 90–100 % of the path-scoped ceiling stops being silent —
|
|
1499
|
+
// that silence is the defect: the ceiling used to surface only inside
|
|
1500
|
+
// `writeApprovedRules`, after the operator had already approved rules.
|
|
1501
|
+
const nearPathScoped =
|
|
1502
|
+
!budget.overPathScopedBudget &&
|
|
1503
|
+
budget.pathScopedByteCeiling > 0 &&
|
|
1504
|
+
budget.bySurface.pathScoped.bytes >= budget.pathScopedByteCeiling * PATH_SCOPED_NEAR_THRESHOLD;
|
|
1505
|
+
|
|
1506
|
+
// #1449 — reported on its own line, never folded into `overBudget`.
|
|
1507
|
+
const defectLine =
|
|
1508
|
+
budget.defects.length > 0 ? formatFrontmatterDefectLine(budget.defects) : null;
|
|
1509
|
+
|
|
1510
|
+
if (!budget.overBudget && !nearPathScoped && !defectLine) return null;
|
|
1511
|
+
|
|
1512
|
+
const nearLine = nearPathScoped ? formatNearPathScopedLine(budget, opts.repoRoot) : null;
|
|
1513
|
+
|
|
1514
|
+
// Nothing breached: one block per finding — the near-threshold line carries
|
|
1515
|
+
// the same remedy as the breach clause below (consolidate — never raise).
|
|
1516
|
+
if (!budget.overBudget) {
|
|
1517
|
+
return {
|
|
1518
|
+
severity: 'warn',
|
|
1519
|
+
message: [
|
|
1520
|
+
...(nearLine
|
|
1521
|
+
? [
|
|
1522
|
+
`⚠ ${nearLine}.`,
|
|
1523
|
+
' Consolidate generated rules per docs/rule-authoring.md § Consolidated rules; never raise the ceiling.',
|
|
1524
|
+
]
|
|
1525
|
+
: []),
|
|
1526
|
+
...(defectLine ? [defectLine] : []),
|
|
1527
|
+
].join('\n'),
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1335
1530
|
|
|
1336
1531
|
// Name only the breached axes — listing a healthy axis would pad the line
|
|
1337
1532
|
// without telling the operator anything they must act on.
|
|
@@ -1377,10 +1572,15 @@ export function checkInstructionBudget(opts = {}) {
|
|
|
1377
1572
|
.map((f) => `${f.file} (${f.count} dir, ${f.bytes} B)`)
|
|
1378
1573
|
.join(', ');
|
|
1379
1574
|
|
|
1575
|
+
// The near-threshold line rides ALONGSIDE the breach line rather than inside
|
|
1576
|
+
// `axes`: the path-scoped axis is not over, and folding a healthy-but-close
|
|
1577
|
+
// axis into a sentence that says "over" would misreport which ceiling broke.
|
|
1380
1578
|
const message = [
|
|
1381
1579
|
`⚠ Instruction budget over — ${axes.join(' · ')} across ${budget.perFile.length} always-on rules.`,
|
|
1580
|
+
...(nearLine ? [` ${nearLine}.`] : []),
|
|
1382
1581
|
` Top files: ${top}`,
|
|
1383
1582
|
' See the instruction-budget audit (#687; archived in the private Meta-Vault) for the prune/demote list.',
|
|
1583
|
+
...(defectLine ? [defectLine] : []),
|
|
1384
1584
|
].join('\n');
|
|
1385
1585
|
|
|
1386
1586
|
return { severity: 'warn', message };
|
package/scripts/lib/io.mjs
CHANGED
|
@@ -711,12 +711,33 @@ export function emitRewrite(updatedInput) {
|
|
|
711
711
|
process.exit(0);
|
|
712
712
|
};
|
|
713
713
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
714
|
+
// `updatedInput` is a MAP in the bundle schema, so an array is as wrong as a
|
|
715
|
+
// string — and an array would serialize into a shape the harness cannot use.
|
|
716
|
+
//
|
|
717
|
+
// PLAIN object only, and `typeof === 'object'` is not that test. An object
|
|
718
|
+
// WRAPPER passes it and then serializes to something else entirely:
|
|
719
|
+
// `new Date()` and `new String('x')` become JSON STRINGS (their `toJSON` /
|
|
720
|
+
// primitive form), `new Map([...])` becomes `{}` — which looks valid and,
|
|
721
|
+
// per the doc above, deletes every field of the tool input. Each of the three
|
|
722
|
+
// reaches the harness as a schema-invalid `updatedInput`, and the bundle
|
|
723
|
+
// turns that into `behavior: "deny"`: on the `AskUserQuestion` path the
|
|
724
|
+
// operator's question is destroyed to save a rewrite. A prototype of
|
|
725
|
+
// `Object.prototype` (a literal) or `null` (`Object.create(null)`) is the
|
|
726
|
+
// whole admissible set; everything else takes the same no-op-plus-stderr
|
|
727
|
+
// contract as `null`, an array or a string.
|
|
728
|
+
const proto =
|
|
729
|
+
updatedInput !== null && typeof updatedInput === 'object'
|
|
730
|
+
? Object.getPrototypeOf(updatedInput)
|
|
731
|
+
: undefined;
|
|
732
|
+
const isPlainObject =
|
|
733
|
+
!Array.isArray(updatedInput) && (proto === Object.prototype || proto === null);
|
|
734
|
+
if (!isPlainObject) {
|
|
735
|
+
let described;
|
|
736
|
+
if (Array.isArray(updatedInput)) described = 'an array';
|
|
737
|
+
else if (updatedInput === null) described = 'null';
|
|
738
|
+
else if (typeof updatedInput !== 'object') described = String(typeof updatedInput);
|
|
739
|
+
else described = `a non-plain object (${proto?.constructor?.name ?? 'unknown prototype'})`;
|
|
740
|
+
bail(`was called with ${described}, not a tool-input object`);
|
|
720
741
|
}
|
|
721
742
|
|
|
722
743
|
let line;
|
|
@@ -731,11 +752,24 @@ export function emitRewrite(updatedInput) {
|
|
|
731
752
|
},
|
|
732
753
|
});
|
|
733
754
|
} catch (err) {
|
|
734
|
-
// A cycle or a BigInt in the caller's object.
|
|
735
|
-
// undefined (a toJSON that yields undefined) — caught by the same guard.
|
|
755
|
+
// A cycle or a BigInt in the caller's object.
|
|
736
756
|
bail(`could not serialize the tool input (${err?.message ?? String(err)})`);
|
|
737
757
|
}
|
|
738
758
|
|
|
759
|
+
// `JSON.stringify` returns `undefined` only for a TOP-LEVEL value it drops —
|
|
760
|
+
// and `updatedInput` sits NESTED inside the envelope literal above, so at this
|
|
761
|
+
// call site it never can. Measured 2026-09-18 with
|
|
762
|
+
// `{ toJSON() { return undefined; } }` as `updatedInput`: stdout is
|
|
763
|
+
// `{"hookSpecificOutput":{"hookEventName":"PreToolUse"}}` (53 bytes) — the
|
|
764
|
+
// property is silently OMITTED, not undefined, and the guard below does not
|
|
765
|
+
// fire. That degraded state is the safe one (an envelope without
|
|
766
|
+
// `updatedInput` carries no rewrite, so the tool call runs with its original
|
|
767
|
+
// input — exactly what `bail` produces), which is why it is documented rather
|
|
768
|
+
// than checked: re-parsing up to REWRITE_ENVELOPE_MAX_BYTES on every rewrite
|
|
769
|
+
// to recover one stderr line is a poor trade. The guard stays because it is
|
|
770
|
+
// the envelope SHAPE that makes the case unreachable: hoist `updatedInput` to
|
|
771
|
+
// the top level, or hand the literal to a helper that returns it, and
|
|
772
|
+
// `undefined` becomes reachable again.
|
|
739
773
|
if (typeof line !== 'string') {
|
|
740
774
|
bail('serialized the tool input to undefined (a toJSON returning undefined?)');
|
|
741
775
|
}
|