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
|
@@ -198,8 +198,27 @@ export const LEARNING_KEY_RE = /^[a-z0-9/-]+$/;
|
|
|
198
198
|
/** `expires-at:` — YYYY-MM-DD. A garbage value makes the loader's expiry gate FAIL OPEN. */
|
|
199
199
|
export const EXPIRES_AT_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
200
200
|
|
|
201
|
-
/**
|
|
202
|
-
|
|
201
|
+
/**
|
|
202
|
+
* `learning-id` / `source-session` — rendered inside backticks, which an
|
|
203
|
+
* interior backtick would close.
|
|
204
|
+
*
|
|
205
|
+
* `/` is admitted since GH#71. A semantic session id is DERIVED FROM A BRANCH
|
|
206
|
+
* NAME, and branch names carry slashes — so the narrower class did not reject
|
|
207
|
+
* hostile text, it rejected a whole legitimate id family. The damage is silent
|
|
208
|
+
* and unbounded: `engine.mjs` degrades the throw to an audited rejection, so
|
|
209
|
+
* every learning from such a session is proposed on every run and rejected on
|
|
210
|
+
* every run, forever. Measured in THIS repo 2026-09-20 @ `9b118cf6`:
|
|
211
|
+
* `jq -r 'select(.session_id != null) | .session_id'
|
|
212
|
+
* .orchestrator/metrics/sessions.jsonl | grep -c '/'` → **3**, all of the
|
|
213
|
+
* `feat/operator-surface-2026-08-08-…` family; `git branch -a --format=…
|
|
214
|
+
* | grep -c '/'` → **6**. It hit two usable learnings in the reporting
|
|
215
|
+
* consumer's run (source: peer session eventdrop-at, 2026-09-20).
|
|
216
|
+
*
|
|
217
|
+
* Widening a SANITISER is only safe against a census of what the new character
|
|
218
|
+
* makes expressible at the call sites — see {@link assertNoPathEscape} for the
|
|
219
|
+
* one hazard `/` adds and for the four it does not.
|
|
220
|
+
*/
|
|
221
|
+
export const PROVENANCE_TOKEN_RE = /^[A-Za-z0-9._:/-]+$/;
|
|
203
222
|
|
|
204
223
|
// The Unicode Control category (C0 + DEL + C1). Deliberately expressed as
|
|
205
224
|
// `\p{Cc}` rather than a hand-written character class: an escape-written class
|
|
@@ -275,6 +294,51 @@ function rejection(field, why, value) {
|
|
|
275
294
|
return new Error(`reconcile-sanitize: rejecting record — ${field} ${why} (got ${shown})`);
|
|
276
295
|
}
|
|
277
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Reject the ONE hazard a `/` adds to a machine token: a path shape.
|
|
299
|
+
*
|
|
300
|
+
* Derived from the call sites, not from caution. Both slash-bearing patterns —
|
|
301
|
+
* {@link PROVENANCE_TOKEN_RE} and {@link LEARNING_KEY_RE} — route through
|
|
302
|
+
* {@link assertMachineToken}, so the guard lives in that ONE shared function
|
|
303
|
+
* rather than at each caller: one guard beats one-guard-per-call-site, and it
|
|
304
|
+
* covers the next caller of an EXPORTED assert by construction
|
|
305
|
+
* (`.claude/rules/build-value.md` BV-003).
|
|
306
|
+
*
|
|
307
|
+
* **What `/` does NOT make expressible**, so this guard deliberately does not
|
|
308
|
+
* re-check it (each is already structurally impossible in every pattern that
|
|
309
|
+
* admits a slash):
|
|
310
|
+
*
|
|
311
|
+
* 1. *Inline-code break-out.* Both provenance values render inside a backtick
|
|
312
|
+
* span (`renderer.mjs:469-470`); a backtick is in no slash-bearing class.
|
|
313
|
+
* 2. *Frontmatter escape.* A newline would open a sibling top-level key — but
|
|
314
|
+
* JS `$` without the `m` flag does not match before a trailing newline
|
|
315
|
+
* (measured 2026-09-20: `/^[A-Za-z0-9._:-]+$/.test("abc\n")` → `false`),
|
|
316
|
+
* so a trailing newline was and stays rejected.
|
|
317
|
+
* 3. *Wrapper forgery.* Every literal in {@link WRAPPER_FORGERY_LITERALS}
|
|
318
|
+
* needs `<`, `>`, `#`, a space or `(` — none of them in these classes.
|
|
319
|
+
* 4. *Dangerous invisibles.* Cf code points are outside these classes too.
|
|
320
|
+
*
|
|
321
|
+
* Gated on a literal `/` being present, which makes the guard a provable no-op
|
|
322
|
+
* for every token shape accepted BEFORE the widening: without a separator there
|
|
323
|
+
* is no traversal to express, and `..` alone in a backtick span is inert text.
|
|
324
|
+
* Census over the live store 2026-09-20 @ `9b118cf6`: 0 of 263 learning-keys and
|
|
325
|
+
* 0 learning ids carry `..` or a leading `/`, so nothing legitimate is caught.
|
|
326
|
+
*
|
|
327
|
+
* @param {string} value a value that already matched its pattern
|
|
328
|
+
* @param {string} field
|
|
329
|
+
* @returns {void}
|
|
330
|
+
* @throws {Error} on a leading `/` (absolute path) or any `..` (traversal)
|
|
331
|
+
*/
|
|
332
|
+
function assertNoPathEscape(value, field) {
|
|
333
|
+
if (!value.includes('/')) return;
|
|
334
|
+
if (value.startsWith('/')) {
|
|
335
|
+
throw rejection(field, 'must not start with "/" (absolute path)', value);
|
|
336
|
+
}
|
|
337
|
+
if (value.includes('..')) {
|
|
338
|
+
throw rejection(field, 'must not contain ".." (path traversal)', value);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
278
342
|
/**
|
|
279
343
|
* Assert that a machine value is a non-empty string matching `pattern`.
|
|
280
344
|
* REJECTS (throws) rather than repairing — see the module doc's dividing line.
|
|
@@ -282,7 +346,8 @@ function rejection(field, why, value) {
|
|
|
282
346
|
* @param {unknown} value
|
|
283
347
|
* @param {{ field: string, pattern: RegExp }} opts
|
|
284
348
|
* @returns {string} the value, unchanged, when it passes
|
|
285
|
-
* @throws {Error} when the value is not a string
|
|
349
|
+
* @throws {Error} when the value is not a string, does not match `pattern`, or
|
|
350
|
+
* carries a path escape (see {@link assertNoPathEscape})
|
|
286
351
|
*/
|
|
287
352
|
export function assertMachineToken(value, { field, pattern }) {
|
|
288
353
|
if (typeof value !== 'string' || value === '') {
|
|
@@ -291,6 +356,7 @@ export function assertMachineToken(value, { field, pattern }) {
|
|
|
291
356
|
if (!pattern.test(value)) {
|
|
292
357
|
throw rejection(field, `must match ${pattern.source}`, value);
|
|
293
358
|
}
|
|
359
|
+
assertNoPathEscape(value, field);
|
|
294
360
|
return value;
|
|
295
361
|
}
|
|
296
362
|
|
|
@@ -45,6 +45,14 @@
|
|
|
45
45
|
* - `surfaceTopN` (`./learnings/surface.mjs`) — the canonical active-learning
|
|
46
46
|
* filter (confidence > floor, not expired); called with a large cap to get
|
|
47
47
|
* the full active set rather than a top-N slice.
|
|
48
|
+
* - `countReconcileBacklog` (`./reconcile/backlog.mjs`) — the SAME eligibility +
|
|
49
|
+
* materialization partition `/reconcile` applies, imported from the LEAF module
|
|
50
|
+
* rather than from `./reconcile/engine.mjs`. The engine is WRITE-capable (it
|
|
51
|
+
* merges the candidate store) and drags the emitter, renderer and unicode
|
|
52
|
+
* validator behind it: importing the count from there took this probe's static
|
|
53
|
+
* closure from 11 modules / 151,946 B to 19 / 322,897 B (+112 %, measured
|
|
54
|
+
* 2026-09-18), inherited unasked by `maintenance-due-banner.mjs`. A banner must
|
|
55
|
+
* not be able to reach a writer.
|
|
48
56
|
* - `filterEligible` (`./reconcile/eligibility.mjs`) — the SAME type/file_paths
|
|
49
57
|
* allow-list gate the reconcile engine itself uses (deliberately NOT
|
|
50
58
|
* confidence-gated — mirrors `runReconcile`'s own posture, see
|
|
@@ -68,7 +76,7 @@ import { join } from 'node:path';
|
|
|
68
76
|
|
|
69
77
|
import { readLearnings } from './learnings/io.mjs';
|
|
70
78
|
import { surfaceTopN } from './learnings/surface.mjs';
|
|
71
|
-
import {
|
|
79
|
+
import { countReconcileBacklog } from './reconcile/backlog.mjs';
|
|
72
80
|
import { loadCandidates } from './reconcile/idempotency.mjs';
|
|
73
81
|
import { readConfigFile } from './config/io.mjs';
|
|
74
82
|
import { _parseReconcile } from './config/reconcile.mjs';
|
|
@@ -82,9 +90,67 @@ export const NUDGE_MIN_LEARNINGS = 20;
|
|
|
82
90
|
/** Nudge threshold (b): new learnings accrued since the last determinable reconcile run. */
|
|
83
91
|
export const NUDGE_MIN_DELTA = 15;
|
|
84
92
|
|
|
85
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Nudge threshold (c): reconcile BACKLOG — rule-eligible learnings (type + file_paths
|
|
95
|
+
* allow-list, not expired, regardless of confidence) MINUS those already materialized
|
|
96
|
+
* in the idempotency sidecar or a `.claude/rules/` provenance marker. Counting the
|
|
97
|
+
* materialized ones too made the probe un-greenable (#1380: 98 eligible, 35 already
|
|
98
|
+
* materialized, and no `/reconcile` run could ever clear it).
|
|
99
|
+
*/
|
|
86
100
|
export const NUDGE_MIN_ELIGIBLE = 3;
|
|
87
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the `reconcile` Session Config settings this module needs, in ONE read.
|
|
104
|
+
*
|
|
105
|
+
* Both consumers go through here, so the nudge can never judge on a different
|
|
106
|
+
* population than `/reconcile` does: the skill reads `reconcile.min-insight-chars`
|
|
107
|
+
* (default 24) and forwards it to the engine, and so must this probe. Before #1380
|
|
108
|
+
* follow-up the value was only read AFTER the backlog had already been counted, which
|
|
109
|
+
* left the placeholder-insight gate practically OFF for the banner.
|
|
110
|
+
*
|
|
111
|
+
* Precedence: an injected already-parsed Session Config wins per KEY (DI seam, avoids a
|
|
112
|
+
* second CLAUDE.md read); any key it does not carry falls back to parsing
|
|
113
|
+
* CLAUDE.md/AGENTS.md; an unreadable config falls back to `_parseReconcile('')`, i.e.
|
|
114
|
+
* the parser's OWN documented defaults — never a literal repeated here.
|
|
115
|
+
*
|
|
116
|
+
* Never throws.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} repoRoot
|
|
119
|
+
* @param {unknown} [config] already-parsed Session Config
|
|
120
|
+
* @returns {Promise<{enabled: boolean, minInsightChars: number}>}
|
|
121
|
+
*/
|
|
122
|
+
async function _resolveReconcileSettings(repoRoot, config) {
|
|
123
|
+
const injected =
|
|
124
|
+
config && typeof config === 'object' && /** @type {any} */ (config).reconcile &&
|
|
125
|
+
typeof (/** @type {any} */ (config).reconcile) === 'object'
|
|
126
|
+
? /** @type {Record<string, unknown>} */ (/** @type {any} */ (config).reconcile)
|
|
127
|
+
: null;
|
|
128
|
+
|
|
129
|
+
const hasEnabled = injected !== null && typeof injected.enabled === 'boolean';
|
|
130
|
+
const hasChars = injected !== null && Number.isFinite(injected['min-insight-chars']);
|
|
131
|
+
if (hasEnabled && hasChars) {
|
|
132
|
+
return {
|
|
133
|
+
enabled: /** @type {boolean} */ (injected.enabled),
|
|
134
|
+
minInsightChars: Number(injected['min-insight-chars']),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = _parseReconcile(await readConfigFile(repoRoot));
|
|
141
|
+
} catch {
|
|
142
|
+
// Config unreadable — take the parser's own defaults (single source of truth).
|
|
143
|
+
parsed = _parseReconcile('');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
enabled: hasEnabled ? /** @type {boolean} */ (injected.enabled) : parsed.enabled,
|
|
148
|
+
minInsightChars: hasChars
|
|
149
|
+
? Number(injected['min-insight-chars'])
|
|
150
|
+
: parsed['min-insight-chars'],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
88
154
|
/**
|
|
89
155
|
* Resolve the max `created_at` across reconcile-candidate sidecar records —
|
|
90
156
|
* the most recent reconcile run's timestamp, or `null` when no run is on
|
|
@@ -110,11 +176,19 @@ function _lastRunAt(candidates) {
|
|
|
110
176
|
*
|
|
111
177
|
* @param {object} [opts]
|
|
112
178
|
* @param {string} [opts.repoRoot] — project root (defaults to process.cwd()).
|
|
113
|
-
* @param {Date|number} [opts.now] — injectable clock, forwarded to the active-learning
|
|
179
|
+
* @param {Date|number} [opts.now] — injectable clock, forwarded to the active-learning
|
|
180
|
+
* filter AND the eligibility expiry gate.
|
|
181
|
+
* @param {number} [opts.minInsightChars] — forwarded to the eligibility filter. When
|
|
182
|
+
* OMITTED the value is read from Session Config (`reconcile.min-insight-chars`,
|
|
183
|
+
* default 24) rather than left inert, so the nudge and `/reconcile` count the SAME
|
|
184
|
+
* population — see `_resolveReconcileSettings`.
|
|
185
|
+
* @param {object} [opts.config] — optional already-parsed Session Config (DI seam).
|
|
114
186
|
* @returns {Promise<{
|
|
115
187
|
* totalLearnings: number,
|
|
116
188
|
* activeLearnings: number,
|
|
117
189
|
* eligibleCount: number,
|
|
190
|
+
* alreadyMaterialized: number,
|
|
191
|
+
* backlogCount: number,
|
|
118
192
|
* lastRunAt: string|null,
|
|
119
193
|
* lastRunCandidateCount: number,
|
|
120
194
|
* delta: number,
|
|
@@ -131,6 +205,8 @@ export async function computeReconcileNudge(opts = {}) {
|
|
|
131
205
|
totalLearnings: 0,
|
|
132
206
|
activeLearnings: 0,
|
|
133
207
|
eligibleCount: 0,
|
|
208
|
+
alreadyMaterialized: 0,
|
|
209
|
+
backlogCount: 0,
|
|
134
210
|
lastRunAt: null,
|
|
135
211
|
lastRunCandidateCount: 0,
|
|
136
212
|
delta: 0,
|
|
@@ -166,20 +242,29 @@ export async function computeReconcileNudge(opts = {}) {
|
|
|
166
242
|
|
|
167
243
|
if (active.length === 0) return empty;
|
|
168
244
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
245
|
+
// Same partition a real /reconcile run applies (engine.mjs step 3/3a) — one
|
|
246
|
+
// truth, so the nudge can only fire on work /reconcile is able to clear.
|
|
247
|
+
// That includes the placeholder-insight gate: when the caller did not supply
|
|
248
|
+
// `minInsightChars`, read it from Session Config BEFORE counting, or the
|
|
249
|
+
// banner counts learnings `/reconcile` would reject.
|
|
250
|
+
const minInsightChars = Number.isFinite(opts.minInsightChars)
|
|
251
|
+
? Number(opts.minInsightChars)
|
|
252
|
+
: (await _resolveReconcileSettings(repoRoot, opts.config)).minInsightChars;
|
|
175
253
|
|
|
254
|
+
// The candidate store is read ONCE, BEFORE the backlog count — the count needs
|
|
255
|
+
// the same records for its sidecar-dedupe half, and reading them here lets it
|
|
256
|
+
// be handed down instead of read a second time (`candidatesRead` says whether
|
|
257
|
+
// the hand-down is trustworthy; on a failed read we hand nothing down and let
|
|
258
|
+
// `countReconcileBacklog` decide for itself rather than pass a fake empty set).
|
|
176
259
|
/** @type {Array<Record<string, unknown>>} */
|
|
177
260
|
let candidates;
|
|
178
261
|
/** @type {number|undefined} */
|
|
179
262
|
let skippedCandidates;
|
|
263
|
+
let candidatesRead = false;
|
|
180
264
|
try {
|
|
181
265
|
const diag = loadCandidates({ repoRoot });
|
|
182
266
|
candidates = Array.isArray(diag?.records) ? diag.records : [];
|
|
267
|
+
candidatesRead = Array.isArray(diag?.records);
|
|
183
268
|
// Absence-preserving, mirroring `engine.mjs` `summary.skipped`: only a
|
|
184
269
|
// finite count means "the store was inspected". Absent ⇒ never checked,
|
|
185
270
|
// 0 ⇒ checked and clean.
|
|
@@ -190,6 +275,25 @@ export async function computeReconcileNudge(opts = {}) {
|
|
|
190
275
|
// fabricating a clean 0.
|
|
191
276
|
}
|
|
192
277
|
|
|
278
|
+
// ONE corpus, ONE population. `entries` (read above via `readLearnings`) is
|
|
279
|
+
// handed down instead of letting the count re-read and re-normalize the same
|
|
280
|
+
// file: the banner line names `activeLearnings` and `backlogCount` side by
|
|
281
|
+
// side, and two independent reads of one file are two populations wearing one
|
|
282
|
+
// sentence (HR-106). `repoRoot` stays required — the sidecar and the
|
|
283
|
+
// `.claude/rules/` provenance scan are resolved from it.
|
|
284
|
+
// Never throws (all-zero on failure).
|
|
285
|
+
const {
|
|
286
|
+
eligible: eligibleCount,
|
|
287
|
+
alreadyMaterialized,
|
|
288
|
+
backlog: backlogCount,
|
|
289
|
+
} = countReconcileBacklog({
|
|
290
|
+
repoRoot,
|
|
291
|
+
learnings: entries,
|
|
292
|
+
...(candidatesRead ? { existingCandidates: candidates } : {}),
|
|
293
|
+
now: opts.now,
|
|
294
|
+
minInsightChars,
|
|
295
|
+
});
|
|
296
|
+
|
|
193
297
|
const lastRunAt = _lastRunAt(candidates);
|
|
194
298
|
const lastRunCandidateCount = Array.isArray(candidates) ? candidates.length : 0;
|
|
195
299
|
const delta = entries.length - lastRunCandidateCount;
|
|
@@ -213,15 +317,18 @@ export async function computeReconcileNudge(opts = {}) {
|
|
|
213
317
|
if (lastRunAt !== null && delta > NUDGE_MIN_DELTA) {
|
|
214
318
|
reasons.push(`${delta} new learnings since the last reconcile run`);
|
|
215
319
|
}
|
|
216
|
-
// (c) — enough rule-eligible learnings to be worth a
|
|
217
|
-
|
|
218
|
-
|
|
320
|
+
// (c) — enough NOT-YET-MATERIALIZED rule-eligible learnings to be worth a
|
|
321
|
+
// batch, independent of confidence. HR-106: the reason names the number judged.
|
|
322
|
+
if (backlogCount >= NUDGE_MIN_ELIGIBLE) {
|
|
323
|
+
reasons.push(`${backlogCount} rule-eligible learnings awaiting a rule`);
|
|
219
324
|
}
|
|
220
325
|
|
|
221
326
|
const computed = {
|
|
222
327
|
totalLearnings: entries.length,
|
|
223
328
|
activeLearnings: active.length,
|
|
224
329
|
eligibleCount,
|
|
330
|
+
alreadyMaterialized,
|
|
331
|
+
backlogCount,
|
|
225
332
|
lastRunAt,
|
|
226
333
|
lastRunCandidateCount,
|
|
227
334
|
delta,
|
|
@@ -236,25 +343,6 @@ export async function computeReconcileNudge(opts = {}) {
|
|
|
236
343
|
return computed;
|
|
237
344
|
}
|
|
238
345
|
|
|
239
|
-
/**
|
|
240
|
-
* Best-effort extraction of `reconcile.enabled` from an already-parsed Session
|
|
241
|
-
* Config object (DI/test seam — avoids re-reading CLAUDE.md when the caller
|
|
242
|
-
* already has it). Returns `null` when unresolvable from `config` alone, in
|
|
243
|
-
* which case `checkReconcileNudge` falls back to reading CLAUDE.md/AGENTS.md.
|
|
244
|
-
*
|
|
245
|
-
* @param {unknown} config
|
|
246
|
-
* @returns {boolean|null}
|
|
247
|
-
*/
|
|
248
|
-
function _reconcileEnabledFromConfig(config) {
|
|
249
|
-
if (config && typeof config === 'object') {
|
|
250
|
-
const rc = /** @type {Record<string, unknown>} */ (config).reconcile;
|
|
251
|
-
if (rc && typeof rc === 'object' && typeof (/** @type {any} */ (rc).enabled) === 'boolean') {
|
|
252
|
-
return /** @type {any} */ (rc).enabled;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
346
|
/**
|
|
259
347
|
* Check reconcile-nudge readiness and produce a session-start banner.
|
|
260
348
|
*
|
|
@@ -274,26 +362,30 @@ export async function checkReconcileNudge(opts = {}) {
|
|
|
274
362
|
const { repoRoot, config, now } = opts;
|
|
275
363
|
if (!repoRoot || typeof repoRoot !== 'string') return null;
|
|
276
364
|
|
|
365
|
+
// ONE config read for both settings, BEFORE the backlog is counted — the
|
|
366
|
+
// `min-insight-chars` gate must reach `countReconcileBacklog`, and reading the
|
|
367
|
+
// config afterwards (as this did before) left it inert.
|
|
368
|
+
let reconcileEnabled = false;
|
|
369
|
+
let minInsightChars;
|
|
370
|
+
try {
|
|
371
|
+
({ enabled: reconcileEnabled, minInsightChars } = await _resolveReconcileSettings(
|
|
372
|
+
repoRoot,
|
|
373
|
+
config,
|
|
374
|
+
));
|
|
375
|
+
} catch {
|
|
376
|
+
// Unreachable in practice (_resolveReconcileSettings never throws) — keep the
|
|
377
|
+
// conservative default and let computeReconcileNudge resolve the gate itself.
|
|
378
|
+
reconcileEnabled = false;
|
|
379
|
+
}
|
|
380
|
+
|
|
277
381
|
let computed;
|
|
278
382
|
try {
|
|
279
|
-
computed = await computeReconcileNudge({ repoRoot, now });
|
|
383
|
+
computed = await computeReconcileNudge({ repoRoot, now, minInsightChars });
|
|
280
384
|
} catch {
|
|
281
385
|
return null;
|
|
282
386
|
}
|
|
283
387
|
if (!computed || computed.nudge !== true) return null;
|
|
284
388
|
|
|
285
|
-
let reconcileEnabled = _reconcileEnabledFromConfig(config);
|
|
286
|
-
if (reconcileEnabled === null) {
|
|
287
|
-
try {
|
|
288
|
-
const content = await readConfigFile(repoRoot);
|
|
289
|
-
reconcileEnabled = _parseReconcile(content).enabled;
|
|
290
|
-
} catch {
|
|
291
|
-
// Config unreadable — fall back to the documented config default (false)
|
|
292
|
-
// so the informational parenthetical still renders conservatively.
|
|
293
|
-
reconcileEnabled = false;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
389
|
// Three-state last-run label. `never` is a claim about history and must only
|
|
298
390
|
// be made when the store was inspected and held nothing: a store whose lines
|
|
299
391
|
// were quarantined by the shape guard is EVIDENCE OF A RUN that can no longer
|
|
@@ -323,7 +415,8 @@ export async function checkReconcileNudge(opts = {}) {
|
|
|
323
415
|
}
|
|
324
416
|
|
|
325
417
|
const lines = [
|
|
326
|
-
`⚠ reconcile-nudge: ${computed.activeLearnings} active learnings, ${computed.
|
|
418
|
+
`⚠ reconcile-nudge: ${computed.activeLearnings} active learnings, ${computed.backlogCount} rule-eligible awaiting a rule ` +
|
|
419
|
+
`(${computed.alreadyMaterialized} already materialized), ` +
|
|
327
420
|
`last reconcile run: ${lastRunLabel} — run /reconcile to convert learnings into rules.`,
|
|
328
421
|
];
|
|
329
422
|
if (reconcileEnabled === false) {
|
|
@@ -30,6 +30,37 @@ export function parseEtimeToMinutes(etime) {
|
|
|
30
30
|
return days * 24 * 60 + hours * 60 + mins;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Parse a `ps` etime field (`[[DD-]HH:]MM:SS`) into whole SECONDS.
|
|
35
|
+
*
|
|
36
|
+
* Twin of {@link parseEtimeToMinutes}, which drops the seconds field because
|
|
37
|
+
* minute resolution is all the zombie counter needs. The orphan reaper
|
|
38
|
+
* (`scripts/lib/orphan-reaper.mjs`) cannot use that: its minimum age is 300
|
|
39
|
+
* seconds and its identity check reconstructs a start time as
|
|
40
|
+
* `now - etimeSeconds * 1000`, where a discarded seconds field would be a
|
|
41
|
+
* systematic error of up to 59 s against a 2 s tolerance.
|
|
42
|
+
*
|
|
43
|
+
* Deliberately a sibling rather than a refactor of the twin: `parseEtimeToMinutes`
|
|
44
|
+
* has live callers (`countZombieProcesses`) whose behaviour must not shift.
|
|
45
|
+
*
|
|
46
|
+
* Returns null when the format is unrecognised. Pure function.
|
|
47
|
+
* @param {string} etime
|
|
48
|
+
* @returns {number|null}
|
|
49
|
+
*/
|
|
50
|
+
export function parseEtimeToSeconds(etime) {
|
|
51
|
+
if (typeof etime !== 'string') return null;
|
|
52
|
+
const s = etime.trim();
|
|
53
|
+
// Same shape as parseEtimeToMinutes: optional "DD-", optional "HH:", then "MM:SS".
|
|
54
|
+
const m = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(s);
|
|
55
|
+
if (!m) return null;
|
|
56
|
+
const days = parseInt(m[1] ?? '0', 10);
|
|
57
|
+
const hours = parseInt(m[2] ?? '0', 10);
|
|
58
|
+
const mins = parseInt(m[3], 10);
|
|
59
|
+
const secs = parseInt(m[4], 10);
|
|
60
|
+
if ([days, hours, mins, secs].some(Number.isNaN)) return null;
|
|
61
|
+
return days * 86400 + hours * 3600 + mins * 60 + secs;
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
/**
|
|
34
65
|
* Parse the detailed ps output and count Claude/Node zombie candidates.
|
|
35
66
|
* A zombie candidate is a process matching "claude" or "node" that:
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* when at least one `scopePath` matches at least one glob pattern.
|
|
12
12
|
*
|
|
13
13
|
* `paths:` is a same-shape alias for `globs:` (issue #795) — some repos use a
|
|
14
|
-
* `paths:` frontmatter convention instead of `globs:` (
|
|
15
|
-
*
|
|
14
|
+
* `paths:` frontmatter convention instead of `globs:` (projects-baseline did
|
|
15
|
+
* when #795 landed; it now carries both keys — measured count:
|
|
16
|
+
* docs/baseline.md). Before #795 these repos'
|
|
16
17
|
* path-scoped rules were silently misclassified as always-on, inflating the
|
|
17
18
|
* `instruction-budget-guard.mjs` (#687) always-on count with a false positive.
|
|
18
19
|
* `paths:` supports the identical inline-array and block-list forms as
|
|
@@ -365,6 +366,35 @@ function metaToEntryFields(meta) {
|
|
|
365
366
|
return out;
|
|
366
367
|
}
|
|
367
368
|
|
|
369
|
+
/**
|
|
370
|
+
* The expiry predicate, shared by the injection gate below and the `/reconcile`
|
|
371
|
+
* provenance reader (`scripts/lib/reconcile/backlog.mjs`) — one `Date.parse`
|
|
372
|
+
* for both, so "expired" can never mean two different things to the two
|
|
373
|
+
* consumers (#1387).
|
|
374
|
+
*
|
|
375
|
+
* FAIL-OPEN by design: an absent, empty or unparseable `expires-at` is NOT
|
|
376
|
+
* expired. Fail-closed here would un-inject (and, in the reconcile reader,
|
|
377
|
+
* un-materialize) the whole corpus on a single typo.
|
|
378
|
+
*
|
|
379
|
+
* Pure apart from the optional `onUnparseable` callback, which exists so the
|
|
380
|
+
* injection path can keep its stderr WARN while the reconcile reader stays
|
|
381
|
+
* silent.
|
|
382
|
+
*
|
|
383
|
+
* @param {unknown} rawExpiry - the raw `expires-at` frontmatter value (or undefined)
|
|
384
|
+
* @param {number} [now] - epoch ms; defaults to `Date.now()`
|
|
385
|
+
* @param {(raw: unknown) => void} [onUnparseable] - called when `expires-at` is present but unparseable
|
|
386
|
+
* @returns {boolean} true only when a PARSEABLE `expires-at` lies before `now`
|
|
387
|
+
*/
|
|
388
|
+
export function isRuleExpired(rawExpiry, now = Date.now(), onUnparseable) {
|
|
389
|
+
if (rawExpiry === undefined || rawExpiry === null) return false;
|
|
390
|
+
const ts = Date.parse(String(rawExpiry));
|
|
391
|
+
if (Number.isNaN(ts)) {
|
|
392
|
+
if (typeof onUnparseable === 'function') onUnparseable(rawExpiry);
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
return ts < now;
|
|
396
|
+
}
|
|
397
|
+
|
|
368
398
|
/**
|
|
369
399
|
* Deterministic gating check (issue #694 + #692). Returns `{ excluded: boolean,
|
|
370
400
|
* reason?: string }`. Applied to BOTH always-on and glob-matched rules.
|
|
@@ -378,18 +408,17 @@ function metaToEntryFields(meta) {
|
|
|
378
408
|
* @returns {{ excluded: boolean }}
|
|
379
409
|
*/
|
|
380
410
|
function applyGates(meta, filePath, mode, hostClass, now, context = null) {
|
|
381
|
-
// Expiry gate — fail-open on a malformed `expires-at
|
|
411
|
+
// Expiry gate — fail-open on a malformed `expires-at` ({@link isRuleExpired}).
|
|
382
412
|
if (Object.prototype.hasOwnProperty.call(meta, 'expires-at')) {
|
|
383
413
|
const rawExpiry = meta['expires-at'];
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
);
|
|
414
|
+
if (
|
|
415
|
+
isRuleExpired(rawExpiry, now, (raw) =>
|
|
416
|
+
process.stderr.write(
|
|
417
|
+
`[rule-loader] Rule ${filePath} has unparseable expires-at ${JSON.stringify(raw)} — ignoring expiry\n`,
|
|
418
|
+
),
|
|
419
|
+
)
|
|
420
|
+
) {
|
|
421
|
+
process.stderr.write(`[rule-loader] Rule ${filePath} expired at ${rawExpiry} — excluded\n`);
|
|
393
422
|
return { excluded: true };
|
|
394
423
|
}
|
|
395
424
|
}
|
|
@@ -10,6 +10,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
10
10
|
import { join, basename, dirname, resolve } from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { validateRuleContent, scanVendoringLeaks } from './validate-vendored-rules.mjs';
|
|
13
|
+
import { isMainModule } from './is-main-module.mjs';
|
|
13
14
|
|
|
14
15
|
// The vendoring sanitizer (issue #1098) lives in validate-vendored-rules.mjs —
|
|
15
16
|
// it has that module's shape ("judge one rule file → findings") and its
|
|
@@ -451,11 +452,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
451
452
|
const __dirname = dirname(__filename);
|
|
452
453
|
|
|
453
454
|
// Detect direct execution: node scripts/lib/rules-sync.mjs
|
|
454
|
-
const isMain =
|
|
455
|
-
typeof process !== 'undefined' &&
|
|
456
|
-
process.argv[1] !== null &&
|
|
457
|
-
process.argv[1] !== undefined &&
|
|
458
|
-
resolve(process.argv[1]) === resolve(__filename);
|
|
455
|
+
const isMain =isMainModule(import.meta.url);
|
|
459
456
|
|
|
460
457
|
if (isMain) {
|
|
461
458
|
const args = process.argv.slice(2);
|