@mmerterden/multi-agent-pipeline 12.3.0 → 12.5.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/package.json +1 -1
  3. package/pipeline/commands/multi-agent/forget/SKILL.md +29 -0
  4. package/pipeline/commands/multi-agent/help/SKILL.md +18 -0
  5. package/pipeline/commands/multi-agent/routines/SKILL.md +30 -0
  6. package/pipeline/commands/multi-agent/save/SKILL.md +46 -0
  7. package/pipeline/commands/multi-agent/sync/SKILL.md +4 -4
  8. package/pipeline/lib/extract-conventions.sh +1 -0
  9. package/pipeline/lib/repo-cache.sh +1 -0
  10. package/pipeline/lib/shadow-git.sh +8 -0
  11. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -3
  12. package/pipeline/multi-agent-refs/phases/phase-0-init.md +9 -1
  13. package/pipeline/multi-agent-refs/phases/phase-4-review.md +3 -2
  14. package/pipeline/multi-agent-refs/phases/phase-5-test.md +10 -0
  15. package/pipeline/multi-agent-refs/phases.md +2 -0
  16. package/pipeline/multi-agent-refs/prompt-assembly.md +31 -0
  17. package/pipeline/schemas/learnings-ledger.schema.json +4 -0
  18. package/pipeline/schemas/migrations/prefs-2.3.0-to-2.4.0.mjs +32 -0
  19. package/pipeline/schemas/prefs.schema.json +36 -1
  20. package/pipeline/schemas/token-budget.json +2 -2
  21. package/pipeline/schemas/triage-corpus.schema.json +5 -1
  22. package/pipeline/scripts/eval-mine-corpus.mjs +19 -5
  23. package/pipeline/scripts/fixtures/install-layout.tsv +7 -7
  24. package/pipeline/scripts/gc-worktrees.sh +23 -1
  25. package/pipeline/scripts/learning-curve.mjs +167 -0
  26. package/pipeline/scripts/learnings-ledger.mjs +9 -2
  27. package/pipeline/scripts/migrate-prefs.mjs +8 -1
  28. package/pipeline/scripts/repo-map.mjs +1 -1
  29. package/pipeline/scripts/routine-registry.mjs +226 -0
  30. package/pipeline/scripts/smoke-generate-issue.sh +3 -3
  31. package/pipeline/scripts/smoke-learning-curve.sh +61 -0
  32. package/pipeline/scripts/smoke-migrate-state.sh +3 -3
  33. package/pipeline/scripts/smoke-pref-migration.sh +8 -6
  34. package/pipeline/scripts/smoke-review-readiness.sh +1 -1
  35. package/pipeline/scripts/smoke-routines.sh +84 -0
  36. package/pipeline/scripts/triage-memory.mjs +48 -10
  37. package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +22 -0
  38. package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +20 -0
  39. package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +27 -0
  40. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +4 -4
package/CHANGELOG.md CHANGED
@@ -14,6 +14,77 @@ Internal file-layout changes that don't affect the slash-command surface are sti
14
14
 
15
15
  ---
16
16
 
17
+ ## [12.5.0] - 2026-07-24
18
+
19
+ Repo learning loop: repeated runs on the same repo produce better and cheaper
20
+ output over time. Plus a round of worktree hardening.
21
+
22
+ ### Added
23
+
24
+ - **Freshness gate** for the triage corpus: `triage-memory` stamps each stored
25
+ row with the file's git `file_sha`, and queries annotate `stale=true` when
26
+ the file changed since the lesson was recorded, so dead lessons stop
27
+ resurfacing in later runs.
28
+ - **Causal diagnosis** on lessons: `learnings-ledger` rows carry an optional
29
+ `diagnosis` (the verbal root-cause "why", per Reflexion), and Phase 4's
30
+ lesson-memory loop now records it. `brief` renders it as `(why: ...)` so the
31
+ reason, not just the outcome, re-enters Phase 1 on the next run.
32
+ - **`learning-curve.mjs`** (+ smoke): a time-bucketed trend over
33
+ `metrics.jsonl` (first-pass clean rate, review cycles, rework per task,
34
+ tokens per task, cache ratio) that shows whether a repo's runs are improving.
35
+ - **`prompt-assembly.md`**: stable-prefix prompt-cache guidance, referenced
36
+ from `phases.md`, so lazy-loaded phase docs stay cache-friendly.
37
+
38
+ ### Changed
39
+
40
+ - Worktree handling hardened: `.worktrees/` is now a first-class member of the
41
+ traversal-prune skip set (`node_modules`, `Pods`, `.build`, `DerivedData`,
42
+ `.next`) across every tree walker (`repo-map`, `repo-cache`,
43
+ `extract-conventions`, `shadow-git`), so no walker descends into a worktree
44
+ checkout. Phase 0 gains an explicit residue-guard + traversal-prune contract;
45
+ Phase 5 heals stale worktree admin state before recreating a worktree.
46
+ - `eval-mine-corpus` now reads the corpus from the per-repo memory dir it is
47
+ actually written to (was a path that never received data).
48
+ - Phase-doc total token budget recalibrated 50000 -> 51000 for the new
49
+ contracts (prose compressed first; every per-phase max still passes).
50
+
51
+ ### Fixed
52
+
53
+ - `gc-worktrees.sh` anchors the repo on the main worktree and resolves paths on
54
+ both sides, so orphan detection no longer misfires from inside a worktree.
55
+
56
+ ## [12.4.0] - 2026-07-23
57
+
58
+ User-defined routines: manage your own recurring, project-specific jobs as
59
+ first-class `/multi-agent` commands.
60
+
61
+ ### Added
62
+
63
+ - **`/multi-agent:save`** turns a recurring job into a reusable
64
+ `/multi-agent:<name>` command. It leads with the work actually executed in
65
+ the current session (distilling repeatable jobs from what was just done),
66
+ then adds named procedures found in `~/.claude/CLAUDE.md`, and offers them in
67
+ a multi-select picker: pick one, pick several to combine into one routine
68
+ (steps concatenated in order), or describe a new one via free text.
69
+ - **`/multi-agent:routines`** lists saved routines with what each does, in
70
+ `outputLanguage`. **`/multi-agent:forget`** removes one (guarded: never
71
+ touches a shipped command).
72
+ - Backed by `routine-registry.mjs` (add/list/remove/get; name validation,
73
+ collision + shipped-command guards, atomic prefs write). Saved routines are
74
+ `local-only: true` command dirs + a `prefs.global.routines` registry entry:
75
+ preserved across `/multi-agent:update` by the install snapshot/restore,
76
+ rejected from the public repo by the sync backstops, and never counted in the
77
+ command inventory.
78
+ - Registered in `/multi-agent:help` (EN + TR "Routines" section with a dynamic
79
+ listing of saved routines).
80
+
81
+ ### Changed
82
+
83
+ - Command inventory 38 -> 41 (`save`, `routines`, `forget`); contract, both sync
84
+ docs, help EN/TR, and the count smokes updated; 3 Copilot twins added.
85
+ - prefs schema `global.routines` + `schemaVersion` 2.4.0; new
86
+ `prefs-2.3.0-to-2.4.0.mjs` migration (idempotent, adds `routines: []`).
87
+
17
88
  ## [12.3.0] - 2026-07-23
