@mmerterden/multi-agent-pipeline 12.3.0 → 12.4.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/CHANGELOG.md CHANGED
@@ -14,6 +14,38 @@ Internal file-layout changes that don't affect the slash-command surface are sti
14
14
 
15
15
  ---
16
16
 
17
+ ## [12.4.0] - 2026-07-23
18
+
19
+ User-defined routines: manage your own recurring, project-specific jobs as
20
+ first-class `/multi-agent` commands.
21
+
22
+ ### Added
23
+
24
+ - **`/multi-agent:save`** turns a recurring job into a reusable
25
+ `/multi-agent:<name>` command. It leads with the work actually executed in
26
+ the current session (distilling repeatable jobs from what was just done),
27
+ then adds named procedures found in `~/.claude/CLAUDE.md`, and offers them in
28
+ a multi-select picker: pick one, pick several to combine into one routine
29
+ (steps concatenated in order), or describe a new one via free text.
30
+ - **`/multi-agent:routines`** lists saved routines with what each does, in
31
+ `outputLanguage`. **`/multi-agent:forget`** removes one (guarded: never
32
+ touches a shipped command).
33
+ - Backed by `routine-registry.mjs` (add/list/remove/get; name validation,
34
+ collision + shipped-command guards, atomic prefs write). Saved routines are
35
+ `local-only: true` command dirs + a `prefs.global.routines` registry entry:
36
+ preserved across `/multi-agent:update` by the install snapshot/restore,
37
+ rejected from the public repo by the sync backstops, and never counted in the
38
+ command inventory.
39
+ - Registered in `/multi-agent:help` (EN + TR "Routines" section with a dynamic
40
+ listing of saved routines).
41
+
42
+ ### Changed
43
+
44
+ - Command inventory 38 -> 41 (`save`, `routines`, `forget`); contract, both sync
45
+ docs, help EN/TR, and the count smokes updated; 3 Copilot twins added.
46
+ - prefs schema `global.routines` + `schemaVersion` 2.4.0; new
47
+ `prefs-2.3.0-to-2.4.0.mjs` migration (idempotent, adds `routines: []`).
48
+
17
49
  ## [12.3.0] - 2026-07-23
18
50
 
