@mmerterden/multi-agent-pipeline 20.8.1 → 20.8.2

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 (75) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/docs/facts.json +3 -3
  3. package/index.js +1 -0
  4. package/install/_codex-agents.mjs +1 -1
  5. package/install/_common.mjs +486 -53
  6. package/install/_mcp-register.mjs +173 -117
  7. package/install/claude.mjs +281 -220
  8. package/install/codex.mjs +7 -7
  9. package/install/copilot.mjs +13 -11
  10. package/install/index.mjs +92 -27
  11. package/install/templates/claude-hooks.json +9 -9
  12. package/manifest.json +75 -78
  13. package/package.json +4 -1
  14. package/pipeline/commands/multi-agent/update/SKILL.md +28 -17
  15. package/pipeline/lib/confusables.json +79 -33
  16. package/pipeline/lib/extract-conventions.sh +3 -3
  17. package/pipeline/lib/json-file-lock.mjs +27 -7
  18. package/pipeline/lib/normalize-text.mjs +86 -17
  19. package/pipeline/lib/outbound-gate.mjs +13 -4
  20. package/pipeline/lib/redact.mjs +87 -14
  21. package/pipeline/multi-agent-refs/analysis/evidence.md +1 -1
  22. package/pipeline/multi-agent-refs/analysis/synthesis.md +1 -1
  23. package/pipeline/multi-agent-refs/component-dispatch.md +1 -1
  24. package/pipeline/multi-agent-refs/conventions-defaults.md +1 -1
  25. package/pipeline/multi-agent-refs/features/unattended-security.md +2 -2
  26. package/pipeline/scripts/agent-guard.py +150 -25
  27. package/pipeline/scripts/audit-log.sh +3 -4
  28. package/pipeline/scripts/autopilot-runner.mjs +14 -5
  29. package/pipeline/scripts/doctor.mjs +8 -2
  30. package/pipeline/scripts/log-metric.sh +9 -3
  31. package/pipeline/scripts/migrate-prefs.mjs +18 -4
  32. package/pipeline/scripts/pre-commit-check.sh +119 -31
  33. package/pipeline/scripts/scan-agent-config.sh +9 -9
  34. package/pipeline/scripts/unattended_policy.py +12 -3
  35. package/pipeline/scripts/uninstall.mjs +88 -1
  36. package/pipeline/scripts/usage-identity.mjs +1 -1
  37. package/pipeline/scripts/usage-register.mjs +1 -1
  38. package/pipeline/skills/.skill-manifest.json +11 -11
  39. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +6 -3
  40. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +43 -0
  41. package/pipeline/skills/shared/external/core-nfc/SKILL.md +31 -0
  42. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
  43. package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +1 -1
  44. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  45. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +2 -1
  46. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +14 -13
  47. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  48. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +7 -7
  49. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +105 -4
  50. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +20 -5
  51. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +8 -7
  52. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +7 -8
  53. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +20 -7
  54. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +4 -3
  55. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +8 -7
  56. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +15 -8
  57. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +17 -9
  58. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +39 -17
  59. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +28 -7
  60. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +5 -4
  61. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +3 -2
  62. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -2
  63. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
  64. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +4 -4
  65. package/pipeline/skills/shared/external/permissionkit/SKILL.md +15 -6
  66. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +2 -1
  67. package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -4
  68. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +1 -1
  69. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +25 -6
  70. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
  71. package/pipeline/skills/shared/external/skill-creator/template.md +1 -1
  72. package/pipeline/skills/shared/external/vision-framework/SKILL.md +3 -1
  73. package/pipeline/scripts/gen-ref-toc.mjs +0 -279
  74. package/pipeline/scripts/make-manifest.mjs +0 -199
  75. package/pipeline/scripts/scorecard-snapshot.mjs +0 -178