18
89
 
19
90
  `/multi-agent:analysis` overhaul: the feature-spec doc is now a development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "12.3.0",
3
+ "version": "12.5.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code and Copilot CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: "Remove a saved /multi-agent routine (created by /multi-agent:save): deletes its local-only command and its registry entry. Asks which one and confirms."
3
+ description-tr: "Kaydedilmiş bir /multi-agent rutinini (/multi-agent:save ile oluşturulan) siler: local-only komutunu ve registry kaydını kaldırır. Hangisini soracak ve onay alacak."
4
+ argument-hint: "[name] - routine to remove; asked if omitted"
5
+ allowed-tools: Bash, Read, AskUserQuestion
6
+ ---
7
+
8
+ # multi-agent forget - Remove a Routine
9
+
10
+ Delete a routine the user saved via `/multi-agent:save`. Backed by `$HOME/.claude/scripts/routine-registry.mjs`.
11
+
12
+ ## Steps
13
+
14
+ 1. **Resolve which routine.** If `$ARGUMENTS` names one, use it. Otherwise run `node "$HOME/.claude/scripts/routine-registry.mjs" list --json`, and `AskUserQuestion` (in `outputLanguage`) with one option per saved routine. No routines -> report there is nothing to forget and stop.
15
+
16
+ 2. **Confirm.** `AskUserQuestion` (in `outputLanguage`): "Remove `/multi-agent:<name>`? This deletes the routine command and its registry entry." Options: `{ label: "Remove", description: "Delete this routine" }`, `{ label: "Cancel", description: "Keep it" }`. Anything but Remove -> stop.
17
+
18
+ 3. **Remove:**
19
+ ```bash
20
+ node "$HOME/.claude/scripts/routine-registry.mjs" remove --name "<name>"
21
+ ```
22
+ The backend guards against removing a shipped command (it only deletes a dir that is BOTH registered AND `local-only: true`).
23
+
24
+ 4. **Report** (in `outputLanguage`): the routine is removed; note that its `/multi-agent:<name>` entry clears from autocomplete on the next session reload.
25
+
26
+ ## Notes
27
+
28
+ - Only user-saved routines can be forgotten; shipped `/multi-agent:*` commands are refused by the backend.
29
+ - To see what you can forget, run `/multi-agent:routines`.
@@ -131,6 +131,15 @@ Setup & Maintenance:
131
131
 
132
132
  ------------------------------------------------------------
133
133
 
134
+ Routines (your own reusable jobs):
135
+
136
+ /multi-agent:save [name] Save a recurring job as a reusable /multi-agent:<name> command
137
+ /multi-agent:routines List your saved routines and what each does
138
+ /multi-agent:forget [name] Remove a saved routine
139
+ <!-- ROUTINES_DYNAMIC: after printing the static lines above, read prefs.global.routines; if non-empty, append one indented line per routine " /multi-agent:<name> <description>" under a "Your saved routines:" subheading, in outputLanguage. If empty, print " (none yet - use /multi-agent:save)". -->
140
+
141
+ ------------------------------------------------------------
142
+
134
143
  Interactive Launchers:
135
144
 
136
145
  /multi-agent:jira List MY open Jira issues → pick → branch → mode → go
