session-orchestrator 3.24.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 +1 -1
- 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 +1125 -2
- package/NOTICE +11 -6
- package/README.md +127 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +65 -21
- package/docs/components.md +36 -15
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- 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 +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1487 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +36 -11
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/plugin.json +27 -0
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-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/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +82 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +26 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +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/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 +13 -4
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +36 -2
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +18 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +181 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- 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-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 +47 -28
- package/scripts/lib/validate/check-unwired-features.mjs +0 -2
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -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/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +337 -29
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- 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 +161 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +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 +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -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)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Single source of truth for the parallel-session-detection preamble.
|
|
4
4
|
> Referenced by: autopilot, session-start, session-plan, wave-executor, session-end (5 orchestrator entry-points).
|
|
5
|
+
> Design history: the 2026-05-26 parallel-aware-sessions spec is superseded on lock liveness (heartbeat, not PID) — see `docs/README.md` § Superseded design notes.
|
|
5
6
|
|
|
6
7
|
## Purpose
|
|
7
8
|
|
|
@@ -70,7 +70,7 @@ Lifecycle: `spawn_agent` (launch, one per wave task) → `list_agents` (enumerat
|
|
|
70
70
|
- **session-reviewer** — quality review; spawn as a separate agent rather than reviewing in the main session
|
|
71
71
|
|
|
72
72
|
**On Cursor IDE:**
|
|
73
|
-
No native Agent() tool or typed agent roles inside the Composer session — wave tasks run sequentially there, and `agents-per-wave` is ignored. This is a statement about the IDE session only: coordinator-side foreign dispatch to Cursor models via the headless `cursor-agent` CLI does exist (`scripts/lib/wave-executor/foreign-dispatch.mjs`, #1150 — detached worktree, filesystem-measured result, `orchestrator.foreign_dispatch.completed` telemetry, mandatory Claude review, `NEVER_FOREIGN_ROLES` lock). Measured 2026-08-25 on `cursor-agent 2026.08.11-e8db854`. Slash commands live in `.cursor/commands/`. There is no Skill tool — Read `skills/<name>/SKILL.md` instead.
|
|
73
|
+
No native Agent() tool or typed agent roles inside the Composer session — wave tasks run sequentially there, and `agents-per-wave` is ignored. This is a statement about the IDE session only: coordinator-side foreign dispatch to Cursor models via the headless `cursor-agent` CLI does exist (`scripts/lib/wave-executor/foreign-dispatch.mjs`, #1150 — detached worktree, filesystem-measured result, `orchestrator.foreign_dispatch.completed` telemetry, mandatory Claude review, `NEVER_FOREIGN_ROLES` lock in `scripts/lib/wave-executor/dispatch-common.mjs`). Measured 2026-08-25 on `cursor-agent 2026.08.11-e8db854`. Slash commands live in `.cursor/commands/`. There is no Skill tool — Read `skills/<name>/SKILL.md` instead.
|
|
74
74
|
|
|
75
75
|
**On Pi:**
|
|
76
76
|
No Session Orchestrator Pi subagent dispatcher exists in v1. Execute wave tasks sequentially in the active Pi session. Treat `agents-per-wave` as advisory until the SDK-based dispatcher lands.
|
|
@@ -147,7 +147,7 @@ This is the wave-plan-time analog of PSA-007 (subagents never race the shared gi
|
|
|
147
147
|
|
|
148
148
|
The rule above deconflicts writers *inside one wave*. The same working copy is also shared across SESSIONS, and `<state-dir>/wave-scope.json` is the one control artefact that constrains writes rather than describing them. It lives in the working copy, not in the session — so before #1123 a manifest written by session A governed session B's every Edit. Measured 2026-08-22 (#1082): a Discovery wave's `allowedPaths: []` — prescribed for every Discovery wave — denied all writes of an unrelated parallel session, with a deny reason that could only tell it to fix a wave plan it does not own.
|
|
149
149
|
|
|
150
|
-
**The manifest is SESSION-BOUND since #1123.** The coordinator that writes it names itself in two optional fields, `
|
|
150
|
+
**The manifest is SESSION-BOUND since #1123.** The coordinator that writes it names itself in two optional fields, `session_id` (the raw harness session id) and `semantic_session_id` (renamed from `session` / `semantic_session` in #1153 P2 to match `session.lock` and `current-session.json`; the legacy pair is still READ until the next minor release), both from ONE `sessionAttribution(repoRoot)` call (`scripts/lib/events.mjs`) — see `skills/wave-executor/wave-loop.md` § Scope Manifest 1. `hooks/enforce-scope.mjs` Gate 3b classifies the manifest with `readProcessLocalSessionIds()` + `classifyManifestSession()` (`scripts/lib/session-identity/own-session.mjs`) — process-local tiers only (hook payload, `CLAUDE_CODE_SESSION_ID`), never the repo-global `session.lock`, which is shared by every session in the checkout and made a peer's manifest read `own` (#1194):
|
|
151
151
|
|
|
152
152
|
- **`foreign`** (ids present, none of them ours) → the gate ALLOWS the write and emits one `orchestrator.scope.foreign_session_ignored` event. A foreign manifest is somebody else's wave plan; it never had authority here, and the event keeps the skip counted rather than silent.
|
|
153
153
|
- **`own`** → enforce, unchanged.
|
|
@@ -30,7 +30,9 @@ Key principles (see [LANGUAGE.md](LANGUAGE.md) for the full list):
|
|
|
30
30
|
- **The interface is the test surface.**
|
|
31
31
|
- **One adapter = hypothetical seam. Two adapters = real seam.**
|
|
32
32
|
|
|
33
|
-
This skill is _informed_ by the project's domain model — `CONTEXT.md` and any `docs/adr/`. The domain language gives names to good seams; ADRs record decisions the skill should not re-litigate. See [CONTEXT-FORMAT.md](
|
|
33
|
+
This skill is _informed_ by the project's domain model — `CONTEXT.md` and any `docs/adr/`. The domain language gives names to good seams; ADRs record decisions the skill should not re-litigate. See [references/CONTEXT-FORMAT.md](./references/CONTEXT-FORMAT.md) and [references/ADR-FORMAT.md](./references/ADR-FORMAT.md).
|
|
34
|
+
|
|
35
|
+
**When to load [references/domain-model.md](./references/domain-model.md):** before this skill's first pass on a repo with no `CONTEXT.md` yet, or one too thin to name the seams below — run its grilling interview to establish the domain vocabulary first. Skip it once `CONTEXT.md` already reflects the domain.
|
|
34
36
|
|
|
35
37
|
## Process
|
|
36
38
|
|
|
@@ -45,7 +47,7 @@ If any of these files don't exist, proceed silently — don't flag their absence
|
|
|
45
47
|
|
|
46
48
|
**Optional pre-pass — export inventory injection (mapper-supported projects only):**
|
|
47
49
|
|
|
48
|
-
Before dispatching the Explore subagent,
|
|
50
|
+
Before dispatching the Explore subagent, read each known entry-point file (e.g. `index.ts`, `src/index.ts`, the main export barrel) and pass its contents to `extractSemanticSlices(filePath, content)` from `scripts/lib/language-mappers/index.mjs`. The signature is `(filePath, content, options?)` — the second argument is the raw file text, **not** an options object, and there is no slice-kind filter parameter. It resolves to an array of `SemanticSlice` records (`{ kind, name, exported, … }`); keep the ones with `exported === true`, format them as structured context, and inject that into the Explore subagent prompt. This gives the subagent an immediate map of the codebase's public surface without requiring it to grep manually.
|
|
49
51
|
|
|
50
52
|
```
|
|
51
53
|
## Export inventory (auto-generated)
|
|
@@ -53,7 +55,7 @@ Before dispatching the Explore subagent, call `extractSemanticSlices(filePath, {
|
|
|
53
55
|
- src/lib/bar.ts: exports `processBar`
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
This pre-pass is OPTIONAL — only activate when the entry-point file is mapper-supported (
|
|
58
|
+
This pre-pass is OPTIONAL — only activate when the entry-point file is mapper-supported (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.md`, `.mdx`, `.swift`, `.py` — the `EXT_TO_LANG` map in `index.mjs` is the source of truth). For any other extension `extractSemanticSlices` **throws** rather than returning an empty array, so wrap the call in a try/catch and skip the pre-pass silently on failure. Never block Explore dispatch on a mapper error.
|
|
57
59
|
|
|
58
60
|
Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction:
|
|
59
61
|
|
|
@@ -86,7 +88,7 @@ Once the user picks a candidate, drop into a grilling conversation. Walk the des
|
|
|
86
88
|
|
|
87
89
|
Side effects happen inline as decisions crystallize:
|
|
88
90
|
|
|
89
|
-
- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as
|
|
91
|
+
- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as the domain-model grilling reference (see [references/CONTEXT-FORMAT.md](./references/CONTEXT-FORMAT.md)). Create the file lazily if it doesn't exist.
|
|
90
92
|
- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there.
|
|
91
|
-
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [ADR-FORMAT.md](
|
|
93
|
+
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [references/ADR-FORMAT.md](./references/ADR-FORMAT.md).
|
|
92
94
|
- **Want to explore alternative interfaces for the deepened module?** See [INTERFACE-DESIGN.md](INTERFACE-DESIGN.md).
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
model:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
# Domain-Model Grilling (reference)
|
|
2
|
+
|
|
3
|
+
> Reference material for the `architecture` skill — **not a skill of its own.**
|
|
4
|
+
> Merged here in v4.0.0 from the former `skills/domain-model/` (retired: zero
|
|
5
|
+
> telemetry, zero fleet invocations, reference-only usage — see
|
|
6
|
+
> `docs/audits/2026-09-06-360-audit.md` § 5A). Invoke it by reading this file
|
|
7
|
+
> from `/architecture`, not via a slash command.
|
|
8
|
+
>
|
|
9
|
+
> Adapted from [`mattpocock/skills@90ea8ee`](https://github.com/mattpocock/skills/tree/main/domain-model), MIT — Copyright (c) 2026 Matt Pocock. See the repo-root `NOTICE`.
|
|
10
10
|
|
|
11
11
|
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer.
|
|
12
12
|
|
|
@@ -377,24 +377,10 @@ identically by readers per the v1 schema additive convention).
|
|
|
377
377
|
|
|
378
378
|
## Configuration
|
|
379
379
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
bg-isolation: worktree # worktree | none (default: worktree) — see #431
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### bg-isolation
|
|
388
|
-
|
|
389
|
-
**Type:** `worktree` | `none` — **Default:** `worktree`
|
|
390
|
-
|
|
391
|
-
Controls whether `autopilot --multi-story` creates a per-story git worktree before spawning sub-sessions.
|
|
392
|
-
|
|
393
|
-
`worktree` (default): Each story pipeline receives its own isolated git worktree via `EnterWorktree`. Parallel writes are safe because every agent edits a private working copy. Cost: disk space proportional to the number of concurrent stories plus the latency of worktree creation at story-start.
|
|
394
|
-
|
|
395
|
-
`none` (opt-in): No worktrees are created. Sub-sessions spawn directly in the main working tree. Useful for monorepos where worktree creation is impractical due to large `node_modules`, sparse-checkout setups, or build caches that must be shared. **Requires file-scope discipline:** when `max-stories > 1`, every story must edit a disjoint set of files. If two stories touch the same file simultaneously, edits will collide silently. To enforce acknowledgement of this discipline, `autopilot-multi` requires `--deconflict-paths=<glob>` whenever `bg-isolation: none` AND `max-stories > 1`; omitting the flag is a hard error (exit 1). See `.claude/rules/parallel-sessions.md` PSA-001/002/003.
|
|
396
|
-
|
|
397
|
-
**Operator-awareness note:** CC 2.1.133 silently flipped `worktree.baseRef` default from `head` to `origin/<default>`, breaking users who relied on unpushed commits being included in their worktree base. The same class of upstream change can affect `bg-isolation` semantics in a future CC release. Treat CC changelog entries related to worktree or `--bg` session behaviour as requiring a re-read of this section before upgrading.
|
|
380
|
+
Single-story `/autopilot` takes no Session Config block. Multi-story
|
|
381
|
+
(`autopilot.bg-isolation`, `--multi-story`, `--deconflict-paths`) was removed in
|
|
382
|
+
4.0.0 — see `docs/migration-v4.md` and `docs/session-config-reference.md` §
|
|
383
|
+
"Autopilot Multi-Story (#431) — removed".
|
|
398
384
|
|
|
399
385
|
## References
|
|
400
386
|
|
|
@@ -39,7 +39,7 @@ PHASE 1 IMPLEMENTED (2026-04-19). Session-end opt-in quality gate. Upstream of `
|
|
|
39
39
|
| 4 | `session-file-existence` | `50-sessions/YYYY-MM-DD-*.md` references anywhere in scope | `existsSync(vault/50-sessions/<file>)` |
|
|
40
40
|
| 5 | `command-count` | "N commands" / "N /commands" claims in prose | compare to `ls commands/*.md \| wc -l`; skipped if no `commands/` dir |
|
|
41
41
|
| 6 | `session-config-parity` | Top-level keys under `## Session Config` in `CLAUDE.md` / `AGENTS.md` | diff against `docs/session-config-template.md`; a missing MANDATORY (minimal-baseline) key is an error, a missing OPT-IN-baseline-only key is a warning |
|
|
42
|
-
| 7 | `vault-dir-parity` | `vault-integration.vault-dir` in BOTH `CLAUDE.md` AND `AGENTS.md` | reuse `_parseVaultIntegration`; flag when the two files disagree |
|
|
42
|
+
| 7 | `vault-dir-parity` | `vault-integration.vault-dir` in BOTH `CLAUDE.md` AND `AGENTS.md` | reuse `_parseVaultIntegration`; flag when the two files disagree. Short-circuits to PASS when the two are the SAME file by construction (symlink / same inode / byte-identical generated copy) |
|
|
43
43
|
| 8 | `generated-rule-staleness` *(WARN only)* | `.claude/rules/*.md` with `auto-generated: true` frontmatter | extract `learning-key`; WARN when the key is absent from `.orchestrator/metrics/learnings.jsonl` or its learning's `expires_at` is in the past; skipped silently when no auto-generated rules exist |
|
|
44
44
|
| 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | five probes: `paths:` frontmatter (error), cited-but-missing rule citations (error), zero-match `globs:` patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped); skipped silently when `.claude/rules/` is absent |
|
|
45
45
|
| 10 | `docs-parity` | `docs/components.md` count-claims, Session Config key parity (`docs/session-config-template.md` vs `docs/session-config-reference.md`), `.claude/metrics/` stale-path references in `docs/*.md` / `docs/examples/*.md` | three sub-checks (a/b/c) — see below; skipped silently when `docs/components.md` is absent |
|
|
@@ -61,6 +61,10 @@ A local CLAUDE.md / AGENTS.md that omits any of these now surfaces a `session-co
|
|
|
61
61
|
|
|
62
62
|
Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruction files rather than the single alias-resolved one. The alias rule (CLAUDE.md wins ties, AGENTS.md is the Codex alias) means `resolveInstructionFile()` picks exactly one — so a repo carrying both files can silently let `AGENTS.md` drift out of sync with `CLAUDE.md`. A sibling project ran for weeks with a correct `vault-integration.vault-dir` in `CLAUDE.md` and a dead path in `AGENTS.md`. Check 7 reads `vault-integration.vault-dir` from each file (reusing the `_parseVaultIntegration` parser from `scripts/lib/config/vault-integration.mjs` — no hand-rolled YAML) and flags a `vault-dir-parity` error when the two values diverge (the error is attributed to `AGENTS.md`, the secondary alias, and names both values). The check skips gracefully when only one instruction file is present (nothing to compare), when neither file declares a `vault-integration:` block, or when explicitly disabled via `--skip-vault-dir-parity`. Two files that both omit `vault-dir` (both unset) agree and pass.
|
|
63
63
|
|
|
64
|
+
**Alias-by-construction short-circuit.** Since this repo's root `AGENTS.md` became a GENERATED byte-identical copy of `CLAUDE.md` (`scripts/generate-agents-skills.mjs`), Check 7 no longer skips here — it RUNS on every invocation. Two files that cannot disagree have nothing to diff, so the check reports parity as satisfied without deriving one: `instructionFilesAreAliased()` recognises a symlink resolving to the same path, the same inode/device (hardlink or already-resolved symlink), and byte-identical content. Branch order is load-bearing: the "neither file has a `vault-integration:` block" skip is evaluated FIRST, because an absent subject is a different (and more informative) outcome than "the files cannot disagree". The error class the check exists for is untouched — two INDEPENDENT files that declare different `vault-dir` values still produce the `vault-dir-parity` error attributed to `AGENTS.md`.
|
|
65
|
+
|
|
66
|
+
The same predicate guards Check 9's probe 2a, which iterates `['CLAUDE.md', 'AGENTS.md']`: with an aliased `AGENTS.md` it scans `CLAUDE.md` only, because otherwise every dangling `.claude/rules/*.md` citation would be reported TWICE — one defect, two errors, and in `--mode strict` a doubled blocking surface against a file the operator is not allowed to edit.
|
|
67
|
+
|
|
64
68
|
Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Four probes: **(1) paths-presence** — a top-level `paths:` frontmatter key is not a key `rule-loader.mjs` recognises (it only reads `globs:`), so a rule with `paths:` silently loads ALWAYS-ON regardless of intended file scope; flagged as an error. **(2) cited-but-missing** — `(a)` `.claude/rules/<name>.md` citations inside `CLAUDE.md`/`AGENTS.md` that don't resolve to a file on disk, and `(b)` bare `<name>.md` tokens in a rule's own `## See Also` footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. `../../skills/_shared/state-ownership.md`, are cross-directory references and explicitly out of scope); both flagged as errors. **(3) zero-match-globs** — a `globs:` pattern matching zero files in `git ls-files` (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules (this repo alone carries ~37 by design — Swift/Next.js/Supabase rules with no matching files in a pure-Node-ESM codebase). **(4) foreign-glob** — a glob pattern containing a PascalCase product-like token (regex `[A-Z][a-z]+[A-Z]`, e.g. `WalkAITalkieTests`) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. Glob matching reuses the same picomatch-with-inline-fallback resolution `scripts/lib/rule-loader.mjs` uses (`parseGlobsFrontmatter` is imported directly; the picomatch resolution itself is duplicated locally since `rule-loader.mjs` does not export a public matcher function). The check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `.claude/rules/` is absent, or explicitly via `--skip-rule-scoping`.
|
|
65
69
|
|
|
66
70
|
Check 10 (`docs-parity`, issue #780) validates the public docs surface against live repository state via three sub-checks, all reported under the single `docs-parity` check id: **(a) count-claims** — `docs/components.md`'s own heading counts ("## Skills (N user-facing)", "## Commands (N)", "## Agents (N typed sub-agents)", "## Hook event types (N)") are compared against the SAME actual on-disk derivation the surface-count family (Check 5) uses (`countSkills`/the commands-dir listing/`countAgents`/`readHookCounts`) — NOT Check 5's `claimRe` regexes, which are tuned for CLAUDE.md/README prose phrasing and verifiably do not match `components.md`'s own heading convention; a dedicated set of regexes tailored to the doc's actual authored structure is used instead. **(b) config-block-parity** — top-level Session Config keys documented in `docs/session-config-template.md` (opt-in baseline, via the Check 6 'last'-occurrence extractor) are compared against `docs/session-config-reference.md`; a key is considered documented in the reference when it appears in a `yaml` fence (any indent), a `##`/`###`/`####` heading, or the first cell of a markdown table row (`` | `key-name` | ... ``) — the reference's dominant documentation shape is one table row per field, so a naive fence-only extraction under-counts what it actually documents. Missing keys surface as `docs-parity` errors attributed to `docs/session-config-reference.md`. **(c) metrics-path-liveness** — any `.claude/metrics/` path reference (stale; canonical is `.orchestrator/metrics/`) found in root `docs/*.md` or `docs/examples/*.md` surfaces as an error at its exact file/line. The whole check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `docs/components.md` is absent, or explicitly via `--skip-docs-parity`.
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* rule-loader.mjs uses); otherwise pure Node stdlib.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
31
|
+
import { readFileSync, readdirSync, existsSync, statSync, lstatSync, realpathSync } from 'node:fs';
|
|
32
32
|
import { join, relative, resolve } from 'node:path';
|
|
33
33
|
import { execFileSync } from 'node:child_process';
|
|
34
34
|
import { createRequire } from 'node:module';
|
|
@@ -294,6 +294,45 @@ function readVaultIntegration(filePath) {
|
|
|
294
294
|
return { present, vaultDir: parsed['vault-dir'] };
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Are the two instruction files the SAME file by construction?
|
|
299
|
+
*
|
|
300
|
+
* Since the cross-harness portable surface landed, this repo's root `AGENTS.md`
|
|
301
|
+
* is GENERATED from `CLAUDE.md` (`scripts/generate-agents-skills.mjs`) as a
|
|
302
|
+
* byte-identical copy, and a consumer repo may instead symlink it. Three shapes
|
|
303
|
+
* therefore count as aliased: a symlink resolving to the same path, the same
|
|
304
|
+
* inode/device (symlink already resolved, or a hardlink), and byte-identical
|
|
305
|
+
* content.
|
|
306
|
+
*
|
|
307
|
+
* This matters because Check 7 exists to catch TWO INDEPENDENT files that
|
|
308
|
+
* disagree. Files that cannot disagree have nothing to diff — running the
|
|
309
|
+
* comparison on them would turn a check that skipped for years into a source of
|
|
310
|
+
* spurious findings the moment AGENTS.md appears. Check 9's probe 2a uses the
|
|
311
|
+
* same predicate to avoid reporting every CLAUDE.md defect twice.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} pathA
|
|
314
|
+
* @param {string} pathB
|
|
315
|
+
* @returns {boolean}
|
|
316
|
+
*/
|
|
317
|
+
function instructionFilesAreAliased(pathA, pathB) {
|
|
318
|
+
if (!existsSync(pathA) || !existsSync(pathB)) return false;
|
|
319
|
+
try {
|
|
320
|
+
if (lstatSync(pathA).isSymbolicLink() || lstatSync(pathB).isSymbolicLink()) {
|
|
321
|
+
if (realpathSync(pathA) === realpathSync(pathB)) return true;
|
|
322
|
+
}
|
|
323
|
+
const a = statSync(pathA);
|
|
324
|
+
const b = statSync(pathB);
|
|
325
|
+
if (a.ino !== 0 && a.ino === b.ino && a.dev === b.dev) return true;
|
|
326
|
+
} catch {
|
|
327
|
+
// stat/realpath failure (race, permissions) — fall back to bytes
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
return readFileSync(pathA).equals(readFileSync(pathB));
|
|
331
|
+
} catch {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
297
336
|
function classifySection(heading) {
|
|
298
337
|
if (!heading) return null;
|
|
299
338
|
if (BACKWARD_HEADING_RE.test(heading)) return 'backward';
|
|
@@ -881,7 +920,19 @@ function main() {
|
|
|
881
920
|
const claudeVi = readVaultIntegration(claudePath);
|
|
882
921
|
const agentsVi = readVaultIntegration(agentsPath);
|
|
883
922
|
if (!claudeVi.present && !agentsVi.present) {
|
|
923
|
+
// Nothing to compare — the SUBJECT of the check is absent from both
|
|
924
|
+
// files. This branch precedes the alias short-circuit on purpose: an
|
|
925
|
+
// absent vault-integration block is a different (and more informative)
|
|
926
|
+
// outcome than "the files cannot disagree".
|
|
884
927
|
checksSkipped.push('vault-dir-parity: neither file has a vault-integration: block');
|
|
928
|
+
} else if (instructionFilesAreAliased(claudePath, agentsPath)) {
|
|
929
|
+
// Identical by construction (generated copy, symlink, or hardlink) —
|
|
930
|
+
// parity is SATISFIED, so the check RAN and found nothing. Reporting
|
|
931
|
+
// this as a skip would be wrong too: the invariant Check 7 guards is
|
|
932
|
+
// actively held here, it is simply held mechanically rather than by
|
|
933
|
+
// agreement, and re-deriving a diff between one file and itself can
|
|
934
|
+
// only manufacture noise.
|
|
935
|
+
vaultDirParityRan = true;
|
|
885
936
|
} else {
|
|
886
937
|
vaultDirParityRan = true;
|
|
887
938
|
const claudeDir = claudeVi.vaultDir;
|
|
@@ -1182,7 +1233,16 @@ function main() {
|
|
|
1182
1233
|
}
|
|
1183
1234
|
|
|
1184
1235
|
// --- Probe 2a: cited-but-missing (CLAUDE.md / AGENTS.md citations) → errors[] ---
|
|
1185
|
-
|
|
1236
|
+
// AGENTS.md is scanned as a SECOND file only when it is genuinely
|
|
1237
|
+
// independent. When it is an alias of CLAUDE.md (this repo generates it
|
|
1238
|
+
// byte-identically), scanning both would report every dangling citation
|
|
1239
|
+
// TWICE — one defect, two errors, and in `--mode strict` a doubled exit
|
|
1240
|
+
// surface for a file the operator cannot edit.
|
|
1241
|
+
const aliasedInstruction = instructionFilesAreAliased(
|
|
1242
|
+
join(vaultDir, 'CLAUDE.md'), join(vaultDir, 'AGENTS.md'),
|
|
1243
|
+
);
|
|
1244
|
+
const instrNames = aliasedInstruction ? ['CLAUDE.md'] : ['CLAUDE.md', 'AGENTS.md'];
|
|
1245
|
+
for (const instrName of instrNames) {
|
|
1186
1246
|
const filePath = join(vaultDir, instrName);
|
|
1187
1247
|
if (!existsSync(filePath) || !statSync(filePath).isFile()) continue;
|
|
1188
1248
|
const fcontent = readFileSync(filePath, 'utf8');
|
|
@@ -32,6 +32,34 @@ PEAK_DIFF_SIZE=$(jq -s 'map(.diff_size) | max' <<< "$WAVE_HISTORY_JSON")
|
|
|
32
32
|
SD_VALUE=$(echo "scale=3; $WAVE_DIFF_SIZE / $PEAK_DIFF_SIZE" | bc)
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Live monitor input (`scripts/lib/convergence-monitor.mjs`)
|
|
36
|
+
|
|
37
|
+
The `shrinking_diff` NDJSON signal compares `files_changed` across two
|
|
38
|
+
consecutive `orchestrator.wave.completed` records. Until 2026-09-05 **no
|
|
39
|
+
producer wrote that key** — 144 such records in
|
|
40
|
+
`.orchestrator/metrics/events.jsonl`, none carrying a measurement key, so the
|
|
41
|
+
signal was structurally dead. The emitter is now
|
|
42
|
+
`hooks/post-tool-batch-wave-signal.mjs` (#980): at an N→N+1 wave transition its
|
|
43
|
+
`orchestrator.wave.completed` for wave N carries
|
|
44
|
+
|
|
45
|
+
- `files_changed` — the DEDUPED count of `git diff --name-only <wave_start_sha>`
|
|
46
|
+
∪ `git ls-files --others --exclude-standard`, and
|
|
47
|
+
- `files_changed_source: 'worktree-vs-wave-start-sha'` — how it was measured.
|
|
48
|
+
|
|
49
|
+
`wave_start_sha` is the `git rev-parse HEAD` the same hook persists into
|
|
50
|
+
`.orchestrator/current-session.json` when wave N is opened. The comparison is
|
|
51
|
+
against the WORKING TREE, not `<sha>..HEAD`, because the coordinator commits at
|
|
52
|
+
session close rather than per wave.
|
|
53
|
+
|
|
54
|
+
**Both keys are optional, and absent is not zero.** Any git failure, a 1.5 s
|
|
55
|
+
timeout, or a missing `wave_start_sha` omits them; the monitor reads the absence
|
|
56
|
+
as `null` and `shrinking_diff` simply does not fire. Two records carry no
|
|
57
|
+
measurement by construction: the final wave's completion from
|
|
58
|
+
`hooks/on-session-end.mjs` (no wave-open transition ran there, so it has no
|
|
59
|
+
start sha) and any completion emitted from an explicit injected `wave_signal`.
|
|
60
|
+
The git/jq recipe above is the SKILL-level computation and is independent of the
|
|
61
|
+
monitor.
|
|
62
|
+
|
|
35
63
|
### Thresholds
|
|
36
64
|
|
|
37
65
|
| SD value | Interpretation |
|
|
@@ -99,6 +127,22 @@ PR_PRIOR=$(echo "$PASS_RATES" | jq '.[length - 2] // 0')
|
|
|
99
127
|
If `quality.incremental` events are absent (test runner not configured), set
|
|
100
128
|
`PR_confidence = 0.0` and trend = `plateau` (neutral; do not block on missing data).
|
|
101
129
|
|
|
130
|
+
### Live monitor input (`scripts/lib/convergence-monitor.mjs`)
|
|
131
|
+
|
|
132
|
+
The `pass_rate_plateau` NDJSON signal reads the **quality-gate envelope**:
|
|
133
|
+
`orchestrator.quality_gate.passed` / `.failed` records carrying BOTH
|
|
134
|
+
`wave_number` and a well-formed `counts` object. It folds `counts.passed` into
|
|
135
|
+
the wave's pass count (and `counts.failed` alongside it), then fires when two
|
|
136
|
+
consecutive waves report the identical pass count. Measured 2026-09-05: 33 such
|
|
137
|
+
records in `.orchestrator/metrics/events.jsonl`; the flat `test.passed` key the
|
|
138
|
+
reader also accepts has **0** producers and is kept only as an alias for a future
|
|
139
|
+
direct emitter.
|
|
140
|
+
|
|
141
|
+
The `wave_number` + `counts` pair is a type-AND-shape gate, not a prefix
|
|
142
|
+
widening: a session-level gate run (no `wave_number`) must stay invisible to the
|
|
143
|
+
monitor, because admitting it would instantiate a `WaveSummary` per gate run and
|
|
144
|
+
burn the once-per-wave emit keys before the real wave record arrives (#966).
|
|
145
|
+
|
|
102
146
|
### Thresholds
|
|
103
147
|
|
|
104
148
|
| PR value | Interpretation |
|
|
@@ -160,6 +204,17 @@ VEL_LINES=$(git diff --shortstat "$WAVE_START_REF" "$WAVE_END_REF" \
|
|
|
160
204
|
VEL_LINES_PRIOR=<read from events.jsonl for wave N-1>
|
|
161
205
|
```
|
|
162
206
|
|
|
207
|
+
### Live monitor input (`scripts/lib/convergence-monitor.mjs`)
|
|
208
|
+
|
|
209
|
+
The `velocity_drop` NDJSON signal counts **one `orchestrator.agent.stopped`
|
|
210
|
+
record per dispatched agent** (wave read from `wave` or `wave_number`) and fires
|
|
211
|
+
when the later wave stopped fewer agents than the earlier one. Measured
|
|
212
|
+
2026-09-05 over `.orchestrator/metrics/events.jsonl`: 11,754
|
|
213
|
+
`orchestrator.agent.stopped` records versus **0** for the `agent.dispatched` type
|
|
214
|
+
the reader also accepts — `agent.stopped` is the only per-agent record this repo
|
|
215
|
+
actually emits with a wave number. The git-based `VEL_COMMITS` / `VEL_LINES`
|
|
216
|
+
recipe above is the SKILL-level computation and is independent of the monitor.
|
|
217
|
+
|
|
163
218
|
### Thresholds
|
|
164
219
|
|
|
165
220
|
| Condition | Interpretation |
|