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
|
@@ -26,7 +26,49 @@
|
|
|
26
26
|
* session id.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import {
|
|
29
|
+
import { readFileSync } from 'node:fs';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import { isLockShape } from '../session-lock-shape.mjs';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read the two session ids out of `<repoRoot>/.orchestrator/session.lock`.
|
|
35
|
+
*
|
|
36
|
+
* A deliberate, behaviour-identical stand-in for `readLock()` from
|
|
37
|
+
* `../session-lock.mjs` (#1153 P7): that import drags a static closure of six
|
|
38
|
+
* modules (session-lock → exclusivity-matrix, file-lock, io, host-identity,
|
|
39
|
+
* crypto-digest-utils — measured 2026-09-04 by following its `^import` lines)
|
|
40
|
+
* into every consumer of this module, of which the live
|
|
41
|
+
* `hooks/enforce-scope.mjs` runs on EVERY Edit/Write. Two strings do not need
|
|
42
|
+
* a lock manager.
|
|
43
|
+
*
|
|
44
|
+
* Tolerance is matched to `readLock()` exactly, which collapses every non-ok
|
|
45
|
+
* outcome of `readLockDetailed()` to `null`: missing file, unreadable file,
|
|
46
|
+
* invalid JSON, and **valid JSON that fails the lock schema** all yield `{}`
|
|
47
|
+
* here. The schema check is not reproduced but IMPORTED — `isLockShape()` from
|
|
48
|
+
* `../session-lock-shape.mjs` is the same predicate `parseLock()` applies, and
|
|
49
|
+
* that module imports nothing, so sharing it costs the hook chain no closure.
|
|
50
|
+
* A copy would have been free to drift the fail-OPEN way: a relaxed
|
|
51
|
+
* `parseLock` plus an unchanged copy here drops the lock tier's ids, the own
|
|
52
|
+
* manifest reads `foreign`, and enforcement switches itself off silently.
|
|
53
|
+
*
|
|
54
|
+
* Never throws.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} repoRoot
|
|
57
|
+
* @returns {{ session_id?: string, semantic_session_id?: string }}
|
|
58
|
+
*/
|
|
59
|
+
function readLockIds(repoRoot) {
|
|
60
|
+
try {
|
|
61
|
+
const raw = readFileSync(
|
|
62
|
+
path.join(repoRoot ?? process.cwd(), '.orchestrator', 'session.lock'),
|
|
63
|
+
'utf8',
|
|
64
|
+
);
|
|
65
|
+
const obj = JSON.parse(raw);
|
|
66
|
+
if (!isLockShape(obj)) return {};
|
|
67
|
+
return { session_id: obj.session_id, semantic_session_id: obj.semantic_session_id };
|
|
68
|
+
} catch {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
30
72
|
|
|
31
73
|
/**
|
|
32
74
|
* The set of session ids that provably name THIS session — the UNION of every
|
|
@@ -112,11 +154,66 @@ export function readOwnSessionIds(repoRoot, { hookInput = null } = {}) {
|
|
|
112
154
|
|
|
113
155
|
// Source 3 — repo-global lock file (the manifest writer's own identity).
|
|
114
156
|
try {
|
|
115
|
-
const lock =
|
|
157
|
+
const lock = readLockIds(repoRoot);
|
|
116
158
|
for (const key of ['session_id', 'semantic_session_id']) add(lock?.[key]);
|
|
117
159
|
} catch {
|
|
118
|
-
/*
|
|
160
|
+
/* readLockIds never throws by contract, but that contract is not ours to trust */
|
|
161
|
+
}
|
|
162
|
+
return ids;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The ids that name this session and are PROCESS-LOCAL — tiers 1 and 2 only,
|
|
167
|
+
* never the lock and never STATE.md.
|
|
168
|
+
*
|
|
169
|
+
* A sibling of {@link readOwnSessionIds}, not a replacement: the two answer
|
|
170
|
+
* different questions and the difference is the whole point.
|
|
171
|
+
*
|
|
172
|
+
* - `readOwnSessionIds()` answers *"could this id name me?"* and unions three
|
|
173
|
+
* tiers, the third of which IS `session.lock`. That union is correct when
|
|
174
|
+
* the thing being classified was written by some OTHER process (a wave-scope
|
|
175
|
+
* manifest), because every id this process can legitimately claim counts.
|
|
176
|
+
* - This function answers *"which process is emitting right now?"*, and for
|
|
177
|
+
* that question the lock is **vacuous**: when the candidate ids under
|
|
178
|
+
* judgement are the lock's OWN values, a membership test against a set that
|
|
179
|
+
* contains the lock matches by construction — a peer-owned lock would
|
|
180
|
+
* classify as `own` 100% of the time.
|
|
181
|
+
*
|
|
182
|
+
* **STATE.md is excluded for the same reason, and this is the #1177-FX1 fix.**
|
|
183
|
+
* `.claude/STATE.md` is a SHARED working-copy artefact written by whichever
|
|
184
|
+
* session owns the working copy — normally the lock holder. So when a peer holds
|
|
185
|
+
* the lock, the peer also wrote STATE.md, and the two "independent" witnesses
|
|
186
|
+
* agree with each other about the PEER. Unioning a shared-file witness with a
|
|
187
|
+
* process-local one lets the weaker witness carry the verdict while a
|
|
188
|
+
* disagreeing process-local id cannot veto it (measured: lock=peer +
|
|
189
|
+
* STATE.md=peer + `CLAUDE_CODE_SESSION_ID`=me stamped the PEER's ids). Tiering
|
|
190
|
+
* rather than unioning is the fix — a better signal REPLACES a worse one
|
|
191
|
+
* (`.claude/rules/host-resources.md` § HR-102).
|
|
192
|
+
*
|
|
193
|
+
* Never throws.
|
|
194
|
+
*
|
|
195
|
+
* @param {{ env?: object, hookInput?: object|null }} [opts]
|
|
196
|
+
* @param {object} [opts.env=process.env] — injectable for tests.
|
|
197
|
+
* @param {object|null} [opts.hookInput=null] — the harness's statement about
|
|
198
|
+
* THIS invocation, when the caller is a hook.
|
|
199
|
+
* @returns {string[]} possibly EMPTY — an empty result means "this process
|
|
200
|
+
* cannot prove who it is", which callers must treat as unprovable rather
|
|
201
|
+
* than as a match.
|
|
202
|
+
*/
|
|
203
|
+
export function readProcessLocalSessionIds({ env = process.env, hookInput = null } = {}) {
|
|
204
|
+
const ids = [];
|
|
205
|
+
const add = (value) => {
|
|
206
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
207
|
+
if (trimmed && !ids.includes(trimmed)) ids.push(trimmed);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Tier 1 — the harness's statement about THIS invocation.
|
|
211
|
+
if (hookInput && typeof hookInput === 'object') {
|
|
212
|
+
for (const key of ['session_id', 'sessionId', 'parent_session_id']) add(hookInput[key]);
|
|
119
213
|
}
|
|
214
|
+
// Tier 2 — process-scoped env var.
|
|
215
|
+
add(env?.CLAUDE_CODE_SESSION_ID);
|
|
216
|
+
|
|
120
217
|
return ids;
|
|
121
218
|
}
|
|
122
219
|
|
|
@@ -135,22 +232,101 @@ export function readOwnSessionIds(repoRoot, { hookInput = null } = {}) {
|
|
|
135
232
|
* - `'own'` — an id matched.
|
|
136
233
|
*
|
|
137
234
|
* Both id fields are consulted because they address the same session under two
|
|
138
|
-
* naming schemes: `
|
|
139
|
-
* Code), `
|
|
235
|
+
* naming schemes: `session_id` is the raw harness session id (a UUID on Claude
|
|
236
|
+
* Code), `semantic_session_id` the `<branch>-<date>-<mode>-<n>` form. A harness
|
|
140
237
|
* that resolves only the semantic one must still recognise its own manifest.
|
|
238
|
+
* The pre-#1153 spellings `session` / `semantic_session` are still READ (see
|
|
239
|
+
* {@link MANIFEST_SESSION_KEYS}).
|
|
141
240
|
*
|
|
142
241
|
* @param {unknown} scope — parsed wave-scope manifest (any shape; a non-object
|
|
143
242
|
* simply yields no ids, hence `'unknown'`).
|
|
144
|
-
* @param {Set<string>} ownIds — from
|
|
243
|
+
* @param {Set<string>} ownIds — from EITHER producer, depending on what is
|
|
244
|
+
* being judged: {@link readOwnSessionIds} when every id this process could
|
|
245
|
+
* legitimately claim counts, or `new Set(`{@link readProcessLocalSessionIds}
|
|
246
|
+
* `(...))` when the lock tier would match vacuously — which is the case for a
|
|
247
|
+
* wave-scope manifest in a checkout shared by two sessions (#1194). Note the
|
|
248
|
+
* latter returns a `string[]`: a bare array is NOT a Set and folds to the
|
|
249
|
+
* empty set below, yielding `'unknown'` for every manifest.
|
|
145
250
|
* @returns {{ verdict: 'own'|'foreign'|'unknown', manifestIds: string[] }}
|
|
251
|
+
* @see MANIFEST_SESSION_KEYS / {@link manifestSessionBinding} — defined
|
|
252
|
+
* immediately below rather than above this doc comment, because the three
|
|
253
|
+
* live hooks import this module on EVERY tool call: a use-before-define here
|
|
254
|
+
* throws inside the hook chain and locks every session sharing the checkout
|
|
255
|
+
* out of Edit/Write/Bash (measured 2026-09-04, ~8 minutes, #1153 P2).
|
|
256
|
+
*/
|
|
257
|
+
/**
|
|
258
|
+
* The session-binding key names of a `wave-scope.json` manifest — the ONE place
|
|
259
|
+
* these literals live (#1153 P2). Every reader imports them from here instead
|
|
260
|
+
* of repeating the strings: `scripts/materialize-wave-scope.mjs`,
|
|
261
|
+
* `scripts/validate-wave-scope.mjs`, `scripts/memory-propose.mjs`, and — via
|
|
262
|
+
* {@link classifyManifestSession} — `scripts/lib/events.mjs` plus the three
|
|
263
|
+
* live hooks.
|
|
264
|
+
*
|
|
265
|
+
* `current` are the canonical names, chosen to match the two neighbouring
|
|
266
|
+
* session artefacts a reader already knows: `.orchestrator/session.lock` and
|
|
267
|
+
* `current-session.json` both spell them `session_id` / `semantic_session_id`,
|
|
268
|
+
* and `scripts/lib/quality-gate.mjs` reads that exact pair. One session
|
|
269
|
+
* identity should not carry two spellings depending on which file names it.
|
|
270
|
+
*
|
|
271
|
+
* `legacy` are the pre-#1153 spellings, and they are **accepted on the READ
|
|
272
|
+
* side only, until the next minor release**. `wave-scope.json` is git-ignored
|
|
273
|
+
* and session-ephemeral, so the one surviving reason to read them is a package
|
|
274
|
+
* upgraded mid-wave with an old-format manifest already on disk. The writer
|
|
275
|
+
* (`scripts/wave-scope-binding.mjs`) emits `current` exclusively.
|
|
276
|
+
*
|
|
277
|
+
* @type {Readonly<{ current: readonly string[], legacy: readonly string[] }>}
|
|
146
278
|
*/
|
|
279
|
+
export const MANIFEST_SESSION_KEYS = Object.freeze({
|
|
280
|
+
current: Object.freeze(['session_id', 'semantic_session_id']),
|
|
281
|
+
legacy: Object.freeze(['session', 'semantic_session']),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Resolve the binding out of a manifest under BOTH key spellings. A non-object
|
|
286
|
+
* — including an ARRAY, which `typeof` calls `'object'` — yields no ids.
|
|
287
|
+
*
|
|
288
|
+
* **A CONFLICT yields no value for that slot, and that is fail-CLOSED.**
|
|
289
|
+
* When both spellings of one slot are present with different non-empty values,
|
|
290
|
+
* the manifest is self-contradictory (`scripts/validate-wave-scope.mjs` calls
|
|
291
|
+
* exactly this an ERROR — but no hook runs the validator, so the classifier is
|
|
292
|
+
* the only thing standing between the manifest and the guard). Preferring the
|
|
293
|
+
* current spelling let a peer DISARM this session's guard by appending
|
|
294
|
+
* `"session_id": "attacker"` beside a legitimate legacy `"session": "<me>"`:
|
|
295
|
+
* the slot then resolved to an id this session does not carry,
|
|
296
|
+
* {@link classifyManifestSession} returned `'foreign'`, and
|
|
297
|
+
* `hooks/enforce-scope.mjs` skips enforcement on `'foreign'`. Dropping the slot
|
|
298
|
+
* instead collapses the verdict to `'unknown'`, which every caller treats as
|
|
299
|
+
* "keep enforcing".
|
|
300
|
+
*
|
|
301
|
+
* Both present and EQUAL (after trim) → that value. Only one present → that
|
|
302
|
+
* value. Both present and different → the slot is omitted.
|
|
303
|
+
*
|
|
304
|
+
* @param {unknown} scope
|
|
305
|
+
* @returns {{ session_id?: string, semantic_session_id?: string }}
|
|
306
|
+
*/
|
|
307
|
+
export function manifestSessionBinding(scope) {
|
|
308
|
+
const out = {};
|
|
309
|
+
if (!scope || typeof scope !== 'object' || Array.isArray(scope)) return out;
|
|
310
|
+
MANIFEST_SESSION_KEYS.current.forEach((key, i) => {
|
|
311
|
+
const legacyKey = MANIFEST_SESSION_KEYS.legacy[i];
|
|
312
|
+
const pick = (v) => (typeof v === 'string' && v.trim() ? v.trim() : '');
|
|
313
|
+
const current = pick(scope[key]);
|
|
314
|
+
const legacy = pick(scope[legacyKey]);
|
|
315
|
+
if (current && legacy && current !== legacy) return; // conflict → no value
|
|
316
|
+
const value = current || legacy;
|
|
317
|
+
if (value) out[key] = value;
|
|
318
|
+
});
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** @see the contract note above `MANIFEST_SESSION_KEYS` — the full docblock for
|
|
323
|
+
* this function sits there, separated from it only because the constants must
|
|
324
|
+
* be defined before use (#1153 P2). */
|
|
147
325
|
export function classifyManifestSession(scope, ownIds) {
|
|
148
326
|
const manifestIds = [];
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (value) manifestIds.push(value);
|
|
153
|
-
}
|
|
327
|
+
const binding = manifestSessionBinding(scope);
|
|
328
|
+
for (const key of MANIFEST_SESSION_KEYS.current) {
|
|
329
|
+
if (binding[key]) manifestIds.push(binding[key]);
|
|
154
330
|
}
|
|
155
331
|
const own = ownIds instanceof Set ? ownIds : new Set();
|
|
156
332
|
if (manifestIds.length === 0 || own.size === 0) return { verdict: 'unknown', manifestIds };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-lock-shape.mjs — the ONE predicate that decides whether a parsed
|
|
3
|
+
* JSON value is a `.orchestrator/session.lock` record (#1153 P7).
|
|
4
|
+
*
|
|
5
|
+
* **Zero imports on purpose.** One of the two consumers is
|
|
6
|
+
* `scripts/lib/session-identity/own-session.mjs`, which the live
|
|
7
|
+
* `hooks/enforce-scope.mjs` loads on EVERY Edit/Write; anything this module
|
|
8
|
+
* imported would join that hook's static closure. It therefore holds six
|
|
9
|
+
* `typeof` checks and nothing else.
|
|
10
|
+
*
|
|
11
|
+
* **The two consumers, and why they must not drift apart:**
|
|
12
|
+
*
|
|
13
|
+
* - `scripts/lib/session-lock.mjs` `parseLock()` — the lock manager's own
|
|
14
|
+
* reader; a value that fails this predicate is `null` there.
|
|
15
|
+
* - `scripts/lib/session-identity/own-session.mjs` `readLockIds()` — the
|
|
16
|
+
* dependency-free stand-in that reads the same two ids out of the same
|
|
17
|
+
* file without dragging the lock manager into a hook.
|
|
18
|
+
*
|
|
19
|
+
* The drift direction is FAIL-OPEN, which is why the predicate is shared
|
|
20
|
+
* rather than repeated: if `parseLock` relaxed the shape and `readLockIds` did
|
|
21
|
+
* not, the lock tier of {@link readOwnSessionIds} would stop contributing its
|
|
22
|
+
* ids, this session's own manifest would classify `foreign`, and
|
|
23
|
+
* `hooks/enforce-scope.mjs` would silently skip enforcement for the whole wave.
|
|
24
|
+
*
|
|
25
|
+
* @param {unknown} obj — a value already parsed from JSON.
|
|
26
|
+
* @returns {boolean} true when `obj` carries all six required lock fields with
|
|
27
|
+
* the right primitive types. `semantic_session_id` and `last_heartbeat` are
|
|
28
|
+
* deliberately NOT required — both are optional by schema (v1 locks predate
|
|
29
|
+
* `last_heartbeat`, and `semantic_session_id` is absent on harnesses that
|
|
30
|
+
* resolve no semantic id).
|
|
31
|
+
*/
|
|
32
|
+
export function isLockShape(obj) {
|
|
33
|
+
return (
|
|
34
|
+
typeof obj === 'object' &&
|
|
35
|
+
obj !== null &&
|
|
36
|
+
typeof obj.session_id === 'string' &&
|
|
37
|
+
typeof obj.started_at === 'string' &&
|
|
38
|
+
typeof obj.mode === 'string' &&
|
|
39
|
+
typeof obj.pid === 'number' &&
|
|
40
|
+
typeof obj.host === 'string' &&
|
|
41
|
+
typeof obj.ttl_hours === 'number'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -43,6 +43,7 @@ import os from 'node:os';
|
|
|
43
43
|
import path from 'node:path';
|
|
44
44
|
import crypto from 'node:crypto';
|
|
45
45
|
import { classifyMode } from './exclusivity-matrix.mjs';
|
|
46
|
+
import { isLockShape } from './session-lock-shape.mjs';
|
|
46
47
|
import { isPidAliveOnHost } from './file-lock.mjs';
|
|
47
48
|
import { writeJsonAtomicSync } from './io.mjs';
|
|
48
49
|
import { hostnamesMatch, lockHostCandidate, recordHostAlias, stableHostname } from './host-identity.mjs';
|
|
@@ -199,16 +200,10 @@ function heartbeatAgeMinutes(lock) {
|
|
|
199
200
|
function parseLock(raw) {
|
|
200
201
|
try {
|
|
201
202
|
const obj = JSON.parse(raw);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
typeof obj.started_at === 'string' &&
|
|
207
|
-
typeof obj.mode === 'string' &&
|
|
208
|
-
typeof obj.pid === 'number' &&
|
|
209
|
-
typeof obj.host === 'string' &&
|
|
210
|
-
typeof obj.ttl_hours === 'number'
|
|
211
|
-
) {
|
|
203
|
+
// The six-field predicate lives in ONE place (#1153 P7) — see
|
|
204
|
+
// `./session-lock-shape.mjs` for the other consumer and the fail-open
|
|
205
|
+
// drift this sharing prevents.
|
|
206
|
+
if (isLockShape(obj)) {
|
|
212
207
|
// Schema v1 → v2 normalisation: when `last_heartbeat` is absent or
|
|
213
208
|
// non-string, treat the lock as if it heartbeat-ed once at started_at.
|
|
214
209
|
const normalised = { ...obj };
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* peer-detection (F2, #168) and hooks/on-stop.mjs clean deregister + zombie
|
|
6
6
|
* sweep (F3, #169).
|
|
7
7
|
*
|
|
8
|
-
* Registry location:
|
|
9
|
-
* Sweep log:
|
|
8
|
+
* Registry location: `<private-config-dir>/sessions/active/<sessionId>.json`
|
|
9
|
+
* Sweep log: `<private-config-dir>/sessions/sweep.log` (JSONL)
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
11
|
+
* `<private-config-dir>` is `resolvePrivateConfigDir()` — `SO_CONFIG_HOME` >
|
|
12
|
+
* `XDG_CONFIG_HOME` > `~/.config/session-orchestrator`. `SO_SESSION_REGISTRY_DIR`
|
|
13
|
+
* overrides the whole `sessions/` directory (not `active/`) and outranks all
|
|
14
|
+
* three — used by tests for isolation.
|
|
13
15
|
*
|
|
14
16
|
* Heartbeat schema (per issue #167):
|
|
15
17
|
* {
|
|
@@ -27,7 +29,6 @@
|
|
|
27
29
|
* deliberately, see `skills/_shared/state-ownership.md` § Schema v1 Sunset.
|
|
28
30
|
*/
|
|
29
31
|
|
|
30
|
-
import os from 'node:os';
|
|
31
32
|
import path from 'node:path';
|
|
32
33
|
import crypto from 'node:crypto';
|
|
33
34
|
import { promises as fs } from 'node:fs';
|
|
@@ -36,16 +37,31 @@ import { digestSha256 } from './crypto-digest-utils.mjs';
|
|
|
36
37
|
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
37
38
|
|
|
38
39
|
import { utcTimestamp, appendJsonl } from './common.mjs';
|
|
40
|
+
import { resolvePrivateConfigDir } from './config/private-config-dir.mjs';
|
|
39
41
|
|
|
40
42
|
// ---------------------------------------------------------------------------
|
|
41
43
|
// Paths
|
|
42
44
|
// ---------------------------------------------------------------------------
|
|
43
45
|
|
|
44
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Parent directory for all session-registry state.
|
|
48
|
+
*
|
|
49
|
+
* Precedence: `SO_SESSION_REGISTRY_DIR` (names the `sessions/` dir ITSELF —
|
|
50
|
+
* highest, and what every existing test uses for isolation) > the host-private
|
|
51
|
+
* config dir resolved by `resolvePrivateConfigDir()` (`SO_CONFIG_HOME` >
|
|
52
|
+
* `XDG_CONFIG_HOME` > `~/.config/session-orchestrator`).
|
|
53
|
+
*
|
|
54
|
+
* Hardcoding `~/.config` here was the #1223 hazard class in its registry form:
|
|
55
|
+
* a sandboxed probe run that set `SO_CONFIG_HOME=<tmp>` moved every OTHER
|
|
56
|
+
* host-private artefact but not this one, so the throwaway session registered
|
|
57
|
+
* itself in the operator's REAL registry and was then discovered as a live peer.
|
|
58
|
+
* `.trim()` guards the whitespace-only env value (`development.md` § Error
|
|
59
|
+
* Handling) — `' '` is truthy and would otherwise be returned verbatim.
|
|
60
|
+
*/
|
|
45
61
|
export function registryBaseDir() {
|
|
46
|
-
const override = process.env.SO_SESSION_REGISTRY_DIR;
|
|
47
|
-
if (override
|
|
48
|
-
return path.join(
|
|
62
|
+
const override = (process.env.SO_SESSION_REGISTRY_DIR || '').trim();
|
|
63
|
+
if (override) return override;
|
|
64
|
+
return path.join(resolvePrivateConfigDir(), 'sessions');
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
/** Directory holding one JSON file per active session. */
|
|
@@ -56,8 +56,31 @@ export const SESSION_KEY_ALIASES = Object.freeze({
|
|
|
56
56
|
// Enums / required field lists
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
58
|
|
|
59
|
-
/**
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Closed set of valid session_type values.
|
|
61
|
+
*
|
|
62
|
+
* `unknown` (GitLab #1234, added 2026-09-06) is NOT a fourth session MODE — it is
|
|
63
|
+
* the absence of a measurement, and it exists so a reconstructed record can say
|
|
64
|
+
* so instead of guessing. Measured 2026-09-06 over the 90-day fleet corpus: all
|
|
65
|
+
* 1.656 `abandoned` records carry `_session_type_inferred: true` + `total_waves: 0`
|
|
66
|
+
* and NO organically written `abandoned` record exists anywhere — i.e. every one
|
|
67
|
+
* of them was labelled `housekeeping` by `scripts/lib/session-close-backfill.mjs`
|
|
68
|
+
* because the enum left it no alternative, and that guess is what produced the
|
|
69
|
+
* fleet-wide "27 % close rate" figure (the real rate is 21,3 %).
|
|
70
|
+
*
|
|
71
|
+
* Only `synthesizeRecord()` in `scripts/lib/session-close-backfill.mjs` writes it,
|
|
72
|
+
* and only for records it also flags `_session_type_inferred` + `_synthetic`.
|
|
73
|
+
* A live session never becomes `unknown`: `/session` still resolves one of the
|
|
74
|
+
* three modes, and `scripts/lib/wave-sizing.mjs` (which THROWS on a fourth value)
|
|
75
|
+
* is only ever fed the live type, never a ledger record.
|
|
76
|
+
*
|
|
77
|
+
* Known mis-bucket, out of this change's scope: `normalizeSessionType()` in
|
|
78
|
+
* `scripts/lib/telemetry/schema.mjs:382` maps any non-empty unlisted value to
|
|
79
|
+
* `'other'` ("something WAS measured and is not one of the three modes"), which
|
|
80
|
+
* is the opposite of what `unknown` means — even though that module already
|
|
81
|
+
* defines `SESSION_TYPE_UNKNOWN = 'unknown'` for its absent-branch.
|
|
82
|
+
*/
|
|
83
|
+
export const VALID_SESSION_TYPES = Object.freeze(['feature', 'deep', 'housekeeping', 'unknown']);
|
|
61
84
|
|
|
62
85
|
/**
|
|
63
86
|
* Required fields for a schema_version=1 record. Validated by validateSession
|
|
@@ -131,4 +154,15 @@ export const OPTIONAL_FIELDS = Object.freeze([
|
|
|
131
154
|
// into a note a human reads" ⊃ "schema-valid". A record missing it is a clean
|
|
132
155
|
// vault-mirror skip, NOT a malformed record.
|
|
133
156
|
'effectiveness',
|
|
157
|
+
// PRD docs/prd/2026-09-06-ultradeep-session-profile.md — `session_profile`
|
|
158
|
+
// names a WAVE-SHAPE variant on top of an unchanged `session_type`. It is
|
|
159
|
+
// additive and optional on purpose: `ultradeep` is deliberately NOT a member
|
|
160
|
+
// of VALID_SESSION_TYPES above, because that set is mirrored in
|
|
161
|
+
// scripts/lib/telemetry/schema.mjs (an unlisted type -> 'other') and in
|
|
162
|
+
// scripts/lib/wave-sizing.mjs (an unlisted type -> TypeError), where a new
|
|
163
|
+
// MODE would be MISLABELLED rather than rejected. That reasoning is unchanged
|
|
164
|
+
// by the `unknown` member added above: `unknown` is the absence of a
|
|
165
|
+
// measurement, not a mode, and nothing dispatches on it. Every historical
|
|
166
|
+
// record lacking the field validates unchanged.
|
|
167
|
+
'session_profile',
|
|
134
168
|
]);
|
|
@@ -28,11 +28,33 @@ const EXPECTED_COST_TIERS = Object.freeze(['quick', 'standard', 'deep']);
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Valid values for the optional `status` field (Epic #724 C1).
|
|
31
|
-
* `completed`
|
|
32
|
-
* `abandoned`
|
|
33
|
-
*
|
|
31
|
+
* `completed` — record written by a normal /close flow.
|
|
32
|
+
* `abandoned` — stub backfilled by the SessionEnd hook because the session
|
|
33
|
+
* terminated without running /close.
|
|
34
|
+
* `unresolved` — the HONEST label for that same stub: "never reached /close" is
|
|
35
|
+
* observed, but "abandoned" is an interpretation of it (a session
|
|
36
|
+
* may have finished its work and merely skipped /close, or been
|
|
37
|
+
* killed). GitLab #1234, added 2026-09-06.
|
|
38
|
+
*
|
|
39
|
+
* CEILING (BV-004) — `unresolved` currently has NO writer, and the emitter must
|
|
40
|
+
* not adopt it yet. Six EXECUTABLE phantom-stub filters key on the literal
|
|
41
|
+
* `abandoned` and none is inside this change's file scope, so flipping the
|
|
42
|
+
* emitter today would make every new stub invisible to all of them and re-open
|
|
43
|
+
* the #834 phantom-in-signal class fleet-wide. Census measured 2026-09-06 via
|
|
44
|
+
* `rg -n "status\s*(!==|===|!=|==)\s*[\"']abandoned[\"']" scripts hooks --glob '!*.test.mjs'`:
|
|
45
|
+
* scripts/lib/session-schema/filters.mjs:56 (isRealSession — the shared helper,
|
|
46
|
+
* imported by sessions-staleness-banner, auto-dream, dialectic-deriver,
|
|
47
|
+
* harness-audit/categories/category4, evolve/autopilot-effectiveness)
|
|
48
|
+
* scripts/lib/sessions-canonical.mjs:149,172
|
|
49
|
+
* scripts/lib/eval/session-resolve.mjs:79
|
|
50
|
+
* scripts/mcp-server.sh:195
|
|
51
|
+
* scripts/compute-grounding-injection.sh:74
|
|
52
|
+
* REVISIT TRIGGER: once those six route through one predicate that accepts both
|
|
53
|
+
* `abandoned` and `unresolved`, switch `synthesizeRecord()` in
|
|
54
|
+
* `scripts/lib/session-close-backfill.mjs` to emit `unresolved` for inferred
|
|
55
|
+
* records — a one-line change, which is exactly why this value lands now.
|
|
34
56
|
*/
|
|
35
|
-
const SESSION_STATUS = Object.freeze(['completed', 'abandoned']);
|
|
57
|
+
const SESSION_STATUS = Object.freeze(['completed', 'abandoned', 'unresolved']);
|
|
36
58
|
|
|
37
59
|
/**
|
|
38
60
|
* Canonical ISO-8601 UTC timestamp regex — accepts `YYYY-MM-DDTHH:MM:SSZ`
|
|
@@ -462,6 +484,18 @@ function _validateOptionalFields(entry) {
|
|
|
462
484
|
}
|
|
463
485
|
}
|
|
464
486
|
|
|
487
|
+
// `session_profile` — optional wave-shape profile (PRD
|
|
488
|
+
// docs/prd/2026-09-06-ultradeep-session-profile.md). Non-empty string or
|
|
489
|
+
// null/absent; absent means "no profile" and is NOT coerced to a string.
|
|
490
|
+
// Deliberately NOT validated against a closed set: unlike `session_type`,
|
|
491
|
+
// no consumer branches on the value, so an unrecognised profile is a
|
|
492
|
+
// readable record with an unknown shape rather than a silent mislabel.
|
|
493
|
+
if (entry.session_profile !== undefined && entry.session_profile !== null) {
|
|
494
|
+
if (typeof entry.session_profile !== 'string' || entry.session_profile.length === 0) {
|
|
495
|
+
throw new ValidationError('session_profile must be a non-empty string or null');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
465
499
|
// `_express_path_detail` — forensic sidecar written by `normalizeSession`
|
|
466
500
|
// when it collapses a legacy object `express_path` onto its boolean. Holds
|
|
467
501
|
// the pre-collapse object verbatim so the conversion stays reversible.
|
|
@@ -176,8 +176,16 @@ export const PROBES = [
|
|
|
176
176
|
args: ({ repoRoot }) => ({ repoRoot }),
|
|
177
177
|
// Bespoke shape: `{status, ok, details, …}` with no `message` field. The
|
|
178
178
|
// banner text is prescribed by SKILL.md § Phase 4.
|
|
179
|
+
//
|
|
180
|
+
// The degraded branch is NOT decoration (#1031): this entry overrides BOTH
|
|
181
|
+
// `render` and `severityOf`, so the module-level defaults that already
|
|
182
|
+
// handle a `{severity:'warn', message, degraded}` result never run for this
|
|
183
|
+
// probe. Without these two lines a degraded ci-status result scored `'ok'`
|
|
184
|
+
// and rendered nothing — "could not read" displayed exactly like "green",
|
|
185
|
+
// which is the confusion the probe's own migration removed one layer down.
|
|
179
186
|
render: (r) => {
|
|
180
187
|
if (!r || typeof r !== 'object') return null;
|
|
188
|
+
if (r.degraded) return typeof r.message === 'string' && r.message ? r.message : null;
|
|
181
189
|
if (r.status === 'red') {
|
|
182
190
|
const pid = r.details?.currentPipelineId ?? '?';
|
|
183
191
|
const green = r.lastGreen
|
|
@@ -193,7 +201,16 @@ export const PROBES = [
|
|
|
193
201
|
return null;
|
|
194
202
|
},
|
|
195
203
|
// `status: 'red'` is an alert even though the probe publishes no severity.
|
|
196
|
-
|
|
204
|
+
// A degraded result is a finding, never clean — same rule as the generic
|
|
205
|
+
// path in `severityOf()` below.
|
|
206
|
+
severityOf: (r) =>
|
|
207
|
+
r?.degraded
|
|
208
|
+
? 'warn'
|
|
209
|
+
: r?.status === 'red'
|
|
210
|
+
? 'alert'
|
|
211
|
+
: r?.status === 'green' && r?.allowFailureJobs
|
|
212
|
+
? 'warn'
|
|
213
|
+
: 'ok',
|
|
197
214
|
},
|
|
198
215
|
{
|
|
199
216
|
id: 'qg-command-drift',
|
|
@@ -134,7 +134,7 @@ export const LOCK_RELEASED_EVENT = 'orchestrator.session.lock.released';
|
|
|
134
134
|
* lock owned by someone else, an unparseable lock, a registry entry pinned to
|
|
135
135
|
* a different root, a registry unlink that failed, a contradictory owner
|
|
136
136
|
* proof, or a filesystem error. Callers WARN on it and continue; see the
|
|
137
|
-
* `
|
|
137
|
+
* `enterWorktree: leaveSourceRoot: <reason>` contract in
|
|
138
138
|
* `skills/_shared/parallel-aware-auq.md`.
|
|
139
139
|
*
|
|
140
140
|
* `reason` is present whenever something other than a full clean teardown
|