session-orchestrator 5.1.0 → 5.3.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 +6 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +7 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +8 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +21 -0
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +7 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +7 -2
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +7 -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 +7 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +7 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +8 -3
- 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 +22 -0
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +7 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +20 -0
- 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 +6 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- 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 +6 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +8 -2
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +21 -0
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +6 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +22 -0
- 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 +6 -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 +6 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +7 -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 +21 -0
- package/.agents/skills/test/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 +7 -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/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +13 -4
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +197 -0
- package/README.md +11 -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 +17 -3
- package/docs/baseline.md +12 -6
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +15 -3
- package/docs/components.md +13 -6
- package/docs/events-schema.md +59 -9
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +135 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +106 -11
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +125 -8
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- 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.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +72 -25
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +211 -23
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +6 -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 +200 -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 +18 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +119 -28
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +385 -5
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/hooks/subagent-telemetry.mjs +15 -19
- 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 +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +2 -2
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +171 -15
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +77 -32
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +220 -11
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- 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/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +408 -33
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +76 -13
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- 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 +524 -5
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +332 -50
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/is-main-module.mjs +82 -0
- 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/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +122 -91
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
- 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/rule-loader.mjs +41 -12
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +429 -7
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +91 -12
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- 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/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tail-window.mjs +56 -0
- 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 +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
- package/scripts/lib/user-invocable-skills.mjs +205 -0
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +329 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- 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 +35 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
- package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
- package/scripts/lib/validate/check-unwired-features.mjs +220 -33
- package/scripts/lib/validate/check-validator-registration.mjs +36 -12
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +39 -12
- 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-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +109 -39
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +227 -0
- package/scripts/validate-plugin.mjs +21 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +11 -4
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +80 -11
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- 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/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +52 -23
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +9 -2
- package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +2 -2
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +21 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +18 -6
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +20 -2
- package/skills/wave-executor/SKILL.md +14 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
|
|
79
79
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
80
80
|
import { basename, join } from 'node:path';
|
|
81
|
+
import { scanEventsBackwards } from './events.mjs';
|
|
81
82
|
import { loadApplicableRules, parseGlobsFrontmatter } from './rule-loader.mjs';
|
|
82
83
|
|
|
83
84
|
/** Default directive ceiling (operator-chosen growth ratchet just above the ~457 baseline). */
|
|
@@ -255,17 +256,17 @@ export const DEFAULT_BYTE_CEILING = 121000;
|
|
|
255
256
|
* → rulesDir extracted from `c73c094f`: { bytes: 76114, files: 8 }
|
|
256
257
|
* → rulesDir extracted from `e4674109`: { bytes: 89763, files: 43 }
|
|
257
258
|
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* /reconcile wave of the size
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
259
|
+
* HISTORY as of 2026-09-16 — every number in this paragraph described the
|
|
260
|
+
* 124,000 ceiling, which no longer exists on this axis. It read, correctly for
|
|
261
|
+
* 2026-09-11: firing rate 0 of 3, not 0 of 2; the generated corpus then sat
|
|
262
|
+
* **2,427 B** below the peak known at that time (87,336 of 89,763 B), not
|
|
263
|
+
* 13,649 B, so one /reconcile wave of the size that session absorbed would have
|
|
264
|
+
* restored its worst recorded state with the ceiling still silent. Superseded
|
|
265
|
+
* on BOTH terms by the RE-CALIBRATED block below (ceiling 95,000, true peak
|
|
266
|
+
* 99,774 B, live distance to the peak 29,833 B measured 2026-09-16). Kept
|
|
267
|
+
* because it records the rule that produced the eventual fix: raising OR
|
|
268
|
+
* lowering a threshold inside a population fix is exactly the conflation this
|
|
269
|
+
* comment exists to end.
|
|
269
270
|
*
|
|
270
271
|
* FIRING-RATE AUDIT — widened from 3 states to 89 (#1308, 2026-09-11T17:26Z,
|
|
271
272
|
* clean tree @ `4a49adc6`). HR-105 forbids a rule nothing records; "0 of 3"
|
|
@@ -273,35 +274,63 @@ export const DEFAULT_BYTE_CEILING = 121000;
|
|
|
273
274
|
* guard was replayed over EVERY commit that touched `.claude/rules/`:
|
|
274
275
|
* `git log --format=%h -- .claude/rules/` → 89 commits, each extracted with
|
|
275
276
|
* `git archive <sha> .claude/rules | tar -x -C <tmp>` and fed to
|
|
276
|
-
* `computeInstructionBudget({rulesDir})
|
|
277
|
+
* `computeInstructionBudget({rulesDir})`. Firing rates only — the headroom
|
|
278
|
+
* annotations this table used to carry are deliberately gone, because an
|
|
279
|
+
* absolute headroom rots between two edits of the corpus; read the live one
|
|
280
|
+
* from the axis itself (command in the RE-MEASURED block below):
|
|
277
281
|
*
|
|
278
282
|
* directive axis (480) → fires 4/89 = 4.5 % rare, healthy
|
|
279
|
-
* byte axis (121,000) → fires 0/89 = 0.0 %
|
|
280
|
-
* generated axis (124,000) → fires 0/
|
|
281
|
-
* pathScoped axis (124,000) → fires
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
283
|
+
* byte axis (121,000) → fires 0/89 = 0.0 %
|
|
284
|
+
* generated axis (124,000) → fires 0/91 = 0.0 % → superseded, see below
|
|
285
|
+
* pathScoped axis (124,000) → fires 5/91 = 5.5 % → calibrated
|
|
286
|
+
*
|
|
287
|
+
* RE-CALIBRATED 2026-09-16 (#1308). The replay was re-run over the FULL
|
|
288
|
+
* recorded history of this directory, not the 89-state subset:
|
|
289
|
+
*
|
|
290
|
+
* `git log --format=%h -- ./.claude/rules/` → 91 states; each materialized
|
|
291
|
+
* with `git archive <sha> .claude/rules | tar -x -C <tmp>` and fed to
|
|
292
|
+
* `computeInstructionBudget({ rulesDir })`.
|
|
293
|
+
*
|
|
294
|
+
* True peak of the generated axis: **99,774 B @ `2ae08fe0`** — which CORRECTS
|
|
295
|
+
* the "peak ever 89,763 B" this docblock carried until now (that number came
|
|
296
|
+
* from a single replayed commit, not from the population). Median state:
|
|
297
|
+
* 4,802 B. Firing table over the same 91 states:
|
|
298
|
+
*
|
|
299
|
+
* 124,000 → 0/91 = 0.0 % (the standing, unfalsifiable value)
|
|
300
|
+
* 102,268 (peak+2.5%)→ 0/91 = 0.0 % (a ceiling above the peak never fires)
|
|
301
|
+
* 95,000 → 1/91 = 1.1 % ← chosen
|
|
302
|
+
* 88,000 → 3/91 = 3.3 %
|
|
303
|
+
* 80,000 → 5/91 = 5.5 %
|
|
304
|
+
*
|
|
305
|
+
* Why 95,000: it FIRES on recorded history (HR-105 — a rule you cannot falsify
|
|
306
|
+
* is not a rule), at 1.1 % it sits in HR-101's rare band (a class above ~10 %
|
|
307
|
+
* is a broken instrument), and it stays silent on the corpus it lands on, so it
|
|
308
|
+
* does not go red before the #1367 consolidation has been committed. "Peak +
|
|
309
|
+
* 2.5 %" is REJECTED here for the reason the old text recommended it: above the
|
|
310
|
+
* peak, the axis is decorative by construction.
|
|
311
|
+
*
|
|
312
|
+
* RE-MEASURED 2026-09-16 @ `ca214376`, after the #1367 consolidation landed in
|
|
313
|
+
* the working tree (the state this ceiling was calibrated for is the state it
|
|
314
|
+
* now judges):
|
|
315
|
+
*
|
|
316
|
+
* node --input-type=module -e "const m = await import('./scripts/lib/instruction-budget-guard.mjs'); \
|
|
317
|
+
* console.log(JSON.stringify(m.computeInstructionBudget({ repoRoot: process.cwd() }).bySurface))"
|
|
318
|
+
* → generated { bytes: 69941, files: 7 } ×1.36 under 95,000
|
|
319
|
+
* pathScoped { bytes: 109932, files: 10 }
|
|
320
|
+
*
|
|
321
|
+
* Read the headroom off that command, never off a sentence in this block: the
|
|
322
|
+
* two numbers a verdict needs are the AXIS and its CEILING, and the distance
|
|
323
|
+
* between them changes with every rule edit. For the record at that SHA the
|
|
324
|
+
* generated axis sat 25,059 B under its ceiling and 29,833 B under the 99,774 B
|
|
325
|
+
* peak — i.e. one /reconcile wave the size of the largest recorded one would
|
|
326
|
+
* still not reach the peak, but a repeat of the growth that produced it would
|
|
327
|
+
* cross 95,000 first. That is the intended, rare signal: a cue to consolidate.
|
|
328
|
+
*
|
|
329
|
+
* BV-004 revisit trigger: re-run the 91-state replay when the generated corpus
|
|
330
|
+
* passes 90,000 B, or on this ceiling's FIRST fire — at which point it is
|
|
331
|
+
* calibrated by a real event rather than by a replay.
|
|
303
332
|
*/
|
|
304
|
-
export const DEFAULT_GENERATED_BYTE_CEILING =
|
|
333
|
+
export const DEFAULT_GENERATED_BYTE_CEILING = 95000;
|
|
305
334
|
|
|
306
335
|
/**
|
|
307
336
|
* Default byte ceiling for the PATH-SCOPED rule surface — the fourth axis,
|
|
@@ -353,11 +382,14 @@ export const DEFAULT_GENERATED_BYTE_CEILING = 124000;
|
|
|
353
382
|
* working tree (2026-09-11): 134,969 B / 11 files → FIRES
|
|
354
383
|
*
|
|
355
384
|
* Firing rate 2 of 3, falsifiable in both directions — the condition
|
|
356
|
-
* {@link DEFAULT_GENERATED_BYTE_CEILING}
|
|
357
|
-
* Confirmed on a wider base (#1308, 2026-09-11T17:26Z @
|
|
358
|
-
* over all 89 commits that touched `.claude/rules/`, this
|
|
359
|
-
* (4.5 %)
|
|
360
|
-
*
|
|
385
|
+
* {@link DEFAULT_GENERATED_BYTE_CEILING} did not meet while it stood at
|
|
386
|
+
* 124,000 (0 of 3). Confirmed on a wider base (#1308, 2026-09-11T17:26Z @
|
|
387
|
+
* `4a49adc6`): replayed over all 89 commits that touched `.claude/rules/`, this
|
|
388
|
+
* axis fires 4/89 (4.5 %) — inside HR-101's rare band, and falsifiable.
|
|
389
|
+
* SUPERSEDED as a CONTRAST 2026-09-16: the sibling ceiling was re-calibrated to
|
|
390
|
+
* 95,000 in the same #1308 replay and now fires 1/91 (1.1 %) on recorded
|
|
391
|
+
* history, so "the property the sibling ceiling lacks" is no longer true of it —
|
|
392
|
+
* both axes are falsifiable, and this one is simply the looser of the two.
|
|
361
393
|
* This is also why it is not re-derived upward off the live number: a ceiling
|
|
362
394
|
* placed above 134,969 would be silent on all three states, i.e. the same
|
|
363
395
|
* unfalsifiable shape, obtained by the threshold-patch move
|
|
@@ -385,9 +417,14 @@ export const DEFAULT_GENERATED_BYTE_CEILING = 124000;
|
|
|
385
417
|
* the threshold-patch move `development.md` § Guard & Threshold Design
|
|
386
418
|
* forbids.
|
|
387
419
|
*
|
|
388
|
-
* Headroom
|
|
389
|
-
*
|
|
390
|
-
*
|
|
420
|
+
* Headroom moves with every rule edit, so it is measured, never quoted from
|
|
421
|
+
* here: `computeInstructionBudget({ repoRoot }).bySurface.pathScoped` against
|
|
422
|
+
* this constant. At 2026-09-16 @ `ca214376` that was 109,932 B / 10 files —
|
|
423
|
+
* 14,068 B under the ceiling, after the #1367 consolidation; on 2026-09-12 it
|
|
424
|
+
* was 1,237 B, which is how narrow this axis can get. The next `/reconcile`
|
|
425
|
+
* rule (~2.5 KB written standalone) trips it from a margin like that one
|
|
426
|
+
* unless it is absorbed into a thematic file in the SAME write step — an
|
|
427
|
+
* obligation written in the /reconcile-overshoot
|
|
391
428
|
* learning of `.claude/rules/measurement-discipline.md` and checked by the
|
|
392
429
|
* reconcile writer's budget pre-flight (`scripts/lib/reconcile/writer.mjs`).
|
|
393
430
|
* A breach sets `overBudget` AND turns `tests/rules/receiving-review.test.mjs`
|
|
@@ -407,6 +444,35 @@ export const DEFAULT_GENERATED_BYTE_CEILING = 124000;
|
|
|
407
444
|
*/
|
|
408
445
|
export const DEFAULT_PATH_SCOPED_BYTE_CEILING = 124000;
|
|
409
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Share of {@link DEFAULT_PATH_SCOPED_BYTE_CEILING} at which the banner warns
|
|
449
|
+
* BEFORE the writer refuses (#1419).
|
|
450
|
+
*
|
|
451
|
+
* The defect #1419 records is not the ceiling but its TIMING: it becomes
|
|
452
|
+
* visible only inside `writeApprovedRules`, after the operator has already
|
|
453
|
+
* decided. Measured on that case (2026-09-20, /reconcile run): 117,973 of
|
|
454
|
+
* 124,000 B = 95.1 % — ~6 kB of headroom holding back `capped: 91`
|
|
455
|
+
* candidates, and `overPathScopedBudget` (a HARD `bytes > ceiling` test) did
|
|
456
|
+
* not fire, so nothing warned.
|
|
457
|
+
*
|
|
458
|
+
* 90 % is the distance of ~4 materialised rules: 10 % of 124,000 B = 12,400 B
|
|
459
|
+
* against the ~1.5 kB a generated rule file occupied on the #1419 tree — i.e.
|
|
460
|
+
* roughly four more accepted learnings until the hard break. A fixed fraction,
|
|
461
|
+
* deliberately not a per-repo calibration: it cannot know how large THIS
|
|
462
|
+
* repo's next rule is, and a second calibrated number would need its own
|
|
463
|
+
* falsification record.
|
|
464
|
+
*
|
|
465
|
+
* BV-004 ceiling + revisit trigger (HR-105): measure this line's firing rate
|
|
466
|
+
* after ~20 sessions from the probe records in
|
|
467
|
+
* `.orchestrator/metrics/events.jsonl`
|
|
468
|
+
* (`jq -c 'select(.event=="orchestrator.probes.completed")
|
|
469
|
+
* | .probes[] | select(.id=="instruction-budget")' …` — count `ran-warn`
|
|
470
|
+
* against the record total).
|
|
471
|
+
* Above ~10 % the instrument is RE-AIMED per HR-101 — neither lowered nor
|
|
472
|
+
* silenced; below it, leave it alone.
|
|
473
|
+
*/
|
|
474
|
+
export const PATH_SCOPED_NEAR_THRESHOLD = 0.9;
|
|
475
|
+
|
|
410
476
|
/**
|
|
411
477
|
* Read the `instruction-budget:` nested block from the `## Session Config`
|
|
412
478
|
* section of CLAUDE.md (or AGENTS.md) at `repoRoot`. Synchronous + never throws.
|
|
@@ -749,7 +815,7 @@ function sumBytes(entries) {
|
|
|
749
815
|
* @param {Record<string, unknown>} meta
|
|
750
816
|
* @returns {boolean}
|
|
751
817
|
*/
|
|
752
|
-
function isMachineGeneratedRule(meta) {
|
|
818
|
+
export function isMachineGeneratedRule(meta) {
|
|
753
819
|
if (!meta || typeof meta !== 'object') return false;
|
|
754
820
|
return (
|
|
755
821
|
meta['auto-generated'] === true ||
|
|
@@ -758,6 +824,110 @@ function isMachineGeneratedRule(meta) {
|
|
|
758
824
|
);
|
|
759
825
|
}
|
|
760
826
|
|
|
827
|
+
/** One day in milliseconds — local to this module, as in every other lib here. */
|
|
828
|
+
const MS_PER_DAY = 86_400_000;
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Whole days from `now` until this rule's `expires-at`, or `null` when the rule
|
|
832
|
+
* declares no expiry or an unparseable one.
|
|
833
|
+
*
|
|
834
|
+
* Negative ⇒ already expired. The comparison basis is deliberately the SAME one
|
|
835
|
+
* `rule-loader.mjs` `applyGates` uses — `Date.parse(expires-at) < now`, i.e. a
|
|
836
|
+
* bare `YYYY-MM-DD` is UTC midnight — so "expired" here means exactly "the
|
|
837
|
+
* loader already filters it out of every wave's rule set", never a second
|
|
838
|
+
* definition of the same word. `Math.floor` therefore returns `-1` for a rule
|
|
839
|
+
* that expired at midnight today: past, but by less than a day.
|
|
840
|
+
*
|
|
841
|
+
* An unparseable date returns `null` (fail-OPEN, matching the loader, which
|
|
842
|
+
* warns and ignores the expiry rather than excluding the file) — the malformed
|
|
843
|
+
* value is a separate finding, not an expiry verdict.
|
|
844
|
+
*
|
|
845
|
+
* @param {{ meta?: Record<string, unknown>, now?: Date|number|string }} [opts]
|
|
846
|
+
* @returns {number|null}
|
|
847
|
+
*/
|
|
848
|
+
export function daysUntilGeneratedRuleExpiry(opts = {}) {
|
|
849
|
+
const meta = opts.meta;
|
|
850
|
+
if (!meta || typeof meta !== 'object') return null;
|
|
851
|
+
if (!Object.prototype.hasOwnProperty.call(meta, 'expires-at')) return null;
|
|
852
|
+
const expiresAt = Date.parse(String(meta['expires-at']));
|
|
853
|
+
if (!Number.isFinite(expiresAt)) return null;
|
|
854
|
+
|
|
855
|
+
const raw = opts.now;
|
|
856
|
+
let now;
|
|
857
|
+
if (raw instanceof Date) now = raw.getTime();
|
|
858
|
+
else if (typeof raw === 'number' && Number.isFinite(raw)) now = raw;
|
|
859
|
+
else if (typeof raw === 'string' && Number.isFinite(Date.parse(raw))) now = Date.parse(raw);
|
|
860
|
+
else now = Date.now();
|
|
861
|
+
|
|
862
|
+
return Math.floor((expiresAt - now) / MS_PER_DAY);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Is this generated rule's `expires-at` already in the past?
|
|
867
|
+
*
|
|
868
|
+
* The defect it names (#1372): `rule-loader.mjs` filters an expired rule out at
|
|
869
|
+
* READ time, but nothing DELETES it — so the file keeps being counted by every
|
|
870
|
+
* byte ceiling above and keeps being loaded verbatim by Claude Code, which has
|
|
871
|
+
* no notion of `expires-at`.
|
|
872
|
+
*
|
|
873
|
+
* @param {{ meta?: Record<string, unknown>, now?: Date|number|string }} [opts]
|
|
874
|
+
* @returns {boolean}
|
|
875
|
+
*/
|
|
876
|
+
export function isExpiredGeneratedRule(opts = {}) {
|
|
877
|
+
const days = daysUntilGeneratedRuleExpiry(opts);
|
|
878
|
+
return days !== null && days < 0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Enumerate the machine-generated rule files in `rulesDir`.
|
|
883
|
+
*
|
|
884
|
+
* Same population, same filter and same frontmatter parser as
|
|
885
|
+
* {@link measureRuleCorpora}'s `generated` surface — that is the point of
|
|
886
|
+
* exporting it: a consumer that needs the FILES rather than the BYTES (the
|
|
887
|
+
* expiry test, the session-start maintenance probe) must not mirror the
|
|
888
|
+
* predicate, because a mirror agrees only until one side is edited.
|
|
889
|
+
*
|
|
890
|
+
* Three-state, like every reader in the banner family: `ok: false` means the
|
|
891
|
+
* directory exists but could not be enumerated, which a caller must record as
|
|
892
|
+
* "undeterminable" and never as "no generated rules". A MISSING directory is
|
|
893
|
+
* `ok: true` with an empty list — a repo without `.claude/rules/` genuinely has
|
|
894
|
+
* none.
|
|
895
|
+
*
|
|
896
|
+
* @param {{ rulesDir?: string, repoRoot?: string }} [opts]
|
|
897
|
+
* @returns {{ ok: boolean, rules: Array<{ file: string, meta: Record<string, unknown>, expiresAt: string|null }> }}
|
|
898
|
+
*/
|
|
899
|
+
export function listMachineGeneratedRules(opts = {}) {
|
|
900
|
+
const dir =
|
|
901
|
+
typeof opts.rulesDir === 'string' && opts.rulesDir.length > 0
|
|
902
|
+
? opts.rulesDir
|
|
903
|
+
: join(opts.repoRoot ?? process.cwd(), '.claude', 'rules');
|
|
904
|
+
|
|
905
|
+
/** @type {Array<{ file: string, meta: Record<string, unknown>, expiresAt: string|null }>} */
|
|
906
|
+
const rules = [];
|
|
907
|
+
let names;
|
|
908
|
+
try {
|
|
909
|
+
names = readdirSync(dir);
|
|
910
|
+
} catch (err) {
|
|
911
|
+
// ENOENT is an answer ("no generated rules"); anything else is not knowing.
|
|
912
|
+
return { ok: err?.code === 'ENOENT', rules };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
for (const file of names) {
|
|
916
|
+
if (!file.endsWith('.md')) continue;
|
|
917
|
+
let meta;
|
|
918
|
+
try {
|
|
919
|
+
({ meta } = parseGlobsFrontmatter(readFileSync(join(dir, file), 'utf8')));
|
|
920
|
+
} catch {
|
|
921
|
+
continue; // unreadable or unparseable — skip, never throw
|
|
922
|
+
}
|
|
923
|
+
if (!isMachineGeneratedRule(meta)) continue;
|
|
924
|
+
const raw = meta?.['expires-at'];
|
|
925
|
+
rules.push({ file, meta, expiresAt: typeof raw === 'string' ? raw : null });
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return { ok: true, rules };
|
|
929
|
+
}
|
|
930
|
+
|
|
761
931
|
/**
|
|
762
932
|
* Single-pass scan of `.claude/rules/*.md` producing the TWO corpus measures
|
|
763
933
|
* this guard's third axis needs, which #1297 proved are NOT the same set:
|
|
@@ -1104,6 +1274,83 @@ export function computeInstructionBudget(opts = {}) {
|
|
|
1104
1274
|
};
|
|
1105
1275
|
}
|
|
1106
1276
|
|
|
1277
|
+
/** Ledger event the backlog number below is read from (`reconcile/engine.mjs`). */
|
|
1278
|
+
const RECONCILE_EVENT = 'orchestrator.reconcile.completed';
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* Wall-clock budget for the backwards ledger walk. Deliberately small: this
|
|
1282
|
+
* runs inside a session-start probe whose own budget is 2 s, and the number it
|
|
1283
|
+
* fetches is DECORATION on a line that is already actionable without it.
|
|
1284
|
+
*/
|
|
1285
|
+
const RECONCILE_SCAN_BUDGET_MS = 150;
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* `capped` from the most recent `orchestrator.reconcile.completed` record —
|
|
1289
|
+
* how many rule-eligible learnings the last /reconcile run held back.
|
|
1290
|
+
*
|
|
1291
|
+
* Three-state on purpose (`feedback_missing_measurement_looks_like_zero`):
|
|
1292
|
+
* `null` means NOT MEASURED (no ledger, no such record, unreadable source, or
|
|
1293
|
+
* the walk ran out of budget), which the caller must render as ABSENCE — never
|
|
1294
|
+
* as `0`, which is a measured "nothing waiting".
|
|
1295
|
+
*
|
|
1296
|
+
* @param {string|undefined} repoRoot
|
|
1297
|
+
* @returns {number|null}
|
|
1298
|
+
*/
|
|
1299
|
+
function readLastReconcileCapped(repoRoot) {
|
|
1300
|
+
if (typeof repoRoot !== 'string' || repoRoot === '') return null;
|
|
1301
|
+
const filePath = join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl');
|
|
1302
|
+
let capped = null;
|
|
1303
|
+
let scan;
|
|
1304
|
+
try {
|
|
1305
|
+
scan = scanEventsBackwards({
|
|
1306
|
+
filePath,
|
|
1307
|
+
budgetMs: RECONCILE_SCAN_BUDGET_MS,
|
|
1308
|
+
// Cheap raw-line pre-filter before JSON.parse — same reason as
|
|
1309
|
+
// `maintenance-due-banner.mjs`: parsing every line of a multi-MB ledger
|
|
1310
|
+
// to answer one question is the cost this option exists to avoid.
|
|
1311
|
+
filter: RECONCILE_EVENT,
|
|
1312
|
+
onRecord: (rec) => {
|
|
1313
|
+
if (rec?.event !== RECONCILE_EVENT) return false;
|
|
1314
|
+
capped = Number.isFinite(rec.capped) ? rec.capped : null;
|
|
1315
|
+
return true; // newest record wins; stop the walk
|
|
1316
|
+
},
|
|
1317
|
+
});
|
|
1318
|
+
} catch {
|
|
1319
|
+
return null; // never throw out of the banner wrapper
|
|
1320
|
+
}
|
|
1321
|
+
// A walk that did not finish has not proven anything about the backlog.
|
|
1322
|
+
if (scan.truncated || scan.unreadable.length > 0) return null;
|
|
1323
|
+
return capped;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* The #1419 early-warning line: the path-scoped ceiling is close, not broken.
|
|
1328
|
+
*
|
|
1329
|
+
* Reports the numbers the RULE judged (HR-106) — the same `pathScoped.bytes`
|
|
1330
|
+
* and ceiling `writeApprovedRules` will compare later — plus, when the ledger
|
|
1331
|
+
* carries one, how many learnings the last /reconcile run already held back.
|
|
1332
|
+
* The percentage is formatted locale-free so the same corpus renders the same
|
|
1333
|
+
* string on every host.
|
|
1334
|
+
*
|
|
1335
|
+
* @param {ReturnType<typeof computeInstructionBudget>} budget
|
|
1336
|
+
* @param {string|undefined} repoRoot
|
|
1337
|
+
* @returns {string}
|
|
1338
|
+
*/
|
|
1339
|
+
function formatNearPathScopedLine(budget, repoRoot) {
|
|
1340
|
+
const used = budget.bySurface.pathScoped.bytes;
|
|
1341
|
+
const ceiling = budget.pathScopedByteCeiling;
|
|
1342
|
+
const pct = ((used / ceiling) * 100).toFixed(1);
|
|
1343
|
+
const capped = readLastReconcileCapped(repoRoot);
|
|
1344
|
+
// Only a POSITIVE measured backlog is worth a clause; `0` and `null` both
|
|
1345
|
+
// add nothing the operator can act on, and printing `0` for `null` would be
|
|
1346
|
+
// the false-negative this helper's three-state contract exists to prevent.
|
|
1347
|
+
const backlog = typeof capped === 'number' && capped > 0 ? ` — ${capped} Learnings warten` : '';
|
|
1348
|
+
return (
|
|
1349
|
+
`path-scoped Regel-Decke zu ${pct} % belegt (${used}/${ceiling} B)${backlog} — ` +
|
|
1350
|
+
'/reconcile wird Learnings zurueckhalten'
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1107
1354
|
/**
|
|
1108
1355
|
* Banner wrapper — session-start Phase 4 convention.
|
|
1109
1356
|
*
|
|
@@ -1128,10 +1375,15 @@ export function computeInstructionBudget(opts = {}) {
|
|
|
1128
1375
|
* @param {number} [opts.byteCeiling] explicit byte-ceiling override (wins over config).
|
|
1129
1376
|
* @param {number} [opts.pathScopedByteCeiling] explicit path-scoped-ceiling override.
|
|
1130
1377
|
* @returns {{ severity: 'warn', message: string } | null}
|
|
1131
|
-
* null when disabled / off / every axis
|
|
1132
|
-
* read failure. Since #1316 each of the four axes — directives, bytes,
|
|
1378
|
+
* null when disabled / off / every axis comfortably under its ceiling OR on
|
|
1379
|
+
* any read failure. Since #1316 each of the four axes — directives, bytes,
|
|
1133
1380
|
* generated, path-scoped — raises the banner on its own (see
|
|
1134
|
-
* DEFAULT_PATH_SCOPED_BYTE_CEILING).
|
|
1381
|
+
* DEFAULT_PATH_SCOPED_BYTE_CEILING). Since #1419 the path-scoped axis ALSO
|
|
1382
|
+
* raises it at {@link PATH_SCOPED_NEAR_THRESHOLD} of its ceiling, before the
|
|
1383
|
+
* breach: that band used to be silent, and the ceiling then surfaced only
|
|
1384
|
+
* inside `writeApprovedRules` — after the operator had approved rules it
|
|
1385
|
+
* refused to write. The RETURN SHAPE is unchanged (`{severity:'warn',
|
|
1386
|
+
* message}` | null), so `session-start-probes.mjs` needs no new vocabulary.
|
|
1135
1387
|
*/
|
|
1136
1388
|
export function checkInstructionBudget(opts = {}) {
|
|
1137
1389
|
let cfg;
|
|
@@ -1191,7 +1443,33 @@ export function checkInstructionBudget(opts = {}) {
|
|
|
1191
1443
|
return null; // never throw out of the banner wrapper
|
|
1192
1444
|
}
|
|
1193
1445
|
|
|
1194
|
-
if (!budget
|
|
1446
|
+
if (!budget) return null;
|
|
1447
|
+
|
|
1448
|
+
// #1419 near-threshold stage. Strictly BELOW the hard test (a breach is
|
|
1449
|
+
// reported as a breach, never twice), and computed before the early return
|
|
1450
|
+
// so a corpus at 90–100 % of the path-scoped ceiling stops being silent —
|
|
1451
|
+
// that silence is the defect: the ceiling used to surface only inside
|
|
1452
|
+
// `writeApprovedRules`, after the operator had already approved rules.
|
|
1453
|
+
const nearPathScoped =
|
|
1454
|
+
!budget.overPathScopedBudget &&
|
|
1455
|
+
budget.pathScopedByteCeiling > 0 &&
|
|
1456
|
+
budget.bySurface.pathScoped.bytes >= budget.pathScopedByteCeiling * PATH_SCOPED_NEAR_THRESHOLD;
|
|
1457
|
+
|
|
1458
|
+
if (!budget.overBudget && !nearPathScoped) return null;
|
|
1459
|
+
|
|
1460
|
+
const nearLine = nearPathScoped ? formatNearPathScopedLine(budget, opts.repoRoot) : null;
|
|
1461
|
+
|
|
1462
|
+
// Near-threshold and nothing breached: a one-finding banner, carrying the
|
|
1463
|
+
// same remedy as the breach clause below (consolidate — never raise).
|
|
1464
|
+
if (!budget.overBudget) {
|
|
1465
|
+
return {
|
|
1466
|
+
severity: 'warn',
|
|
1467
|
+
message: [
|
|
1468
|
+
`⚠ ${nearLine}.`,
|
|
1469
|
+
' Consolidate generated rules per docs/rule-authoring.md § Consolidated rules; never raise the ceiling.',
|
|
1470
|
+
].join('\n'),
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1195
1473
|
|
|
1196
1474
|
// Name only the breached axes — listing a healthy axis would pad the line
|
|
1197
1475
|
// without telling the operator anything they must act on.
|
|
@@ -1237,8 +1515,12 @@ export function checkInstructionBudget(opts = {}) {
|
|
|
1237
1515
|
.map((f) => `${f.file} (${f.count} dir, ${f.bytes} B)`)
|
|
1238
1516
|
.join(', ');
|
|
1239
1517
|
|
|
1518
|
+
// The near-threshold line rides ALONGSIDE the breach line rather than inside
|
|
1519
|
+
// `axes`: the path-scoped axis is not over, and folding a healthy-but-close
|
|
1520
|
+
// axis into a sentence that says "over" would misreport which ceiling broke.
|
|
1240
1521
|
const message = [
|
|
1241
1522
|
`⚠ Instruction budget over — ${axes.join(' · ')} across ${budget.perFile.length} always-on rules.`,
|
|
1523
|
+
...(nearLine ? [` ${nearLine}.`] : []),
|
|
1242
1524
|
` Top files: ${top}`,
|
|
1243
1525
|
' See the instruction-budget audit (#687; archived in the private Meta-Vault) for the prune/demote list.',
|
|
1244
1526
|
].join('\n');
|
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
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* is-main-module.mjs — the one entry-guard predicate, symlink-safe.
|
|
3
|
+
*
|
|
4
|
+
* ## The incident class
|
|
5
|
+
*
|
|
6
|
+
* An ESM module that wants to run `main()` only when invoked as a CLI has to
|
|
7
|
+
* compare "the module I am" against "the script Node was told to run". The two
|
|
8
|
+
* sides are NOT the same kind of path:
|
|
9
|
+
*
|
|
10
|
+
* - `import.meta.url` is resolved by Node to the module's **realpath**.
|
|
11
|
+
* - `process.argv[1]` is the path **as typed on the command line**.
|
|
12
|
+
*
|
|
13
|
+
* With a symlink anywhere in the invocation path — a `node_modules/.bin/` shim,
|
|
14
|
+
* a `~/bin/foo -> /repo/scripts/foo.mjs` convenience link, a plugin directory
|
|
15
|
+
* symlinked into `~/.claude/plugins/`, or `/tmp` itself (on macOS `/tmp` is a
|
|
16
|
+
* symlink to `/private/tmp`) — the two strings differ and the guard is FALSE.
|
|
17
|
+
*
|
|
18
|
+
* The failure is silent and total: `main()` never runs, nothing is printed, and
|
|
19
|
+
* the process exits 0. Every caller — a hook, a CI job, an npm script, an
|
|
20
|
+
* operator — reads that as success. This is the single worst shape a defect can
|
|
21
|
+
* have in this repo, which is why the predicate is centralised here instead of
|
|
22
|
+
* being re-typed per file.
|
|
23
|
+
*
|
|
24
|
+
* ## The textual variants this replaces
|
|
25
|
+
*
|
|
26
|
+
* Measured 2026-09-16 @ ca214376 over the 50 files swept for #1371, the guard
|
|
27
|
+
* had been hand-written in 15 distinct textual forms (measured by extracting the
|
|
28
|
+
* guard expression from every file at HEAD), collapsing into four
|
|
29
|
+
* semantic variants — all four wrong in the same way:
|
|
30
|
+
*
|
|
31
|
+
* V1 import.meta.url === `file://${process.argv[1]}`
|
|
32
|
+
* (also breaks on any space or non-ASCII char in the path — no escaping)
|
|
33
|
+
* V2 import.meta.url === pathToFileURL(process.argv[1] ?? '').href
|
|
34
|
+
* V3 resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))
|
|
35
|
+
* / resolve(process.argv[1]) === resolve(__filename)
|
|
36
|
+
* V4 fileURLToPath(import.meta.url) === process.argv[1]
|
|
37
|
+
*
|
|
38
|
+
* V2/V3 fix the `undefined`-argv and relative-path cases; none of them resolve
|
|
39
|
+
* symlinks, because only `realpath` does.
|
|
40
|
+
*
|
|
41
|
+
* ## Non-goal: Windows drive-letter / case folding
|
|
42
|
+
*
|
|
43
|
+
* `realpathSync.native` delegates to the OS, so on NTFS it canonicalises the
|
|
44
|
+
* stored casing of every path component and the two sides agree. Where the
|
|
45
|
+
* realpath call FAILS (deleted path, permission error) the fallback is a raw
|
|
46
|
+
* string compare, which on Windows can be case-sensitive against a
|
|
47
|
+
* case-insensitive filesystem. That residual case is deliberately out of scope:
|
|
48
|
+
* this repo runs on macOS and Linux CI, and a case-only mismatch degrades to
|
|
49
|
+
* today's behaviour rather than to something worse.
|
|
50
|
+
*
|
|
51
|
+
* @module scripts/lib/is-main-module
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
import { realpathSync } from 'node:fs';
|
|
55
|
+
import { fileURLToPath } from 'node:url';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether the module identified by `importMetaUrl` is the script Node was
|
|
59
|
+
* invoked with.
|
|
60
|
+
*
|
|
61
|
+
* Symlink-safe: both sides are canonicalised via `realpathSync.native` before
|
|
62
|
+
* comparison, so an invocation through a symlink (shim, `~/bin` link, `/tmp` on
|
|
63
|
+
* macOS) still matches. When either path cannot be realpath'd — it was deleted
|
|
64
|
+
* between spawn and this call, or is unreadable — the comparison degrades to a
|
|
65
|
+
* raw string compare rather than throwing, because an entry guard must never be
|
|
66
|
+
* the thing that crashes an import.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} importMetaUrl the calling module's `import.meta.url`
|
|
69
|
+
* @param {string | undefined} [argv1] the invoked script path; defaults to
|
|
70
|
+
* `process.argv[1]`, which is `undefined` under `node -e`, the REPL and some
|
|
71
|
+
* dynamic-import contexts — those return `false`
|
|
72
|
+
* @returns {boolean} true when this module IS the entrypoint
|
|
73
|
+
*/
|
|
74
|
+
export function isMainModule(importMetaUrl, argv1 = process.argv[1]) {
|
|
75
|
+
if (!argv1) return false;
|
|
76
|
+
const self = fileURLToPath(importMetaUrl);
|
|
77
|
+
try {
|
|
78
|
+
return realpathSync.native(argv1) === realpathSync.native(self);
|
|
79
|
+
} catch {
|
|
80
|
+
return argv1 === self;
|
|
81
|
+
}
|
|
82
|
+
}
|