19
51
  `/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.4.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
@@ -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
 
@@ -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.",
@@ -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
7
  .claude/multi-agent-refs 51
8
8
  .claude/rules 12
9
- .claude/schemas 23
10
- .claude/scripts 195
9
+ .claude/schemas 24
10
+ .claude/scripts 197
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 197
19
+ .copilot/skills 470
@@ -18,7 +18,7 @@ const DEFAULT_FILE = path.join(os.homedir(), ".claude", "multi-agent-preferences
18
18
 
19
19
  // Single source of truth for the migration target version. Referenced by the
20
20
  // migrate() bump chain AND every user-facing message, so they cannot drift.
21
- const TARGET_VERSION = "2.3.0";
21
+ const TARGET_VERSION = "2.4.0";
22
22
 
23
23
  const USAGE = "Usage: migrate-prefs.mjs [--dry-run] [--file <path>]";
24
24
 
@@ -164,6 +164,9 @@ function migrate(prefs) {
164
164
  const TARGET = TARGET_VERSION;
165
165
  if (out.schemaVersion === TARGET) {
166
166
  // already on target - skip version bump, but still run sub-migrations below
167
+ } else if (out.schemaVersion === "2.3.0") {
168
+ out.schemaVersion = TARGET;
169
+ changes.push(`schemaVersion: 2.3.0 → ${TARGET}`);
167
170
  } else if (out.schemaVersion === "2.2.0") {
168
171
  out.schemaVersion = TARGET;
169
172
  changes.push(`schemaVersion: 2.2.0 → ${TARGET}`);
@@ -241,6 +244,10 @@ function migrate(prefs) {
241
244
  out.global.accounts = [];
242
245
  changes.push("added empty accounts (v7.4.0 per-account host overrides)");
243
246
  }
247
+ if (!Array.isArray(out.global.routines)) {
248
+ out.global.routines = [];
249
+ changes.push("added empty routines (v2.4.0 user-defined /multi-agent:save routines)");
250
+ }
244
251
  if (!out.global.serviceStatus) {
245
252
  out.global.serviceStatus = {};
246
253
  changes.push("added empty serviceStatus");
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ // routine-registry.mjs - backend for the user-defined routine commands
3
+ // (/multi-agent:save, :routines, :forget).
4
+ //
5
+ // A "routine" is a saved, reusable procedure the user binds to its own
6
+ // /multi-agent:<name> slash command. Each routine is TWO artifacts:
7
+ // 1. a local-only command dir ~/.claude/commands/multi-agent/<name>/SKILL.md
8
+ // (frontmatter `local-only: true` -> the installer preserves it across
9
+ // updates and /multi-agent:sync refuses to leak it into the public repo)
10
+ // 2. a registry entry in ~/.claude/multi-agent-preferences.json
11
+ // at global.routines[] (name, description, createdAt, source)
12
+ //
13
+ // This tool is the mechanical half; the save/routines/forget SKILLs drive the
14
+ // UX (candidate discovery, pickers, confirmation) and call these subcommands.
15
+ //
16
+ // Zero deps. Never writes under a repo `pipeline/` tree; only ever under
17
+ // <home>/.claude/. Refuses to touch a shipped (non-local-only) command dir.
18
+ //
19
+ // Usage:
20
+ // node routine-registry.mjs list [--json] [--home <dir>]
21
+ // node routine-registry.mjs get --name <n> [--home <dir>]
22
+ // node routine-registry.mjs add --name <n> --description <d>
23
+ // [--description-tr <t>] [--argument-hint <h>] --source <s> --body-file <f> [--home <dir>]
24
+ // node routine-registry.mjs remove --name <n> [--home <dir>]
25
+ //
26
+ // Exit: 0 ok, 1 error (validation / collision / not-found), 64 usage.
27
+
28
+ import {
29
+ existsSync,
30
+ readFileSync,
31
+ writeFileSync,
32
+ mkdirSync,
33
+ rmSync,
34
+ renameSync,
35
+ } from "node:fs";
36
+ import { join, dirname } from "node:path";
37
+
38
+ const NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
39
+ const MAX_NAME = 40;
40
+ const MAX_ROUTINES = 100;
41
+
42
+ function parseArgs(argv) {
43
+ const out = { _: [] };
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const a = argv[i];
46
+ if (a.startsWith("--")) {
47
+ const key = a.slice(2);
48
+ const next = argv[i + 1];
49
+ if (next === undefined || next.startsWith("--")) {
50
+ out[key] = true;
51
+ } else {
52
+ out[key] = next;
53
+ i++;
54
+ }
55
+ } else {
56
+ out._.push(a);
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+
62
+ function fail(msg) {
63
+ console.error(`routine-registry: ${msg}`);
64
+ process.exit(1);
65
+ }
66
+
67
+ function homeDir(args) {
68
+ return args.home || process.env.HOME || process.env.USERPROFILE || "";
69
+ }
70
+ function prefsPath(home) {
71
+ return join(home, ".claude", "multi-agent-preferences.json");
72
+ }
73
+ function routineDir(home, name) {
74
+ return join(home, ".claude", "commands", "multi-agent", name);
75
+ }
76
+
77
+ function readPrefs(home) {
78
+ const p = prefsPath(home);
79
+ if (!existsSync(p)) return { global: {} };
80
+ try {
81
+ const prefs = JSON.parse(readFileSync(p, "utf-8"));
82
+ if (!prefs.global) prefs.global = {};
83
+ return prefs;
84
+ } catch {
85
+ fail(`could not parse ${p}`);
86
+ }
87
+ }
88
+
89
+ function writePrefs(home, prefs) {
90
+ const p = prefsPath(home);
91
+ mkdirSync(dirname(p), { recursive: true });
92
+ const tmp = `${p}.tmp.${process.pid}`;
93
+ writeFileSync(tmp, `${JSON.stringify(prefs, null, 2)}\n`);
94
+ renameSync(tmp, p);
95
+ }
96
+
97
+ function getRoutines(prefs) {
98
+ return Array.isArray(prefs.global.routines) ? prefs.global.routines : [];
99
+ }
100
+
101
+ // A dir is a shipped (non-routine) command when it exists and its SKILL.md is
102
+ // NOT marked local-only. Those must never be overwritten or removed.
103
+ function isLocalOnlyDir(dir) {
104
+ const skill = join(dir, "SKILL.md");
105
+ if (!existsSync(skill)) return false;
106
+ return /^local-only:\s*true\s*$/m.test(readFileSync(skill, "utf-8"));
107
+ }
108
+
109
+ function cmdList(home, json) {
110
+ const routines = getRoutines(readPrefs(home));
111
+ if (json) {
112
+ process.stdout.write(`${JSON.stringify(routines, null, 2)}\n`);
113
+ return;
114
+ }
115
+ if (routines.length === 0) {
116
+ console.log("(no routines saved)");
117
+ return;
118
+ }
119
+ for (const r of routines) {
120
+ console.log(`${r.name}\t${r.description || ""}\t${r.createdAt || ""}`);
121
+ }
122
+ }
123
+
124
+ function cmdGet(home, name) {
125
+ const r = getRoutines(readPrefs(home)).find((x) => x.name === name);
126
+ if (!r) fail(`no routine named "${name}"`);
127
+ process.stdout.write(`${JSON.stringify(r, null, 2)}\n`);
128
+ }
129
+
130
+ function buildSkill(args) {
131
+ const desc = args.description;
132
+ const descTr = args["description-tr"];
133
+ const hint = args["argument-hint"] || "[optional args]";
134
+ const body = readFileSync(args["body-file"], "utf-8").trimEnd();
135
+ const fm = [
136
+ "---",
137
+ `description: ${JSON.stringify(desc)}`,
138
+ descTr ? `description-tr: ${JSON.stringify(descTr)}` : null,
139
+ `argument-hint: ${JSON.stringify(hint)}`,
140
+ "allowed-tools: Skill, Bash, Read, Edit, Write, AskUserQuestion",
141
+ "local-only: true",
142
+ "---",
143
+ ].filter(Boolean);
144
+ return `${fm.join("\n")}\n\n# multi-agent ${args.name} - saved routine\n\n**Input**: $ARGUMENTS\n\n${body}\n`;
145
+ }
146
+
147
+ function cmdAdd(home, args) {
148
+ const name = args.name;
149
+ if (!name || !NAME_RE.test(name) || name.length > MAX_NAME) {
150
+ fail(`invalid --name (need ${NAME_RE} , <= ${MAX_NAME} chars): ${name}`);
151
+ }
152
+ if (!args.description) fail("--description is required");
153
+ if (!args["body-file"] || !existsSync(args["body-file"])) {
154
+ fail("--body-file is required and must exist");
155
+ }
156
+ const dir = routineDir(home, name);
157
+ // Collision: an existing non-local-only dir is a shipped command - never touch.
158
+ if (existsSync(dir) && !isLocalOnlyDir(dir)) {
159
+ fail(`"${name}" collides with a shipped command; pick another name`);
160
+ }
161
+ const prefs = readPrefs(home);
162
+ const routines = getRoutines(prefs);
163
+ if (routines.some((r) => r.name === name)) {
164
+ fail(`a routine named "${name}" already exists; forget it first or pick another name`);
165
+ }
166
+ if (routines.length >= MAX_ROUTINES) fail(`routine cap (${MAX_ROUTINES}) reached`);
167
+
168
+ mkdirSync(dir, { recursive: true });
169
+ const skill = buildSkill(args);
170
+ // Safety: the generated file MUST be local-only so it never syncs to the repo.
171
+ if (!/^local-only:\s*true\s*$/m.test(skill)) fail("internal: generated SKILL missing local-only");
172
+ writeFileSync(join(dir, "SKILL.md"), skill);
173
+
174
+ routines.push({
175
+ name,
176
+ description: args.description,
177
+ createdAt: new Date().toISOString(),
178
+ source: args.source || "user",
179
+ });
180
+ prefs.global.routines = routines;
181
+ writePrefs(home, prefs);
182
+ console.log(`routine-registry: saved "${name}" -> ${join(dir, "SKILL.md")}`);
183
+ console.log("routine-registry: available as /multi-agent:" + name + " after a session reload");
184
+ }
185
+
186
+ function cmdRemove(home, name) {
187
+ if (!name) fail("--name is required");
188
+ const prefs = readPrefs(home);
189
+ const routines = getRoutines(prefs);
190
+ const idx = routines.findIndex((r) => r.name === name);
191
+ const dir = routineDir(home, name);
192
+ // Guard: only remove a dir that is BOTH registered AND local-only.
193
+ if (existsSync(dir) && !isLocalOnlyDir(dir)) {
194
+ fail(`"${name}" is a shipped command, not a routine; refusing to remove`);
195
+ }
196
+ if (idx < 0 && !existsSync(dir)) fail(`no routine named "${name}"`);
197
+ if (existsSync(dir) && isLocalOnlyDir(dir)) rmSync(dir, { recursive: true, force: true });
198
+ if (idx >= 0) {
199
+ routines.splice(idx, 1);
200
+ prefs.global.routines = routines;
201
+ writePrefs(home, prefs);
202
+ }
203
+ console.log(`routine-registry: removed "${name}" (clears from autocomplete on next session reload)`);
204
+ }
205
+
206
+ function main() {
207
+ const [, , sub, ...rest] = process.argv;
208
+ const args = parseArgs(rest);
209
+ const home = homeDir(args);
210
+ if (!home) fail("cannot resolve home dir");
211
+ switch (sub) {
212
+ case "list":
213
+ return cmdList(home, Boolean(args.json));
214
+ case "get":
215
+ return cmdGet(home, args.name);
216
+ case "add":
217
+ return cmdAdd(home, args);
218
+ case "remove":
219
+ return cmdRemove(home, args.name);
220
+ default:
221
+ console.error("usage: routine-registry.mjs <list|get|add|remove> [flags] (see header)");
222
+ process.exit(64);
223
+ }
224
+ }
225
+
226
+ main();
@@ -91,11 +91,11 @@ echo ""
91
91
  echo "→ 8. registration + count consistency (36)"
