@outerlayer/cli 0.1.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +238 -77
  3. package/dist/agent-setup-IFH3FB5X.js +197 -0
  4. package/dist/build-TZUUTRGH.js +9 -0
  5. package/dist/build-info.json +1 -1
  6. package/dist/check-WQP2CZDW.js +94 -0
  7. package/dist/chunk-2E54Z3P5.js +232 -0
  8. package/dist/chunk-3TT7RQYB.js +55 -0
  9. package/dist/chunk-3YPKXE5L.js +1683 -0
  10. package/dist/{chunk-HNCCD2IX.js → chunk-5DLBAXY5.js} +209 -61
  11. package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
  12. package/dist/chunk-5Y3QQRUH.js +300 -0
  13. package/dist/chunk-6Y63SXFO.js +383 -0
  14. package/dist/chunk-774HEQL6.js +178 -0
  15. package/dist/chunk-7A6RVXPZ.js +2233 -0
  16. package/dist/chunk-7YIOOFVJ.js +71 -0
  17. package/dist/chunk-ABIWDUMS.js +104 -0
  18. package/dist/chunk-AUFG23AA.js +534 -0
  19. package/dist/chunk-B2G7JHHB.js +42 -0
  20. package/dist/chunk-BCJNHMZT.js +34 -0
  21. package/dist/chunk-BCLJSQCV.js +56 -0
  22. package/dist/chunk-BFESLKPP.js +61 -0
  23. package/dist/chunk-BJ3KTMDH.js +63 -0
  24. package/dist/chunk-BKO6JEZI.js +47 -0
  25. package/dist/chunk-BOLTI6LR.js +37 -0
  26. package/dist/chunk-CBPPJSAR.js +89 -0
  27. package/dist/chunk-CBZB6XY6.js +102 -0
  28. package/dist/{chunk-RCQXYLMO.js → chunk-DVDEBNQJ.js} +18 -18
  29. package/dist/{work-pr-cmd-GOYSPEN2.js → chunk-DVYHCMB2.js} +10 -25
  30. package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
  31. package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
  32. package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
  33. package/dist/chunk-F6GFZXZ3.js +60 -0
  34. package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
  35. package/dist/chunk-FAJSS2WN.js +1357 -0
  36. package/dist/chunk-FS2VYGZT.js +1764 -0
  37. package/dist/chunk-FV2CCGXK.js +9 -0
  38. package/dist/{chunk-BQB3U2VD.js → chunk-G7FSV7JX.js} +5113 -3729
  39. package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
  40. package/dist/chunk-HE2D4EY5.js +83 -0
  41. package/dist/chunk-L2DBRKRA.js +94 -0
  42. package/dist/chunk-LF6MLJCY.js +38 -0
  43. package/dist/chunk-M5POMKKW.js +1051 -0
  44. package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
  45. package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
  46. package/dist/chunk-N6LURUEF.js +64 -0
  47. package/dist/chunk-NYC54VBD.js +1084 -0
  48. package/dist/chunk-OGFGZQA3.js +23 -0
  49. package/dist/chunk-QOZKPLVJ.js +226 -0
  50. package/dist/chunk-RA3O54FC.js +30 -0
  51. package/dist/chunk-TBT347UY.js +41 -0
  52. package/dist/{chunk-3VQ4XXQA.js → chunk-UFFXNXLQ.js} +115 -71
  53. package/dist/chunk-USO2DKBD.js +421 -0
  54. package/dist/chunk-W4SQCOZU.js +206 -0
  55. package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
  56. package/dist/chunk-WO2BXCTQ.js +83 -0
  57. package/dist/chunk-WZBHBH3J.js +20 -0
  58. package/dist/{chunk-IWIUYLDR.js → chunk-XCCLFVXM.js} +118 -189
  59. package/dist/{context-materialize-J6CRQ7O7.js → chunk-XDDW4FRS.js} +134 -26
  60. package/dist/chunk-YYYXRJUW.js +101 -0
  61. package/dist/chunk-ZM2IMMYN.js +76 -0
  62. package/dist/chunk-ZMYPLFG3.js +71 -0
  63. package/dist/chunk-ZNA27WEV.js +470 -0
  64. package/dist/{cli-EFJIG2VT.js → cli-NHUXABYH.js} +597 -965
  65. package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
  66. package/dist/config-XVJYZ4IQ.js +9 -0
  67. package/dist/connect-cmd-OOG4TPU7.js +16 -0
  68. package/dist/context-adopt-UMA4O5NY.js +105 -0
  69. package/dist/context-materialize-G2FUFC72.js +19 -0
  70. package/dist/dist-RW4UPPC2.js +6 -0
  71. package/dist/docker-NEGDLU6D.js +7 -0
  72. package/dist/doctor-53F2PYY7.js +43 -0
  73. package/dist/doctor-UFVL4PZY.js +426 -0
  74. package/dist/{emit-artifact-cmd-KPPD3YLO.js → emit-artifact-cmd-KTYW2TN2.js} +25 -19
  75. package/dist/emit-cmd-DCERAU27.js +9 -0
  76. package/dist/emit-criteria-cmd-VAUUSAYD.js +166 -0
  77. package/dist/{emit-finding-cmd-VR5VEGXJ.js → emit-finding-cmd-UR4ID45Z.js} +35 -30
  78. package/dist/{emit-result-cmd-YOA5BJKL.js → emit-result-cmd-FDTUKGKE.js} +35 -61
  79. package/dist/exec-client-4XGXV3XN.js +8 -0
  80. package/dist/guest-init-GENRHNP7.js +8 -0
  81. package/dist/hook-fast-EYENPK6F.js +9 -0
  82. package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
  83. package/dist/host-key-KDYZ5ECC.js +7 -0
  84. package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
  85. package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
  86. package/dist/index.js +4 -4
  87. package/dist/init-DXBCOAJK.js +36 -0
  88. package/dist/init-YEXK35CF.js +221 -0
  89. package/dist/init-cmd-NHR7PRSQ.js +135 -0
  90. package/dist/install-cmd-7WXOIFDO.js +55 -0
  91. package/dist/lima-XN65D7GN.js +55 -0
  92. package/dist/login-browser-7XGSV7MA.js +10 -0
  93. package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
  94. package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
  95. package/dist/loop-VZOLL4WQ.js +43 -0
  96. package/dist/machine-WSG52J75.js +35 -0
  97. package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
  98. package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-JPTP5FMS.js} +20 -9
  99. package/dist/paths-OYKMVYJP.js +6 -0
  100. package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
  101. package/dist/real-deps-SU24ZA2K.js +21 -0
  102. package/dist/relay-L76HDX72.js +46 -0
  103. package/dist/settings-J2652U5N.js +7 -0
  104. package/dist/starter-pack-LIZYMKYQ.js +10 -0
  105. package/dist/{status-JHVSZYC5.js → status-2UZKKNB7.js} +31 -12
  106. package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
  107. package/dist/sync-cmd-VPPYCNNM.js +28 -0
  108. package/dist/version-BWM6VLDI.js +6 -0
  109. package/dist/{watch-NRLXJUST.js → watch-5C4ZBBGO.js} +20 -7
  110. package/dist/{work-claim-cmd-HXB7K27H.js → work-claim-cmd-LSO2B2EX.js} +34 -19
  111. package/dist/work-cmd-UOQO2AD4.js +17 -0
  112. package/dist/work-comment-cmd-WF3EOLAE.js +106 -0
  113. package/dist/{work-launch-YNNCKIH3.js → work-launch-YI4CJDAP.js} +2 -1
  114. package/dist/work-open-pr-cmd-4SSQZMYG.js +156 -0
  115. package/dist/work-pr-cmd-6AN6RRQJ.js +16 -0
  116. package/package.json +13 -3
  117. package/skill-pack/maintained/amend/SKILL.md +104 -0
  118. package/skill-pack/maintained/emitting-evidence/SKILL.md +99 -0
  119. package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
  120. package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
  121. package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
  122. package/skill-pack/template/build/SKILL.md +193 -0
  123. package/skill-pack/template/build/references/agent-briefs.md +243 -0
  124. package/skill-pack/template/build/references/criteria-judge.md +91 -0
  125. package/skill-pack/template/build/references/evidence.md +42 -0
  126. package/skill-pack/template/build/references/release.md +74 -0
  127. package/skill-pack/template/build/references/review-briefs.md +275 -0
  128. package/skill-pack/template/build/references/review-loop.md +158 -0
  129. package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
  130. package/skill-pack/template/spec/SKILL.md +84 -0
  131. package/skill-pack/template/writing-specs/SKILL.md +134 -0
  132. package/dist/chunk-JJP7YLMN.js +0 -25
  133. package/dist/chunk-KKEU6FFR.js +0 -589
  134. package/dist/chunk-OZ7C3XUE.js +0 -34
  135. package/dist/chunk-WQ6VGRGZ.js +0 -150
  136. package/dist/emit-commit-credit-cmd-656BRAQ4.js +0 -121
  137. package/dist/hook-fast-I2XTHDE6.js +0 -8
  138. package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
  139. package/dist/init-PTBITAUO.js +0 -103
  140. package/dist/login-cmd-IRX6LZT7.js +0 -62
  141. package/dist/loop-BWE2BS6J.js +0 -646
  142. package/dist/sync-cmd-S7Q6DWU3.js +0 -15
  143. package/dist/work-cmd-ABP6XDBP.js +0 -16