@@ -345,6 +354,15 @@ Setup & Maintenance:
345
354
 
346
355
  ------------------------------------------------------------
347
356
 
357
+ Rutinler (kendi tekrar eden işlerin):
358
+
359
+ /multi-agent:save [ad] Sık yaptığın bir işi yeniden kullanılabilir /multi-agent:<ad> komutu olarak kaydet
360
+ /multi-agent:routines Kayıtlı rutinlerini ve ne yaptıklarını listele
361
+ /multi-agent:forget [ad] Kayıtlı bir rutini kaldır
362
+ <!-- ROUTINES_DYNAMIC: yukarıdaki statik satırlardan sonra prefs.global.routines'i oku; boş değilse her rutin için " /multi-agent:<ad> <açıklama>" satırını "Kayıtlı rutinlerin:" alt başlığı altında ekle (outputLanguage). Boşsa " (henüz yok - /multi-agent:save kullan)" yaz. -->
363
+
364
+ ------------------------------------------------------------
365
+
348
366
  İnteraktif Launcher'lar:
349
367
 
350
368
  /multi-agent:jira BENİM açık Jira issue'larımı listele → seç → branch → mode → başla
@@ -0,0 +1,30 @@
1
+ ---
2
+ description: "List your saved /multi-agent routines (from /multi-agent:save) with what each one does, rendered in outputLanguage."
3
+ description-tr: "Kaydettiğin /multi-agent rutinlerini (/multi-agent:save ile) listeler; her birinin ne yaptığını outputLanguage'e göre gösterir."
4
+ argument-hint: ""
5
+ allowed-tools: Bash, Read
6
+ ---
7
+
8
+ # multi-agent routines - List Saved Routines
9
+
10
+ Show every routine the user saved via `/multi-agent:save`. Backed by `$HOME/.claude/scripts/routine-registry.mjs`.
11
+
12
+ ## Steps
13
+
14
+ 1. **Read the registry:**
15
+ ```bash
16
+ node "$HOME/.claude/scripts/routine-registry.mjs" list --json
17
+ ```
18
+
19
+ 2. **Render in `outputLanguage`.** Empty list -> tell the user there are no saved routines yet and that `/multi-agent:save` creates one. Otherwise print a compact table, one row per routine:
20
+
21
+ | Komut / Command | Ne yapar / What it does | Oluşturma / Created |
22
+ |---|---|---|
23
+ | `/multi-agent:<name>` | `<description>` | `<createdAt date>` |
24
+
25
+ 3. **Integrity note (optional).** If a registry entry has no matching `~/.claude/commands/multi-agent/<name>/` dir (or a local-only routine dir exists with no registry entry), flag it as `needs repair` so the user can re-save or `/multi-agent:forget` it. Never modify anything here - this command is read-only.
26
+
27
+ ## Notes
28
+
29
+ - Read-only. To add a routine use `/multi-agent:save`; to remove one use `/multi-agent:forget <name>`.
30
+ - Routines are local-only and never synced; this list reflects only the current machine.
@@ -0,0 +1,46 @@
1
+ ---
2
+ description: "Save a recurring job as a reusable /multi-agent:<name> command. Reviews the conversation + your CLAUDE.md for candidate routines, you pick one and name it; it is stored local-only and never synced."
3
+ description-tr: "Sık yaptığın bir işi yeniden kullanılabilir /multi-agent:<ad> komutu olarak kaydeder. Konuşmayı + CLAUDE.md'ni tarayıp aday rutinler önerir, sen seçip ad verirsin; local-only saklanır, asla sync edilmez."
4
+ argument-hint: "[name] - optional command name; asked if omitted"
5
+ allowed-tools: Bash, Read, AskUserQuestion
6
+ ---
7
+
8
+ # multi-agent save - Save a Routine
9
+
10
+ **Input**: $ARGUMENTS (optional routine name)
11
+
12
+ Turn a recurring, project-specific procedure into a first-class `/multi-agent:<name>` command. The saved routine is a **local-only** command (`local-only: true`) plus a registry entry in `prefs.global.routines`; it is never synced to the public repo (the `/multi-agent:sync` backstops enforce this).
13
+
14
+ Backed by `$HOME/.claude/scripts/routine-registry.mjs`.
15
+
16
+ ## Steps
17
+
18
+ 1. **Distill candidates from what was just done (primary source).** The user usually runs `:save` right after performing a job they want to repeat, so FIRST review the recent session: group the tasks/steps actually executed this conversation into coherent, repeatable jobs. Each distinct job is a candidate - give it a suggested short name, a one-line summary, and the ordered steps it would replay. THEN add, as further candidates, any named end-to-end procedures in `$HOME/.claude/CLAUDE.md` (heading + trigger phrase + numbered steps, e.g. a "... kontrol et" procedure). If nothing repeatable is found in the session and CLAUDE.md has none, say so and offer the free-text path only.
19
+
20
+ 2. **Pick (multi-select) + optionally combine.** `AskUserQuestion` with `multiSelect: true` (in `outputLanguage`): one option per distilled candidate (label = suggested name, description = the one-line summary), PLUS an always-present `Other` free-text path to describe a brand-new routine by hand. Semantics:
21
+ - one candidate selected -> that becomes the routine;
22
+ - several selected -> **combine them into ONE routine**, concatenating their steps in the order shown (the routine replays them in sequence); ask for a single name for the combined routine;
23
+ - `Other` -> the user types the routine themselves.
24
+ Empty selection is not consent - re-ask.
25
+
26
+ 3. **Name the command.** Use `$ARGUMENTS` if given, else ask. Validate: lowercase `^[a-z0-9][a-z0-9-]*$`, <= 40 chars. `routine-registry.mjs add` rejects a name that collides with a shipped command or an existing routine; on rejection, ask for another name.
27
+
28
+ 4. **Write the routine body** to a temp file: the full procedure the routine should run every time. For a single candidate, embed its steps (when the source is a CLAUDE.md section, reference that section by name AND embed a faithful copy so the routine is self-contained). For a combined (multi-select) routine, concatenate the selected candidates' steps in the shown order under numbered phase headings. Keep the user's own wording; do not invent steps.
29
+
30
+ 5. **Save.** Run:
31
+ ```bash
32
+ node "$HOME/.claude/scripts/routine-registry.mjs" add \
33
+ --name "<name>" \
34
+ --description "<one-line summary>" \
35
+ --description-tr "<Turkish one-line summary>" \
36
+ --source "<CLAUDE.md section name | conversation | user>" \
37
+ --body-file "/tmp/routine-<name>.md"
38
+ ```
39
+
40
+ 6. **Report + offer to run.** Tell the user (in `outputLanguage`): the routine is saved and will appear as `/multi-agent:<name>` in autocomplete **after the next session reload** (a freshly written command is not hot-loaded this session). Then offer to run it now (you have the procedure in hand, so you can execute it immediately without waiting for the slash command to register).
41
+
42
+ ## Notes
43
+
44
+ - Never write under a repo `pipeline/` tree; routines live only under `~/.claude`.
45
+ - The routine file always carries `local-only: true` (the backend asserts this), so `/multi-agent:sync` and the install namespace-wipe both preserve it and keep it out of the public repo.
46
+ - List saved routines with `/multi-agent:routines`; remove one with `/multi-agent:forget <name>`.
@@ -56,7 +56,7 @@ Run every step automatically:
56
56
  ```
57
57
  Step 1: PLATFORM Detect macOS / Linux / Windows (Git Bash / WSL); export PLATFORM env
58
58
  Step 1.5: DETECT Compare timestamps, find stale targets
59
- Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 38 sub-command skills)
59
+ Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 41 sub-command skills)
60
60
  Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub, bash -n on all sh)
61
61
  Step 3c: PLUGINS pipeline shared/external -> multi-agent-plugins marketplace (rebuild knowledge/,
62
62
  bump changed plugins' patch version, commit + push the plugins repo)
@@ -257,14 +257,14 @@ This runs on the Claude <-> Copilot axis — the two CLIs the pipeline supports
257
257
  |-------------|-------------|
258
258
  | `~/.claude/commands/multi-agent/{cmd}/SKILL.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
