@outerlayer/cli 0.17.0 → 0.18.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 (144) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +15 -0
  3. package/dist/agent-setup-RBTEACYU.js +28 -0
  4. package/dist/{artifact-link-cmd-2F47LT7F.js → artifact-link-cmd-AVM2EKTV.js} +4 -3
  5. package/dist/{build-BG2YENGK.js → build-TQ6X5PYN.js} +5 -4
  6. package/dist/build-info.json +1 -1
  7. package/dist/{builder-2D3WSOOY.js → builder-DUI2ZH2H.js} +5 -6
  8. package/dist/{check-5XE7XMC4.js → check-5C4YDZYV.js} +53 -50
  9. package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
  10. package/dist/{chunk-G5NPRL2K.js → chunk-3OFZM4X6.js} +548 -68
  11. package/dist/{chunk-2MNPHL23.js → chunk-47TTLEDX.js} +6 -5
  12. package/dist/{chunk-PLMTLGYH.js → chunk-52NC2FIU.js} +4 -3
  13. package/dist/chunk-6CHIYEZZ.js +564 -0
  14. package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
  15. package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
  16. package/dist/{chunk-YU3VKZ5F.js → chunk-B3UKB46N.js} +6 -6
  17. package/dist/{chunk-JXYVA45C.js → chunk-BMYGAGBJ.js} +1050 -304
  18. package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
  19. package/dist/chunk-CRB3POP4.js +34 -0
  20. package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
  21. package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
  22. package/dist/{chunk-NBGDBTO2.js → chunk-ESC6UR76.js} +6 -5
  23. package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
  24. package/dist/{chunk-42ZI52JW.js → chunk-F4XSVQJ4.js} +4 -3
  25. package/dist/{chunk-LWSJQJC6.js → chunk-FUB7LRUE.js} +7 -7
  26. package/dist/chunk-G2YNJIGF.js +44 -0
  27. package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
  28. package/dist/{chunk-VQYBXPI7.js → chunk-GOBOJMOJ.js} +5 -5
  29. package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
  30. package/dist/chunk-JBJTC3UU.js +35 -0
  31. package/dist/{chunk-NWQTTSBB.js → chunk-JF42URPU.js} +16 -15
  32. package/dist/{chunk-ADJRGVZC.js → chunk-KDVIU23F.js} +1 -1
  33. package/dist/{chunk-S3NYLI2P.js → chunk-KK4VBOH6.js} +93 -37
  34. package/dist/{chunk-KMDLFI4P.js → chunk-L2USZT6T.js} +1 -1
  35. package/dist/{chunk-WTE3MQML.js → chunk-LXAY4A2G.js} +3 -12
  36. package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
  37. package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
  38. package/dist/{chunk-POJMEEJJ.js → chunk-MNTSH4CL.js} +1 -1
  39. package/dist/{chunk-WUYTWNMA.js → chunk-MQJSXHV7.js} +1 -1
  40. package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
  41. package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
  42. package/dist/{chunk-J5M7BSJQ.js → chunk-N3CUDFCW.js} +9 -3
  43. package/dist/{chunk-4KSQKQXE.js → chunk-NACDBXUG.js} +19 -15
  44. package/dist/{chunk-ONKJUM6R.js → chunk-NL4XPC7W.js} +1 -1
  45. package/dist/{chunk-WVUIRTWA.js → chunk-QQM2UJ6T.js} +31 -26
  46. package/dist/{chunk-KG5ECZGJ.js → chunk-RAZTWYZN.js} +10 -9
  47. package/dist/{chunk-4NPZVMJU.js → chunk-S26M3YBJ.js} +60 -4
  48. package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
  49. package/dist/{chunk-3NSNIIBE.js → chunk-S76AYB4M.js} +17 -16
  50. package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
  51. package/dist/{chunk-OZGHL5Y5.js → chunk-SWCTGCSC.js} +2 -2
  52. package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
  53. package/dist/{chunk-C5Y5ZJJZ.js → chunk-T4Y6SCG3.js} +5 -4
  54. package/dist/{chunk-BWQ66M6N.js → chunk-ULSIJGLS.js} +45 -13
  55. package/dist/{chunk-NNRTPG7X.js → chunk-UYOYTKE2.js} +4 -4
  56. package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
  57. package/dist/{chunk-YZ5F4PIS.js → chunk-W5VPCONO.js} +146 -247
  58. package/dist/{chunk-6Y5RUTHO.js → chunk-WBT5X3NH.js} +5 -4
  59. package/dist/{chunk-RLYZLUQ7.js → chunk-WGAUY5LE.js} +2 -2
  60. package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
  61. package/dist/{chunk-OHVJA253.js → chunk-X5N6KC6V.js} +47 -29
  62. package/dist/{chunk-22RMZBGW.js → chunk-XGP3VYOG.js} +1 -1
  63. package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
  64. package/dist/{chunk-HGECYFEI.js → chunk-Y5GE76XC.js} +131 -54
  65. package/dist/{chunk-MAK5LYRF.js → chunk-Y5MXET4M.js} +277 -535
  66. package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
  67. package/dist/{chunk-YO4PSLVB.js → chunk-ZRL6HKBY.js} +1 -1
  68. package/dist/claude-launcher-YVXI76NX.js +102 -0
  69. package/dist/{cli-A76AXELQ.js → cli-C77H54BV.js} +468 -161
  70. package/dist/{command-R2QCVX55.js → command-H5CILO4S.js} +40 -37
  71. package/dist/{config-N3C6DFQA.js → config-FXDG4NPJ.js} +2 -3
  72. package/dist/{connect-cmd-JBKAQNSF.js → connect-cmd-ZW5FV3ZW.js} +9 -8
  73. package/dist/{context-adopt-NFHCLVUB.js → context-adopt-O5OFXMKI.js} +13 -12
  74. package/dist/context-materialize-Y5IJ4MQT.js +23 -0
  75. package/dist/{dist-LKDWQWZ7.js → dist-QTX4RSCL.js} +2 -2
  76. package/dist/{docker-4J77Q2BF.js → docker-RC3PRX3Q.js} +7 -8
  77. package/dist/{doctor-4TAIARIG.js → doctor-2NQQML6P.js} +46 -39
  78. package/dist/{emit-artifact-cmd-MPZVR56N.js → emit-artifact-cmd-7MP63KXU.js} +25 -22
  79. package/dist/emit-cmd-PPN4QQF2.js +14 -0
  80. package/dist/{emit-criteria-cmd-EFGYI23V.js → emit-criteria-cmd-3RXPDLNK.js} +16 -14
  81. package/dist/{emit-finding-cmd-5GF3V4LR.js → emit-finding-cmd-OL7JB6H3.js} +18 -16
  82. package/dist/{emit-result-cmd-I4OADKMD.js → emit-result-cmd-UZVMOCF4.js} +20 -17
  83. package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
  84. package/dist/governing-source-BUU4IABS.js +35 -0
  85. package/dist/guest-init-KPBQOFE5.js +9 -0
  86. package/dist/{hook-fast-R4FVSZ2H.js → hook-fast-W5MKHMJT.js} +4 -1
  87. package/dist/host-key-U4B5QETR.js +8 -0
  88. package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-ZO6KGHI2.js} +7 -5
  89. package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-KJMWL5V6.js} +5 -3
  90. package/dist/index.js +8 -4
  91. package/dist/init-VWXIZEAN.js +54 -0
  92. package/dist/{init-PWU5YVL7.js → init-WNXXYMO7.js} +38 -35
  93. package/dist/{init-cmd-XAVO7HYZ.js → init-cmd-WS6TIRYT.js} +28 -24
  94. package/dist/{init-update-CVCWD6VS.js → init-update-E3J6TVIN.js} +27 -26
  95. package/dist/{install-HDBIPHTX.js → install-CBWIEF3R.js} +4 -5
  96. package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
  97. package/dist/{login-browser-QZCS3WW6.js → login-browser-O63QZN7D.js} +4 -3
  98. package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
  99. package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
  100. package/dist/loop-UGI2BJOT.js +76 -0
  101. package/dist/machine-JOW4RNZ7.js +49 -0
  102. package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
  103. package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-DOTY5FKY.js} +10 -8
  104. package/dist/paths-7XP63OAX.js +7 -0
  105. package/dist/pending-532ABZYC.js +8 -0
  106. package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
  107. package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
  108. package/dist/{runner-env-EN7YE7QX.js → runner-env-4NB2GA7O.js} +5 -6
  109. package/dist/{runner-key-6SLBI7PP.js → runner-key-TC3NFWFV.js} +38 -35
  110. package/dist/{session-sender-FO3LCREV.js → session-sender-NNVPSPY7.js} +17 -14
  111. package/dist/spec-cmd-OWVTBB4J.js +116 -0
  112. package/dist/{starter-pack-KF66HGZR.js → starter-pack-OPWUDOWX.js} +7 -5
  113. package/dist/{status-7VNQN5PW.js → status-TKCLLKFG.js} +42 -39
  114. package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
  115. package/dist/{sync-cmd-GVPPFZ3G.js → sync-cmd-Q6VYNPWG.js} +16 -13
  116. package/dist/{watch-23HSAEI6.js → watch-ARAGADTF.js} +16 -13
  117. package/dist/{work-claim-cmd-LKL3GHFQ.js → work-claim-cmd-34FERLYX.js} +25 -27
  118. package/dist/work-cmd-7S5B2H2P.js +25 -0
  119. package/dist/{work-comment-cmd-AWHP56M2.js → work-comment-cmd-SZJSPM5S.js} +15 -13
  120. package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
  121. package/dist/{work-open-pr-cmd-IOM4AVQQ.js → work-open-pr-cmd-RPXKCBWS.js} +17 -15
  122. package/dist/work-pr-cmd-5LGANL32.js +26 -0
  123. package/package.json +3 -2
  124. package/skill-pack/maintained/outerlayer-amend/SKILL.md +4 -2
  125. package/skill-pack/shipped.json +17 -2
  126. package/skill-pack/template/build/SKILL.md +23 -10
  127. package/skill-pack/template/build/references/agent-briefs.md +3 -1
  128. package/skill-pack/template/build/references/criteria-judge.md +27 -0
  129. package/skill-pack/template/build/references/release.md +19 -28
  130. package/skill-pack/template/spec/SKILL.md +35 -10
  131. package/skill-pack/validators/acceptance-criteria.yaml +25 -26
  132. package/dist/agent-setup-T624CWPD.js +0 -27
  133. package/dist/chunk-BYT5O5RV.js +0 -61
  134. package/dist/context-materialize-IV2TUUFO.js +0 -22
  135. package/dist/emit-cmd-IJ3GVRFD.js +0 -12
  136. package/dist/guest-init-LJP5GPZF.js +0 -9
  137. package/dist/host-key-YMLXY6QB.js +0 -7
  138. package/dist/init-6XPDRDTC.js +0 -51
  139. package/dist/loop-FK3LPMMZ.js +0 -73
  140. package/dist/machine-SQXEBAM2.js +0 -46
  141. package/dist/paths-OYKMVYJP.js +0 -6
  142. package/dist/pending-MXLSRYMO.js +0 -7
  143. package/dist/work-cmd-Z2WXLZPH.js +0 -23
  144. package/dist/work-pr-cmd-HNEUIEN7.js +0 -24