92
92
  grep -Fq '| `create-jira ' "$DISPATCHER" && pass "dispatcher routing: create-jira" || fail "dispatcher routing missing: generate"
93
93
  grep -Fq "create-jira/SKILL.md" "$DISPATCHER" && pass "dispatcher loading row" || fail "dispatcher loading row missing"
94
- grep -Fq "## 1. Command Inventory (38 commands)" "$CONTRACT" && pass "contract count = 38" || fail "contract count wrong"
94
+ grep -Fq "## 1. Command Inventory (41 commands)" "$CONTRACT" && pass "contract count = 41" || fail "contract count wrong"
95
95
  grep -Eq '(^|[ ,])create-jira([ ,]|$)' "$CONTRACT" && pass "contract list has create-jira" || fail "contract list missing generate"
96
- grep -Fq "**38 commands are synced**" "$SYNC" && pass "sync count = 38" || fail "sync count wrong"
96
+ grep -Fq "**41 commands are synced**" "$SYNC" && pass "sync count = 41" || fail "sync count wrong"
97
97
  SYNC_SKILL="$ROOT/pipeline/skills/shared/core/multi-agent-sync/SKILL.md"
98
- grep -Fq "**38 commands are synced**" "$SYNC_SKILL" && pass "sync SKILL count = 38" || fail "sync SKILL count wrong (Copilot parity)"
98
+ grep -Fq "**41 commands are synced**" "$SYNC_SKILL" && pass "sync SKILL count = 41" || fail "sync SKILL count wrong (Copilot parity)"
99
99
  grep -Fq "create-jira" "$ROOT/pipeline/skills/shared/core/multi-agent-help/SKILL.md" && pass "help SKILL lists create-jira" || fail "help SKILL missing create-jira (Copilot parity)"