259
259
 
260
- **38 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
260
+ **41 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
261
261
 
262
262
  ```
263
263
  analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira, dev,
264
- dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, garbage-collect,
264
+ dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, forget, garbage-collect,
265
265
  help, issue, jira, kill, language, local,
266
266
  local-autopilot, log, manual-test, prune-logs, purge, refactor, resume, review, review-issue, review-jira,
267
- scan, search, setup, stack, status, sync, test, uninstall, update
267
+ routines, save, scan, search, setup, stack, status, sync, test, uninstall, update
268
268
  ```
269
269
 
270
270
  **NOT synced**: `$HOME/.claude/multi-agent-refs/*` - lazy-load references, Claude Code specific
@@ -63,6 +63,7 @@ budget_exhausted() {
63
63
 
64
64
  # ---- skip dirs ----------------------------------------------------------------
65
65
  SKIP_PRUNE_ARGS=(
66
+ -path '*/.worktrees' -prune -o
66
67
  -path '*/.build' -prune -o
67
68
  -path '*/DerivedData' -prune -o
68
69
  -path '*/Pods' -prune -o
@@ -212,6 +212,7 @@ print(json.dumps(out))
212
212
  find "$root" -maxdepth "$MAXDEPTH" -type d -name ".git" \
213
213
  \( -path "*/node_modules/*" -prune -o -path "*/.build/*" -prune -o \
214
214
  -path "*/DerivedData/*" -prune -o -path "*/Pods/*" -prune -o \
215
+ -path "*/.worktrees/*" -prune -o \
215
216
  -path "*/.next/*" -prune -o -print \) 2>/dev/null \
216
217
  | while read -r gitdir; do
217
218
  [ -n "$gitdir" ] || continue
@@ -110,6 +110,13 @@ do_init() {
110
110
  if [ -f "$project_root/.gitignore" ]; then
111
111
  cat "$project_root/.gitignore" >> "$sd/.git/info/exclude"
112
112
  fi
113
+ # `.worktrees/` is forced-excluded too: worktrees live INSIDE the repo at
114
+ # <repo>/.worktrees/<id>/ and each carries a `.git` gitlink, so a blanket
115
+ # `add -A` would record every worktree as a "Subproject commit" and churn a
116
+ # fresh gitlink into every per-tool-call snapshot. The real clone keeps this
117
+ # out of its own index via .git/info/exclude; the shadow repo has its own
118
+ # exclude file and must repeat the rule. This mirrors the traversal-prune
119
+ # contract in phase-0-init.md (never descend into .worktrees on a tree walk).
113
120
  cat >> "$sd/.git/info/exclude" <<EOF
114
121
  node_modules/
115
122
  Pods/
@@ -119,6 +126,7 @@ DerivedData/
119
126
  .gradle/
120
127
  __pycache__/
121
128
  .venv/
129
+ .worktrees/
122
130
  *.log
123
131
  EOF
124
132
  # Initial author setup - keep separate from user's global git identity to
@@ -6,14 +6,14 @@
6
6
 
7
7
  ---
8
8
 
9
- ## 1. Command Inventory (38 commands)
9
+ ## 1. Command Inventory (41 commands)
10
10
 
11
11
  ```
12
12
  analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira, dev,
13
- dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, garbage-collect,
13
+ dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, forget, garbage-collect,
14
14
  help, issue, jira, kill, language, local,
15
15
  local-autopilot, log, manual-test, prune-logs, purge, refactor, resume, review, review-issue, review-jira,
16
- scan, search, setup, stack, status, sync, test, uninstall, update
16
+ routines, save, scan, search, setup, stack, status, sync, test, uninstall, update
17
17
  ```
18
18
 
19
19
  Categories:
@@ -25,6 +25,7 @@ Categories:
25
25
  - **Tail modes** (run the pipeline tail over already-done local work): `finish`
26
26
  - **Ops commands** (one-shot, no worktree): `status`, `log`, `kill`, `purge`, `uninstall`, `resume`, `review`, `review-jira`, `review-issue`, `analysis`, `analysis-resolve`, `build-optimize`, `channels`, `scan`, `search`, `diff-explain`, `garbage-collect`, `prune-logs`
27
27
  - **Meta-ops**: `setup`, `sync`, `update`, `help`, `refactor`, `test`, `stack`, `manual-test`, `language`
28
+ - **Routines** (user-defined routine registry; the routines they create are local-only and never synced): `save`, `routines`, `forget`
28
29
 
29
30
  > **Inventory drift is a contract violation.** Adding a slash command under `pipeline/commands/multi-agent/` without updating this list + its counterpart Copilot dir (`pipeline/skills/shared/core/multi-agent-<cmd>/`) is a merge blocker. `smoke-commands-skills-parity.sh` enforces command ↔ skill directory parity; `smoke-cross-cli-behavior.sh` enforces behavior parity. This doc is the authoritative command list - bump the count + table together.
30
31
 
@@ -450,7 +450,7 @@ fi
450
450
 
451
451
  If the path is still registered and healthy after the heal, enter + pull instead of re-adding (existing behavior). Only when `worktree add` still fails after the heal do the rollback / collision flow in the multi-repo block below.
452
452
 
453
- **Worktree residue guard (required once per repo, idempotent):** every worktree dir contains a `.git` file, so a blanket `git add -A` / `git add .` in the parent tree records `.worktrees/{id}` as a gitlink ("Subproject commit ...") that pollutes the branch and later needs a manual removal commit. Keep `.worktrees/` out of the index via the clone-local exclude file (never committed, so no PR noise in shared repos):
453
+ **Worktree residue guard (required once per repo, idempotent):** every worktree dir holds a `.git` file, so a blanket `git add -A` in the parent tree records `.worktrees/{id}` as a gitlink that pollutes the branch. Keep `.worktrees/` out of the index via the clone-local exclude file (never committed, so no PR noise):
454
454
 
455
455
  ```bash
456
456
  ex="$(git -C "$PROJECT_ROOT" rev-parse --path-format=absolute --git-common-dir)/info/exclude"
@@ -458,6 +458,14 @@ mkdir -p "$(dirname "$ex")"
458
458
  grep -qxF '.worktrees/' "$ex" 2>/dev/null || printf '.worktrees/\n' >> "$ex"
459
459
  ```
460
460
 
461
+ **Traversal-prune contract:** the exclude guard covers only the git INDEX, not
462
+ filesystem scans. Since each worktree is a full checkout, an unpruned tree walk
463
+ double-processes every file and can re-stage gitlinks. So `.worktrees` joins the
464
+ skip set (`node_modules`, `Pods`, `.build`, `DerivedData`, `.next`): every
465
+ `find`, walker, or `git add -A` MUST prune it. Already applied in the Step 2
466
+ scan, `shadow-git.sh` excludes, and the shared walkers (`extract-conventions.sh`,
467
+ `repo-cache.sh`, `repo-map.mjs`); add it to any new tree walk too.
468
+
461
469
  **v2.1.0+ Multi-Repo Worktree Setup**:
462
470
 
463
471
  When `state.projects[].length > 1`, repeat steps 2-4 **serially per repo** (worktrees are cheap; serial keeps git index sane and surfaces collisions one at a time):
@@ -414,11 +414,12 @@ At the end of every fix/rework round (each Phase 3 re-entry that resolved accept
414
414
 
415
415
  ```bash
416
416
  node pipeline/scripts/learnings-ledger.mjs add --kind fact \
417
- --statement "<one-line root cause: what class of mistake produced the finding and why (<=140 chars)>" \
417
+ --statement "<one-line rule/what-to-do so this class of finding does not recur (<=140 chars)>" \
418
+ --diagnosis "<the causal WHY the failure happened, one line>" \
418
419
  --scope "<file-or-area glob from the finding>" --task "$TASK_ID"
419
420
  ```
420
421
 
421
- Statement shape: root cause, not symptom - "force-unwrapped optional in async callback path", not "fixed crash in LoginView". Dedup built in (exit 2 = already stored, skip). Lessons re-enter Phase 1 + triage via `learnings-ledger.mjs brief`.
422
+ Statement shape: the durable rule/root cause, not the symptom - "force-unwrapped optional in async callback path", not "fixed crash in LoginView". Always pass `--diagnosis` with the causal why (Reflexion: the verbal reason prevents recurrence; a bare outcome does not). Dedup built in (exit 2 = already stored). Lessons re-enter Phase 1 + triage via `learnings-ledger.mjs brief` (renders diagnosis as "(why: ...)").
422
423
 
423
424
  Progress line: ` → writing lesson to learnings ledger ({N} entries)`
424
425
 
@@ -103,6 +103,16 @@ Tier 1 / Tier 2 records print `screenshotUrl` from the captured evidence (Tier 2
103
103
  The waiting state persists in `tracker-state.json` across the handoff; `/multi-agent:finish` and `/multi-agent:manual-test` CONTINUE this state file and never re-init it (`$HOME/.claude/multi-agent-refs/tracker-contract.md` "Continuation runs").
104
104
  6. If fix needed:
105
105
  - Branch already has WIP commit (from step 2) - changes are safe
106
+ - **Heal stale admin state first** (same contract as Phase 0 - step 3's
107
+ `worktree remove` or an interrupted run can leave a stale entry, so a bare
108
+ re-add fails with `already exists`/`already registered`):
109
+ ```bash
110
+ git -C "$PROJECT_ROOT" worktree prune 2>/dev/null || true
111
+ if git -C "$PROJECT_ROOT" worktree list --porcelain | grep -qF "{worktree-path}"; then
112
+ git -C "$PROJECT_ROOT" worktree unlock "{worktree-path}" 2>/dev/null || true
113
+ fi
114
+ ```
115
+ Phase 0's `.worktrees/` residue guard is already in `.git/info/exclude` - no re-add.
106
116
  - Recreate worktree from branch: `git -C $PROJECT_ROOT worktree add {worktree-path} {branch}`
107
117
  - Re-set git identity: `git -C {worktree-path} config user.name/email` (from state)
108
118
  - Go back to Phase 3
@@ -149,6 +149,8 @@ Each phase doc is lazy-loaded - only the current phase's spec is in context. B
149
149
 
150
150
  Token estimate: `ceil(chars / 4)`. Budget covers phase-\*.md files only. Guides, rules, and agents are loaded separately on demand.
151
151
 
152
+ **Prompt caching (token learning-curve):** assemble each phase prompt as a stable, cacheable prefix (phase instructions + repo learnings brief + conventions + repo evidence) followed by the volatile task suffix, so repeated runs on a known repo pay cache-read price on the prefix. Full contract: `$HOME/.claude/multi-agent-refs/prompt-assembly.md`. The effect (rising cache ratio, falling tokens/task) is what `learning-curve.mjs` trends.
153
+
152
154
  ## SubPhase Convention
153
155
 
154
156
  When a specialized skill takes over a main pipeline phase, progress is reported as **SubPhases** nested under the parent. Top-level stays 8 phases (0-7); specialized flows get sub-phase detail.
@@ -0,0 +1,31 @@
1
+ # Prompt assembly: stable-prefix caching (token learning-curve)
2
+
3
+ Anthropic prompt caching is **prefix-based**: the longest unchanged leading span of a request is served from cache at a large discount on repeated calls. As the pipeline accumulates durable knowledge about a repo (conventions, learnings brief, repo evidence), that knowledge should become a near-free cached prefix instead of a growing per-run token cost. This is the mechanism that makes "knowing the repo better" literally cheaper each run.
4
+
5
+ ## The rule
6
+
7
+ Assemble every phase prompt as two spans, in this order:
8
+
9
+ 1. **Stable prefix (cacheable, repo-stable, changes rarely):**
10
+ - system / role instructions for the phase
11
+ - the repo learnings brief (`learnings-ledger.mjs brief`)
12
+ - extracted conventions (Phase 1c) and the design/analysis doc when applicable
13
+ - repo structural evidence (the reuse-first buckets, Code Connect index)
14
+ 2. **Volatile suffix (task-specific, changes every run):**
15
+ - the specific task / issue / diff
16
+ - the current file(s) under edit
17
+ - per-run state and the immediate instruction
18
+
19
+ Put the stable material FIRST and the volatile material LAST. Reordering volatile content into the middle of the prefix invalidates the cache for everything after it, so keep the boundary clean.
20
+
21
+ ## Why it matters
22
+
23
+ - Repeated runs on the same repo pay roughly cache-read price (~10% of input) on the stable prefix instead of full price - the dominant per-run token cost once a repo is "known."
24
+ - It turns accumulated learnings from a cost into an asset: more durable knowledge => bigger cached prefix => lower marginal cost, not higher.
25
+ - The effect is visible in `metrics.jsonl` `tokens_cached` / `cache_ratio` and is exactly what `learning-curve.mjs` trends over time (rising cache ratio + falling tokens/task = the learning curve working).
26
+
27
+ ## Anti-patterns
28
+
29
+ - Interleaving the task text with conventions/learnings (breaks the prefix).
30
+ - Letting the stable prefix bloat past what the phase needs (context rot; cache a lean prefix, not everything - see the corpus merge/drop + progressive-disclosure discipline).
31
+ - Regenerating repo evidence from scratch each run instead of reusing cached/learned artifacts (defeats both the cache and the learning curve).
@@ -34,6 +34,10 @@
34
34
  "type": "string",
35
35
  "enum": ["low", "medium", "high"],
36
36
  "description": "How strongly to weight the entry when replaying it."
37
+ },
38
+ "diagnosis": {
39
+ "type": "string",
40
+ "description": "Optional causal 'why' behind the lesson (Reflexion): the root-cause reasoning that turns a detected failure into a prevented one. Rendered in the brief as '(why: ...)'. Absent for plain facts/conventions."
37
41
  }
38
42
  }
39
43
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * prefs-2.3.0-to-2.4.0.mjs - preference schema upgrade 2.3.0 -> 2.4.0
3
+ *
4
+ * Adds the user-defined routine registry:
5
+ * - global.routines (v2.4.0) - array of {name, description, createdAt,
6
+ * source, trigger?} indexing local-only
7
+ * /multi-agent:<name> commands saved via
8
+ * /multi-agent:save. Never synced (personal).
9
+ *
10
+ * Defaults to an empty array. Existing v2.3.0 prefs validate against the
11
+ * v2.4.0 schema without it, but the migrator initializes it so the routine
12
+ * commands can read/append without null-guards.
13
+ *
14
+ * Idempotent: safe on already-2.4.0 input. Non-destructive: preserves any
15
+ * user-saved routines.
16
+ *
17
+ * @param {object} data - parsed JSON content of the preferences file
18
+ * @returns {object} - same data with schemaVersion = "2.4.0"
19
+ */
20
+ export default function migrate(data) {
21
+ const out = JSON.parse(JSON.stringify(data));
22
+
23
+ out.schemaVersion = "2.4.0";
24
+
25
+ if (!out.global) out.global = {};
26
+
27
+ if (!Array.isArray(out.global.routines)) {
28
+ out.global.routines = [];
29
+ }
30
+
31
+ return out;
32
+ }
@@ -17,7 +17,8 @@
17
17
  "2.0.0",
18
18
  "2.1.0",
19
19
  "2.2.0",
20
- "2.3.0"
20
+ "2.3.0",
21
+ "2.4.0"
21
22
  ],
22
23
  "description": "v2.0.0: pre-v3.7. v2.1.0: v3.7+ adds identities[].servicePatMap, platformIdentityRouting, recentGroups, recentBranches, serviceStatus, settings, expanded keychainMapping. v2.2.0: v6.0.0 formalizes v5.7 / v5.8 additions (reportChannels, reportContent with technicalAnalysis, wikiScope, autopilotReportTimeoutSeconds) that had been running as 2.1.0 sub-migrations without a proper version bump."
23
24
  },
@@ -273,6 +274,40 @@
273
274
  "maxItems": 10
274
275
  }