@@ -1,21 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { checkStarterFiles, contextIsGoverned } from './chunk-NBGDBTO2.js';
4
- import { resolveCredentials, getJson, WorkCommandError } from './chunk-S3NYLI2P.js';
5
- import { readStatuslineState, STATE_FRESH_MS } from './chunk-F47JBIAW.js';
6
- import { isLinkedRepository } from './chunk-3NSNIIBE.js';
7
- import { sanitizeTerminal } from './chunk-WUYTWNMA.js';
8
- import { resolveRepoIdentity, compareVersions, SUPPORTED_VERSIONS } from './chunk-JXYVA45C.js';
9
- import { C } from './chunk-YRYMNBGQ.js';
3
+ import { checkStarterFiles, contextIsGoverned } from './chunk-ESC6UR76.js';
4
+ import { resolveCredentials, getJson, WorkCommandError } from './chunk-KK4VBOH6.js';
5
+ import { isLinkedRepository } from './chunk-S76AYB4M.js';
6
+ import { sanitizeTerminal } from './chunk-MQJSXHV7.js';
7
+ import { readStatuslineState, STATE_FRESH_MS } from './chunk-DSF2FWAZ.js';
8
+ import { resolveRepoIdentity, compareVersions, SUPPORTED_VERSIONS } from './chunk-BMYGAGBJ.js';
9
+ import { C } from './chunk-ANPNT6PM.js';
10
10
  import { readSettings, settingsPath, isOurStatuslineSlot, statuslineWrappedOriginal, REGISTERED_EVENTS, isOurHookCommand, ourCommandBins, cliBinResolvable, ourDirectScriptBins, wrappedOriginalCommand } from './chunk-5QRP3MVS.js';