@@ -1,14 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { staleBuildRefusal, staleBuildWarning, ARTIFACT_MAX_CAPTION_LENGTH, ArtifactCriterionIdSchema, ARTIFACT_MAX_FILENAME_LENGTH, readEffectiveArtifactSpoolState, localArtifactIds, isArtifactTombstoned, mediaTypeForArtifactPath, inferArtifactKind, resolveRepoIdentity, detectActiveSession, launchRefusalForSession, artifactBlobsDir, appendArtifactRecord, artifactsSpoolPath, findPendingArtifactRecord, detectCi, appendArtifactTombstone, ARTIFACT_MAX_REPLACES_TARGETS } from './chunk-BQB3U2VD.js';
3
+ import { staleBuildRefusal, staleBuildWarning } from './chunk-3YPKXE5L.js';
4
+ import { detectCi } from './chunk-HE2D4EY5.js';
5
+ import { launchRefusalForSession } from './chunk-BCJNHMZT.js';
6
+ import './chunk-TBT347UY.js';
7
+ import './chunk-F7CU5ABH.js';
8
+ import './chunk-BKO6JEZI.js';
9
+ import { detectActiveSession } from './chunk-BFESLKPP.js';
10
+ import { readEffectiveArtifactSpoolState, localArtifactIds, isArtifactTombstoned, artifactBlobsDir, appendArtifactRecord, artifactsSpoolPath, findPendingArtifactRecord, appendArtifactTombstone } from './chunk-6Y63SXFO.js';
11
+ import { ARTIFACT_MAX_CAPTION_LENGTH, ArtifactCriterionIdSchema, ARTIFACT_MAX_FILENAME_LENGTH, mediaTypeForArtifactPath, inferArtifactKind, resolveRepoIdentity, ARTIFACT_MAX_REPLACES_TARGETS } from './chunk-G7FSV7JX.js';
4
12
  import './chunk-YRYMNBGQ.js';
5
- import { parseTrackerNumber, trackerNumberError } from './chunk-RCQXYLMO.js';
13
+ import './chunk-DVDEBNQJ.js';
14
+ import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
15
+ import './chunk-5QRP3MVS.js';
6
16
  import './chunk-ZJSNLVCE.js';
