session-orchestrator 3.23.0 → 4.0.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 +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +13 -0
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1401 -0
- package/NOTICE +11 -6
- package/README.md +127 -92
- package/agents/db-specialist.md +0 -1
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +249 -48
- package/docs/codex-setup.md +66 -22
- package/docs/components.md +37 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +51 -10
- package/docs/instruction-delivery.md +62 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +341 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +8 -8
- package/docs/session-config-reference.md +120 -61
- package/docs/session-config-template.md +40 -33
- package/docs/telemetry/telemetry-claims.md +11 -10
- package/docs/telemetry.md +187 -4
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/atomic-json.mjs +111 -0
- package/hooks/_lib/hook-import-set.json +1487 -0
- package/hooks/_lib/subagent-paths.mjs +143 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +11 -31
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/enforce-scope.mjs +35 -6
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +280 -14
- package/hooks/on-session-start.mjs +153 -4
- package/hooks/on-stop.mjs +371 -17
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +189 -4
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +278 -392
- package/hooks/post-tool-batch-wave-signal.mjs +272 -44
- package/hooks/post-tool-failure-corrective-context.mjs +11 -34
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +24 -30
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/plugin.json +27 -0
- package/scripts/autopilot.mjs +26 -12
- package/scripts/backfill-abandoned-sessions.mjs +130 -15
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/emit-event.mjs +10 -2
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +249 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auq/parse.mjs +5 -29
- package/scripts/lib/auto-dialectic.mjs +68 -0
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/autopilot/worktree-pipeline.mjs +82 -6
- package/scripts/lib/build-live-signals.mjs +25 -22
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +82 -6
- package/scripts/lib/cold-start-detector.mjs +23 -14
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +63 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +9 -3
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +388 -0
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +234 -0
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/config.mjs +31 -3
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/enumerate.mjs +2 -17
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events-schema.mjs +48 -0
- package/scripts/lib/events.mjs +256 -7
- package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +26 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-banner.mjs +20 -8
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +43 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +207 -14
- package/scripts/lib/peer-discovery.mjs +20 -2
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +406 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +249 -9
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/scope-gate.mjs +36 -0
- package/scripts/lib/session-close-backfill.mjs +125 -18
- package/scripts/lib/session-discovery.mjs +57 -3
- package/scripts/lib/session-end/phase-skip.mjs +2 -2
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +187 -11
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +36 -2
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +18 -1
- package/scripts/lib/session-transition.mjs +1 -1
- package/scripts/lib/sessions-canonical.mjs +446 -0
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +255 -17
- package/scripts/lib/telemetry/sync.mjs +417 -24
- package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-doc-cli-commands.mjs +9 -33
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
- package/scripts/lib/validate/check-skill-links.mjs +163 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +455 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
- package/scripts/lib/validate/check-unwired-features.mjs +0 -9
- package/scripts/lib/validate/check-validator-registration.mjs +254 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/markdown-fences.mjs +196 -0
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/board-lock.mjs +185 -0
- package/scripts/lib/vault-status/board-writer.mjs +174 -135
- package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +502 -0
- package/scripts/lib/wave-resource-gate.mjs +133 -7
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +142 -8
- package/scripts/materialize-wave-scope.mjs +32 -9
- package/scripts/memory-propose.mjs +146 -8
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/promote-vault-strict.mjs +4 -15
- package/scripts/release.mjs +337 -29
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +36 -4
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +187 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/vault-consolidate.mjs +3 -11
- package/scripts/vault-integration-watcher.mjs +2 -4
- package/scripts/vault-mirror.mjs +111 -26
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-auq.md +31 -2
- package/skills/_shared/parallel-aware-preamble.md +18 -4
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/ecosystem-health/SKILL.md +4 -1
- package/skills/ecosystem-health/wizard.md +5 -0
- package/skills/evolve/SKILL.md +87 -11
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +38 -2
- package/skills/remote-offload/SKILL.md +89 -0
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +19 -9
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +41 -900
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +16 -2
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1271
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -269
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -33,7 +33,7 @@ import { spawn } from 'node:child_process';
|
|
|
33
33
|
|
|
34
34
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
35
35
|
import { appendSkillInvocation } from '../scripts/lib/skill-invocations-schema.mjs';
|
|
36
|
-
import {
|
|
36
|
+
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
37
37
|
import { shouldDailyFlush } from '../scripts/lib/telemetry/sync.mjs';
|
|
38
38
|
import { resolveConsent, readTelemetryState } from '../scripts/lib/telemetry/consent.mjs';
|
|
39
39
|
import { loadOwnerConfig } from '../scripts/lib/owner-yaml.mjs';
|
|
@@ -42,9 +42,21 @@ import { loadOwnerConfig } from '../scripts/lib/owner-yaml.mjs';
|
|
|
42
42
|
// Constants
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Metrics directory / ledger path. FUNCTIONS, not module-level consts:
|
|
47
|
+
* `getProjectDir()` walks the filesystem on first call, and this hook must not
|
|
48
|
+
* pay that at import time (#1153 P5).
|
|
49
|
+
*
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
function metricsDirPath() {
|
|
53
|
+
return path.join(getProjectDir(), '.orchestrator', 'metrics');
|
|
54
|
+
}
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
/** @returns {string} */
|
|
57
|
+
function jsonlPath() {
|
|
58
|
+
return path.join(metricsDirPath(), 'skill-invocations.jsonl');
|
|
59
|
+
}
|
|
48
60
|
|
|
49
61
|
/** Absolute path to the telemetry CLI (carries the hidden `_flush` subcommand). */
|
|
50
62
|
const TELEMETRY_CLI_PATH = path.resolve(
|
|
@@ -123,7 +135,7 @@ export function maybeSpawnDailyFlush({
|
|
|
123
135
|
now = Date.now(),
|
|
124
136
|
statePath,
|
|
125
137
|
queuePath,
|
|
126
|
-
metricsDir =
|
|
138
|
+
metricsDir = metricsDirPath(),
|
|
127
139
|
} = {}) {
|
|
128
140
|
try {
|
|
129
141
|
// 1. Cheapest gate: env kill-switches, no file I/O.
|
|
@@ -187,7 +199,7 @@ async function main() {
|
|
|
187
199
|
schema_version: 1,
|
|
188
200
|
};
|
|
189
201
|
|
|
190
|
-
await appendSkillInvocation(
|
|
202
|
+
await appendSkillInvocation(jsonlPath(), record);
|
|
191
203
|
|
|
192
204
|
// Daily-fallback telemetry flush — non-blocking, best-effort, never throws.
|
|
193
205
|
maybeSpawnDailyFlush();
|
|
@@ -84,13 +84,23 @@ if (!shouldRunHook('subagent-telemetry')) process.exit(0);
|
|
|
84
84
|
import fs from 'node:fs';
|
|
85
85
|
import path from 'node:path';
|
|
86
86
|
import { appendSubagent } from '../scripts/lib/subagents-schema.mjs';
|
|
87
|
-
import {
|
|
87
|
+
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
88
|
+
import { resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
|
|
88
89
|
|
|
89
90
|
// ---------------------------------------------------------------------------
|
|
90
91
|
// Constants
|
|
91
92
|
// ---------------------------------------------------------------------------
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Absolute path to the subagent ledger. A FUNCTION, not a module-level const:
|
|
96
|
+
* `getProjectDir()` walks the filesystem on first call, and this hook must not
|
|
97
|
+
* pay that at import time (#1153 P5).
|
|
98
|
+
*
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
function jsonlPath() {
|
|
102
|
+
return path.join(getProjectDir(), '.orchestrator', 'metrics', 'subagents.jsonl');
|
|
103
|
+
}
|
|
94
104
|
|
|
95
105
|
/**
|
|
96
106
|
* Defense-in-depth byte ceiling for transcript reads (#624). The transcript path
|
|
@@ -489,38 +499,22 @@ function extractTranscriptUsage(transcriptPath) {
|
|
|
489
499
|
* Derive the path of the subagent's OWN transcript from the parent transcript
|
|
490
500
|
* path the harness sends on stdin (#949).
|
|
491
501
|
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* Falling back to `transcript_path` IS the #949 defect: it makes every stop
|
|
501
|
-
* inherit the parent's running token totals, so the caller must record null
|
|
502
|
-
* instead (an honest absence).
|
|
503
|
-
*
|
|
504
|
-
* The agent_id is charset-restricted before it is interpolated into a path.
|
|
505
|
-
* Real ids are hex-ish tokens (e.g. `a60348a01ca982b4c`); anything else is
|
|
506
|
-
* rejected rather than sanitised, so no payload value can traverse out of the
|
|
507
|
-
* `subagents/` directory.
|
|
502
|
+
* Thin wrapper over the consolidated derivation (#1196) —
|
|
503
|
+
* `hooks/_lib/subagent-paths.mjs` `resolveSubagentSidecar()` — which now
|
|
504
|
+
* applies a `{1,64}` bound to `agentId` (STRICTER than this file's prior
|
|
505
|
+
* unbounded `+` charset check; see that module's header for the full
|
|
506
|
+
* divergence table). Returns null — never the parent path — when the
|
|
507
|
+
* derivation is not possible. Falling back to `transcript_path` IS the #949
|
|
508
|
+
* defect: it makes every stop inherit the parent's running token totals, so
|
|
509
|
+
* the caller must record null instead (an honest absence).
|
|
508
510
|
*
|
|
509
511
|
* @param {string|undefined|null} parentTranscriptPath — stdin `transcript_path`
|
|
510
512
|
* @param {string} agentId — the stopping agent's id
|
|
511
513
|
* @returns {string|null} absolute candidate path, or null when underivable
|
|
512
514
|
*/
|
|
513
515
|
function resolveSubagentTranscriptPath(parentTranscriptPath, agentId) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
// 'unknown' is the no-usable-id fallback — it names no file.
|
|
517
|
-
if (agentId === 'unknown') return null;
|
|
518
|
-
|
|
519
|
-
const dir = path.dirname(parentTranscriptPath);
|
|
520
|
-
const base = path.basename(parentTranscriptPath).replace(/\.jsonl$/i, '');
|
|
521
|
-
if (!base || base === '.' || base === '..') return null;
|
|
522
|
-
|
|
523
|
-
return path.join(dir, base, 'subagents', `agent-${agentId}.jsonl`);
|
|
516
|
+
const sidecar = resolveSubagentSidecar({ transcriptPath: parentTranscriptPath, agentId });
|
|
517
|
+
return sidecar === null ? null : sidecar.transcript;
|
|
524
518
|
}
|
|
525
519
|
|
|
526
520
|
/**
|
|
@@ -684,7 +678,7 @@ async function main() {
|
|
|
684
678
|
// duration measurement and the explicit orphan discriminator below.
|
|
685
679
|
// 'unknown' is the agent-id fallback for a payload with no usable id —
|
|
686
680
|
// joining on it would collide across unrelated agents, so never scan for it.
|
|
687
|
-
const startedAtMs = agentId === 'unknown' ? null : findStartTimestampMs(
|
|
681
|
+
const startedAtMs = agentId === 'unknown' ? null : findStartTimestampMs(jsonlPath(), agentId);
|
|
688
682
|
|
|
689
683
|
// duration_ms (#917): prefer the harness value, else join backwards to this
|
|
690
684
|
// agent's own start record, else null. Never 0 — see resolveDurationMs().
|
|
@@ -732,7 +726,7 @@ async function main() {
|
|
|
732
726
|
record['gen_ai.system'] = 'anthropic';
|
|
733
727
|
}
|
|
734
728
|
|
|
735
|
-
await appendSubagent(
|
|
729
|
+
await appendSubagent(jsonlPath(), record);
|
|
736
730
|
}
|
|
737
731
|
|
|
738
732
|
// Exit 0 always — informational hook must never block Claude.
|
package/monitors/monitors.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
{
|
|
3
3
|
"name": "ecosystem-health",
|
|
4
4
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/lib/ecosystem-health.mjs --watch",
|
|
5
|
-
"description": "Ecosystem repo health watcher (CI status, branch drift, stale baselines across vault-registered projects). Emits one NDJSON line per detected state change.",
|
|
6
|
-
"when": "on-skill-invoke:
|
|
5
|
+
"description": "Ecosystem repo health watcher (CI status, branch drift, stale baselines across vault-registered projects). Emits one NDJSON line per detected state change. Hung on session-start because the previous trigger (on-skill-invoke:ecosystem-health) was self-referential: it could only start when the ecosystem-health skill ran, and that skill has 0 recorded invocations fleet-wide, so the watcher never started once.",
|
|
6
|
+
"when": "on-skill-invoke:session-start"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"name": "convergence-monitor",
|
|
10
10
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/lib/convergence-monitor.mjs --tail",
|
|
11
|
-
"description": "Wave convergence telemetry monitor — tails .orchestrator/metrics/events.jsonl
|
|
11
|
+
"description": "Wave convergence telemetry monitor — tails .orchestrator/metrics/events.jsonl (that file only; it never opens sessions.jsonl) and surfaces convergence signals (shrinking diff, pass-rate plateau, velocity).",
|
|
12
12
|
"when": "on-skill-invoke:wave-executor"
|
|
13
13
|
},
|
|
14
14
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "session-orchestrator",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Loop engineering for AI coding agents — turn ad-hoc sessions into a repeatable research → plan → wave-execute → close loop with verification gates. Runs on Claude Code, Codex CLI, Cursor, and Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://session-orchestrator.com",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
".mcp.json",
|
|
26
|
+
"AGENTS.md",
|
|
26
27
|
"CHANGELOG.md",
|
|
28
|
+
"plugin.json",
|
|
27
29
|
"pi/",
|
|
28
30
|
"skills/",
|
|
29
31
|
"agents/",
|
|
@@ -37,11 +39,14 @@
|
|
|
37
39
|
"assets/",
|
|
38
40
|
".claude-plugin/",
|
|
39
41
|
".codex-plugin/",
|
|
42
|
+
".agents/",
|
|
40
43
|
".cursor/",
|
|
44
|
+
".orchestrator/policy/",
|
|
41
45
|
"docs/*.md",
|
|
42
46
|
"docs/telemetry/telemetry-claims.md",
|
|
43
47
|
"NOTICE",
|
|
44
48
|
"SECURITY.md",
|
|
49
|
+
"!skills/vault-sync/package-lock.json",
|
|
45
50
|
"!scripts/tests/**",
|
|
46
51
|
"!skills/vault-sync/tests/**",
|
|
47
52
|
"!**/node_modules/**",
|
|
@@ -66,9 +71,12 @@
|
|
|
66
71
|
"ajv": "^8.17.1",
|
|
67
72
|
"js-yaml": "^4.3.1",
|
|
68
73
|
"mdast-util-to-string": "^4.0.0",
|
|
74
|
+
"picomatch": "^4.0.4",
|
|
69
75
|
"remark": "^15.0.0",
|
|
70
76
|
"remark-parse": "^11.0.0",
|
|
71
77
|
"unified": "^11.0.0",
|
|
78
|
+
"yaml": "^2.9.0",
|
|
79
|
+
"zod": "^3.25.76",
|
|
72
80
|
"zx": "^8.1.0"
|
|
73
81
|
},
|
|
74
82
|
"devDependencies": {
|
package/pi/prompts/session.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Start a development session (housekeeping, feature, deep)
|
|
3
|
-
argument-hint: "[housekeeping|feature|deep]"
|
|
2
|
+
description: Start a development session (housekeeping, feature, deep; ultradeep = deep + profile)
|
|
3
|
+
argument-hint: "[housekeeping|feature|deep|ultradeep]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /session
|
package/plugin.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "session-orchestrator",
|
|
4
|
+
"version": "4.0.0",
|
|
5
|
+
"description": "Loop engineering for AI coding agents — turn ad-hoc sessions into a repeatable research → plan → wave-execute → close loop with verification gates. Runs on Claude Code, Codex CLI, Cursor, and Pi.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Bernhard Goetzendorfer"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://session-orchestrator.com",
|
|
10
|
+
"repository": "https://github.com/Kanevry/session-orchestrator",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"session",
|
|
14
|
+
"orchestration",
|
|
15
|
+
"waves",
|
|
16
|
+
"gitlab",
|
|
17
|
+
"github",
|
|
18
|
+
"quality-gates",
|
|
19
|
+
"verification-gates",
|
|
20
|
+
"multi-agent",
|
|
21
|
+
"codex",
|
|
22
|
+
"cursor",
|
|
23
|
+
"pi"
|
|
24
|
+
],
|
|
25
|
+
"skills": "./skills/",
|
|
26
|
+
"mcpServers": "./.mcp.json"
|
|
27
|
+
}
|
package/scripts/autopilot.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import { selectMode } from './lib/mode-selector.mjs';
|
|
|
47
47
|
import { probe, evaluate, DEFAULT_RESOURCE_THRESHOLDS } from './lib/resource-probe.mjs';
|
|
48
48
|
import { detectPeers } from './lib/session-registry.mjs';
|
|
49
49
|
import { normalizeSession } from './lib/session-schema.mjs';
|
|
50
|
+
import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
50
51
|
|
|
51
52
|
// ---------------------------------------------------------------------------
|
|
52
53
|
// CLI-level flag extraction
|
|
@@ -174,17 +175,29 @@ const branch = detectBranch();
|
|
|
174
175
|
const SESSIONS_JSONL_PATH = resolve('.orchestrator/metrics/sessions.jsonl');
|
|
175
176
|
|
|
176
177
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
178
|
+
* Set of the DISTINCT session identities currently recorded in sessions.jsonl.
|
|
179
|
+
* Empty set when the file is missing or unreadable.
|
|
180
|
+
*
|
|
181
|
+
* Identities, not lines (#1167): the ledger is append-only, so one physical
|
|
182
|
+
* session can occupy two lines (an abandoned stub plus the authoritative
|
|
183
|
+
* record that supersedes it, or the systemic double-stub pair). The
|
|
184
|
+
* pre/post comparison below asks "did the session I just spawned record
|
|
185
|
+
* itself?", and a raw line count answers that question wrong in BOTH
|
|
186
|
+
* directions — a duplicate pair looks like two sessions, while a supersede
|
|
187
|
+
* append (+1 line, −1 stub) leaves a canonical COUNT unchanged. Comparing the
|
|
188
|
+
* id SETS is immune to both.
|
|
189
|
+
*
|
|
190
|
+
* @returns {Set<string>}
|
|
179
191
|
*/
|
|
180
|
-
function
|
|
181
|
-
if (!existsSync(SESSIONS_JSONL_PATH)) return
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
192
|
+
function readSessionIds() {
|
|
193
|
+
if (!existsSync(SESSIONS_JSONL_PATH)) return new Set();
|
|
194
|
+
const ids = new Set();
|
|
195
|
+
for (const rec of readCanonicalSessions({ filePath: SESSIONS_JSONL_PATH })) {
|
|
196
|
+
if (typeof rec.session_id === 'string' && rec.session_id.length > 0) {
|
|
197
|
+
ids.add(rec.session_id);
|
|
198
|
+
}
|
|
187
199
|
}
|
|
200
|
+
return ids;
|
|
188
201
|
}
|
|
189
202
|
|
|
190
203
|
/**
|
|
@@ -249,7 +262,7 @@ async function modeSelector() {
|
|
|
249
262
|
* @returns {Promise<{session_id: string, agent_summary?: object, effectiveness?: object}>}
|
|
250
263
|
*/
|
|
251
264
|
async function sessionRunner({ mode, autopilotRunId }) {
|
|
252
|
-
const
|
|
265
|
+
const preIds = readSessionIds();
|
|
253
266
|
|
|
254
267
|
await new Promise((res, rej) => {
|
|
255
268
|
const childStdio = hasVerbose
|
|
@@ -281,8 +294,9 @@ async function sessionRunner({ mode, autopilotRunId }) {
|
|
|
281
294
|
});
|
|
282
295
|
});
|
|
283
296
|
|
|
284
|
-
const
|
|
285
|
-
|
|
297
|
+
const postIds = readSessionIds();
|
|
298
|
+
const appeared = [...postIds].some((id) => !preIds.has(id));
|
|
299
|
+
if (!appeared) {
|
|
286
300
|
throw new Error('no session record appended');
|
|
287
301
|
}
|
|
288
302
|
|
|
@@ -42,10 +42,12 @@ import { parseArgs } from 'node:util';
|
|
|
42
42
|
import { fileURLToPath } from 'node:url';
|
|
43
43
|
|
|
44
44
|
import { backfillAbandonedSession, isUuid } from './lib/session-close-backfill.mjs';
|
|
45
|
-
import {
|
|
45
|
+
import { emitEvent } from './lib/events.mjs';
|
|
46
|
+
import { getProjectDir } from './lib/platform.mjs';
|
|
46
47
|
|
|
47
48
|
const LOCK_ACQUIRED = 'orchestrator.session.lock.acquired';
|
|
48
49
|
const SESSION_STARTED = 'orchestrator.session.started';
|
|
50
|
+
const SESSION_ENDED = 'orchestrator.session.ended';
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
53
|
* Default cap on how many candidates may reach the (expensive) shared core in a
|
|
@@ -54,12 +56,31 @@ const SESSION_STARTED = 'orchestrator.session.started';
|
|
|
54
56
|
*/
|
|
55
57
|
export const SESSION_START_LIMIT = 25;
|
|
56
58
|
|
|
57
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Read a JSONL file into parsed objects; MISSING (ENOENT) → `[]` silently,
|
|
61
|
+
* UNREADABLE (EACCES/EISDIR/…) → `[]` with a stderr WARN (#1188); malformed
|
|
62
|
+
* lines skipped.
|
|
63
|
+
*/
|
|
58
64
|
function readJsonl(filePath) {
|
|
59
65
|
let raw;
|
|
60
66
|
try {
|
|
61
67
|
raw = fs.readFileSync(filePath, 'utf8');
|
|
62
|
-
} catch {
|
|
68
|
+
} catch (err) {
|
|
69
|
+
// #1188 — ENOENT and EACCES/EISDIR are different facts: a missing ledger is
|
|
70
|
+
// the ordinary fresh-repo case; an UNREADABLE one previously read as "no
|
|
71
|
+
// records" and made every downstream count silently wrong. Same split as
|
|
72
|
+
// readLockDetailed (session-lock.mjs § absent vs unreadable).
|
|
73
|
+
if (!err || err.code !== 'ENOENT') {
|
|
74
|
+
process.stderr.write(
|
|
75
|
+
`⚠ backfill-abandoned-sessions: cannot read ${filePath} ` +
|
|
76
|
+
`(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
|
|
77
|
+
'treating as EMPTY, counts below are floors\n',
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
// CEILING (BV-004): still [] rather than throw — this runs on the
|
|
81
|
+
// SessionStart path (SESSION_START_LIMIT), where a permissions fault must
|
|
82
|
+
// not crash the session start. REVISIT if the warn rate in events.jsonl
|
|
83
|
+
// shows masked corruption.
|
|
63
84
|
return [];
|
|
64
85
|
}
|
|
65
86
|
const out = [];
|
|
@@ -86,16 +107,22 @@ function readJsonl(filePath) {
|
|
|
86
107
|
export function planSessions({ repoRoot }) {
|
|
87
108
|
const events = readJsonl(path.join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl'));
|
|
88
109
|
|
|
89
|
-
|
|
110
|
+
// Two independent UUID -> semantic bridges (#1167). `lock.acquired` is the
|
|
111
|
+
// original one, but a session that LOST the lock-acquire race never emits it;
|
|
112
|
+
// its `session.ended` event carries `semantic_session_id` all the same (since
|
|
113
|
+
// #1068 AC1) and was never read here. Without that second bridge the
|
|
114
|
+
// candidate resolved semantic=null and the shared core minted a SYNTHETIC id,
|
|
115
|
+
// writing a duplicate stub beside the record the SessionEnd hook had already
|
|
116
|
+
// written under the real semantic id (measured 2026-09-02 @ c3ab480: 8 pairs).
|
|
117
|
+
// lock.acquired keeps precedence — it is the older, mode-carrying attestation.
|
|
118
|
+
const semanticFromLock = new Map();
|
|
119
|
+
const semanticFromEnded = new Map();
|
|
90
120
|
for (const ev of events) {
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
typeof ev.semantic_session_id === 'string'
|
|
95
|
-
) {
|
|
96
|
-
semanticByUuid.set(ev.session_id, ev.semantic_session_id);
|
|
97
|
-
}
|
|
121
|
+
if (typeof ev.session_id !== 'string' || typeof ev.semantic_session_id !== 'string') continue;
|
|
122
|
+
if (ev.event === LOCK_ACQUIRED) semanticFromLock.set(ev.session_id, ev.semantic_session_id);
|
|
123
|
+
else if (ev.event === SESSION_ENDED) semanticFromEnded.set(ev.session_id, ev.semantic_session_id);
|
|
98
124
|
}
|
|
125
|
+
const semanticByUuid = new Map([...semanticFromEnded, ...semanticFromLock]);
|
|
99
126
|
|
|
100
127
|
const seen = new Set();
|
|
101
128
|
const plan = [];
|
|
@@ -133,7 +160,14 @@ function readRecordedIds(repoRoot) {
|
|
|
133
160
|
const records = readJsonl(path.join(repoRoot, '.orchestrator', 'metrics', 'sessions.jsonl'));
|
|
134
161
|
const ids = new Set();
|
|
135
162
|
for (const r of records) {
|
|
136
|
-
if (r
|
|
163
|
+
if (!r) continue;
|
|
164
|
+
if (typeof r.session_id === 'string') ids.add(r.session_id);
|
|
165
|
+
// #1167 — a backfilled record now stamps the harness UUID it was
|
|
166
|
+
// reconstructed from. Indexing it lets a bare-UUID candidate be recognised
|
|
167
|
+
// as already-recorded even though the record itself is keyed semantically —
|
|
168
|
+
// the join that was structurally impossible while the field was absent
|
|
169
|
+
// (0 of 286 records carried it, measured 2026-09-02 @ c3ab480).
|
|
170
|
+
if (typeof r.raw_session_id === 'string') ids.add(r.raw_session_id);
|
|
137
171
|
}
|
|
138
172
|
return ids;
|
|
139
173
|
}
|
|
@@ -238,6 +272,7 @@ export async function runMigration({
|
|
|
238
272
|
case 'backfilled':
|
|
239
273
|
summary.backfilled += 1;
|
|
240
274
|
if (res.deadByAge) summary.dead_by_age += 1;
|
|
275
|
+
await emitBackfillCompleted(repoRoot, item, res);
|
|
241
276
|
// Keep the pre-filter snapshot in step with what we just wrote, so a
|
|
242
277
|
// second candidate bridging to the SAME semantic id is skipped cheaply
|
|
243
278
|
// instead of re-entering the core (which would reach the same verdict).
|
|
@@ -263,6 +298,40 @@ export async function runMigration({
|
|
|
263
298
|
return summary;
|
|
264
299
|
}
|
|
265
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Emit `orchestrator.session.backfill_completed` for a record THIS path wrote
|
|
303
|
+
* (#1167). Mirrors the payload of `hooks/on-session-end.mjs::emitBackfillOutcome`
|
|
304
|
+
* so both writers are queryable with one filter.
|
|
305
|
+
*
|
|
306
|
+
* Until now the startup/CLI path wrote records SILENTLY: only the SessionEnd
|
|
307
|
+
* hook emitted the event, so nothing in the event stream distinguished "the
|
|
308
|
+
* backfill never ran" from "the backfill ran here". Best-effort by contract —
|
|
309
|
+
* observability must never fail a migration run.
|
|
310
|
+
*
|
|
311
|
+
* @param {string} repoRoot
|
|
312
|
+
* @param {{sessionId: string, semanticSessionId: string|null}} item
|
|
313
|
+
* @param {{action: string, sessionId?: string, supersedes?: string, error?: string}} res
|
|
314
|
+
*/
|
|
315
|
+
async function emitBackfillCompleted(repoRoot, item, res) {
|
|
316
|
+
try {
|
|
317
|
+
await emitEvent(
|
|
318
|
+
'orchestrator.session.backfill_completed',
|
|
319
|
+
{
|
|
320
|
+
kind: 'abandoned',
|
|
321
|
+
action: typeof res?.action === 'string' ? res.action : 'unknown',
|
|
322
|
+
...(typeof item?.sessionId === 'string' ? { session_id: item.sessionId } : {}),
|
|
323
|
+
...(typeof item?.semanticSessionId === 'string'
|
|
324
|
+
? { semantic_session_id: item.semanticSessionId }
|
|
325
|
+
: {}),
|
|
326
|
+
...(typeof res?.sessionId === 'string' ? { record_id: res.sessionId } : {}),
|
|
327
|
+
...(typeof res?.supersedes === 'string' ? { supersedes: res.supersedes } : {}),
|
|
328
|
+
...(typeof res?.error === 'string' ? { reason: res.error } : {}),
|
|
329
|
+
},
|
|
330
|
+
{ repoRoot },
|
|
331
|
+
);
|
|
332
|
+
} catch { /* observability is best-effort */ }
|
|
333
|
+
}
|
|
334
|
+
|
|
266
335
|
/**
|
|
267
336
|
* SessionStart entry point (#926) — decouple the backfill from the /close path.
|
|
268
337
|
*
|
|
@@ -313,13 +382,59 @@ export async function backfillOnSessionStart({ repoRoot, limit = SESSION_START_L
|
|
|
313
382
|
if (typeof repoRoot !== 'string' || repoRoot.length === 0) return null;
|
|
314
383
|
// newestFirst is mandatory here — see runMigration's param docs for the
|
|
315
384
|
// measured failure (budget burned on ancient already-recorded candidates).
|
|
316
|
-
|
|
385
|
+
const summary = await runMigration({ repoRoot, apply: true, limit, newestFirst: true });
|
|
386
|
+
// The telemetry flush rides the SAME start-time trigger, for the same reason
|
|
387
|
+
// the backfill does — see flushTelemetryOnSessionStart.
|
|
388
|
+
await flushTelemetryOnSessionStart(repoRoot);
|
|
389
|
+
return summary;
|
|
317
390
|
} catch {
|
|
318
391
|
// Swallowed by contract — see the docblock. The hook is informational-only.
|
|
319
392
|
return null;
|
|
320
393
|
}
|
|
321
394
|
}
|
|
322
395
|
|
|
396
|
+
/** POST budget for the start-time telemetry drain (ms). Deliberately tighter than
|
|
397
|
+
* the close-time budget: a session START must not wait on the network. Expiry is
|
|
398
|
+
* LOSSLESS — flush() routes a timed-out batch into the offline queue. */
|
|
399
|
+
const STARTUP_FLUSH_TIMEOUT_MS = 1500;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* SECOND FLUSH TRIGGER (GitLab #1234) — drain the PREVIOUS session's telemetry
|
|
403
|
+
* queue at the NEXT session's start.
|
|
404
|
+
*
|
|
405
|
+
* THE BUG THIS FIXES: the mechanical flush (#1138) lives only in
|
|
406
|
+
* `hooks/on-session-end.mjs`, which fires on a REGULAR close. Measured 2026-09-06
|
|
407
|
+
* over 90 fleet days, the real close rate is 21,3 % (429 clean closes / 2.016
|
|
408
|
+
* distinct `session.started` ids) — so roughly four sessions in five never reach
|
|
409
|
+
* the only code path that sends. This is the exact argument
|
|
410
|
+
* `backfillOnSessionStart` already makes for the ledger, applied to the ping:
|
|
411
|
+
* SessionStart is the trigger that survives whatever killed the last session.
|
|
412
|
+
*
|
|
413
|
+
* Three properties make this safe to run on the hot start path:
|
|
414
|
+
* - LAZY IMPORT. `sync.mjs` is pulled in at CALL time, not module load. Loading
|
|
415
|
+
* it statically would put the whole telemetry subtree onto the SessionStart
|
|
416
|
+
* hook's import graph — the graph `tests/hooks/on-stop.test.mjs` guards for
|
|
417
|
+
* bare specifiers, and the cost would be paid on every start whether or not
|
|
418
|
+
* telemetry is even consented to.
|
|
419
|
+
* - GATED, ALWAYS. `flush()` opens with `resolveConsent()`; with no consent it
|
|
420
|
+
* returns before any network, queue write, or anon-ID mint.
|
|
421
|
+
* - NEVER THROWS, NEVER BLOCKS A START. Every failure is swallowed, and the POST
|
|
422
|
+
* is bounded at STARTUP_FLUSH_TIMEOUT_MS; a timeout queues the batch rather
|
|
423
|
+
* than losing it.
|
|
424
|
+
*
|
|
425
|
+
* @param {string} repoRoot
|
|
426
|
+
* @returns {Promise<void>}
|
|
427
|
+
*/
|
|
428
|
+
async function flushTelemetryOnSessionStart(repoRoot) {
|
|
429
|
+
try {
|
|
430
|
+
const { flush } = await import('./lib/telemetry/sync.mjs');
|
|
431
|
+
await flush({
|
|
432
|
+
metricsDir: path.join(repoRoot, '.orchestrator', 'metrics'),
|
|
433
|
+
timeoutMs: STARTUP_FLUSH_TIMEOUT_MS,
|
|
434
|
+
});
|
|
435
|
+
} catch { /* fire-and-forget by contract */ }
|
|
436
|
+
}
|
|
437
|
+
|
|
323
438
|
function renderHuman(summary) {
|
|
324
439
|
const lines = [];
|
|
325
440
|
lines.push(`Backfill abandoned sessions — ${summary.mode}`);
|
|
@@ -372,7 +487,7 @@ async function main() {
|
|
|
372
487
|
' --dry-run preview only (DEFAULT — nothing is written)\n' +
|
|
373
488
|
' --apply synthesize + append abandoned-session stubs\n' +
|
|
374
489
|
' --json emit the summary as JSON to stdout\n' +
|
|
375
|
-
' --repo-root override the project root (default: resolved
|
|
490
|
+
' --repo-root override the project root (default: resolved project dir)\n' +
|
|
376
491
|
' --assume-dead-before ISO-8601 cutoff; a candidate whose last known event\n' +
|
|
377
492
|
' predates it bypasses a live foreign session.lock\n' +
|
|
378
493
|
'Exit codes: 0 ok, 1 arg error, 2 system error\n'
|
|
@@ -382,7 +497,7 @@ async function main() {
|
|
|
382
497
|
|
|
383
498
|
// --apply is an explicit opt-in; absent it (or with --dry-run) we never write.
|
|
384
499
|
const apply = values.apply === true && values['dry-run'] !== true;
|
|
385
|
-
const repoRoot = values['repo-root'] ||
|
|
500
|
+
const repoRoot = values['repo-root'] || getProjectDir();
|
|
386
501
|
|
|
387
502
|
let assumeDeadBeforeMs = null;
|
|
388
503
|
if (typeof values['assume-dead-before'] === 'string' && values['assume-dead-before'].length > 0) {
|
|
@@ -71,7 +71,7 @@ import { parseSessionConfig } from './lib/config.mjs';
|
|
|
71
71
|
import { subjectToSlug, parseFrontmatter } from './lib/vault-mirror/utils.mjs';
|
|
72
72
|
import { kebab } from './lib/learnings/kebab.mjs';
|
|
73
73
|
import { validateLearning } from './lib/learnings/schema.mjs';
|
|
74
|
-
import { appendLearning } from './lib/learnings/io.mjs';
|
|
74
|
+
import { appendLearning, isBackupOf } from './lib/learnings/io.mjs';
|
|
75
75
|
|
|
76
76
|
const DEFAULT_RULES_DIR = '.claude/rules';
|
|
77
77
|
const DEFAULT_STORE = '.orchestrator/metrics/learnings.jsonl';
|
|
@@ -620,7 +620,13 @@ function idsInStore(filePath) {
|
|
|
620
620
|
return ids;
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
-
/**
|
|
623
|
+
/**
|
|
624
|
+
* List `<store>.bak[-.]*` siblings (a verbatim original beats any
|
|
625
|
+
* reconstruction). Both delimiter forms count — the legacy dot form
|
|
626
|
+
* (`learnings.jsonl.bak.evolve-<ts>`) is still on disk in consumer repos and
|
|
627
|
+
* was silently skipped here, i.e. a backup that exists and is not found in the
|
|
628
|
+
* one case it is for (#1173).
|
|
629
|
+
*/
|
|
624
630
|
function backupPaths(storePath) {
|
|
625
631
|
const dir = dirname(storePath);
|
|
626
632
|
const base = basename(storePath);
|
|
@@ -633,7 +639,7 @@ function backupPaths(storePath) {
|
|
|
633
639
|
return [];
|
|
634
640
|
}
|
|
635
641
|
return names
|
|
636
|
-
.filter((n) =>
|
|
642
|
+
.filter((n) => isBackupOf(base, n))
|
|
637
643
|
.sort()
|
|
638
644
|
.map((n) => join(dir, n));
|
|
639
645
|
}
|