11
- import { readPin, readContextManifest, readDegraded, readSourceCache } from './chunk-WTE3MQML.js';
12
- import { checkFormerSkillNames } from './chunk-C5Y5ZJJZ.js';
13
- import { defaultFetch } from './chunk-YO4PSLVB.js';
14
- import { resolveGatewayCredentials } from './chunk-PLMTLGYH.js';
11
+ import { readPin, readContextManifest, readDegraded, readSourceCache } from './chunk-LXAY4A2G.js';
12
+ import { checkFormerSkillNames } from './chunk-T4Y6SCG3.js';
13
+ import { defaultFetch } from './chunk-ZRL6HKBY.js';
14
+ import { resolveGatewayCredentials } from './chunk-52NC2FIU.js';
15
15
  import { readCloudConfig } from './chunk-EABW6AJQ.js';
16
- import { sanitizeForContext, spoolDir, assessSyncHealth, readSyncStatus, SYNC_STALE_AFTER_MS, formatAgo, checkInstalledCliStaleness, workspaceRootOf, hookFiredMarkerPath, refreshCommand } from './chunk-BWQ66M6N.js';
16
+ import { sanitizeForContext, spoolDir, assessSyncHealth, readSyncStatus, SYNC_STALE_AFTER_MS, formatAgo, checkInstalledCliStaleness, workspaceRootOf, hookFiredMarkerPath, refreshCommand } from './chunk-ULSIJGLS.js';
17
+ import { hookCommandEscapesControlPlane, CONTROL_PLANE_DIR_VARIABLE } from './chunk-G2YNJIGF.js';
17
18
  import { deadHooksState, preparePackageManagerCommand, contextExcludesPresent } from './chunk-XCCLFVXM.js';
18
19
  import { findGitDirs } from './chunk-BJ3KTMDH.js';
20
+ import { cliInvocation } from './chunk-CRB3POP4.js';
19
21
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
20
22
  import { readFileSync, mkdirSync, writeFileSync, renameSync, existsSync, accessSync, constants, statSync, readdirSync, realpathSync } from 'fs';
21
23
  import { join, dirname, resolve, delimiter } from 'path';
@@ -193,6 +195,8 @@ function runDoctor(env = {}) {
193
195
  ...laptop ? checkRenamedSkills(env.cwd) : [],
194
196
  ...starter !== void 0 && !starter.mcpOutdated ? checkMcpCommand(env.cwd, env.processEnv) : [],
195
197
  ...checkContextSource(home, env.cwd),
198
+ ...checkGovernsList(env.cwd),
199
+ ...checkControlPlaneHooks(env.cwd),
196
200
  ...checkInstalledCli(home, env.cwd ?? process.cwd())
197
201
  ];
198
202
  }
@@ -275,11 +279,11 @@ function checkStatusline(home, now, cwd) {
275
279
  name,
276
280
  status: "warn",
277
281
  detail: `the OuterLayer segment was installed (recorded ${record.updatedAt}) but the slot no longer holds it \u2014 settings were rewritten or reset${record.original !== void 0 ? `; it wrapped: ${record.original}` : ""}`,
278
- fix: "Run `outerlayer init` to reinstall the segment."
282
+ fix: `Run \`${cliInvocation()} init\` to reinstall the segment.`
279
283
  }];
280
284
  }
281
285
  return [
282
- foreign !== void 0 ? { name, status: "warn", detail: `slot occupied by: ${foreign}`, fix: "Run `outerlayer init` to add the OuterLayer segment (wraps the existing command, never replaces it)." } : { name, status: "warn", detail: "not installed", fix: "Run `outerlayer init`." }
286
+ foreign !== void 0 ? { name, status: "warn", detail: `slot occupied by: ${foreign}`, fix: `Run \`${cliInvocation()} init\` to add the OuterLayer segment (wraps the existing command, never replaces it).` } : { name, status: "warn", detail: "not installed", fix: `Run \`${cliInvocation()} init\`.` }
283
287
  ];