100
100
  grep -c "multi-agent:create-jira" "$HELP" | awk '{exit !($1>=2)}' && pass "help.md has EN + TR entries" || fail "help.md entries incomplete"
101
101
  # no stale split-command references anywhere in the synced surfaces
@@ -75,12 +75,12 @@ else
75
75
  fail "unknown flag did not exit 1"
76
76
  fi
77
77
 
78
- # Test 7 - type auto-detection works
78
+ # Test 7 - type auto-detection works (feed a doc already at the latest version)
79
79
  cat > "$TMPDIR/multi-agent-preferences.json" <<'EOF'
80
- {"schemaVersion":"2.3.0","global":{"identities":[],"keychainMapping":{},"recentAccounts":[],"accounts":[]},"projects":{}}
80
+ {"schemaVersion":"2.4.0","global":{"identities":[],"keychainMapping":{},"recentAccounts":[],"accounts":[],"routines":[]},"projects":{}}
81
81
  EOF
82
82
  OUT=$(node "$RUNNER" "$TMPDIR/multi-agent-preferences.json" 2>&1)
83
- if echo "$OUT" | grep -q "already at schemaVersion 2.3.0"; then
83
+ if echo "$OUT" | grep -q "already at schemaVersion 2.4.0"; then
84
84
  pass "prefs type auto-detected from filename"