275
276
  },
277
+ "routines": {
278
+ "type": "array",
279
+ "description": "v2.4.0+. User-defined routines saved via /multi-agent:save. Each entry indexes a local-only /multi-agent:<name> command dir under ~/.claude/commands/multi-agent/. Never synced (personal). Managed by routine-registry.mjs.",
280
+ "maxItems": 100,
281
+ "items": {
282
+ "type": "object",
283
+ "additionalProperties": false,
284
+ "required": ["name", "description"],
285
+ "properties": {
286
+ "name": {
287
+ "type": "string",
288
+ "pattern": "^[a-z0-9][a-z0-9-]*$",
289
+ "maxLength": 40,
290
+ "description": "Routine command name; invocable as /multi-agent:<name>."
291
+ },
292
+ "description": {
293
+ "type": "string",
294
+ "description": "One-line summary shown by /multi-agent:routines and :help."
295
+ },
296
+ "createdAt": {
297
+ "type": "string",
298
+ "format": "date-time"
299
+ },
300
+ "source": {
301
+ "type": "string",
302
+ "description": "Where the routine came from, e.g. a CLAUDE.md section name or 'user'."
303
+ },
304
+ "trigger": {
305
+ "type": ["string", "null"],
306
+ "description": "Optional natural-language trigger phrase the routine captures."
307
+ }
308
+ }
309
+ }
310
+ },
276
311
  "recentGroups": {
277
312
  "type": "array",
278
313
  "description": "v2.1.0+. LRU of multi-repo selection groups. Each group is a set of repo paths chosen together for multi-repo tasks. Max 10.",
@@ -12,6 +12,6 @@
12
12
  "phase-6-commit": { "max_tokens": 6150, "warn_tokens": 5400 },
13
13
  "phase-7-report": { "max_tokens": 5600, "warn_tokens": 4950 }
14
14
  },