284
288
  }
285
289
  const checks = [
@@ -288,12 +292,12 @@ function checkStatusline(home, now, cwd) {
288
292
  const stateName = "Status-line state";
289
293
  const state = readStatuslineState(home);
290
294
  if (!state) {
291
- checks.push({ name: stateName, status: "warn", detail: "no state file \u2014 line runs degraded (session cost only)", fix: "Start `outerlayer daemon` (it maintains this file)." });
295
+ checks.push({ name: stateName, status: "warn", detail: "no state file \u2014 line runs degraded (session cost only)", fix: `Start \`${cliInvocation()} daemon\` (it maintains this file).` });
292
296
  return checks;
293
297
  }
294
298
  const age = now() - Date.parse(state.generatedAt);
295
299
  if (!Number.isFinite(age) || age > STATE_FRESH_MS) {
296
- checks.push({ name: stateName, status: "warn", detail: `state ${formatAgo(age)} \u2014 line runs degraded`, fix: "Restart `outerlayer daemon`." });
300
+ checks.push({ name: stateName, status: "warn", detail: `state ${formatAgo(age)} \u2014 line runs degraded`, fix: `Restart \`${cliInvocation()} daemon\`.` });
297
301
  return checks;
298
302
  }
299
303
  checks.push({ name: stateName, status: "pass", detail: `fresh (${formatAgo(age)})` });
@@ -339,7 +343,7 @@ function checkTranscripts(transcripts, now) {
339
343
  return { name: "Transcripts", status: "warn", detail: "no projects directory yet", fix: "Run a Claude Code session, then re-check." };
340
344
  }
341
345
  if (transcripts.count === 0) {
342
- return { name: "Transcripts", status: "warn", detail: "0 transcripts found", fix: "Run a Claude Code session, then run `outerlayer doctor` again." };
346
+ return { name: "Transcripts", status: "warn", detail: "0 transcripts found", fix: `Run a Claude Code session, then run \`${cliInvocation()} doctor\` again.` };
343
347
  }
344
348
  const age = formatAgeH(now() - transcripts.newest);
345
349
  return { name: "Transcripts", status: "pass", detail: `${transcripts.count} transcripts, newest ${age}` };
@@ -402,7 +406,7 @@ function checkHooksInstalled(home, cwd, firingFresh) {
402
406
  name,
403
407
  status: "fail",
404
408
  detail: `hooks in ${path} run ${missing.join(", ")}, which no longer exists \u2014 no session is being captured`,
405
- fix: "Run `outerlayer init` to reinstall."
409
+ fix: `Run \`${cliInvocation()} init\` to reinstall.`
406
410
  };
407
411
  }
408
412
  const unexecutable = ourDirectScriptBins(settings).filter((bin) => !isExecutable(bin));
@@ -411,13 +415,13 @@ function checkHooksInstalled(home, cwd, firingFresh) {
411
415
  name,
412
416
  status: "fail",
413
417
  detail: `hooks in ${path} run ${unexecutable.join(", ")} directly, and it is not executable \u2014 every hook fails with "Permission denied" and no session is linked to its work item`,
414
- fix: "Run `outerlayer init` to rewrite the hooks so they run through node."
418
+ fix: `Run \`${cliInvocation()} init\` to rewrite the hooks so they run through node.`
415
419
  };
416
420
  }
417
421
  return { name, status: "pass", detail: `${installed.join(", ")} (${scope})` };
418
422
  }
419
423
  if (installed.length > 0) {
420
- return { name, status: "warn", detail: `only ${installed.join(", ")} installed`, fix: "Run `outerlayer init` to (re)install all lifecycle hooks." };
424
+ return { name, status: "warn", detail: `only ${installed.join(", ")} installed`, fix: `Run \`${cliInvocation()} init\` to (re)install all lifecycle hooks.` };
421
425
  }
422
426
  }
423
427
  const manifest = readManifest(home);
@@ -428,13 +432,13 @@ function checkHooksInstalled(home, cwd, firingFresh) {
428
432
  name,
429
433
  status: "fail",
430
434
  detail: `hooks were installed into ${recorded} (recorded ${record.updatedAt}) but the settings file no longer contains them \u2014 it was rewritten or reset`,
431
- fix: "Run `outerlayer init` to reinstall."
435
+ fix: `Run \`${cliInvocation()} init\` to reinstall.`
432
436
  };
433
437
  }
434
438
  if (firingFresh) {
435
439
  return { name, status: "pass", detail: "not in user/project settings, but hooks are firing \u2014 installed in another scope (managed settings?)" };
436
440
  }
437
- return { name, status: "fail", detail: "no OuterLayer hooks found", fix: "Run `outerlayer init`." };
441
+ return { name, status: "fail", detail: "no OuterLayer hooks found", fix: `Run \`${cliInvocation()} init\`.` };
438
442
  }
439
443
  function checkWrappedHooks(home, cwd) {
440
444
  const manifest = readManifest(home);
@@ -469,7 +473,7 @@ function checkWrappedHooks(home, cwd) {
469
473
  name: "Wrapped hooks",
470
474
  status: "warn",
471
475
  detail: `recorded as wrapped but the settings no longer carry the wrapped form \u2014 ${missing.join("; ")}`,
472
- fix: "Run `outerlayer hooks wrap` to re-wrap, or `outerlayer hooks unwrap` if the wrap is no longer wanted."
476
+ fix: `Run \`${cliInvocation()} hooks wrap\` to re-wrap, or \`${cliInvocation()} hooks unwrap\` if the wrap is no longer wanted.`
473
477
  };
474
478
  }
