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
|
@@ -60,6 +60,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
60
60
|
import { emitEvent, sessionAttribution } from './events.mjs';
|
|
61
61
|
import { tryAcquireFileLock, releaseFileLock } from './file-lock.mjs';
|
|
62
62
|
import { readLock, isLockLive } from './session-lock.mjs';
|
|
63
|
+
import { resolveSubagentSidecar } from '../../hooks/_lib/subagent-paths.mjs';
|
|
63
64
|
|
|
64
65
|
const DEFAULT_INTERVAL_S = 2;
|
|
65
66
|
const EVENTS_FILE_REL = '.orchestrator/metrics/events.jsonl';
|
|
@@ -95,9 +96,107 @@ const RE_EMIT_EVERY = 10;
|
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
98
|
* git subcommands that mutate the shared index, the stash stack, or remote
|
|
98
|
-
* history — the PSA-007 prohibition list, verbatim.
|
|
99
|
+
* history — the PSA-007 prohibition list, verbatim. Matching the subcommand
|
|
100
|
+
* LITERAL is only the first half: the arguments decide whether the invocation
|
|
101
|
+
* writes anything at all (#1215).
|
|
102
|
+
*
|
|
103
|
+
* Group 1 is the subcommand, group 2 the remaining argument string.
|
|
104
|
+
*
|
|
105
|
+
* The global-flag run has TWO alternatives on purpose (W4/F2). A bare
|
|
106
|
+
* `(?:-[^\s]+\s+)*` cannot absorb a VALUE-taking global flag: in
|
|
107
|
+
* `git -C /tmp stash` the run eats `-C ` and then meets `/tmp`, which is not a
|
|
108
|
+
* subcommand, so the whole invocation read as NOT a git write — measured 2026-09-04,
|
|
109
|
+
* `isGitWrite('git -C /tmp stash')` and `isGitWrite('git -c user.name=x commit -m y')`
|
|
110
|
+
* both returned false. `-C`/`-c`/`--git-dir`/`--work-tree`/`--namespace`/
|
|
111
|
+
* `--exec-path`/`--config-env` are the value-taking globals; the space-separated
|
|
112
|
+
* form is matched first, the `--flag=value` form falls through to the generic
|
|
113
|
+
* alternative unchanged.
|
|
114
|
+
*
|
|
115
|
+
* `command git …` and `env VAR=x git …` prefixes are absorbed too — both are
|
|
116
|
+
* ordinary agent shapes that hid a write behind one token.
|
|
117
|
+
*
|
|
118
|
+
* DELIBERATE — `-C <dir>` is NOT treated as fixture context. `FIXTURE_CONTEXT_RE`
|
|
119
|
+
* keys on `cd /tmp` / `mktemp`, the shapes agents actually write around a test
|
|
120
|
+
* fixture; teaching it `-C` would hand back the exact one-token evasion this fix
|
|
121
|
+
* closes (`git -C /tmp/../<repo> commit`). A genuine `git -C "$TMPDIR" commit`
|
|
122
|
+
* therefore reports — a false alarm on an advisory detector, never a missed breach.
|
|
123
|
+
*
|
|
124
|
+
* CEILING (BV-004): the prefix list is enumerated, not general. `sudo git …`,
|
|
125
|
+
* `nice -n 5 git …` and any other wrapper still hide the write; and a git write
|
|
126
|
+
* inside `$(…)` or a quoted string was never detected (see `isGitWrite`).
|
|
127
|
+
* Revisit if a real PSA-007 breach is ever missed through a wrapper.
|
|
99
128
|
*/
|
|
100
|
-
const GIT_WRITE_RE =
|
|
129
|
+
const GIT_WRITE_RE =
|
|
130
|
+
/^\s*(?:command\s+|env\s+(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)+)*git\s+(?:(?:-C|-c|--git-dir|--work-tree|--namespace|--exec-path|--config-env)\s+\S+\s+|-[^\s]+\s+)*(add|commit|stash|push|mv|rm|reset|checkout)\b(.*)$/;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `git stash` sub-subcommands that actually touch the stash stack. Bare
|
|
134
|
+
* `git stash` (no sub-subcommand) is `stash push`, so it counts too; anything
|
|
135
|
+
* else — `list`, `show`, an unknown word — is a read.
|
|
136
|
+
*/
|
|
137
|
+
const STASH_WRITE_SUBS = new Set([
|
|
138
|
+
'push',
|
|
139
|
+
'save',
|
|
140
|
+
'pop',
|
|
141
|
+
'apply',
|
|
142
|
+
'drop',
|
|
143
|
+
'clear',
|
|
144
|
+
'branch',
|
|
145
|
+
'create',
|
|
146
|
+
'store',
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Argument-aware PSA-007 classification. Decision table (#1215):
|
|
151
|
+
*
|
|
152
|
+
* | invocation | verdict | why |
|
|
153
|
+
* |-----------------------------------|---------|----------------------------------------|
|
|
154
|
+
* | `git stash` / `stash push|save` | write | pushes onto the shared stash stack |
|
|
155
|
+
* | `stash pop|apply|drop|clear` | write | mutates / consumes the stack |
|
|
156
|
+
* | `stash branch|create|store` | write | creates refs from the stack |
|
|
157
|
+
* | `git stash list` / `stash show` | READ | capability/inspection probe |
|
|
158
|
+
* | `--help` / `-h` / `--version` | READ | capability probe, any subcommand |
|
|
159
|
+
* | `--dry-run` (any subcommand) | READ | prints, never writes |
|
|
160
|
+
* | `-n` on add / rm / mv / push | READ | `-n` IS `--dry-run` there |
|
|
161
|
+
* | `-n` on commit | WRITE | `-n` is `--no-verify` — a REAL commit |
|
|
162
|
+
* | `git checkout -- <path>` | write | discards sibling work (destructive) |
|
|
163
|
+
* | `git checkout <branch>` | READ | no index/worktree content destroyed |
|
|
164
|
+
* | `git reset` (any form) | write | destructive, kept unconditional |
|
|
165
|
+
* | `git add|commit|push|mv|rm` else | write | the PSA-007 list, verbatim |
|
|
166
|
+
*
|
|
167
|
+
* The `-n` split is measured, not assumed — `git commit -h` prints
|
|
168
|
+
* `-n, --no-verify` while `git add -h` / `git rm -h` / `git push -h` all print
|
|
169
|
+
* `-n, --[no-]dry-run` (measured 2026-09-04, git in this repo's PATH).
|
|
170
|
+
* Treating `commit -n` as a read would blind the detector to
|
|
171
|
+
* `git commit --no-verify`, which `parallel-sessions.md` § PSA-007 names as an
|
|
172
|
+
* anti-pattern verbatim.
|
|
173
|
+
*
|
|
174
|
+
* Ceiling: flags are matched as whole tokens after quoted spans are stripped,
|
|
175
|
+
* so `git commit -m "fix -h"` stays a write. Bundled short flags (`-nv`) and
|
|
176
|
+
* flags hidden in `$(…)` are not decoded — they read as writes, a false alarm,
|
|
177
|
+
* never a missed breach. Revisit if agents start writing bundled git flags.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} sub — the matched git subcommand
|
|
180
|
+
* @param {string} rawArgs — everything after it in the segment
|
|
181
|
+
* @returns {boolean}
|
|
182
|
+
*/
|
|
183
|
+
function gitSegmentWrites(sub, rawArgs) {
|
|
184
|
+
// Strip quoted spans so a commit message can never supply a read flag.
|
|
185
|
+
const args = String(rawArgs || '').replace(/"[^"]*"|'[^']*'/g, ' ');
|
|
186
|
+
const tokens = args.split(/\s+/).filter(Boolean);
|
|
187
|
+
|
|
188
|
+
for (const t of tokens) {
|
|
189
|
+
if (t === '--help' || t === '-h' || t === '--version' || t === '--dry-run') return false;
|
|
190
|
+
if (t === '-n' && sub !== 'commit') return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (sub === 'checkout') return tokens.includes('--');
|
|
194
|
+
if (sub === 'stash') {
|
|
195
|
+
const first = tokens.find((t) => !t.startsWith('-'));
|
|
196
|
+
return first === undefined || STASH_WRITE_SUBS.has(first);
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
101
200
|
|
|
102
201
|
/**
|
|
103
202
|
* STATUS literals that all report as pattern `status-partial`.
|
|
@@ -282,9 +381,22 @@ export function classifyErrorClass(text) {
|
|
|
282
381
|
* `/private/tmp`, `/var/folders`, `$TMPDIR`). A fixture repo created somewhere
|
|
283
382
|
* else is still reported — a false alarm, never a missed breach. Revisit if
|
|
284
383
|
* agents start seeding fixtures outside these paths.
|
|
384
|
+
*
|
|
385
|
+
* #1172: the temp path must also match BARE — the reported false alarm was
|
|
386
|
+
* `cd /tmp && git init && git commit -m x`, where the old pattern required a
|
|
387
|
+
* trailing `/` after `/tmp` and therefore saw nothing. The terminator class
|
|
388
|
+
* (`/`, quote, whitespace, `;`, `&`, `|`, end of string) is what keeps
|
|
389
|
+
* `cd /tmpfoo` — a real directory that is NOT the temp dir — reported.
|
|
390
|
+
*
|
|
391
|
+
* The destructive guard's own temp allowlist (`hooks/pre-bash-destructive-guard.mjs`
|
|
392
|
+
* `isSafeRmTarget`) is deliberately NOT reused: it exports nothing
|
|
393
|
+
* (`grep -n "^export" hooks/pre-bash-destructive-guard.mjs` → 0 matches,
|
|
394
|
+
* measured 2026-09-04) and resolves paths on the filesystem against a loaded
|
|
395
|
+
* policy, whereas this is a lexical test on a transcript string with no repo
|
|
396
|
+
* context. Same intent, different substrate.
|
|
285
397
|
*/
|
|
286
398
|
const FIXTURE_CONTEXT_RE =
|
|
287
|
-
/\bmktemp\b|\bcd\s+["']?(?:\/private)?\/(?:tmp|var\/folders)
|
|
399
|
+
/\bmktemp\b|\bcd\s+["']?(?:\/private)?\/(?:tmp|var\/folders)(?:["'\s;&|/]|$)|\bcd\s+["']?\$\{?(?:TMPDIR|TMP|SCRATCH)/;
|
|
288
400
|
|
|
289
401
|
/**
|
|
290
402
|
* True when the Bash command contains a git INDEX/HISTORY write in ANY
|
|
@@ -300,7 +412,10 @@ const FIXTURE_CONTEXT_RE =
|
|
|
300
412
|
export function isGitWrite(command) {
|
|
301
413
|
const text = String(command || '');
|
|
302
414
|
if (FIXTURE_CONTEXT_RE.test(text)) return false;
|
|
303
|
-
return text.split(/&&|\|\||;|\||\n/).some((seg) =>
|
|
415
|
+
return text.split(/&&|\|\||;|\||\n/).some((seg) => {
|
|
416
|
+
const m = GIT_WRITE_RE.exec(seg);
|
|
417
|
+
return m ? gitSegmentWrites(m[1], m[2]) : false;
|
|
418
|
+
});
|
|
304
419
|
}
|
|
305
420
|
|
|
306
421
|
/**
|
|
@@ -606,13 +721,32 @@ function mayCarrySignal(line) {
|
|
|
606
721
|
}
|
|
607
722
|
|
|
608
723
|
/**
|
|
609
|
-
*
|
|
724
|
+
* Read the agent TYPE from the sidecar `agent-<id>.meta.json` the harness
|
|
725
|
+
* writes next to the subagent transcript. `agentId` here comes from a
|
|
726
|
+
* `readdirSync()` filename match (tailLoop below), not from untrusted stdin —
|
|
727
|
+
* but it is validated through the SAME consolidated derivation (#1196) as the
|
|
728
|
+
* hook-side copies for consistency: an id readdirSync happened to list that
|
|
729
|
+
* fails `{1,64}`/charset/`'unknown'` short-circuits to the honest `'unknown'`
|
|
730
|
+
* return, same as any other lookup failure.
|
|
731
|
+
*
|
|
732
|
+
* `projectsDir`/`sessionId` reconstruct the PARENT transcript path
|
|
733
|
+
* (`<projectsDir>/<sessionId>.jsonl`) that `resolveSubagentSidecar()` expects —
|
|
734
|
+
* the real on-disk location of the coordinator's own transcript, sibling of
|
|
735
|
+
* the `subagents/` directory this function reads from.
|
|
736
|
+
*
|
|
737
|
+
* @param {string} projectsDir
|
|
738
|
+
* @param {string} sessionId
|
|
610
739
|
* @param {string} agentId
|
|
611
740
|
* @returns {string}
|
|
612
741
|
*/
|
|
613
|
-
function readAgentType(
|
|
742
|
+
function readAgentType(projectsDir, sessionId, agentId) {
|
|
743
|
+
const sidecar = resolveSubagentSidecar({
|
|
744
|
+
transcriptPath: join(projectsDir, `${sessionId}.jsonl`),
|
|
745
|
+
agentId,
|
|
746
|
+
});
|
|
747
|
+
if (sidecar === null) return 'unknown';
|
|
614
748
|
try {
|
|
615
|
-
const meta = JSON.parse(readFileSync(
|
|
749
|
+
const meta = JSON.parse(readFileSync(sidecar.meta, 'utf8'));
|
|
616
750
|
const t = meta?.agentType;
|
|
617
751
|
return typeof t === 'string' && t ? t : 'unknown';
|
|
618
752
|
} catch {
|
|
@@ -705,7 +839,7 @@ async function tailLoop({ intervalS }) {
|
|
|
705
839
|
offsets.set(file, tick.offset);
|
|
706
840
|
if (tick.lines.length === 0) continue;
|
|
707
841
|
const agentId = file.slice('agent-'.length, -'.jsonl'.length);
|
|
708
|
-
if (!agentTypes.has(agentId)) agentTypes.set(agentId, readAgentType(
|
|
842
|
+
if (!agentTypes.has(agentId)) agentTypes.set(agentId, readAgentType(projectsDir, sessionId, agentId));
|
|
709
843
|
for (const line of tick.lines) {
|
|
710
844
|
if (!mayCarrySignal(line)) continue;
|
|
711
845
|
let rec;
|
|
@@ -13,12 +13,24 @@
|
|
|
13
13
|
* After the aggregate is published, per-agent declarations left behind by an
|
|
14
14
|
* earlier materialization of the SAME wave are reconciled away (#1103) — but
|
|
15
15
|
* only against a proven session owner. See {@link reconcileOrphans}.
|
|
16
|
+
*
|
|
17
|
+
* PEER RECORDS (`peer-session-*`, #1195) get NO per-agent file: shape (a) is
|
|
18
|
+
* `$AGENT_FILESCOPE_JSON`, addressed by an agent id at dispatch time, and no
|
|
19
|
+
* agent is ever dispatched for a peer session — nothing reads it. They are
|
|
20
|
+
* still written into the aggregate, where `--assert-disjoint` and
|
|
21
|
+
* `hooks/post-bash-write-verify.mjs` do read them; `--union` excludes them so
|
|
22
|
+
* `allowedPaths` never grants a peer's territory to this wave's agents.
|
|
16
23
|
*/
|
|
17
24
|
|
|
18
25
|
import { readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
19
26
|
import { resolve } from 'node:path';
|
|
20
27
|
import { fileURLToPath } from 'node:url';
|
|
21
28
|
import { writeJsonAtomicSync } from './lib/io.mjs';
|
|
29
|
+
import { isPeerRecordId } from './lib/scope-gate.mjs';
|
|
30
|
+
import {
|
|
31
|
+
MANIFEST_SESSION_KEYS,
|
|
32
|
+
manifestSessionBinding,
|
|
33
|
+
} from './lib/session-identity/own-session.mjs';
|
|
22
34
|
|
|
23
35
|
const HELP = `Usage: node scripts/materialize-wave-scope.mjs --state-dir <dir> --wave <positive-int> [--json]
|
|
24
36
|
|
|
@@ -50,6 +62,9 @@ Examples:
|
|
|
50
62
|
|
|
51
63
|
Writes:
|
|
52
64
|
<state-dir>/filescopes/wave-N/<id>.json Bare string[] for each record
|
|
65
|
+
EXCEPT a peer-session-* record (#1195), which is
|
|
66
|
+
aggregate-only: no agent is dispatched for it, so nothing
|
|
67
|
+
would read its per-agent file.
|
|
53
68
|
<state-dir>/filescopes/wave-N.scopes.json Aggregate [{id, files}, ...]
|
|
54
69
|
|
|
55
70
|
Removes (only with a proven owner — see --session):
|
|
@@ -209,7 +224,8 @@ export function validateScopeRecords(value) {
|
|
|
209
224
|
/**
|
|
210
225
|
* Read the session ids the sibling manifest `<state-dir>/wave-scope.json`
|
|
211
226
|
* declares for this state directory (#1123 wrote both `session` — the raw
|
|
212
|
-
* `session_id` — and its human-readable twin `
|
|
227
|
+
* `session_id` — and its human-readable twin `semantic_session_id`; the
|
|
228
|
+
* pre-#1153 spellings `session` / `semantic_session` are still read).
|
|
213
229
|
*
|
|
214
230
|
* A caller may legitimately hold either spelling, so BOTH are returned and a
|
|
215
231
|
* match against either proves ownership. Any failure to read or parse the
|
|
@@ -228,10 +244,12 @@ export function manifestSessionIds(stateDir, readFile = readFileSync) {
|
|
|
228
244
|
return [];
|
|
229
245
|
}
|
|
230
246
|
if (!isRecord(manifest)) return [];
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
247
|
+
// #1153 P2 — the key spellings live in own-session.mjs, which also reads the
|
|
248
|
+
// pre-#1153 `session`/`semantic_session` names for one transition release.
|
|
249
|
+
const binding = manifestSessionBinding(manifest);
|
|
250
|
+
return MANIFEST_SESSION_KEYS.current
|
|
251
|
+
.map((key) => binding[key])
|
|
252
|
+
.filter((value) => typeof value === 'string' && value.length > 0);
|
|
235
253
|
}
|
|
236
254
|
|
|
237
255
|
/**
|
|
@@ -368,7 +386,12 @@ export function materializeWaveScope(records, {
|
|
|
368
386
|
}) {
|
|
369
387
|
const scopeDir = resolve(stateDir, 'filescopes', `wave-${wave}`);
|
|
370
388
|
const aggregatePath = resolve(stateDir, 'filescopes', `wave-${wave}.scopes.json`);
|
|
371
|
-
|
|
389
|
+
// #1195 — peer records are aggregate-only (see the header note): no reader
|
|
390
|
+
// exists for a per-agent file that no dispatch will ever address. Excluding
|
|
391
|
+
// them from `keepIds` below also lets a stale `peer-session-*.json` written
|
|
392
|
+
// before this rule be reconciled away like any other orphan.
|
|
393
|
+
const agentRecords = records.filter((r) => !isPeerRecordId(r.id));
|
|
394
|
+
const perAgentPaths = agentRecords.map(({ id }) => resolve(scopeDir, `${id}.json`));
|
|
372
395
|
|
|
373
396
|
try {
|
|
374
397
|
unlinkSync(aggregatePath);
|
|
@@ -378,8 +401,8 @@ export function materializeWaveScope(records, {
|
|
|
378
401
|
}
|
|
379
402
|
}
|
|
380
403
|
|
|
381
|
-
for (let index = 0; index <
|
|
382
|
-
const result = writeJson(perAgentPaths[index],
|
|
404
|
+
for (let index = 0; index < agentRecords.length; index++) {
|
|
405
|
+
const result = writeJson(perAgentPaths[index], agentRecords[index].files, { tmpPrefix: '.materialize-wave-scope' });
|
|
383
406
|
if (!result?.ok) {
|
|
384
407
|
throw new WriteError(`cannot write per-agent declaration ${perAgentPaths[index]}: ${result?.error ?? 'unknown write failure'}`);
|
|
385
408
|
}
|
|
@@ -396,7 +419,7 @@ export function materializeWaveScope(records, {
|
|
|
396
419
|
// no aggregate at all — deleting coverage that nothing replaced.
|
|
397
420
|
const { removed, retained } = reconcileOrphans({
|
|
398
421
|
scopeDir,
|
|
399
|
-
keepIds:
|
|
422
|
+
keepIds: agentRecords.map(({ id }) => id),
|
|
400
423
|
ownerIds: manifestSessionIds(stateDir, readFile),
|
|
401
424
|
sessionId: session,
|
|
402
425
|
readDir,
|
|
@@ -46,6 +46,8 @@ import { dirname, join, isAbsolute } from 'node:path';
|
|
|
46
46
|
import { fileURLToPath } from 'node:url';
|
|
47
47
|
|
|
48
48
|
import { isWaveAgentContext, WAVE_AGENT_ENV_VAR, WAVE_AGENT_ENV_VALUE } from './lib/wave-context.mjs';
|
|
49
|
+
import { readProcessLocalSessionIds, manifestSessionBinding } from './lib/session-identity/own-session.mjs';
|
|
50
|
+
import { readLockDetailed } from './lib/session-lock.mjs';
|
|
49
51
|
|
|
50
52
|
// ---------------------------------------------------------------------------
|
|
51
53
|
// Constants & defaults
|
|
@@ -259,11 +261,14 @@ const confidence = confidenceVal;
|
|
|
259
261
|
|
|
260
262
|
let frontmatter = {};
|
|
261
263
|
let stateMdReadOk = false;
|
|
264
|
+
/** Directory holding STATE.md — the same state-dir the wave manifest lives in (#1166). */
|
|
265
|
+
let stateDir = null;
|
|
262
266
|
|
|
263
267
|
try {
|
|
264
268
|
// resolveStateMdPath from state-md.mjs: falls back to .claude/STATE.md
|
|
265
269
|
const stateMdMod = await import('./lib/state-md.mjs');
|
|
266
270
|
const stateMdPath = stateMdMod.resolveStateMdPath(process.cwd());
|
|
271
|
+
stateDir = dirname(stateMdPath);
|
|
267
272
|
|
|
268
273
|
if (!existsSync(stateMdPath)) {
|
|
269
274
|
if (!dryRun) {
|
|
@@ -352,17 +357,150 @@ if (!dryRun && (currentWaveRaw === undefined || currentWaveRaw === null || curre
|
|
|
352
357
|
}
|
|
353
358
|
|
|
354
359
|
// ---------------------------------------------------------------------------
|
|
355
|
-
// Step 3 — Build wave ID
|
|
356
|
-
// when !dryRun; falls back to a 'W-dryrun' placeholder under --dry-run when
|
|
357
|
-
// STATE.md was absent/unparseable/missing the field — #741.3)
|
|
360
|
+
// Step 3 — Build the RUNNING wave ID (#1166)
|
|
358
361
|
// ---------------------------------------------------------------------------
|
|
362
|
+
//
|
|
363
|
+
// #1166 — two writers, two semantics: `skills/wave-executor/wave-loop.md` § 3a
|
|
364
|
+
// writes STATE.md `current-wave` = the JUST-COMPLETED wave, while the
|
|
365
|
+
// coordinator's `<state-dir>/wave-scope.json` carries `wave` = the RUNNING
|
|
366
|
+
// wave. Stamping proposals off `current-wave` verbatim filed every wave-N+1
|
|
367
|
+
// proposal into the W<N> quota bucket. Priority: the manifest ONLY when it is
|
|
368
|
+
// bound to THIS session, else `current-wave + 1`. An UNBOUND manifest is not
|
|
369
|
+
// trusted (#1177 FX1) — since #1123 both writers stamp the binding, so a
|
|
370
|
+
// binding-less manifest is a peer's or a stale artefact.
|
|
371
|
+
//
|
|
372
|
+
// The session binding compares the manifest's `semantic_session_id` — the SEMANTIC label
|
|
373
|
+
// — against the semantic id looked up in `session.lock`, and that lookup is only
|
|
374
|
+
// honoured when this PROCESS's own raw session id equals the lock's raw
|
|
375
|
+
// `session_id` (#1188). STATE.md's `session` field is NOT a witness here: it is
|
|
376
|
+
// written by the lock owner, so under a peer's lock both "sides" name the peer.
|
|
377
|
+
|
|
378
|
+
/** @returns {string|undefined} */
|
|
379
|
+
function resolveRunningWaveId() {
|
|
380
|
+
if (stateDir !== null) {
|
|
381
|
+
try {
|
|
382
|
+
const manifestPath = join(stateDir, 'wave-scope.json');
|
|
383
|
+
if (existsSync(manifestPath)) {
|
|
384
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
385
|
+
// #1153 P2 — key spellings resolved in ONE place (own-session.mjs),
|
|
386
|
+
// which also reads the pre-#1153 `session` / `semantic_session` names
|
|
387
|
+
// for one transition release. Absent slots come back as `undefined`.
|
|
388
|
+
const binding = manifestSessionBinding(manifest);
|
|
389
|
+
const boundSession = binding.semantic_session_id ?? binding.session_id;
|
|
390
|
+
const unbound =
|
|
391
|
+
boundSession === undefined || boundSession === null || boundSession === '';
|
|
392
|
+
// BOTH sides must be non-empty strings. `undefined === undefined` is
|
|
393
|
+
// true, so a STATE.md WITHOUT a `session` key previously "matched" any
|
|
394
|
+
// manifest whose `semantic_session` was also absent-but-present-in-the
|
|
395
|
+
// -comparison — adopting a foreign coordinator's wave number as its own.
|
|
396
|
+
const mineSide = binding.semantic_session_id;
|
|
397
|
+
// #1188 — STATE.md is a working-copy artefact of the LOCK OWNER, not a
|
|
398
|
+
// process-local witness: when a peer holds the lock the peer wrote BOTH
|
|
399
|
+
// STATE.md and the manifest, so the two "independent" sides agree about
|
|
400
|
+
// the PEER. Same class as #1177 FX1; same shape as attributionForRecord
|
|
401
|
+
// (scripts/lib/events.mjs): the lock is a raw->semantic LOOKUP,
|
|
402
|
+
// authorised by a process-local match on the RAW id. Measured
|
|
403
|
+
// 2026-09-02: a wave-agent process carries the COORDINATOR's raw uuid in
|
|
404
|
+
// CLAUDE_CODE_SESSION_ID and no semantic id at all, so comparing the
|
|
405
|
+
// process-local id against `semantic_session` directly never matches.
|
|
406
|
+
// CEILING (BV-004): a harness exporting no session id (Codex CLI,
|
|
407
|
+
// Cursor) resolves no process-local id -> binding unprovable ->
|
|
408
|
+
// current-wave + 1 fallback with a stderr line. REVISIT if that fallback
|
|
409
|
+
// rate is measured non-trivial in events.jsonl.
|
|
410
|
+
//
|
|
411
|
+
// #1207 — kept hand-rolled rather than calling `attributionForRecord()`
|
|
412
|
+
// (scripts/lib/events.mjs): that function's contract is `{}`-on-any-
|
|
413
|
+
// mismatch with no detail, but the stderr diagnostics below (raw id,
|
|
414
|
+
// lock status, process-local id list) are load-bearing for debugging a
|
|
415
|
+
// wrongly-bucketed proposal, and attributionForRecord() has no lock-
|
|
416
|
+
// status channel to source them from (it swallows `readLock()` errors
|
|
417
|
+
// silently by design). `readLockDetailed()` is used here specifically
|
|
418
|
+
// for that `.status` field `readLock()` (attributionForRecord's own
|
|
419
|
+
// primitive) does not return.
|
|
420
|
+
const lock = readLockDetailed({ repoRoot: process.cwd() });
|
|
421
|
+
const processLocal = readProcessLocalSessionIds();
|
|
422
|
+
const lockRaw =
|
|
423
|
+
lock.status === 'ok' ? String(lock.lock.session_id ?? '').trim() : '';
|
|
424
|
+
const authorised = lockRaw !== '' && processLocal.includes(lockRaw);
|
|
425
|
+
const oursSide = authorised ? (lock.lock.semantic_session_id ?? '') : '';
|
|
426
|
+
if (!authorised) {
|
|
427
|
+
// A SILENT non-match is the exact failure mode this fix exists to make
|
|
428
|
+
// visible: without this line, a proposal bucketed by the fallback is
|
|
429
|
+
// indistinguishable from one bucketed by a trusted manifest.
|
|
430
|
+
process.stderr.write(
|
|
431
|
+
`⚠ memory-propose: session binding unprovable (session.lock ${lock.status}` +
|
|
432
|
+
`${lockRaw ? `, raw ${lockRaw}` : ''}; process-local ids: ` +
|
|
433
|
+
`${processLocal.length > 0 ? processLocal.join(', ') : 'none'}) — ` +
|
|
434
|
+
'ignoring wave-scope.json, falling back to current-wave + 1\n',
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const mine =
|
|
438
|
+
typeof mineSide === 'string' && mineSide.length > 0 &&
|
|
439
|
+
typeof oursSide === 'string' && oursSide.length > 0 &&
|
|
440
|
+
mineSide === oursSide;
|
|
441
|
+
// An UNBOUND manifest is NOT trusted (#1177 FX1). It used to be, as
|
|
442
|
+
// "legacy" — but since #1123 BOTH writers stamp the binding, so a
|
|
443
|
+
// manifest without one today is a PEER's or a stale artefact, and
|
|
444
|
+
// trusting it files this session's proposals into a foreign quota
|
|
445
|
+
// bucket. Unbound → say so once on stderr and fall back.
|
|
446
|
+
if (unbound) {
|
|
447
|
+
process.stderr.write(
|
|
448
|
+
'memory-propose: wave-scope.json unbound (no semantic_session_id) — ignored\n',
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (mine) {
|
|
452
|
+
const raw = manifest?.wave;
|
|
453
|
+
const num =
|
|
454
|
+
typeof raw === 'number'
|
|
455
|
+
? raw
|
|
456
|
+
: typeof raw === 'string' && raw.trim() !== ''
|
|
457
|
+
? Number(raw)
|
|
458
|
+
: NaN;
|
|
459
|
+
if (Number.isInteger(num)) return `W${num}`;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch (err) {
|
|
463
|
+
// Unreadable / malformed manifest → fall through to the +1 fallback, but
|
|
464
|
+
// SAY SO. A swallowed parse error makes a wrongly-bucketed proposal look
|
|
465
|
+
// like ordinary fallback behaviour; the operator then has no signal that
|
|
466
|
+
// the coordinator's own manifest is corrupt. Diagnostics on stderr keeps
|
|
467
|
+
// the stdout JSON contract intact (cli-design.md § JSON-First Output).
|
|
468
|
+
process.stderr.write(
|
|
469
|
+
`⚠ memory-propose: cannot read ${join(stateDir, 'wave-scope.json')} ` +
|
|
470
|
+
`(${err?.message ?? String(err)}) — falling back to current-wave + 1\n`,
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (currentWaveRaw === undefined || currentWaveRaw === null || currentWaveRaw === '') {
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
const completed = Number(currentWaveRaw);
|
|
479
|
+
if (!Number.isFinite(completed)) return undefined; // never 'WNaN'
|
|
480
|
+
// CEILING (BV-004): `+1` assumes waves run consecutively and that STATE.md is
|
|
481
|
+
// maintained — it is WRONG when a wave is skipped, when the plan is adapted
|
|
482
|
+
// mid-session, or under `persistence: false` where `current-wave` never
|
|
483
|
+
// advances. It is the fallback precisely because wave-scope.json is the only
|
|
484
|
+
// artefact that KNOWS the running wave.
|
|
485
|
+
// REVISIT when a wave-scope.json manifest is present in every dispatch (then
|
|
486
|
+
// delete the fallback and reject instead), or when a proposal is observed in
|
|
487
|
+
// the wrong quota bucket while the manifest was readable.
|
|
488
|
+
return `W${completed + 1}`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const resolvedWaveId = resolveRunningWaveId();
|
|
359
492
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
493
|
+
if (!dryRun && resolvedWaveId === undefined) {
|
|
494
|
+
exit(
|
|
495
|
+
{
|
|
496
|
+
status: STATUS.REJECTED_WRONG_CONTEXT,
|
|
497
|
+
detail: `Cannot resolve the running wave: no usable wave-scope.json and STATE.md 'current-wave' is not numeric (got '${currentWaveRaw}')`,
|
|
498
|
+
},
|
|
499
|
+
3,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
364
502
|
|
|
365
|
-
const waveId = dryRun ? (
|
|
503
|
+
const waveId = dryRun ? (resolvedWaveId ?? 'W-dryrun') : resolvedWaveId;
|
|
366
504
|
|
|
367
505
|
// ---------------------------------------------------------------------------
|
|
368
506
|
// Step 4 — Read Session Config (quota + floor)
|
|
@@ -143,7 +143,10 @@ if (applyFlag && dryRunFlag) {
|
|
|
143
143
|
process.exit(1);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
// Default is dry-run
|
|
146
|
+
// Default is dry-run: a migration script writes only when `--apply` is passed, so an
|
|
147
|
+
// invocation with no flags can never mutate a store. Repo-wide convention for every
|
|
148
|
+
// `scripts/migrate-*` / `scripts/backfill-*` CLI; live example:
|
|
149
|
+
// `scripts/migrate-sessions-jsonl.mjs` (`args.apply = false` default, `--apply` opts in).
|
|
147
150
|
const dryRun = !applyFlag;
|
|
148
151
|
|
|
149
152
|
// `--repos` is kept as the raw comma-separated string here (split later, at
|
package/scripts/parse-config.mjs
CHANGED
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
* parse-config.mjs — Parse ## Session Config from CLAUDE.md or AGENTS.md and output validated JSON.
|
|
4
4
|
* Part of Session Orchestrator v3.0.0 (originally parse-config.sh in v2).
|
|
5
5
|
*
|
|
6
|
-
* Usage: node scripts/parse-config.mjs [path/to/CLAUDE.md|AGENTS.md]
|
|
6
|
+
* Usage: node scripts/parse-config.mjs [--json] [path/to/CLAUDE.md|AGENTS.md]
|
|
7
7
|
* If no path given, walks up from cwd to find project root and uses its CLAUDE.md (or AGENTS.md).
|
|
8
8
|
*
|
|
9
9
|
* Output: Single JSON object to stdout with ALL config fields (defaults applied).
|
|
10
|
+
* `--json` is an explicit NO-OP alias for that default — accepted because
|
|
11
|
+
* `docs/session-config-template.md` (lines 13 and 965) documents
|
|
12
|
+
* `node scripts/parse-config.mjs --json` as THE validation command, and the
|
|
13
|
+
* flag used to be swallowed by the positional-path slot and rejected with
|
|
14
|
+
* `File not found: --json` (exit 1). `cli-design.md` § JSON-First Output also
|
|
15
|
+
* requires every CLI to accept `--json`.
|
|
10
16
|
* Exit codes: 0 success, 1 error (message to stderr)
|
|
11
17
|
*
|
|
12
18
|
* Unparsable lines inside `## Session Config` (#1097) are reported per line on
|
|
@@ -25,6 +31,7 @@ import { join, dirname, resolve, parse as parsePath } from 'node:path';
|
|
|
25
31
|
import { fileURLToPath } from 'node:url';
|
|
26
32
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
27
33
|
import { collectUnparsableLines } from './lib/config/section-extractor.mjs';
|
|
34
|
+
import { findUnterminatedComment } from './lib/config/block-preprocess.mjs';
|
|
28
35
|
import { ENFORCEMENT_VALUES } from './lib/config-schema.mjs';
|
|
29
36
|
|
|
30
37
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
@@ -93,10 +100,46 @@ function resolveConfigFile(argPath) {
|
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
// ---------------------------------------------------------------------------
|
|
96
|
-
// Main
|
|
103
|
+
// Main — argument parsing
|
|
97
104
|
// ---------------------------------------------------------------------------
|
|
105
|
+
//
|
|
106
|
+
// Exactly two recognised flags plus one optional positional path. `--json` is
|
|
107
|
+
// accepted and IGNORED: stdout is already a single JSON object, and the
|
|
108
|
+
// documented validation command passes the flag. Anything else starting with
|
|
109
|
+
// `-` is rejected rather than silently treated as a filename, so a typo cannot
|
|
110
|
+
// masquerade as a missing file.
|
|
111
|
+
|
|
112
|
+
const HELP_TEXT = `Usage: parse-config.mjs [--json] [path/to/CLAUDE.md|AGENTS.md]
|
|
113
|
+
|
|
114
|
+
Parse the \`## Session Config\` block from CLAUDE.md (or AGENTS.md) and write
|
|
115
|
+
the validated config to stdout as a single JSON object.
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
--json No-op alias for the default output (stdout is always JSON).
|
|
119
|
+
--help Show this help and exit 0.
|
|
120
|
+
|
|
121
|
+
Without a path, walks up from cwd to the project root and uses its CLAUDE.md,
|
|
122
|
+
falling back to AGENTS.md. Exit codes: 0 success, 1 error (message on stderr).
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
let argPath = null;
|
|
126
|
+
for (const arg of process.argv.slice(2)) {
|
|
127
|
+
if (arg === '--help' || arg === '-h') {
|
|
128
|
+
process.stdout.write(HELP_TEXT);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
} else if (arg === '--json') {
|
|
131
|
+
// no-op — the default output already IS JSON
|
|
132
|
+
} else if (arg.startsWith('-')) {
|
|
133
|
+
process.stderr.write(`parse-config.mjs: Unknown option: ${arg}\n\n${HELP_TEXT}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
} else if (argPath === null) {
|
|
136
|
+
argPath = arg;
|
|
137
|
+
} else {
|
|
138
|
+
process.stderr.write(`parse-config.mjs: Unexpected extra argument: ${arg}\n\n${HELP_TEXT}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
98
142
|
|
|
99
|
-
const argPath = process.argv[2] ?? null;
|
|
100
143
|
const configFile = resolveConfigFile(argPath);
|
|
101
144
|
|
|
102
145
|
let content;
|
|
@@ -107,6 +150,20 @@ try {
|
|
|
107
150
|
process.exit(1);
|
|
108
151
|
}
|
|
109
152
|
|
|
153
|
+
// Unterminated-comment gate — ONE line per session, not one per parser.
|
|
154
|
+
// An `<!--` with no `-->` after it would put the shared HTML-comment skipper in
|
|
155
|
+
// the swallowing state for the rest of the document; `stripHtmlCommentBlocks`
|
|
156
|
+
// now fails CLOSED (returns the lines unfiltered) rather than letting every
|
|
157
|
+
// later block disappear into its defaults, but the operator still has to learn
|
|
158
|
+
// that the document has a defect. This is the surface that says so — never
|
|
159
|
+
// fatal, exit code unchanged.
|
|
160
|
+
const unterminatedCommentLine = findUnterminatedComment(content.split(/\r?\n/));
|
|
161
|
+
if (unterminatedCommentLine !== null) {
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
`⚠ ${parsePath(configFile).base}: unterminated <!-- at line ${unterminatedCommentLine} — comment stripping disabled for the whole document\n`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
110
167
|
let config;
|
|
111
168
|
try {
|
|
112
169
|
config = parseSessionConfig(content);
|
|
@@ -23,6 +23,7 @@ import { homedir } from 'node:os';
|
|
|
23
23
|
import { spawnSync } from 'node:child_process';
|
|
24
24
|
import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-repo.mjs';
|
|
25
25
|
import { validatePathInsideProject } from './lib/path-utils.mjs';
|
|
26
|
+
import { expandTilde } from './lib/common.mjs';
|
|
26
27
|
|
|
27
28
|
const COMMIT_MSG = 'chore(orchestrator): Promote vault-integration to strict mode — refs #305';
|
|
28
29
|
|
|
@@ -53,18 +54,6 @@ const noBaseline = args.includes('--no-baseline');
|
|
|
53
54
|
const repoIdx = args.indexOf('--repo');
|
|
54
55
|
const singleRepo = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : null;
|
|
55
56
|
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// Path resolution
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
/** Expand leading ~ to the user's home directory. */
|
|
61
|
-
function expandHome(p) {
|
|
62
|
-
if (p.startsWith('~/')) {
|
|
63
|
-
return join(homedir(), p.slice(2));
|
|
64
|
-
}
|
|
65
|
-
return p;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
57
|
// ---------------------------------------------------------------------------
|
|
69
58
|
// Vault-integration mode replacement
|
|
70
59
|
// ---------------------------------------------------------------------------
|
|
@@ -424,7 +413,7 @@ async function main() {
|
|
|
424
413
|
|
|
425
414
|
if (singleRepo) {
|
|
426
415
|
// Single-repo mode
|
|
427
|
-
const absRepo =
|
|
416
|
+
const absRepo = expandTilde(singleRepo);
|
|
428
417
|
const singleRoot = getConfinementRoot();
|
|
429
418
|
const singleGuard = validatePathInsideProject(absRepo, singleRoot);
|
|
430
419
|
if (!singleGuard.ok) {
|
|
@@ -449,7 +438,7 @@ async function main() {
|
|
|
449
438
|
|
|
450
439
|
const batchRoot = getConfinementRoot();
|
|
451
440
|
for (const rawPath of eligibleRepos) {
|
|
452
|
-
const absRepo =
|
|
441
|
+
const absRepo = expandTilde(rawPath);
|
|
453
442
|
const guard = validatePathInsideProject(absRepo, batchRoot);
|
|
454
443
|
if (!guard.ok) {
|
|
455
444
|
process.stderr.write(
|
|
@@ -469,7 +458,7 @@ async function main() {
|
|
|
469
458
|
(p) => p === 'projects-baseline' || p.endsWith('/projects-baseline')
|
|
470
459
|
);
|
|
471
460
|
if (baselineEntry) {
|
|
472
|
-
const baselineRepoDir =
|
|
461
|
+
const baselineRepoDir = expandTilde(
|
|
473
462
|
baselineEntry.startsWith('~') || baselineEntry.startsWith('/')
|
|
474
463
|
? baselineEntry
|
|
475
464
|
: `~/Projects/${baselineEntry}`
|