15
- "total_max_tokens": 50000,
16
- "note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration."
15
+ "total_max_tokens": 51000,
16
+ "note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration. Total bumped 50000 -> 51000 at v12.5.0 after the worktree residue/traversal-prune contract (Phase 0 + Phase 5 heal) and the Reflexion causal-diagnosis contract (Phase 4 lesson memory) landed; the prose was compressed first (161 tokens reclaimed) and every per-phase max still passes - only the aggregate needed room."
17
17
  }
@@ -26,6 +26,10 @@
26
26
  "issue": { "type": "string", "minLength": 1 },
27
27
  "fix": { "type": ["string", "null"] },
28
28
  "reason": { "type": ["string", "null"] },
29
- "reviewer": { "type": ["string", "null"] }
29
+ "reviewer": { "type": ["string", "null"] },
30
+ "file_sha": {
31
+ "type": ["string", "null"],
32
+ "description": "git blob SHA (git hash-object) of `file` at ingest time. On query, a differing current hash flags the hit stale (freshness gate). Null when not computable (file gone / not a git repo)."
33
+ }
30
34
  }
31
35
  }
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // eval-mine-corpus.mjs - grow the triage eval set from real production runs.
3
3
  //
4
- // The pipeline already records every Phase 4 triage decision to
5
- // `~/.claude/logs/multi-agent/triage-corpus.jsonl` (one line per finding:
4
+ // The pipeline already records every Phase 4 triage decision to the per-repo
5
+ // `~/.claude/memory/multi-agent/<repo-slug>/triage-corpus.jsonl` (one line per finding:
6
6
  // severity/file/line/issue/fix/reviewer + the classification the run gave it,