475
479
  function checkHooksFiring(transcripts, lastFired, now) {
@@ -488,7 +492,7 @@ function checkHooksFiring(transcripts, lastFired, now) {
488
492
  name,
489
493
  status: "fail",
490
494
  detail: `sessions as recent as ${formatAgeH(now() - transcripts.newest)}, but no lifecycle hook has fired since ${formatAgeH(now() - lastFired)}`,
491
- fix: "Hooks are configured but not running \u2014 run `outerlayer init` and check `claude --debug` hook output."
495
+ fix: `Hooks are configured but not running \u2014 run \`${cliInvocation()} init\` and check \`claude --debug\` hook output.`
492
496
  };
493
497
  }
494
498
  return { name, status: "pass", detail: `last fired ${formatAgeH(now() - lastFired)}` };
@@ -504,7 +508,7 @@ function checkDeadHooksState(cwd) {
504
508
  name,
505
509
  status: "fail",
506
510
  detail: `${sanitizeForContext(state.hooksDir)} could not be read (${state.unreadableError.code ?? state.unreadableError.message}) \u2014 git's hooks status is unknown, not confirmed healthy`,
507
- fix: "Fix the directory's permissions so it can be listed, then re-run `outerlayer doctor`."
511
+ fix: `Fix the directory's permissions so it can be listed, then re-run \`${cliInvocation()} doctor\`.`
508
512
  }
509
513
  ];
510
514
  }
@@ -536,15 +540,15 @@ function checkSpoolWritable(home) {
536
540
  function checkLastSync(home, now) {
537
541
  const name = "Cloud sync";
538
542
  if (!existsSync(join(home, ".outerlayer", "config.json"))) {
539
- return { name, status: "warn", detail: "not configured \u2014 sessions stay on this machine", fix: "Run `outerlayer login` to connect." };
543
+ return { name, status: "warn", detail: "not configured \u2014 sessions stay on this machine", fix: `Run \`${cliInvocation()} login\` to connect.` };
540
544
  }
541
545
  const health = assessSyncHealth(readSyncStatus(home), now(), SYNC_STALE_AFTER_MS);
542
546
  const detail = [health.headline, ...health.details].join("; ");
543
547
  if (health.level === "error") {
544
- return { name, status: "fail", detail, fix: "Check the destination and API key in ~/.outerlayer/config.json, then run `outerlayer sync` to see the full error." };
548
+ return { name, status: "fail", detail, fix: `Check the destination and API key in ~/.outerlayer/config.json, then run \`${cliInvocation()} sync\` to see the full error.` };
545
549
  }
546
550
  if (health.level === "warn" || health.level === "unknown") {
547
- return { name, status: "warn", detail, fix: "Run `outerlayer sync` to confirm the connection works." };
551
+ return { name, status: "warn", detail, fix: `Run \`${cliInvocation()} sync\` to confirm the connection works.` };
548
552
  }
549
553
  return { name, status: "pass", detail };
550
554
  }
@@ -552,17 +556,17 @@ function checkDaemon(home) {
552
556
  const pidfile = join(home, ".outerlayer", "daemon.pid");
553
557
  const heartbeat = join(home, ".outerlayer", "daemon.heartbeat");
554
558
  if (!existsSync(pidfile)) {
555
- return { name: "Daemon running", status: "warn", detail: "not running", fix: "Start it with `outerlayer daemon` (or a login agent)." };
559
+ return { name: "Daemon running", status: "warn", detail: "not running", fix: `Start it with \`${cliInvocation()} daemon\` (or a login agent).` };
556
560
  }
557
561
  try {
558
562
  const beatMs = statSync(heartbeat).mtimeMs;
559
563
  const staleMin = (Date.now() - beatMs) / 6e4;
560
564
  if (staleMin > 30) {
561
- return { name: "Daemon running", status: "warn", detail: `heartbeat ${Math.round(staleMin)}m stale`, fix: "Restart `outerlayer daemon`." };
565
+ return { name: "Daemon running", status: "warn", detail: `heartbeat ${Math.round(staleMin)}m stale`, fix: `Restart \`${cliInvocation()} daemon\`.` };
562
566
  }
563
567
  return { name: "Daemon running", status: "pass", detail: "heartbeat fresh" };
564
568
  } catch {
565
- return { name: "Daemon running", status: "warn", detail: "pidfile present, no heartbeat", fix: "Restart `outerlayer daemon`." };
569
+ return { name: "Daemon running", status: "warn", detail: "pidfile present, no heartbeat", fix: `Restart \`${cliInvocation()} daemon\`.` };
566
570
  }
567
571
  }
568
572
  function checkCleanupPeriod(home) {
@@ -580,11 +584,11 @@ function checkCleanupPeriod(home) {
580
584
  name: "Retention (cleanupPeriodDays)",
581
585
  status: "fail",
582
586
  detail: "set to 0 \u2014 this DISABLES transcript persistence (upstream anthropics/claude-code#23710 and #59248)",
583
- fix: "Remove cleanupPeriodDays or set it \u226514; run `outerlayer daemon` to mirror before deletion."
587
+ fix: `Remove cleanupPeriodDays or set it \u226514; run \`${cliInvocation()} daemon\` to mirror before deletion.`
584
588
  };
585
589
  }
586
590
  if (days < 14) {
587
- return { name: "Retention (cleanupPeriodDays)", status: "warn", detail: `${days}d \u2014 transcripts deleted quickly`, fix: "Run `outerlayer daemon` so raws are mirrored before cleanup." };
591
+ return { name: "Retention (cleanupPeriodDays)", status: "warn", detail: `${days}d \u2014 transcripts deleted quickly`, fix: `Run \`${cliInvocation()} daemon\` so raws are mirrored before cleanup.` };
588
592
  }
589
593
  return { name: "Retention (cleanupPeriodDays)", status: "pass", detail: `${days}d` };
590
594
  }
