session-orchestrator 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +9 -30
- package/CHANGELOG.md +206 -0
- package/README.md +13 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +22 -7
- package/docs/ci-setup.md +57 -4
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +12 -10
- package/docs/events-schema.md +65 -15
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +84 -17
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +154 -12
- package/docs/session-config-template.md +41 -3
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +87 -6
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +101 -11
- package/hooks/on-stop.mjs +170 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +202 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
- package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +792 -74
- package/hooks/subagent-telemetry.mjs +111 -38
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +3 -3
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/navigator.md +12 -0
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +7 -2
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +6 -2
- package/rules/opt-in-stack/backend-data.md +10 -2
- package/rules/opt-in-stack/backend.md +10 -4
- package/rules/opt-in-stack/frontend.md +7 -2
- package/rules/opt-in-stack/security-web.md +10 -4
- package/rules/opt-in-stack/swift.md +6 -2
- package/scripts/apply-session-learnings.mjs +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +70 -53
- package/scripts/backfill-abandoned-sessions.mjs +118 -16
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +371 -31
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -0
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +18 -2
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +19 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +163 -0
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +85 -10
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +545 -7
- package/scripts/lib/fleet-protocol.mjs +270 -0
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +40 -5
- package/scripts/lib/gates/gate-full.mjs +35 -5
- package/scripts/lib/gates/gate-helpers.mjs +336 -23
- package/scripts/lib/gates/gate-incremental.mjs +49 -23
- package/scripts/lib/gates/gate-per-file.mjs +42 -8
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +208 -8
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +316 -0
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +950 -0
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +152 -33
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +141 -74
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +3 -4
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +56 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +118 -4
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +210 -84
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +306 -0
- package/scripts/release.mjs +40 -8
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +357 -197
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +16 -4
- package/scripts/validate-plugin.mjs +19 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +81 -47
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +47 -18
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +3 -3
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/learning-patterns.md +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +32 -60
- package/skills/session-plan/SKILL.md +17 -8
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +9 -5
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +3 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* apply-session-learnings.mjs — the ONLY sanctioned session-end write path for
|
|
4
|
+
* `learnings.jsonl` (GitLab #1446 / GitHub #69). session-end Phase 3.6 and the
|
|
5
|
+
* `/plan retro` §3.3 learnings update hand their collected changes to this CLI;
|
|
6
|
+
* neither may rewrite the store with a shell `>` or append to it with `>>`
|
|
7
|
+
* (`scripts/lib/validate/check-learnings-shell-writes.mjs` gates the prose).
|
|
8
|
+
*
|
|
9
|
+
* Why a CLI and not prose: until #1446, Phase 3.6 step 4g told the coordinator
|
|
10
|
+
* to "write the entire result back (atomic rewrite with `>`)" from a template
|
|
11
|
+
* that carried no `scope` and no `file_paths`. A shell rewrite bypasses
|
|
12
|
+
* `validateLearning`, the `.bak-<ISO>` snapshot and the archive sidecar — and
|
|
13
|
+
* the producer that wrote a file PATH into the `scope` column (GH#69) was
|
|
14
|
+
* never rejected by anything.
|
|
15
|
+
*
|
|
16
|
+
* Input (stdin, or `--input PATH`), one JSON object:
|
|
17
|
+
* {
|
|
18
|
+
* "confidence_updates": [{ "id": "<existing id>", "operation": "confirm"|"contradict" }],
|
|
19
|
+
* "new_learnings": [ <complete learning objects> ]
|
|
20
|
+
* }
|
|
21
|
+
* Both keys are optional (absent = empty list).
|
|
22
|
+
*
|
|
23
|
+
* Steps — every one delegates to an existing helper:
|
|
24
|
+
* 1. read the store `readLearnings()` (io.mjs)
|
|
25
|
+
* 2. confirm → +0.15, cap 1.0, `expires_at` reset via `deriveExpiresAt(now, type)`
|
|
26
|
+
* contradict → −0.2, floor 0, `expires_at` untouched
|
|
27
|
+
* 3. new learnings → stamped like `appendLearning()` (`created_at`,
|
|
28
|
+
* `expires_at`, `schema_version: 1` when absent), then STRICT
|
|
29
|
+
* `validateLearning()` — one invalid record exits 1 with nothing written
|
|
30
|
+
* 4. passive decay (#89) → `learning-decay-rate` subtracted from every
|
|
31
|
+
* existing learning NOT confirmed/contradicted this run, clamped ≥ 0,
|
|
32
|
+
* `expires_at` untouched
|
|
33
|
+
* 5. prune (expired, confidence ≤ 0) + consolidate (type+subject, highest
|
|
34
|
+
* confidence wins) + archive the losers + atomic rewrite with `.bak-<ISO>`
|
|
35
|
+
* → `pruneLearnings()` (expiry-sweep.mjs). Archived records carry this
|
|
36
|
+
* run's DECAYED confidence, a value never stored in the live file: decay
|
|
37
|
+
* (step 4) runs before prune by design, so a record pruned at
|
|
38
|
+
* `confidence <= 0` is archived with the value that pruned it.
|
|
39
|
+
*
|
|
40
|
+
* On `--apply`, steps 1-5 run as ONE critical section under the store lock
|
|
41
|
+
* (`withLearningsLock()`, io.mjs; lock file `<store>.lock`): a parallel
|
|
42
|
+
* `--apply` or an `appendLearning()` cannot land between the read and the
|
|
43
|
+
* rewrite and be lost. A lock not acquired within 10 s exits 2, nothing written.
|
|
44
|
+
*
|
|
45
|
+
* Usage:
|
|
46
|
+
* node scripts/apply-session-learnings.mjs [--input PATH] [--file PATH]
|
|
47
|
+
* [--repo-root PATH] [--dry-run|--apply] [--json] [--decay-rate N]
|
|
48
|
+
*
|
|
49
|
+
* Flags:
|
|
50
|
+
* --input PATH JSON input file (default: read stdin)
|
|
51
|
+
* --repo-root PATH Repo root (default: cwd). Resolves a relative `--file`,
|
|
52
|
+
* supplies the Session Config for the decay rate, and pins
|
|
53
|
+
* the telemetry record.
|
|
54
|
+
* --file PATH Learnings store (default: .orchestrator/metrics/learnings.jsonl).
|
|
55
|
+
* The archive is its sibling `learnings-archive.jsonl`.
|
|
56
|
+
* --dry-run Preview the summary; write nothing, no `.bak` (DEFAULT)
|
|
57
|
+
* --apply Perform the archive append + store rewrite
|
|
58
|
+
* --json Emit one machine-parseable JSON summary line
|
|
59
|
+
* --decay-rate N Passive decay per run, 0..1 (default: Session Config
|
|
60
|
+
* `learning-decay-rate`, else 0.05). 0 disables decay.
|
|
61
|
+
*
|
|
62
|
+
* Telemetry: `--apply` emits `orchestrator.learnings.session_write_applied`
|
|
63
|
+
* after the write — its presence is the proof the write ran. Best-effort: a
|
|
64
|
+
* telemetry failure never changes the exit code. Skipped (stderr WARN) when
|
|
65
|
+
* `--file` lies outside `--repo-root`, so the record is never pinned to a repo
|
|
66
|
+
* whose store was not written.
|
|
67
|
+
*
|
|
68
|
+
* Exit codes:
|
|
69
|
+
* 0 Success (including a no-op)
|
|
70
|
+
* 1 Usage/input/validation error — nothing written
|
|
71
|
+
* 2 Library/IO error (unreadable store, malformed store lines on --apply,
|
|
72
|
+
* a failure inside the prune/rewrite, store lock not acquired)
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
import { readFileSync } from 'node:fs';
|
|
76
|
+
import path from 'node:path';
|
|
77
|
+
import { LearningsLockError, readLearnings, withLearningsLock } from './lib/learnings/io.mjs';
|
|
78
|
+
import { pruneLearnings } from './lib/learnings/expiry-sweep.mjs';
|
|
79
|
+
import {
|
|
80
|
+
CURRENT_SCHEMA_VERSION,
|
|
81
|
+
deriveExpiresAt,
|
|
82
|
+
validateLearning,
|
|
83
|
+
} from './lib/learnings/schema.mjs';
|
|
84
|
+
import { parseSessionConfig, readConfigFile } from './lib/config.mjs';
|
|
85
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
86
|
+
|
|
87
|
+
const DEFAULT_FILE = '.orchestrator/metrics/learnings.jsonl';
|
|
88
|
+
const ARCHIVE_BASENAME = 'learnings-archive.jsonl';
|
|
89
|
+
const DEFAULT_DECAY_RATE = 0.05;
|
|
90
|
+
const CONFIRM_DELTA = 0.15;
|
|
91
|
+
const CONTRADICT_DELTA = 0.2;
|
|
92
|
+
const OPERATIONS = new Set(['confirm', 'contradict']);
|
|
93
|
+
|
|
94
|
+
/** Event name — registered in docs/events-schema.md. */
|
|
95
|
+
export const SESSION_WRITE_EVENT = 'orchestrator.learnings.session_write_applied';
|
|
96
|
+
|
|
97
|
+
class UsageError extends Error {}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Round to 6 decimals. Without it, ten 0.05 decay steps from 0.5 leave a
|
|
101
|
+
* float residue > 0 that the `confidence <= 0` prune never catches.
|
|
102
|
+
*
|
|
103
|
+
* @param {number} n
|
|
104
|
+
* @returns {number}
|
|
105
|
+
*/
|
|
106
|
+
function round6(n) {
|
|
107
|
+
return Math.round(n * 1e6) / 1e6;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function printHelp() {
|
|
111
|
+
process.stdout.write(
|
|
112
|
+
'Usage: node scripts/apply-session-learnings.mjs [--input PATH] [--file PATH] ' +
|
|
113
|
+
'[--repo-root PATH] [--dry-run|--apply] [--json] [--decay-rate N]\n',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseArgs(argv) {
|
|
118
|
+
const opts = { input: null, file: null, repoRoot: null, apply: false, json: false, decayRate: null };
|
|
119
|
+
const valueOf = (i, flag) => {
|
|
120
|
+
const v = argv[i + 1];
|
|
121
|
+
if (v === undefined || v.startsWith('--')) throw new UsageError(`${flag} requires a value`);
|
|
122
|
+
return v;
|
|
123
|
+
};
|
|
124
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
125
|
+
const a = argv[i];
|
|
126
|
+
switch (a) {
|
|
127
|
+
case '--help':
|
|
128
|
+
case '-h':
|
|
129
|
+
opts.help = true;
|
|
130
|
+
break;
|
|
131
|
+
case '--apply':
|
|
132
|
+
opts.apply = true;
|
|
133
|
+
break;
|
|
134
|
+
case '--dry-run':
|
|
135
|
+
opts.apply = false;
|
|
136
|
+
break;
|
|
137
|
+
case '--json':
|
|
138
|
+
opts.json = true;
|
|
139
|
+
break;
|
|
140
|
+
case '--input':
|
|
141
|
+
opts.input = valueOf(i, a);
|
|
142
|
+
i += 1;
|
|
143
|
+
break;
|
|
144
|
+
case '--file':
|
|
145
|
+
opts.file = valueOf(i, a);
|
|
146
|
+
i += 1;
|
|
147
|
+
break;
|
|
148
|
+
case '--repo-root':
|
|
149
|
+
opts.repoRoot = valueOf(i, a);
|
|
150
|
+
i += 1;
|
|
151
|
+
break;
|
|
152
|
+
case '--decay-rate': {
|
|
153
|
+
const raw = valueOf(i, a);
|
|
154
|
+
const n = Number(raw);
|
|
155
|
+
if (raw.trim() === '' || !Number.isFinite(n) || n < 0 || n > 1) {
|
|
156
|
+
throw new UsageError(`--decay-rate must be a number in [0, 1], got: ${raw}`);
|
|
157
|
+
}
|
|
158
|
+
opts.decayRate = n;
|
|
159
|
+
i += 1;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
default:
|
|
163
|
+
throw new UsageError(`unknown argument: ${a}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return opts;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Parse and shape-check the input document. Throws UsageError.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} raw
|
|
173
|
+
* @returns {{ updates: {id: string, operation: string}[], newLearnings: object[] }}
|
|
174
|
+
*/
|
|
175
|
+
function parseInput(raw) {
|
|
176
|
+
let doc;
|
|
177
|
+
try {
|
|
178
|
+
doc = JSON.parse(raw);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
throw new UsageError(`input is not valid JSON: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
183
|
+
throw new UsageError('input must be a JSON object {confidence_updates, new_learnings}');
|
|
184
|
+
}
|
|
185
|
+
const updates = doc.confidence_updates ?? [];
|
|
186
|
+
const newLearnings = doc.new_learnings ?? [];
|
|
187
|
+
if (!Array.isArray(updates)) throw new UsageError('confidence_updates must be an array');
|
|
188
|
+
if (!Array.isArray(newLearnings)) throw new UsageError('new_learnings must be an array');
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
updates.forEach((u, i) => {
|
|
191
|
+
if (!u || typeof u.id !== 'string' || u.id.length === 0) {
|
|
192
|
+
throw new UsageError(`confidence_updates[${i}]: id must be a non-empty string`);
|
|
193
|
+
}
|
|
194
|
+
if (!OPERATIONS.has(u.operation)) {
|
|
195
|
+
throw new UsageError(
|
|
196
|
+
`confidence_updates[${i}] (id=${u.id}): operation must be confirm|contradict, got: ${u.operation}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (seen.has(u.id)) throw new UsageError(`confidence_updates[${i}]: id ${u.id} listed twice`);
|
|
200
|
+
seen.add(u.id);
|
|
201
|
+
});
|
|
202
|
+
return { updates, newLearnings };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Session Config `learning-decay-rate`, or the default when no config is
|
|
207
|
+
* readable. Never throws.
|
|
208
|
+
*
|
|
209
|
+
* @param {string} repoRoot
|
|
210
|
+
* @returns {Promise<number>}
|
|
211
|
+
*/
|
|
212
|
+
async function resolveDecayRate(repoRoot) {
|
|
213
|
+
try {
|
|
214
|
+
const cfg = parseSessionConfig(await readConfigFile(repoRoot));
|
|
215
|
+
const rate = cfg?.['learning-decay-rate'];
|
|
216
|
+
return typeof rate === 'number' && Number.isFinite(rate) ? rate : DEFAULT_DECAY_RATE;
|
|
217
|
+
} catch {
|
|
218
|
+
return DEFAULT_DECAY_RATE;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Build the next store generation from the current one plus the input.
|
|
224
|
+
* Pure apart from the clock. Throws UsageError on an unknown/duplicate id or
|
|
225
|
+
* an invalid new learning.
|
|
226
|
+
*
|
|
227
|
+
* @returns {{ next: object[], confirmed: number, contradicted: number, appended: number,
|
|
228
|
+
* decayed: number, undecayable: number }}
|
|
229
|
+
*/
|
|
230
|
+
export function buildNextGeneration({ current, updates, newLearnings, decayRate, nowIso }) {
|
|
231
|
+
const byId = new Map();
|
|
232
|
+
for (const entry of current) {
|
|
233
|
+
if (typeof entry?.id === 'string' && entry.id.length > 0) byId.set(entry.id, entry);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const updateOf = new Map();
|
|
237
|
+
for (const u of updates) {
|
|
238
|
+
if (!byId.has(u.id)) {
|
|
239
|
+
throw new UsageError(`confidence_updates: id ${u.id} not found in the store`);
|
|
240
|
+
}
|
|
241
|
+
updateOf.set(u.id, u.operation);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let confirmed = 0;
|
|
245
|
+
let contradicted = 0;
|
|
246
|
+
let decayed = 0;
|
|
247
|
+
let undecayable = 0;
|
|
248
|
+
const next = current.map((entry) => {
|
|
249
|
+
const op = typeof entry?.id === 'string' ? updateOf.get(entry.id) : undefined;
|
|
250
|
+
const c = entry?.confidence;
|
|
251
|
+
if (op === 'confirm') {
|
|
252
|
+
confirmed += 1;
|
|
253
|
+
return {
|
|
254
|
+
...entry,
|
|
255
|
+
confidence: round6(Math.min(1, (typeof c === 'number' ? c : 0) + CONFIRM_DELTA)),
|
|
256
|
+
expires_at: deriveExpiresAt(nowIso, entry.type),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (op === 'contradict') {
|
|
260
|
+
contradicted += 1;
|
|
261
|
+
return { ...entry, confidence: round6(Math.max(0, (typeof c === 'number' ? c : 0) - CONTRADICT_DELTA)) };
|
|
262
|
+
}
|
|
263
|
+
if (decayRate > 0 && typeof c === 'number' && c > 0) {
|
|
264
|
+
// An id-less record is keyed by a content fingerprint in pruneLearnings'
|
|
265
|
+
// reconcile pass, so changing its confidence would archive the old
|
|
266
|
+
// version as a spurious "pruned" duplicate every run. It is left to age
|
|
267
|
+
// out via `expires_at` instead — revisit once the legacy id-less records
|
|
268
|
+
// are gone (`migrateLegacyLearning` backfill).
|
|
269
|
+
if (typeof entry?.id !== 'string' || entry.id.length === 0) {
|
|
270
|
+
undecayable += 1;
|
|
271
|
+
return entry;
|
|
272
|
+
}
|
|
273
|
+
decayed += 1;
|
|
274
|
+
return { ...entry, confidence: round6(Math.max(0, c - decayRate)) };
|
|
275
|
+
}
|
|
276
|
+
return entry;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const newIds = new Set();
|
|
280
|
+
newLearnings.forEach((raw, i) => {
|
|
281
|
+
const label = `new_learnings[${i}] (id=${raw?.id ?? '<missing>'})`;
|
|
282
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
283
|
+
throw new UsageError(`${label}: must be an object`);
|
|
284
|
+
}
|
|
285
|
+
// Stamping mirrors appendLearning() (io.mjs) so both write paths agree.
|
|
286
|
+
const createdAt =
|
|
287
|
+
typeof raw.created_at === 'string' && raw.created_at.length > 0 ? raw.created_at : nowIso;
|
|
288
|
+
const stamped = {
|
|
289
|
+
...raw,
|
|
290
|
+
created_at: createdAt,
|
|
291
|
+
expires_at:
|
|
292
|
+
typeof raw.expires_at === 'string' && raw.expires_at.length > 0
|
|
293
|
+
? raw.expires_at
|
|
294
|
+
: deriveExpiresAt(createdAt, raw.type),
|
|
295
|
+
schema_version: raw.schema_version ?? CURRENT_SCHEMA_VERSION,
|
|
296
|
+
};
|
|
297
|
+
let validated;
|
|
298
|
+
try {
|
|
299
|
+
validated = validateLearning(stamped);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
throw new UsageError(`${label}: ${err.message}`);
|
|
302
|
+
}
|
|
303
|
+
if (byId.has(validated.id) || newIds.has(validated.id)) {
|
|
304
|
+
throw new UsageError(`${label}: id already exists — use confidence_updates to reinforce it`);
|
|
305
|
+
}
|
|
306
|
+
newIds.add(validated.id);
|
|
307
|
+
next.push(validated);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return { next, confirmed, contradicted, appended: newIds.size, decayed, undecayable };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function emitSessionWriteApplied({ repoRoot, filePath, payload }) {
|
|
314
|
+
const rel = path.relative(repoRoot, filePath);
|
|
315
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
316
|
+
process.stderr.write(
|
|
317
|
+
`apply-session-learnings: skipped ${SESSION_WRITE_EVENT} — ${filePath} lies outside ` +
|
|
318
|
+
`--repo-root ${repoRoot}\n`,
|
|
319
|
+
);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
|
|
324
|
+
await emitEvent(
|
|
325
|
+
SESSION_WRITE_EVENT,
|
|
326
|
+
{ file: rel, ...payload, ...sessionAttribution(repoRoot) },
|
|
327
|
+
{ repoRoot },
|
|
328
|
+
);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
// Best-effort telemetry — the write already happened and is authoritative.
|
|
331
|
+
process.stderr.write(`apply-session-learnings: ${SESSION_WRITE_EVENT} not recorded: ${err?.message ?? err}\n`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function main(argv) {
|
|
336
|
+
let opts;
|
|
337
|
+
let input;
|
|
338
|
+
try {
|
|
339
|
+
opts = parseArgs(argv);
|
|
340
|
+
if (opts.help) {
|
|
341
|
+
printHelp();
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
if (opts.input === null && process.stdin.isTTY) {
|
|
345
|
+
throw new UsageError('no input — pipe JSON on stdin or pass --input PATH');
|
|
346
|
+
}
|
|
347
|
+
let raw;
|
|
348
|
+
try {
|
|
349
|
+
raw = readFileSync(opts.input ?? 0, 'utf8');
|
|
350
|
+
} catch (err) {
|
|
351
|
+
throw new UsageError(`cannot read input: ${err.message}`);
|
|
352
|
+
}
|
|
353
|
+
input = parseInput(raw);
|
|
354
|
+
} catch (err) {
|
|
355
|
+
if (err instanceof UsageError) {
|
|
356
|
+
process.stderr.write(`apply-session-learnings: ${err.message}\n`);
|
|
357
|
+
printHelp();
|
|
358
|
+
return 1;
|
|
359
|
+
}
|
|
360
|
+
throw err;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const repoRoot = path.resolve(opts.repoRoot ?? process.cwd());
|
|
364
|
+
const filePath = path.resolve(repoRoot, opts.file ?? DEFAULT_FILE);
|
|
365
|
+
const archivePath = path.join(path.dirname(filePath), ARCHIVE_BASENAME);
|
|
366
|
+
const decayRate = opts.decayRate ?? (await resolveDecayRate(repoRoot));
|
|
367
|
+
const now = new Date();
|
|
368
|
+
const nowIso = now.toISOString();
|
|
369
|
+
|
|
370
|
+
// Read → build → prune/rewrite: one critical section on --apply (see header).
|
|
371
|
+
// Returns `{ code }` for an early exit, else the three results.
|
|
372
|
+
const readBuildPrune = async () => {
|
|
373
|
+
let read;
|
|
374
|
+
try {
|
|
375
|
+
read = await readLearnings(filePath);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
process.stderr.write(`apply-session-learnings: cannot read ${filePath}: ${err.message}\n`);
|
|
378
|
+
return { code: 2 };
|
|
379
|
+
}
|
|
380
|
+
if (opts.apply && read.malformed.length > 0) {
|
|
381
|
+
// pruneLearnings rewrites from parsed entries only — a malformed line
|
|
382
|
+
// would vanish without an archive record.
|
|
383
|
+
process.stderr.write(
|
|
384
|
+
`apply-session-learnings: refusing to rewrite — ${read.malformed.length} malformed line(s) in ${filePath}\n`,
|
|
385
|
+
);
|
|
386
|
+
return { code: 2 };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let built;
|
|
390
|
+
try {
|
|
391
|
+
built = buildNextGeneration({
|
|
392
|
+
current: read.entries,
|
|
393
|
+
updates: input.updates,
|
|
394
|
+
newLearnings: input.newLearnings,
|
|
395
|
+
decayRate,
|
|
396
|
+
nowIso,
|
|
397
|
+
});
|
|
398
|
+
} catch (err) {
|
|
399
|
+
if (err instanceof UsageError) {
|
|
400
|
+
process.stderr.write(`apply-session-learnings: ${err.message} — nothing written\n`);
|
|
401
|
+
return { code: 1 };
|
|
402
|
+
}
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let res;
|
|
407
|
+
try {
|
|
408
|
+
// Joins the lock held below reentrantly; takes none on a dry run.
|
|
409
|
+
res = await pruneLearnings({ filePath, archivePath, entries: built.next, now, dryRun: !opts.apply });
|
|
410
|
+
} catch (err) {
|
|
411
|
+
process.stderr.write(`apply-session-learnings: prune/rewrite failed: ${err.message}\n`);
|
|
412
|
+
return { code: 2 };
|
|
413
|
+
}
|
|
414
|
+
return { read, built, res };
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
let outcome;
|
|
418
|
+
try {
|
|
419
|
+
outcome = opts.apply ? await withLearningsLock(filePath, readBuildPrune) : await readBuildPrune();
|
|
420
|
+
} catch (err) {
|
|
421
|
+
if (err instanceof LearningsLockError) {
|
|
422
|
+
process.stderr.write(`apply-session-learnings: ${err.message}\n`);
|
|
423
|
+
return 2;
|
|
424
|
+
}
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
if ('code' in outcome) return outcome.code;
|
|
428
|
+
const { read, built, res } = outcome;
|
|
429
|
+
|
|
430
|
+
const byReason = res.byReason ?? {};
|
|
431
|
+
const pruned = (byReason.expired ?? 0) + (byReason.pruned ?? 0);
|
|
432
|
+
const summary = {
|
|
433
|
+
dry_run: !opts.apply,
|
|
434
|
+
file: filePath,
|
|
435
|
+
read: read.entries.length,
|
|
436
|
+
malformed: read.malformed.length,
|
|
437
|
+
confirmed: built.confirmed,
|
|
438
|
+
contradicted: built.contradicted,
|
|
439
|
+
appended: built.appended,
|
|
440
|
+
decay_rate: decayRate,
|
|
441
|
+
decayed: built.decayed,
|
|
442
|
+
undecayable: built.undecayable,
|
|
443
|
+
pruned,
|
|
444
|
+
consolidated: byReason.superseded ?? 0,
|
|
445
|
+
archived: res.archived,
|
|
446
|
+
kept: res.kept,
|
|
447
|
+
by_reason: byReason,
|
|
448
|
+
written: opts.apply ? res.kept : 0,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// The event's presence is documented as proof the write ran
|
|
452
|
+
// (docs/events-schema.md). pruneLearnings() returns early WITHOUT writing
|
|
453
|
+
// exactly when the store and the next generation are both empty — the only
|
|
454
|
+
// path that reports `scanned: 0` — so no store + empty input emits nothing.
|
|
455
|
+
if (opts.apply && res.scanned > 0) {
|
|
456
|
+
await emitSessionWriteApplied({
|
|
457
|
+
repoRoot,
|
|
458
|
+
filePath,
|
|
459
|
+
payload: {
|
|
460
|
+
appended: built.appended,
|
|
461
|
+
confirmed: built.confirmed,
|
|
462
|
+
contradicted: built.contradicted,
|
|
463
|
+
decayed: built.decayed,
|
|
464
|
+
pruned,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (opts.json) {
|
|
470
|
+
process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
471
|
+
} else {
|
|
472
|
+
process.stdout.write(
|
|
473
|
+
`${summary.dry_run ? '[dry-run] ' : ''}learnings: read ${summary.read}, confirmed ${summary.confirmed}, ` +
|
|
474
|
+
`contradicted ${summary.contradicted}, appended ${summary.appended}, decayed ${summary.decayed} ` +
|
|
475
|
+
`(rate ${decayRate}), pruned ${pruned}, consolidated ${summary.consolidated}, kept ${summary.kept}` +
|
|
476
|
+
`${summary.dry_run ? ' — nothing written' : ` — wrote ${summary.written}`}\n`,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return 0;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (isMainModule(import.meta.url)) {
|
|
483
|
+
main(process.argv.slice(2)).then(
|
|
484
|
+
(code) => {
|
|
485
|
+
process.exitCode = code;
|
|
486
|
+
},
|
|
487
|
+
(err) => {
|
|
488
|
+
process.stderr.write(`apply-session-learnings: ${err?.stack ?? err}\n`);
|
|
489
|
+
process.exitCode = 2;
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
}
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Flow:
|
|
14
14
|
* 1. findProjectRoot → resolve CLAUDE.md → parseSessionConfig → vault-integration.vault-dir
|
|
15
|
-
* (host-resolved: SO_VAULT_DIR env > owner.yaml
|
|
15
|
+
* (host-resolved: SO_VAULT_DIR env > owner.yaml vault-dirs: cwd match >
|
|
16
|
+
* owner.yaml paths.vault-dir > committed — `resolveVaultDir` in lib/config.mjs).
|
|
16
17
|
* 2. Enumerate tracked .md files under --prd-dir (git ls-files), excluding
|
|
17
18
|
* *.original-uncommitted.md. A missing/empty --prd-dir yields [] → clean
|
|
18
19
|
* report, exit 0 (no crash) — see listTrackedPrds.
|