7
- import './chunk-3VQ4XXQA.js';
8
- import './chunk-IWIUYLDR.js';
9
- import './chunk-YOOSBOKS.js';
10
- import './chunk-R5KBGVII.js';
11
- import { readCloudConfig } from './chunk-TFUIDMOB.js';
17
+ import { defaultFetch } from './chunk-BCLJSQCV.js';
18
+ import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
19
+ import './chunk-EABW6AJQ.js';
20
+ import './chunk-M5POMKKW.js';
21
+ import './chunk-ZM2IMMYN.js';
22
+ import './chunk-UFFXNXLQ.js';
23
+ import './chunk-XCCLFVXM.js';
24
+ import './chunk-BJ3KTMDH.js';
12
25
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
13
26
  import './chunk-Z5GEFMRW.js';
14
27
  import { createHash, randomUUID } from 'crypto';
@@ -249,16 +262,9 @@ async function runEmitArtifact(opts) {
249
262
  "nothing to attach this to \u2014 no recorded session, no PR, and no git repo. Run inside a recorded Claude Code session, from a git checkout, in CI with PR context, or pass --pr <number>."
250
263
  );
251
264
  }
252
- const fileConfig = readCloudConfig(home);
253
- const url = opts.url ?? env.OUTERLAYER_URL ?? fileConfig.url;
254
- const apiKey = opts.apiKey ?? env.OUTERLAYER_API_KEY ?? fileConfig.apiKey;
255
- const appId = opts.appId ?? env.OUTERLAYER_APP_ID ?? fileConfig.appId;
256
- if (!url || !apiKey || !appId) {
257
- const missing = [!url && "--url", !apiKey && "--api-key", !appId && "--app-id"].filter(Boolean).join(", ");
258
- throw new EmitArtifactError(
259
- `missing ${missing}. Run \`outerlayer login --url <your gateway>\` and paste the key at the prompt, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.`
260
- );
261
- }
265
+ const resolved = resolveGatewayCredentials({ ...opts, home, env });
266
+ if (!resolved.ok) throw new EmitArtifactError(resolved.message);
267
+ const { url, credential: apiKey, appId } = resolved;
262
268
  const clientArtifactId = randomUUID();
263
269
  const singleTarget = replacesTargets !== void 0 && replacesTargets.length === 1 ? replacesTargets[0] : void 0;
264
270
  const targetPendingLocally = singleTarget !== void 0 && findPendingArtifactRecord(singleTarget, home) !== void 0;
@@ -285,7 +291,7 @@ async function runEmitArtifact(opts) {
285
291
  },
286
292
  blob: { data: bytes.toString("base64") }
287
293
  };
288
- const fetchImpl = opts.fetchImpl ?? fetch;
294
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
289
295
  const endpoint = new URL("/v1/artifacts", url).toString();
290
296
  let response;