@@ -606,7 +610,7 @@ function checkClaudeVersion(env) {
606
610
  const parsed = version.match(/\d+\.\d+\.\d+/)?.[0];
607
611
  if (!parsed) return { name: "Claude Code version", status: "warn", detail: `unrecognized: ${version}` };
608
612
  if (compareVersions(parsed, SUPPORTED_VERSIONS.max) > 0) {
609
- return { name: "Claude Code version", status: "warn", detail: `${parsed} is newer than validated (${SUPPORTED_VERSIONS.max}) \u2014 parser runs best-effort`, fix: "Update OuterLayer if you see parse warnings." };
613
+ return { name: "Claude Code version", status: "pass", detail: `${parsed} (newer than ${SUPPORTED_VERSIONS.max}, the latest version this release was tested with)` };
610
614
  }
611
615
  if (compareVersions(parsed, SUPPORTED_VERSIONS.min) < 0) {
612
616
  return { name: "Claude Code version", status: "warn", detail: `${parsed} is older than validated (${SUPPORTED_VERSIONS.min})` };
@@ -666,19 +670,92 @@ function defaultClaudeInstalls() {
666
670
  function contextDegradedFix(kind) {
667
671
  switch (kind) {
668
672
  case "unreachable":
669
- return "Check network/VPN access to the control plane, then run `outerlayer context materialize`.";
673
+ return `Check network/VPN access to the control plane, then run \`${cliInvocation()} context materialize\`.`;
670
674
  case "no-access":
671
675
  return "Check your git credentials (SSH key or token) and org/SSO access to the control plane, then retry.";
672
676
  case "designation-unverified":
673
- return "Check OuterLayer cloud connectivity/config (~/.outerlayer/config.json), then run `outerlayer context materialize`.";
677
+ return `Check OuterLayer cloud connectivity/config (~/.outerlayer/config.json), then run \`${cliInvocation()} context materialize\`.`;
674
678
  case "not-governed":
675
- return "Expected if the control plane's `governs` list changed intentionally; otherwise re-designate the source.";
679
+ return "Expected after the control plane was removed, or after this repository stopped being linked to its factory. Otherwise link the repository to the factory and, in the factory's Settings, choose the control plane in the Repositories list with Use as control plane.";
676
680
  case "adoption-conflict":
677
- return "Remove the committed AGENTS.md/.claude/.outerlayer files in a commit, then run `outerlayer context materialize`.";
681
+ return `Remove the committed AGENTS.md/.claude/.outerlayer files in a commit, then run \`${cliInvocation()} context materialize\`.`;
678
682
  case "conflict":
679
- return "Revoke the designation from all but one of the claiming factories, then run `outerlayer context materialize`.";
683
+ return `This record is stale: a gateway that stores the control plane never reports two. Run \`${cliInvocation()} context materialize\` again.`;
680
684
  }
681
685
  }
686
+ function checkGovernsList(cwd) {
687
+ let repoRoot;
688
+ try {
689
+ repoRoot = execFileSync("git", ["-C", cwd ?? process.cwd(), "rev-parse", "--show-toplevel"], {
690
+ encoding: "utf8",
691
+ stdio: ["ignore", "pipe", "ignore"],
692
+ timeout: 2e3
693
+ }).trim();
694
+ } catch {
695
+ return [];
696
+ }
697
+ let config;
698
+ try {
699
+ config = JSON.parse(readFileSync(join(repoRoot, ".outerlayer", "config.json"), "utf8"));
700
+ } catch {
701
+ return [];
702
+ }
703
+ if (typeof config !== "object" || config === null || !("governs" in config)) return [];
704
+ return [
705
+ {
706
+ name: "Governs list",
707
+ status: "warn",
708
+ detail: "`governs` in .outerlayer/config.json is ignored. A control plane governs every repository its factory links, so the list decides nothing.",
709
+ fix: "Delete `governs` from .outerlayer/config.json once every machine and runner host runs CLI 0.17.0 or later. Older CLIs still read it."
710
+ }
711
+ ];
712
+ }
713
+ function hookCommandsOf(settings) {
714
+ const hooks = settings?.hooks;
715
+ if (typeof hooks !== "object" || hooks === null) return [];
716
+ const commands = [];
717
+ for (const groups of Object.values(hooks)) {
718
+ if (!Array.isArray(groups)) continue;
719
+ for (const group of groups) {
720
+ const entries = group?.hooks;
721
+ if (!Array.isArray(entries)) continue;
722
+ for (const entry of entries) {
723
+ const command = entry?.command;
724
+ if (typeof command === "string") commands.push(command);
725
+ }
726
+ }
727
+ }
728
+ return commands;
729
+ }
730
+ function checkControlPlaneHooks(cwd) {
731
+ let repoRoot;
732
+ try {
733
+ repoRoot = execFileSync("git", ["-C", cwd ?? process.cwd(), "rev-parse", "--show-toplevel"], {
734
+ encoding: "utf8",
735
+ stdio: ["ignore", "pipe", "ignore"],
736
+ timeout: 2e3
737
+ }).trim();
738
+ } catch {
739
+ return [];
740
+ }
741
+ if (!existsSync(join(repoRoot, ".outerlayer"))) return [];
742
+ let settings;
743
+ try {
744
+ settings = JSON.parse(readFileSync(join(repoRoot, ".claude", "settings.json"), "utf8"));
745
+ } catch {
746
+ return [];
747
+ }
748
+ const escaping = hookCommandsOf(settings).filter(hookCommandEscapesControlPlane);
749
+ if (escaping.length === 0) return [];
750
+ return [
751
+ {
752
+ name: "Control plane hook paths",
753
+ status: "warn",
754
+ detail: `${escaping.length === 1 ? "this hook command reaches" : "these hook commands reach"} a .claude directory by a path a governed build does not provide, so ${escaping.length === 1 ? "it fails" : "they fail"} there without a message: ${escaping.map((command) => `\`${command}\``).join(", ")}`,
755
+ fix: `Name the script as \`\${${CONTROL_PLANE_DIR_VARIABLE}:-$CLAUDE_PROJECT_DIR}/.claude/\u2026\`. A governed build sets ${CONTROL_PLANE_DIR_VARIABLE} to where it reads the control plane; a checkout that holds its own copy falls back to the project directory.`
756
+ }
757
+ ];
758
+ }
682
759
  function checkContextSource(home, cwd) {
683
760
  const dir = cwd ?? process.cwd();
684
761
  let repoRoot;
@@ -711,7 +788,7 @@ function checkContextSource(home, cwd) {
711
788
  name,
712
789
  status: "warn",
713
790
  detail: `pin (${pin.sha.slice(0, 7)}) and manifest (${manifest.sha.slice(0, 7)}) disagree`,
714
- fix: "Run `outerlayer context materialize` to reconcile."
791
+ fix: `Run \`${cliInvocation()} context materialize\` to reconcile.`
715
792
  });
716
793
  } else if (pin ?? manifest) {
717
794
  const sha = (pin ?? manifest).sha;
@@ -730,7 +807,7 @@ function checkContextSource(home, cwd) {
730
807
  name: "Context exclude block",
731
808
  status: "warn",
732
809
  detail: "materialized paths are not excluded in .git/info/exclude",
733
- fix: "Run `outerlayer context materialize` to repair it."
810
+ fix: `Run \`${cliInvocation()} context materialize\` to repair it.`
734
811
  });
