session-orchestrator 3.24.0 → 4.0.1
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 +3 -2
- package/.codex-plugin/skills/architecture/SKILL.md +20 -0
- package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
- package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
- package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
- package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
- package/.codex-plugin/skills/close/SKILL.md +21 -0
- package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
- package/.codex-plugin/skills/debug/SKILL.md +21 -0
- package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/discovery/SKILL.md +21 -0
- package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
- package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
- package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
- package/.codex-plugin/skills/eli5/SKILL.md +21 -0
- package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/eval/SKILL.md +21 -0
- package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/evolve/SKILL.md +21 -0
- package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
- package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
- package/.codex-plugin/skills/go/SKILL.md +22 -0
- package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/grill/SKILL.md +21 -0
- package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
- package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
- package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
- package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
- package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/plan/SKILL.md +22 -0
- package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
- package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
- package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
- package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
- package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/release/SKILL.md +22 -0
- package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
- package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session/SKILL.md +21 -0
- package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session-end/SKILL.md +22 -0
- package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
- package/.codex-plugin/skills/session-start/SKILL.md +22 -0
- package/.codex-plugin/skills/spinout/SKILL.md +21 -0
- package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
- package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
- package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test/SKILL.md +21 -0
- package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
- package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
- package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
- 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 +1 -1
- package/.cursor-plugin/plugin.json +30 -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 +1314 -2
- package/NOTICE +11 -6
- package/README.md +135 -94
- 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 +108 -62
- package/docs/codex-setup.md +107 -29
- package/docs/components.md +38 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +69 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +365 -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 +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1488 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- 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 +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +60 -14
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-codex-skills.mjs +246 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +292 -0
- package/scripts/lib/agent-status.mjs +13 -2
- 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/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +88 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -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 +2 -1
- 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 +7 -2
- package/scripts/lib/config/host-paths.mjs +20 -4
- 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 +2 -1
- 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/convergence-monitor.mjs +82 -16
- 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.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +30 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- 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-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +41 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +215 -15
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +414 -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 +51 -11
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- 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 +64 -3
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +30 -1
- 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 +202 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-codex-skills.mjs +191 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
- package/scripts/lib/validate/check-skill-links.mjs +193 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
- package/scripts/lib/validate/check-unwired-features.mjs +163 -15
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/confidential-names.mjs +95 -30
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/repo-files.mjs +48 -14
- 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/narrative-mirror.mjs +127 -18
- 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 +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +430 -31
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +344 -8
- 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 +164 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- 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/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- 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 +22 -904
- 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 +160 -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/SKILL.md +10 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- 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 -1309
- 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 -270
- 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
|
@@ -168,6 +168,18 @@ export const PROBES = [
|
|
|
168
168
|
// vault-staleness-banner.mjs's header for the "one vocabulary, not two"
|
|
169
169
|
// rationale.
|
|
170
170
|
},
|
|
171
|
+
{
|
|
172
|
+
id: 'telemetry-flush-health',
|
|
173
|
+
spec: local('./telemetry-flush-health-banner.mjs'),
|
|
174
|
+
fn: 'checkTelemetryFlushHealth',
|
|
175
|
+
network: false,
|
|
176
|
+
args: ({ repoRoot }) => ({ repoRoot }),
|
|
177
|
+
// No custom render/severityOf: the probe returns `{severity:'warn',
|
|
178
|
+
// message}` — exactly the shape the module-level defaults below read. It
|
|
179
|
+
// reports the LAST `orchestrator.telemetry.flush` record when that record
|
|
180
|
+
// is a `sandbox:*` refusal (#1255); the refusal reached the ledger and
|
|
181
|
+
// nothing else before this entry existed.
|
|
182
|
+
},
|
|
171
183
|
{
|
|
172
184
|
id: 'ci-status',
|
|
173
185
|
spec: local('./ci-status-banner.mjs'),
|
|
@@ -176,8 +188,16 @@ export const PROBES = [
|
|
|
176
188
|
args: ({ repoRoot }) => ({ repoRoot }),
|
|
177
189
|
// Bespoke shape: `{status, ok, details, …}` with no `message` field. The
|
|
178
190
|
// banner text is prescribed by SKILL.md § Phase 4.
|
|
191
|
+
//
|
|
192
|
+
// The degraded branch is NOT decoration (#1031): this entry overrides BOTH
|
|
193
|
+
// `render` and `severityOf`, so the module-level defaults that already
|
|
194
|
+
// handle a `{severity:'warn', message, degraded}` result never run for this
|
|
195
|
+
// probe. Without these two lines a degraded ci-status result scored `'ok'`
|
|
196
|
+
// and rendered nothing — "could not read" displayed exactly like "green",
|
|
197
|
+
// which is the confusion the probe's own migration removed one layer down.
|
|
179
198
|
render: (r) => {
|
|
180
199
|
if (!r || typeof r !== 'object') return null;
|
|
200
|
+
if (r.degraded) return typeof r.message === 'string' && r.message ? r.message : null;
|
|
181
201
|
if (r.status === 'red') {
|
|
182
202
|
const pid = r.details?.currentPipelineId ?? '?';
|
|
183
203
|
const green = r.lastGreen
|
|
@@ -193,7 +213,16 @@ export const PROBES = [
|
|
|
193
213
|
return null;
|
|
194
214
|
},
|
|
195
215
|
// `status: 'red'` is an alert even though the probe publishes no severity.
|
|
196
|
-
|
|
216
|
+
// A degraded result is a finding, never clean — same rule as the generic
|
|
217
|
+
// path in `severityOf()` below.
|
|
218
|
+
severityOf: (r) =>
|
|
219
|
+
r?.degraded
|
|
220
|
+
? 'warn'
|
|
221
|
+
: r?.status === 'red'
|
|
222
|
+
? 'alert'
|
|
223
|
+
: r?.status === 'green' && r?.allowFailureJobs
|
|
224
|
+
? 'warn'
|
|
225
|
+
: 'ok',
|
|
197
226
|
},
|
|
198
227
|
{
|
|
199
228
|
id: 'qg-command-drift',
|
|
@@ -132,6 +132,7 @@ import path from 'node:path';
|
|
|
132
132
|
|
|
133
133
|
import { readLock, DEFAULT_TTL_HOURS } from './session-lock.mjs';
|
|
134
134
|
import { isRealSession } from './session-schema/filters.mjs';
|
|
135
|
+
import { readCanonicalSessions } from './sessions-canonical.mjs';
|
|
135
136
|
|
|
136
137
|
/** Repo-relative path to the session ledger (one record per closed session). */
|
|
137
138
|
const SESSIONS_PATH = '.orchestrator/metrics/sessions.jsonl';
|
|
@@ -240,20 +241,21 @@ function keepNewer(current, candidate) {
|
|
|
240
241
|
* result so the caller can say the anchor is a stub start, not a measured close;
|
|
241
242
|
* the key is omitted (`undefined`) on the genuine path.
|
|
242
243
|
*
|
|
243
|
-
*
|
|
244
|
+
* `records` is the CANONICAL (#1209b) record set — `readCanonicalSessions()`
|
|
245
|
+
* has already collapsed a duplicated `session_id` to its newest occurrence, so
|
|
246
|
+
* a since-corrected raw LINE for the same identity (e.g. one later marked
|
|
247
|
+
* `status: 'abandoned'`, or a stale `completed_at` a later record for the same
|
|
248
|
+
* id superseded) can no longer independently skew this max-reduce the way a
|
|
249
|
+
* raw-line scan over every append could.
|
|
250
|
+
*
|
|
251
|
+
* @param {object[]} records
|
|
244
252
|
* @returns {{iso: string, ms: number, stubFallback?: true}|null}
|
|
245
253
|
*/
|
|
246
|
-
function lastLedgerEntry(
|
|
254
|
+
function lastLedgerEntry(records) {
|
|
247
255
|
let newestGenuine = null; // newest genuine completed_at (or started_at floor)
|
|
248
256
|
let newestStub = null; // newest stub started_at
|
|
249
257
|
|
|
250
|
-
for (const
|
|
251
|
-
let record;
|
|
252
|
-
try {
|
|
253
|
-
record = JSON.parse(line);
|
|
254
|
-
} catch {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
258
|
+
for (const record of records) {
|
|
257
259
|
if (!record || typeof record !== 'object') continue;
|
|
258
260
|
|
|
259
261
|
if (isBackfillStub(record)) {
|
|
@@ -370,10 +372,15 @@ export function checkSessionsStaleness({ repoRoot, now = Date.now() } = {}) {
|
|
|
370
372
|
|
|
371
373
|
const nowMs = typeof now === 'number' && Number.isFinite(now) ? now : Date.now();
|
|
372
374
|
|
|
373
|
-
const
|
|
375
|
+
const sessionsPath = path.join(repoRoot, SESSIONS_PATH);
|
|
376
|
+
const sessionLines = readJsonlLines(sessionsPath);
|
|
374
377
|
if (sessionLines === null || sessionLines.length === 0) return null;
|
|
375
378
|
|
|
376
|
-
|
|
379
|
+
// CANONICAL (#1209b) record set — readJsonlLines() above only decides
|
|
380
|
+
// missing-vs-empty (readCanonicalSessions() cannot tell those apart, see
|
|
381
|
+
// its own doc); the anchor scan itself now runs over the collapsed set.
|
|
382
|
+
const sessionRecords = readCanonicalSessions({ filePath: sessionsPath });
|
|
383
|
+
const ledger = lastLedgerEntry(sessionRecords);
|
|
377
384
|
if (ledger === null) return null;
|
|
378
385
|
|
|
379
386
|
const eventLines = readJsonlLines(path.join(repoRoot, EVENTS_PATH));
|
|
@@ -35,6 +35,7 @@ import path from 'node:path';
|
|
|
35
35
|
import { fileURLToPath } from 'node:url';
|
|
36
36
|
|
|
37
37
|
import { isRealSession } from '../session-schema/filters.mjs';
|
|
38
|
+
import { readCanonicalSessions } from '../sessions-canonical.mjs';
|
|
38
39
|
|
|
39
40
|
const DEFAULT_INVOCATIONS_PATH = path.resolve(
|
|
40
41
|
fileURLToPath(import.meta.url),
|
|
@@ -81,6 +82,15 @@ async function readJsonl(filePath) {
|
|
|
81
82
|
* it to route the join to the `abandoned` outcome bucket instead of counting
|
|
82
83
|
* a zero-signal join as `sessionsJoined`.
|
|
83
84
|
*
|
|
85
|
+
* Callers pass `sessionRecords` from `readCanonicalSessions()` (#1209b) — one
|
|
86
|
+
* record per physical session (newest-wins per `session_id`, systemic
|
|
87
|
+
* double-stub twin dropped, superseded stubs removed). Without that upstream
|
|
88
|
+
* collapse, the two-writer double-stub class (a SEPARATE synthetic
|
|
89
|
+
* `session_id` for the SAME physical session) would enter this map as a
|
|
90
|
+
* second, independent `abandoned` entry — this function's own `map.set()`
|
|
91
|
+
* overwrite only dedupes an EXACT `session_id` repeat, never two different
|
|
92
|
+
* ids for one session.
|
|
93
|
+
*
|
|
84
94
|
* @param {object[]} sessionRecords
|
|
85
95
|
* @returns {Map<string, { agentSummary: { complete: number, partial: number, failed: number, spiral: number }, real: boolean }>}
|
|
86
96
|
*/
|
|
@@ -121,10 +131,13 @@ export async function joinSkillOutcomes({
|
|
|
121
131
|
invocationsPath = DEFAULT_INVOCATIONS_PATH,
|
|
122
132
|
sessionsPath = DEFAULT_SESSIONS_PATH,
|
|
123
133
|
} = {}) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
// Invocations stay on the raw async reader (no identity-collapse concept
|
|
135
|
+
// applies to a selection-event stream). Sessions move to the CANONICAL
|
|
136
|
+
// (#1209b) reader — synchronous by contract (see sessions-canonical.mjs) —
|
|
137
|
+
// so a session_id counted twice under the two-writer double-stub bug no
|
|
138
|
+
// longer inflates `sessionsAbandoned` (see buildSessionMap doc above).
|
|
139
|
+
const invocations = await readJsonl(invocationsPath);
|
|
140
|
+
const sessionRecords = readCanonicalSessions({ filePath: sessionsPath });
|
|
128
141
|
|
|
129
142
|
const sessionMap = buildSessionMap(sessionRecords);
|
|
130
143
|
|
package/scripts/lib/state-md.mjs
CHANGED
|
@@ -9,8 +9,15 @@
|
|
|
9
9
|
* @see scripts/lib/state-md/body-sections.mjs readCurrentTask, appendDeviation, markExpressPathComplete, appendWhatNotToRetry, readWhatNotToRetry, readOpenQuestions, appendOpenQuestion, markOpenQuestionAnswered
|
|
10
10
|
* @see scripts/lib/state-md/mission-status.mjs parseMissionStatus, parseMissionStatusStrict, MISSION_STATUS_VALUES, writeMissionStatus, setMissionStatus, setMissionStatusDetailed, readMissionStatus, recoverFrontmatterMissionStatusDetailed, writeMissionStatusOnDisk, setMissionStatusOnDisk
|
|
11
11
|
* @see scripts/lib/state-md/recommendations.mjs parseRecommendations
|
|
12
|
+
*
|
|
13
|
+
* Plus ONE small non-re-export surface: the `session-profile` frontmatter
|
|
14
|
+
* accessors at the bottom of this file (see their docblock for why they are
|
|
15
|
+
* composed here rather than added as a fourth mutator module).
|
|
12
16
|
*/
|
|
13
17
|
|
|
18
|
+
import { parseStateMd as _parseStateMd } from './state-md/yaml-parser.mjs';
|
|
19
|
+
import { updateFrontmatterFields as _updateFrontmatterFields } from './state-md/frontmatter-mutators.mjs';
|
|
20
|
+
|
|
14
21
|
export { parseStateMd, serializeStateMd } from './state-md/yaml-parser.mjs';
|
|
15
22
|
|
|
16
23
|
export {
|
|
@@ -61,3 +68,74 @@ export {
|
|
|
61
68
|
} from './state-md/mission-status.mjs';
|
|
62
69
|
|
|
63
70
|
export { parseRecommendations } from './state-md/recommendations.mjs';
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Session profile (PRD docs/prd/2026-09-06-ultradeep-session-profile.md)
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
//
|
|
76
|
+
// `session-profile` is an OPTIONAL STATE.md frontmatter scalar that names a
|
|
77
|
+
// wave-shape variant on top of an unchanged `session-type`. Today exactly one
|
|
78
|
+
// value is defined — `ultradeep` (7 waves, coordinator-direct Synthesis-Gate at
|
|
79
|
+
// wave 2) — resolved from the `/session ultradeep` argument alias in
|
|
80
|
+
// `commands/session.md`.
|
|
81
|
+
//
|
|
82
|
+
// The vocabulary is deliberately NOT a closed set here. A profile changes only
|
|
83
|
+
// how the coordinator shapes waves; unlike `session_type` (a closed set in
|
|
84
|
+
// scripts/lib/session-schema/constants.mjs, telemetry and the close-backfill),
|
|
85
|
+
// no consumer branches on the value, so an unknown one degrades to "a profile
|
|
86
|
+
// this reader does not recognise" rather than to a silent mislabel. Revisit
|
|
87
|
+
// trigger: the first consumer that BRANCHES on a specific profile value — at
|
|
88
|
+
// that point the set becomes load-bearing and belongs in a shared constant.
|
|
89
|
+
//
|
|
90
|
+
// Composed from the two existing helpers above rather than reaching into the
|
|
91
|
+
// frontmatter with a second parser: `parseStateMd` for the read,
|
|
92
|
+
// `updateFrontmatterFields` for the write (whose null/undefined semantics
|
|
93
|
+
// already mean DELETE, which is exactly "no profile").
|
|
94
|
+
|
|
95
|
+
/** Frontmatter key holding the optional session profile. */
|
|
96
|
+
export const SESSION_PROFILE_FIELD = 'session-profile';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Read the session profile from STATE.md contents.
|
|
100
|
+
*
|
|
101
|
+
* ABSENCE IS NEVER COERCED. Returns `null` — not `''`, not `'none'` — when the
|
|
102
|
+
* document has no frontmatter, no `session-profile` key, or a value that is not
|
|
103
|
+
* a non-empty string. Callers test `=== null` for "no profile"; they must not
|
|
104
|
+
* test truthiness of a string they assumed was always present.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} contents Full STATE.md text.
|
|
107
|
+
* @returns {string|null} The profile name, or null when no profile is set.
|
|
108
|
+
*/
|
|
109
|
+
export function readSessionProfile(contents) {
|
|
110
|
+
if (typeof contents !== 'string') return null;
|
|
111
|
+
const parsed = _parseStateMd(contents);
|
|
112
|
+
if (parsed === null) return null;
|
|
113
|
+
const value = parsed.frontmatter[SESSION_PROFILE_FIELD];
|
|
114
|
+
if (typeof value !== 'string') return null;
|
|
115
|
+
const trimmed = value.trim();
|
|
116
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Set or clear the session profile in STATE.md contents.
|
|
121
|
+
*
|
|
122
|
+
* Passing `null` DELETES the key, restoring the absent (= no profile) state —
|
|
123
|
+
* it never writes a placeholder value. Every other frontmatter key, including
|
|
124
|
+
* unknown extensions, is preserved verbatim by `updateFrontmatterFields`.
|
|
125
|
+
* No-ops (returns the input unchanged) when `contents` has no frontmatter.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} contents Full STATE.md text.
|
|
128
|
+
* @param {string|null} profile Profile name, or null to clear.
|
|
129
|
+
* @returns {string} The new STATE.md text.
|
|
130
|
+
* @throws {TypeError} when `profile` is neither a non-empty string nor null.
|
|
131
|
+
*/
|
|
132
|
+
export function setSessionProfile(contents, profile) {
|
|
133
|
+
if (profile !== null && (typeof profile !== 'string' || profile.trim().length === 0)) {
|
|
134
|
+
throw new TypeError(
|
|
135
|
+
`setSessionProfile: profile must be a non-empty string or null, got: ${JSON.stringify(profile)}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return _updateFrontmatterFields(contents, {
|
|
139
|
+
[SESSION_PROFILE_FIELD]: profile === null ? null : profile.trim(),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -471,6 +471,12 @@ function collectFiles(dir) {
|
|
|
471
471
|
function isBoilerplateSite(relPath, kind, name) {
|
|
472
472
|
if (kind === 'agent') {
|
|
473
473
|
if (relPath === `agents/${name}.md`) return true;
|
|
474
|
+
// Kept for repoRoots where an authoring-spec file still lives at this path
|
|
475
|
+
// (pre-4.0.0 checkouts, template/consumer repos) — see
|
|
476
|
+
// tests/lib/sunset-walker.test.mjs "boilerplate exclusion (agents)". The
|
|
477
|
+
// session-orchestrator repo itself moved the spec to docs/agent-authoring.md,
|
|
478
|
+
// which is outside SCAN_DIRS and can never appear as a relPath here, so
|
|
479
|
+
// there is no live path to repoint this exclusion to.
|
|
474
480
|
if (relPath === 'agents/AGENTS.md') return true;
|
|
475
481
|
if (relPath === `agents/schemas/${name}.schema.json`) return true;
|
|
476
482
|
// Routing-table / validator boilerplate.
|
|
@@ -35,9 +35,10 @@
|
|
|
35
35
|
* none and holds no mutable runtime state.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
import {
|
|
38
|
+
import { getPlatform, getPluginRoot } from '../platform.mjs';
|
|
39
39
|
import { enumerateSurface } from '../sunset/walker.mjs';
|
|
40
40
|
import { readPluginVersionFromPackageJson } from '../bootstrap-lock-freshness.mjs';
|
|
41
|
+
import { VALID_SESSION_PROFILES } from '../session-schema/constants.mjs';
|
|
41
42
|
|
|
42
43
|
// ---------------------------------------------------------------------------
|
|
43
44
|
// Constants
|
|
@@ -70,6 +71,52 @@ export const USAGE_PING_FIELDS = Object.freeze([
|
|
|
70
71
|
'commands',
|
|
71
72
|
]);
|
|
72
73
|
|
|
74
|
+
/**
|
|
75
|
+
* ADDITIVE optional fields (schema v1, GitLab #1234). Deliberately a SECOND
|
|
76
|
+
* list rather than an extension of USAGE_PING_FIELDS above, for two reasons:
|
|
77
|
+
*
|
|
78
|
+
* 1. USAGE_PING_FIELDS is the REQUIRED v1 contract — `tests/telemetry/parity.mjs`
|
|
79
|
+
* asserts, field by field, that the server independently REQUIRES every
|
|
80
|
+
* member. An optional field is by definition not required, so putting it in
|
|
81
|
+
* that list would force the parity guard to be weakened for all 15 fields to
|
|
82
|
+
* accommodate one.
|
|
83
|
+
* 2. The frozen-list tripwire (`projectUsagePing` drops anything unlisted)
|
|
84
|
+
* stays intact: the projection whitelist is the UNION of both lists, so a
|
|
85
|
+
* leaky field still has to be added to a reviewed list before it can reach
|
|
86
|
+
* the wire.
|
|
87
|
+
*
|
|
88
|
+
* Server tolerance: `server/ingest/validate.mjs` accepts unknown top-level
|
|
89
|
+
* fields and preserves them verbatim in `raw_json` (see its module docblock), so
|
|
90
|
+
* every member here round-trips through a server that predates it.
|
|
91
|
+
*/
|
|
92
|
+
export const USAGE_PING_OPTIONAL_FIELDS = Object.freeze([
|
|
93
|
+
'fleet_self_declared',
|
|
94
|
+
'session_record',
|
|
95
|
+
'session_profile',
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The full projection whitelist: required v1 fields + additive optional fields.
|
|
100
|
+
* `projectUsagePing` is driven from THIS, so neither list alone can leak a field.
|
|
101
|
+
*/
|
|
102
|
+
const USAGE_PING_PROJECTED_FIELDS = Object.freeze([...USAGE_PING_FIELDS, ...USAGE_PING_OPTIONAL_FIELDS]);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* `session_record` provenance tokens — WHICH source the session facts
|
|
106
|
+
* (`session_type`, `duration_bucket`) in this ping came from.
|
|
107
|
+
*
|
|
108
|
+
* - `ledger` — `.orchestrator/metrics/sessions.jsonl` had a matching record.
|
|
109
|
+
* - `derived` — no ledger record; the facts were reconstructed from
|
|
110
|
+
* `events.jsonl` (`orchestrator.session.started` + last event).
|
|
111
|
+
* - `absent` — neither source produced a session type. `session_type` is then
|
|
112
|
+
* `'unknown'` and `duration_bucket` is NOT a measurement.
|
|
113
|
+
*
|
|
114
|
+
* Measured 2026-09-06 (d8 audit): this repo has NO sessions.jsonl, so 100 % of
|
|
115
|
+
* its pings took the `absent` path and were indistinguishable on the wire from
|
|
116
|
+
* a genuinely-measured `other` / `<15m` session — 32 such pings on the server.
|
|
117
|
+
*/
|
|
118
|
+
export const SESSION_RECORD_SOURCES = Object.freeze(['ledger', 'derived', 'absent']);
|
|
119
|
+
|
|
73
120
|
/** Exact duration-bucket tokens (ASCII, stable wire values). */
|
|
74
121
|
export const DURATION_BUCKETS = Object.freeze(['<15m', '15-60m', '1-3h', '>3h']);
|
|
75
122
|
|
|
@@ -78,11 +125,68 @@ const ROSTER_OTHER = 'other';
|
|
|
78
125
|
const MAX_NAME_LENGTH = 64;
|
|
79
126
|
const MAX_NAMES = 100;
|
|
80
127
|
|
|
128
|
+
/**
|
|
129
|
+
* The two list bounds that are genuinely SHARED with the ingest server
|
|
130
|
+
* (`server/ingest/validate.mjs` `INGEST_LIST_BOUNDS` — MAX_LIST_ITEMS /
|
|
131
|
+
* MAX_LIST_ITEM_LEN). Exported so `tests/telemetry/parity.test.mjs` can compare
|
|
132
|
+
* the two trees instead of trusting two hand-typed numbers: a client that caps
|
|
133
|
+
* higher than the server emits pings the server 400s.
|
|
134
|
+
*
|
|
135
|
+
* The server's other five bounds (MAX_ANON_ID, MAX_SENT_AT, MAX_PLUGIN_VERSION,
|
|
136
|
+
* MAX_SESSION_TYPE, MAX_SESSION_PROFILE) are SERVER-ONLY BY DESIGN — they bound
|
|
137
|
+
* inputs from any client, including foreign or tampered ones, and have no client
|
|
138
|
+
* counterpart to keep in lockstep. Do not mirror them here.
|
|
139
|
+
*/
|
|
140
|
+
export const SHARED_LIST_BOUNDS = Object.freeze({
|
|
141
|
+
maxItems: MAX_NAMES,
|
|
142
|
+
maxItemLength: MAX_NAME_LENGTH,
|
|
143
|
+
});
|
|
144
|
+
|
|
81
145
|
/** Enum fallbacks. */
|
|
82
146
|
const VALID_PLATFORMS = Object.freeze(['claude', 'codex', 'cursor', 'pi']);
|
|
147
|
+
// DELIBERATE ASYMMETRY, not a drift: this is the TELEMETRY type set and omits
|
|
148
|
+
// `'unknown'`, which session-schema/constants.mjs VALID_SESSION_TYPES carries.
|
|
149
|
+
// There `unknown` is a storable ledger value; here it is a NORMALIZER FALLBACK
|
|
150
|
+
// (SESSION_TYPE_UNKNOWN below) that must never be reachable from the input set,
|
|
151
|
+
// or "we could not tell" would be indistinguishable from a measured type. Not
|
|
152
|
+
// unified with constants.mjs for exactly that reason.
|
|
83
153
|
const VALID_SESSION_TYPES = Object.freeze(['housekeeping', 'feature', 'deep']);
|
|
84
154
|
const PLATFORM_OTHER = 'other';
|
|
85
155
|
const SESSION_TYPE_OTHER = 'other';
|
|
156
|
+
/**
|
|
157
|
+
* The type was never measured — distinct from `'other'`, which means "measured,
|
|
158
|
+
* but not one of the three known modes". Conflating the two is the defect this
|
|
159
|
+
* token fixes: before it existed, a ping built with NO session record silently
|
|
160
|
+
* reported `'other'`, so "we could not tell" and "we looked and it was unusual"
|
|
161
|
+
* were the same wire value.
|
|
162
|
+
*
|
|
163
|
+
* NOT an enum widening server-side: `session_type` is validated as a bounded
|
|
164
|
+
* STRING (`server/ingest/validate.mjs` `requireString` + `MAX_SESSION_TYPE`),
|
|
165
|
+
* never against a closed set, so `'unknown'` is accepted by today's server.
|
|
166
|
+
*/
|
|
167
|
+
const SESSION_TYPE_UNKNOWN = 'unknown';
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* CLOSED whitelist of PUBLIC session-profile names that may reach the wire.
|
|
171
|
+
*
|
|
172
|
+
* RE-EXPORT, not a definition (GitLab #1252): the SSOT is
|
|
173
|
+
* `scripts/lib/session-schema/constants.mjs` `VALID_SESSION_PROFILES`, whose
|
|
174
|
+
* docblock carries the reproduced leak and the whitelist-over-regex rationale.
|
|
175
|
+
*
|
|
176
|
+
* Kept as a pure COMPATIBILITY SHIM, and measured as one: since 2026-09-07 no
|
|
177
|
+
* module in this repo imports the name from here (`rg -n VALID_SESSION_PROFILES
|
|
178
|
+
* scripts server hooks skills tests` — the two telemetry tests that used to now
|
|
179
|
+
* address constants.mjs). It stays because removing an export is a MAJOR change
|
|
180
|
+
* and this is a patch line; a deep importer outside the repo may still hold it.
|
|
181
|
+
* `tests/telemetry/schema.test.mjs` pins it with an IDENTITY assertion
|
|
182
|
+
* (`toBe`, not `toEqual`) so the shim can never quietly become a second
|
|
183
|
+
* definition of the whitelist.
|
|
184
|
+
*
|
|
185
|
+
* Adding a profile is a reviewed edit in constants.mjs AND in the server's
|
|
186
|
+
* mirror (`server/ingest/validate.mjs` `SESSION_PROFILES`) — the same two-tree
|
|
187
|
+
* contract `ACCEPTED_VERSIONS` already uses server-side.
|
|
188
|
+
*/
|
|
189
|
+
export { VALID_SESSION_PROFILES };
|
|
86
190
|
|
|
87
191
|
/**
|
|
88
192
|
* Closed sets for os/arch client-side normalization. A value outside the set —
|
|
@@ -121,7 +225,8 @@ function isNonEmptyString(v) {
|
|
|
121
225
|
// ---------------------------------------------------------------------------
|
|
122
226
|
|
|
123
227
|
/**
|
|
124
|
-
* Project an arbitrary object onto the usage-ping whitelist (USAGE_PING_FIELDS
|
|
228
|
+
* Project an arbitrary object onto the usage-ping whitelist (USAGE_PING_FIELDS
|
|
229
|
+
* PLUS USAGE_PING_OPTIONAL_FIELDS).
|
|
125
230
|
* Fully data-driven: any key not on the whitelist — paths, repo names, prompts,
|
|
126
231
|
* args, hostnames, rogue extras — is dropped. Array fields (skills, commands) are
|
|
127
232
|
* copied as NEW arrays so no caller reference leaks into the projection.
|
|
@@ -132,7 +237,7 @@ function isNonEmptyString(v) {
|
|
|
132
237
|
export function projectUsagePing(input) {
|
|
133
238
|
if (!isPlainObject(input)) return {};
|
|
134
239
|
const out = {};
|
|
135
|
-
for (const key of
|
|
240
|
+
for (const key of USAGE_PING_PROJECTED_FIELDS) {
|
|
136
241
|
if (key in input) {
|
|
137
242
|
const v = input[key];
|
|
138
243
|
out[key] = Array.isArray(v) ? [...v] : v;
|
|
@@ -163,7 +268,7 @@ export function projectUsagePing(input) {
|
|
|
163
268
|
export function loadRoster({ pluginRoot } = {}) {
|
|
164
269
|
const root = (typeof pluginRoot === 'string' && pluginRoot.trim() !== '')
|
|
165
270
|
? pluginRoot
|
|
166
|
-
:
|
|
271
|
+
: getPluginRoot();
|
|
167
272
|
|
|
168
273
|
if (!isNonEmptyString(root)) {
|
|
169
274
|
process.stderr.write(
|
|
@@ -309,8 +414,9 @@ export function deriveDurationBucket(startedAtISO, completedAtISO) {
|
|
|
309
414
|
* whose null return (missing/unparseable package.json or non-string version) maps to 'unknown'.
|
|
310
415
|
*/
|
|
311
416
|
function resolvePluginVersion() {
|
|
312
|
-
|
|
313
|
-
|
|
417
|
+
const root = getPluginRoot();
|
|
418
|
+
if (!isNonEmptyString(root)) return 'unknown';
|
|
419
|
+
return readPluginVersionFromPackageJson(root) ?? 'unknown';
|
|
314
420
|
}
|
|
315
421
|
|
|
316
422
|
/** Normalize the detected platform to the closed enum (+ 'other' fallback). */
|
|
@@ -320,7 +426,36 @@ function normalizePlatform(platform) {
|
|
|
320
426
|
|
|
321
427
|
/** Normalize the session type to the closed enum (+ 'other' fallback). */
|
|
322
428
|
function normalizeSessionType(sessionType) {
|
|
323
|
-
|
|
429
|
+
if (VALID_SESSION_TYPES.includes(sessionType)) return sessionType;
|
|
430
|
+
// ABSENT ≠ UNRECOGNISED. A missing/blank value means nothing was measured
|
|
431
|
+
// (`unknown`); a present-but-unlisted value means something WAS measured and
|
|
432
|
+
// is not one of the three modes (`other`). The old single-branch version
|
|
433
|
+
// returned `other` for both, which is how 394 pings from a host with no
|
|
434
|
+
// sessions.jsonl arrived looking like measured `other` sessions (d8, 2026-09-06).
|
|
435
|
+
return isNonEmptyString(sessionType) ? SESSION_TYPE_OTHER : SESSION_TYPE_UNKNOWN;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Normalize a session PROFILE against the closed VALID_SESSION_PROFILES
|
|
440
|
+
* whitelist. Anything else — absent, blank, or an unlisted repo-authored value —
|
|
441
|
+
* returns `null`, and the caller OMITS the field.
|
|
442
|
+
*
|
|
443
|
+
* DELIBERATELY NOT MIRRORING normalizeSessionType's two-token report. That helper
|
|
444
|
+
* can distinguish ABSENT (`'unknown'`) from UNRECOGNISED (`'other'`) because both
|
|
445
|
+
* of its answers are public constants. Here the unrecognised VALUE is precisely
|
|
446
|
+
* what must not travel, and there is no third token that carries the distinction
|
|
447
|
+
* without carrying the string: an `'other'` profile would say "this host ran a
|
|
448
|
+
* profile we do not ship" — a fact of no product use, bought with a new wire
|
|
449
|
+
* value. So both cases collapse to omission, and the wire cannot tell them apart
|
|
450
|
+
* BY DESIGN. The local `session-profile` key is untouched either way.
|
|
451
|
+
*
|
|
452
|
+
* @param {unknown} profile
|
|
453
|
+
* @returns {string|null} a whitelisted profile name, or null (⇒ field omitted).
|
|
454
|
+
*/
|
|
455
|
+
export function normalizeSessionProfile(profile) {
|
|
456
|
+
if (!isNonEmptyString(profile)) return null;
|
|
457
|
+
const trimmed = profile.trim();
|
|
458
|
+
return VALID_SESSION_PROFILES.includes(trimmed) ? trimmed : null;
|
|
324
459
|
}
|
|
325
460
|
|
|
326
461
|
/**
|
|
@@ -403,6 +538,9 @@ export function buildUsagePing({
|
|
|
403
538
|
env = process.env,
|
|
404
539
|
now = new Date().toISOString(),
|
|
405
540
|
roster,
|
|
541
|
+
consentState,
|
|
542
|
+
sessionRecordSource,
|
|
543
|
+
sessionProfile,
|
|
406
544
|
} = {}) {
|
|
407
545
|
const session = isPlainObject(sessionRecord) ? sessionRecord : {};
|
|
408
546
|
const invocations = Array.isArray(skillInvocations) ? skillInvocations : [];
|
|
@@ -431,19 +569,74 @@ export function buildUsagePing({
|
|
|
431
569
|
(kind === 'command' ? commandNames : skillNames).push(name);
|
|
432
570
|
}
|
|
433
571
|
|
|
572
|
+
// ── fleet: a statement about the OPERATOR, derived from the RESOLVED consent
|
|
573
|
+
// state, not from a raw owner.yaml read ────────────────────────────────────
|
|
574
|
+
// The old expression was `ownerConfig?.telemetry?.enabled === true` — a
|
|
575
|
+
// statement about a FILE. Measured 2026-09-06 (d8): the operator's second Mac
|
|
576
|
+
// has consent granted but no `telemetry:` block in owner.yaml, so 394 of 490
|
|
577
|
+
// server records (80,4 %) counted the operator as an external user and every
|
|
578
|
+
// week's `fleet_vs_external` was wrong.
|
|
579
|
+
//
|
|
580
|
+
// `resolveConsent()` already answers this question correctly: it returns
|
|
581
|
+
// `enabled-fleet` for the owner.yaml opt-in AND `enabled-env` for
|
|
582
|
+
// `SO_TELEMETRY=1`, both of which are operator-side postures. Callers that
|
|
583
|
+
// pass `consentState` get that answer; the ownerConfig fallback below keeps
|
|
584
|
+
// the two-argument callers (and the CLI preview) behaviourally identical.
|
|
585
|
+
//
|
|
586
|
+
// CEILING, named because the client cannot close it: this is still
|
|
587
|
+
// SELF-DECLARED — a sandbox or a host whose owner.yaml is unreachable declares
|
|
588
|
+
// `false` however honest it is. The authoritative classification is
|
|
589
|
+
// server-side (`SO_INGEST_FLEET_ANON_IDS`, server/ingest/config.mjs).
|
|
590
|
+
// `session_profile` (STATE.md frontmatter `session-profile`) is bounded by the
|
|
591
|
+
// CLOSED VALID_SESSION_PROFILES whitelist — it was the one free-text value on
|
|
592
|
+
// the wire until 2026-09-06, and a repo-authored one at that (see the
|
|
593
|
+
// whitelist's own docblock for the reproduced leak). It is still deliberately
|
|
594
|
+
// NOT routed through normalizeSessionType: that helper degrades anything
|
|
595
|
+
// outside ['housekeeping','feature','deep'] to 'other', which is exactly the
|
|
596
|
+
// silent loss the profile exists to prevent. The contract is
|
|
597
|
+
// `session_type: "deep"` PLUS `session_profile: "ultradeep"` — never
|
|
598
|
+
// `session_type: "ultradeep"`, and never a profile flattened to 'other'.
|
|
599
|
+
//
|
|
600
|
+
// ABSENT IS NOT EMPTY: with no profile — and with an UNLISTED one — the KEY IS
|
|
601
|
+
// OMITTED, matching every other optional ping field. `projectUsagePing` copies
|
|
602
|
+
// only keys that are `in` the input, so an omitted key never reaches the wire
|
|
603
|
+
// as `null`, and an unlisted profile never reaches it at all.
|
|
604
|
+
//
|
|
605
|
+
// SOURCE PRECEDENCE is resolved BEFORE normalization (explicit argument beats
|
|
606
|
+
// the session record), so an unlisted explicit profile is dropped rather than
|
|
607
|
+
// silently replaced by the record's value.
|
|
608
|
+
const rawProfile =
|
|
609
|
+
isNonEmptyString(sessionProfile) ? sessionProfile
|
|
610
|
+
: isNonEmptyString(session.session_profile) ? session.session_profile
|
|
611
|
+
: null;
|
|
612
|
+
const profile = normalizeSessionProfile(rawProfile);
|
|
613
|
+
|
|
614
|
+
const fleetSelfDeclared =
|
|
615
|
+
consentState === undefined || consentState === null
|
|
616
|
+
? ownerConfig?.telemetry?.enabled === true
|
|
617
|
+
: consentState === 'enabled-fleet' || consentState === 'enabled-env';
|
|
618
|
+
|
|
434
619
|
return {
|
|
435
620
|
record_kind: 'usage-ping',
|
|
436
621
|
schema_version: USAGE_PING_SCHEMA_VERSION,
|
|
437
622
|
sent_at: now,
|
|
438
623
|
plugin_version: resolvePluginVersion(),
|
|
439
|
-
platform: normalizePlatform(
|
|
624
|
+
platform: normalizePlatform(getPlatform()),
|
|
440
625
|
os: normalizeOs(process.platform),
|
|
441
626
|
arch: normalizeArch(process.arch),
|
|
442
627
|
node_major: parseInt(process.versions.node, 10),
|
|
443
628
|
ci: deriveCi(env),
|
|
444
|
-
fleet:
|
|
629
|
+
fleet: fleetSelfDeclared,
|
|
630
|
+
// DEPRECATED alias of `fleet_self_declared`, kept for one schema generation
|
|
631
|
+
// (removal: 2027-03-06 — see docs/telemetry.md § Schema evolution) so the
|
|
632
|
+
// server's existing `fleet` column stays comparable across the rename.
|
|
633
|
+
fleet_self_declared: fleetSelfDeclared,
|
|
445
634
|
session_type: normalizeSessionType(session.session_type),
|
|
635
|
+
...(profile !== null ? { session_profile: profile } : {}),
|
|
446
636
|
duration_bucket: deriveDurationBucket(session.started_at, session.completed_at),
|
|
637
|
+
session_record: SESSION_RECORD_SOURCES.includes(sessionRecordSource)
|
|
638
|
+
? sessionRecordSource
|
|
639
|
+
: 'absent',
|
|
447
640
|
skills: filterRosterNames(skillNames, rosterSkills),
|
|
448
641
|
commands: filterRosterNames(commandNames, rosterCommands),
|
|
449
642
|
};
|