85
85
  else
86
86
  fail "prefs auto-detection failed (got: $OUT)"
@@ -64,7 +64,7 @@ F1=$(run_fixture "f1-v2.0.0-legacy" '{
64
64
  },
65
65
  "projects": {}
66
66
  }')
67
- assert_json_has "f1" "$F1" ".schemaVersion" "2.3.0"
67
+ assert_json_has "f1" "$F1" ".schemaVersion" "2.4.0"
68
68
  assert_json_has "f1" "$F1" ".global.identities" "array:1"
69
69
  assert_json_has "f1" "$F1" ".global.gitIdentities" "undefined"
70
70
  assert_json_has "f1" "$F1" ".global.settings" "object:8"
@@ -73,6 +73,7 @@ assert_json_has "f1" "$F1" ".global.platformIdentityRouting" "object:0"
73
73
  assert_json_has "f1" "$F1" ".global.recentGroups" "array:0"
74
74
  assert_json_has "f1" "$F1" ".global.recentAccounts" "array:0"
75
75
  assert_json_has "f1" "$F1" ".global.accounts" "array:0"
76
+ assert_json_has "f1" "$F1" ".global.routines" "array:0"
76
77
 
77
78
  echo ""
78
79
  echo "→ Fixture 2: no schemaVersion (pre-2.0.0 fossil)"
@@ -80,15 +81,15 @@ F2=$(run_fixture "f2-no-version" '{
80
81
  "global": {"gitIdentities": []},
81
82
  "projects": {}
82
83
  }')
83
- assert_json_has "f2" "$F2" ".schemaVersion" "2.3.0"
84
+ assert_json_has "f2" "$F2" ".schemaVersion" "2.4.0"
84
85
  assert_json_has "f2" "$F2" ".global.identities" "array:0"
85
86
 
86
87
  echo ""
87
- echo "→ Fixture 3: v2.3.0 already (no-op)"
88
- F3_FILE="$TMP/f3-v2.3.0.json"
88
+ echo "→ Fixture 3: v2.4.0 already (no-op)"
89
+ F3_FILE="$TMP/f3-v2.4.0.json"
89
90
  cat > "$F3_FILE" <<'EOF'