735
812
  }
736
813
  if (pin && !readCloudConfig(home).url) {
@@ -738,7 +815,7 @@ function checkContextSource(home, cwd) {
738
815
  name,
739
816
  status: "warn",
740
817
  detail: "a pin exists but OuterLayer cloud sync isn't configured \u2014 the designation cannot be confirmed",
741
- fix: "Run `outerlayer login` to connect."
818
+ fix: `Run \`${cliInvocation()} login\` to connect.`
742
819
  });
743
820
  }
744
821
  if (looksGoverned) {
@@ -759,7 +836,7 @@ function checkContextSource(home, cwd) {
759
836
  name,
760
837
  status: "fail",
761
838
  detail: `committed context files shadow the control plane: ${sample}`,
762
- fix: "Remove the committed AGENTS.md/.claude/.outerlayer files in a commit, then run `outerlayer context materialize`."
839
+ fix: `Remove the committed AGENTS.md/.claude/.outerlayer files in a commit, then run \`${cliInvocation()} context materialize\`.`
763
840
  });
764
841
  }
765
842
  }
@@ -816,7 +893,7 @@ function githubAppRefusalCheck(err) {
816
893
  if (err instanceof WorkCommandError && err.status === 403) {
817
894
  return { name, status: "warn", detail: `the key cannot read the factory's repositories: ${message}`, fix: "Use a key that holds git.read." };
818
895
  }
819
- return { name, status: "warn", detail: `could not read the factory's repositories: ${message}`, fix: "Run `outerlayer doctor` again; check the gateway URL in ~/.outerlayer/config.json." };
896
+ return { name, status: "warn", detail: `could not read the factory's repositories: ${message}`, fix: `Run \`${cliInvocation()} doctor\` again; check the gateway URL in ~/.outerlayer/config.json.` };
820
897
  }