291
297
  try {
@@ -303,7 +309,7 @@ async function runEmitArtifact(opts) {
303
309
  }
304
310
  if (response.status === 401) {
305
311
  throw new EmitArtifactError(
306
- "not authorized \u2014 the API key is unknown, expired, or bound to a different app. Mint a fresh key: dashboard \u2192 Settings \u2192 API keys."
312
+ unauthorizedMessage(apiKey)
307
313
  );
308
314
  }
309
315
  if (!response.ok) {
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { EmitError, runEmit } from './chunk-H5NCNVDA.js';
4
+ import './chunk-WO2BXCTQ.js';
5
+ import './chunk-EMY4I27X.js';
6
+ import './chunk-RA3O54FC.js';
7
+ import './chunk-Z5GEFMRW.js';
8
+
9
+ createRequire(import.meta.url);
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { launchRefusalForSession } from './chunk-BCJNHMZT.js';
4
+ import { detectActiveSession } from './chunk-BFESLKPP.js';
5
+ import { resolveRepoIdentity, CriteriaListSchema } from './chunk-G7FSV7JX.js';
6
+ import { launchedItemForSession } from './chunk-DVDEBNQJ.js';
7
+ import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
8
+ import { defaultFetch } from './chunk-BCLJSQCV.js';
9
+ import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
10
+ import './chunk-EABW6AJQ.js';
11
+ import './chunk-M5POMKKW.js';
12
+ import './chunk-ZM2IMMYN.js';
13
+ import './chunk-BJ3KTMDH.js';
14
+ import { homeFromEnv } from './chunk-VNVZDWO3.js';
15
+ import './chunk-Z5GEFMRW.js';
16
+ import { readFileSync } from 'fs';
17
+
18
+ createRequire(import.meta.url);
19
+ var GREEN = "\x1B[32m";
20
+ var DIM = "\x1B[2m";
21
+ var RESET = "\x1B[0m";
22
+ var EmitCriteriaError = class extends Error {
23
+ };
24
+ var REPLACE_REFUSED_MESSAGE = "this item already has a recorded list, and a person must replace it. Run the command yourself, outside the session, with your own login. A repository's .outerlayer/policy.yaml can let sessions replace it with `criteria: { replace: anyone }`.";
25
+ async function readAllStdin() {
26
+ const chunks = [];
27
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
28
+ return Buffer.concat(chunks).toString("utf8");
29
+ }
30
+ function valueAt(raw, path) {
31
+ let current = raw;
32
+ for (const key of path) current = current?.[key];
33
+ return current;
34
+ }
35
+ function describeIssue(raw, issue) {
36
+ const where = issue.path.join(".");
37
+ if (issue.code === "custom") return `${where}: ${issue.message}`;
38
+ const value = valueAt(raw, issue.path);
39
+ const leaf = issue.path[issue.path.length - 1];
40
+ if ((leaf === "id" || leaf === "proof") && value !== void 0) {
41
+ return `${where}: ${JSON.stringify(value)} is not accepted (${issue.message})`;
42
+ }
43
+ return `${where}: ${issue.message}`;
44
+ }
45
+ function parseFile(raw) {
46
+ const parsed = CriteriaListSchema.safeParse(raw);
47
+ if (parsed.success) return parsed.data;
48
+ const issue = parsed.error.issues[0];
49
+ throw new EmitCriteriaError(
50
+ `invalid criteria list \u2014 ${issue ? describeIssue(raw, issue) : "malformed"}`
51
+ );
52
+ }
53
+ function resolveTarget(opts) {
54
+ let item;
55
+ if (opts.item !== void 0) {
56
+ const number = parseTrackerNumber(opts.item);
57
+ if (number === void 0) throw new EmitCriteriaError(trackerNumberError("--item", opts.item));
58
+ item = { number };
59
+ }
60
+ const { sessionId } = opts;
61
+ if (sessionId !== void 0) {
62
+ const refusal = launchRefusalForSession(sessionId, opts.home);
63
+ if (refusal !== void 0) throw new EmitCriteriaError(refusal);
64
+ }
65
+ if (item !== void 0) return sessionId !== void 0 ? { item, sessionId } : { item };
66
+ if (sessionId === void 0) {
67
+ throw new EmitCriteriaError(
68
+ "nothing to record this on \u2014 no work item. Pass --item <number>, or run inside a recorded session launched with OUTERLAYER_WORK \u2014 its own launch record names the item automatically."
69
+ );
70
+ }
71
+ const launched = launchedItemForSession(sessionId, opts.home);
72
+ if (launched === void 0) {
73
+ throw new EmitCriteriaError(
74
+ "nothing to record this on \u2014 no work item. Pass --item <number>; this recorded session's own launch record names no item either."
75
+ );
76
+ }
77
+ return { item: { number: launched }, sessionId };
78
+ }
79
+ async function errorCode(response) {
80
+ let text = "";
81
+ try {
82
+ text = await response.text();
83
+ } catch {
84
+ }
85
+ let code = null;
86
+ try {
87
+ const parsed = JSON.parse(text);
88
+ if (typeof parsed.error?.code === "string") code = parsed.error.code;
89
+ } catch {
90
+ }
91
+ return { code, detail: text.replace(/\s+/g, " ").trim().slice(0, 160) };
92
+ }
93
+ async function runEmitCriteria(opts) {
94
+ const cwd = opts.cwd ?? process.cwd();
95
+ const home = opts.home ?? homeFromEnv();
96
+ const env = opts.env ?? process.env;
97
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
98
+ let text;
99
+ if (opts.file === "-") {
100
+ text = await (opts.readStdin ?? readAllStdin)();
101
+ } else {
102
+ try {
103
+ text = readFileSync(opts.file, "utf8");
104
+ } catch (err) {
105
+ throw new EmitCriteriaError(`cannot read "${opts.file}": ${String(err)}`);
106
+ }
107
+ }
108
+ let raw;
109
+ try {
110
+ raw = JSON.parse(text);
111
+ } catch (err) {
112
+ throw new EmitCriteriaError(`"${opts.file}" is not valid JSON: ${String(err)}`);
113
+ }
114
+ const list = parseFile(raw);
115
+ const identity = resolveRepoIdentity(cwd);
116
+ const sessionId = detectActiveSession({
117
+ env,
118
+ home,
119
+ cwds: identity.repoRoot !== void 0 ? [identity.repoRoot, cwd] : [cwd],
120
+ nowMs: now().getTime()
121
+ });
122
+ const target = resolveTarget({ item: opts.item, sessionId, home });
123
+ const resolved = resolveGatewayCredentials({ url: opts.url, apiKey: opts.apiKey, appId: opts.appId, cwd, home, env });
124
+ if (!resolved.ok) throw new EmitCriteriaError(resolved.message);
125
+ const { url, credential: apiKey, appId } = resolved;
126
+ const request = {
127
+ ...list,
128
+ item: target.item,
129
+ ...target.sessionId !== void 0 ? { sessionId: target.sessionId } : {}
130
+ };
131
+ const endpoint = new URL("/v1/criteria", url).toString();
132
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
133
+ let response;
134
+ try {
135
+ response = await fetchImpl(endpoint, {
136
+ method: "POST",
137
+ headers: {
138
+ "content-type": "application/json",
139
+ authorization: `Bearer ${apiKey}`,
140
+ "x-outerlayer-app-id": appId
141
+ },
142
+ body: JSON.stringify(request)
143
+ });
144
+ } catch (err) {
145
+ throw new EmitCriteriaError(`network error reaching ${endpoint}: ${String(err)}`);
146
+ }
147
+ if (response.status === 401) throw new EmitCriteriaError(unauthorizedMessage(apiKey));
148
+ if (!response.ok) {
149
+ const { code, detail } = await errorCode(response);
150
+ if (response.status === 409 && code === "criteria_replace_refused") {
151
+ throw new EmitCriteriaError(`refused (409): ${REPLACE_REFUSED_MESSAGE}`);
152
+ }
153
+ throw new EmitCriteriaError(`emit failed (${response.status})${detail ? `: ${detail}` : ""}`);
154
+ }
155
+ let data = {};
156
+ try {
157
+ data = (await response.json()).data ?? {};
158
+ } catch {
159
+ }
160
+ const count = typeof data.count === "number" ? data.count : list.criteria.length;
161
+ const output = opts.json ? JSON.stringify(data) : `${GREEN}\u2713${RESET} recorded ${count} ${count === 1 ? "criterion" : "criteria"} ${DIM}\xB7${RESET} item #${target.item.number}`;
162
+ if (!opts.quiet) process.stdout.write(output + "\n");
163
+ return { data, output, exitCode: 0 };
164
+ }
165
+
166
+ export { EmitCriteriaError, runEmitCriteria };
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { FindingBatchSchema, resolveRepoIdentity, detectActiveSession, launchRefusalForSession } from './chunk-BQB3U2VD.js';
4
- import './chunk-YRYMNBGQ.js';
5
- import { launchedItemForSession, parseTrackerNumber, trackerNumberError } from './chunk-RCQXYLMO.js';
3
+ import { launchRefusalForSession } from './chunk-BCJNHMZT.js';
4
+ import { detectActiveSession } from './chunk-BFESLKPP.js';
5
+ import { FindingBatchSchema, resolveRepoIdentity } from './chunk-G7FSV7JX.js';
6
+ import { launchedItemForSession } from './chunk-DVDEBNQJ.js';
7
+ import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
6
8
  import { readLatestProvenanceForSession } from './chunk-ZJSNLVCE.js';
7
- import './chunk-3VQ4XXQA.js';
8
- import './chunk-IWIUYLDR.js';
9
- import './chunk-YOOSBOKS.js';
10
- import './chunk-R5KBGVII.js';
11
- import { readCloudConfig } from './chunk-TFUIDMOB.js';
9
+ import { defaultFetch } from './chunk-BCLJSQCV.js';
10
+ import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
11
+ import './chunk-EABW6AJQ.js';
12
+ import './chunk-M5POMKKW.js';
13
+ import './chunk-ZM2IMMYN.js';
14
+ import './chunk-BJ3KTMDH.js';
12
15
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
13
16
  import './chunk-Z5GEFMRW.js';
14
17
  import { readFileSync } from 'fs';
@@ -19,6 +22,14 @@ var DIM = "\x1B[2m";
19
22
  var RESET = "\x1B[0m";
20
23
  var EmitFindingError = class extends Error {
21
24
  };
25
+ var REMOVED_FINDING_FLAGS = ["subject", "kind", "severity", "verdict", "fixed", "source"];
26
+ function refuseRemovedFindingFlags(opts) {
27
+ const used = REMOVED_FINDING_FLAGS.find((flag) => opts[flag] !== void 0);
28
+ if (used === void 0) return;
29
+ throw new EmitFindingError(
30
+ `--${used} is no longer accepted: findings record problems in the factory, not review verdicts. Pass --category <context|flaky-test|tooling|environment|defect|other> instead.`
31
+ );
32
+ }
22
33
  function parseItemFlag(raw) {
23
34
  if (raw === void 0) return void 0;
24
35
  const number = parseTrackerNumber(raw);
@@ -48,23 +59,19 @@ function recordFromFlags(opts) {
48
59
  const ruleLine = parseIntFlag(opts.ruleLine, "--rule-line", { min: 1 });
49
60
  const turnIndex = parseIntFlag(opts.turn, "--turn", { min: 0 });
50
61
  const round = parseIntFlag(opts.round, "--round", { min: 0 });
62
+ const hasRule = opts.rulePath !== void 0 || opts.ruleQuote !== void 0 || opts.ruleRelation !== void 0 || ruleLine !== void 0;
51
63
  return {
52
64
  id: opts.id,
53
- subject: opts.subject,
65
+ category: opts.category,
54
66
  title: opts.title,
55
67
  file: opts.file,
56
68
  line: line ?? null,
57
- severity: opts.severity ?? null,
58
- kind: opts.kind,
59
- verdict: opts.verdict,
60
- fixed: opts.fixed ?? false,
61
- rule: opts.rulePath !== void 0 ? {
69
+ rule: hasRule ? {
62
70
  path: opts.rulePath,
63
71
  line: ruleLine ?? null,
64
- quote: opts.ruleQuote ?? "",
72
+ ...opts.ruleQuote !== void 0 ? { quote: opts.ruleQuote } : {},
65
73
  relation: opts.ruleRelation
66
74
  } : null,
67
- source: opts.source,
68
75
  where: {
69
76
  label: opts.where,
70
77
  ...opts.traceId !== void 0 ? { traceId: opts.traceId } : {},
@@ -110,22 +117,15 @@ function resolveUploadTarget(opts) {
110
117
  return { item: { number: launchedNumber }, sessionId };
111
118
  }
112
119
  async function upload(batch, target, opts) {
113
- const fileConfig = readCloudConfig(opts.home);
114
- const url = opts.url ?? opts.env.OUTERLAYER_URL ?? fileConfig.url;
115
- const apiKey = opts.apiKey ?? opts.env.OUTERLAYER_API_KEY ?? fileConfig.apiKey;
116
- const appId = opts.appId ?? opts.env.OUTERLAYER_APP_ID ?? fileConfig.appId;
117
- if (!url || !apiKey || !appId) {
118
- const missing = [!url && "--url", !apiKey && "--api-key", !appId && "--app-id"].filter(Boolean).join(", ");
119
- throw new EmitFindingError(
120
- `missing ${missing}. Run \`outerlayer login --url <your gateway>\` and paste the key at the prompt, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.`
121
- );
122
- }
120
+ const resolved = resolveGatewayCredentials(opts);
121
+ if (!resolved.ok) throw new EmitFindingError(resolved.message);
122
+ const { url, credential: apiKey, appId } = resolved;
123
123
  const request = {
124
124
  ...batch,
125
125
  item: target.item,
126
126
  ...target.sessionId !== void 0 ? { sessionId: target.sessionId } : {}
127
127
  };
128
- const fetchImpl = opts.fetchImpl ?? fetch;
128
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
129
129
  const endpoint = new URL("/v1/findings", url).toString();
130
130
  let response;
131
131
  try {
@@ -143,7 +143,7 @@ async function upload(batch, target, opts) {
143
143
  }
144
144
  if (response.status === 401) {
145
145
  throw new EmitFindingError(
146
- "not authorized \u2014 the API key is unknown, expired, or bound to a different app. Mint a fresh key: dashboard \u2192 Settings \u2192 API keys."
146
+ unauthorizedMessage(apiKey)
147
147
  );
148
148
  }
149
149
  if (!response.ok) {
@@ -172,7 +172,7 @@ async function runEmitFinding(opts) {
172
172
  const sessionId = detectUploadSessionId({ cwd, home, env, now });
173
173
  const contextSourceSha = opts.contextSourceSha ?? (sessionId !== void 0 ? readLatestProvenanceForSession(sessionId, home)?.sha ?? void 0 : void 0);
174
174
  const rawBatch = {
175
- schemaVersion: 1,
175
+ schemaVersion: 2,
176
176
  ...opts.headSha !== void 0 ? { headSha: opts.headSha } : {},
177
177
  ...contextSourceSha !== void 0 ? { contextSourceSha } : {},
178
178
  findings: [recordFromFlags(opts)]
@@ -214,6 +214,11 @@ async function runEmitFindings(opts) {
214
214
  } catch (err) {
215
215
  throw new EmitFindingError(`"${opts.file}" is not valid JSON: ${String(err)}`);
216
216
  }
217
+ if (typeof raw === "object" && raw !== null && raw.schemaVersion === 1) {
218
+ throw new EmitFindingError(
219
+ `"${opts.file}" uses schemaVersion 1, which is no longer accepted. Use schemaVersion 2: each finding takes a category (context, flaky-test, tooling, environment, defect or other) in place of subject, kind, severity, verdict, fixed and source.`
220
+ );
221
+ }
217
222
  const parsed = FindingBatchSchema.safeParse(raw);
218
223
  if (!parsed.success) {
219
224
  throw new EmitFindingError(`invalid finding batch \u2014 ${firstIssueMessage(parsed)}`);
@@ -232,4 +237,4 @@ async function runEmitFindings(opts) {
232
237
  });
233
238
  }
234
239
 
235
- export { EmitFindingError, runEmitFinding, runEmitFindings };
240
+ export { EmitFindingError, refuseRemovedFindingFlags, runEmitFinding, runEmitFindings };
@@ -1,14 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { EmittedNameSchema, ARTIFACT_REVIEW_CHECK_NAME, EMITTED_RESULT_MAX_LINK_LENGTH, EMITTED_RESULTS, EMITTED_RESULT_MAX_BODY_LENGTH, resolveRepoIdentity, detectActiveSession, launchRefusalForSession, detectCi } from './chunk-BQB3U2VD.js';
3
+ import './chunk-3YPKXE5L.js';
4
+ import { detectCi } from './chunk-HE2D4EY5.js';
5
+ import { launchRefusalForSession } from './chunk-BCJNHMZT.js';
6
+ import './chunk-TBT347UY.js';
7
+ import './chunk-F7CU5ABH.js';
8
+ import './chunk-BKO6JEZI.js';
9
+ import { detectActiveSession } from './chunk-BFESLKPP.js';
10
+ import './chunk-6Y63SXFO.js';
11
+ import { EmittedNameSchema, EMITTED_RESULT_MAX_LINK_LENGTH, EMITTED_RESULTS, EMITTED_RESULT_MAX_BODY_LENGTH, resolveRepoIdentity } from './chunk-G7FSV7JX.js';
4
12
  import './chunk-YRYMNBGQ.js';
5
- import { parseTrackerNumber, trackerNumberError } from './chunk-RCQXYLMO.js';
13
+ import './chunk-DVDEBNQJ.js';
14
+ import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
15
+ import './chunk-5QRP3MVS.js';
6
16
  import './chunk-ZJSNLVCE.js';
7
- import './chunk-3VQ4XXQA.js';
8
- import './chunk-IWIUYLDR.js';
9
- import './chunk-YOOSBOKS.js';
10
- import './chunk-R5KBGVII.js';
11
- import { readCloudConfig } from './chunk-TFUIDMOB.js';
17
+ import { defaultFetch } from './chunk-BCLJSQCV.js';
18
+ import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
19
+ import './chunk-EABW6AJQ.js';
20
+ import './chunk-M5POMKKW.js';
21
+ import './chunk-ZM2IMMYN.js';
22
+ import './chunk-UFFXNXLQ.js';
23
+ import './chunk-XCCLFVXM.js';
24
+ import './chunk-BJ3KTMDH.js';
12
25
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
13
26
  import './chunk-Z5GEFMRW.js';
14
27
  import { randomUUID } from 'crypto';
@@ -51,23 +64,7 @@ async function runEmitResult(opts) {
51
64
  `invalid name "${opts.name}" \u2014 an emit name is 1-64 characters: a lowercase letter followed by lowercase letters, digits, ".", "_" or "-" (e.g. smoke.pass)`
52
65
  );
53
66
  }
54
- const isArtifactReview = opts.name === ARTIFACT_REVIEW_CHECK_NAME;
55
- const artifactId = opts.artifact === "" ? void 0 : opts.artifact;
56
- if (isArtifactReview) {
57
- if (artifactId === void 0) {
58
- throw new EmitResultError(
59
- `--artifact <id> is required for ${ARTIFACT_REVIEW_CHECK_NAME} \u2014 it names which artifact this verdict is on`
60
- );
61
- }
62
- } else if (artifactId !== void 0) {
63
- throw new EmitResultError(`--artifact is only accepted with ${ARTIFACT_REVIEW_CHECK_NAME}`);
64
- }
65
67
  const link = opts.link === "" ? void 0 : opts.link;
66
- if (isArtifactReview && link !== void 0) {
67
- throw new EmitResultError(
68
- `--link is refused for ${ARTIFACT_REVIEW_CHECK_NAME} \u2014 an artifact verdict is a judgment on evidence already on file, never a run to point at; pass --body / --body-file instead`
69
- );
70
- }
71
68
  if (link !== void 0) {
72
69
  if (!link.startsWith("http://") && !link.startsWith("https://")) {
73
70
  throw new EmitResultError(`invalid --link "${link}" \u2014 expected an http:// or https:// URL`);
@@ -98,8 +95,6 @@ async function runEmitResult(opts) {
98
95
  body = opts.body;
99
96
  } else if (opts.bodyFile !== void 0) {
100
97
  body = await readBodyFile(opts.bodyFile, opts.readStdin);
101
- } else if (isArtifactReview && result === "fail") {
102
- body = await (opts.readStdin ?? readAllStdin)();
103
98
  }
104
99
  if (body !== void 0) {
105
100
  body = body.trim();
@@ -114,13 +109,7 @@ async function runEmitResult(opts) {
114
109
  );
115
110
  }
116
111
  }
117
- if (isArtifactReview) {
118
- if (result === "fail" && body === void 0) {
119
- throw new EmitResultError(
120
- `a failing artifact verdict needs a sentence \u2014 pass --body or --body-file with one sentence saying what is wrong`
121
- );
122
- }
123
- } else if (result === "fail" && link === void 0 && body === void 0) {
112
+ if (result === "fail" && link === void 0 && body === void 0) {
124
113
  throw new EmitResultError(
125
114
  `a failing check needs something to act on \u2014 pass --link with the run URL, or --body / --body-file with one sentence saying what is wrong`
126
115
  );
@@ -133,37 +122,23 @@ async function runEmitResult(opts) {
133
122
  cwds: identity.repoRoot !== void 0 ? [identity.repoRoot, cwd] : [cwd],
134
123
  nowMs: now().getTime()
135
124
  });
136
- if (isArtifactReview && sessionId !== void 0) {
125
+ if (item === void 0 && sessionId === void 0) {
137
126
  throw new EmitResultError(
138
- "an artifact verdict is a person's act \u2014 record it with a person's API key, outside a recorded session."
127
+ "nothing to attach this to \u2014 no work item. Pass --item <number>; a recorded session alone does not name one."
139
128
  );
140
129
  }
141
- if (!isArtifactReview) {
142
- if (item === void 0 && sessionId === void 0) {
143
- throw new EmitResultError(
144
- "nothing to attach this to \u2014 no work item. Pass --item <number>; a recorded session alone does not name one, and a session with no --item is always refused as self-judgment."
145
- );
146
- }
147
- if (sessionId !== void 0) {
148
- const refusal = launchRefusalForSession(sessionId, home);
149
- if (refusal !== void 0) throw new EmitResultError(refusal);
150
- }
151
- if (item === void 0 && sessionId !== void 0) {
152
- throw new EmitResultError(
153
- "a session records a check only on a work item it was not launched for, named with --item \u2014 the item this session was launched for needs a machine key (an API key run outside the recorded session) or CI."
154
- );
155
- }
130
+ if (sessionId !== void 0) {
131
+ const refusal = launchRefusalForSession(sessionId, home);
132
+ if (refusal !== void 0) throw new EmitResultError(refusal);
156
133
  }
157
- const fileConfig = readCloudConfig(home);
158
- const url = opts.url ?? env.OUTERLAYER_URL ?? fileConfig.url;
159
- const apiKey = opts.apiKey ?? env.OUTERLAYER_API_KEY ?? fileConfig.apiKey;
160
- const appId = opts.appId ?? env.OUTERLAYER_APP_ID ?? fileConfig.appId;
161
- if (!url || !apiKey || !appId) {
162
- const missing = [!url && "--url", !apiKey && "--api-key", !appId && "--app-id"].filter(Boolean).join(", ");
134
+ if (item === void 0) {
163
135
  throw new EmitResultError(
164
- `missing ${missing}. Run \`outerlayer login --url <your gateway>\` and paste the key at the prompt, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.`
136
+ "nothing to attach this to \u2014 no work item. Pass --item <number>; a recorded session alone does not name one."
165
137
  );
166
138
  }
139
+ const resolved = resolveGatewayCredentials({ ...opts, home, env });
140
+ if (!resolved.ok) throw new EmitResultError(resolved.message);
141
+ const { url, credential: apiKey, appId } = resolved;
167
142
  const ci = detectCi(env);
168
143
  const request = {
169
144
  schemaVersion: 1,
@@ -176,11 +151,10 @@ async function runEmitResult(opts) {
176
151
  emittedAt: now().toISOString(),
177
152
  ...ci ? { ci: true } : {},
178
153
  ...sessionId !== void 0 ? { sessionId } : {},
179
- ...item !== void 0 ? { item } : {},
180
- ...isArtifactReview ? { artifactId } : {}
154
+ ...item !== void 0 ? { item } : {}
181
155
  }
182
156
  };
183
- const fetchImpl = opts.fetchImpl ?? fetch;
157
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
184
158
  const endpoint = new URL("/v1/emitted-results", url).toString();
185
159
  let response;
186
160
  try {
@@ -198,7 +172,7 @@ async function runEmitResult(opts) {
198
172
  }
199
173
  if (response.status === 401) {
200
174
  throw new EmitResultError(
201
- "not authorized \u2014 the API key is unknown, expired, or bound to a different app. Mint a fresh key: dashboard \u2192 Settings \u2192 API keys."
175
+ unauthorizedMessage(apiKey)
202
176
  );
203
177
  }
204
178
  if (!response.ok) {
@@ -214,7 +188,7 @@ async function runEmitResult(opts) {
214
188
  data = (await response.json()).data ?? {};
215
189
  } catch {
216
190
  }
217
- const anchorLabel = isArtifactReview ? `artifact ${artifactId}` : `item #${item.number}`;
191
+ const anchorLabel = `item #${item.number}`;
218
192
  const recordedId = typeof data.id === "string" ? data.id : "";
219
193
  const idSuffix = recordedId ? ` ${DIM}\xB7${RESET} ${recordedId}` : "";
220
194
  const output = opts.json ? JSON.stringify(data) : `${GREEN}\u2713${RESET} emitted ${opts.name} (${result}) ${DIM}\xB7${RESET} ${anchorLabel}${idSuffix}`;
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { CAPTURE_LIMIT_BYTES, ExecConnectionError, HANDSHAKE_TIMEOUT_MS, execCapture, runExec, runVmExec, vmExecArgs, waitForAgent } from './chunk-W4SQCOZU.js';
4
+ import './chunk-B2G7JHHB.js';
5
+ import './chunk-AUFG23AA.js';
6
+ import './chunk-Z5GEFMRW.js';
7
+
8
+ createRequire(import.meta.url);
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { DOCKERD_LOG, HELPER_PATH, realGuestHost, relayPortsFrom, runGuestInit } from './chunk-CBPPJSAR.js';
4
+ import './chunk-B2G7JHHB.js';
5
+ import './chunk-AUFG23AA.js';
6
+ import './chunk-Z5GEFMRW.js';
7
+
8
+ createRequire(import.meta.url);
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { AUTO_SYNC_DEBOUNCE_MS, WORK_LEASE_RENEW_DEBOUNCE_MS, WORK_LINK_RETRIES, contextPossiblyGoverned, handleWorkLaunch, handleWorkSessionEnd, hookFiredMarkerPath, maybeAutoSync, maybeRenewWorkLease, runHookFast, spoolDir } from './chunk-UFFXNXLQ.js';
4
+ import './chunk-XCCLFVXM.js';
5
+ import './chunk-BJ3KTMDH.js';
6
+ import './chunk-VNVZDWO3.js';
7
+ import './chunk-Z5GEFMRW.js';
8
+
9
+ createRequire(import.meta.url);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- export { hookExecSpoolPath, hookWrapStdio, installAbortForwarding, makeTailCollector, parseHookWrapArgs, runHookWrapFast, spoolDir } from './chunk-YOOSBOKS.js';
4
- import './chunk-R5KBGVII.js';
3
+ export { hookExecSpoolPath, hookWrapStdio, installAbortForwarding, makeTailCollector, parseHookWrapArgs, runHookWrapFast, spoolDir } from './chunk-F7CU5ABH.js';
4
+ import './chunk-5QRP3MVS.js';
5
5
  import './chunk-VNVZDWO3.js';
6
6
  import './chunk-Z5GEFMRW.js';
7
7
 
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ export { ensureHostKey, loadHostKey, signerFor } from './chunk-M5POMKKW.js';
4
+ import './chunk-ZM2IMMYN.js';
5
+ import './chunk-Z5GEFMRW.js';
6
+
7
+ createRequire(import.meta.url);
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { loadMaintainedFiles, writeMaintainedFiles } from './chunk-WO2BXCTQ.js';
4
+ import './chunk-EMY4I27X.js';
5
+ import './chunk-RA3O54FC.js';
6
+ import './chunk-Z5GEFMRW.js';
7
+ import { statSync, existsSync, readFileSync, writeFileSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ createRequire(import.meta.url);
11
+ var GREEN = "\x1B[32m";
12
+ var DIM = "\x1B[2m";
13
+ var YELLOW = "\x1B[33m";
14
+ var RESET = "\x1B[0m";
15
+ var ImportCaptureError = class extends Error {
16
+ };
17
+ var SNIPPET_PATH = ".outerlayer/skills/emitting-evidence/references/agents-snippet.md";
18
+ var AGENTS_MD_PATH = ".outerlayer/AGENTS.md";
19
+ var AGENTS_MD_MARKER = "<!-- outerlayer:capture-pack -->";
20
+ function runImportCapture(opts = {}) {
21
+ const cwd = opts.cwd ?? process.cwd();
22
+ let cwdStat;
23
+ try {
24
+ cwdStat = statSync(cwd);
25
+ } catch {
26
+ throw new ImportCaptureError(`no such directory: ${cwd}`);
27
+ }
28
+ if (!cwdStat.isDirectory()) throw new ImportCaptureError(`not a directory: ${cwd}`);
29
+ const bundle = loadMaintainedFiles();
30
+ const { created: files, rewritten } = writeMaintainedFiles(cwd, bundle);
31
+ const snippet = bundle.find((f) => f.path === SNIPPET_PATH)?.content ?? "";
32
+ let appendedAgentsMd = false;
33
+ const agentsMdAbs = join(cwd, AGENTS_MD_PATH);
34
+ if (existsSync(agentsMdAbs)) {
35
+ const existing = readFileSync(agentsMdAbs, "utf8");
36
+ if (!existing.includes(AGENTS_MD_MARKER)) {
37
+ writeFileSync(agentsMdAbs, existing + "\n" + snippet);
38
+ appendedAgentsMd = true;
39
+ }
40
+ }
41
+ let output;
42
+ if (opts.json) {
43
+ output = JSON.stringify({ files, rewritten, appendedAgentsMd });
44
+ } else {
45
+ const lines = [
46
+ ...files.map((f) => `${GREEN}\u2713${RESET} wrote ${f}`),
47
+ ...rewritten.map((f) => `${YELLOW}!${RESET} rewrote ${f} to match this CLI's bundle (local edits were overwritten)`)
48
+ ];
49
+ if (appendedAgentsMd) lines.push(`${GREEN}\u2713${RESET} appended the evidence snippet to ${AGENTS_MD_PATH}`);
50
+ if (files.length === 0 && rewritten.length === 0 && !appendedAgentsMd) {
51
+ lines.push(`${DIM}capture pack already installed \u2014 nothing to write${RESET}`);
52
+ }
53
+ lines.push(`Run ${YELLOW}\`outerlayer emit\`${RESET} to compile the pack into your agent's native files.`);
54
+ output = lines.join("\n");
55
+ }
56
+ if (!opts.quiet) {
57
+ process.stdout.write(output + "\n");
58
+ if (!opts.json && !appendedAgentsMd && !existsSync(agentsMdAbs)) {
59
+ process.stdout.write(
60
+ `${DIM} no ${AGENTS_MD_PATH} to append to \u2014 paste ${SNIPPET_PATH} into your instructions file if your harness reads no skills${RESET}
61
+ `
62
+ );
63
+ }
64
+ }
65
+ return { files, rewritten, appendedAgentsMd, output };
66
+ }
67
+
68
+ export { ImportCaptureError, runImportCapture };