@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.
- package/CHANGELOG.md +47 -0
- package/README.md +15 -0
- package/dist/agent-setup-RBTEACYU.js +28 -0
- package/dist/{artifact-link-cmd-2F47LT7F.js → artifact-link-cmd-AVM2EKTV.js} +4 -3
- package/dist/{build-BG2YENGK.js → build-TQ6X5PYN.js} +5 -4
- package/dist/build-info.json +1 -1
- package/dist/{builder-2D3WSOOY.js → builder-DUI2ZH2H.js} +5 -6
- package/dist/{check-5XE7XMC4.js → check-5C4YDZYV.js} +53 -50
- package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
- package/dist/{chunk-G5NPRL2K.js → chunk-3OFZM4X6.js} +548 -68
- package/dist/{chunk-2MNPHL23.js → chunk-47TTLEDX.js} +6 -5
- package/dist/{chunk-PLMTLGYH.js → chunk-52NC2FIU.js} +4 -3
- package/dist/chunk-6CHIYEZZ.js +564 -0
- package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
- package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
- package/dist/{chunk-YU3VKZ5F.js → chunk-B3UKB46N.js} +6 -6
- package/dist/{chunk-JXYVA45C.js → chunk-BMYGAGBJ.js} +1050 -304
- package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
- package/dist/chunk-CRB3POP4.js +34 -0
- package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
- package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
- package/dist/{chunk-NBGDBTO2.js → chunk-ESC6UR76.js} +6 -5
- package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
- package/dist/{chunk-42ZI52JW.js → chunk-F4XSVQJ4.js} +4 -3
- package/dist/{chunk-LWSJQJC6.js → chunk-FUB7LRUE.js} +7 -7
- package/dist/chunk-G2YNJIGF.js +44 -0
- package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
- package/dist/{chunk-VQYBXPI7.js → chunk-GOBOJMOJ.js} +5 -5
- package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
- package/dist/chunk-JBJTC3UU.js +35 -0
- package/dist/{chunk-NWQTTSBB.js → chunk-JF42URPU.js} +16 -15
- package/dist/{chunk-ADJRGVZC.js → chunk-KDVIU23F.js} +1 -1
- package/dist/{chunk-S3NYLI2P.js → chunk-KK4VBOH6.js} +93 -37
- package/dist/{chunk-KMDLFI4P.js → chunk-L2USZT6T.js} +1 -1
- package/dist/{chunk-WTE3MQML.js → chunk-LXAY4A2G.js} +3 -12
- package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
- package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
- package/dist/{chunk-POJMEEJJ.js → chunk-MNTSH4CL.js} +1 -1
- package/dist/{chunk-WUYTWNMA.js → chunk-MQJSXHV7.js} +1 -1
- package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
- package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
- package/dist/{chunk-J5M7BSJQ.js → chunk-N3CUDFCW.js} +9 -3
- package/dist/{chunk-4KSQKQXE.js → chunk-NACDBXUG.js} +19 -15
- package/dist/{chunk-ONKJUM6R.js → chunk-NL4XPC7W.js} +1 -1
- package/dist/{chunk-WVUIRTWA.js → chunk-QQM2UJ6T.js} +31 -26
- package/dist/{chunk-KG5ECZGJ.js → chunk-RAZTWYZN.js} +10 -9
- package/dist/{chunk-4NPZVMJU.js → chunk-S26M3YBJ.js} +60 -4
- package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
- package/dist/{chunk-3NSNIIBE.js → chunk-S76AYB4M.js} +17 -16
- package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
- package/dist/{chunk-OZGHL5Y5.js → chunk-SWCTGCSC.js} +2 -2
- package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
- package/dist/{chunk-C5Y5ZJJZ.js → chunk-T4Y6SCG3.js} +5 -4
- package/dist/{chunk-BWQ66M6N.js → chunk-ULSIJGLS.js} +45 -13
- package/dist/{chunk-NNRTPG7X.js → chunk-UYOYTKE2.js} +4 -4
- package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
- package/dist/{chunk-YZ5F4PIS.js → chunk-W5VPCONO.js} +146 -247
- package/dist/{chunk-6Y5RUTHO.js → chunk-WBT5X3NH.js} +5 -4
- package/dist/{chunk-RLYZLUQ7.js → chunk-WGAUY5LE.js} +2 -2
- package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
- package/dist/{chunk-OHVJA253.js → chunk-X5N6KC6V.js} +47 -29
- package/dist/{chunk-22RMZBGW.js → chunk-XGP3VYOG.js} +1 -1
- package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
- package/dist/{chunk-HGECYFEI.js → chunk-Y5GE76XC.js} +131 -54
- package/dist/{chunk-MAK5LYRF.js → chunk-Y5MXET4M.js} +277 -535
- package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
- package/dist/{chunk-YO4PSLVB.js → chunk-ZRL6HKBY.js} +1 -1
- package/dist/claude-launcher-YVXI76NX.js +102 -0
- package/dist/{cli-A76AXELQ.js → cli-C77H54BV.js} +468 -161
- package/dist/{command-R2QCVX55.js → command-H5CILO4S.js} +40 -37
- package/dist/{config-N3C6DFQA.js → config-FXDG4NPJ.js} +2 -3
- package/dist/{connect-cmd-JBKAQNSF.js → connect-cmd-ZW5FV3ZW.js} +9 -8
- package/dist/{context-adopt-NFHCLVUB.js → context-adopt-O5OFXMKI.js} +13 -12
- package/dist/context-materialize-Y5IJ4MQT.js +23 -0
- package/dist/{dist-LKDWQWZ7.js → dist-QTX4RSCL.js} +2 -2
- package/dist/{docker-4J77Q2BF.js → docker-RC3PRX3Q.js} +7 -8
- package/dist/{doctor-4TAIARIG.js → doctor-2NQQML6P.js} +46 -39
- package/dist/{emit-artifact-cmd-MPZVR56N.js → emit-artifact-cmd-7MP63KXU.js} +25 -22
- package/dist/emit-cmd-PPN4QQF2.js +14 -0
- package/dist/{emit-criteria-cmd-EFGYI23V.js → emit-criteria-cmd-3RXPDLNK.js} +16 -14
- package/dist/{emit-finding-cmd-5GF3V4LR.js → emit-finding-cmd-OL7JB6H3.js} +18 -16
- package/dist/{emit-result-cmd-I4OADKMD.js → emit-result-cmd-UZVMOCF4.js} +20 -17
- package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
- package/dist/governing-source-BUU4IABS.js +35 -0
- package/dist/guest-init-KPBQOFE5.js +9 -0
- package/dist/{hook-fast-R4FVSZ2H.js → hook-fast-W5MKHMJT.js} +4 -1
- package/dist/host-key-U4B5QETR.js +8 -0
- package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-ZO6KGHI2.js} +7 -5
- package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-KJMWL5V6.js} +5 -3
- package/dist/index.js +8 -4
- package/dist/init-VWXIZEAN.js +54 -0
- package/dist/{init-PWU5YVL7.js → init-WNXXYMO7.js} +38 -35
- package/dist/{init-cmd-XAVO7HYZ.js → init-cmd-WS6TIRYT.js} +28 -24
- package/dist/{init-update-CVCWD6VS.js → init-update-E3J6TVIN.js} +27 -26
- package/dist/{install-HDBIPHTX.js → install-CBWIEF3R.js} +4 -5
- package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
- package/dist/{login-browser-QZCS3WW6.js → login-browser-O63QZN7D.js} +4 -3
- package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
- package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
- package/dist/loop-UGI2BJOT.js +76 -0
- package/dist/machine-JOW4RNZ7.js +49 -0
- package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
- package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-DOTY5FKY.js} +10 -8
- package/dist/paths-7XP63OAX.js +7 -0
- package/dist/pending-532ABZYC.js +8 -0
- package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
- package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
- package/dist/{runner-env-EN7YE7QX.js → runner-env-4NB2GA7O.js} +5 -6
- package/dist/{runner-key-6SLBI7PP.js → runner-key-TC3NFWFV.js} +38 -35
- package/dist/{session-sender-FO3LCREV.js → session-sender-NNVPSPY7.js} +17 -14
- package/dist/spec-cmd-OWVTBB4J.js +116 -0
- package/dist/{starter-pack-KF66HGZR.js → starter-pack-OPWUDOWX.js} +7 -5
- package/dist/{status-7VNQN5PW.js → status-TKCLLKFG.js} +42 -39
- package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
- package/dist/{sync-cmd-GVPPFZ3G.js → sync-cmd-Q6VYNPWG.js} +16 -13
- package/dist/{watch-23HSAEI6.js → watch-ARAGADTF.js} +16 -13
- package/dist/{work-claim-cmd-LKL3GHFQ.js → work-claim-cmd-34FERLYX.js} +25 -27
- package/dist/work-cmd-7S5B2H2P.js +25 -0
- package/dist/{work-comment-cmd-AWHP56M2.js → work-comment-cmd-SZJSPM5S.js} +15 -13
- package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
- package/dist/{work-open-pr-cmd-IOM4AVQQ.js → work-open-pr-cmd-RPXKCBWS.js} +17 -15
- package/dist/work-pr-cmd-5LGANL32.js +26 -0
- package/package.json +3 -2
- package/skill-pack/maintained/outerlayer-amend/SKILL.md +4 -2
- package/skill-pack/shipped.json +17 -2
- package/skill-pack/template/build/SKILL.md +23 -10
- package/skill-pack/template/build/references/agent-briefs.md +3 -1
- package/skill-pack/template/build/references/criteria-judge.md +27 -0
- package/skill-pack/template/build/references/release.md +19 -28
- package/skill-pack/template/spec/SKILL.md +35 -10
- package/skill-pack/validators/acceptance-criteria.yaml +25 -26
- package/dist/agent-setup-T624CWPD.js +0 -27
- package/dist/chunk-BYT5O5RV.js +0 -61
- package/dist/context-materialize-IV2TUUFO.js +0 -22
- package/dist/emit-cmd-IJ3GVRFD.js +0 -12
- package/dist/guest-init-LJP5GPZF.js +0 -9
- package/dist/host-key-YMLXY6QB.js +0 -7
- package/dist/init-6XPDRDTC.js +0 -51
- package/dist/loop-FK3LPMMZ.js +0 -73
- package/dist/machine-SQXEBAM2.js +0 -46
- package/dist/paths-OYKMVYJP.js +0 -6
- package/dist/pending-MXLSRYMO.js +0 -7
- package/dist/work-cmd-Z2WXLZPH.js +0 -23
- 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-
|
|
4
|
-
import { resolveCredentials, getJson, WorkCommandError } from './chunk-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { resolveRepoIdentity, compareVersions, SUPPORTED_VERSIONS } from './chunk-
|
|
9
|
-
import { C } from './chunk-
|
|
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-
|
|
12
|
-
import { checkFormerSkillNames } from './chunk-
|
|
13
|
-
import { defaultFetch } from './chunk-
|
|
14
|
-
import { resolveGatewayCredentials } from './chunk-
|
|
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-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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: "
|
|
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
|
|
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
|
|
677
|
+
return `Check OuterLayer cloud connectivity/config (~/.outerlayer/config.json), then run \`${cliInvocation()} context materialize\`.`;
|
|
674
678
|
case "not-governed":
|
|
675
|
-
return "Expected
|
|
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
|
|
681
|
+
return `Remove the committed AGENTS.md/.claude/.outerlayer files in a commit, then run \`${cliInvocation()} context materialize\`.`;
|
|
678
682
|
case "conflict":
|
|
679
|
-
return
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
881
|
-
var CONNECT_FIX =
|
|
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 =
|
|
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
|
|
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,
|
|
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",
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
})();
|