821
898
  function describeMissing(missing) {
822
899
  const held = missing.granted === null ? "not granted" : `has ${sanitizeTerminal(missing.granted)}`;
@@ -877,8 +954,8 @@ function repositoryAccessCheck(access) {
877
954
  return { name, status: "pass", detail: `every permission builds need is accepted; ${pullRequest}` };
878
955
  }
879
956
  var CREDENTIAL_CHECK_TIMEOUT_MS = 15e3;
880
- var LOGIN_FIX = "Run `outerlayer login` to sign in again.";
881
- var CONNECT_FIX = "Run `outerlayer connect` to choose a factory for this repository.";
957
+ var LOGIN_FIX = `Run \`${cliInvocation()} login\` to sign in again.`;
958
+ var CONNECT_FIX = `Run \`${cliInvocation()} connect\` to choose a factory for this repository.`;
882
959
  var SETUP_PANEL_FIX = "Link the repository on the factory's setup panel in the dashboard.";
883
960
  var MINT_KEY_FIX = "Mint a key for a factory that exists: dashboard \u2192 Settings \u2192 API keys.";
884
961
  async function askGateway(fetchImpl, url, path, credential, appId) {
@@ -906,7 +983,7 @@ function refusalMessage(body) {
906
983
  const message = body?.error?.message;
907
984
  return typeof message === "string" ? sanitizeTerminal(message) : "";
908
985
  }
909
- var NETWORK_FIX = "Check the network and the gateway address, then run `outerlayer doctor` again.";
986
+ var NETWORK_FIX = `Check the network and the gateway address, then run \`${cliInvocation()} doctor\` again.`;
910
987
  var UPDATE_FIX = "Update the outerlayer CLI and the gateway to the same release.";
911
988
  var skippedCheck = (name, detail, fix) => ({ name, status: "skipped", detail, ...fix ? { fix } : {} });
912
989
  function withSkippedRest(first, reason) {
@@ -976,7 +1053,7 @@ async function loginChecks(fetchImpl, url, token, appId, local, inRepository) {
976
1053
  return [login, factoryCheck, { name: "Repository", status: "pass", detail: `${shown} is linked to ${factorySlug(factory)}` }];
977
1054
  }
978
1055
  const elsewhere = factories.find((f) => f.id !== factory.id && f.linked === true);
979
- const fix = elsewhere === void 0 ? SETUP_PANEL_FIX : `Run \`outerlayer connect --factory ${factorySlug(elsewhere)}\` to use the factory this repository is linked to.`;
1056
+ const fix = elsewhere === void 0 ? SETUP_PANEL_FIX : `Run \`${cliInvocation()} connect --factory ${factorySlug(elsewhere)}\` to use the factory this repository is linked to.`;
980
1057
  return [login, factoryCheck, { name: "Repository", status: "warn", detail: `${shown} is not linked to ${factorySlug(factory)}`, fix }];
981
1058
  }
982
1059
  async function factoryKeyChecks(fetchImpl, url, key, appId, source, local) {
@@ -1074,7 +1151,7 @@ async function credentialChecks(options = {}) {
1074
1151
  const resolved = resolveGatewayCredentials({ home: options.home, env, cwd });
1075
1152
  if (resolved.credential === void 0 || resolved.url === void 0) {
1076
1153
  const reason = resolved.credential === void 0 ? "no login and no factory key is saved" : "no gateway address is saved";
1077
- return withSkippedRest(skippedCheck("Credential", reason, "Run `outerlayer login` to sign in."), "there is no credential to check");
1154
+ return withSkippedRest(skippedCheck("Credential", reason, `Run \`${cliInvocation()} login\` to sign in.`), "there is no credential to check");
1078
1155
  }
1079
1156
  const fetchImpl = options.fetchImpl ?? defaultFetch;
1080
1157
  const local = resolveRepoIdentity(cwd).gitRepo;
@@ -1093,7 +1170,7 @@ async function governanceChecks(options = {}) {
1093
1170
  const cwd = options.cwd ?? process.cwd();
1094
1171
  const resolved = resolveGatewayCredentials({ home: options.home, env, cwd });
1095
1172
  if (resolved.credential === void 0 || resolved.url === void 0) {
1096
- return [skippedCheck(name, "there is no credential to ask with", "Run `outerlayer login` to sign in.")];
1173
+ return [skippedCheck(name, "there is no credential to ask with", `Run \`${cliInvocation()} login\` to sign in.`)];
1097
1174
  }
1098
1175
  const local = resolveRepoIdentity(cwd).gitRepo;
1099
1176
  if (local === void 0) return [skippedCheck(name, "not inside a git repository with a remote")];
@@ -1103,7 +1180,7 @@ async function governanceChecks(options = {}) {
1103
1180
  const unanswered = unansweredDetail(reply);
1104
1181
  if (unanswered !== void 0 && !("failure" in reply) && reply.status === 503) {
1105
1182
  const message = refusalMessage(reply.body);
1106
- return [{ name, status: "warn", detail: message === "" ? unanswered : `governed, but ${message}`, fix: "Check that the GitHub App can read the control plane, then run `outerlayer doctor` again." }];
1183
+ return [{ name, status: "warn", detail: message === "" ? unanswered : `governed, but ${message}`, fix: `Check that the GitHub App can read the control plane, then run \`${cliInvocation()} doctor\` again.` }];
1107
1184
  }
1108
1185
  if (unanswered !== void 0) return [{ name, status: "warn", detail: unanswered, fix: NETWORK_FIX }];
1109
1186
  const { status, body } = reply;
@@ -1140,7 +1217,7 @@ function doctorExitCode(checks) {
1140
1217
 
1141
1218
  // src/doctor-report.ts
1142
1219
  async function gatherDoctorChecks() {
1143
- const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-N3C6DFQA.js');
1220
+ const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-FXDG4NPJ.js');
1144
1221
  const configPath = defaultRunnerConfigPath();
1145
1222
  const checks = runDoctor({ runnerHost: hasRunnerBlock(configPath) });
1146
1223
  const credential = await credentialChecks();
@@ -1152,7 +1229,7 @@ async function gatherDoctorChecks() {
1152
1229
  const { runnerVmCheck } = await import('./doctor-53F2PYY7.js');
1153
1230
  checks.push(runnerVmCheck(configPath, { lima: realLima(), home: homeFromEnv(), instance: runnerVm.instance }));
1154
1231
  } else if (hasRunnerBlock(configPath)) {
1155
- const { runnerHostChecks } = await import('./doctor-4TAIARIG.js');
1232
+ const { runnerHostChecks } = await import('./doctor-2NQQML6P.js');
1156
1233
  const hooks = (() => {
1157
1234
  try {
1158
1235
  return loadRunnerConfig(configPath).hooks;
@@ -1187,7 +1264,7 @@ async function gatherDoctorChecks() {
1187
1264
  const { existsSync: existsSync3 } = await import('fs');
1188
1265
  if (process.platform !== "linux" || !existsSync3("/run/systemd/system")) return void 0;
1189
1266
  const { inspectService, realInspectProbes } = await import('./inspect-6UYAAXH7.js');
1190
- const { DEFAULT_UNIT_NAME } = await import('./install-HDBIPHTX.js');
1267
+ const { DEFAULT_UNIT_NAME } = await import('./install-CBWIEF3R.js');
1191
1268
  const { dirname: dirname2 } = await import('path');
1192
1269
  return inspectService(DEFAULT_UNIT_NAME, dirname2(configPath), realInspectProbes());
1193
1270
  })();