7
7
  // plus an optional human `reason`/`reviewer`). Hand-authoring eval fixtures is
8
8
  // the bottleneck; this closes the loop by turning those real decisions into
@@ -38,19 +38,33 @@ const candidatesDir = join(root, "pipeline", "eval", "triage-candidates");
38
38
  const validator = join(root, "pipeline", "scripts", "validate-triage.mjs");
39
39
 
40
40
  const args = process.argv.slice(2);
41
- const opts = { corpus: null, limit: Infinity, dryRun: false };
41
+ const opts = { corpus: null, limit: Infinity, dryRun: false, repoSlug: null };
42
42
  for (let i = 0; i < args.length; i++) {
43
43
  if (args[i] === "--corpus") opts.corpus = args[++i];
44
+ else if (args[i] === "--repo-slug") opts.repoSlug = args[++i];
44
45
  else if (args[i] === "--limit") opts.limit = Number(args[++i]) || Infinity;
45
46
  else if (args[i] === "--dry-run") opts.dryRun = true;
46
47
  else if (args[i] === "-h" || args[i] === "--help") {
47
- console.log("Usage: eval-mine-corpus.mjs [--corpus <path>] [--limit N] [--dry-run]");
48
+ console.log("Usage: eval-mine-corpus.mjs [--corpus <path>] [--repo-slug <slug>] [--limit N] [--dry-run]");
48
49
  process.exit(0);
49
50
  }
50
51
  }
