@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.
- package/README.md +4 -1
- package/dist/{agent-setup-YHEW3WXB.js → agent-setup-FBAT5LGX.js} +10 -10
- package/dist/{artifact-link-cmd-7FYKWA7J.js → artifact-link-cmd-LWPY3V4N.js} +2 -2
- package/dist/{build-SEAQVLD5.js → build-LSU25G3M.js} +2 -2
- package/dist/{build-disk-ECTLWTPC.js → build-disk-ZDGSJLPA.js} +5 -5
- package/dist/build-info.json +1 -1
- package/dist/{builder-LUPHKUFM.js → builder-QKH2HBAO.js} +4 -4
- package/dist/{check-XPMN4GHD.js → check-4MYU7S73.js} +32 -32
- package/dist/{chunk-QKNFU5GA.js → chunk-2GWMYY4M.js} +5 -5
- package/dist/{chunk-HLZMAJJD.js → chunk-2HCXEIJD.js} +2 -2
- package/dist/{chunk-VWQCYOBS.js → chunk-2UF4H4PG.js} +2 -2
- package/dist/{chunk-Z57X4KNC.js → chunk-3CUGPUCP.js} +2 -2
- package/dist/{chunk-EXZACZ2P.js → chunk-3FMARI3L.js} +1 -1
- package/dist/{chunk-6SPD266O.js → chunk-4H2DWORO.js} +3 -3
- package/dist/{chunk-EHOFFTVX.js → chunk-4V5V2HAQ.js} +2 -2
- package/dist/{chunk-VMIOUVJF.js → chunk-5CHRWX3K.js} +5 -5
- package/dist/{chunk-L6MDOEHN.js → chunk-74Q24R3B.js} +7 -7
- package/dist/{chunk-BLHUFOOB.js → chunk-7JETLQED.js} +12 -12
- package/dist/{chunk-AKZRGFGT.js → chunk-B44BFR2Q.js} +3 -3
- package/dist/{chunk-V673DFOL.js → chunk-B5MSDF5B.js} +4 -4
- package/dist/{chunk-X4JEBMYN.js → chunk-BBGVXGSO.js} +1 -1
- package/dist/{chunk-NIY2UBT6.js → chunk-BO42KT3D.js} +1 -1
- package/dist/{chunk-6CXNWKWD.js → chunk-CQRPR4WZ.js} +143 -22
- package/dist/{chunk-F2D5BSXS.js → chunk-EOTMDIYC.js} +1 -1
- package/dist/{chunk-NE63JADS.js → chunk-ES4ULBAL.js} +2 -2
- package/dist/{emit-demo-cmd-IQRS7BP2.js → chunk-GODAKHDD.js} +6 -35
- package/dist/{chunk-RZOO7X4G.js → chunk-HG6INMG3.js} +3 -3
- package/dist/{chunk-JEVKOZBB.js → chunk-HMCBX3PL.js} +1 -1
- package/dist/{chunk-7UPMYRPS.js → chunk-HUUYTDFV.js} +4 -4
- package/dist/{chunk-VSLJ6PPG.js → chunk-J32AF6D2.js} +5 -5
- package/dist/{chunk-D3EDZPJR.js → chunk-KLGYSJ3A.js} +5 -5
- package/dist/{chunk-4YLL2E7Y.js → chunk-KSZXJE3R.js} +12 -12
- package/dist/{chunk-YY3OQJ4X.js → chunk-LFBMXXE6.js} +14 -14
- package/dist/{chunk-MPC2UAW2.js → chunk-MECW77XP.js} +1 -1
- package/dist/{work-proofs-cmd-XBESDEPM.js → chunk-NYHQF6BT.js} +30 -30
- package/dist/{chunk-DKFUXYGD.js → chunk-OLCIPTST.js} +4 -4
- package/dist/{chunk-S5CNYXUA.js → chunk-OVRNGOD2.js} +4 -4
- package/dist/{chunk-UK4RGH46.js → chunk-SHIGGHZO.js} +2 -2
- package/dist/{chunk-EJL6CWSP.js → chunk-SVLFZLTE.js} +6 -6
- package/dist/{chunk-5M2J7B5N.js → chunk-TCGVYHII.js} +2 -2
- package/dist/{chunk-LYGI3T66.js → chunk-TIRHZC2M.js} +2 -2
- package/dist/{chunk-OGMXVPCS.js → chunk-TYBK2UCW.js} +2 -2
- package/dist/{chunk-FT7MUQGO.js → chunk-UF2FU3CM.js} +2 -2
- package/dist/{work-comment-cmd-QEZHRXC5.js → chunk-VY2FE5VT.js} +4 -24
- package/dist/{chunk-GO2DUPQS.js → chunk-WEUCD3J3.js} +3 -3
- package/dist/{chunk-KUE4V2SK.js → chunk-XFAJQZN2.js} +2 -2
- package/dist/{chunk-2LFWCIIE.js → chunk-XXTXM47K.js} +15 -15
- package/dist/{chunk-7V5GUEDU.js → chunk-Y5YCX6AL.js} +1 -1
- package/dist/chunk-YNJUN6BB.js +148 -0
- package/dist/{chunk-ZF24JN7R.js → chunk-YPCDDQFU.js} +1 -1
- package/dist/{chunk-7IFYI7XF.js → chunk-YQ5BEACL.js} +3 -3
- package/dist/{chunk-44V5SD4G.js → chunk-ZGB4ZJ5E.js} +1 -1
- package/dist/{chunk-YXHKCHWG.js → chunk-ZVLSYWDF.js} +1 -1
- package/dist/{claude-launcher-YRPW4SWA.js → claude-launcher-YXRS4EZE.js} +5 -5
- package/dist/{cli-65ZQJ622.js → cli-QQBC67PY.js} +132 -108
- package/dist/{command-QENZQGZL.js → command-27SJOFEM.js} +21 -21
- package/dist/{config-MVA7YTFW.js → config-7M4PJUZ5.js} +6 -6
- package/dist/{connect-cmd-KWXV2CGC.js → connect-cmd-XPSJT6JW.js} +8 -8
- package/dist/{container-start-RVH5743X.js → container-start-FDUQKTOF.js} +27 -27
- package/dist/{context-cleanup-A2FGVVPH.js → context-cleanup-YGWYIFFV.js} +3 -3
- package/dist/{context-show-cmd-6F4UF4ND.js → context-show-cmd-4HLZ6DEB.js} +9 -9
- package/dist/{control-plane-launch-7SOH6IMA.js → control-plane-launch-AXBWT5CC.js} +6 -6
- package/dist/{dist-IG2DKUJJ.js → dist-ELNMCU72.js} +2 -2
- package/dist/{docker-YKYQJHL2.js → docker-7XRXEGMX.js} +4 -4
- package/dist/{doctor-MFLM7DMY.js → doctor-Y3F7KETW.js} +22 -22
- package/dist/{emit-artifact-cmd-6QTTWBVN.js → emit-artifact-cmd-SX6A3N46.js} +13 -13
- package/dist/{emit-cmd-3FG7FEGO.js → emit-cmd-A4CB7BEC.js} +6 -6
- package/dist/{emit-criteria-cmd-22VVCL2W.js → emit-criteria-cmd-4LXKENTI.js} +10 -10
- package/dist/emit-demo-cmd-ECMS73O6.js +48 -0
- package/dist/{emit-finding-cmd-TXDPJFVT.js → emit-finding-cmd-K73WX2UP.js} +10 -10
- package/dist/{emit-result-cmd-QHJDUC7J.js → emit-result-cmd-SUQASZMD.js} +12 -12
- package/dist/{emit-step-cmd-TQCOLXAA.js → emit-step-cmd-5NQ6W4F3.js} +10 -10
- package/dist/{findings-cmd-EURKADAY.js → findings-cmd-2BOST4CX.js} +10 -10
- package/dist/{fleet-readiness-GXDSFTLP.js → fleet-readiness-VAKMSSWI.js} +8 -8
- package/dist/{governing-source-INEXO2CK.js → governing-source-NSQ5TIMO.js} +13 -13
- package/dist/{hook-fast-3IYSWDL4.js → hook-fast-WENTLDMA.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/{init-WIHSXL74.js → init-E2N3A4MB.js} +21 -21
- package/dist/{init-VWJIINBC.js → init-SFZ5VUZ6.js} +21 -21
- package/dist/{init-cmd-3VWJUWXA.js → init-cmd-BNOWYMNA.js} +18 -18
- package/dist/{init-update-LFKSC6Y4.js → init-update-6QK5URLQ.js} +17 -17
- package/dist/{install-47Z7VNWP.js → install-E6DEU5NV.js} +7 -7
- package/dist/{layout-44S4OYLK.js → layout-NAPG4SX2.js} +4 -4
- package/dist/{login-browser-BJZYKWHF.js → login-browser-DQ6YRXUB.js} +2 -2
- package/dist/{loop-5QV4VZOF.js → loop-6RHTIIIE.js} +32 -32
- package/dist/{machine-XTSUAX3K.js → machine-OYI4ZWQS.js} +21 -21
- package/dist/{machine-skills-GOKW3U5N.js → machine-skills-2TT3RXMJ.js} +4 -4
- package/dist/{runner-env-ILP6BAC2.js → runner-env-EKBC5EYE.js} +8 -8
- package/dist/{runner-identity-MF3AYBR6.js → runner-identity-KN4CW5VQ.js} +4 -4
- package/dist/{runner-key-N4IIFMUX.js → runner-key-W6KG4QD7.js} +21 -21
- package/dist/{session-sender-2DYAJIMM.js → session-sender-Q7I6NUJU.js} +13 -13
- package/dist/{spec-cmd-7W4ERDL7.js → spec-cmd-3RWLOGZW.js} +11 -11
- package/dist/{starter-pack-FWTXE6XB.js → starter-pack-5D4BANFU.js} +5 -5
- package/dist/{status-VBUCLDN5.js → status-G26SS4VW.js} +26 -26
- package/dist/{sync-cmd-NGX2PGIW.js → sync-cmd-2W5F23IM.js} +12 -12
- package/dist/{watch-ZIWZVU3Z.js → watch-QW4LMB23.js} +12 -12
- package/dist/{work-claim-cmd-G3DC5LJK.js → work-claim-cmd-D2VABY7U.js} +16 -16
- package/dist/{work-cmd-J2LJ3V4A.js → work-cmd-FVDCWSPE.js} +10 -10
- package/dist/work-comment-cmd-RW7FZBBH.js +33 -0
- package/dist/{work-demo-cmd-A7XE4NKA.js → work-demo-cmd-M5NL3IPF.js} +10 -10
- package/dist/{work-logs-cmd-UA5PAXRP.js → work-logs-cmd-KUG5JARJ.js} +10 -10
- package/dist/{work-open-pr-cmd-ZYKNAJJM.js → work-open-pr-cmd-HSVGGLFC.js} +11 -11
- package/dist/{work-pr-cmd-DH43AH3V.js → work-pr-cmd-KBHTEAIT.js} +10 -10
- package/dist/work-proofs-cmd-A6QNVO6B.js +34 -0
- package/dist/work-proofs-init-QB2UL3BC.js +35 -0
- package/dist/work-proofs-record-APK4VWRB.js +402 -0
- package/dist/{work-stop-cmd-ZDIW3MVX.js → work-stop-cmd-HSGFTRIX.js} +10 -10
- package/package.json +1 -1
- package/skill-pack/maintained/outerlayer-amend/SKILL.md +5 -5
- package/skill-pack/maintained/outerlayer-build/SKILL.md +4 -3
- package/skill-pack/maintained/outerlayer-build/references/agent-briefs.md +30 -17
- package/skill-pack/maintained/outerlayer-build/references/evidence.md +17 -15
- 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-
|
|
3
|
+
import { resolveCredentials, postJson, WorkCommandError } from './chunk-J32AF6D2.js';
|
|
4
4
|
import './chunk-IP7BIBXJ.js';
|
|
5
|
-
import './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
10
|
-
import './chunk-
|
|
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-
|
|
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-
|
|
26
|
-
import './chunk-
|
|
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.
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
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.
|
|
345
|
-
|
|
346
|
-
|
|
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
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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.
|
|
28
|
-
|
|
29
|
-
run ends. A criterion still listed goes in
|
|
30
|
-
reason. The command leaves out criteria
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|