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
|
@@ -73,6 +73,7 @@ import path from 'node:path';
|
|
|
73
73
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
74
74
|
import { execFileSync } from 'node:child_process';
|
|
75
75
|
import { warn } from './lib/common.mjs';
|
|
76
|
+
import { MANIFEST_SESSION_KEYS } from './lib/session-identity/own-session.mjs';
|
|
76
77
|
import {
|
|
77
78
|
assertFileScopeSubset,
|
|
78
79
|
assertTestSiblingCoverage,
|
|
@@ -289,10 +290,13 @@ function validateOptionalSessionId(obj, key, errors) {
|
|
|
289
290
|
}
|
|
290
291
|
|
|
291
292
|
/**
|
|
292
|
-
* Validate the OPTIONAL session binding (#1123): `
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* call — see `skills/wave-executor/wave-loop.md` § Scope Manifest.
|
|
293
|
+
* Validate the OPTIONAL session binding (#1123): `session_id` (the raw session
|
|
294
|
+
* id of the session that WROTE this manifest) and its human-readable twin
|
|
295
|
+
* `semantic_session_id`. Both come from one `sessionAttribution(repoRoot)`
|
|
296
|
+
* call — see `skills/wave-executor/wave-loop.md` § Scope Manifest. The
|
|
297
|
+
* pre-#1153 spellings `session` / `semantic_session` are still ACCEPTED here
|
|
298
|
+
* (read side only, until the next minor release) — key names come from
|
|
299
|
+
* `MANIFEST_SESSION_KEYS` so the writer and every reader share one list.
|
|
296
300
|
*
|
|
297
301
|
* Deliberately NOT part of {@link validateRequired}, and that is a compatibility
|
|
298
302
|
* constraint rather than a preference: `wave-scope.json` is a shared
|
|
@@ -306,9 +310,25 @@ function validateOptionalSessionId(obj, key, errors) {
|
|
|
306
310
|
* @param {string[]} errors
|
|
307
311
|
* @param {string[]} warnings
|
|
308
312
|
*/
|
|
309
|
-
function
|
|
310
|
-
|
|
311
|
-
|
|
313
|
+
function validateSessionBinding(obj, errors, warnings) {
|
|
314
|
+
let present = false;
|
|
315
|
+
MANIFEST_SESSION_KEYS.current.forEach((key, i) => {
|
|
316
|
+
const legacyKey = MANIFEST_SESSION_KEYS.legacy[i];
|
|
317
|
+
const hasCurrent = validateOptionalSessionId(obj, key, errors);
|
|
318
|
+
const hasLegacy = validateOptionalSessionId(obj, legacyKey, errors);
|
|
319
|
+
// Both spellings of the SAME slot, disagreeing, is the one case the reader
|
|
320
|
+
// cannot resolve honestly: it silently prefers `key` and drops the other
|
|
321
|
+
// id, so a manifest that names two different sessions would classify as
|
|
322
|
+
// `own` for one of them. Name it here rather than let the preference decide.
|
|
323
|
+
if (hasCurrent && hasLegacy && obj[key] !== obj[legacyKey]) {
|
|
324
|
+
errors.push(
|
|
325
|
+
`${key} and legacy ${legacyKey} are both present with DIFFERENT values — ` +
|
|
326
|
+
'a manifest binds to exactly one session; drop the legacy key (accepted on ' +
|
|
327
|
+
'the read side only, until the next minor release, #1153)',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (i === 0) present = hasCurrent || hasLegacy;
|
|
331
|
+
});
|
|
312
332
|
if (!present) {
|
|
313
333
|
warnings.push(
|
|
314
334
|
'no session field — manifest is not session-bound (legacy, #1123), so every session sharing this ' +
|
|
@@ -749,7 +769,7 @@ function validate(
|
|
|
749
769
|
const warnings = [];
|
|
750
770
|
|
|
751
771
|
validateRequired(obj, errors);
|
|
752
|
-
|
|
772
|
+
validateSessionBinding(obj, errors, warnings);
|
|
753
773
|
validateAllowedPaths(obj, errors, warnings);
|
|
754
774
|
validateBlockedCommands(obj, errors);
|
|
755
775
|
validateGates(obj, errors);
|
|
@@ -84,10 +84,9 @@
|
|
|
84
84
|
|
|
85
85
|
import { promises as fs } from 'node:fs';
|
|
86
86
|
import path from 'node:path';
|
|
87
|
-
import os from 'node:os';
|
|
88
87
|
import { pathToFileURL } from 'node:url';
|
|
89
88
|
|
|
90
|
-
import { die, utcTimestamp } from './lib/common.mjs';
|
|
89
|
+
import { die, utcTimestamp, expandTilde } from './lib/common.mjs';
|
|
91
90
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
92
91
|
import {
|
|
93
92
|
SCRIPT_NAME,
|
|
@@ -150,13 +149,6 @@ Exit codes:
|
|
|
150
149
|
process.stderr.write(usage);
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
function expandHome(p) {
|
|
154
|
-
if (typeof p !== 'string' || p.length === 0) return p;
|
|
155
|
-
if (p === '~') return os.homedir();
|
|
156
|
-
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
|
|
157
|
-
return p;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
152
|
async function isDir(p) {
|
|
161
153
|
try {
|
|
162
154
|
const st = await fs.stat(p);
|
|
@@ -222,8 +214,8 @@ async function main() {
|
|
|
222
214
|
const isDryRun = !isApply;
|
|
223
215
|
const isJson = parsed.values.json === true;
|
|
224
216
|
|
|
225
|
-
const sourceRoot = path.resolve(
|
|
226
|
-
const canonicalRoot = path.resolve(
|
|
217
|
+
const sourceRoot = path.resolve(expandTilde(parsed.values.source ?? DEFAULT_SOURCE));
|
|
218
|
+
const canonicalRoot = path.resolve(expandTilde(parsed.values.canonical ?? DEFAULT_CANONICAL));
|
|
227
219
|
|
|
228
220
|
// Parse --resolve flags into a Map<relPath, "src"|"dst"|"skip">
|
|
229
221
|
const resolutions = new Map();
|
|
@@ -31,11 +31,10 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { spawnSync } from 'node:child_process';
|
|
34
|
-
import { join } from 'node:path';
|
|
35
|
-
import { homedir } from 'node:os';
|
|
36
34
|
import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-repo.mjs';
|
|
37
35
|
import { validatePathInsideProject } from './lib/path-utils.mjs';
|
|
38
36
|
import { resolveRepoSpec, redactUrlCredentials } from './lib/vcs-repo-spec.mjs';
|
|
37
|
+
import { expandTilde } from './lib/common.mjs';
|
|
39
38
|
|
|
40
39
|
// ── Argument parsing ──────────────────────────────────────────────────────────
|
|
41
40
|
|
|
@@ -332,9 +331,8 @@ async function main() {
|
|
|
332
331
|
// Load the config-driven repo list before doing any glab work
|
|
333
332
|
const watcherRoot = getConfinementRoot();
|
|
334
333
|
// Home-expand each entry before confinement, matching sibling scripts (W4-Q2 LOW).
|
|
335
|
-
const expandHome = (p) => (p.startsWith('~/') ? join(homedir(), p.slice(2)) : p);
|
|
336
334
|
const flipRepos = (await getCrossRepoProjects()).filter((r) => {
|
|
337
|
-
const guard = validatePathInsideProject(
|
|
335
|
+
const guard = validatePathInsideProject(expandTilde(r), watcherRoot);
|
|
338
336
|
if (!guard.ok) {
|
|
339
337
|
process.stderr.write(
|
|
340
338
|
`vault-integration-watcher: WARN rejecting confined-path violation for ${JSON.stringify(r)} (reason: ${guard.reason})\n`
|
package/scripts/vault-mirror.mjs
CHANGED
|
@@ -63,6 +63,7 @@ import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
|
63
63
|
import { resolveRepoNamespace } from './lib/vault-mirror/namespace.mjs';
|
|
64
64
|
import { resolveCanonicalSuffixes } from './lib/named-vault-resolver.mjs';
|
|
65
65
|
import { loadOwnerConfig } from './lib/owner-yaml.mjs';
|
|
66
|
+
import { canonicalizeSessions } from './lib/sessions-canonical.mjs';
|
|
66
67
|
|
|
67
68
|
// ── Canonical-vault helpers (#600 D2 / #607 D2) ────────────────────────────────
|
|
68
69
|
// These are module-level (above the CLI bootstrap) so the module is import-safe
|
|
@@ -489,24 +490,24 @@ async function main() {
|
|
|
489
490
|
qualityMinConfidence,
|
|
490
491
|
};
|
|
491
492
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
493
|
+
/**
|
|
494
|
+
* Dispatch one already-parsed entry to its processor and account for the
|
|
495
|
+
* result. Extracted (#1186c) so the `--kind session` path below can call it
|
|
496
|
+
* AFTER a whole-file dedup pass instead of once per raw line; every branch
|
|
497
|
+
* is byte-identical to the pre-#1186c per-line loop body.
|
|
498
|
+
* @param {unknown} entry — parsed JSONL value (usually an object; a bare
|
|
499
|
+
* `null`/primitive line is a real shape this must keep handling, see the
|
|
500
|
+
* #1186c session branch below for why it is never filtered out here).
|
|
501
|
+
* @param {number} entryLineNum — 1-based JSONL line number, or (for the
|
|
502
|
+
* `--kind session` dedup path) the line of the record that WON the
|
|
503
|
+
* collapse. `_lineNum` is read ONLY for telemetry (process.mjs
|
|
504
|
+
* `emitEntryAction` → `line:` on the per-entry ledger event), never to
|
|
505
|
+
* derive content — process.mjs itself tolerates a non-finite value by
|
|
506
|
+
* suppressing just that one ledger record, but every call site here
|
|
507
|
+
* always supplies a real line number.
|
|
508
|
+
* @returns {Promise<void>}
|
|
509
|
+
*/
|
|
510
|
+
async function dispatchEntry(entry, entryLineNum) {
|
|
510
511
|
try {
|
|
511
512
|
// Both processors return the `action` string they emitted (every one of
|
|
512
513
|
// their exit paths is an `emitAction` call), so the tally needs no second
|
|
@@ -514,8 +515,8 @@ async function main() {
|
|
|
514
515
|
// stale the first time a branch is added.
|
|
515
516
|
const action =
|
|
516
517
|
kind === 'learning'
|
|
517
|
-
? await processLearning(entry,
|
|
518
|
-
: await processSession(entry,
|
|
518
|
+
? await processLearning(entry, entryLineNum, ctx)
|
|
519
|
+
: await processSession(entry, entryLineNum, ctx);
|
|
519
520
|
tally(action);
|
|
520
521
|
} catch (err) {
|
|
521
522
|
// Validation errors (missing required fields) → per-entry skip, not a global failure
|
|
@@ -530,13 +531,13 @@ async function main() {
|
|
|
530
531
|
await emitMirrorEvent({
|
|
531
532
|
action: 'skipped-invalid',
|
|
532
533
|
kind,
|
|
533
|
-
line:
|
|
534
|
+
line: entryLineNum,
|
|
534
535
|
recordId: entryId,
|
|
535
536
|
skipClass: 'validation',
|
|
536
537
|
reason: err.message,
|
|
537
538
|
dryRun,
|
|
538
539
|
});
|
|
539
|
-
|
|
540
|
+
return;
|
|
540
541
|
}
|
|
541
542
|
// #718: discriminate genuine filesystem/system errors (which must still
|
|
542
543
|
// abort the whole run — a partially-written vault is worse than a loud
|
|
@@ -552,7 +553,7 @@ async function main() {
|
|
|
552
553
|
(typeof err.code === 'string' && err.code.length > 0) || Boolean(err.syscall);
|
|
553
554
|
if (!isSystemError) {
|
|
554
555
|
process.stderr.write(
|
|
555
|
-
`vault-mirror: mapper crash on line ${
|
|
556
|
+
`vault-mirror: mapper crash on line ${entryLineNum} (${err.message}) — record skipped\n`,
|
|
556
557
|
);
|
|
557
558
|
const entryId = entry?.id ?? entry?.session_id ?? null;
|
|
558
559
|
process.stdout.write(
|
|
@@ -569,21 +570,105 @@ async function main() {
|
|
|
569
570
|
await emitMirrorEvent({
|
|
570
571
|
action: 'skipped-invalid',
|
|
571
572
|
kind,
|
|
572
|
-
line:
|
|
573
|
+
line: entryLineNum,
|
|
573
574
|
recordId: entryId,
|
|
574
575
|
skipClass: 'mapper-crash',
|
|
575
576
|
reason: err.message,
|
|
576
577
|
dryRun,
|
|
577
578
|
});
|
|
578
|
-
|
|
579
|
+
return;
|
|
579
580
|
}
|
|
580
581
|
// Unexpected filesystem errors → fatal
|
|
581
|
-
process.stderr.write(`vault-mirror: filesystem error on line ${
|
|
582
|
+
process.stderr.write(`vault-mirror: filesystem error on line ${entryLineNum}: ${err.message}\n`);
|
|
582
583
|
await finishRun('filesystem-error');
|
|
583
584
|
process.exit(2);
|
|
584
585
|
}
|
|
585
586
|
}
|
|
586
587
|
|
|
588
|
+
// #1186c: for `--kind session`, every parsed entry is buffered here instead
|
|
589
|
+
// of dispatched inline — the dedup pass below needs the WHOLE file before it
|
|
590
|
+
// can tell which of several same-`session_id` lines is the winner. `--kind
|
|
591
|
+
// learning` is unaffected: it still dispatches per line, inline, immediately
|
|
592
|
+
// below (a malformed line further down the file must not undo an already-
|
|
593
|
+
// dispatched learning — pinned by the existing `total: 2, created: 1` abort
|
|
594
|
+
// test in tests/unit/vault-mirror.test.mjs).
|
|
595
|
+
const sessionEntries = [];
|
|
596
|
+
const sessionLineNums = [];
|
|
597
|
+
|
|
598
|
+
for (const line of lines) {
|
|
599
|
+
lineNum++;
|
|
600
|
+
const trimmed = line.trim();
|
|
601
|
+
if (!trimmed) continue;
|
|
602
|
+
runState.total++;
|
|
603
|
+
|
|
604
|
+
let entry;
|
|
605
|
+
try {
|
|
606
|
+
entry = JSON.parse(trimmed);
|
|
607
|
+
} catch (err) {
|
|
608
|
+
process.stderr.write(`vault-mirror: malformed JSON on line ${lineNum}: ${err.message}\n`);
|
|
609
|
+
// Close the run out BEFORE exiting: `process.exit` runs no `finally`, so
|
|
610
|
+
// without this the abort is the one outcome that leaves no run record —
|
|
611
|
+
// exactly the shape reserved for a broken emitter.
|
|
612
|
+
await finishRun('malformed-json');
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (kind === 'session') {
|
|
617
|
+
sessionEntries.push(entry);
|
|
618
|
+
sessionLineNums.push(lineNum);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
await dispatchEntry(entry, lineNum);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (kind === 'session') {
|
|
626
|
+
// Only an OBJECT entry carrying a non-empty `session_id` is eligible for
|
|
627
|
+
// the dedup collapse — the same predicate canonicalizeSessions itself uses
|
|
628
|
+
// internally (sessions-canonical.mjs `isRecordObject` + `isNonEmptyString`,
|
|
629
|
+
// not exported, so re-stated here rather than reached into). Everything
|
|
630
|
+
// else — a bare `null`/primitive JSONL line, or a legacy record with no
|
|
631
|
+
// `session_id` field — is dispatched EXACTLY as before: unaffected, in
|
|
632
|
+
// original file order, through the SAME validation/mapper-crash paths
|
|
633
|
+
// process.mjs already has for those shapes. Two regression-guard tests in
|
|
634
|
+
// tests/unit/vault-mirror.test.mjs depend on this (a bare `null` line and a
|
|
635
|
+
// legacy `session`-keyed record both still reach processSession() and its
|
|
636
|
+
// existing error handling, never silently vanish into the collapse).
|
|
637
|
+
const isIdentifiable = (e) =>
|
|
638
|
+
e !== null &&
|
|
639
|
+
typeof e === 'object' &&
|
|
640
|
+
!Array.isArray(e) &&
|
|
641
|
+
typeof e.session_id === 'string' &&
|
|
642
|
+
e.session_id.length > 0;
|
|
643
|
+
const identifiable = sessionEntries.filter(isIdentifiable);
|
|
644
|
+
// canonicalizeSessions never clones — the survivors are the SAME object
|
|
645
|
+
// references as in `sessionEntries`, so reference identity below is exact,
|
|
646
|
+
// never a guess (scripts/lib/sessions-canonical.mjs header, "RULE ORDER").
|
|
647
|
+
const survivors = new Set(canonicalizeSessions(identifiable));
|
|
648
|
+
|
|
649
|
+
for (let i = 0; i < sessionEntries.length; i++) {
|
|
650
|
+
const entry = sessionEntries[i];
|
|
651
|
+
if (isIdentifiable(entry) && !survivors.has(entry)) {
|
|
652
|
+
// A losing duplicate: an earlier line whose `session_id` a LATER line
|
|
653
|
+
// in this same batch supersedes or overwrites (crash-recovery
|
|
654
|
+
// re-append, #1068 stub/supersede pair). No dispatch, no stdout line,
|
|
655
|
+
// no tally for it — the winning occurrence (dispatched below, at its
|
|
656
|
+
// own position) already produces the ONE note this physical session
|
|
657
|
+
// gets. BV-004 ceiling: `runState.total` still counts this raw line,
|
|
658
|
+
// so `created+updated+skipped+failed` no longer partitions `total`
|
|
659
|
+
// for a `--kind session` run that collapsed at least one duplicate —
|
|
660
|
+
// no test pins that invariant for session kind (only for `learning`,
|
|
661
|
+
// where duplicates are not collapsed), and a partially-written vault
|
|
662
|
+
// from a batch that could not be fully deduped is the worse failure
|
|
663
|
+
// mode. Revisit with a dedicated telemetry action if an operator ever
|
|
664
|
+
// needs to name WHICH lines were collapsed, not just how many notes
|
|
665
|
+
// were written.
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
await dispatchEntry(entry, sessionLineNums[i]);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
587
672
|
// ── Run close-out (#1147) ───────────────────────────────────────────────────
|
|
588
673
|
//
|
|
589
674
|
// The happy tail. Both roll-ups live in `finishRun` above, which every abort
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* wave-scope-binding.mjs — print the session binding for a `wave-scope.json`
|
|
4
|
+
* manifest, and record the fail-closed case as an event (#1153 P4).
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/wave-scope-binding.mjs [--merge] [--wave N] [--role R] [--repo-root DIR]
|
|
8
|
+
*
|
|
9
|
+
* Prints ONE JSON object on stdout:
|
|
10
|
+
*
|
|
11
|
+
* {"session_id":"<raw session_id>","semantic_session_id":"<semantic id>"}
|
|
12
|
+
*
|
|
13
|
+
* Those key names are the canonical ones since #1153 P2 — the same spelling
|
|
14
|
+
* `.orchestrator/session.lock` and `current-session.json` already use. Readers
|
|
15
|
+
* additionally accept the pre-#1153 `session` / `semantic_session` spellings
|
|
16
|
+
* until the next minor release (`MANIFEST_SESSION_KEYS` in
|
|
17
|
+
* `scripts/lib/session-identity/own-session.mjs`); this writer never emits them.
|
|
18
|
+
*
|
|
19
|
+
* Keys whose value is unavailable are OMITTED, never written as `""` — an empty
|
|
20
|
+
* id is present-but-equal-to-nobody, which every reader classifies as FOREIGN
|
|
21
|
+
* (the one disposition that skips enforcement entirely), and
|
|
22
|
+
* `validate-wave-scope.mjs` rejects it outright.
|
|
23
|
+
*
|
|
24
|
+
* WHY A COMMAND AND NOT PROSE: this binding used to be an inline
|
|
25
|
+
* `node --input-type=module -e` block in `skills/wave-executor/wave-loop.md`,
|
|
26
|
+
* retyped by the coordinator once per wave. An unbound manifest (`{}`) is the
|
|
27
|
+
* fail-closed direction and therefore SILENT — indistinguishable from a
|
|
28
|
+
* coordinator that skipped the step. `orchestrator.scope.unbound_manifest` is
|
|
29
|
+
* what makes the silent case countable; 0 hits repo-wide before this file.
|
|
30
|
+
*
|
|
31
|
+
* The binding itself is NOT recomputed here. It is exactly one
|
|
32
|
+
* `attributionForRecord()` call, which reads `.orchestrator/session.lock` and
|
|
33
|
+
* confirms the raw `session_id` against this process's own identity before
|
|
34
|
+
* returning anything — under a peer-owned lock it returns `{}` rather than the
|
|
35
|
+
* peer's ids. Duplicating that logic would be the one-fact-two-copies class this
|
|
36
|
+
* repo keeps paying for.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { parseArgs } from 'node:util';
|
|
40
|
+
import { resolve as resolvePath } from 'node:path';
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
import { attributionForRecord, emitEvent } from './lib/events.mjs';
|
|
43
|
+
import { MANIFEST_SESSION_KEYS } from './lib/session-identity/own-session.mjs';
|
|
44
|
+
|
|
45
|
+
const HELP = `Usage: node scripts/wave-scope-binding.mjs [--merge] [--wave N] [--role R] [--repo-root DIR]
|
|
46
|
+
|
|
47
|
+
Print the session-binding keys for a wave-scope.json manifest as one JSON object.
|
|
48
|
+
Empty values are OMITTED; an unbound binding prints {} and emits exactly one
|
|
49
|
+
orchestrator.scope.unbound_manifest event.
|
|
50
|
+
|
|
51
|
+
With --merge, read the DRAFT manifest as one JSON object on stdin and print that
|
|
52
|
+
same manifest with the binding keys merged in (or with them omitted, plus the
|
|
53
|
+
unbound event, when the binding is {}). Every other field is passed through.
|
|
54
|
+
|
|
55
|
+
Options:
|
|
56
|
+
--merge Read the draft manifest on stdin, print it bound.
|
|
57
|
+
--wave <n> Wave number, recorded in the unbound event payload.
|
|
58
|
+
--role <role> Wave role, recorded in the unbound event payload.
|
|
59
|
+
--repo-root <dir> Repo root to resolve the lock and the events log against
|
|
60
|
+
(default: process.cwd()).
|
|
61
|
+
-h, --help Show this help and exit 0.
|
|
62
|
+
|
|
63
|
+
Output:
|
|
64
|
+
stdout — exactly one JSON object. Diagnostics go to stderr.
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the manifest binding for `repoRoot`.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} repoRoot
|
|
71
|
+
* @returns {{ session_id?: string, semantic_session_id?: string }} binding with
|
|
72
|
+
* empty values omitted
|
|
73
|
+
*/
|
|
74
|
+
export function resolveBinding(repoRoot) {
|
|
75
|
+
const attribution = attributionForRecord(repoRoot) ?? {};
|
|
76
|
+
/** @type {{ session_id?: string, semantic_session_id?: string }} */
|
|
77
|
+
const out = {};
|
|
78
|
+
const session = typeof attribution.session_id === 'string' ? attribution.session_id.trim() : '';
|
|
79
|
+
const semantic = typeof attribution.semantic_session_id === 'string'
|
|
80
|
+
? attribution.semantic_session_id.trim()
|
|
81
|
+
: '';
|
|
82
|
+
if (session) out.session_id = session;
|
|
83
|
+
if (semantic) out.semantic_session_id = semantic;
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function main(argv) {
|
|
88
|
+
let parsed;
|
|
89
|
+
try {
|
|
90
|
+
parsed = parseArgs({
|
|
91
|
+
args: argv,
|
|
92
|
+
options: {
|
|
93
|
+
wave: { type: 'string' },
|
|
94
|
+
role: { type: 'string' },
|
|
95
|
+
'repo-root': { type: 'string' },
|
|
96
|
+
merge: { type: 'boolean' },
|
|
97
|
+
help: { type: 'boolean', short: 'h' },
|
|
98
|
+
},
|
|
99
|
+
allowPositionals: false,
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
process.stderr.write(`wave-scope-binding: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
103
|
+
process.stderr.write(HELP);
|
|
104
|
+
process.exitCode = 2;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (parsed.values.help) {
|
|
109
|
+
process.stdout.write(HELP);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const repoRoot = parsed.values['repo-root'] ?? process.cwd();
|
|
114
|
+
const binding = resolveBinding(repoRoot);
|
|
115
|
+
|
|
116
|
+
if (Object.keys(binding).length === 0) {
|
|
117
|
+
// The fail-closed case, and the whole reason this is a command: an unbound
|
|
118
|
+
// manifest enforces against EVERY session in the checkout, and until now it
|
|
119
|
+
// left no trace at all. Best-effort — the binding is still printed if the
|
|
120
|
+
// append fails, because a broken events log must not stall a wave.
|
|
121
|
+
try {
|
|
122
|
+
await emitEvent(
|
|
123
|
+
'orchestrator.scope.unbound_manifest',
|
|
124
|
+
{
|
|
125
|
+
// Numeric when it parses as one, so the field matches every other
|
|
126
|
+
// `wave` in the stream; `null` (never `undefined`) when absent, since
|
|
127
|
+
// an undefined `wave` lets emitEvent's correlation envelope fill it
|
|
128
|
+
// from the live manifest — the very artefact this event says is
|
|
129
|
+
// unbound.
|
|
130
|
+
wave: Number.isFinite(Number(parsed.values.wave)) && parsed.values.wave !== undefined
|
|
131
|
+
? Number(parsed.values.wave)
|
|
132
|
+
: (parsed.values.wave ?? null),
|
|
133
|
+
role: parsed.values.role ?? null,
|
|
134
|
+
reason: 'no-confirmed-session-attribution',
|
|
135
|
+
},
|
|
136
|
+
{ repoRoot },
|
|
137
|
+
);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
process.stderr.write(
|
|
140
|
+
`wave-scope-binding: could not record unbound_manifest event: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!parsed.values.merge) {
|
|
146
|
+
process.stdout.write(`${JSON.stringify(binding)}\n`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// --merge: the coordinator pipes the draft manifest in and gets the SAME
|
|
151
|
+
// manifest back with the binding merged, instead of hand-copying two keys
|
|
152
|
+
// out of the printed object into the JSON it is about to write (#1207).
|
|
153
|
+
let draftRaw;
|
|
154
|
+
try {
|
|
155
|
+
draftRaw = await readStdin();
|
|
156
|
+
} catch (error) {
|
|
157
|
+
process.stderr.write(
|
|
158
|
+
`wave-scope-binding: could not read the draft manifest from stdin: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
159
|
+
);
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
let draft;
|
|
164
|
+
try {
|
|
165
|
+
draft = JSON.parse(draftRaw);
|
|
166
|
+
} catch {
|
|
167
|
+
process.stderr.write('wave-scope-binding: --merge expects ONE JSON object on stdin\n');
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!draft || typeof draft !== 'object' || Array.isArray(draft)) {
|
|
172
|
+
process.stderr.write('wave-scope-binding: --merge expects ONE JSON object on stdin\n');
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Any binding key already in the draft is dropped first: under an unbound or
|
|
178
|
+
// peer-owned lock the merged manifest must name NOBODY, and a stale key
|
|
179
|
+
// surviving the merge would name somebody. Omitted, never `""` — see above.
|
|
180
|
+
// BOTH spellings are dropped (close-review 2026-09-04, HIGH): a legacy
|
|
181
|
+
// `session`/`semantic_session` pair surviving the merge is still READ by every
|
|
182
|
+
// consumer and would name a foreign session — `foreign` = gates stand down.
|
|
183
|
+
const merged = { ...draft };
|
|
184
|
+
for (const key of [...MANIFEST_SESSION_KEYS.current, ...MANIFEST_SESSION_KEYS.legacy]) delete merged[key];
|
|
185
|
+
// Explicit per-key copy, never Object.assign: `merged` derives from a
|
|
186
|
+
// JSON.parse'd draft, and a `__proto__` payload key would reach the
|
|
187
|
+
// Object.prototype setter through [[Set]] semantics (CWE-1321, semgrep
|
|
188
|
+
// prototype-pollution-object-assign — CI-red on ce6a28aa).
|
|
189
|
+
for (const key of MANIFEST_SESSION_KEYS.current) {
|
|
190
|
+
if (typeof binding[key] === 'string' && binding[key]) merged[key] = binding[key];
|
|
191
|
+
}
|
|
192
|
+
process.stdout.write(`${JSON.stringify(merged)}\n`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Read all of stdin as UTF-8. Resolves to `''` when stdin is closed/empty.
|
|
197
|
+
* @returns {Promise<string>}
|
|
198
|
+
*/
|
|
199
|
+
function readStdin() {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
let buf = '';
|
|
202
|
+
process.stdin.setEncoding('utf8');
|
|
203
|
+
process.stdin.on('data', (chunk) => {
|
|
204
|
+
buf += chunk;
|
|
205
|
+
});
|
|
206
|
+
process.stdin.on('end', () => resolve(buf));
|
|
207
|
+
process.stdin.on('error', reject);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// `import.meta.main` is not available on every supported Node — compare argv[1]
|
|
212
|
+
// instead, so importing this module from a test never runs the CLI.
|
|
213
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === resolvePath(process.argv[1])) {
|
|
214
|
+
await main(process.argv.slice(2));
|
|
215
|
+
}
|
|
@@ -28,6 +28,16 @@ Apply these steps in order against the repo root. Stop at the first hit. Never m
|
|
|
28
28
|
- Never read both files. Never concatenate, diff, or cross-validate them — the SSOT is whichever the rule selects.
|
|
29
29
|
- The resolved kind (`claude` | `agents`) is part of the contract. Consumers that report paths in JSON output (e.g., `skills/claude-md-drift-check/checker.mjs`) must surface the resolved path so users on either platform can audit the result.
|
|
30
30
|
|
|
31
|
+
## Interaction with the root `AGENTS.md` this repo now ships
|
|
32
|
+
|
|
33
|
+
Since the cross-harness portable surface landed, this repo carries BOTH files at its root. That does not weaken the rule above — it is what makes the rule safe to hold while still serving foreign readers:
|
|
34
|
+
|
|
35
|
+
- **Our own readers still never read both.** `resolveInstructionFile()` picks exactly one (`CLAUDE.md` wins ties), and every consumer listed below goes through it. Nothing merges, diffs, or cross-validates the two as sources.
|
|
36
|
+
- **The root `AGENTS.md` exists for FOREIGN readers**, not for ours. 7 of 8 surveyed harnesses (Codex CLI, Cursor, Copilot CLI, OpenCode, Amp, Kiro, …) read `AGENTS.md`; only Claude Code reads `CLAUDE.md`, and only Copilot CLI reads both. Without a root `AGENTS.md` this repo's `## Session Config` was unreachable from six of them.
|
|
37
|
+
- **It is byte-identical by construction, and generated.** `scripts/generate-agents-skills.mjs` copies `CLAUDE.md` verbatim; `--check` (wired into `scripts/validate-plugin.mjs`) fails CI on any divergence. **Never edit `AGENTS.md`** — edit `CLAUDE.md` and regenerate. A consumer repo may instead symlink it; both shapes are accepted by the drift gate.
|
|
38
|
+
- **Why a copy and not a symlink here:** `package.json` `files[]` does not publish `CLAUDE.md`, so a symlink would be DANGLING in the npm tarball; and `core.symlinks` defaults to false on Windows without Developer Mode, where git materialises the link as a 10-byte regular file containing the literal text `CLAUDE.md` — a pointer with no Session Config, which is precisely the failure this file guards against.
|
|
39
|
+
- **The invariant is "the two cannot disagree", not "exactly one file exists."** `claude-md-drift-check` Check 7 (`vault-dir-parity`) enforces it: alias-by-construction → parity satisfied; two independent files that diverge → error. Check 9's probe 2a uses the same predicate so a defect in `CLAUDE.md` is never reported twice.
|
|
40
|
+
|
|
31
41
|
## Reference implementations
|
|
32
42
|
|
|
33
43
|
### Bash one-liner (matches `skills/_shared/bootstrap-gate.md` style)
|
|
@@ -101,10 +101,39 @@ The two slots are `parallelPeer.mode` and `parallelPeer.pid`; `<ageHours>` is th
|
|
|
101
101
|
|
|
102
102
|
### Outcome handling
|
|
103
103
|
|
|
104
|
-
- **Worktree anlegen + starten** → invoke `enterWorktree({ basePath, sessionId, branch, repoRoot })` from `scripts/lib/autopilot/worktree-pipeline.mjs`. The helper creates a sibling worktree at `<basePath>/<repo-name>-<sessionId>/`, runs idempotency + boundary checks, and logs a WARN line to stderr on fresh creation. When `<branch>` is already checked out by another worktree — the normal case, since Phase 0.5 passes the current HEAD — the worktree lands on a fresh `so/<sessionId>` branch created at `<branch>` and the helper returns `{ branch: 'so/<sessionId>', promotedFrom: '<branch>' }` (#1067); the new worktree's STATE.md `branch` MUST record `so/<sessionId>` and note `promoted from <branch>@<repoRoot>`.
|
|
104
|
+
- **Worktree anlegen + starten** → invoke `enterWorktree({ basePath, sessionId, branch, repoRoot, rawSessionId, reason: 'worktree-promotion' })` from `scripts/lib/autopilot/worktree-pipeline.mjs`. The helper creates a sibling worktree at `<basePath>/<repo-name>-<sessionId>/`, runs idempotency + boundary checks, and logs a WARN line to stderr on fresh creation. When `<branch>` is already checked out by another worktree — the normal case, since Phase 0.5 passes the current HEAD — the worktree lands on a fresh `so/<sessionId>` branch created at `<branch>` and the helper returns `{ branch: 'so/<sessionId>', promotedFrom: '<branch>' }` (#1067); the new worktree's STATE.md `branch` MUST record `so/<sessionId>` and note `promoted from <branch>@<repoRoot>`. Since #1170, `enterWorktree` releases the source root ITSELF once the destination worktree provably exists — it calls `leaveSourceRoot({ repoRoot, sessionId: rawSessionId, semanticSessionId: sessionId, reason })` from `scripts/lib/session-transition.mjs` internally, on BOTH success exits, so this AUQ handler makes no separate `leaveSourceRoot` call. `rawSessionId` — **read from this root's `.orchestrator/session.lock` via `readLock({ repoRoot })`, never the semantic label, and never `current-session.json`, which may describe a peer session (#863)** — is the RAW physical `session_id` owning this root's lock/registry entry; a wrong id aborts the teardown with `left.ok: false, reason: 'lock-session-mismatch:<owner>'` and removes nothing. In detail: the promotion is a PROCESS BOUNDARY, not a live migration (#1069) — the old root is deregistered and its `session.lock` released BEFORE the new worktree's own Phase 1.2 acquires, so the two roots never both own a live claim at once. `enterWorktree`'s return value carries the outcome as `left: { ok, steps, reason? }`; `leaveSourceRoot()` never throws, so on `left.ok !== true` `enterWorktree` itself emits the stderr WARN `enterWorktree: leaveSourceRoot: <reason>` and the promotion continues regardless (the destination worktree already exists — aborting here would leave the two-live-roots state the call prevents). Then exit the current preamble flow — the new worktree's own session-start runs from scratch (Phase 1 onwards). On failure (`WorktreeBoundaryError` or `git worktree add` non-zero exit), emit a stderr warning `parallel-aware: enterWorktree failed: <error>; falling back to Manuell` and proceed via the Manuell path.
|
|
105
105
|
- **Manuell** → append a Deviation via `appendDeviationOnDisk()`:
|
|
106
106
|
`Worktree-Auto-Promotion declined; running in-place alongside session_id=<peer.sessionId>, mode=<peer.mode>, pid=<peer.pid>. PSA-001/PSA-002/PSA-004 discipline applies.`
|
|
107
|
-
Continue Phase-0.
|
|
107
|
+
Continue Phase-0 — and run the **Peer-Scope-Union** protocol below before the first write.
|
|
108
|
+
|
|
109
|
+
### Peer-Scope-Union (Manuell only, #1195)
|
|
110
|
+
|
|
111
|
+
In-place beside a peer is survivable when the two scopes are DECLARED to each other rather than discovered by collision. Measured 2026-09-02 in a consumer repo: a 4-subagent session ran beside a deep session (wave 4, `enforcement: strict`) in ONE checkout, no worktree, zero collisions — the peer's paths were carried in the deep session's `allowedPaths` union (19 → 41, `--assert-subset` green) across a wave rollover.
|
|
112
|
+
|
|
113
|
+
Four steps, in order. Steps 1 and 2 are the protocol; 3 and 4 are what keeps it honest.
|
|
114
|
+
|
|
115
|
+
1. **Declare the COMPLETE path list.** The arriving session sends the peer every path it will write — including the ones a script produces (fixtures, snapshots, result files, temp helpers), not only the ones it plans to edit by hand. A path omitted here is a path the peer's guard reports as a violation. In the same message it adopts the peer's resource rules.
|
|
116
|
+
2. **The peer unions.** The receiving coordinator adds those paths to its `allowedPaths` as ONE record `peer-session-<id>` in the wave's scope manifest, re-asserts subset/disjointness, and re-materializes them on every wave rollover (`skills/wave-executor/wave-loop.md` § Scope Manifest).
|
|
117
|
+
3. **Probe with ONE real write.** Before dispatching any agent, make one small PLANNED Edit from the declared list. A denial here costs one edit; the same denial found after a fan-out costs the wave.
|
|
118
|
+
4. **Announce before committing.** The arriving session sends its final file list, the peer sequences its own push behind it, and the SHAs come back. The git index is shared (PSA-007) — sequencing it is the only thing that makes two sessions in one checkout committable.
|
|
119
|
+
|
|
120
|
+
Message template for step 1 (`SendMessage`, first line self-contained per `.claude/rules/cross-session-messaging.md`):
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Scope-union request: I will write exactly these paths in <repo> — please add them to your allowedPaths.
|
|
124
|
+
|
|
125
|
+
Paths (complete, incl. files my scripts write):
|
|
126
|
+
- <path>
|
|
127
|
+
- <path>
|
|
128
|
+
|
|
129
|
+
Resource rules I adopt from you: no build, no dev-server ports, no service stop/restart,
|
|
130
|
+
commit only via `git commit --only <my files>` after announcing, no push, no tag.
|
|
131
|
+
|
|
132
|
+
I will probe with ONE planned edit before dispatching, announce my final file list before
|
|
133
|
+
committing, and send you the SHAs afterwards.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Delivery is never guaranteed (CSM-004): an unanswered request establishes nothing. Without a confirmed union, do not write beside the peer — take the worktree instead.
|
|
108
137
|
- **Abbrechen** → exit Phase-0 immediately. No file writes.
|
|
109
138
|
|
|
110
139
|
## Always-OK Pass-Through (no AUQ)
|