90
91
  {
91
- "schemaVersion": "2.3.0",
92
+ "schemaVersion": "2.4.0",
92
93
  "global": {
93
94
  "identities": [],
94
95
  "keychainMapping": {
@@ -117,7 +118,8 @@ cat > "$F3_FILE" <<'EOF'
117
118
  "wikiScope": ["main", "ios", "screenshots", "index"],
118
119
  "autopilotReportTimeoutSeconds": 1800,
119
120
  "recentAccounts": [],
120
- "accounts": []
121
+ "accounts": [],
122
+ "routines": []
121
123
  },
122
124
  "projects": {}
123
125
  }
@@ -66,7 +66,7 @@ grep -Fq '| `review-jira ' "$DISPATCHER" && pass "dispatcher desc: review-jira"
66
66
  grep -Fq '| `review-issue ' "$DISPATCHER" && pass "dispatcher desc: review-issue" || fail "dispatcher desc missing review-issue"
67
67
  grep -Fq "review-jira/SKILL.md" "$DISPATCHER" && pass "dispatcher routing: review-jira" || fail "dispatcher routing missing review-jira"
68
68
  grep -Fq "review-issue/SKILL.md" "$DISPATCHER" && pass "dispatcher routing: review-issue" || fail "dispatcher routing missing review-issue"
69
- grep -Fq "## 1. Command Inventory (38 commands)" "$CONTRACT" && pass "contract count = 38" || fail "contract count wrong"
69
+ grep -Fq "## 1. Command Inventory (41 commands)" "$CONTRACT" && pass "contract count = 41" || fail "contract count wrong"
70
70
  grep -Eq '(^|[ ,])review-jira([ ,]|$)' "$CONTRACT" && pass "contract inventory has review-jira" || fail "contract missing review-jira"
71
71
  grep -Eq '(^|[ ,])review-issue([ ,]|$)' "$CONTRACT" && pass "contract inventory has review-issue" || fail "contract missing review-issue"
72
72
  grep -Fq "multi-agent:review-jira" "$HELP" && pass "help lists review-jira" || fail "help missing review-jira"
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-routines.sh - contract for routine-registry.mjs, the backend of the
3
+ # user-defined routine commands (/multi-agent:save, :routines, :forget).
4
+
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ RR="$SCRIPT_DIR/routine-registry.mjs"
9
+
10
+ PASS=0
11
+ FAIL=0
12
+ record_pass() { PASS=$((PASS + 1)); printf ' \342\234\223 %s\n' "$1"; }
13
+ record_fail() { FAIL=$((FAIL + 1)); printf ' \342\234\227 %s\n' "$1"; }
14
+
15
+ printf '\342\206\222 smoke-routines: routine-registry backend contract\n'
16
+
17
+ tmp=$(mktemp -d "${TMPDIR:-/tmp}/routines.XXXXXX")
18
+ trap 'rm -rf "$tmp"' EXIT
19
+ H="$tmp/home"
20
+ mkdir -p "$H/.claude/commands/multi-agent"
21
+ printf '{"global":{}}\n' > "$H/.claude/multi-agent-preferences.json"
22
+ printf 'Run steps 1-6 of the checkin service migration.\n' > "$tmp/body.md"
23
+
24
+ # 1. add -> writes a local-only command dir + registry entry
25
+ node "$RR" add --name checkin-services --description "Checkin servis kontrolu" \
26
+ --source "CLAUDE.md" --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
27
+ skill="$H/.claude/commands/multi-agent/checkin-services/SKILL.md"
28
+ if [ -f "$skill" ] && grep -qx "local-only: true" "$skill"; then
29
+ record_pass "add writes a local-only command SKILL.md"
30
+ else
31
+ record_fail "add did not write a local-only routine command"
32
+ fi
33
+ if node "$RR" list --json --home "$H" | grep -q '"name": "checkin-services"'; then
34
+ record_pass "registry records the routine"
35
+ else
36
+ record_fail "registry missing the routine"
37
+ fi
38
+
39
+ # 2. add is name-collision safe (same name rejected)
40
+ node "$RR" add --name checkin-services --description x --source y --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
41
+ [ "$?" -eq 1 ] && record_pass "duplicate name rejected" || record_fail "duplicate name should reject"
42
+
43
+ # 3. invalid name rejected
44
+ node "$RR" add --name "Bad Name" --description x --source y --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
45
+ [ "$?" -eq 1 ] && record_pass "invalid name rejected" || record_fail "invalid name should reject"
46
+
47
+ # 4. shipped-command guard: never remove a non-local-only command
48
+ mkdir -p "$H/.claude/commands/multi-agent/status"
49
+ printf -- '---\ndescription: "shipped"\n---\nbody\n' > "$H/.claude/commands/multi-agent/status/SKILL.md"
50
+ node "$RR" remove --name status --home "$H" >/dev/null 2>&1
51
+ if [ "$?" -eq 1 ] && [ -d "$H/.claude/commands/multi-agent/status" ]; then
52
+ record_pass "refuses to remove a shipped command"
53
+ else
54
+ record_fail "shipped command must be protected"
55
+ fi
56
+
57
+ # 5. sync backstop proxy: the generated routine carries local-only:true, which is
58
+ # exactly what sync/SKILL.md greps to keep it out of the public repo.
59
+ if grep -qx "local-only: true" "$skill"; then
60
+ record_pass "routine is sync-excluded (local-only marker present)"
61
+ else
62
+ record_fail "routine missing local-only marker (would leak on sync)"
63
+ fi
64
+
65
+ # 6. remove -> deletes dir + registry entry
66
+ node "$RR" remove --name checkin-services --home "$H" >/dev/null 2>&1
67
+ if [ ! -d "$H/.claude/commands/multi-agent/checkin-services" ] \
68
+ && ! node "$RR" list --json --home "$H" | grep -q "checkin-services"; then
69
+ record_pass "remove deletes dir + registry entry"
70
+ else
71
+ record_fail "remove left dir or registry entry behind"
72
+ fi
73
+
74
+ # 7. install preservation proxy: snapshotLocalOnlyWrappers keys on local-only:true.
75
+ # Re-add, then confirm the marker survives a simulated namespace re-read.
76
+ node "$RR" add --name my-routine --description d --source s --body-file "$tmp/body.md" --home "$H" >/dev/null 2>&1
77
+ if grep -qx "local-only: true" "$H/.claude/commands/multi-agent/my-routine/SKILL.md"; then
78
+ record_pass "re-added routine keeps the install-preserving marker"
79
+ else
80
+ record_fail "re-added routine lost its local-only marker"
81
+ fi
82
+
83
+ printf '\n\342\225\220\342\225\220 smoke-routines: %d passed, %d failed \342\225\220\342\225\220\n' "$PASS" "$FAIL"
84
+ [ "$FAIL" -eq 0 ]
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: multi-agent-forget
3
+ language: en
4
+ 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."
5
+ user-invocable: true
6
+ argument-hint: "[name]"
7
+ ---
8
+
9
+ # multi-agent-forget - Remove a Routine
10
+
11
+ Delete a routine saved via `/multi-agent:save`. Backed by `$HOME/.copilot/scripts/routine-registry.mjs`.
12
+
13
+ ## Steps
14
+
15
+ 1. Resolve which routine: from `$ARGUMENTS`, else `routine-registry.mjs list --json` + `AskUserQuestion` (in `outputLanguage`). None -> nothing to forget.
16
+ 2. Confirm via `AskUserQuestion`: "Remove /multi-agent:<name>?" (Remove / Cancel). Anything but Remove -> stop.
17
+ 3. `node "$HOME/.copilot/scripts/routine-registry.mjs" remove --name "<name>"` (backend refuses to remove a shipped command; only a registered `local-only: true` dir).
18
+ 4. Report removed; note the autocomplete entry clears on the next session reload.
19
+
20
+ ## Notes
21
+
22
+ - Only user-saved routines can be forgotten. See `/multi-agent:routines` for the list.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: multi-agent-routines
3
+ language: en
4
+ description: "List your saved /multi-agent routines (from /multi-agent:save) with what each one does, rendered in outputLanguage."
5
+ user-invocable: true
6
+ ---
7
+
8
+ # multi-agent-routines - List Saved Routines
9
+
10
+ Show every routine saved via `/multi-agent:save`. Read-only. Backed by `$HOME/.copilot/scripts/routine-registry.mjs`.
11
+
12
+ ## Steps
13
+
14
+ 1. `node "$HOME/.copilot/scripts/routine-registry.mjs" list --json`
15
+ 2. Render in `outputLanguage`: empty -> say there are none yet and point to `/multi-agent:save`; otherwise a compact table of `/multi-agent:<name>` + description + created date.
16
+ 3. Flag any registry entry with no matching command dir (or vice versa) as `needs repair`; never modify anything.
17
+
18
+ ## Notes
19
+
20
+ - Add with `/multi-agent:save`, remove with `/multi-agent:forget <name>`. Routines are local-only and never synced.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: multi-agent-save
3
+ language: en
4
+ 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; stored local-only, never synced."
5
+ user-invocable: true
6
+ argument-hint: "[name]"
7
+ ---
8
+
9
+ # multi-agent-save - Save a Routine
10
+
11
+ **Input**: $ARGUMENTS (optional routine name)
12
+
13
+ Turn a recurring procedure into a reusable `/multi-agent:<name>` command. The routine is stored local-only (a `local-only: true` command + a `prefs.global.routines` entry) and is never synced. Backed by `$HOME/.copilot/scripts/routine-registry.mjs`.
14
+
15
+ ## Steps
16
+
17
+ 1. Distill candidates from what was just done (primary): group the tasks executed this session into coherent, repeatable jobs (suggested name + one-line summary + ordered steps). Then add named end-to-end procedures from `$HOME/.claude/CLAUDE.md`.
18
+ 2. `AskUserQuestion` with `multiSelect: true` (in `outputLanguage`): one option per candidate + an always-present `Other` free-text. One selected -> that routine; several selected -> combine into ONE routine (steps concatenated in shown order, single name); `Other` -> user types it. Empty is not consent.
19
+ 3. Name the command (from `$ARGUMENTS` or ask); validate `^[a-z0-9][a-z0-9-]*$`, <= 40 chars; the backend rejects collisions.
20
+ 4. Write the full procedure body to a temp file (single candidate: embed its steps, reference the CLAUDE.md section by name when that is the source; combined: concatenate the selected candidates' steps in order under numbered phase headings). Do not invent steps.
21
+ 5. Save: `node "$HOME/.copilot/scripts/routine-registry.mjs" add --name "<name>" --description "<summary>" --source "<source>" --body-file "/tmp/routine-<name>.md"`.
22
+ 6. Report it is available as `/multi-agent:<name>` after a session reload; offer to run it now.
23
+
24
+ ## Notes
25
+
26
+ - Never writes under a repo `pipeline/` tree; routines live only under the CLI home.
27
+ - List with `/multi-agent:routines`; remove with `/multi-agent:forget <name>`.
@@ -29,7 +29,7 @@ Run all steps automatically:
29
29
 
30
30
  ```
31
31
  Step 1: DETECT Compare timestamps, find stale targets
32
- Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 38 sub-command skills)
32
+ Step 2: COPILOT Claude Code -> Copilot CLI (instructions + 41 sub-command skills)
33
33
  Step 3: REPO Claude Code -> pipeline repo (genericized, personal data scrub)
34
34
  Step 4: WEBSITE Version + phase/model counts -> {website-host} (i18n + projects.ts)
35
35
  Step 5: Commit Commit + push all changed repos
@@ -136,14 +136,14 @@ When invoked with the `release` argument:
136
136
  |-------------|-------------|
137
137
  | `~/.claude/commands/multi-agent/{cmd}.md` | `~/.copilot/skills/multi-agent-{cmd}/SKILL.md` |
138
138
 
139
- **38 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
139
+ **41 commands are synced** (canonical inventory - must match `cross-cli-contract.md` section 1; drift = contract violation):
140
140
 
141
141
  ```
142
142
  analysis, analysis-resolve, autopilot, build-optimize, channels, create-jira, dev,
143
- dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, garbage-collect,
143
+ dev-autopilot, dev-local, dev-local-autopilot, diff-explain, finish, forget, garbage-collect,
144
144
  help, issue, jira, kill, language, local,
145
145
  local-autopilot, log, manual-test, prune-logs, purge, refactor, resume, review, review-issue, review-jira,
146
- scan, search, setup, stack, status, sync, test, uninstall, update
146
+ routines, save, scan, search, setup, stack, status, sync, test, uninstall, update
147
147
  ```
148
148
 
149
149
  **NOT synced**: `refs/*` - Lazy-load references, Claude Code specific