package/install/codex.mjs CHANGED
@@ -241,7 +241,7 @@ function installRouterSkill(pipelineSrc, skillsDir) {
241
241
 
242
242
  // ensureRealDir only swaps a --link-era symlink for a real dir; ensureDir
243
243
  // creates a missing one. Every owned tree below needs both.
244
- ensureRealDir(skillsDir);
244
+ skillsDir = ensureRealDir(skillsDir, { userOwned: true });
245
245
  ensureDir(skillsDir);
246
246
  pruneLegacyCommandSkills(skillsDir);
247
247
 
@@ -297,7 +297,7 @@ function installRefs(pipelineSrc, dest) {
297
297
  const refsSrc = join(pipelineSrc, "multi-agent-refs");
298
298
  const commandsSrc = join(pipelineSrc, "commands", "multi-agent");
299
299
 
300
- ensureRealDir(dest);
300
+ if (ensureRealDir(dest) === null) return;
301
301
  ensureDir(dest);
302
302
  wipeDir(dest);
303
303
 
@@ -373,7 +373,7 @@ function installPersonas(pipelineSrc, dest) {
373
373
  * orchestrator - one spec, two entry points.
374
374
  */
375
375
  function installPrompt(promptsDir) {
376
- ensureRealDir(promptsDir);
376
+ promptsDir = ensureRealDir(promptsDir, { userOwned: true });
377
377
  ensureDir(promptsDir);
378
378
  const body = [
379
379
  "Run the multi-agent development pipeline.",
@@ -409,7 +409,7 @@ function installSkillRefs(pipelineSrc, dest, home, platformFlag) {
409
409
  const externalSrc = join(pipelineSrc, "skills", "shared", "external");
410
410
  const coreSrc = join(pipelineSrc, "skills", "shared", "core");
411
411
 
412
- ensureRealDir(dest);
412
+ if (ensureRealDir(dest) === null) return;
413
413
  ensureDir(dest);
414
414
  wipeDir(dest);
415
415
 
@@ -482,7 +482,7 @@ function installScripts(pipelineSrc, dest, useSymlinks) {
482
482
  const src = join(pipelineSrc, "scripts");
483
483
  if (!existsSync(src)) return;
484
484
  if (!useSymlinks) {
485
- ensureRealDir(dest);
485
+ if (ensureRealDir(dest) === null) return;
486
486
  ensureDir(dest);
487
487
  }
488
488
  wipeDir(dest);
@@ -512,7 +512,7 @@ function installTemplates(pipelineSrc, dest, useSymlinks) {
512
512
  const src = join(dirname(pipelineSrc), "install", "templates");
513
513
  if (!existsSync(src)) return;
514
514
  if (!useSymlinks) {
515
- ensureRealDir(dest);
515
+ if (ensureRealDir(dest) === null) return;
516
516
  ensureDir(dest);
517
517
  }
518
518
  wipeDir(dest);
@@ -524,7 +524,7 @@ function installTree(name, pipelineSrc, dest, useSymlinks) {
524
524
  const src = join(pipelineSrc, name);
525
525
  if (!existsSync(src)) return;
526
526
  if (!useSymlinks) {
527
- ensureRealDir(dest);
527
+ if (ensureRealDir(dest) === null) return;
528
528
  ensureDir(dest);
529
529
  }
530
530
  wipeDir(dest);
@@ -225,7 +225,7 @@ function installScripts(pipelineSrc, dest, useSymlinks) {
225
225
  if (!existsSync(scriptsSrc)) return;
226
226
  // Replace a --link-era symlink with a real dir before wiping/copying so the
227
227
  // install can never land inside the developer's repo checkout.
228
- if (!useSymlinks) ensureRealDir(dest);
228
+ if (!useSymlinks && ensureRealDir(dest) === null) return;
229
229
  wipeDir(dest);
230
230
  copyDir(scriptsSrc, dest, { exclude: DEV_ONLY_SCRIPTS, useSymlinks });
231
231
  // Count files actually excluded by walking the tree: DEV_ONLY_SCRIPTS mixes
@@ -243,11 +243,12 @@ function installAgents(pipelineSrc, dest, useSymlinks) {
243
243
  // ~/.copilot/agents also holds user-authored agent files. Never wipe the
244
244
  // whole dir; remove only the pipeline-shipped agent files (name set derived
245
245
  // from the source tree so it cannot go stale), then copy the fresh set in.
246
+ let target = dest;
246
247
  if (!useSymlinks) {
247
- ensureRealDir(dest);
248
- removePipelineAgentFiles(dest, agentsSrc);
248
+ target = ensureRealDir(dest, { userOwned: true });
249
+ removePipelineAgentFiles(target, agentsSrc);
249
250
  }
250
- copyDir(agentsSrc, dest, { useSymlinks });
251
+ copyDir(agentsSrc, target, { useSymlinks });
251
252
  console.log(` -> ${countFiles(agentsSrc)} files copied to ${dest}`);
252
253
  }
253
254
 
@@ -255,7 +256,7 @@ function installSchemas(pipelineSrc, dest, useSymlinks) {
255
256
  console.log(" [Copilot CLI] Installing JSON schemas...");
256
257
  const schemasSrc = join(pipelineSrc, "schemas");
257
258
  if (!existsSync(schemasSrc)) return;
258
- if (!useSymlinks) ensureRealDir(dest);
259
+ if (!useSymlinks && ensureRealDir(dest) === null) return;
259
260
  wipeDir(dest);
260
261
  copyDir(schemasSrc, dest, { useSymlinks });
261
262
  console.log(` -> ${countFiles(schemasSrc)} files copied to ${dest}`);
@@ -269,7 +270,7 @@ function installTemplates(pipelineSrc, dest, useSymlinks) {
269
270
  console.log(" [Copilot CLI] Installing templates...");
270
271
  const templatesSrc = join(dirname(pipelineSrc), "install", "templates");
271
272
  if (!existsSync(templatesSrc)) return;
272
- if (!useSymlinks) ensureRealDir(dest);
273
+ if (!useSymlinks && ensureRealDir(dest) === null) return;
273
274
  wipeDir(dest);
274
275
  copyDir(templatesSrc, dest, { useSymlinks });
275
276
  console.log(` -> ${countFiles(templatesSrc)} files copied to ${dest}`);
@@ -279,7 +280,7 @@ function installLib(pipelineSrc, dest, useSymlinks) {
279
280
  console.log(" [Copilot CLI] Installing shell libraries...");
280
281
  const libSrc = join(pipelineSrc, "lib");
281
282
  if (!existsSync(libSrc)) return;
282
- if (!useSymlinks) ensureRealDir(dest);
283
+ if (!useSymlinks && ensureRealDir(dest) === null) return;
283
284
  wipeDir(dest);
284
285
  copyDir(libSrc, dest, { useSymlinks });
285
286
  console.log(` -> ${countFiles(libSrc)} files copied to ${dest}`);
@@ -299,7 +300,7 @@ function installRules(pipelineSrc, dest, useSymlinks) {
299
300
  console.log(" [Copilot CLI] Installing convention rules...");
300
301
  const rulesSrc = join(pipelineSrc, "rules");
301
302
  if (!existsSync(rulesSrc)) return;
302
- if (!useSymlinks) ensureRealDir(dest);
303
+ if (!useSymlinks && ensureRealDir(dest) === null) return;
303
304
  wipeDir(dest);
304
305
  if (useSymlinks) {
305
306
  copyDir(rulesSrc, dest, { useSymlinks });
@@ -318,15 +319,16 @@ function installRules(pipelineSrc, dest, useSymlinks) {
318
319
  }
319
320
 
320
321
  function installSkills(opts) {
321
- const { home, pipelineSrc, dest, indexOnly, useSymlinks, platformFlag } = opts;
322
+ const { home, pipelineSrc, indexOnly, useSymlinks, platformFlag } = opts;
322
323
  console.log(" [Copilot CLI] Installing skills...");
323
324
 
324
325
  // The skills root is always a real directory, in --link mode too: the manifest,
325
326
  // index and README written below are generated files, and an older --link
326
327
  // install pointed the whole root at pipeline/skills/shared/core, so every one
327
328
  // of those writes (and the external / plugin symlinks) landed in the checkout.
328
- // Link mode now links each authored skill dir individually instead.
329
- ensureRealDir(dest);
329
+ // Link mode now links each authored skill dir individually instead. A
330
+ // symlink elsewhere (the user's own skills repo) is written through.
331
+ const dest = ensureRealDir(opts.dest, { pipelineSrc, userOwned: true });
330
332
 
331
333
  if (indexOnly) {
332
334
  try {
package/install/index.mjs CHANGED
@@ -12,7 +12,7 @@ import { dirname } from "path";
12
12
  import { execFileSync } from "node:child_process";
13
13
  import { fileURLToPath } from "url";
14
14
 
15
- import { readFileSync } from "fs";
15
+ import { existsSync, readFileSync } from "fs";
16
16
  import { join } from "path";
17
17
 
18
18
  import { installClaude } from "./claude.mjs";
@@ -59,6 +59,7 @@ export async function runInstall(argv) {
59
59
  const KNOWN_FLAGS = [
60
60
  ...TOOL_FLAGS,
61
61
  "--all",
62
+ "--installed",
62
63
  "--link",
63
64
  "--index-only",
64
65
  "--dry-run",
@@ -82,13 +83,26 @@ export async function runInstall(argv) {
82
83
  const dryRun = flags.includes("--dry-run");
83
84
  setDryRun(dryRun);
84
85
 
85
- const isExplicitlyTargeted = flags.some((f) => TOOL_FLAGS.includes(f)) || flags.includes("--all");
86
+ const installed = flags.includes("--installed") ? installedTargets(HOME) : null;
87
+ const isExplicitlyTargeted =
88
+ flags.some((f) => TOOL_FLAGS.includes(f)) ||
89
+ flags.includes("--all") ||
90
+ Boolean(installed && (installed.claude || installed.copilot || installed.codex));
91
+ if (installed && !isExplicitlyTargeted) {
92
+ console.log(" --installed: no target carries a version stamp yet; installing Claude Code.");
93
+ }
86
94
 
87
- const forClaude = flags.includes("--claude") || flags.includes("--all") || !isExplicitlyTargeted;
95
+ const forClaude =
96
+ flags.includes("--claude") ||
97
+ flags.includes("--all") ||
98
+ Boolean(installed?.claude) ||
99
+ !isExplicitlyTargeted;
88
100
 
89
- const forCopilot = flags.includes("--copilot") || flags.includes("--all");
101
+ const forCopilot =
102
+ flags.includes("--copilot") || flags.includes("--all") || Boolean(installed?.copilot);
90
103
 
91
- const forCodex = flags.includes("--codex") || flags.includes("--all");
104
+ const forCodex =
105
+ flags.includes("--codex") || flags.includes("--all") || Boolean(installed?.codex);
92
106
 
93
107
  const useSymlinks = flags.includes("--link");
94
108
  const indexOnly = flags.includes("--index-only");
@@ -111,9 +125,14 @@ export async function runInstall(argv) {
111
125
  if (dryRun) console.log(" (dry-run: no files will be written)");
112
126
  console.log("");
113
127
 
114
- if (forClaude) installClaude(installerCtx);
115
- if (forCopilot) installCopilot(installerCtx);
116
- if (forCodex) installCodex(installerCtx);
128
+ try {
129
+ if (forClaude) installClaude(installerCtx);
130
+ if (forCopilot) installCopilot(installerCtx);
131
+ if (forCodex) installCodex(installerCtx);
132
+ } catch (err) {
133
+ reportInstallFailure(err);
134
+ process.exit(1);
135
+ }
117
136
 
118
137
  // Version marker: update-check.sh falls back to this for npx-only installs
119
138
  // that have no pipeline repo clone to read a version from.
@@ -136,11 +155,53 @@ export async function runInstall(argv) {
136
155
  }
137
156
 
138
157
  /**
139
- * Stamp the installed package version into each target so the advisory
140
- * update check works without a repo clone. Failure is non-fatal.
158
+ * One line for a failed install, and a non-zero exit from the caller. The
159
+ * stack trace is for debugging the installer itself, so it prints only with
160
+ * MULTI_AGENT_INSTALL_DEBUG=1.
141
161
  *
142
- * @param {{home: string, forClaude: boolean, forCopilot: boolean, forCodex?: boolean}} opts
162
+ * @param {unknown} err
163
+ */
164
+ function reportInstallFailure(err) {
165
+ const reason =
166
+ err?.code === "INSTALL_INCOMPLETE"
167
+ ? err.message
168
+ : `${err?.code ? `${err.code}: ` : ""}${String(err?.message || err).split("\n")[0]}`;
169
+ console.error("");
170
+ console.error(` install incomplete: ${reason}.`);
171
+ console.error(" Fix the cause and re-run the same install command.");
172
+ if (process.env.MULTI_AGENT_INSTALL_DEBUG === "1" && err?.stack) {
173
+ console.error(err.stack);
174
+ if (err.cause?.stack) console.error(err.cause.stack);
175
+ } else {
176
+ console.error(" (MULTI_AGENT_INSTALL_DEBUG=1 prints the stack trace.)");
177
+ }
178
+ }
179
+
180
+ /** Version stamp each target's installer writes. */
181
+ export function versionStampPaths(home) {
182
+ return {
183
+ claude: join(home, ".claude", ".pipeline-version"),
184
+ copilot: join(home, ".copilot", ".pipeline-version"),
185
+ codex: join(home, ".codex", ".pipeline-version"),
186
+ };
187
+ }
188
+
189
+ /**
190
+ * The targets an earlier install laid down, told by their version stamps.
191
+ * `--installed` uses it so an update refreshes what is there and nothing else.
192
+ *
193
+ * @param {string} home
194
+ * @returns {{claude: boolean, copilot: boolean, codex: boolean}}
143
195
  */
196
+ export function installedTargets(home) {
197
+ const stamps = versionStampPaths(home);
198
+ return {
199
+ claude: existsSync(stamps.claude),
200
+ copilot: existsSync(stamps.copilot),
201
+ codex: existsSync(stamps.codex),
202
+ };
203
+ }
204
+
144
205
  /**
145
206
  * Print the unattended permission profile, then apply it. Printing first is the
146
207
  * contract: an installer that widens a permission set has to say what it is
@@ -196,6 +257,12 @@ function applyUnattended({ home, forClaude, dryRun = false }) {
196
257
  }
197
258
  }
198
259
 
260
+ /**
261
+ * Stamp the installed package version into each target so the advisory
262
+ * update check works without a repo clone. Failure is non-fatal.
263
+ *
264
+ * @param {{home: string, forClaude: boolean, forCopilot: boolean, forCodex?: boolean}} opts
265
+ */
199
266
  export function writeVersionMarkers({ home, forClaude, forCopilot, forCodex }) {
200
267
  let version;
201
268
  try {
@@ -204,10 +271,11 @@ export function writeVersionMarkers({ home, forClaude, forCopilot, forCodex }) {
204
271
  return;
205
272
  }
206
273
  if (!version) return;
274
+ const stamps = versionStampPaths(home);
207
275
  const targets = [];
208
- if (forClaude) targets.push(join(home, ".claude", ".pipeline-version"));
209
- if (forCopilot) targets.push(join(home, ".copilot", ".pipeline-version"));
210
- if (forCodex) targets.push(join(home, ".codex", ".pipeline-version"));
276
+ if (forClaude) targets.push(stamps.claude);
277
+ if (forCopilot) targets.push(stamps.copilot);
278
+ if (forCodex) targets.push(stamps.codex);
211
279
  for (const target of targets) {
212
280
  try {
213
281
  writeFile(target, `${version}\n`);
@@ -217,12 +285,8 @@ export function writeVersionMarkers({ home, forClaude, forCopilot, forCodex }) {
217
285
  }
218
286
  }
219
287
 
220
- /**
221
- * @param {string[]} flags
222
- * @returns {"ios"|"android"|"all"}
223
- */
224
288
  // Presence check only - never a hard requirement. The pipeline's own engine is
225
- // node; jq is a convenience several shell helpers reached for.
289
+ // node; jq is a convenience several shell helpers reach for.
226
290
  function hasJq() {
227
291
  try {
228
292
  execFileSync("jq", ["--version"], { stdio: "ignore", timeout: 3000 });
@@ -232,6 +296,10 @@ function hasJq() {
232
296
  }
233
297
  }
234
298
 
299
+ /**
300
+ * @param {string[]} flags
301
+ * @returns {"ios"|"android"|"all"}
302
+ */
235
303
  function parsePlatformFlag(flags) {
236
304
  const raw = flags.find((f) => f.startsWith("--platform="));
237
305
  if (!raw) return "all";
@@ -272,9 +340,8 @@ function printSummary(opts) {
272
340
  console.log("");
273
341
  }
274
342
 
275
- // All three register automatically now. Claude Code was the last to gain it, and
276
- // this summary kept telling users to do it by hand long enough that the
277
- // hand-registered entry became the only reason it worked on any given machine.
343
+ // Each installer registers the server itself; the lines below name the
344
+ // command it ran, for a host whose CLI was not on PATH.
278
345
  console.log(" For UI testing, the multi-agent-toolkit MCP server is registered automatically:");
279
346
  if (forClaude) {
280
347
  console.log(
@@ -291,11 +358,9 @@ function printSummary(opts) {
291
358
  " Skipped only when the host CLI is not on PATH - the command to run is printed above.",
292
359
  );
293
360
  console.log("");
294
- // jq is not a declared engine and macOS did not always ship it, yet thirteen
295
- // runtime paths guard on it and skip in silence when it is missing. Silence is
296
- // the problem: a cost summary that never renders and a tracker that never
297
- // updates look like bugs, not like an absent tool. Say it once, here, where
298
- // somebody can act on it.
361
+ // jq is not a declared engine and not every macOS ships it. Several runtime
362
+ // paths skip in silence without it, which reads as a bug rather than an
363
+ // absent tool, so the install says it once, where somebody can act on it.
299
364
  if (!hasJq()) {
300
365
  console.log(" Note: jq was not found on PATH.");
301
366
  console.log(" Everything installs and the pipeline runs, but a few shell");
@@ -7,13 +7,13 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "bash $HOME/.claude/scripts/pre-commit-check.sh",
10
+ "command": "bash \"$HOME/.claude/scripts/pre-commit-check.sh\"",
11
11
  "timeout": 15,
12
12
  "statusMessage": "Scanning staged changes for secrets..."
13
13
  },
14
14
  {
15
15
  "type": "command",
16
- "command": "bash $HOME/.claude/scripts/agent-guard.sh",
16
+ "command": "bash \"$HOME/.claude/scripts/agent-guard.sh\"",
17
17
  "timeout": 10,
18
18
  "statusMessage": "Checking commit/push safety (attribution + force-push)..."
19
19
  }
@@ -24,7 +24,7 @@
24
24
  "hooks": [
25
25
  {
26
26
  "type": "command",
27
- "command": "bash $HOME/.claude/scripts/agent-guard.sh",
27
+ "command": "bash \"$HOME/.claude/scripts/agent-guard.sh\"",
28
28
  "timeout": 10,
29
29
  "statusMessage": "Checking the edit target..."
30
30
  }
@@ -35,7 +35,7 @@
35
35
  "hooks": [
36
36
  {
37
37
  "type": "command",
38
- "command": "bash $HOME/.claude/scripts/agent-guard.sh",
38
+ "command": "bash \"$HOME/.claude/scripts/agent-guard.sh\"",
39
39
  "timeout": 10,
40
40
  "statusMessage": "Checking the fetch host..."
41
41
  }
@@ -46,7 +46,7 @@
46
46
  "hooks": [
47
47
  {
48
48
  "type": "command",
49
- "command": "bash $HOME/.claude/scripts/check-read-size.sh",
49
+ "command": "bash \"$HOME/.claude/scripts/check-read-size.sh\"",
50
50
  "timeout": 10,
51
51
  "statusMessage": "Checking read size..."
52
52
  }
@@ -58,7 +58,7 @@
58
58
  "hooks": [
59
59
  {
60
60
  "type": "command",
61
- "command": "bash $HOME/.claude/scripts/capture-flush.sh --quiet",
61
+ "command": "bash \"$HOME/.claude/scripts/capture-flush.sh\" --quiet",
62
62
  "timeout": 20,
63
63
  "statusMessage": "Persisting what this run learned before compaction..."
64
64
  }
@@ -70,13 +70,13 @@
70
70
  "hooks": [
71
71
  {
72
72
  "type": "command",
73
- "command": "bash $HOME/.claude/scripts/capture-flush.sh --if-stale --quiet",
73
+ "command": "bash \"$HOME/.claude/scripts/capture-flush.sh\" --if-stale --quiet",
74
74
  "timeout": 20,
75
75
  "statusMessage": "Persisting what this run learned..."
76
76
  },
77
77
  {
78
78
  "type": "command",
79
- "command": "bash $HOME/.claude/scripts/note-session.sh",
79
+ "command": "bash \"$HOME/.claude/scripts/note-session.sh\"",
80
80
  "timeout": 20,
81
81
  "statusMessage": "Noting this session..."
82
82
  }
@@ -89,7 +89,7 @@
89
89
  "hooks": [
90
90
  {
91
91
  "type": "command",
92
- "command": "bash $HOME/.claude/scripts/capture-resume.sh",
92
+ "command": "bash \"$HOME/.claude/scripts/capture-resume.sh\"",
93
93
  "timeout": 10,
94
94
  "statusMessage": "Checking for unfinished runs..."
95
95
  }