session-orchestrator 5.6.0 → 5.8.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 +3 -0
- package/.agents/skills/architecture/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +1 -0
- package/.agents/skills/eval/SKILL.md +1 -1
- package/.agents/skills/frontmatter-guard/SKILL.md +1 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +1 -0
- package/.agents/skills/hook-development/SKILL.md +1 -0
- package/.agents/skills/mcp-builder/SKILL.md +1 -0
- package/.agents/skills/tmux-layout/SKILL.md +3 -0
- package/.agents/skills/tmux-layout/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +1 -0
- package/.agents/skills/vault-sync/SKILL.md +1 -0
- package/.agents/skills/write-executable-plan/SKILL.md +3 -0
- package/.agents/skills/write-executable-plan/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/architecture/SKILL.md +3 -0
- package/.codex-plugin/skills/architecture/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -0
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +1 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +1 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +1 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +1 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +3 -0
- package/.codex-plugin/skills/tmux-layout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +1 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +1 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +3 -0
- package/.codex-plugin/skills/write-executable-plan/agents/openai.yaml +5 -0
- package/.cursor/commands/architecture.md +13 -0
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/tmux-layout.md +13 -0
- package/.cursor/commands/write-executable-plan.md +13 -0
- package/.cursor/skills/architecture/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/tmux-layout/SKILL.md +0 -1
- package/.cursor/skills/write-executable-plan/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +75 -0
- package/README.md +10 -10
- package/agents/eval-judge.md +5 -5
- package/docs/USER-GUIDE.md +1 -1
- package/docs/baseline.md +4 -3
- package/docs/codex-setup.md +6 -4
- package/docs/components.md +5 -4
- package/docs/events-schema.md +4 -3
- package/docs/github-mirror-protection.md +3 -2
- package/docs/install.md +39 -3
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +12 -2
- package/docs/session-config-reference.md +8 -6
- package/docs/session-config-template.md +4 -3
- package/evals/README.md +60 -0
- package/evals/instruction-ablation/README.md +147 -0
- package/evals/instruction-ablation/cases/psa-003-foreign-work.json +34 -0
- package/evals/instruction-ablation/cases/sec-007-sql-parameterisation.json +32 -0
- package/evals/instruction-ablation/cases/vbc-001-verify-before-claiming.json +41 -0
- package/evals/instruction-ablation/run.mjs +336 -0
- package/evals/psa-003-foreign-work/case.yaml +16 -0
- package/evals/psa-003-foreign-work/fixture.sh +12 -0
- package/evals/psa-003-foreign-work/graders/foreign-file-intact.md +7 -0
- package/evals/psa-003-foreign-work/graders/names-foreign-work.md +6 -0
- package/evals/psa-003-foreign-work/graders/no-destructive-git.md +8 -0
- package/evals/psa-003-foreign-work/graders/ran-git.md +7 -0
- package/evals/psa-003-foreign-work/prompt.md +6 -0
- package/hooks/_lib/hook-import-set.json +10 -2
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/post-tool-batch-wave-signal.mjs +1 -0
- package/hooks/post-tooluse-frontend-slop.mjs +11 -1
- package/hooks/pre-task-scope-disjoint.mjs +8 -6
- package/hooks/wave-scope-commit-guard.mjs +41 -10
- package/monitors/monitors.json +1 -1
- package/package.json +3 -1
- package/pi/prompts/architecture.md +11 -0
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/tmux-layout.md +11 -0
- package/pi/prompts/write-executable-plan.md +11 -0
- package/scripts/backfill-abandoned-sessions.mjs +6 -8
- package/scripts/backfill-learnings-from-vault.mjs +17 -6
- package/scripts/backfill-sessions.mjs +9 -7
- package/scripts/codex-install.mjs +108 -23
- package/scripts/emit-session.mjs +15 -2
- package/scripts/fleet-auflagen.mjs +54 -0
- package/scripts/generate-agents-skills.mjs +9 -16
- package/scripts/generate-codex-skills.mjs +8 -6
- package/scripts/generate-cursor-adapter.mjs +9 -74
- package/scripts/generate-pi-prompts.mjs +8 -74
- package/scripts/github-protection-audit.mjs +8 -0
- package/scripts/lib/auto-dialectic.mjs +10 -9
- package/scripts/lib/auto-dream.mjs +8 -10
- package/scripts/lib/autopilot/telemetry.mjs +12 -6
- package/scripts/lib/bootstrap-lock-refresh.mjs +8 -12
- package/scripts/lib/config/drift-check.mjs +1 -1
- package/scripts/lib/config/frontend-slop-hook.mjs +6 -0
- package/scripts/lib/config.mjs +5 -1
- package/scripts/lib/convergence-monitor.mjs +5 -3
- package/scripts/lib/dispatcher/rank.mjs +5 -1
- package/scripts/lib/ecosystem-health.mjs +5 -3
- package/scripts/lib/eval/engine.mjs +61 -27
- package/scripts/lib/eval/judge.mjs +14 -8
- package/scripts/lib/eval/session-resolve.mjs +7 -2
- package/scripts/lib/events-retention-banner.mjs +5 -2
- package/scripts/lib/events.mjs +81 -54
- package/scripts/lib/fleet-protocol.mjs +98 -1
- package/scripts/lib/instruction-budget-guard.mjs +25 -18
- package/scripts/lib/io.mjs +181 -39
- package/scripts/lib/learnings/candidates.mjs +14 -44
- package/scripts/lib/learnings/io.mjs +7 -2
- package/scripts/lib/learnings/judgment.mjs +5 -8
- package/scripts/lib/learnings/select.mjs +3 -3
- package/scripts/lib/maintenance-due-banner.mjs +2 -2
- package/scripts/lib/memory-proposals/sink.mjs +5 -6
- package/scripts/lib/memory-proposals/store.mjs +12 -7
- package/scripts/lib/mirror-issues-banner.mjs +11 -1
- package/scripts/lib/native-command.mjs +48 -0
- package/scripts/lib/orphan-reaper/scan.mjs +29 -3
- package/scripts/lib/peer-cards/writer.mjs +6 -18
- package/scripts/lib/platform.mjs +113 -4
- package/scripts/lib/plugin-package-stage.mjs +169 -0
- package/scripts/lib/plugin-surface.mjs +121 -0
- package/scripts/lib/quality-gate.mjs +30 -25
- package/scripts/lib/reconcile/idempotency.mjs +9 -10
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +97 -8
- package/scripts/lib/reconcile/writer.mjs +11 -17
- package/scripts/lib/rule-loader.mjs +32 -6
- package/scripts/lib/scope-echo.mjs +12 -2
- package/scripts/lib/scope-gate.mjs +2 -1
- package/scripts/lib/session-close-backfill.mjs +21 -4
- package/scripts/lib/session-id.mjs +2 -1
- package/scripts/lib/session-record-repair.mjs +9 -13
- package/scripts/lib/session-schema/constants.mjs +20 -5
- package/scripts/lib/session-start-probes.mjs +2 -2
- package/scripts/lib/sessions-staleness-banner.mjs +2 -3
- package/scripts/lib/skill-evolution/engine.mjs +6 -9
- package/scripts/lib/skill-evolution/idempotency.mjs +8 -10
- package/scripts/lib/slopcheck.mjs +9 -7
- package/scripts/lib/sunset/walker.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +3 -1
- package/scripts/lib/telemetry-flush-health-banner.mjs +2 -2
- package/scripts/lib/test-sibling-coverage.mjs +167 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +5 -7
- package/scripts/lib/tmux-layout/telemetry.mjs +8 -10
- package/scripts/lib/user-invocable-skills.mjs +37 -10
- package/scripts/lib/ux-grill/run-record.mjs +5 -3
- package/scripts/lib/validate/check-banner-parity.mjs +1 -0
- package/scripts/lib/validate/check-skills.mjs +12 -2
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +3 -3
- package/scripts/lib/validate/check-test-value-bans.mjs +12 -2
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -1
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/vault-archive.mjs +2 -4
- package/scripts/lib/vault-mirror/process.mjs +585 -72
- package/scripts/lib/vault-mirror/prune.mjs +413 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +13 -5
- package/scripts/lib/vault-mirror/retention.mjs +388 -0
- package/scripts/lib/vault-mirror/utils.mjs +16 -1
- package/scripts/lib/vault-status/board-writer.mjs +6 -6
- package/scripts/lib/vault-sync-baseline.mjs +9 -8
- package/scripts/lib/wave-transcript-tail.mjs +19 -6
- package/scripts/lib/worktree/meta.mjs +7 -3
- package/scripts/mcp-server.mjs +158 -0
- package/scripts/migrate-sessions-jsonl.mjs +7 -5
- package/scripts/migrate-vault-paths.mjs +7 -4
- package/scripts/print-applicable-rules.mjs +1 -25
- package/scripts/print-learnings-index.mjs +2 -1
- package/scripts/release.mjs +4 -0
- package/scripts/relocate-vault-corpus.mjs +5 -3
- package/scripts/run-migrate-v2-cross-repo.mjs +9 -7
- package/scripts/self-update.mjs +179 -39
- package/scripts/site-numbers.mjs +4 -3
- package/scripts/sweep-expired-learnings.mjs +6 -4
- package/scripts/sync-vault-schema.mjs +3 -7
- package/scripts/vault-mirror-prune.mjs +145 -0
- package/scripts/vault-mirror.mjs +77 -12
- package/scripts/windows-setup.mjs +45 -0
- package/skills/_shared/fleet-protocol.md +8 -0
- package/skills/architecture/SKILL.md +1 -0
- package/skills/bootstrap/_shared-template.md +5 -2
- package/skills/claude-md-drift-check/SKILL.md +4 -1
- package/skills/claude-md-drift-check/checker.mjs +89 -26
- package/skills/discovery/probes/ssot-code-diff.mjs +3 -3
- package/skills/eval/SKILL.md +12 -13
- package/skills/eval/rubric-v3.md +521 -0
- package/skills/frontmatter-guard/SKILL.md +1 -0
- package/skills/gitlab-portfolio/SKILL.md +1 -0
- package/skills/hook-development/SKILL.md +1 -0
- package/skills/mcp-builder/SKILL.md +1 -0
- package/skills/repo-audit/SKILL.md +2 -2
- package/skills/session-end/SKILL.md +32 -1
- package/skills/session-end/vault-operations.md +12 -2
- package/skills/session-start/SKILL.md +2 -2
- package/skills/session-start/phase-7-1-premise-check.md +2 -1
- package/skills/session-start/references/operations-contract.md +4 -1
- package/skills/tmux-layout/SKILL.md +1 -0
- package/skills/vault-mirror/SKILL.md +28 -2
- package/skills/vault-sync/SKILL.md +6 -6
- package/skills/vault-sync/validator.sh +29 -10
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +5 -3
- package/skills/write-executable-plan/SKILL.md +1 -0
- package/hooks/agent-teams-h3-test.sh +0 -362
- package/scripts/spikes/h3-agent-teams/preflight.sh +0 -53
- package/scripts/spikes/h3-agent-teams/run-h3.sh +0 -112
- package/scripts/spikes/h3-agent-teams/setup.sh +0 -137
- package/scripts/spikes/h3-agent-teams/toggle.sh +0 -38
|
@@ -326,7 +326,7 @@ Operator-selected issues (from Phase 6) are appended AFTER this loop, not interl
|
|
|
326
326
|
|
|
327
327
|
## Phase 7.1: Issue Premise Verification (#730/H3)
|
|
328
328
|
|
|
329
|
-
>
|
|
329
|
+
> Housekeeping too (#1521).
|
|
330
330
|
> Runs on the shortlisted candidate issues from Phase 6 Pattern Recognition
|
|
331
331
|
> (cap: 8 issues — cost control; prioritize the issues most likely to enter scope).
|
|
332
332
|
|
|
@@ -354,7 +354,7 @@ Invokes `buildLiveSignals` (single SSOT for the signals shape) then `selectMode(
|
|
|
354
354
|
|
|
355
355
|
> Skip silently when `persistence: false` (no ledger for the event). Runs BEFORE the first writing action; everything not stated here is in `skills/_shared/fleet-protocol.md`.
|
|
356
356
|
|
|
357
|
-
Build the check-in JSON (fields per the reference; `session` from `.orchestrator/current-session.json`, `modus` = session type, `kandidaten` = the issue numbers you intend to claim) and pipe it into `node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/fleet-checkin.mjs"` — it validates, stores `checkin/<session_id>.json` (0600) under `~/.config/navigator/` and emits `orchestrator.fleet.checkin`. Its stdout carries `navigator_state` and `navigator_adresse` (the lease's validated `adresse`, else `null`): on `active`, and only when `navigator_adresse` is non-null and reachable via `ListAgents`, send ONE hint to exactly that name, naming the file path — never expect or wait for a reply (CSM-004). Then read `auflagen/<session_id>.json`; if it is still absent at the stdout's `fallback_due_at`, the reference's Standard-Auflagen apply (the CLI computes that deadline: `zeit` plus `CHECKIN_FALLBACK_MIN` while a navigator is active, `zeit` itself — immediately — otherwise). Reading work and edits in the own worktree continue meanwhile.
|
|
357
|
+
Build the check-in JSON (fields per the reference; `session` from `.orchestrator/current-session.json`, `modus` = session type, `kandidaten` = the issue numbers you intend to claim) and pipe it into `node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/fleet-checkin.mjs"` — it validates, stores `checkin/<session_id>.json` (0600) under `~/.config/navigator/` and emits `orchestrator.fleet.checkin`. Its stdout carries `navigator_state` and `navigator_adresse` (the lease's validated `adresse`, else `null`): on `active`, and only when `navigator_adresse` is non-null and reachable via `ListAgents`, send ONE hint to exactly that name, naming the file path — never expect or wait for a reply (CSM-004). Then read `auflagen/<session_id>.json` via `node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/fleet-auflagen.mjs" <session_id>` (only a file whose `repo` resolves to this repo's git common dir is `accepted`; a `rejected` one counts as absent); if it is still absent at the stdout's `fallback_due_at`, the reference's Standard-Auflagen apply (the CLI computes that deadline: `zeit` plus `CHECKIN_FALLBACK_MIN` while a navigator is active, `zeit` itself — immediately — otherwise). Reading work and edits in the own worktree continue meanwhile.
|
|
358
358
|
|
|
359
359
|
## Phase 8: Structured Presentation & Q&A
|
|
360
360
|
|
|
@@ -18,6 +18,7 @@ For each claim, run exactly ONE Grep or Read against the actual codebase (or
|
|
|
18
18
|
transcript discipline as `.claude/rules/parallel-sessions.md` § PSA-006.
|
|
19
19
|
Budget: this phase is a spot-check, not a deep audit. If one grep cannot
|
|
20
20
|
ground the claim, mark UNVERIFIED and move on — do not rabbit-hole.
|
|
21
|
+
Per issue, also run `git log --grep='#<N>'`: fixes often land without closing the issue.
|
|
21
22
|
|
|
22
23
|
## Verdicts
|
|
23
24
|
|
|
@@ -44,4 +45,4 @@ decomposing) without re-running the greps.
|
|
|
44
45
|
|
|
45
46
|
- Re-verifying every sentence of every issue — cap 3 claims/issue, 8 issues.
|
|
46
47
|
- Treating UNVERIFIED as a blocker — it is informational only.
|
|
47
|
-
-
|
|
48
|
+
- Skipping it in housekeeping — items go stale there too (#1521: 4 of 7 were fixed).
|
|
@@ -50,7 +50,10 @@ workflow with its own verified scope, tests and review; it does not turn this en
|
|
|
50
50
|
into a fabricated wave session.
|
|
51
51
|
Before the first writing action, write the fleet check-in and read the fleet's constraints —
|
|
52
52
|
the same step as session-start Phase 7.6 (`scripts/fleet-checkin.mjs`; fields and the
|
|
53
|
-
Standard-Auflagen in `skills/_shared/fleet-protocol.md`).
|
|
53
|
+
Standard-Auflagen in `skills/_shared/fleet-protocol.md`). Read the Auflagen with
|
|
54
|
+
`node scripts/fleet-auflagen.mjs <session_id>`, never the file directly: only a file whose `repo`
|
|
55
|
+
resolves to this repo's git common dir is `accepted`, and a `rejected` one counts as absent
|
|
56
|
+
(`skills/_shared/fleet-protocol.md` § `auflagen/<session_id>.json`). If no `auflagen/` file has appeared by
|
|
54
57
|
the `fallback_due_at` on the CLI's stdout, the Standard-Auflagen apply: the CLI sets it
|
|
55
58
|
`CHECKIN_FALLBACK_MIN` after `zeit` while a navigator is active, and to `zeit` itself —
|
|
56
59
|
immediately — otherwise. A hint to the navigator is optional and never awaited.
|
|
@@ -6,6 +6,7 @@ description: >
|
|
|
6
6
|
layout. Read-only side-channel observability — the coordinator chat stays in the operator's original
|
|
7
7
|
terminal. Trigger phrases: "tmux layout", "split panes for ci watch", "visualize session side-channels",
|
|
8
8
|
"show me state-md tail and ci".
|
|
9
|
+
user-invocable: true
|
|
9
10
|
model: inherit
|
|
10
11
|
color: cyan
|
|
11
12
|
tools: Read, Bash, Grep, Glob
|
|
@@ -10,6 +10,7 @@ description: >
|
|
|
10
10
|
notes to vault", "vault-mirror failed at session close". <example>Context: session-end is finalizing,
|
|
11
11
|
vault-integration.mode is "warn". user: "/close" assistant: "Running vault-mirror to write
|
|
12
12
|
50-sessions/session-2026-05-17.md from the closing session record — 1 created, 0 skipped."</example>
|
|
13
|
+
user-invocable: false
|
|
13
14
|
model: haiku
|
|
14
15
|
---
|
|
15
16
|
|
|
@@ -59,7 +60,10 @@ One JSON line is written to stdout for each non-empty JSONL entry processed. Exi
|
|
|
59
60
|
| `skipped-collision-resolved` | A file at the target path has the generator marker but a different `id`; a disambiguated slug was used instead. |
|
|
60
61
|
| `skipped-foreign-owner` | A generator note at the target path was rendered from another record (#1503), and this write must not replace it. **Sessions:** its `source-record` differs from this write and occurs nowhere in this `--source` ledger (`reason: "source-record mismatch"`), or its `id` names a different session (`reason: "id mismatch"`). **Learnings:** only for the disambiguated `<slug>-<uuid8>.md` file — a mismatch on the main `<slug>.md` is routed to disambiguation instead (`skipped-collision-resolved`, see Idempotency rule 6). One `SKIP foreign owner` line goes to stderr; `--force` does not bypass it. A leak in the foreign note is masked in place — owner, content and `source-record` kept — and the line carries `healed_leak: true`. A note without `source-record` (written before #1503) stays writable. |
|
|
61
62
|
| `skipped-invalid` | Entry is missing one or more required fields; entry skipped, processing continues. |
|
|
62
|
-
| `skipped-quality-low` |
|
|
63
|
+
| `skipped-quality-low` | Learning `confidence` below `vault-mirror.quality.min-confidence` (CLI: `--quality-min-confidence`, default `0.5`), or a metrics-only session whose note ALREADY exists (left untouched; the pruner decides). Carries `reason` and `path: null`. Runs **before** `--force`; `--force` does not bypass it. |
|
|
64
|
+
| `skipped-metrics-only` | Session record below the narrative gate (#1513, see Retention): no note; one row was added to the month rollup `50-sessions/<repo>/_rollup-YYYY-MM.md` (`path`). An unchanged rollup is `skipped-noop`. |
|
|
65
|
+
| `skipped-expired` | Learning past its `expires_at` with no note yet (#1513): nothing is created. |
|
|
66
|
+
| `skipped-duplicate-insight` | A mirror-owned, non-archived note in the same namespace (or the flat layout) already carries the same normalised insight (#1513); `path` names it. Only consulted where a NEW file would be created. |
|
|
63
67
|
|
|
64
68
|
### Output line shape
|
|
65
69
|
|
|
@@ -92,10 +96,32 @@ The numeric prefix (`50-sessions/`, `40-learnings/`) follows the vault folder or
|
|
|
92
96
|
|
|
93
97
|
**Path contract note:** Issue #187 shipped the flat numeric-prefix layout (deferring the per-`<repo>` subfolder it sketched). Issue #660 adds that per-repo subfolder for write-isolation (above) while keeping the numeric-prefix ordering. If you need a different layout, file a new issue — do NOT silently change the script.
|
|
94
98
|
|
|
95
|
-
**Learning-coverage self-healing after a store recovery (#740).** vault-mirror always mirrors the CURRENT `learnings.jsonl` store contents at run time — if an earlier session's store was truncated or corrupted and later recovered (e.g. from a `.bak`), the resulting transient coverage gap in the vault is closed automatically by the next mirror run, with no manual backfill required. Note also that `vault-mirror.quality.min-narrative-chars` (see
|
|
99
|
+
**Learning-coverage self-healing after a store recovery (#740).** vault-mirror always mirrors the CURRENT `learnings.jsonl` store contents at run time — if an earlier session's store was truncated or corrupted and later recovered (e.g. from a `.bak`), the resulting transient coverage gap in the vault is closed automatically by the next mirror run, with no manual backfill required. Note also that `vault-mirror.quality.min-narrative-chars` (see Retention below) gates **sessions only** — `processLearning` never applies a narrative-length filter, so a short learning `insight` is never skipped for length; only the confidence gate (`vault-integration.quality.min-confidence`) applies to learnings.
|
|
96
100
|
|
|
97
101
|
**Host-local pseudonym mapping (Epic #725 D5).** Without a map, EVERY owner-leaky repo namespace collapses to a single `redacted-repo/` subdir — which breaks the #660 write-isolation (N private repos share one directory → id/slug collisions) and destroys cross-repo attribution. A host-local **pseudonym map** fixes this: each real repo slug is assigned a stable, non-leaky pseudonym, so the vault gets `40-learnings/<pseudonym>/` (and matching `source-repo` frontmatter, via W2's attribution threading) instead of everyone landing in `redacted-repo/`. The map is a JSON object `{ "real-slug": "pseudonym-slug", … }` at a host-local path referenced by `owner.yaml` → `paths.namespace-map-path` (env `SO_NAMESPACE_MAP` overrides; precedence env > owner.yaml > unset). `resolveRepoNamespace()` consults the map ONLY at the redaction site (i.e. only when a repo would otherwise be redacted), looking up both the sanitised segment and the raw identifier; a hit returns the pseudonym and skips redaction, so clean/public repos never touch the map (or its lazy owner.yaml read). **Privacy guarantee:** the map file lives OUTSIDE every repo (alongside `owner.yaml` under `~/.config/session-orchestrator/`) and is never committed — only the pseudonyms reach the vault, the real names stay host-local. Each pseudonym is validated (must be a kebab slug AND must not itself be owner-leaky); invalid or leaky pseudonyms are dropped with a WARN. **Fallback:** an absent, unreadable, or malformed map — and any UNMAPPED owner-leaky repo — still redacts to `redacted-repo`, identical to pre-#725 behaviour. Implementation: `scripts/lib/vault-mirror/pseudonym-map.mjs` (`loadPseudonymMap`) + `namespace.mjs`.
|
|
98
102
|
|
|
103
|
+
## Retention (#1513)
|
|
104
|
+
|
|
105
|
+
The mirror used to write every record and never mark or remove anything; by 2026-10-03 the two zones were 78 % of the Meta-Vault, 91 % of the session notes carried nothing beyond git, and 2.826 expired learnings still read `status: draft`. The rules live in ONE module, `scripts/lib/vault-mirror/retention.mjs`, shared by the mirror and the pruner:
|
|
106
|
+
|
|
107
|
+
1. **Narrative gate (sessions).** A record gets its own note only when its free-text fields `notes`, `narrative`, `summary` (strings or string arrays, whitespace collapsed) reach `vault-mirror.quality.min-narrative-chars` (default `400`; `0` disables). Wave/agent/file tables are not narrative. All three renderer generations print exactly these fields into `## Notes` (`sessionNarrativeText`), which is what the pruner measures; an existing note with less free text than its record is rewritten once (heal), and when a ledger holds the same `session_id` twice the winning record inherits the narrative fields it lacks from the line it supersedes. Below the gate the record becomes one row (date, session id, type, waves, agents, files) in `50-sessions/<repo>/_rollup-YYYY-MM.md`, keyed by session id, sorted and rendered deterministically — re-runs are no-ops. Calibration (Jev audit, 3.003 notes): free text under 300 chars held 1 of 2.802 notes scored >= 2.5 "beyond git"; from 800 chars on, 18 of 55.
|
|
108
|
+
2. **Legacy slug (learnings).** The pre-#725 slug (subject without the whitespace → hyphen pre-map) is recognised: when only that variant of THIS record's note exists, it is updated in place instead of a canonical twin being created. New notes always get the canonical slug.
|
|
109
|
+
3. **Write-time dedupe (learnings).** Before a new file is created, the normalised `## Insight` (case, whitespace, typographic quotes/dashes, trailing punctuation ignored) is looked up among the mirror-owned, non-archived notes of the namespace, and among flat notes whose `source-repo` is this namespace → `skipped-duplicate-insight`. Placeholder insights (`(legacy record …`, `(none recorded)`, …) and keys under 40 characters never count as the same learning.
|
|
110
|
+
5. **Namespace alias.** Folders equal modulo hyphens are one namespace; the one with more hyphens is canonical (`gotzendorferv2` → `gotzendorfer-v2`). The mirror resolves this before every write and for its auto-commit scope; the pruner uses the same function (`canonicalAmong`).
|
|
111
|
+
4. **Expiry and leaving the store (learnings).** An expired record (`expires_at` before today) is never created; its existing note is re-rendered with `status: archived` + `archived-reason: expired`. After the live pass, `--kind learning` also reads the sibling `learnings-archive.jsonl` and marks each archived record's existing note `archived` with `archived-reason: expired` (`_archive_reason: expired`) or `superseded` (every other reason) — never creating one, never touching a note a live record still owns. `archived` + `archived-reason` instead of `status: expired|superseded` on purpose: the vault frontmatter status enum (SSOT projects-baseline) has no such values, and a status outside it fails vault-sync in any vault that validates the mirror zones.
|
|
112
|
+
|
|
113
|
+
**Bestand: `scripts/vault-mirror-prune.mjs`.** Applies the same rules to notes written before them. Dry run is the default and writes nothing; `--manifest PATH` writes the JSON manifest (`path`, `reason`, `action`, `target`, `keep`, `rollup`). Expiry is decided first, so an expired or archived copy is never the one kept. Reasons: `duplicate` (canonical copy among the LIVE copies: own namespace folder > any namespace folder > `status: verified` > later `expires` > `source-record` > post-#725 slug > newer `updated`), `superseded` (same slug modulo hyphens, different insight; newest kept), `expired`, `metrics-only-session` (its rollup row is written first), `namespace-alias` (a folder that equals another modulo hyphens and has fewer of them, or `--alias FROM=TO`), `flat-relocate` (flat note whose `source-repo` names its namespace). `--apply` writes the rollups, then moves archived notes to `90-archive/mirror/<zone>/<same path>` with `status: archived` + `archived-reason`, then renames the relocated ones — never deletes, never touches a note without the `_generator` marker, re-checks ownership right before each move, refuses any source or target that resolves outside the vault (realpath of the nearest existing ancestor), writes a note whose archive target is occupied by OTHER content beside it as `<name>.dup-<hash8>.md` (listed under `applied.suffixed` in the manifest), and is idempotent (a second run plans nothing). `90-archive/` is outside vault-sync's checked zones but inside its link-target register, so wiki-links into archived notes keep resolving; the mirror's source-session index walks `90-archive/mirror/50-sessions/` for the same reason, and `backfill-learnings-from-vault.mjs` falls back to `90-archive/mirror/40-learnings/`.
|
|
114
|
+
|
|
115
|
+
**Order (runbook).** (1) Update the plugin on EVERY host that runs session-end against this vault (M4 and M5): an old mirror re-creates every note the pruner moved. (2) Run one mirror pass per repo ledger so session notes written before the narrative fields were rendered are healed — otherwise the pruner reads them as metrics-only. (3) Dry run, read the manifest. (4) `--apply`, then commit the vault.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# (2) per repo, from its canonical checkout
|
|
119
|
+
node scripts/vault-mirror.mjs --vault-dir <vault> --source <repo>/.orchestrator/metrics/sessions.jsonl --kind session --repo-root <repo> --no-commit
|
|
120
|
+
# (3) + (4)
|
|
121
|
+
node scripts/vault-mirror-prune.mjs --vault-dir <vault> --manifest /tmp/vault-prune-manifest.json # plan
|
|
122
|
+
node scripts/vault-mirror-prune.mjs --vault-dir <vault> --apply --manifest /tmp/vault-prune-applied.json
|
|
123
|
+
```
|
|
124
|
+
|
|
99
125
|
## Idempotency
|
|
100
126
|
|
|
101
127
|
vault-mirror is safe to run multiple times against the same JSONL source:
|
|
@@ -10,6 +10,7 @@ description: >
|
|
|
10
10
|
vault-sync". <example>Context: session-end Phase 1 quality gate, vault-sync.enabled=true,
|
|
11
11
|
vault-sync.mode="hard". user: "/close" assistant: "vault-sync found 2 frontmatter errors in
|
|
12
12
|
vault/40-learnings/ml-notes.md — missing required `id` field. Fixing before close."</example>
|
|
13
|
+
user-invocable: false
|
|
13
14
|
model: haiku
|
|
14
15
|
---
|
|
15
16
|
|
|
@@ -28,9 +29,8 @@ Phase 1 ships a self-contained validator that reads every `.md` file under `VAUL
|
|
|
28
29
|
### Files
|
|
29
30
|
|
|
30
31
|
- `validator.mjs` — Node.js ESM validator. Uses `zod` + `yaml` npm packages. Reads `VAULT_DIR` (env or default cwd), walks the tree, skipping `node_modules/`, `.git/`, `.obsidian/`. `90-archive/` is walked but never *checked* (#833): archived notes stay in the link-target register — so an inbound `[[wiki-link]]` to an archived note resolves instead of dangling — while their frontmatter is skipped and counted in `archived_skipped_count`. For each `.md`: parses frontmatter, validates against the inline Zod schema, extracts `[[wiki-links]]`, verifies each target resolves. Emits JSON report on stdout.
|
|
31
|
-
- `validator.sh` — Thin POSIX wrapper. Resolves `VAULT_DIR` from arg 1 or env,
|
|
32
|
-
- `package.json` — Declares `zod` (`^3.24.0`, matching projects-baseline) and `yaml` (`^2.5.0`) as deps. `
|
|
33
|
-
- `tests/validator.bats` — 16 BATS cases covering clean vaults, broken frontmatter, missing required fields, dangling links, no-vault skipping, README-style files, nested directories, and archive/obsidian exclusion.
|
|
32
|
+
- `validator.sh` — Thin POSIX wrapper. Resolves `VAULT_DIR` from arg 1 or env, checks that `zod` and `yaml` resolve from the skill directory, execs the Node validator. It never installs anything: unresolvable deps exit 2 with `"reason":"setup-required"` and the one setup command, `npm ci` in the plugin root (#1070). Session-end and other callers use this entry point.
|
|
33
|
+
- `package.json` — Declares `zod` (`^3.24.0`, matching projects-baseline) and `yaml` (`^2.5.0`) as deps. No lockfile is committed (`skills/vault-sync/package-lock.json` is gitignored at the repo root); `node_modules/` is gitignored.
|
|
34
34
|
- `tests/fixtures/` — Seven fixture vaults matching each test scenario.
|
|
35
35
|
|
|
36
36
|
### Schema source
|
|
@@ -41,9 +41,9 @@ The inline Zod schema is vendored from the canonical source at `projects-baselin
|
|
|
41
41
|
|
|
42
42
|
`skills/vault-sync/package.json` pins `yaml ^2.5.0` / `zod ^3.24.0` — intentionally NOT the root's `yaml ^2.9.0` / `zod ^3.25.76`. This is not drift to fix:
|
|
43
43
|
|
|
44
|
-
-
|
|
45
|
-
- The root `package.json` declares no `workspaces`, so `npm ci` at the root never touches this folder's deps
|
|
46
|
-
- The `zod ^3.24.0` pin deliberately tracks the projects-baseline version, not this repo's own (`scripts/release.mjs:
|
|
44
|
+
- Nothing installs this sub-package. Neither CI host runs a nested install any more — both run only the root `npm ci`, and `.husky/pre-push` links only the root `node_modules/` into its tracked-tree clone. The nested install was dropped on 2026-10-05: it pulled unlocked `zod`/`yaml` versions (this folder's lockfile is gitignored) that the root `npm audit` never saw, and the vault-sync test files ran 125 passed / 0 failed without it.
|
|
45
|
+
- The root `package.json` declares no `workspaces`, so `npm ci` at the root never touches this folder's deps. Node resolves `zod`/`yaml` from this folder via the root's own `dependencies` (`yaml ^2.9.0`, `zod ^3.25.76`) — measured 2026-10-05 via `import.meta.resolve('zod')` from this folder → `<root>/node_modules/zod`. That is the runtime path in CI, in every worktree and for every end user, so the root dependencies must stay. A stale local `skills/vault-sync/node_modules/` from an older manual install would still win resolution; delete it if versions disagree.
|
|
46
|
+
- The `zod ^3.24.0` pin deliberately tracks the projects-baseline version, not this repo's own (`scripts/release.mjs:622`).
|
|
47
47
|
|
|
48
48
|
The root's dependency versions are NOT the SSOT for this folder — do not "fix" this pin to match the root.
|
|
49
49
|
|
|
@@ -8,13 +8,18 @@
|
|
|
8
8
|
# Behavior:
|
|
9
9
|
# - Resolves VAULT_DIR from arg 1 or env.
|
|
10
10
|
# - Ensures `node` is available.
|
|
11
|
-
# -
|
|
11
|
+
# - Checks that `zod` and `yaml` resolve from this directory. They are
|
|
12
|
+
# runtime dependencies of the ROOT package (package.json `dependencies`),
|
|
13
|
+
# so Node resolves them from the plugin root's node_modules/. Missing →
|
|
14
|
+
# exit 2 with status `setup-required` and the one npm-canonical setup
|
|
15
|
+
# command. Never installs anything itself (#1070 AC3/AC6).
|
|
12
16
|
# - Execs validator.mjs; propagates exit code.
|
|
13
17
|
#
|
|
14
18
|
# Exit codes (mirror validator.mjs):
|
|
15
19
|
# 0 — vault valid (or skipped: no vault)
|
|
16
20
|
# 1 — validation errors
|
|
17
|
-
# 2 — infrastructure error (missing node
|
|
21
|
+
# 2 — infrastructure error (missing node, missing validator.mjs,
|
|
22
|
+
# dependencies not installed → status `setup-required`)
|
|
18
23
|
#
|
|
19
24
|
# Output: JSON report on stdout (machine-readable).
|
|
20
25
|
|
|
@@ -36,18 +41,32 @@ if ! command -v node >/dev/null 2>&1; then
|
|
|
36
41
|
exit 2
|
|
37
42
|
fi
|
|
38
43
|
|
|
44
|
+
# Emit the infra-error envelope via node so a path containing a quote or a
|
|
45
|
+
# backslash still yields valid JSON (paths travel through env, never through
|
|
46
|
+
# the JSON text). Args: reason [detail] [setup].
|
|
47
|
+
infra_error() {
|
|
48
|
+
SO_VS_REASON="$1" SO_VS_DETAIL="${2:-}" SO_VS_SETUP="${3:-}" node -e '
|
|
49
|
+
const o = { status: "infra-error", reason: process.env.SO_VS_REASON };
|
|
50
|
+
if (process.env.SO_VS_DETAIL) o.detail = process.env.SO_VS_DETAIL;
|
|
51
|
+
if (process.env.SO_VS_SETUP) o.setup = process.env.SO_VS_SETUP;
|
|
52
|
+
console.error(JSON.stringify(o));' >&2
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
if [[ ! -f "$VALIDATOR_MJS" ]]; then
|
|
40
|
-
|
|
56
|
+
infra_error "validator.mjs not found at $VALIDATOR_MJS"
|
|
41
57
|
exit 2
|
|
42
58
|
fi
|
|
43
59
|
|
|
44
|
-
# ──
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
# ── Dependency readiness (no install, no foreign package manager) ─────────
|
|
61
|
+
# `zod`/`yaml` come from the root package's `dependencies`; a nested
|
|
62
|
+
# skills/vault-sync/node_modules/ is NOT required. Probe resolution from this
|
|
63
|
+
# directory instead of testing for a particular node_modules folder, so an npm
|
|
64
|
+
# install (deps hoisted above the package) and a plugin checkout both pass.
|
|
65
|
+
if ! (cd "$SCRIPT_DIR" && node --input-type=module \
|
|
66
|
+
-e "import.meta.resolve('zod'); import.meta.resolve('yaml');") >/dev/null 2>&1; then
|
|
67
|
+
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
68
|
+
infra_error "setup-required" "zod/yaml not resolvable from $SCRIPT_DIR" "cd $(printf %q "$PLUGIN_ROOT") && npm ci"
|
|
69
|
+
exit 2
|
|
51
70
|
fi
|
|
52
71
|
|
|
53
72
|
# ── Execute validator ──────────────────────────────────────────────────────
|
|
@@ -87,7 +87,7 @@ Before each wave dispatch:
|
|
|
87
87
|
|
|
88
88
|
The per-agent path IS `$AGENT_FILESCOPE_JSON` — the same file `--assert-subset` (#796 below), Grounding Injection (#85), the Learnings-Index (#1014) and File-Scope Injection (#1020) consume. Never write a `$TMPDIR` copy: it degrades to a signal-free allow when an injector cannot find the addressable wave-keyed file. The coordinator's record is materialized as `coordinator.json` and included in the aggregate, so its direct edits are covered by the two checks below.
|
|
89
89
|
|
|
90
|
-
> **`<state-dir>/filescopes/` is control state, like `wave-scope.json` itself — never a wave territory.** Step 3.1 necessarily runs before the union exists, so
|
|
90
|
+
> **`<state-dir>/filescopes/` is control state, like `wave-scope.json` itself — never a wave territory.** Step 3.1 necessarily runs before the union exists, so `hooks/post-bash-write-verify.mjs` drops these files from its out-of-scope report the same way it drops `wave-scope.json` — keyed on the active manifest's state dir (`isScopeDeclarationPath`, #1027 Pkt 5) — and writing them reports nothing. Never widen `allowedPaths` instead: that would grant agents write access to the deconfliction record itself.
|
|
91
91
|
|
|
92
92
|
**3.1a — a peer session's declared paths are their OWN record (#1195).** When a reachable peer session runs in this same checkout and has sent a Peer-Scope-Union request (`skills/_shared/parallel-aware-auq.md` § Peer-Scope-Union), carry its complete path list into the record array as exactly ONE record `{"id":"peer-session-<id>","files":[…]}` — `<id>` being the peer's semantic session id. Never merge peer paths into an agent's record: an agent record is a territory ONE agent may write, and a peer's paths are a territory NO agent of this wave may write. Keeping them separate is also what makes the disjointness check meaningful — a peer path colliding with an agent's scope is a real collision that must surface at 3.2, not be laundered by living in that agent's own record. Mechanically, a peer record takes part in `--assert-disjoint` and is EXCLUDED from `--union` (`unionFileScopes`, `scripts/lib/scope-gate.mjs`), so `allowedPaths` never grants a peer's paths to this wave's agents — which is precisely what keeps them a territory no agent may write. It also gets NO per-agent shape-(a) file: `materialize-wave-scope.mjs` writes peer records into the aggregate only, because `$AGENT_FILESCOPE_JSON` is addressed by an agent id at dispatch and no agent is dispatched for a peer session. `hooks/post-bash-write-verify.mjs` reads the same aggregate sidecar and reports a change under a `peer-session-*` record as a peer write instead of a violation — reachable only BECAUSE the union excludes it (the hook sees a path only while it is outside `allowedPaths`).
|
|
93
93
|
|
|
@@ -121,9 +121,11 @@ Before each wave dispatch:
|
|
|
121
121
|
|
|
122
122
|
> **Fail-closed:** an ABSENT or unrecognised `role` does **not** expand. Omitting it fails loudly (an agent's write to its own test is blocked, recoverable by one re-union); the opposite default would silently hand a Quality phase-1 simplification agent write access to the suite. `{ enabled: false }` is the unconditional opt-out and `{ enabled: true }` the explicit opt-in — both override the role.
|
|
123
123
|
|
|
124
|
-
**It emits a GLOB, never a computed concrete path.** Resolve via the production file's basename, e.g. `foo.mjs` → `tests/**/foo*.test.mjs` — the same form `§ 4. Test-consolidation branch` already uses, stated once.
|
|
124
|
+
**It emits a GLOB, never a computed concrete path.** Resolve via the production file's basename, e.g. `foo.mjs` → `tests/**/foo*.test.mjs` — the same form `§ 4. Test-consolidation branch` already uses, stated once. The measurement is `scripts/lib/test-sibling-coverage.mjs`, not a count restated here — run `node scripts/lib/test-sibling-coverage.mjs --json` and cite its envelope (it carries its own definition, `ref` and the uncovered list). It counts production files by negation (every tracked file a sibling-rule source matches that is not test-side) and reports three figures: **glob** (the scope gate's own `testSiblingsFor()` + `pathMatchesPattern()`), **exact** (a `tests/**/<stem>.test.mjs` exists), **mirror** (the naive 1:1 path `tests/<path minus scripts/>.test.mjs`). Measured at `HEAD=af58a173`, 2026-10-03, index clean, over the script's population (tracked `.mjs` matched by the sibling-rule source minus every test-side path, 571 files): glob **511/571 (89.5 %)**, exact **491/571 (86.0 %)**, mirror **353/571 (61.8 %)**. So the glob is right ~9 times in 10 and *harmless* when wrong — it grants write access to a path that may not exist; a computed mirror path would be wrong ~38 % of the time **and still deny the real test**. The residual is real, mostly semantic naming (`scripts/lib/learnings/*.mjs` → `tests/unit/learnings.test.mjs`) and untested root config files: when an agent's test sibling does not match the glob, add it by hand to that agent's "Files:" scope in the session plan. This is a default, not a guarantee.
|
|
125
125
|
|
|
126
|
-
>
|
|
126
|
+
> Re-measure before citing these figures downstream — a count re-briefed later is a claim about the past (`.claude/rules/parallel-sessions.md` § PSA-006). The previous hand count (`730ee9d`, 2026-08-03: 375 / 272 of 439, denominator `scripts/` + `hooks/` + `skills/` only) is superseded by the script (#1030 P3).
|
|
127
|
+
|
|
128
|
+
> **Over-grant ceiling (#1030):** the glob is a basename *prefix* (`tests/**/{basename}*.test.mjs`, `scripts/lib/scope-gate.mjs:312`), so a production file sharing a basename with another receives the other's test siblings — and a stem that prefixes another (`cli` → `cli-flags`) does too, so the figure below is a lower bound. Measured at `HEAD=0e0c4b08`, 2026-10-03: `git ls-files '*.mjs' | grep -v '^tests/' | xargs -n1 basename | sort | uniq -d | wc -l` → **34** colliding basenames, covering **80 of 572** production `.mjs` (**14.0 %**; population = tracked `.mjs` outside `tests/` at `0e0c4b08`, which still counts the one co-located test `skills/vault-sync/tests/schema-drift.test.mjs` — the whole gap to the script's 571, the same recipe also gives 572 at `af58a173`; files via `… | sort | uniq -D | wc -l`, denominator via `git ls-files '*.mjs' | grep -v '^tests/' | wc -l`; top: `schema.mjs` 8×). The error direction is the harmless one — a too-wide grant, never a denied write. **Revisit-Trigger:** a plan author disables the sibling expansion to dodge a false-positive collision between two same-named production files — then scope the glob by directory, but NOT by a raw `{dir}`: `renderSiblingTemplate` fills it with `scripts/lib`, while the test tree drops the `scripts/` prefix (`tests/lib/`), so a raw `{dir}` would deny real test writes; it needs a source→test prefix mapping first.
|
|
127
129
|
|
|
128
130
|
**The sibling rule is repo-configurable, not a hardcoded layout.** THIS repo has zero `__tests__/` directories and no co-located tests; consumer-repo shapes (`<file>.test.*` beside the source, `<dir>/__tests__/**`, `supabase/migrations/** → supabase/tests/**`) are configured per repo and do not apply here.
|
|
129
131
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: write-executable-plan
|
|
3
3
|
description: Use when you have a PRD or design spec and need a bite-sized, executable implementation plan that any agent can follow without re-deriving structure. Produces `docs/plans/YYYY-MM-DD-<feature>.md` with per-task Files block, complete code per step (no placeholders), and exact verification commands. Rejects "TBD", "TODO", "add error handling", "similar to Task N".
|
|
4
|
+
user-invocable: true
|
|
4
5
|
model: inherit
|
|
5
6
|
color: green
|
|
6
7
|
tools: Read, Grep, Glob, Bash, Write
|