session-orchestrator 5.0.0 → 5.2.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/autopilot/SKILL.md +1 -0
- package/.agents/skills/bootstrap/SKILL.md +2 -0
- package/.agents/skills/brainstorm/SKILL.md +3 -0
- package/.agents/skills/close/SKILL.md +17 -0
- package/.agents/skills/debug/SKILL.md +2 -0
- package/.agents/skills/discovery/SKILL.md +2 -1
- package/.agents/skills/dispatcher/SKILL.md +2 -0
- package/.agents/skills/eli5/SKILL.md +2 -0
- package/.agents/skills/eval/SKILL.md +1 -0
- package/.agents/skills/evolve/SKILL.md +2 -1
- package/.agents/skills/go/SKILL.md +18 -0
- package/.agents/skills/grill/SKILL.md +2 -0
- package/.agents/skills/harness-audit/SKILL.md +16 -0
- package/.agents/skills/memory-cleanup/SKILL.md +1 -0
- package/.agents/skills/persona-panel/SKILL.md +1 -0
- package/.agents/skills/plan/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +17 -0
- package/.agents/skills/reconcile/SKILL.md +1 -0
- package/.agents/skills/release/SKILL.md +18 -0
- package/.agents/skills/repo-audit/SKILL.md +1 -0
- package/.agents/skills/spinout/SKILL.md +1 -0
- package/.agents/skills/sunset-review/SKILL.md +2 -0
- package/.agents/skills/test/SKILL.md +17 -0
- package/.agents/skills/ux-grill/SKILL.md +2 -0
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +2 -2
- 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 +2 -0
- package/.codex-plugin/skills/convergence-monitoring/agents/openai.yaml +5 -0
- 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 +2 -0
- package/.codex-plugin/skills/npm-publish/agents/openai.yaml +5 -0
- 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/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/convergence-monitoring.md +13 -0
- 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/npm-publish.md +13 -0
- 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/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/010-session-workflow.mdc +2 -2
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -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/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/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 +2 -2
- package/.orchestrator/policy/blocked-commands.json +10 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +80 -0
- package/README.md +74 -235
- package/commands/session.md +10 -0
- package/docs/USER-GUIDE.md +24 -0
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +1 -1
- package/docs/components.md +12 -5
- package/docs/events-schema.md +5 -1
- package/docs/install.md +128 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +83 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +6 -4
- package/docs/session-config-template.md +38 -0
- package/docs/telemetry.md +15 -0
- package/hooks/_lib/hook-import-set.json +46 -6
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- package/hooks/enforce-scope.mjs +42 -1
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/on-session-end.mjs +14 -2
- package/hooks/on-stop.mjs +43 -1
- package/hooks/post-bash-write-verify.mjs +3 -0
- package/hooks/pre-auq-clarity.mjs +3 -0
- package/hooks/pre-bash-issue-budget.mjs +103 -17
- package/hooks/pre-task-scope-disjoint.mjs +152 -3
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/package.json +3 -2
- 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/convergence-monitoring.md +11 -0
- 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/npm-publish.md +11 -0
- 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 +1 -1
- 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/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/backfill-abandoned-sessions.mjs +57 -3
- 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/ci/assert-vitest-green.mjs +2 -1
- package/scripts/emit-session.mjs +2 -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-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +173 -9
- 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/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +209 -9
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/convergence-monitor.mjs +2 -2
- 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-wizard.mjs +2 -1
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/instruction-budget-guard.mjs +186 -46
- package/scripts/lib/is-main-module.mjs +82 -0
- package/scripts/lib/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +69 -3
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +63 -2
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +642 -0
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +392 -7
- package/scripts/lib/session-close-backfill.mjs +58 -6
- package/scripts/lib/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tests-src-ratio.mjs +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +2 -1
- package/scripts/lib/user-invocable-skills.mjs +185 -0
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +2 -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 +366 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- 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-skill-links.mjs +27 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +2 -2
- package/scripts/lib/validate/check-test-git-config-target.mjs +2 -2
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +2 -2
- package/scripts/lib/validate/check-unwired-features.mjs +266 -11
- package/scripts/lib/validate/check-validator-registration.mjs +2 -2
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate-vendored-rules.mjs +35 -9
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- 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.mjs +124 -35
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +216 -0
- package/scripts/validate-plugin.mjs +9 -0
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +2 -3
- 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/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +58 -4
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- 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 +14 -0
- package/skills/evolve/SKILL.md +8 -1
- package/skills/evolve/references/evolve-dialectic-mode.md +6 -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 +1 -1
- 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/skills/playwright-driver/SKILL.md +7 -10
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +10 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-plan/SKILL.md +1 -1
- 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 +4 -3
- 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 +19 -1
- package/skills/wave-executor/SKILL.md +7 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +13 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +3 -1
- 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
|
@@ -255,17 +255,17 @@ export const DEFAULT_BYTE_CEILING = 121000;
|
|
|
255
255
|
* → rulesDir extracted from `c73c094f`: { bytes: 76114, files: 8 }
|
|
256
256
|
* → rulesDir extracted from `e4674109`: { bytes: 89763, files: 43 }
|
|
257
257
|
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* /reconcile wave of the size
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
258
|
+
* HISTORY as of 2026-09-16 — every number in this paragraph described the
|
|
259
|
+
* 124,000 ceiling, which no longer exists on this axis. It read, correctly for
|
|
260
|
+
* 2026-09-11: firing rate 0 of 3, not 0 of 2; the generated corpus then sat
|
|
261
|
+
* **2,427 B** below the peak known at that time (87,336 of 89,763 B), not
|
|
262
|
+
* 13,649 B, so one /reconcile wave of the size that session absorbed would have
|
|
263
|
+
* restored its worst recorded state with the ceiling still silent. Superseded
|
|
264
|
+
* on BOTH terms by the RE-CALIBRATED block below (ceiling 95,000, true peak
|
|
265
|
+
* 99,774 B, live distance to the peak 29,833 B measured 2026-09-16). Kept
|
|
266
|
+
* because it records the rule that produced the eventual fix: raising OR
|
|
267
|
+
* lowering a threshold inside a population fix is exactly the conflation this
|
|
268
|
+
* comment exists to end.
|
|
269
269
|
*
|
|
270
270
|
* FIRING-RATE AUDIT — widened from 3 states to 89 (#1308, 2026-09-11T17:26Z,
|
|
271
271
|
* clean tree @ `4a49adc6`). HR-105 forbids a rule nothing records; "0 of 3"
|
|
@@ -273,35 +273,63 @@ export const DEFAULT_BYTE_CEILING = 121000;
|
|
|
273
273
|
* guard was replayed over EVERY commit that touched `.claude/rules/`:
|
|
274
274
|
* `git log --format=%h -- .claude/rules/` → 89 commits, each extracted with
|
|
275
275
|
* `git archive <sha> .claude/rules | tar -x -C <tmp>` and fed to
|
|
276
|
-
* `computeInstructionBudget({rulesDir})
|
|
276
|
+
* `computeInstructionBudget({rulesDir})`. Firing rates only — the headroom
|
|
277
|
+
* annotations this table used to carry are deliberately gone, because an
|
|
278
|
+
* absolute headroom rots between two edits of the corpus; read the live one
|
|
279
|
+
* from the axis itself (command in the RE-MEASURED block below):
|
|
277
280
|
*
|
|
278
281
|
* 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
|
-
*
|
|
282
|
+
* byte axis (121,000) → fires 0/89 = 0.0 %
|
|
283
|
+
* generated axis (124,000) → fires 0/91 = 0.0 % → superseded, see below
|
|
284
|
+
* pathScoped axis (124,000) → fires 5/91 = 5.5 % → calibrated
|
|
285
|
+
*
|
|
286
|
+
* RE-CALIBRATED 2026-09-16 (#1308). The replay was re-run over the FULL
|
|
287
|
+
* recorded history of this directory, not the 89-state subset:
|
|
288
|
+
*
|
|
289
|
+
* `git log --format=%h -- ./.claude/rules/` → 91 states; each materialized
|
|
290
|
+
* with `git archive <sha> .claude/rules | tar -x -C <tmp>` and fed to
|
|
291
|
+
* `computeInstructionBudget({ rulesDir })`.
|
|
292
|
+
*
|
|
293
|
+
* True peak of the generated axis: **99,774 B @ `2ae08fe0`** — which CORRECTS
|
|
294
|
+
* the "peak ever 89,763 B" this docblock carried until now (that number came
|
|
295
|
+
* from a single replayed commit, not from the population). Median state:
|
|
296
|
+
* 4,802 B. Firing table over the same 91 states:
|
|
297
|
+
*
|
|
298
|
+
* 124,000 → 0/91 = 0.0 % (the standing, unfalsifiable value)
|
|
299
|
+
* 102,268 (peak+2.5%)→ 0/91 = 0.0 % (a ceiling above the peak never fires)
|
|
300
|
+
* 95,000 → 1/91 = 1.1 % ← chosen
|
|
301
|
+
* 88,000 → 3/91 = 3.3 %
|
|
302
|
+
* 80,000 → 5/91 = 5.5 %
|
|
303
|
+
*
|
|
304
|
+
* Why 95,000: it FIRES on recorded history (HR-105 — a rule you cannot falsify
|
|
305
|
+
* is not a rule), at 1.1 % it sits in HR-101's rare band (a class above ~10 %
|
|
306
|
+
* is a broken instrument), and it stays silent on the corpus it lands on, so it
|
|
307
|
+
* does not go red before the #1367 consolidation has been committed. "Peak +
|
|
308
|
+
* 2.5 %" is REJECTED here for the reason the old text recommended it: above the
|
|
309
|
+
* peak, the axis is decorative by construction.
|
|
310
|
+
*
|
|
311
|
+
* RE-MEASURED 2026-09-16 @ `ca214376`, after the #1367 consolidation landed in
|
|
312
|
+
* the working tree (the state this ceiling was calibrated for is the state it
|
|
313
|
+
* now judges):
|
|
314
|
+
*
|
|
315
|
+
* node --input-type=module -e "const m = await import('./scripts/lib/instruction-budget-guard.mjs'); \
|
|
316
|
+
* console.log(JSON.stringify(m.computeInstructionBudget({ repoRoot: process.cwd() }).bySurface))"
|
|
317
|
+
* → generated { bytes: 69941, files: 7 } ×1.36 under 95,000
|
|
318
|
+
* pathScoped { bytes: 109932, files: 10 }
|
|
319
|
+
*
|
|
320
|
+
* Read the headroom off that command, never off a sentence in this block: the
|
|
321
|
+
* two numbers a verdict needs are the AXIS and its CEILING, and the distance
|
|
322
|
+
* between them changes with every rule edit. For the record at that SHA the
|
|
323
|
+
* generated axis sat 25,059 B under its ceiling and 29,833 B under the 99,774 B
|
|
324
|
+
* peak — i.e. one /reconcile wave the size of the largest recorded one would
|
|
325
|
+
* still not reach the peak, but a repeat of the growth that produced it would
|
|
326
|
+
* cross 95,000 first. That is the intended, rare signal: a cue to consolidate.
|
|
327
|
+
*
|
|
328
|
+
* BV-004 revisit trigger: re-run the 91-state replay when the generated corpus
|
|
329
|
+
* passes 90,000 B, or on this ceiling's FIRST fire — at which point it is
|
|
330
|
+
* calibrated by a real event rather than by a replay.
|
|
303
331
|
*/
|
|
304
|
-
export const DEFAULT_GENERATED_BYTE_CEILING =
|
|
332
|
+
export const DEFAULT_GENERATED_BYTE_CEILING = 95000;
|
|
305
333
|
|
|
306
334
|
/**
|
|
307
335
|
* Default byte ceiling for the PATH-SCOPED rule surface — the fourth axis,
|
|
@@ -353,11 +381,14 @@ export const DEFAULT_GENERATED_BYTE_CEILING = 124000;
|
|
|
353
381
|
* working tree (2026-09-11): 134,969 B / 11 files → FIRES
|
|
354
382
|
*
|
|
355
383
|
* 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
|
-
*
|
|
384
|
+
* {@link DEFAULT_GENERATED_BYTE_CEILING} did not meet while it stood at
|
|
385
|
+
* 124,000 (0 of 3). Confirmed on a wider base (#1308, 2026-09-11T17:26Z @
|
|
386
|
+
* `4a49adc6`): replayed over all 89 commits that touched `.claude/rules/`, this
|
|
387
|
+
* axis fires 4/89 (4.5 %) — inside HR-101's rare band, and falsifiable.
|
|
388
|
+
* SUPERSEDED as a CONTRAST 2026-09-16: the sibling ceiling was re-calibrated to
|
|
389
|
+
* 95,000 in the same #1308 replay and now fires 1/91 (1.1 %) on recorded
|
|
390
|
+
* history, so "the property the sibling ceiling lacks" is no longer true of it —
|
|
391
|
+
* both axes are falsifiable, and this one is simply the looser of the two.
|
|
361
392
|
* This is also why it is not re-derived upward off the live number: a ceiling
|
|
362
393
|
* placed above 134,969 would be silent on all three states, i.e. the same
|
|
363
394
|
* unfalsifiable shape, obtained by the threshold-patch move
|
|
@@ -385,9 +416,14 @@ export const DEFAULT_GENERATED_BYTE_CEILING = 124000;
|
|
|
385
416
|
* the threshold-patch move `development.md` § Guard & Threshold Design
|
|
386
417
|
* forbids.
|
|
387
418
|
*
|
|
388
|
-
* Headroom
|
|
389
|
-
*
|
|
390
|
-
*
|
|
419
|
+
* Headroom moves with every rule edit, so it is measured, never quoted from
|
|
420
|
+
* here: `computeInstructionBudget({ repoRoot }).bySurface.pathScoped` against
|
|
421
|
+
* this constant. At 2026-09-16 @ `ca214376` that was 109,932 B / 10 files —
|
|
422
|
+
* 14,068 B under the ceiling, after the #1367 consolidation; on 2026-09-12 it
|
|
423
|
+
* was 1,237 B, which is how narrow this axis can get. The next `/reconcile`
|
|
424
|
+
* rule (~2.5 KB written standalone) trips it from a margin like that one
|
|
425
|
+
* unless it is absorbed into a thematic file in the SAME write step — an
|
|
426
|
+
* obligation written in the /reconcile-overshoot
|
|
391
427
|
* learning of `.claude/rules/measurement-discipline.md` and checked by the
|
|
392
428
|
* reconcile writer's budget pre-flight (`scripts/lib/reconcile/writer.mjs`).
|
|
393
429
|
* A breach sets `overBudget` AND turns `tests/rules/receiving-review.test.mjs`
|
|
@@ -749,7 +785,7 @@ function sumBytes(entries) {
|
|
|
749
785
|
* @param {Record<string, unknown>} meta
|
|
750
786
|
* @returns {boolean}
|
|
751
787
|
*/
|
|
752
|
-
function isMachineGeneratedRule(meta) {
|
|
788
|
+
export function isMachineGeneratedRule(meta) {
|
|
753
789
|
if (!meta || typeof meta !== 'object') return false;
|
|
754
790
|
return (
|
|
755
791
|
meta['auto-generated'] === true ||
|
|
@@ -758,6 +794,110 @@ function isMachineGeneratedRule(meta) {
|
|
|
758
794
|
);
|
|
759
795
|
}
|
|
760
796
|
|
|
797
|
+
/** One day in milliseconds — local to this module, as in every other lib here. */
|
|
798
|
+
const MS_PER_DAY = 86_400_000;
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Whole days from `now` until this rule's `expires-at`, or `null` when the rule
|
|
802
|
+
* declares no expiry or an unparseable one.
|
|
803
|
+
*
|
|
804
|
+
* Negative ⇒ already expired. The comparison basis is deliberately the SAME one
|
|
805
|
+
* `rule-loader.mjs` `applyGates` uses — `Date.parse(expires-at) < now`, i.e. a
|
|
806
|
+
* bare `YYYY-MM-DD` is UTC midnight — so "expired" here means exactly "the
|
|
807
|
+
* loader already filters it out of every wave's rule set", never a second
|
|
808
|
+
* definition of the same word. `Math.floor` therefore returns `-1` for a rule
|
|
809
|
+
* that expired at midnight today: past, but by less than a day.
|
|
810
|
+
*
|
|
811
|
+
* An unparseable date returns `null` (fail-OPEN, matching the loader, which
|
|
812
|
+
* warns and ignores the expiry rather than excluding the file) — the malformed
|
|
813
|
+
* value is a separate finding, not an expiry verdict.
|
|
814
|
+
*
|
|
815
|
+
* @param {{ meta?: Record<string, unknown>, now?: Date|number|string }} [opts]
|
|
816
|
+
* @returns {number|null}
|
|
817
|
+
*/
|
|
818
|
+
export function daysUntilGeneratedRuleExpiry(opts = {}) {
|
|
819
|
+
const meta = opts.meta;
|
|
820
|
+
if (!meta || typeof meta !== 'object') return null;
|
|
821
|
+
if (!Object.prototype.hasOwnProperty.call(meta, 'expires-at')) return null;
|
|
822
|
+
const expiresAt = Date.parse(String(meta['expires-at']));
|
|
823
|
+
if (!Number.isFinite(expiresAt)) return null;
|
|
824
|
+
|
|
825
|
+
const raw = opts.now;
|
|
826
|
+
let now;
|
|
827
|
+
if (raw instanceof Date) now = raw.getTime();
|
|
828
|
+
else if (typeof raw === 'number' && Number.isFinite(raw)) now = raw;
|
|
829
|
+
else if (typeof raw === 'string' && Number.isFinite(Date.parse(raw))) now = Date.parse(raw);
|
|
830
|
+
else now = Date.now();
|
|
831
|
+
|
|
832
|
+
return Math.floor((expiresAt - now) / MS_PER_DAY);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Is this generated rule's `expires-at` already in the past?
|
|
837
|
+
*
|
|
838
|
+
* The defect it names (#1372): `rule-loader.mjs` filters an expired rule out at
|
|
839
|
+
* READ time, but nothing DELETES it — so the file keeps being counted by every
|
|
840
|
+
* byte ceiling above and keeps being loaded verbatim by Claude Code, which has
|
|
841
|
+
* no notion of `expires-at`.
|
|
842
|
+
*
|
|
843
|
+
* @param {{ meta?: Record<string, unknown>, now?: Date|number|string }} [opts]
|
|
844
|
+
* @returns {boolean}
|
|
845
|
+
*/
|
|
846
|
+
export function isExpiredGeneratedRule(opts = {}) {
|
|
847
|
+
const days = daysUntilGeneratedRuleExpiry(opts);
|
|
848
|
+
return days !== null && days < 0;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Enumerate the machine-generated rule files in `rulesDir`.
|
|
853
|
+
*
|
|
854
|
+
* Same population, same filter and same frontmatter parser as
|
|
855
|
+
* {@link measureRuleCorpora}'s `generated` surface — that is the point of
|
|
856
|
+
* exporting it: a consumer that needs the FILES rather than the BYTES (the
|
|
857
|
+
* expiry test, the session-start maintenance probe) must not mirror the
|
|
858
|
+
* predicate, because a mirror agrees only until one side is edited.
|
|
859
|
+
*
|
|
860
|
+
* Three-state, like every reader in the banner family: `ok: false` means the
|
|
861
|
+
* directory exists but could not be enumerated, which a caller must record as
|
|
862
|
+
* "undeterminable" and never as "no generated rules". A MISSING directory is
|
|
863
|
+
* `ok: true` with an empty list — a repo without `.claude/rules/` genuinely has
|
|
864
|
+
* none.
|
|
865
|
+
*
|
|
866
|
+
* @param {{ rulesDir?: string, repoRoot?: string }} [opts]
|
|
867
|
+
* @returns {{ ok: boolean, rules: Array<{ file: string, meta: Record<string, unknown>, expiresAt: string|null }> }}
|
|
868
|
+
*/
|
|
869
|
+
export function listMachineGeneratedRules(opts = {}) {
|
|
870
|
+
const dir =
|
|
871
|
+
typeof opts.rulesDir === 'string' && opts.rulesDir.length > 0
|
|
872
|
+
? opts.rulesDir
|
|
873
|
+
: join(opts.repoRoot ?? process.cwd(), '.claude', 'rules');
|
|
874
|
+
|
|
875
|
+
/** @type {Array<{ file: string, meta: Record<string, unknown>, expiresAt: string|null }>} */
|
|
876
|
+
const rules = [];
|
|
877
|
+
let names;
|
|
878
|
+
try {
|
|
879
|
+
names = readdirSync(dir);
|
|
880
|
+
} catch (err) {
|
|
881
|
+
// ENOENT is an answer ("no generated rules"); anything else is not knowing.
|
|
882
|
+
return { ok: err?.code === 'ENOENT', rules };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
for (const file of names) {
|
|
886
|
+
if (!file.endsWith('.md')) continue;
|
|
887
|
+
let meta;
|
|
888
|
+
try {
|
|
889
|
+
({ meta } = parseGlobsFrontmatter(readFileSync(join(dir, file), 'utf8')));
|
|
890
|
+
} catch {
|
|
891
|
+
continue; // unreadable or unparseable — skip, never throw
|
|
892
|
+
}
|
|
893
|
+
if (!isMachineGeneratedRule(meta)) continue;
|
|
894
|
+
const raw = meta?.['expires-at'];
|
|
895
|
+
rules.push({ file, meta, expiresAt: typeof raw === 'string' ? raw : null });
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return { ok: true, rules };
|
|
899
|
+
}
|
|
900
|
+
|
|
761
901
|
/**
|
|
762
902
|
* Single-pass scan of `.claude/rules/*.md` producing the TWO corpus measures
|
|
763
903
|
* this guard's third axis needs, which #1297 proved are NOT the same set:
|
|
@@ -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
|
+
}
|
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* locks/index.mjs — convenience barrel for the
|
|
3
|
-
* out of session-lock.mjs in #630 (A1 barrel-preserving split).
|
|
2
|
+
* locks/index.mjs — DEPRECATED convenience barrel for the lock protocols
|
|
3
|
+
* split out of session-lock.mjs in #630 (A1 barrel-preserving split).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the original
|
|
9
|
-
* importable directly.
|
|
5
|
+
* @deprecated since 5.2.0, removed in 6.0.0. Import directly from
|
|
6
|
+
* `./state-md-lock.mjs` / `./staging-fence-lock.mjs`, or from
|
|
7
|
+
* `scripts/lib/session-lock.mjs` for the canonical 22-symbol barrel that
|
|
8
|
+
* preserves the original import surface for all its importers.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* This file shipped in 5.1.0 with zero in-repo importers and was deleted in
|
|
11
|
+
* dd05e0f8 (`check-unwired-features` S4: no hook, npm script, CI job or husky
|
|
12
|
+
* stage reached it, and it named symbols — not modules — so it did not qualify
|
|
13
|
+
* for the pure `export *` exemption that shape gets). Restored here as a
|
|
14
|
+
* one-cycle deprecation shim (`.claude/rules/development.md` § Package Lifecycle
|
|
15
|
+
* & Versioning) because `package.json` carries no `exports` map: every packed
|
|
16
|
+
* file under `scripts/lib/` is public, so a consumer that deep-imported this
|
|
17
|
+
* path before the deletion has no compile error to warn it, only a runtime
|
|
18
|
+
* "module not found" — a shim buys that consumer one minor cycle to migrate
|
|
19
|
+
* before 6.0.0 removes the path outright.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately the `export *` form with ZERO named exports (matches the
|
|
22
|
+
* `scripts/lib/autopilot-telemetry.mjs` precedent): a named-export re-export
|
|
23
|
+
* barrel is exactly the shape `check-unwired-features.mjs` S4 flags as an
|
|
24
|
+
* unreachable library module (condition 4 in `collectUnreachableLibraryModules`
|
|
25
|
+
* requires named exports before a re-export shim over live code enters that
|
|
26
|
+
* census) — that flag is what got this file deleted the first time. A star
|
|
27
|
+
* re-export names no symbol, so the shim reads as `exports: []` and stays
|
|
28
|
+
* invisible to that census while its target modules stay reachable through it.
|
|
13
29
|
*/
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
releaseStateLock,
|
|
21
|
-
withStateMdLock,
|
|
22
|
-
} from './state-md-lock.mjs';
|
|
31
|
+
console.warn(
|
|
32
|
+
'[deprecated] scripts/lib/locks/index.mjs is deprecated since 5.2.0 and will be removed in 6.0.0 — ' +
|
|
33
|
+
"import from './state-md-lock.mjs' / './staging-fence-lock.mjs' directly, or from " +
|
|
34
|
+
'scripts/lib/session-lock.mjs for the canonical surface.',
|
|
35
|
+
);
|
|
23
36
|
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
DEFAULT_STAGING_FENCE_LOCK_TIMEOUT_MS,
|
|
27
|
-
STAGING_FENCE_LOCK_POLL_MS,
|
|
28
|
-
acquireStagingFenceLock,
|
|
29
|
-
releaseStagingFenceLock,
|
|
30
|
-
withStagingFenceLock,
|
|
31
|
-
} from './staging-fence-lock.mjs';
|
|
37
|
+
export * from './state-md-lock.mjs';
|
|
38
|
+
export * from './staging-fence-lock.mjs';
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* ## Design constraints it is written against
|
|
21
21
|
*
|
|
22
22
|
* - **HR-101 (a signal may only warn if it is rare).** Nothing is due ⇒ the
|
|
23
|
-
* probe is SILENT.
|
|
23
|
+
* probe is SILENT. Seven independent signals are ANDed with a cooldown, not
|
|
24
24
|
* ORed into a permanent warning: a repo that ran `/session housekeeping`
|
|
25
25
|
* within the last {@link HOUSEKEEPING_COOLDOWN_DAYS} days says nothing at
|
|
26
26
|
* all, because the operator already did the thing the banner would ask for.
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* an existing module constant. Same posture as `reconcile-nudge-banner.mjs`
|
|
34
34
|
* and `loop-readiness-banner.mjs`: an advisory banner needs no switch.
|
|
35
35
|
*
|
|
36
|
-
* ## The
|
|
36
|
+
* ## The seven signals
|
|
37
37
|
*
|
|
38
38
|
* | id | due when | source |
|
|
39
39
|
* |-------------------|-----------------------------------------------------------------------|--------|
|
|
@@ -43,6 +43,20 @@
|
|
|
43
43
|
* | `dialectic` | `shouldDispatchAutoDialectic().trigger === true` | `auto-dialectic.mjs` |
|
|
44
44
|
* | `memory-cleanup` | `shouldDispatchAutoDream().trigger === true` | `auto-dream.mjs` |
|
|
45
45
|
* | `pending-sidecar` | a pending dream/dialectic proposal younger than 14 days is unapplied | `.orchestrator/*-pending*.md` |
|
|
46
|
+
* | `generated-rules-expiring` | a machine-generated rule is expired or expires within {@link GENERATED_RULE_EXPIRY_HORIZON_DAYS} days | `instruction-budget-guard.mjs` |
|
|
47
|
+
*
|
|
48
|
+
* ### Why the expiry alarm is HERE and not in the test suite (#1372 follow-up)
|
|
49
|
+
*
|
|
50
|
+
* The invariant "no generated rule file sits expired in `.claude/rules/`" was
|
|
51
|
+
* first written as a vitest case comparing `expires-at` against TODAY. That is
|
|
52
|
+
* a calendar time-bomb in a BLOCKING gate: with nothing committed and nothing
|
|
53
|
+
* broken, `npm test` — pre-push hook and CI alike — turns red on the morning
|
|
54
|
+
* after the earliest date and blocks every unrelated hotfix, while the repair
|
|
55
|
+
* (consolidate the file, move its provenance pairs, delete it) is a human
|
|
56
|
+
* decision no test run can take. HR-101/HR-105: a signal must fire when
|
|
57
|
+
* something CHANGED, at a moment the operator can act, and be repairable by the
|
|
58
|
+
* thing that fires it. Session start, recommending `/session housekeeping`, is
|
|
59
|
+
* that moment; the predicate itself is still tested, against an INJECTED clock.
|
|
46
60
|
*
|
|
47
61
|
* Only SIDE-EFFECT-FREE signal functions are called — never a variant that
|
|
48
62
|
* advances `.orchestrator/dialectic-last-run`, because a probe that writes the
|
|
@@ -66,13 +80,32 @@ import { shouldDispatchAutoDream } from './auto-dream.mjs';
|
|
|
66
80
|
import { resolveMemoryDir } from './memory-paths.mjs';
|
|
67
81
|
import { readCanonicalSessions } from './sessions-canonical.mjs';
|
|
68
82
|
import { filterRealSessions } from './session-schema.mjs';
|
|
83
|
+
import {
|
|
84
|
+
daysUntilGeneratedRuleExpiry,
|
|
85
|
+
listMachineGeneratedRules,
|
|
86
|
+
} from './instruction-budget-guard.mjs';
|
|
69
87
|
|
|
70
88
|
/**
|
|
71
89
|
* How many signals this probe knows about. The banner denominator is this
|
|
72
90
|
* number MINUS the signals a kill-switch skipped on this host — see the
|
|
73
91
|
* `skipped` array in {@link computeMaintenanceDue}'s result.
|
|
74
92
|
*/
|
|
75
|
-
export const MAINTENANCE_TOTAL_SIGNALS =
|
|
93
|
+
export const MAINTENANCE_TOTAL_SIGNALS = 7;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* How far ahead the `generated-rules-expiring` signal looks.
|
|
97
|
+
*
|
|
98
|
+
* NAMED CEILING (BV-004): 7 days is one week of sessions — long enough that the
|
|
99
|
+
* operator meets the warning before the loader silently starts dropping the
|
|
100
|
+
* rule, short enough that it is not standing noise (HR-104: a signal that is
|
|
101
|
+
* essentially always present is no signal). On this repo's corpus at
|
|
102
|
+
* 2026-09-16 the earliest `expires-at` was 14 days out, so the row is silent.
|
|
103
|
+
*
|
|
104
|
+
* REVISIT TRIGGER: if the row is due on more than ~1 session start in 10, the
|
|
105
|
+
* horizon is measuring the reconcile engine's 30-day default TTL rather than an
|
|
106
|
+
* overdue consolidation — re-aim it (HR-101), never widen it.
|
|
107
|
+
*/
|
|
108
|
+
export const GENERATED_RULE_EXPIRY_HORIZON_DAYS = 7;
|
|
76
109
|
|
|
77
110
|
/**
|
|
78
111
|
* Active-learning floor for the `evolve` signal. Deliberately the SAME number
|
|
@@ -391,6 +424,39 @@ export async function computeMaintenanceDue(opts = {}) {
|
|
|
391
424
|
undeterminable.push('pending-sidecar');
|
|
392
425
|
}
|
|
393
426
|
|
|
427
|
+
// --- generated-rules-expiring (S7) ---------------------------------------
|
|
428
|
+
// The predicate and the population both come from `instruction-budget-guard`,
|
|
429
|
+
// which is where the generated corpus is DEFINED (its byte ceiling judges the
|
|
430
|
+
// same set). A local re-implementation would be a second definition of
|
|
431
|
+
// "machine-generated" that agrees only until one of the two is edited.
|
|
432
|
+
try {
|
|
433
|
+
const listed = listMachineGeneratedRules({ repoRoot });
|
|
434
|
+
if (!listed.ok) {
|
|
435
|
+
// The directory is there but unreadable — not knowing is not clean.
|
|
436
|
+
undeterminable.push('generated-rules-expiring');
|
|
437
|
+
} else {
|
|
438
|
+
const expiring = listed.rules
|
|
439
|
+
.map((r) => ({ ...r, days: daysUntilGeneratedRuleExpiry({ meta: r.meta, now }) }))
|
|
440
|
+
.filter((r) => r.days !== null && r.days <= GENERATED_RULE_EXPIRY_HORIZON_DAYS)
|
|
441
|
+
.sort((a, b) => a.days - b.days);
|
|
442
|
+
if (expiring.length > 0) {
|
|
443
|
+
// HR-106: name the files and the dates the verdict was computed from —
|
|
444
|
+
// the operator has to open exactly those files to repair this.
|
|
445
|
+
// HR-106 again, one step further: name the REPAIR, not only the
|
|
446
|
+
// finding. The sweep (#1377) is the mechanical half — prose out, the
|
|
447
|
+
// provenance pair kept as `markers only` — and an operator who is told
|
|
448
|
+
// only which files expired has to rediscover that the command exists.
|
|
449
|
+
markDue(
|
|
450
|
+
'generated-rules-expiring',
|
|
451
|
+
`${expiring.map((r) => `${r.file} ${r.expiresAt}`).join(', ')} → ` +
|
|
452
|
+
'node scripts/sweep-expired-rules.mjs (dry-run first)',
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
undeterminable.push('generated-rules-expiring');
|
|
458
|
+
}
|
|
459
|
+
|
|
394
460
|
return {
|
|
395
461
|
due,
|
|
396
462
|
// HR-106: the denominator counts the signals this host actually EVALUATED.
|
|
@@ -68,6 +68,7 @@ import { readLock, isLockLive, LOCK_PATH } from './session-lock.mjs';
|
|
|
68
68
|
import { checkPeerStateMd } from './state-md-peer-guard.mjs';
|
|
69
69
|
import { listWorktreesChecked } from './worktree/listing.mjs';
|
|
70
70
|
import { parseSessionId } from './session-id.mjs';
|
|
71
|
+
import { isMainModule } from './is-main-module.mjs';
|
|
71
72
|
|
|
72
73
|
/** Closed enum of provenance sources. */
|
|
73
74
|
const SOURCE_DISCOVERED = 'discovered'; // lock + registry unified (irreversibly merged upstream)
|
|
@@ -901,11 +902,7 @@ async function _cliMain(argv) {
|
|
|
901
902
|
}
|
|
902
903
|
|
|
903
904
|
const __filename = fileURLToPath(import.meta.url);
|
|
904
|
-
const _isCliMain =
|
|
905
|
-
typeof process !== 'undefined'
|
|
906
|
-
&& process.argv[1] !== null
|
|
907
|
-
&& process.argv[1] !== undefined
|
|
908
|
-
&& path.resolve(process.argv[1]) === path.resolve(__filename);
|
|
905
|
+
const _isCliMain =isMainModule(import.meta.url);
|
|
909
906
|
|
|
910
907
|
// Called WITHOUT `await` on purpose: a top-level await would make this module
|
|
911
908
|
// async for every importer, and `_cliMain` cannot reject (all paths are caught
|