@outerlayer/cli 0.33.0 → 0.34.0-next.20261023233807

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 (113) hide show
  1. package/README.md +4 -1
  2. package/dist/{agent-setup-YHEW3WXB.js → agent-setup-FBAT5LGX.js} +10 -10
  3. package/dist/{artifact-link-cmd-7FYKWA7J.js → artifact-link-cmd-LWPY3V4N.js} +2 -2
  4. package/dist/{build-SEAQVLD5.js → build-LSU25G3M.js} +2 -2
  5. package/dist/{build-disk-ECTLWTPC.js → build-disk-ZDGSJLPA.js} +5 -5
  6. package/dist/build-info.json +1 -1
  7. package/dist/{builder-LUPHKUFM.js → builder-QKH2HBAO.js} +4 -4
  8. package/dist/{check-XPMN4GHD.js → check-4MYU7S73.js} +32 -32
  9. package/dist/{chunk-QKNFU5GA.js → chunk-2GWMYY4M.js} +5 -5
  10. package/dist/{chunk-HLZMAJJD.js → chunk-2HCXEIJD.js} +2 -2
  11. package/dist/{chunk-VWQCYOBS.js → chunk-2UF4H4PG.js} +2 -2
  12. package/dist/{chunk-Z57X4KNC.js → chunk-3CUGPUCP.js} +2 -2
  13. package/dist/{chunk-EXZACZ2P.js → chunk-3FMARI3L.js} +1 -1
  14. package/dist/{chunk-6SPD266O.js → chunk-4H2DWORO.js} +3 -3
  15. package/dist/{chunk-EHOFFTVX.js → chunk-4V5V2HAQ.js} +2 -2
  16. package/dist/{chunk-VMIOUVJF.js → chunk-5CHRWX3K.js} +5 -5
  17. package/dist/{chunk-L6MDOEHN.js → chunk-74Q24R3B.js} +7 -7
  18. package/dist/{chunk-BLHUFOOB.js → chunk-7JETLQED.js} +12 -12
  19. package/dist/{chunk-AKZRGFGT.js → chunk-B44BFR2Q.js} +3 -3
  20. package/dist/{chunk-V673DFOL.js → chunk-B5MSDF5B.js} +4 -4
  21. package/dist/{chunk-X4JEBMYN.js → chunk-BBGVXGSO.js} +1 -1
  22. package/dist/{chunk-NIY2UBT6.js → chunk-BO42KT3D.js} +1 -1
  23. package/dist/{chunk-6CXNWKWD.js → chunk-CQRPR4WZ.js} +143 -22
  24. package/dist/{chunk-F2D5BSXS.js → chunk-EOTMDIYC.js} +1 -1
  25. package/dist/{chunk-NE63JADS.js → chunk-ES4ULBAL.js} +2 -2
  26. package/dist/{emit-demo-cmd-IQRS7BP2.js → chunk-GODAKHDD.js} +6 -35
  27. package/dist/{chunk-RZOO7X4G.js → chunk-HG6INMG3.js} +3 -3
  28. package/dist/{chunk-JEVKOZBB.js → chunk-HMCBX3PL.js} +1 -1
  29. package/dist/{chunk-7UPMYRPS.js → chunk-HUUYTDFV.js} +4 -4
  30. package/dist/{chunk-VSLJ6PPG.js → chunk-J32AF6D2.js} +5 -5
  31. package/dist/{chunk-D3EDZPJR.js → chunk-KLGYSJ3A.js} +5 -5
  32. package/dist/{chunk-4YLL2E7Y.js → chunk-KSZXJE3R.js} +12 -12
  33. package/dist/{chunk-YY3OQJ4X.js → chunk-LFBMXXE6.js} +14 -14
  34. package/dist/{chunk-MPC2UAW2.js → chunk-MECW77XP.js} +1 -1
  35. package/dist/{work-proofs-cmd-XBESDEPM.js → chunk-NYHQF6BT.js} +30 -30
  36. package/dist/{chunk-DKFUXYGD.js → chunk-OLCIPTST.js} +4 -4
  37. package/dist/{chunk-S5CNYXUA.js → chunk-OVRNGOD2.js} +4 -4
  38. package/dist/{chunk-UK4RGH46.js → chunk-SHIGGHZO.js} +2 -2
  39. package/dist/{chunk-EJL6CWSP.js → chunk-SVLFZLTE.js} +6 -6
  40. package/dist/{chunk-5M2J7B5N.js → chunk-TCGVYHII.js} +2 -2
  41. package/dist/{chunk-LYGI3T66.js → chunk-TIRHZC2M.js} +2 -2
  42. package/dist/{chunk-OGMXVPCS.js → chunk-TYBK2UCW.js} +2 -2
  43. package/dist/{chunk-FT7MUQGO.js → chunk-UF2FU3CM.js} +2 -2
  44. package/dist/{work-comment-cmd-QEZHRXC5.js → chunk-VY2FE5VT.js} +4 -24
  45. package/dist/{chunk-GO2DUPQS.js → chunk-WEUCD3J3.js} +3 -3
  46. package/dist/{chunk-KUE4V2SK.js → chunk-XFAJQZN2.js} +2 -2
  47. package/dist/{chunk-2LFWCIIE.js → chunk-XXTXM47K.js} +15 -15
  48. package/dist/{chunk-7V5GUEDU.js → chunk-Y5YCX6AL.js} +1 -1
  49. package/dist/chunk-YNJUN6BB.js +148 -0
  50. package/dist/{chunk-ZF24JN7R.js → chunk-YPCDDQFU.js} +1 -1
  51. package/dist/{chunk-7IFYI7XF.js → chunk-YQ5BEACL.js} +3 -3
  52. package/dist/{chunk-44V5SD4G.js → chunk-ZGB4ZJ5E.js} +1 -1
  53. package/dist/{chunk-YXHKCHWG.js → chunk-ZVLSYWDF.js} +1 -1
  54. package/dist/{claude-launcher-YRPW4SWA.js → claude-launcher-YXRS4EZE.js} +5 -5
  55. package/dist/{cli-65ZQJ622.js → cli-QQBC67PY.js} +132 -108
  56. package/dist/{command-QENZQGZL.js → command-27SJOFEM.js} +21 -21
  57. package/dist/{config-MVA7YTFW.js → config-7M4PJUZ5.js} +6 -6
  58. package/dist/{connect-cmd-KWXV2CGC.js → connect-cmd-XPSJT6JW.js} +8 -8
  59. package/dist/{container-start-RVH5743X.js → container-start-FDUQKTOF.js} +27 -27
  60. package/dist/{context-cleanup-A2FGVVPH.js → context-cleanup-YGWYIFFV.js} +3 -3
  61. package/dist/{context-show-cmd-6F4UF4ND.js → context-show-cmd-4HLZ6DEB.js} +9 -9
  62. package/dist/{control-plane-launch-7SOH6IMA.js → control-plane-launch-AXBWT5CC.js} +6 -6
  63. package/dist/{dist-IG2DKUJJ.js → dist-ELNMCU72.js} +2 -2
  64. package/dist/{docker-YKYQJHL2.js → docker-7XRXEGMX.js} +4 -4
  65. package/dist/{doctor-MFLM7DMY.js → doctor-Y3F7KETW.js} +22 -22
  66. package/dist/{emit-artifact-cmd-6QTTWBVN.js → emit-artifact-cmd-SX6A3N46.js} +13 -13
  67. package/dist/{emit-cmd-3FG7FEGO.js → emit-cmd-A4CB7BEC.js} +6 -6
  68. package/dist/{emit-criteria-cmd-22VVCL2W.js → emit-criteria-cmd-4LXKENTI.js} +10 -10
  69. package/dist/emit-demo-cmd-ECMS73O6.js +48 -0
  70. package/dist/{emit-finding-cmd-TXDPJFVT.js → emit-finding-cmd-K73WX2UP.js} +10 -10
  71. package/dist/{emit-result-cmd-QHJDUC7J.js → emit-result-cmd-SUQASZMD.js} +12 -12
  72. package/dist/{emit-step-cmd-TQCOLXAA.js → emit-step-cmd-5NQ6W4F3.js} +10 -10
  73. package/dist/{findings-cmd-EURKADAY.js → findings-cmd-2BOST4CX.js} +10 -10
  74. package/dist/{fleet-readiness-GXDSFTLP.js → fleet-readiness-VAKMSSWI.js} +8 -8
  75. package/dist/{governing-source-INEXO2CK.js → governing-source-NSQ5TIMO.js} +13 -13
  76. package/dist/{hook-fast-3IYSWDL4.js → hook-fast-WENTLDMA.js} +1 -1
  77. package/dist/index.js +4 -4
  78. package/dist/{init-WIHSXL74.js → init-E2N3A4MB.js} +21 -21
  79. package/dist/{init-VWJIINBC.js → init-SFZ5VUZ6.js} +21 -21
  80. package/dist/{init-cmd-3VWJUWXA.js → init-cmd-BNOWYMNA.js} +18 -18
  81. package/dist/{init-update-LFKSC6Y4.js → init-update-6QK5URLQ.js} +17 -17
  82. package/dist/{install-47Z7VNWP.js → install-E6DEU5NV.js} +7 -7
  83. package/dist/{layout-44S4OYLK.js → layout-NAPG4SX2.js} +4 -4
  84. package/dist/{login-browser-BJZYKWHF.js → login-browser-DQ6YRXUB.js} +2 -2
  85. package/dist/{loop-5QV4VZOF.js → loop-6RHTIIIE.js} +32 -32
  86. package/dist/{machine-XTSUAX3K.js → machine-OYI4ZWQS.js} +21 -21
  87. package/dist/{machine-skills-GOKW3U5N.js → machine-skills-2TT3RXMJ.js} +4 -4
  88. package/dist/{runner-env-ILP6BAC2.js → runner-env-EKBC5EYE.js} +8 -8
  89. package/dist/{runner-identity-MF3AYBR6.js → runner-identity-KN4CW5VQ.js} +4 -4
  90. package/dist/{runner-key-N4IIFMUX.js → runner-key-W6KG4QD7.js} +21 -21
  91. package/dist/{session-sender-2DYAJIMM.js → session-sender-Q7I6NUJU.js} +13 -13
  92. package/dist/{spec-cmd-7W4ERDL7.js → spec-cmd-3RWLOGZW.js} +11 -11
  93. package/dist/{starter-pack-FWTXE6XB.js → starter-pack-5D4BANFU.js} +5 -5
  94. package/dist/{status-VBUCLDN5.js → status-G26SS4VW.js} +26 -26
  95. package/dist/{sync-cmd-NGX2PGIW.js → sync-cmd-2W5F23IM.js} +12 -12
  96. package/dist/{watch-ZIWZVU3Z.js → watch-QW4LMB23.js} +12 -12
  97. package/dist/{work-claim-cmd-G3DC5LJK.js → work-claim-cmd-D2VABY7U.js} +16 -16
  98. package/dist/{work-cmd-J2LJ3V4A.js → work-cmd-FVDCWSPE.js} +10 -10
  99. package/dist/work-comment-cmd-RW7FZBBH.js +33 -0
  100. package/dist/{work-demo-cmd-A7XE4NKA.js → work-demo-cmd-M5NL3IPF.js} +10 -10
  101. package/dist/{work-logs-cmd-UA5PAXRP.js → work-logs-cmd-KUG5JARJ.js} +10 -10
  102. package/dist/{work-open-pr-cmd-ZYKNAJJM.js → work-open-pr-cmd-HSVGGLFC.js} +11 -11
  103. package/dist/{work-pr-cmd-DH43AH3V.js → work-pr-cmd-KBHTEAIT.js} +10 -10
  104. package/dist/work-proofs-cmd-A6QNVO6B.js +34 -0
  105. package/dist/work-proofs-init-QB2UL3BC.js +35 -0
  106. package/dist/work-proofs-record-APK4VWRB.js +402 -0
  107. package/dist/{work-stop-cmd-ZDIW3MVX.js → work-stop-cmd-HSGFTRIX.js} +10 -10
  108. package/package.json +1 -1
  109. package/skill-pack/maintained/outerlayer-amend/SKILL.md +5 -5
  110. package/skill-pack/maintained/outerlayer-build/SKILL.md +4 -3
  111. package/skill-pack/maintained/outerlayer-build/references/agent-briefs.md +30 -17
  112. package/skill-pack/maintained/outerlayer-build/references/evidence.md +17 -15
  113. package/skill-pack/maintained/outerlayer-amend/scripts/rerecord-demo.mjs +0 -187
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { runWorkComment } from './chunk-VY2FE5VT.js';
4
+ import { recipeKeyFor, ownPullRequest, otherPullRequestHeads, PROOFS_DIR, SETUP_FILE, proofsCommentId, planEntries, checkEmitPath, insideRoot, kindProblem, proofsCommentBody } from './chunk-YNJUN6BB.js';
5
+ import { resolveProofsItem, classifyProofsAt, runWorkProofs } from './chunk-NYHQF6BT.js';
6
+ import { runEmitDemo } from './chunk-GODAKHDD.js';
7
+ import { readNewestDemo } from './chunk-ER6KQCVJ.js';
8
+ import { runEmitArtifact } from './chunk-2GWMYY4M.js';
9
+ import { runSync } from './chunk-KLGYSJ3A.js';
10
+ import './chunk-HUUYTDFV.js';
11
+ import './chunk-ESVW4MJY.js';
12
+ import './chunk-TBT347UY.js';
13
+ import './chunk-2PGI6RDE.js';
14
+ import './chunk-ES4ULBAL.js';
15
+ import { WorkCommandError, resolveCredentials, getJson } from './chunk-J32AF6D2.js';
16
+ import './chunk-IP7BIBXJ.js';
17
+ import './chunk-2HCXEIJD.js';
18
+ import './chunk-OVRNGOD2.js';
19
+ import './chunk-MECW77XP.js';
20
+ import { resolveRepoIdentity } from './chunk-BO42KT3D.js';
21
+ import './chunk-TIRHZC2M.js';
22
+ import './chunk-Y5YCX6AL.js';
23
+ import './chunk-G2JWZDFA.js';
24
+ import './chunk-ANPNT6PM.js';
25
+ import { defaultFetch } from './chunk-6RKD7YZ6.js';
26
+ import './chunk-PJDQPF5N.js';
27
+ import './chunk-SOI6K5DY.js';
28
+ import './chunk-PA6CK6WQ.js';
29
+ import './chunk-BALIE3BA.js';
30
+ import './chunk-SHIGGHZO.js';
31
+ import './chunk-ZJSNLVCE.js';
32
+ import './chunk-2O5KA67H.js';
33
+ import './chunk-BKO6JEZI.js';
34
+ import './chunk-HCVLP7XA.js';
35
+ import './chunk-BJ3KTMDH.js';
36
+ import './chunk-ZGB4ZJ5E.js';
37
+ import './chunk-5BHIBJDA.js';
38
+ import './chunk-CDDED64E.js';
39
+ import './chunk-RA3O54FC.js';
40
+ import './chunk-T2Q7ISPE.js';
41
+ import './chunk-WZBHBH3J.js';
42
+ import './chunk-CRB3POP4.js';
43
+ import './chunk-URIZRCRY.js';
44
+ import './chunk-TUSYOL2O.js';
45
+ import { parseProofRecipe, parseProofSetup } from './chunk-CQRPR4WZ.js';
46
+ import { ARTIFACT_MAX_CAPTION_LENGTH } from './chunk-EOTMDIYC.js';
47
+ import './chunk-VNVZDWO3.js';
48
+ import './chunk-HACGUL52.js';
49
+ import './chunk-MESOOLF4.js';
50
+ import './chunk-Z5GEFMRW.js';
51
+ import { spawnSync, spawn } from 'child_process';
52
+ import { mkdtempSync, rmSync, statSync, openSync, closeSync, readdirSync, readFileSync } from 'fs';
53
+ import { tmpdir } from 'os';
54
+ import { join } from 'path';
55
+
56
+ createRequire(import.meta.url);
57
+ var runShell = (command, params) => {
58
+ const logPath = join(params.logDir, `${params.groups.length}-${Date.now()}.log`);
59
+ const fd = openSync(logPath, "w");
60
+ return new Promise((resolve) => {
61
+ const child = spawn("sh", ["-c", command], { cwd: params.cwd, env: params.env, detached: true, stdio: ["ignore", fd, fd] });
62
+ closeSync(fd);
63
+ if (child.pid !== void 0) params.groups.push(child.pid);
64
+ let timedOut = false;
65
+ const timer = setTimeout(() => {
66
+ timedOut = true;
67
+ try {
68
+ process.kill(-child.pid, "SIGKILL");
69
+ } catch {
70
+ }
71
+ }, params.timeoutMs);
72
+ const finish = (status) => {
73
+ clearTimeout(timer);
74
+ let text = "";
75
+ try {
76
+ text = readFileSync(logPath, "utf8");
77
+ } catch {
78
+ }
79
+ resolve({ status, timedOut, tail: text.trimEnd().split("\n").slice(-8).join("\n") });
80
+ };
81
+ child.on("error", () => finish(null));
82
+ child.on("exit", (status) => finish(status));
83
+ });
84
+ };
85
+
86
+ // src/work-proofs-record.ts
87
+ var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
88
+ var MTIME_SLOP_MS = 2e3;
89
+ function showAtHead(root, path) {
90
+ const shown = spawnSync("git", ["show", `HEAD:${path}`], { cwd: root, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
91
+ return shown.status === 0 ? shown.stdout : void 0;
92
+ }
93
+ function findManifests(folder) {
94
+ const found = [];
95
+ for (const entry of readdirSync(folder, { withFileTypes: true })) {
96
+ const path = join(folder, entry.name);
97
+ if (entry.isDirectory()) found.push(...findManifests(path));
98
+ else if (entry.name === "manifest.json" && folder.split("/").pop() === "demo") found.push(path);
99
+ }
100
+ return found;
101
+ }
102
+ function unstoredUploads(result, spooled) {
103
+ if (typeof result !== "object" || result === null) return [];
104
+ const { artifactsDropped, artifactsHeld, rejected, uploadedArtifacts } = result;
105
+ if (Array.isArray(uploadedArtifacts)) {
106
+ const stored = new Set(uploadedArtifacts.map((a) => a?.artifactId));
107
+ return spooled.filter((id) => !stored.has(id));
108
+ }
109
+ const lost = typeof artifactsDropped === "number" && artifactsDropped > 0 || typeof artifactsHeld === "number" && artifactsHeld > 0 || Array.isArray(rejected) && rejected.length > 0;
110
+ return lost ? "all" : [];
111
+ }
112
+ function shortSha(sha) {
113
+ return sha.slice(0, 7);
114
+ }
115
+ function oneLine(text) {
116
+ return text.replace(/\s+/g, " ").trim();
117
+ }
118
+ async function runWorkProofsRecord(opts) {
119
+ const env = opts.env ?? process.env;
120
+ const item = resolveProofsItem({ item: opts.item, env });
121
+ const cwd = opts.cwd ?? process.cwd();
122
+ const identity = resolveRepoIdentity(cwd);
123
+ if (identity.repoRoot === void 0 || identity.commitSha === void 0) {
124
+ throw new WorkCommandError("this is not a git checkout with a commit. Run this inside the repository, after the recipe is committed.");
125
+ }
126
+ const { repoRoot: root, commitSha: head } = identity;
127
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
128
+ const creds = resolveCredentials({ ...opts, env });
129
+ const home = opts.home;
130
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
131
+ const lines = [];
132
+ const say = (line) => void lines.push(line);
133
+ const { data } = await getJson(creds.url, `/v1/work-items/${item}`, creds.apiKey, creds.appId, fetchImpl);
134
+ const body = data;
135
+ const key = recipeKeyFor(body.trackerKey ?? "");
136
+ if (key === void 0) {
137
+ throw new WorkCommandError(`the item's tracker key "${body.trackerKey ?? ""}" cannot name a recipe file.`);
138
+ }
139
+ const own = ownPullRequest(body.pullRequests ?? [], opts.pr, identity.gitRepo);
140
+ if ("ambiguous" in own) {
141
+ throw new WorkCommandError(`the item links several open pull requests (${own.ambiguous.map((n) => `#${n}`).join(", ")}). Pass --pr <number> to name this checkout's.`);
142
+ }
143
+ const ownPr = own.pr;
144
+ const heads = /* @__PURE__ */ new Set([head.toLowerCase(), ...otherPullRequestHeads(body.pullRequests ?? [], ownPr, identity.gitRepo)]);
145
+ const states = classifyProofsAt(body, heads);
146
+ const postGeneral = async (text, clientCommentId) => {
147
+ try {
148
+ await runWorkComment({ ...creds, item, body: text, clientCommentId, cwd, home, env, fetchImpl, quiet: true });
149
+ } catch (err) {
150
+ if (!(err instanceof WorkCommandError && err.code === "comment_conflict")) {
151
+ say(`The comment on the item's thread was not posted: ${oneLine(String(err instanceof Error ? err.message : err))}`);
152
+ }
153
+ }
154
+ };
155
+ const recipePath = `${PROOFS_DIR}/${key}.yaml`;
156
+ const setupPath = `${PROOFS_DIR}/${SETUP_FILE}`;
157
+ const recipeText = showAtHead(root, recipePath);
158
+ const setupText = showAtHead(root, setupPath);
159
+ const recipeParsed = recipeText === void 0 ? { ok: true, value: { run: [], manual: [] } } : parseProofRecipe(recipeText);
160
+ const setupParsed = setupText === void 0 ? { ok: true, value: { setup: [] } } : parseProofSetup(setupText);
161
+ for (const [file, parsed] of [
162
+ [recipePath, recipeParsed],
163
+ [setupPath, setupParsed]
164
+ ]) {
165
+ if (parsed.ok) continue;
166
+ const problem = `${file} cannot be read: ${parsed.problems.join("; ")}`;
167
+ if (opts.comment) {
168
+ const text = `The proof recipe cannot be read at ${shortSha(head)}, so no proof was recorded. ${problem}`.slice(0, 2e3);
169
+ await postGeneral(text, proofsCommentId(item, head, [{ id: file, proof: "recipe", reason: problem }]));
170
+ }
171
+ throw new WorkCommandError(problem);
172
+ }
173
+ const recipe = recipeParsed.value;
174
+ const setup = setupParsed.value;
175
+ if (recipeText === void 0) say(`No recipe at ${recipePath} in the HEAD commit, so no entry runs.`);
176
+ const dirty = spawnSync("git", ["diff", "--quiet", "HEAD", "--", PROOFS_DIR], { cwd: root });
177
+ if (dirty.status === 1) say(`The working copy of ${PROOFS_DIR} differs from HEAD. Only the committed files run. Commit the change, then record again.`);
178
+ const declared = new Map((body.criteria?.items ?? []).map((criterion) => [criterion.id, criterion]));
179
+ const reasons = /* @__PURE__ */ new Map();
180
+ const emitted = /* @__PURE__ */ new Map();
181
+ const manual = new Map(recipe.manual.map((entry) => [entry.criterion, entry]));
182
+ const plan = planEntries(recipe, states);
183
+ const toRun = [];
184
+ for (const planned of plan) {
185
+ if (planned.skip !== null) continue;
186
+ const label = planned.entry.emit === "demo" ? "the demo entry" : `the entry for ${planned.entry.for}`;
187
+ const refuse = (reason) => {
188
+ say(`Skipped ${label}: ${reason}`);
189
+ for (const id of planned.criteria) reasons.set(id, reason);
190
+ };
191
+ if (planned.entry.command.trim() === "") {
192
+ refuse("it has no command. Fill in the recipe.");
193
+ continue;
194
+ }
195
+ if (planned.entry.emit !== "demo") {
196
+ const checked = checkEmitPath(root, planned.entry.emit);
197
+ if (!checked.ok) {
198
+ refuse(checked.reason);
199
+ continue;
200
+ }
201
+ }
202
+ toRun.push(planned);
203
+ }
204
+ const logDir = mkdtempSync(join(tmpdir(), "outerlayer-proofs-"));
205
+ const groups = [];
206
+ const runCommand = opts.runCommand ?? runShell;
207
+ const baseEnv = { ...process.env, ...env };
208
+ let spooledCount = 0;
209
+ const spooledCriteria = /* @__PURE__ */ new Set();
210
+ const emitArtifact = async (emitOpts) => {
211
+ const result = await (opts.emitArtifact ?? runEmitArtifact)(emitOpts);
212
+ if (result.spooled) spooledCount += 1;
213
+ return result;
214
+ };
215
+ const common = { url: creds.url, apiKey: creds.apiKey, appId: creds.appId, cwd: root, home, env, fetchImpl, quiet: true };
216
+ let setupStarted = false;
217
+ let setupFailed;
218
+ const stopGroups = () => {
219
+ for (const pid of groups) {
220
+ try {
221
+ process.kill(-pid, "SIGTERM");
222
+ } catch {
223
+ }
224
+ }
225
+ };
226
+ const onSignal = (signal) => {
227
+ stopGroups();
228
+ rmSync(logDir, { recursive: true, force: true });
229
+ process.exit(signal === "SIGINT" ? 130 : 143);
230
+ };
231
+ const onInterrupt = () => onSignal("SIGINT");
232
+ const onTerminate = () => onSignal("SIGTERM");
233
+ process.once("SIGINT", onInterrupt);
234
+ process.once("SIGTERM", onTerminate);
235
+ try {
236
+ if (toRun.length > 0) {
237
+ setupStarted = true;
238
+ for (const command of setup.setup) {
239
+ const result = await runCommand(command, { cwd: root, env: baseEnv, timeoutMs, logDir, groups });
240
+ if (result.status !== 0) {
241
+ setupFailed = `setup failed: "${oneLine(command)}" ${result.timedOut ? `timed out after ${Math.round(timeoutMs / 1e3)} s` : `exited with status ${result.status}`}`;
242
+ say(setupFailed);
243
+ if (result.tail !== "") say(result.tail);
244
+ break;
245
+ }
246
+ }
247
+ }
248
+ for (const planned of toRun) {
249
+ if (setupFailed !== void 0) {
250
+ for (const id of planned.criteria) reasons.set(id, setupFailed);
251
+ continue;
252
+ }
253
+ const { entry } = planned;
254
+ const proofOut = mkdtempSync(join(tmpdir(), "outerlayer-proof-out-"));
255
+ const startedAt = Date.now();
256
+ try {
257
+ const result = await runCommand(entry.command, { cwd: root, env: { ...baseEnv, OUTERLAYER_PROOF_OUT: proofOut }, timeoutMs, logDir, groups });
258
+ const failure = result.timedOut ? `the command timed out after ${Math.round(timeoutMs / 1e3)} s` : result.status === 0 ? void 0 : `the command exited ${result.status}`;
259
+ if (entry.emit === "demo") {
260
+ await recordDemo({ planned, proofOut, failure, tail: result.tail });
261
+ } else {
262
+ await recordFile({ planned, failure, tail: result.tail, startedAt });
263
+ }
264
+ } finally {
265
+ rmSync(proofOut, { recursive: true, force: true });
266
+ }
267
+ }
268
+ } finally {
269
+ if (setupStarted) {
270
+ for (const command of setup.teardown ?? []) {
271
+ const result = await runCommand(command, { cwd: root, env: baseEnv, timeoutMs, logDir, groups });
272
+ if (result.status !== 0) say(`teardown failed: "${oneLine(command)}" ${result.timedOut ? "timed out" : `exited with status ${result.status}`}`);
273
+ }
274
+ }
275
+ process.off("SIGINT", onInterrupt);
276
+ process.off("SIGTERM", onTerminate);
277
+ stopGroups();
278
+ rmSync(logDir, { recursive: true, force: true });
279
+ }
280
+ async function recordFile(params) {
281
+ const { entry } = params.planned;
282
+ const id = entry.for;
283
+ const fail = (reason) => {
284
+ reasons.set(id, reason);
285
+ say(`Not recorded, ${id}: ${reason}`);
286
+ if (params.tail !== "" && params.failure !== void 0) say(params.tail);
287
+ };
288
+ if (params.failure !== void 0) return fail(params.failure);
289
+ const checked = checkEmitPath(root, entry.emit);
290
+ if (!checked.ok) return fail(checked.reason);
291
+ let modified;
292
+ try {
293
+ const stat = statSync(checked.path);
294
+ if (!stat.isFile()) return fail(`${entry.emit} is not a file`);
295
+ modified = stat.mtimeMs;
296
+ } catch {
297
+ return fail(`the command did not write ${entry.emit}`);
298
+ }
299
+ if (!insideRoot(root, checked.path)) return fail(`${entry.emit} is outside the repository: it links out of it`);
300
+ if (modified < params.startedAt - MTIME_SLOP_MS) return fail(`the command did not write ${entry.emit}: the file is older than this run`);
301
+ const problem = kindProblem(declared.get(id)?.proof ?? null, checked.path);
302
+ if (problem !== void 0) return fail(problem);
303
+ try {
304
+ const caption = (declared.get(id)?.text ?? `Proof for ${id}`).slice(0, ARTIFACT_MAX_CAPTION_LENGTH);
305
+ const before = spooledCount;
306
+ const result = await emitArtifact({ ...common, file: checked.path, caption, criterionId: id, ...ownPr === void 0 ? {} : { pr: ownPr } });
307
+ const artifactId = result.record?.artifactId ?? result.data?.clientArtifactId;
308
+ if (artifactId === void 0) return fail("the upload returned no artifact id");
309
+ emitted.set(id, artifactId);
310
+ if (spooledCount > before) spooledCriteria.add(id);
311
+ say(`Recorded ${id} from ${entry.emit}.`);
312
+ } catch (err) {
313
+ fail(`the upload failed: ${oneLine(String(err instanceof Error ? err.message : err))}`);
314
+ }
315
+ }
316
+ async function recordDemo(params) {
317
+ const fail = (reason) => {
318
+ for (const id of params.planned.criteria) reasons.set(id, reason);
319
+ say(`Not recorded, the demo: ${reason}`);
320
+ if (params.tail !== "" && params.failure !== void 0) say(params.tail);
321
+ };
322
+ const manifests = findManifests(params.proofOut);
323
+ if (manifests.length !== 1) {
324
+ const status = params.failure === void 0 ? "" : ` (${params.failure})`;
325
+ return fail(manifests.length === 0 ? `the command left no demo manifest${status}` : `the command left ${manifests.length} demo manifests, so it is unclear which to store`);
326
+ }
327
+ let replaces;
328
+ try {
329
+ replaces = (await readNewestDemo({ url: creds.url, apiKey: creds.apiKey, appId: creds.appId, item, fetchImpl }))?.artifactId;
330
+ } catch (err) {
331
+ replaces = void 0;
332
+ say(`The previous demo could not be read, so the new demo does not replace it: ${oneLine(String(err instanceof Error ? err.message : err))}`);
333
+ }
334
+ const before = spooledCount;
335
+ let result;
336
+ try {
337
+ result = await (opts.emitDemo ?? runEmitDemo)({
338
+ ...common,
339
+ manifest: manifests[0],
340
+ item,
341
+ ...ownPr === void 0 ? {} : { pr: ownPr },
342
+ ...replaces === void 0 ? {} : { replaces },
343
+ emitArtifact
344
+ });
345
+ } catch (err) {
346
+ return fail(`the demo was not stored: ${oneLine(String(err instanceof Error ? err.message : err))}`);
347
+ }
348
+ for (const id of params.planned.criteria) {
349
+ const proof = declared.get(id)?.proof;
350
+ const artifactId = proof === "screenshot" ? result.screenshots.find((shot) => shot.criterionId === id && shot.artifactId !== void 0)?.artifactId : result.clips.find((clip) => clip.criterionId === id && clip.bound)?.clipArtifactId;
351
+ if (artifactId !== void 0) {
352
+ emitted.set(id, artifactId);
353
+ if (spooledCount > before) spooledCriteria.add(id);
354
+ } else reasons.set(id, result.failures.length > 0 ? `the demo failed a check: ${oneLine(result.failures[0])}` : "the demo has no passing chapter for it");
355
+ }
356
+ say(`Recorded the demo: ${params.planned.criteria.filter((id) => emitted.has(id)).length} of ${params.planned.criteria.length} proofs.`);
357
+ for (const failure of result.failures) say(`Demo check failed: ${oneLine(failure)}`);
358
+ }
359
+ if (spooledCount > 0) {
360
+ try {
361
+ const synced = await (opts.sync ?? (() => runSync({ ...common })))();
362
+ const unstored = unstoredUploads(
363
+ synced,
364
+ [...spooledCriteria].map((id) => emitted.get(id))
365
+ );
366
+ const lostIds = unstored === "all" ? [...spooledCriteria] : [...spooledCriteria].filter((id) => unstored.includes(emitted.get(id)));
367
+ for (const id of lostIds) {
368
+ emitted.delete(id);
369
+ reasons.set(id, "the sync did not store the upload, so the proof is stored only in the session record");
370
+ }
371
+ if (lostIds.length > 0) say("The sync did not store an upload. The proofs that went through the session record are not stored.");
372
+ } catch (err) {
373
+ for (const id of spooledCriteria) {
374
+ emitted.delete(id);
375
+ reasons.set(id, "the sync failed, so the proof is stored only in the session record");
376
+ }
377
+ say(`The sync failed, so the new proofs are stored only in the session record: ${oneLine(String(err instanceof Error ? err.message : err))}`);
378
+ }
379
+ }
380
+ let finalStates = states;
381
+ try {
382
+ const { data: fresh } = await getJson(creds.url, `/v1/work-items/${item}`, creds.apiKey, creds.appId, fetchImpl);
383
+ finalStates = classifyProofsAt(fresh, heads);
384
+ } catch {
385
+ }
386
+ const notCurrent = finalStates.filter((state) => state.state !== "current" && !emitted.has(state.id)).map((state) => ({ id: state.id, proof: state.proof, reason: reasons.get(state.id) ?? manual.get(state.id)?.reason ?? "no entry in the recipe" }));
387
+ if (opts.comment && notCurrent.length > 0) {
388
+ await postGeneral(proofsCommentBody(head, notCurrent), proofsCommentId(item, head, notCurrent));
389
+ }
390
+ for (const proof of notCurrent) say(`Not current, ${proof.id} (${proof.proof}): ${proof.reason}`);
391
+ const closing = await runWorkProofs({ ...opts, item, env, quiet: true, checkoutHead: head, fetchImpl });
392
+ const output = [...lines, closing.output].join("\n");
393
+ if (!opts.quiet) process.stdout.write(output + "\n");
394
+ return {
395
+ exitCode: closing.exitCode,
396
+ output,
397
+ emitted: [...emitted].map(([criterion, artifactId]) => ({ criterion, artifactId })),
398
+ notCurrent
399
+ };
400
+ }
401
+
402
+ export { runWorkProofsRecord };
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { resolveCredentials, postJson, WorkCommandError } from './chunk-VSLJ6PPG.js';
3
+ import { resolveCredentials, postJson, WorkCommandError } from './chunk-J32AF6D2.js';
4
4
  import './chunk-IP7BIBXJ.js';
5
- import './chunk-HLZMAJJD.js';
6
- import './chunk-S5CNYXUA.js';
7
- import './chunk-MPC2UAW2.js';
8
- import './chunk-NIY2UBT6.js';
9
- import './chunk-LYGI3T66.js';
10
- import './chunk-7V5GUEDU.js';
5
+ import './chunk-2HCXEIJD.js';
6
+ import './chunk-OVRNGOD2.js';
7
+ import './chunk-MECW77XP.js';
8
+ import './chunk-BO42KT3D.js';
9
+ import './chunk-TIRHZC2M.js';
10
+ import './chunk-Y5YCX6AL.js';
11
11
  import './chunk-G2JWZDFA.js';
12
12
  import './chunk-ANPNT6PM.js';
13
13
  import { defaultFetch } from './chunk-6RKD7YZ6.js';
@@ -16,14 +16,14 @@ import './chunk-SOI6K5DY.js';
16
16
  import './chunk-PA6CK6WQ.js';
17
17
  import './chunk-BALIE3BA.js';
18
18
  import './chunk-BJ3KTMDH.js';
19
- import './chunk-44V5SD4G.js';
19
+ import './chunk-ZGB4ZJ5E.js';
20
20
  import './chunk-5BHIBJDA.js';
21
21
  import './chunk-CDDED64E.js';
22
22
  import './chunk-RA3O54FC.js';
23
23
  import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
24
24
  import './chunk-CRB3POP4.js';
25
- import './chunk-6CXNWKWD.js';
26
- import './chunk-F2D5BSXS.js';
25
+ import './chunk-CQRPR4WZ.js';
26
+ import './chunk-EOTMDIYC.js';
27
27
  import './chunk-VNVZDWO3.js';
28
28
  import './chunk-HACGUL52.js';
29
29
  import './chunk-MESOOLF4.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outerlayer/cli",
3
- "version": "0.33.0",
3
+ "version": "0.34.0-next.20261023233807",
4
4
  "description": "OuterLayer CLI — commands: init, claude, connect, doctor, login, logout, sync, daemon (alias: watch), emit, findings, work, spec, runner, hooks, mcp, context. Capture and sync coding-agent sessions, and record proof of factory work.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Magu Studios, Inc.",
@@ -127,15 +127,15 @@ repository. Commit and push in each checkout you change, to its own branch only.
127
127
  8. **Check nothing is left waiting.** Reread threads. A thread still waiting
128
128
  on an agent has a reply that did not land or was flagged `unchanged`. Fix
129
129
  or report it. Then run `outerlayer sync` and `outerlayer work proofs`.
130
- 9. **Record the demo again.** Skip this step when you pushed nothing. With
131
- the app running, run in each pushed checkout
132
- `node .outerlayer/skills/outerlayer-amend/scripts/rerecord-demo.mjs --item "$OUTERLAYER_WORK" --pr <prNumber> --repository <owner/name>`.
133
- It prints the demo line for the report.
130
+ 9. **Record the proofs again.** Skip this step when you pushed nothing. If a
131
+ fix changes how a proof is made, commit the recipe edit
132
+ (`.outerlayer/proofs/<item key>.yaml`). Push, run
133
+ `outerlayer work proofs --record --comment --pr <prNumber>` per pushed checkout.
134
134
  10. **Report.** Write one line per thread: the criterion (or the general
135
135
  thread), and fixed (with the replacement) or handed off (with the reason).
136
136
  Add a line for a merge conflict (merged with the base sha, or handed off
137
137
  with the reason), a CI line when you pushed (green, or each red check and
138
- why), the demo line from step 9, and a line naming each proof missing or
138
+ why), the lines from step 9, and a line naming each proof missing or
139
139
  out of date.
140
140
  Post the lines once on the general thread:
141
141
  `outerlayer work comment --item "$OUTERLAYER_WORK" --body "<the lines>"`.
@@ -341,9 +341,10 @@ Run them in order.
341
341
  Watch CI until every check has concluded.
342
342
  6. **Evidence.** Run `outerlayer emit step "Evidence" --item "$OUTERLAYER_WORK"`,
343
343
  then apply the team's changes for this phase. Read [references/evidence.md](references/evidence.md)
344
- when the pull request opens, and run it. Capture the proof each criterion
345
- declares, emit it. Then check the proofs with `outerlayer work proofs`, as
346
- that file says.
344
+ when the pull request opens, and run it. Write and commit the proof recipe
345
+ (`outerlayer work proofs --init`), then capture each proof with
346
+ `outerlayer work proofs --record`. Check the proofs with
347
+ `outerlayer work proofs`, as that file says.
347
348
 
348
349
  Optional steps a team may turn off: the manual test and screenshots in the
349
350
  Evidence phase, for a repository with no user interface.
@@ -262,23 +262,36 @@ mark the file so that CI skips it, as `apps/e2e` does.
262
262
 
263
263
  Record each criterion that declares `proof: screenshot` as a chapter of the same
264
264
  demo, with a `demo.screenshot()` call where the result shows. The frame then
265
- comes from the committed test, so an amend records it again at the new head. Put
266
- a capture in a script under `build-reports/evidence/` and run the script only
267
- when no demo test can reach its state. A later fix can invalidate what
268
- you captured, and regenerating it must be one command. The scripts go when the
269
- run's report directory goes. The emitted artifacts outlive the run.
270
-
271
- Record the criteria that declare `proof: video` or `proof: screenshot` as one demo, as the
272
- outerlayer-evidence skill's "Record a demo" says. Write one Playwright test with
273
- `@outerlayer/playwright` and one chapter for each of those criteria, in the
274
- criteria's order. Run it, then run `outerlayer emit demo <manifest> --item
275
- "$OUTERLAYER_WORK" --pr <number>`. Emit each other artifact with `outerlayer
276
- emit artifact ... --for <criterion id> --pr <number>`. Always pass `--pr`.
277
- When the tree invalidates a video, record and emit the whole demo again: the
278
- newer demo replaces the older one. Regenerate any other invalidated evidence
279
- with `--replaces`. Then run `outerlayer sync`, and run `outerlayer work
280
- proofs --item "$OUTERLAYER_WORK"`. For each proof it lists as out of date or
281
- missing, capture and emit that criterion again, and run the command once more.
265
+ comes from the committed test, so a later session records it again at the new head.
266
+
267
+ Every proof comes from a recipe that you commit with the change. Run
268
+ `outerlayer work proofs --init --pr <number>`. It writes
269
+ `.outerlayer/proofs/<item key>.yaml` and, when absent, `.outerlayer/proofs/setup.yaml`.
270
+ Fill them in:
271
+
272
+ - `setup.yaml` lists the commands that seed and start the app (`setup`) and
273
+ stop it (`teardown`). Each setup command must return once the app is ready.
274
+ - The one `emit: demo` entry holds the command that runs the demo test. Write
275
+ the test with `@outerlayer/playwright`, one chapter for each criterion in the
276
+ criteria's order, as the outerlayer-evidence skill's "Record a demo" says. The
277
+ test writes its manifest where `OUTERLAYER_PROOF_OUT` names. The entry covers
278
+ every screenshot and video criterion that no other entry names.
279
+ - Every other proof is a command entry: a `command` that writes one file, the
280
+ repository-relative `emit` path of that file, and `for`, the criterion id.
281
+ The kind of the file must be the kind the criterion declares.
282
+ - A proof that only a person can capture goes under `manual` with a `reason`.
283
+ Name each criterion in one place only.
284
+
285
+ Commit the recipe, the setup file and the demo test. Then run
286
+ `outerlayer work proofs --record` from the committed checkout. It runs only
287
+ what is committed at HEAD, and emits each proof at HEAD. Never emit by hand a
288
+ proof that a recipe entry can record, and never keep a capture script outside
289
+ the recipe. Always pass `--pr <number>` when the item links more than one pull
290
+ request in the repository. Record again after every later commit. It skips an
291
+ entry whose proofs are current.
292
+
293
+ Run `outerlayer sync`, then `outerlayer work proofs --item "$OUTERLAYER_WORK"`.
294
+ For each proof it lists as out of date or missing, fix its entry and record again.
282
295
 
283
296
  Write `build-reports/evidence.md` naming every criterion with its artifact, or
284
297
  with the reason none could be produced. A criterion you could not evidence is
@@ -21,24 +21,26 @@ state no demo test can reach. One that declares `proof: video` needs a video: it
21
21
  item's one demo. A screenshot never satisfies `proof: video`. A criterion with no proof form is proven by its
22
22
  test, and needs no artifact.
23
23
 
24
+ The evidence agent captures every proof from a recipe it commits with the
25
+ change: `outerlayer work proofs --init` writes it, and
26
+ `outerlayer work proofs --record` runs it at HEAD and emits each proof. A
27
+ capture script outside the recipe is not allowed, because no later session can
28
+ run it again.
29
+
24
30
  After the evidence agent returns, and after every later commit, run
25
31
  `outerlayer sync`, then `outerlayer work proofs --item "$OUTERLAYER_WORK"`. It
26
32
  lists each declared proof as current, out of date or missing at the checkout's
27
- head. For each criterion it lists as out of date or missing, run the evidence
28
- step again for that criterion only, then run the command once more before the
29
- run ends. A criterion still listed goes in `build-reports/evidence.md` with the
30
- reason. The command leaves out criteria that declare a test proof.
31
-
32
- When a commit lands on the branch after the evidence agent captured, decide for
33
- each capture from that commit's diff. A capture is stale when the diff touches
34
- a file the capture's subject runs through: the feature's own files, the shared
35
- code they import, a migration or schema behind them, or a test the capture is a
36
- log of. A sync that brings in only files the feature never reaches leaves the
37
- capture valid. Spawn the evidence agent again with the stale list. It reruns
38
- their scripts and emits each with `--replaces`. A stale video means the whole
39
- demo is recorded and emitted again. The final report names the
40
- commit and every capture with its call, kept or regenerated. A commit that
41
- lands after this call reopens it. Never drive the browser yourself.
33
+ head. Commit the work, then run `outerlayer work proofs --record`: it records
34
+ each proof that is not current and skips the rest. Run the list command once
35
+ more before the run ends. A criterion still listed goes in
36
+ `build-reports/evidence.md` with the reason. The command leaves out criteria
37
+ that declare a test proof.
38
+
39
+ When a commit lands on the branch after the evidence agent captured, run
40
+ `outerlayer work proofs --record` again. It records every proof that is not
41
+ current at the new head. The final report names the commit and the proofs it
42
+ recorded again. A commit that lands after this call reopens it. Never drive the
43
+ browser yourself.
42
44
 
43
45
  Re-read the pull request's evidence comment. Resolve or escalate every row that
44
46
  needs attention. A row is resolved when a re-read shows it, not when the fixing