51
52
 
53
+ // Resolve the corpus path the SAME way triage-memory.mjs writes it:
54
+ // ~/.claude/memory/multi-agent/<repo-slug>/triage-corpus.jsonl (per repo).
55
+ // (The old default read ~/.claude/logs/.../triage-corpus.jsonl - a flat path
56
+ // nothing writes to - so without --corpus it always found nothing.)
57
+ function resolveRepoSlug() {
58
+ if (opts.repoSlug) return String(opts.repoSlug);
59
+ if (process.env.MULTI_AGENT_REPO_SLUG) return process.env.MULTI_AGENT_REPO_SLUG;
60
+ const r = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" });
61
+ if (r.status === 0 && r.stdout.trim()) return r.stdout.trim().split("/").pop();
62
+ return "default";
63
+ }
64
+
52
65
  const corpusPath =
53
- opts.corpus || join(homedir(), ".claude", "logs", "multi-agent", "triage-corpus.jsonl");
66
+ opts.corpus ||
67
+ join(homedir(), ".claude", "memory", "multi-agent", resolveRepoSlug(), "triage-corpus.jsonl");
54
68
 
55
69
  if (!existsSync(corpusPath)) {
56
70
  console.log(`eval-mine-corpus: no corpus at ${corpusPath} - nothing to mine.`);
@@ -1,19 +1,19 @@
1
1
  .claude/.pipeline-version 1
2
2
  .claude/CLAUDE.md 1
3
3
  .claude/agents 8
4
- .claude/commands 44
4
+ .claude/commands 47
5
5
  .claude/lib 27
6
6
  .claude/multi-agent-preferences.json 1
7
- .claude/multi-agent-refs 51
7
+ .claude/multi-agent-refs 52
8
8
  .claude/rules 12
9
- .claude/schemas 23
10
- .claude/scripts 195
9
+ .claude/schemas 24
10
+ .claude/scripts 199
11
11
  .claude/settings.json 1
12
12
  .claude/skills 428
13
13
  .copilot/.pipeline-version 1
14
14
  .copilot/agents 8
15
15
  .copilot/copilot-instructions.md 1
16
16
  .copilot/lib 27
17
- .copilot/schemas 23
18
- .copilot/scripts 195
19
- .copilot/skills 467
17
+ .copilot/schemas 24
18
+ .copilot/scripts 199
19
+ .copilot/skills 470