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
|
@@ -6,10 +6,18 @@
|
|
|
6
6
|
* 1. `/evolve` learnings (`.orchestrator/metrics/learnings.jsonl` records)
|
|
7
7
|
* 2. `claude-md-drift-check` output (`driftResult.errors[]`)
|
|
8
8
|
*
|
|
9
|
-
* This module
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* This module WRITES nothing. Its only read is target resolution for the
|
|
10
|
+
* learnings feeder: at most one `git ls-files` per `extractCandidates` call
|
|
11
|
+
* (lazy — only when a learning survives the cheaper filters), plus a
|
|
12
|
+
* `realpathSync` containment check that applies with or without git. A
|
|
13
|
+
* learning becomes a candidate only when its extracted path resolves — symlinks
|
|
14
|
+
* followed — to a regular file inside the realpath of `repoRoot` that is also
|
|
15
|
+
* the ONE tracked file it names (tracking is skipped when git is unavailable);
|
|
16
|
+
* the kept `target_path` is the repo-relative form of that resolved file, so a
|
|
17
|
+
* bare basename becomes its full path and `scripts/../x.mjs` becomes `x.mjs`. Persistence and
|
|
18
|
+
* the `processed_at` / `superseded_by` lifecycle are OWNED BY the sibling
|
|
19
|
+
* `idempotency.mjs` module — this module only emits the raw candidates with
|
|
20
|
+
* those fields nulled out.
|
|
13
21
|
*
|
|
14
22
|
* The `id` field is a deterministic short hash of (source, target_path,
|
|
15
23
|
* fingerprint), so the same input always yields the same id. That determinism
|
|
@@ -18,7 +26,10 @@
|
|
|
18
26
|
* Part of Epic #643 → issue #647 (C2 auto-repair engine).
|
|
19
27
|
*/
|
|
20
28
|
|
|
29
|
+
import { execFileSync } from 'node:child_process';
|
|
21
30
|
import { createHash } from 'node:crypto';
|
|
31
|
+
import { realpathSync, statSync } from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
35
|
* @typedef {Object} RepairCandidate
|
|
@@ -96,6 +107,104 @@ function extractPath(text) {
|
|
|
96
107
|
return match ? match[0] : null;
|
|
97
108
|
}
|
|
98
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Build the target resolver for ONE extraction run. `PATH_RE` also admits bare
|
|
112
|
+
* basenames (`engine.mjs`, `mcp.json`) that exist nowhere at the repo root, so
|
|
113
|
+
* an extracted path is a claim, not a target, until it resolves here.
|
|
114
|
+
*
|
|
115
|
+
* Resolution is fail-closed — `null` drops the candidate. In BOTH modes the
|
|
116
|
+
* path is resolved with `realpathSync` (every symlink followed) and must land
|
|
117
|
+
* on a regular file inside `realpathSync(repoRoot)`; any resolution error
|
|
118
|
+
* drops it. The kept `target_path` is the repo-relative POSIX form of that
|
|
119
|
+
* RESOLVED path, so `scripts/../CLAUDE.md` and `CLAUDE.md` name one target
|
|
120
|
+
* (and mint one id). Containment follows `classifyTarget` in
|
|
121
|
+
* `blast-radius-classifier.mjs`, plus the realpath of the target itself.
|
|
122
|
+
*
|
|
123
|
+
* With git, the resolved path must additionally be a tracked file:
|
|
124
|
+
* - a path containing `/` is resolved as given;
|
|
125
|
+
* - a bare basename must match exactly ONE tracked file (zero = unresolvable,
|
|
126
|
+
* two or more = ambiguous) and is resolved from that file's full path.
|
|
127
|
+
* A tracked symlink whose target lies outside the repo is therefore dropped.
|
|
128
|
+
*
|
|
129
|
+
* The tracked-file index is read lazily and at most once per resolver
|
|
130
|
+
* (`git ls-files -z` in `repoRoot`), so a run without a qualifying learning
|
|
131
|
+
* never spawns git. Without git (binary missing, not a repository) only the
|
|
132
|
+
* realpath containment applies — a bare basename then resolves only at the
|
|
133
|
+
* root, since nothing enumerates the tree.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} repoRoot
|
|
136
|
+
* @returns {(extracted: string) => string|null}
|
|
137
|
+
*/
|
|
138
|
+
function makeTargetResolver(repoRoot) {
|
|
139
|
+
/** @type {{ tracked: Set<string>, byBasename: Map<string, string[]> }|null|undefined} */
|
|
140
|
+
let index; // undefined = not read yet, null = git unavailable
|
|
141
|
+
|
|
142
|
+
// Canonicalise the root once, as blast-radius-classifier does: a symlinked
|
|
143
|
+
// repoRoot (macOS /var → /private/var) must still anchor the escape check.
|
|
144
|
+
let root;
|
|
145
|
+
try {
|
|
146
|
+
root = realpathSync(path.resolve(repoRoot));
|
|
147
|
+
} catch {
|
|
148
|
+
root = path.resolve(repoRoot);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function readIndex() {
|
|
152
|
+
try {
|
|
153
|
+
const out = execFileSync('git', ['ls-files', '-z'], {
|
|
154
|
+
cwd: repoRoot,
|
|
155
|
+
encoding: 'utf8',
|
|
156
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
157
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
158
|
+
});
|
|
159
|
+
const tracked = new Set(out.split('\0').filter(Boolean));
|
|
160
|
+
/** @type {Map<string, string[]>} */
|
|
161
|
+
const byBasename = new Map();
|
|
162
|
+
for (const file of tracked) {
|
|
163
|
+
const base = file.slice(file.lastIndexOf('/') + 1);
|
|
164
|
+
const list = byBasename.get(base);
|
|
165
|
+
if (list) list.push(file);
|
|
166
|
+
else byBasename.set(base, [file]);
|
|
167
|
+
}
|
|
168
|
+
return { tracked, byBasename };
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Realpath-resolve `rel` against the canonical root.
|
|
176
|
+
* @param {string} rel
|
|
177
|
+
* @returns {string|null} repo-relative POSIX path of the regular file it
|
|
178
|
+
* resolves to, or null when it escapes the root, is not a regular file, or
|
|
179
|
+
* does not resolve at all.
|
|
180
|
+
*/
|
|
181
|
+
function resolveInsideRoot(rel) {
|
|
182
|
+
let real;
|
|
183
|
+
try {
|
|
184
|
+
real = realpathSync(path.resolve(root, rel));
|
|
185
|
+
if (!statSync(real).isFile()) return null;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const back = path.relative(root, real);
|
|
190
|
+
if (back === '' || back.startsWith('..') || path.isAbsolute(back)) return null;
|
|
191
|
+
return back.split(path.sep).join('/');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return (extracted) => {
|
|
195
|
+
if (index === undefined) index = readIndex();
|
|
196
|
+
if (index === null) return resolveInsideRoot(extracted);
|
|
197
|
+
let candidate = extracted;
|
|
198
|
+
if (!extracted.includes('/')) {
|
|
199
|
+
const matches = index.byBasename.get(extracted) ?? [];
|
|
200
|
+
if (matches.length !== 1) return null;
|
|
201
|
+
candidate = matches[0];
|
|
202
|
+
}
|
|
203
|
+
const resolved = resolveInsideRoot(candidate);
|
|
204
|
+
return resolved !== null && index.tracked.has(resolved) ? resolved : null;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
99
208
|
/**
|
|
100
209
|
* Map a single learning record to a RepairCandidate, or null when it fails any
|
|
101
210
|
* actionable filter. learnings.jsonl records are NOT uniform — only `confidence`
|
|
@@ -104,9 +213,10 @@ function extractPath(text) {
|
|
|
104
213
|
* @param {Record<string, unknown>} learning
|
|
105
214
|
* @param {number} evidenceFloor
|
|
106
215
|
* @param {string} nowIso
|
|
216
|
+
* @param {(extracted: string) => string|null} resolveTarget
|
|
107
217
|
* @returns {RepairCandidate|null}
|
|
108
218
|
*/
|
|
109
|
-
function learningToCandidate(learning, evidenceFloor, nowIso) {
|
|
219
|
+
function learningToCandidate(learning, evidenceFloor, nowIso, resolveTarget) {
|
|
110
220
|
if (!learning || typeof learning !== 'object') return null;
|
|
111
221
|
|
|
112
222
|
// Filter 1: confidence gate.
|
|
@@ -123,13 +233,18 @@ function learningToCandidate(learning, evidenceFloor, nowIso) {
|
|
|
123
233
|
const subject = typeof learning.subject === 'string' ? learning.subject : '';
|
|
124
234
|
const insight = typeof learning.insight === 'string' ? learning.insight : '';
|
|
125
235
|
|
|
126
|
-
// Filter 2:
|
|
127
|
-
const
|
|
128
|
-
if (!
|
|
236
|
+
// Filter 2: a path-shaped token in subject OR insight.
|
|
237
|
+
const extracted = extractPath(subject) ?? extractPath(insight);
|
|
238
|
+
if (!extracted) return null;
|
|
129
239
|
|
|
130
240
|
// Filter 3: insight must be prescriptive.
|
|
131
241
|
if (!PRESCRIPTIVE_RE.test(insight)) return null;
|
|
132
242
|
|
|
243
|
+
// Filter 5: the token resolves to exactly one tracked file (last — it is the
|
|
244
|
+
// only filter that may spawn git).
|
|
245
|
+
const targetPath = resolveTarget(extracted);
|
|
246
|
+
if (!targetPath) return null;
|
|
247
|
+
|
|
133
248
|
const source = 'evolve-learning';
|
|
134
249
|
const sourceRef = typeof learning.id === 'string' && learning.id.length > 0
|
|
135
250
|
? learning.id
|
|
@@ -218,7 +333,9 @@ function driftErrorToCandidate(err, nowIso) {
|
|
|
218
333
|
|
|
219
334
|
/**
|
|
220
335
|
* Ingest the two repair-candidate feeders and return a normalised
|
|
221
|
-
* `RepairCandidate[]`.
|
|
336
|
+
* `RepairCandidate[]`. Writes nothing; learnings are resolved against the files
|
|
337
|
+
* tracked in `repoRoot` (see `makeTargetResolver`). Drift errors pass through
|
|
338
|
+
* unresolved — the checker reported them from a file it read.
|
|
222
339
|
*
|
|
223
340
|
* @param {Object} params
|
|
224
341
|
* @param {Array<Record<string, unknown>>} [params.learnings] - `/evolve` learning records.
|
|
@@ -226,7 +343,10 @@ function driftErrorToCandidate(err, nowIso) {
|
|
|
226
343
|
* claude-md-drift-check output. Only `errors[]` are mapped; `warnings[]`
|
|
227
344
|
* are skipped. A null result, or a status of `skipped`/`skipped-mode-off`/
|
|
228
345
|
* undefined, emits zero drift candidates.
|
|
229
|
-
* @param {string} [params.repoRoot] - repo root
|
|
346
|
+
* @param {string} [params.repoRoot] - repo root learning targets must resolve in
|
|
347
|
+
* (default `process.cwd()`). A learning is dropped unless its extracted
|
|
348
|
+
* path realpath-resolves to a regular file inside this root that is
|
|
349
|
+
* exactly one tracked file there (tracking skipped without git).
|
|
230
350
|
* @param {number} [params.evidenceFloor=0.5] - minimum learning confidence to qualify.
|
|
231
351
|
* @param {string} [params.now] - ISO timestamp for `created_at` + expiry checks (test determinism).
|
|
232
352
|
* @returns {RepairCandidate[]}
|
|
@@ -234,13 +354,14 @@ function driftErrorToCandidate(err, nowIso) {
|
|
|
234
354
|
export function extractCandidates({
|
|
235
355
|
learnings,
|
|
236
356
|
driftResult,
|
|
237
|
-
// eslint-disable-next-line no-unused-vars -- accepted for caller symmetry; pure transform does no path resolution
|
|
238
357
|
repoRoot,
|
|
239
358
|
evidenceFloor = 0.5,
|
|
240
359
|
now,
|
|
241
360
|
} = {}) {
|
|
242
361
|
const nowIso = typeof now === 'string' && now.length > 0 ? now : new Date().toISOString();
|
|
243
362
|
const floor = Number.isFinite(evidenceFloor) ? evidenceFloor : 0.5;
|
|
363
|
+
const root = typeof repoRoot === 'string' && repoRoot.length > 0 ? repoRoot : process.cwd();
|
|
364
|
+
const resolveTarget = makeTargetResolver(root);
|
|
244
365
|
|
|
245
366
|
/** @type {RepairCandidate[]} */
|
|
246
367
|
const candidates = [];
|
|
@@ -248,7 +369,7 @@ export function extractCandidates({
|
|
|
248
369
|
// Feeder 1: /evolve learnings.
|
|
249
370
|
if (Array.isArray(learnings)) {
|
|
250
371
|
for (const learning of learnings) {
|
|
251
|
-
const candidate = learningToCandidate(learning, floor, nowIso);
|
|
372
|
+
const candidate = learningToCandidate(learning, floor, nowIso, resolveTarget);
|
|
252
373
|
if (candidate) candidates.push(candidate);
|
|
253
374
|
}
|
|
254
375
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* — the matrix is the load-bearing deliverable, not the file-write mechanics.
|
|
9
9
|
*
|
|
10
10
|
* COMPOSITION (the five siblings, all DI-injectable via `opts`):
|
|
11
|
-
* - candidate-intake.mjs → extractCandidates (
|
|
11
|
+
* - candidate-intake.mjs → extractCandidates (no writes; resolves targets against repoRoot)
|
|
12
12
|
* - idempotency.mjs → mergeCandidates / markProcessed / isProcessed
|
|
13
13
|
* / loadCandidates (store I/O + supersession)
|
|
14
14
|
* - blast-radius-classifier.mjs→ classifyTarget (R5 posture/gate triple)
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* PIPELINE:
|
|
19
19
|
* extractCandidates(learnings, driftResult)
|
|
20
|
-
* → mergeCandidates (persist + supersede)
|
|
20
|
+
* → mergeCandidates (persist + supersede; skipped under dryRun)
|
|
21
21
|
* → for each NON-processed candidate:
|
|
22
22
|
* classify → decide per matrix → act
|
|
23
23
|
*
|
|
@@ -583,7 +583,8 @@ async function finishOpenMr({ candidate, candidateId, targetPath, targetType, re
|
|
|
583
583
|
* @param {Array<Record<string, unknown>>} [params.learnings] — `/evolve` learning records.
|
|
584
584
|
* @param {{ status?: string, errors?: Array<Record<string, unknown>> }|null} [params.driftResult]
|
|
585
585
|
* claude-md-drift-check output.
|
|
586
|
-
* @param {boolean} [params.dryRun=false] — when true, no mutation/MR/stamp
|
|
586
|
+
* @param {boolean} [params.dryRun=false] — when true, no mutation/MR/stamp and no
|
|
587
|
+
* candidate-store write (the skip is logged via `opts.log`); previews only.
|
|
587
588
|
* @param {Object} [opts] — DI seams (all default to the real sibling functions).
|
|
588
589
|
* @param {typeof realExtractCandidates} [opts.extractCandidates]
|
|
589
590
|
* @param {typeof realMergeCandidates} [opts.mergeCandidates]
|
|
@@ -629,7 +630,7 @@ export async function runRepairEngine(
|
|
|
629
630
|
const outcomes = [];
|
|
630
631
|
const summary = { autonomousApplied: 0, mrsOpened: 0, advisories: 0, blocked: 0, total: 0 };
|
|
631
632
|
|
|
632
|
-
// --- Pipeline step 1 — intake (
|
|
633
|
+
// --- Pipeline step 1 — intake (no writes; repoRoot anchors target resolution)
|
|
633
634
|
let candidates;
|
|
634
635
|
try {
|
|
635
636
|
candidates = seams.extractCandidates({
|
|
@@ -650,11 +651,19 @@ export async function runRepairEngine(
|
|
|
650
651
|
}
|
|
651
652
|
|
|
652
653
|
// --- Pipeline step 2 — merge (persist + supersede) -----------------------
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
654
|
+
// A dry run previews from the in-memory candidates and never writes the store.
|
|
655
|
+
if (dryRun === true) {
|
|
656
|
+
seams.log(
|
|
657
|
+
'info',
|
|
658
|
+
`engine: dry-run — candidate store write skipped (${candidates.length} candidate(s) not persisted)`,
|
|
659
|
+
);
|
|
660
|
+
} else {
|
|
661
|
+
try {
|
|
662
|
+
seams.mergeCandidates({ candidates, repoRoot });
|
|
663
|
+
} catch (err) {
|
|
664
|
+
seams.log('error', `engine: mergeCandidates threw — ${err?.message ?? err}`);
|
|
665
|
+
// Persistence failure is non-fatal: continue with the in-memory candidates.
|
|
666
|
+
}
|
|
658
667
|
}
|
|
659
668
|
|
|
660
669
|
// --- Pipeline step 3 — per-candidate classify → decide → act -------------
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* - estimateInputTokens(str) — char-count/4 heuristic
|
|
32
32
|
* - checkBudget(estimated, budget) — verdict for the budget gate
|
|
33
33
|
* - buildJudgePrompt(skills, tail, nonce) — pure prompt assembly (untrusted-data fence)
|
|
34
|
+
* - evidenceBudgetChars(skills, budget) — characters left for evidence after the prompt frame
|
|
34
35
|
* - parseJudgeResponse(text) — extract one fenced ```json block, validate, drop malformed
|
|
35
36
|
*/
|
|
36
37
|
|
|
@@ -54,6 +55,9 @@ export const DEFAULT_BUDGET = Object.freeze({ input: 8000, output: 4000 });
|
|
|
54
55
|
/** Industry-standard heuristic: ~4 chars per token for English prose. */
|
|
55
56
|
const CHARS_PER_TOKEN = 4;
|
|
56
57
|
|
|
58
|
+
/** Hex length of the per-call untrusted-data nonce (`randomBytes(16)` → 32 hex). */
|
|
59
|
+
const NONCE_HEX_LENGTH = 32;
|
|
60
|
+
|
|
57
61
|
// ---------------------------------------------------------------------------
|
|
58
62
|
// Pure-function gates
|
|
59
63
|
// ---------------------------------------------------------------------------
|
|
@@ -182,6 +186,25 @@ export function buildJudgePrompt(selectedSkills, transcriptTail, nonce) {
|
|
|
182
186
|
].join('\n');
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
/**
|
|
190
|
+
* How many characters of evidence fit inside `budget.input` for THIS judged set.
|
|
191
|
+
*
|
|
192
|
+
* The caller must not have to guess: the prompt frame is MEASURED by building
|
|
193
|
+
* the very prompt that will be sent with an empty fence, so the answer tracks
|
|
194
|
+
* every future edit to `buildJudgePrompt` automatically. The nonce is
|
|
195
|
+
* length-accurate (`NONCE_HEX_LENGTH`) rather than the real random value, since
|
|
196
|
+
* only its length affects the frame.
|
|
197
|
+
*
|
|
198
|
+
* @param {string[]} selectedSkills — the judged set (its JSON is part of the frame)
|
|
199
|
+
* @param {{input?: number}} [budget]
|
|
200
|
+
* @returns {number} characters available for `evidence.text` (never negative)
|
|
201
|
+
*/
|
|
202
|
+
export function evidenceBudgetChars(selectedSkills = [], budget = DEFAULT_BUDGET) {
|
|
203
|
+
const maxInput = typeof budget?.input === 'number' ? budget.input : DEFAULT_BUDGET.input;
|
|
204
|
+
const frame = buildJudgePrompt(selectedSkills, '', 'f'.repeat(NONCE_HEX_LENGTH)).length;
|
|
205
|
+
return Math.max(0, maxInput * CHARS_PER_TOKEN - frame);
|
|
206
|
+
}
|
|
207
|
+
|
|
185
208
|
/**
|
|
186
209
|
* Parse the judge response into validated judgment records. Extracts the FIRST
|
|
187
210
|
* fenced ```json block, JSON.parses it, and validates each entry against
|
|
@@ -253,7 +276,7 @@ export function parseJudgeResponse(text) {
|
|
|
253
276
|
|
|
254
277
|
/**
|
|
255
278
|
* @typedef {Object} JudgeResult
|
|
256
|
-
* @property {'ok' | 'empty-input' | 'budget-exceeded'} status
|
|
279
|
+
* @property {'ok' | 'empty-input' | 'no-evidence' | 'budget-exceeded'} status
|
|
257
280
|
* @property {Array<{skill: string, applied: string, completed: string, confidence: number}>} judgments
|
|
258
281
|
* @property {{input_tokens?: number, output_tokens?: number, estimated_input?: number}} [usage]
|
|
259
282
|
* @property {string} [skipped_reason]
|
|
@@ -269,6 +292,7 @@ export function parseJudgeResponse(text) {
|
|
|
269
292
|
*
|
|
270
293
|
* Control flow (matches the ratified #645 L3 contract):
|
|
271
294
|
* - empty selectedSkills → {status:'empty-input', judgments:[]} (no dispatch).
|
|
295
|
+
* - empty evidence text → {status:'no-evidence', judgments:[]} (no dispatch, #1399).
|
|
272
296
|
* - budget exceeded → {status:'budget-exceeded', judgments:[]} (no dispatch,
|
|
273
297
|
* NOT truncated — mirrors dialectic-deriver).
|
|
274
298
|
* - else → dispatch → parse → {status:'ok', judgments, usage}.
|
|
@@ -277,7 +301,8 @@ export function parseJudgeResponse(text) {
|
|
|
277
301
|
* @param {(args: {model: string, prompt: string, maxTokens: number}) => Promise<{text: string, usage?: {input_tokens?: number, output_tokens?: number}}>} opts.dispatchAgent — REQUIRED, injected DI boundary
|
|
278
302
|
* @param {string} [opts.repoRoot]
|
|
279
303
|
* @param {string|null} [opts.sessionId]
|
|
280
|
-
* @param {string} [opts.
|
|
304
|
+
* @param {{text?: string}} [opts.evidence] — preferred input; `buildSkillEvidence()` output (#1399)
|
|
305
|
+
* @param {string} [opts.transcriptTail] — legacy raw-string input; used when `evidence` is absent
|
|
281
306
|
* @param {string[]} [opts.selectedSkills]
|
|
282
307
|
* @param {'haiku'|'sonnet'|'opus'} [opts.model='haiku']
|
|
283
308
|
* @param {{input: number, output: number}} [opts.budget]
|
|
@@ -293,6 +318,7 @@ export async function runSkillJudge({
|
|
|
293
318
|
// `_`-prefixed names so the contract keys stay documented without tripping no-unused-vars.
|
|
294
319
|
repoRoot: _repoRoot,
|
|
295
320
|
sessionId: _sessionId,
|
|
321
|
+
evidence,
|
|
296
322
|
transcriptTail = '',
|
|
297
323
|
selectedSkills = [],
|
|
298
324
|
model = 'haiku',
|
|
@@ -313,8 +339,24 @@ export async function runSkillJudge({
|
|
|
313
339
|
return { status: 'empty-input', judgments: [], skipped_reason: 'no-selected-skills' };
|
|
314
340
|
}
|
|
315
341
|
|
|
342
|
+
// Gate 2b (#1399): empty evidence — do NOT dispatch. Measured 2026-09-19 at
|
|
343
|
+
// `8f15f77b`: a prompt whose fence is empty is 1387 chars = 346 estimated
|
|
344
|
+
// tokens, so the budget gate below waves it through and the judge is asked to
|
|
345
|
+
// rule on a transcript it was never shown. An advisory verdict from no
|
|
346
|
+
// evidence is worse than no verdict — it looks exactly like a real one in
|
|
347
|
+
// `skill-judgments.jsonl`.
|
|
348
|
+
const evidenceText =
|
|
349
|
+
typeof evidence?.text === 'string' && evidence.text.trim()
|
|
350
|
+
? evidence.text
|
|
351
|
+
: typeof transcriptTail === 'string'
|
|
352
|
+
? transcriptTail
|
|
353
|
+
: '';
|
|
354
|
+
if (!evidenceText.trim()) {
|
|
355
|
+
return { status: 'no-evidence', judgments: [], skipped_reason: 'empty-transcript-evidence' };
|
|
356
|
+
}
|
|
357
|
+
|
|
316
358
|
const nonce = randomNonce();
|
|
317
|
-
const prompt = buildJudgePrompt(skills,
|
|
359
|
+
const prompt = buildJudgePrompt(skills, evidenceText, nonce);
|
|
318
360
|
|
|
319
361
|
// Gate 3: budget — fail-fast BEFORE dispatch when the prompt would exceed it.
|
|
320
362
|
const estimatedInput = estimateInputTokens(prompt);
|
package/scripts/lib/state-md.mjs
CHANGED
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
* @see scripts/lib/state-md/mission-status.mjs parseMissionStatus, parseMissionStatusStrict, MISSION_STATUS_VALUES, writeMissionStatus, setMissionStatus, setMissionStatusDetailed, readMissionStatus, recoverFrontmatterMissionStatusDetailed, writeMissionStatusOnDisk, setMissionStatusOnDisk
|
|
11
11
|
* @see scripts/lib/state-md/recommendations.mjs parseRecommendations
|
|
12
12
|
*
|
|
13
|
-
* Plus
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Plus TWO small non-re-export surfaces at the bottom of this file (each with
|
|
14
|
+
* its own docblock explaining why it is composed here rather than added as a
|
|
15
|
+
* fourth mutator module): the `session-profile` frontmatter accessors, and the
|
|
16
|
+
* `started_at` / `session-id` frontmatter SOURCES (#1368).
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import { parseStateMd as _parseStateMd } from './state-md/yaml-parser.mjs';
|
|
19
20
|
import { updateFrontmatterFields as _updateFrontmatterFields } from './state-md/frontmatter-mutators.mjs';
|
|
21
|
+
import { readLock as _readLock } from './session-lock.mjs';
|
|
20
22
|
|
|
21
23
|
export { parseStateMd, serializeStateMd } from './state-md/yaml-parser.mjs';
|
|
22
24
|
|
|
@@ -140,3 +142,82 @@ export function setSessionProfile(contents, profile) {
|
|
|
140
142
|
[SESSION_PROFILE_FIELD]: profile === null ? null : profile.trim(),
|
|
141
143
|
});
|
|
142
144
|
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// STATE.md frontmatter SOURCES (#1368) — `started_at` / `session-id`.
|
|
148
|
+
//
|
|
149
|
+
// These two helpers exist because the frontmatter they feed is written by the
|
|
150
|
+
// coordinator LLM from prose, and prose with no named source gets written from
|
|
151
|
+
// `new Date()` at WRITE time. `started_at` then names the moment STATE.md was
|
|
152
|
+
// created, not the moment the session began — measured 2026-09-13: 48 minutes
|
|
153
|
+
// apart. Every other producer of that timestamp (the lock's own `started_at`,
|
|
154
|
+
// `orchestrator.session.lock.acquired`, `orchestrator.session.started`) agrees
|
|
155
|
+
// within 1 ms, because all three descend from `buildLock()`. So the lock is
|
|
156
|
+
// the source, and these functions are how the template cites it.
|
|
157
|
+
//
|
|
158
|
+
// They live in this barrel rather than in `state-md/`: the barrel is the
|
|
159
|
+
// documented entry point for STATE.md work, and the edge barrel → session-lock
|
|
160
|
+
// already exists (state-md/frontmatter-mutators.mjs imports `withStateMdLock`
|
|
161
|
+
// from it), so no cycle is created — session-lock.mjs imports nothing from
|
|
162
|
+
// this module or its submodules.
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The canonical `started_at` for a STATE.md written during THIS session.
|
|
167
|
+
*
|
|
168
|
+
* Returns the live lock's `started_at`, normalised to ISO-8601 UTC ("Z"), so
|
|
169
|
+
* STATE.md, the lock, both lock events and the ledger all name the SAME
|
|
170
|
+
* instant. Falls back to `new Date().toISOString()` ONLY when no usable lock
|
|
171
|
+
* exists — a missing lock means `persistence: false`, a lock that could not be
|
|
172
|
+
* acquired, or a corrupt lock file, and in all three the writer has no better
|
|
173
|
+
* source than the present moment. The fallback is deliberately silent: it is a
|
|
174
|
+
* degradation of PRECISION, not a failure.
|
|
175
|
+
*
|
|
176
|
+
* Never throws.
|
|
177
|
+
*
|
|
178
|
+
* @param {{ repoRoot?: string }} [opts]
|
|
179
|
+
* @returns {string} ISO-8601 UTC timestamp.
|
|
180
|
+
*/
|
|
181
|
+
export function resolveSessionStartedAt({ repoRoot } = {}) {
|
|
182
|
+
try {
|
|
183
|
+
const lock = _readLock({ repoRoot });
|
|
184
|
+
const raw = lock?.started_at;
|
|
185
|
+
if (typeof raw === 'string' && raw.length > 0) {
|
|
186
|
+
const ms = Date.parse(raw);
|
|
187
|
+
if (Number.isFinite(ms)) return new Date(ms).toISOString();
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
/* readLock is no-throw by contract; the contract is not ours to trust */
|
|
191
|
+
}
|
|
192
|
+
return new Date().toISOString();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The session ids a STATE.md writer may stamp into frontmatter: `session-id`
|
|
197
|
+
* (the RAW/native harness id) and `session` (the semantic attribution label),
|
|
198
|
+
* both read from the live lock.
|
|
199
|
+
*
|
|
200
|
+
* Both fields are `null` when no usable lock exists — ABSENCE IS NEVER
|
|
201
|
+
* COERCED, and a writer must then OMIT the key rather than write a
|
|
202
|
+
* placeholder. Neither id grants ownership of anything (see
|
|
203
|
+
* `skills/_shared/state-ownership.md`); `session-id` exists so `/close`'s #429
|
|
204
|
+
* pre-check can join STATE.md to sessions.jsonl on a native identity instead
|
|
205
|
+
* of falling through to the label + timestamp path.
|
|
206
|
+
*
|
|
207
|
+
* Never throws.
|
|
208
|
+
*
|
|
209
|
+
* @param {{ repoRoot?: string }} [opts]
|
|
210
|
+
* @returns {{ session_id: string|null, semantic_session_id: string|null }}
|
|
211
|
+
*/
|
|
212
|
+
export function resolveSessionIds({ repoRoot } = {}) {
|
|
213
|
+
const pick = (value) => (typeof value === 'string' && value.trim().length > 0 ? value.trim() : null);
|
|
214
|
+
try {
|
|
215
|
+
const lock = _readLock({ repoRoot });
|
|
216
|
+
return {
|
|
217
|
+
session_id: pick(lock?.session_id),
|
|
218
|
+
semantic_session_id: pick(lock?.semantic_session_id),
|
|
219
|
+
};
|
|
220
|
+
} catch {
|
|
221
|
+
return { session_id: null, semantic_session_id: null };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
|
|
40
40
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
41
41
|
import path from 'node:path';
|
|
42
|
-
import { pathToFileURL } from 'node:url';
|
|
43
42
|
import { normalizeSkillInvocation } from '../skill-invocations-schema.mjs';
|
|
44
43
|
import { normalizeSkillJudgment } from '../skill-judgments-schema.mjs';
|
|
44
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
45
|
+
import { userInvocableSkills } from '../user-invocable-skills.mjs';
|
|
45
46
|
|
|
46
47
|
// ---------------------------------------------------------------------------
|
|
47
48
|
// Named threshold constants
|
|
@@ -602,6 +603,16 @@ export function staticReferenceScan(repoRoot, { kind, name }) {
|
|
|
602
603
|
* Parse commands/*.md for the skill each command invokes. Recognises the
|
|
603
604
|
* conventional `skills/<name>/SKILL.md` reference inside a command file.
|
|
604
605
|
*
|
|
606
|
+
* NOTE since the 2026-09-16 command→skill fold (#1370): this linkage now covers
|
|
607
|
+
* almost nothing. `commands/` holds two files (`session`, `templates-ack`), so
|
|
608
|
+
* `skillToCommands` is near-empty by construction — the operator entry point for
|
|
609
|
+
* every other slash command is an explicit `user-invocable: true` in the skill's
|
|
610
|
+
* own frontmatter, not a command file pointing at it. `runSunsetWalk` therefore
|
|
611
|
+
* threads that flag alongside this linkage, and `classifyItem` treats the two as
|
|
612
|
+
* equivalent evidence of "an operator can invoke this directly". Without that, 26
|
|
613
|
+
* live slash commands would have been demoted the day their command file was
|
|
614
|
+
* deleted, for a reason that is a rename, not a disuse signal.
|
|
615
|
+
*
|
|
605
616
|
* @param {string} repoRoot
|
|
606
617
|
* @returns {{commandToSkill: Map<string,string|null>, skillToCommands: Map<string,string[]>}}
|
|
607
618
|
*/
|
|
@@ -772,6 +783,11 @@ export function classifyItem({
|
|
|
772
783
|
const strictRefs = staticRefs.strictRefs;
|
|
773
784
|
const nonBoilerplateRefs = staticRefs.nonBoilerplateRefs;
|
|
774
785
|
const invokedByCommands = linkage?.invokedByCommands ?? [];
|
|
786
|
+
// #1370 fold: an explicit `user-invocable: true` in SKILL.md frontmatter is the
|
|
787
|
+
// post-fold form of "an operator invokes this directly" — the same evidence a
|
|
788
|
+
// command file used to carry. Default false so older callers/tests that do not
|
|
789
|
+
// thread it behave exactly as before.
|
|
790
|
+
const userInvocable = linkage?.userInvocable === true;
|
|
775
791
|
const invokesSkill = linkage?.invokesSkill ?? null;
|
|
776
792
|
// When a command invokes a skill, the caller passes whether that skill is
|
|
777
793
|
// present on disk. Default-true so callers that don't thread the surface set
|
|
@@ -786,6 +802,7 @@ export function classifyItem({
|
|
|
786
802
|
proseRefs,
|
|
787
803
|
nonBoilerplateRefs,
|
|
788
804
|
invokedByCommands,
|
|
805
|
+
userInvocable,
|
|
789
806
|
invokesSkill,
|
|
790
807
|
invokedSkillExists,
|
|
791
808
|
lowConfidence,
|
|
@@ -842,6 +859,10 @@ export function classifyItem({
|
|
|
842
859
|
active = true;
|
|
843
860
|
reasons.push(`skill invoked by command(s): ${invokedByCommands.join(', ')}`);
|
|
844
861
|
}
|
|
862
|
+
if (kind === 'skill' && userInvocable) {
|
|
863
|
+
active = true;
|
|
864
|
+
reasons.push('skill is an operator-facing slash command (user-invocable: true)');
|
|
865
|
+
}
|
|
845
866
|
if (kind === 'command' && invokesSkill && invokedSkillExists) {
|
|
846
867
|
active = true;
|
|
847
868
|
reasons.push(`command invokes live skill: ${invokesSkill}`);
|
|
@@ -878,7 +899,10 @@ export function classifyItem({
|
|
|
878
899
|
|
|
879
900
|
// --- Demote (near-zero) — skills & commands (agents handled above) ---------
|
|
880
901
|
const lonelySkill =
|
|
881
|
-
kind === 'skill' &&
|
|
902
|
+
kind === 'skill' &&
|
|
903
|
+
nonBoilerplateRefs <= 1 &&
|
|
904
|
+
invokedByCommands.length === 0 &&
|
|
905
|
+
!userInvocable;
|
|
882
906
|
const lonelyCommand =
|
|
883
907
|
kind === 'command' && !invokesSkill && nonBoilerplateRefs <= 1;
|
|
884
908
|
if (lonelySkill || lonelyCommand) {
|
|
@@ -942,6 +966,9 @@ export function runSunsetWalk(repoRoot, opts = {}) {
|
|
|
942
966
|
// classification below behaves byte-for-byte identically to the L1-only path.
|
|
943
967
|
const skillJudgments = readSkillJudgmentCounts(judgmentsPath, { windowDays, now: nowMs });
|
|
944
968
|
const linkage = commandSkillLinkage(repoRoot);
|
|
969
|
+
// Post-#1370 replacement for the command→skill linkage as an operator-entry
|
|
970
|
+
// signal (see commandSkillLinkage's note). Read once per walk, not per skill.
|
|
971
|
+
const userInvocableSet = new Set(userInvocableSkills(repoRoot));
|
|
945
972
|
const coverageDays = dispatch.coverageDays;
|
|
946
973
|
const lowConfidence = coverageDays < windowDays;
|
|
947
974
|
|
|
@@ -967,7 +994,7 @@ export function runSunsetWalk(repoRoot, opts = {}) {
|
|
|
967
994
|
name,
|
|
968
995
|
dispatch: skillDispatch,
|
|
969
996
|
static: staticRefs,
|
|
970
|
-
linkage: { invokedByCommands },
|
|
997
|
+
linkage: { invokedByCommands, userInvocable: userInvocableSet.has(name) },
|
|
971
998
|
windowDays,
|
|
972
999
|
coverageDays,
|
|
973
1000
|
judge,
|
|
@@ -1192,7 +1219,7 @@ function main(argv) {
|
|
|
1192
1219
|
}
|
|
1193
1220
|
|
|
1194
1221
|
// Import-safe main-guard.
|
|
1195
|
-
if (import.meta.url
|
|
1222
|
+
if (isMainModule(import.meta.url)) {
|
|
1196
1223
|
const code = main(process.argv.slice(2));
|
|
1197
1224
|
process.exit(code);
|
|
1198
1225
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tail-window.mjs — the ONE bounded tail-window reader for append-only JSONL
|
|
3
|
+
* ledgers and transcripts (#1272).
|
|
4
|
+
*
|
|
5
|
+
* Five call sites used to carry their own copy of this byte-window primitive,
|
|
6
|
+
* and one of them (hooks/subagent-telemetry.mjs) ignored the returned read
|
|
7
|
+
* length and decoded a whole `allocUnsafe` buffer — on a short read that is
|
|
8
|
+
* uninitialised memory handed to JSON.parse. This module owns ONLY the byte
|
|
9
|
+
* window; every caller keeps its own error mapping (null / [] / discriminated
|
|
10
|
+
* result / throw) and its own line logic.
|
|
11
|
+
*
|
|
12
|
+
* Ceiling: the whole window is held in memory at once and decoded in one pass
|
|
13
|
+
* — fine for the windows in use today (64 KiB .. 1 MiB). Revisit if a caller
|
|
14
|
+
* needs a window above ~16 MiB: stream backwards in chunks instead.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read the last `maxBytes` of `file` as UTF-8.
|
|
21
|
+
*
|
|
22
|
+
* `cut` is true when the window did not start at byte 0 — the first line of
|
|
23
|
+
* `text` is then (or may be) a fragment of a record, possibly starting inside
|
|
24
|
+
* a multi-byte UTF-8 sequence, and callers that parse lines should drop it.
|
|
25
|
+
* Loops until the window is filled or the file reports EOF, so a short read
|
|
26
|
+
* never leaves undecoded buffer bytes in `text`.
|
|
27
|
+
*
|
|
28
|
+
* THROWS on any fs error (ENOENT, EACCES, …) — mapping the failure is the
|
|
29
|
+
* caller's decision.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} file
|
|
32
|
+
* @param {number} maxBytes window size in bytes (> 0)
|
|
33
|
+
* @returns {{ text: string, cut: boolean, size: number }} `size` is the file size at open time
|
|
34
|
+
*/
|
|
35
|
+
export function readTailWindow(file, maxBytes) {
|
|
36
|
+
const fd = fs.openSync(file, 'r');
|
|
37
|
+
try {
|
|
38
|
+
const size = fs.fstatSync(fd).size;
|
|
39
|
+
const want = Math.min(size, maxBytes);
|
|
40
|
+
const start = size - want;
|
|
41
|
+
const buf = Buffer.allocUnsafe(want);
|
|
42
|
+
let read = 0;
|
|
43
|
+
while (read < want) {
|
|
44
|
+
const n = fs.readSync(fd, buf, read, want - read, start + read);
|
|
45
|
+
if (n <= 0) break;
|
|
46
|
+
read += n;
|
|
47
|
+
}
|
|
48
|
+
return { text: buf.subarray(0, read).toString('utf8'), cut: start > 0, size };
|
|
49
|
+
} finally {
|
|
50
|
+
try {
|
|
51
|
+
fs.closeSync(fd);
|
|
52
|
+
} catch {
|
|
53
|
+
/* best-effort */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|