session-orchestrator 3.24.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.codex-plugin/skills/architecture/SKILL.md +20 -0
- package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
- package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
- package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
- package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
- package/.codex-plugin/skills/close/SKILL.md +21 -0
- package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
- package/.codex-plugin/skills/debug/SKILL.md +21 -0
- package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/discovery/SKILL.md +21 -0
- package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
- package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
- package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
- package/.codex-plugin/skills/eli5/SKILL.md +21 -0
- package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/eval/SKILL.md +21 -0
- package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/evolve/SKILL.md +21 -0
- package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
- package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
- package/.codex-plugin/skills/go/SKILL.md +22 -0
- package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/grill/SKILL.md +21 -0
- package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
- package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
- package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
- package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
- package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/plan/SKILL.md +22 -0
- package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
- package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
- package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
- package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
- package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/release/SKILL.md +22 -0
- package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
- package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session/SKILL.md +21 -0
- package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session-end/SKILL.md +22 -0
- package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
- package/.codex-plugin/skills/session-start/SKILL.md +22 -0
- package/.codex-plugin/skills/spinout/SKILL.md +21 -0
- package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
- package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
- package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test/SKILL.md +21 -0
- package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
- package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
- package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.cursor-plugin/plugin.json +30 -0
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1314 -2
- package/NOTICE +11 -6
- package/README.md +135 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +107 -29
- package/docs/components.md +38 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +69 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +365 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1488 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +60 -14
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-codex-skills.mjs +246 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +292 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +88 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/host-paths.mjs +20 -4
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +30 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +41 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +215 -15
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +414 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +51 -11
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +64 -3
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +30 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +202 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-codex-skills.mjs +191 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
- package/scripts/lib/validate/check-skill-links.mjs +193 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
- package/scripts/lib/validate/check-unwired-features.mjs +163 -15
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/confidential-names.mjs +95 -30
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/repo-files.mjs +48 -14
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +430 -31
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +344 -8
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +164 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/SKILL.md +10 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* Usage:
|
|
24
24
|
* node scripts/sweep-expired-learnings.mjs [--prune] [--dry-run|--apply] [--json]
|
|
25
25
|
* [--grace-days N] [--entries PATH] [--file PATH] [--archive PATH]
|
|
26
|
+
* [--appended N] [--boosted M] [--duration-ms D] [--skipped a,b] [--repo-root PATH]
|
|
26
27
|
*
|
|
27
28
|
* Flags:
|
|
28
29
|
* --prune Decision-driven prune+consolidate+rewrite instead of the
|
|
@@ -40,6 +41,23 @@
|
|
|
40
41
|
* store.
|
|
41
42
|
* --file PATH Learnings store (default: .orchestrator/metrics/learnings.jsonl)
|
|
42
43
|
* --archive PATH Archive sidecar (default: .orchestrator/metrics/learnings-archive.jsonl)
|
|
44
|
+
* --appended N PRUNE ONLY (#1206). New learnings written this `/evolve`
|
|
45
|
+
* run (Step 3.5(4)); folded into the mechanical
|
|
46
|
+
* `orchestrator.evolve.completed` emit alongside this
|
|
47
|
+
* call's own `pruned` count. Default 0.
|
|
48
|
+
* --boosted M PRUNE ONLY (#1206). Existing learnings reinforced this
|
|
49
|
+
* run (Step 3.5(2)). Default 0.
|
|
50
|
+
* --duration-ms D PRUNE ONLY (#1206). Elapsed ms since the run's telemetry
|
|
51
|
+
* start marker. Default 0.
|
|
52
|
+
* --skipped a,b PRUNE ONLY (#1206). Comma-separated list of optional
|
|
53
|
+
* steps that ran but were themselves skipped this run
|
|
54
|
+
* (HR-105 — e.g. `skill-evolution-off`). Default none.
|
|
55
|
+
* --repo-root PATH PRUNE ONLY (#1206). Repo root the
|
|
56
|
+
* `orchestrator.evolve.completed` record is pinned to.
|
|
57
|
+
* No default and NO process.cwd() fallback (#1119) — when
|
|
58
|
+
* omitted, no event is emitted at all (stderr WARN, exit
|
|
59
|
+
* code unaffected). Emission is best-effort and never
|
|
60
|
+
* changes this command's exit code or stdout contract.
|
|
43
61
|
*
|
|
44
62
|
* Exit codes:
|
|
45
63
|
* 0 Success (including no-op when nothing is archive-eligible)
|
|
@@ -51,6 +69,7 @@
|
|
|
51
69
|
import { existsSync } from 'node:fs';
|
|
52
70
|
import { sweepExpiredLearnings, pruneLearnings } from './lib/learnings/expiry-sweep.mjs';
|
|
53
71
|
import { readLearnings } from './lib/learnings/io.mjs';
|
|
72
|
+
import { emitEvolveCompleted } from './lib/learnings/evolve-telemetry.mjs';
|
|
54
73
|
|
|
55
74
|
const DEFAULT_FILE = '.orchestrator/metrics/learnings.jsonl';
|
|
56
75
|
const DEFAULT_ARCHIVE = '.orchestrator/metrics/learnings-archive.jsonl';
|
|
@@ -97,6 +116,20 @@ function parseArgs(argv) {
|
|
|
97
116
|
entries: null,
|
|
98
117
|
file: DEFAULT_FILE,
|
|
99
118
|
archive: DEFAULT_ARCHIVE,
|
|
119
|
+
appended: 0,
|
|
120
|
+
boosted: 0,
|
|
121
|
+
durationMs: 0,
|
|
122
|
+
skipped: [],
|
|
123
|
+
// #1119 — NO process.cwd() fallback. Most CLI callers (every test in this
|
|
124
|
+
// file except the SKILL.md-extraction fixture, which sets its own tmp
|
|
125
|
+
// `cwd`) invoke this script from the repo root without `--repo-root`; a
|
|
126
|
+
// cwd fallback would silently append synthetic `orchestrator.evolve.
|
|
127
|
+
// completed` records to the operator's REAL fleet ledger on every
|
|
128
|
+
// `--prune --apply` run in `npm test`. `emitEvolveCompleted()` already
|
|
129
|
+
// refuses to emit (stderr WARN, no throw) when repoRoot is absent — same
|
|
130
|
+
// fail-closed contract as `emitReconcileCompleted` / `express-path.mjs`.
|
|
131
|
+
repoRoot: null,
|
|
132
|
+
telemetryExplicit: false,
|
|
100
133
|
};
|
|
101
134
|
for (let i = 0; i < argv.length; i++) {
|
|
102
135
|
const a = argv[i];
|
|
@@ -122,6 +155,37 @@ function parseArgs(argv) {
|
|
|
122
155
|
args.file = argv[++i];
|
|
123
156
|
} else if (a === '--archive') {
|
|
124
157
|
args.archive = argv[++i];
|
|
158
|
+
} else if (a === '--appended') {
|
|
159
|
+
const raw = argv[++i];
|
|
160
|
+
const v = Number(raw);
|
|
161
|
+
if (!Number.isInteger(v) || v < 0) {
|
|
162
|
+
usageError(`--appended requires a non-negative integer, got: ${raw}`);
|
|
163
|
+
}
|
|
164
|
+
args.appended = v;
|
|
165
|
+
args.telemetryExplicit = true;
|
|
166
|
+
} else if (a === '--boosted') {
|
|
167
|
+
const raw = argv[++i];
|
|
168
|
+
const v = Number(raw);
|
|
169
|
+
if (!Number.isInteger(v) || v < 0) {
|
|
170
|
+
usageError(`--boosted requires a non-negative integer, got: ${raw}`);
|
|
171
|
+
}
|
|
172
|
+
args.boosted = v;
|
|
173
|
+
args.telemetryExplicit = true;
|
|
174
|
+
} else if (a === '--duration-ms') {
|
|
175
|
+
const raw = argv[++i];
|
|
176
|
+
const v = Number(raw);
|
|
177
|
+
if (!Number.isFinite(v) || v < 0) {
|
|
178
|
+
usageError(`--duration-ms requires a non-negative number, got: ${raw}`);
|
|
179
|
+
}
|
|
180
|
+
args.durationMs = v;
|
|
181
|
+
args.telemetryExplicit = true;
|
|
182
|
+
} else if (a === '--skipped') {
|
|
183
|
+
const raw = argv[++i];
|
|
184
|
+
args.skipped = typeof raw === 'string' ? raw.split(',').filter((s) => s.length > 0) : [];
|
|
185
|
+
args.telemetryExplicit = true;
|
|
186
|
+
} else if (a === '--repo-root') {
|
|
187
|
+
args.repoRoot = argv[++i];
|
|
188
|
+
args.telemetryExplicit = true;
|
|
125
189
|
} else if (a === '--help' || a === '-h') {
|
|
126
190
|
printHelp();
|
|
127
191
|
process.exit(0);
|
|
@@ -140,6 +204,12 @@ function parseArgs(argv) {
|
|
|
140
204
|
if (!args.prune && args.entries !== null) {
|
|
141
205
|
usageError('--entries is only valid with --prune');
|
|
142
206
|
}
|
|
207
|
+
if (!args.prune && args.telemetryExplicit) {
|
|
208
|
+
usageError(
|
|
209
|
+
'--appended/--boosted/--duration-ms/--skipped/--repo-root are only valid with --prune ' +
|
|
210
|
+
'(the sweep path never emits orchestrator.evolve.completed)',
|
|
211
|
+
);
|
|
212
|
+
}
|
|
143
213
|
return args;
|
|
144
214
|
}
|
|
145
215
|
|
|
@@ -265,6 +335,26 @@ async function runPrune(args) {
|
|
|
265
335
|
...result,
|
|
266
336
|
};
|
|
267
337
|
|
|
338
|
+
// #1206 — the ONE mechanical call site for `orchestrator.evolve.completed`:
|
|
339
|
+
// this `--prune --apply` invocation IS `/evolve analyze`'s Step 3.5(5) store
|
|
340
|
+
// write, so folding the emit in here (instead of a separate
|
|
341
|
+
// `emit-event.mjs` call in skill prose) means the event can no longer be
|
|
342
|
+
// forgotten independently of the write it reports on. Gated on `!dryRun` —
|
|
343
|
+
// a `--prune --dry-run` preview (the docs' own recommended pre-check for a
|
|
344
|
+
// hand-assembled `--entries` sidecar) never wrote anything, so it must not
|
|
345
|
+
// report a completed run either. Best-effort — never changes this
|
|
346
|
+
// command's exit code or stdout contract.
|
|
347
|
+
if (!args.dryRun) {
|
|
348
|
+
await emitEvolveCompleted({
|
|
349
|
+
repoRoot: args.repoRoot,
|
|
350
|
+
appended: args.appended,
|
|
351
|
+
boosted: args.boosted,
|
|
352
|
+
pruned: result.archived,
|
|
353
|
+
durationMs: args.durationMs,
|
|
354
|
+
skipped: args.skipped,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
268
358
|
if (args.json) {
|
|
269
359
|
process.stdout.write(JSON.stringify(summary) + '\n');
|
|
270
360
|
} else {
|
|
@@ -141,7 +141,9 @@ function stripCommentLine(line) {
|
|
|
141
141
|
return null;
|
|
142
142
|
}
|
|
143
143
|
// Single-line doc comment
|
|
144
|
-
|
|
144
|
+
// A bare `//` (blank line inside a canonical comment run) is a comment too —
|
|
145
|
+
// without this the generated block carried a stray `//` (measured 2026-09-03).
|
|
146
|
+
if (trimmed === '//' || (trimmed.startsWith('// ') && !trimmed.startsWith('// ──'))) {
|
|
145
147
|
// Keep structural separator comments, drop narrative ones
|
|
146
148
|
return null;
|
|
147
149
|
}
|
package/scripts/telemetry.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import { queueStats } from './lib/telemetry/queue.mjs';
|
|
|
33
33
|
import { flush, buildBatch } from './lib/telemetry/sync.mjs';
|
|
34
34
|
import { loadOwnerConfig } from './lib/owner-yaml.mjs';
|
|
35
35
|
import { readPluginVersionFromPackageJson } from './lib/bootstrap-lock-freshness.mjs';
|
|
36
|
-
import {
|
|
36
|
+
import { getPluginRoot } from './lib/platform.mjs';
|
|
37
37
|
|
|
38
38
|
const EXIT_OK = 0;
|
|
39
39
|
const EXIT_USER = 1;
|
|
@@ -69,7 +69,7 @@ ENV KILL-SWITCHES
|
|
|
69
69
|
|
|
70
70
|
/** Plugin version for `--version` — single-sourced via readPluginVersionFromPackageJson (null → 'unknown'). */
|
|
71
71
|
function readPkgVersion() {
|
|
72
|
-
return readPluginVersionFromPackageJson(
|
|
72
|
+
return readPluginVersionFromPackageJson(getPluginRoot()) ?? 'unknown';
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
@@ -94,6 +94,42 @@ function runCheck(script) {
|
|
|
94
94
|
return result.status ?? 1;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Advisory second pass for `check-skill-script-paths.mjs` (#1208): run it a
|
|
99
|
+
* second time with `--strict-sh --json` so the flag has a run surface inside
|
|
100
|
+
* validate-plugin, without promoting `.sh` findings to blocking here — the
|
|
101
|
+
* BLOCKING `.mjs`-only call above stays exactly as it is. Same non-blocking
|
|
102
|
+
* shape as the `check-unwired-features.mjs` / `check-learning-provenance.mjs`
|
|
103
|
+
* / `check-vcs-repo-flag.mjs` calls further down (exit code deliberately
|
|
104
|
+
* ignored; `checkFailed` is never set from this call). `runCheck()` cannot be
|
|
105
|
+
* reused here because it always spawns `[script, PLUGIN_ROOT]` with no room
|
|
106
|
+
* for extra flags, so this is a small dedicated spawn instead.
|
|
107
|
+
*
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
110
|
+
function runStrictShAdvisory() {
|
|
111
|
+
const result = spawnSync(
|
|
112
|
+
'node',
|
|
113
|
+
[path.join(VALIDATE_DIR, 'check-skill-script-paths.mjs'), PLUGIN_ROOT, '--strict-sh', '--json'],
|
|
114
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
115
|
+
);
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(result.stdout || '');
|
|
119
|
+
} catch {
|
|
120
|
+
process.stdout.write(
|
|
121
|
+
' WARN: check-skill-script-paths --strict-sh: could not parse JSON output (advisory)\n',
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const shDead = (parsed.findings || []).filter(
|
|
126
|
+
(f) => f.kind === 'missing-path' && f.path.endsWith('.sh'),
|
|
127
|
+
).length;
|
|
128
|
+
process.stdout.write(
|
|
129
|
+
` check-skill-script-paths --strict-sh: ${shDead} dead .sh citation(s) (advisory)\n`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
97
133
|
/**
|
|
98
134
|
* Run the surface-count drift family (issue #663) from the claude-md-drift-check
|
|
99
135
|
* checker over the doc surfaces that carry artifact counts (README.md +
|
|
@@ -175,6 +211,105 @@ function runDriftCheck() {
|
|
|
175
211
|
return 0;
|
|
176
212
|
}
|
|
177
213
|
|
|
214
|
+
/**
|
|
215
|
+
* BLOCKING (#1224): the committed hook-reachable allowlist
|
|
216
|
+
* (`hooks/_lib/hook-import-set.json`) must match a fresh crawl of the four hook
|
|
217
|
+
* manifests. The allowlist is what gates `hooks/post-edit-import-probe.mjs`, so
|
|
218
|
+
* a stale set silently NARROWS that probe — a newly hook-reachable module would
|
|
219
|
+
* be edited without ever being checked, which is precisely the 2026-09-04
|
|
220
|
+
* host-wide-block incident class the probe exists to catch. A rotting allowlist
|
|
221
|
+
* is therefore a real defect, not a cosmetic drift, hence FAIL rather than WARN.
|
|
222
|
+
*
|
|
223
|
+
* `scripts/generate-hook-import-set.mjs --check` owns the comparison (it exits 1
|
|
224
|
+
* on drift); this adapter only translates it into validate-plugin's vocabulary.
|
|
225
|
+
* `runCheck()` cannot be reused — it spawns from `scripts/lib/validate/` and
|
|
226
|
+
* passes PLUGIN_ROOT positionally, while the generator takes `--plugin-root`.
|
|
227
|
+
*
|
|
228
|
+
* @returns {number} 0 when in sync, 1 on drift or a generator error.
|
|
229
|
+
*/
|
|
230
|
+
function runHookImportSetCheck() {
|
|
231
|
+
console.log('--- Check: hook-import-set drift (#1224) ---');
|
|
232
|
+
const script = path.join(SCRIPT_DIR, 'generate-hook-import-set.mjs');
|
|
233
|
+
const result = spawnSync(
|
|
234
|
+
process.execPath,
|
|
235
|
+
[script, '--plugin-root', PLUGIN_ROOT, '--check'],
|
|
236
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
237
|
+
);
|
|
238
|
+
const combined = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
|
|
239
|
+
|
|
240
|
+
// A silent exit 0 is NOT a pass. The generator's CLI entry is gated on
|
|
241
|
+
// `import.meta.url === file://${process.argv[1]}`, which does not hold when
|
|
242
|
+
// the spawned path traverses a symlink (measured: `/tmp` → `/private/tmp` on
|
|
243
|
+
// macOS makes the whole CLI body a no-op that still exits 0). Demanding the
|
|
244
|
+
// in-sync marker turns that failure mode from a silent pass into a finding.
|
|
245
|
+
const inSync = combined.match(/(\d+) modules, in sync/);
|
|
246
|
+
if (result.status === 0 && inSync) {
|
|
247
|
+
console.log(` PASS: hook-import-set: in sync (${inSync[1]} entries)`);
|
|
248
|
+
totalPass += 1;
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
if (result.status === 0) {
|
|
252
|
+
console.log(' FAIL: hook-import-set: generator exited 0 without reporting a comparison '
|
|
253
|
+
+ `(no in-sync marker in output: ${JSON.stringify(combined.slice(0, 120))})`);
|
|
254
|
+
totalFail += 1;
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
const detail = combined.split('\n').filter(Boolean).pop()
|
|
258
|
+
?? `generator exited ${result.status}`;
|
|
259
|
+
console.log(` FAIL: hook-import-set: ${detail.replace(/^✗\s*hook-import-set:\s*/, '')}`);
|
|
260
|
+
console.log(' Remedy: node scripts/generate-hook-import-set.mjs && stage hooks/_lib/hook-import-set.json');
|
|
261
|
+
totalFail += 1;
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* BLOCKING: the cross-harness portable surface (`AGENTS.md` +
|
|
267
|
+
* `.agents/skills/`) must match a fresh generation from `CLAUDE.md` +
|
|
268
|
+
* `skills/`. 7 of 8 surveyed harnesses read `AGENTS.md` and never `CLAUDE.md`,
|
|
269
|
+
* so a stale or absent copy means those harnesses silently see NO Session
|
|
270
|
+
* Config — a failure that is invisible from inside Claude Code, which is
|
|
271
|
+
* exactly why it needs a mechanical gate rather than a convention.
|
|
272
|
+
*
|
|
273
|
+
* `scripts/generate-agents-skills.mjs --check` owns the comparison (exit 1 on
|
|
274
|
+
* drift); this adapter only translates it. `runCheck()` cannot be reused — it
|
|
275
|
+
* spawns from `scripts/lib/validate/` and passes PLUGIN_ROOT positionally,
|
|
276
|
+
* while the generator takes `--plugin-root`. The in-sync marker is demanded
|
|
277
|
+
* for the same reason `runHookImportSetCheck()` demands one: a spawn whose
|
|
278
|
+
* path traverses a symlink can make the generator's CLI body a no-op that
|
|
279
|
+
* still exits 0.
|
|
280
|
+
*
|
|
281
|
+
* @returns {number} 0 when in sync, 1 on drift or a generator error.
|
|
282
|
+
*/
|
|
283
|
+
function runAgentsSurfaceCheck() {
|
|
284
|
+
console.log('--- Check: cross-harness portable surface (AGENTS.md + .agents/skills/) ---');
|
|
285
|
+
const script = path.join(SCRIPT_DIR, 'generate-agents-skills.mjs');
|
|
286
|
+
const result = spawnSync(
|
|
287
|
+
process.execPath,
|
|
288
|
+
[script, '--plugin-root', PLUGIN_ROOT, '--check'],
|
|
289
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
290
|
+
);
|
|
291
|
+
const combined = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
|
|
292
|
+
|
|
293
|
+
const inSync = combined.match(/agents-surface: (\d+) artefact\(s\), in sync/);
|
|
294
|
+
if (result.status === 0 && inSync) {
|
|
295
|
+
console.log(` PASS: agents-surface: in sync (${inSync[1]} artefacts)`);
|
|
296
|
+
totalPass += 1;
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
if (result.status === 0) {
|
|
300
|
+
console.log(' FAIL: agents-surface: generator exited 0 without reporting a comparison '
|
|
301
|
+
+ `(no in-sync marker in output: ${JSON.stringify(combined.slice(0, 120))})`);
|
|
302
|
+
totalFail += 1;
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
for (const line of combined.split('\n').filter((l) => l.startsWith('✗'))) {
|
|
306
|
+
console.log(` FAIL: ${line.replace(/^✗\s*/, '')}`);
|
|
307
|
+
totalFail += 1;
|
|
308
|
+
}
|
|
309
|
+
console.log(' Remedy: node scripts/generate-agents-skills.mjs');
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
178
313
|
// ---------------------------------------------------------------------------
|
|
179
314
|
// Run all checks — same order as validate-plugin.sh
|
|
180
315
|
// plugin.json checks are prerequisite; abort early if they fail.
|
|
@@ -220,6 +355,9 @@ if (runDriftCheck() !== 0) checkFailed = 1;
|
|
|
220
355
|
process.stdout.write('\n');
|
|
221
356
|
if (runCheck('check-hooks-symmetry.mjs') !== 0) checkFailed = 1;
|
|
222
357
|
|
|
358
|
+
process.stdout.write('\n');
|
|
359
|
+
if (runHookImportSetCheck() !== 0) checkFailed = 1;
|
|
360
|
+
|
|
223
361
|
process.stdout.write('\n');
|
|
224
362
|
if (runCheck('check-guard-requires-parity.mjs') !== 0) checkFailed = 1;
|
|
225
363
|
|
|
@@ -238,6 +376,9 @@ if (runCheck('check-path-utils-canary.mjs') !== 0) checkFailed = 1;
|
|
|
238
376
|
process.stdout.write('\n');
|
|
239
377
|
if (runCheck('check-codex-plugin.mjs') !== 0) checkFailed = 1;
|
|
240
378
|
|
|
379
|
+
process.stdout.write('\n');
|
|
380
|
+
if (runCheck('check-codex-skills.mjs') !== 0) checkFailed = 1;
|
|
381
|
+
|
|
241
382
|
process.stdout.write('\n');
|
|
242
383
|
if (runCheck('check-pi-package.mjs') !== 0) checkFailed = 1;
|
|
243
384
|
|
|
@@ -247,6 +388,18 @@ if (runCheck('check-pi-prompts.mjs') !== 0) checkFailed = 1;
|
|
|
247
388
|
process.stdout.write('\n');
|
|
248
389
|
if (runCheck('check-cursor-adapter.mjs') !== 0) checkFailed = 1;
|
|
249
390
|
|
|
391
|
+
// BLOCKING: the vendor-neutral portable surface — root AGENTS.md ↔ CLAUDE.md
|
|
392
|
+
// alias, root plugin.json (agent-plugins.org 1.0.0) manifest-version parity
|
|
393
|
+
// against package.json, and the .agents/skills/ mirror against the
|
|
394
|
+
// agentskills.io field list. The generator's own --check runs first (below);
|
|
395
|
+
// this checker is the INDEPENDENT oracle over the same artefacts, so a
|
|
396
|
+
// generator bug cannot vouch for its own output.
|
|
397
|
+
process.stdout.write('\n');
|
|
398
|
+
if (runCheck('check-agents-skills.mjs') !== 0) checkFailed = 1;
|
|
399
|
+
|
|
400
|
+
process.stdout.write('\n');
|
|
401
|
+
if (runAgentsSurfaceCheck() !== 0) checkFailed = 1;
|
|
402
|
+
|
|
250
403
|
process.stdout.write('\n');
|
|
251
404
|
if (runCheck('check-session-plan-routing.mjs') !== 0) checkFailed = 1;
|
|
252
405
|
|
|
@@ -327,6 +480,17 @@ runCheck('check-doc-cli-commands.mjs');
|
|
|
327
480
|
// citation's own line or the line immediately above.
|
|
328
481
|
process.stdout.write('\n');
|
|
329
482
|
if (runCheck('check-skill-script-paths.mjs') !== 0) checkFailed = 1;
|
|
483
|
+
runStrictShAdvisory();
|
|
484
|
+
|
|
485
|
+
// BLOCKING (#1157 follow-up): a relative markdown link under skills/, commands/,
|
|
486
|
+
// agents/ or .claude/rules/ that does not resolve from the LINKING file's own
|
|
487
|
+
// directory. The check above judges cited SCRIPT paths; this one judges links
|
|
488
|
+
// between the instruction files themselves — the defect class a `references/`
|
|
489
|
+
// split creates, where the moved text is byte-identical and every `./sibling.md`
|
|
490
|
+
// in it now points one directory too deep. Three such links shipped broken on
|
|
491
|
+
// 2026-09-06 and no gate saw them; the worst silently detached the six
|
|
492
|
+
// session-end tail phases from their dispatcher.
|
|
493
|
+
if (runCheck('check-skill-links.mjs') !== 0) checkFailed = 1;
|
|
330
494
|
|
|
331
495
|
// WARN-only: a state-mutating `git` call in tests/ that names no target resolves
|
|
332
496
|
// its destination from the ambient cwd (or an inherited GIT_DIR) — the 2026-08-19
|
|
@@ -73,6 +73,7 @@ import path from 'node:path';
|
|
|
73
73
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
74
74
|
import { execFileSync } from 'node:child_process';
|
|
75
75
|
import { warn } from './lib/common.mjs';
|
|
76
|
+
import { MANIFEST_SESSION_KEYS } from './lib/session-identity/own-session.mjs';
|
|
76
77
|
import {
|
|
77
78
|
assertFileScopeSubset,
|
|
78
79
|
assertTestSiblingCoverage,
|
|
@@ -289,10 +290,13 @@ function validateOptionalSessionId(obj, key, errors) {
|
|
|
289
290
|
}
|
|
290
291
|
|
|
291
292
|
/**
|
|
292
|
-
* Validate the OPTIONAL session binding (#1123): `
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* call — see `skills/wave-executor/wave-loop.md` § Scope Manifest.
|
|
293
|
+
* Validate the OPTIONAL session binding (#1123): `session_id` (the raw session
|
|
294
|
+
* id of the session that WROTE this manifest) and its human-readable twin
|
|
295
|
+
* `semantic_session_id`. Both come from one `sessionAttribution(repoRoot)`
|
|
296
|
+
* call — see `skills/wave-executor/wave-loop.md` § Scope Manifest. The
|
|
297
|
+
* pre-#1153 spellings `session` / `semantic_session` are still ACCEPTED here
|
|
298
|
+
* (read side only, until the next minor release) — key names come from
|
|
299
|
+
* `MANIFEST_SESSION_KEYS` so the writer and every reader share one list.
|
|
296
300
|
*
|
|
297
301
|
* Deliberately NOT part of {@link validateRequired}, and that is a compatibility
|
|
298
302
|
* constraint rather than a preference: `wave-scope.json` is a shared
|
|
@@ -306,9 +310,25 @@ function validateOptionalSessionId(obj, key, errors) {
|
|
|
306
310
|
* @param {string[]} errors
|
|
307
311
|
* @param {string[]} warnings
|
|
308
312
|
*/
|
|
309
|
-
function
|
|
310
|
-
|
|
311
|
-
|
|
313
|
+
function validateSessionBinding(obj, errors, warnings) {
|
|
314
|
+
let present = false;
|
|
315
|
+
MANIFEST_SESSION_KEYS.current.forEach((key, i) => {
|
|
316
|
+
const legacyKey = MANIFEST_SESSION_KEYS.legacy[i];
|
|
317
|
+
const hasCurrent = validateOptionalSessionId(obj, key, errors);
|
|
318
|
+
const hasLegacy = validateOptionalSessionId(obj, legacyKey, errors);
|
|
319
|
+
// Both spellings of the SAME slot, disagreeing, is the one case the reader
|
|
320
|
+
// cannot resolve honestly: it silently prefers `key` and drops the other
|
|
321
|
+
// id, so a manifest that names two different sessions would classify as
|
|
322
|
+
// `own` for one of them. Name it here rather than let the preference decide.
|
|
323
|
+
if (hasCurrent && hasLegacy && obj[key] !== obj[legacyKey]) {
|
|
324
|
+
errors.push(
|
|
325
|
+
`${key} and legacy ${legacyKey} are both present with DIFFERENT values — ` +
|
|
326
|
+
'a manifest binds to exactly one session; drop the legacy key (accepted on ' +
|
|
327
|
+
'the read side only, until the next minor release, #1153)',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (i === 0) present = hasCurrent || hasLegacy;
|
|
331
|
+
});
|
|
312
332
|
if (!present) {
|
|
313
333
|
warnings.push(
|
|
314
334
|
'no session field — manifest is not session-bound (legacy, #1123), so every session sharing this ' +
|
|
@@ -749,7 +769,7 @@ function validate(
|
|
|
749
769
|
const warnings = [];
|
|
750
770
|
|
|
751
771
|
validateRequired(obj, errors);
|
|
752
|
-
|
|
772
|
+
validateSessionBinding(obj, errors, warnings);
|
|
753
773
|
validateAllowedPaths(obj, errors, warnings);
|
|
754
774
|
validateBlockedCommands(obj, errors);
|
|
755
775
|
validateGates(obj, errors);
|
|
@@ -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)
|