@outerlayer/cli 0.33.0-next.20261023003700 → 0.33.0-next.20261023030517
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 +9 -3
- package/dist/{agent-setup-55ZDEXCS.js → agent-setup-APFM4MZ4.js} +10 -10
- package/dist/{artifact-link-cmd-6FF2Y6QI.js → artifact-link-cmd-IHHMU4EA.js} +2 -2
- package/dist/{build-disk-JHTMPRAA.js → build-disk-55M4ZMDX.js} +4 -4
- package/dist/build-info.json +1 -1
- package/dist/{builder-Y4P7QARW.js → builder-DQZCPB7J.js} +3 -3
- package/dist/{check-MRYFTLOO.js → check-AEMFDR55.js} +28 -28
- package/dist/{chunk-EUVE2EAE.js → chunk-2WZ66FXA.js} +3 -3
- package/dist/{chunk-BJQ25STI.js → chunk-35DSTT37.js} +4 -4
- package/dist/{chunk-Q2CMEQV7.js → chunk-4NKHOCTF.js} +3 -3
- package/dist/{chunk-VT4TTJPG.js → chunk-5FSVA3CO.js} +5 -5
- package/dist/{chunk-SDU2IE22.js → chunk-67KCGRKA.js} +12 -12
- package/dist/{chunk-HXA5GIHK.js → chunk-6ANIBEQ6.js} +6 -6
- package/dist/{chunk-AVTI3FL5.js → chunk-6ZLI652Y.js} +1 -1
- package/dist/{chunk-HE4FBF4T.js → chunk-7HZH5D7H.js} +2 -2
- package/dist/{chunk-6Z4SZHV2.js → chunk-A5FFANL7.js} +5 -5
- package/dist/{chunk-PWH3BEZF.js → chunk-AXIK6F52.js} +13 -13
- package/dist/{chunk-7HHNGZS5.js → chunk-B6WW6AKO.js} +4 -4
- package/dist/{chunk-ULBNTSAD.js → chunk-C66Q2CLW.js} +2 -2
- package/dist/{chunk-UIUGTW5C.js → chunk-C7WGCI77.js} +2 -2
- package/dist/{chunk-B2JURP6A.js → chunk-CCOU4NXE.js} +2 -2
- package/dist/{chunk-SVDOA4W2.js → chunk-DHUHQPRQ.js} +7 -7
- package/dist/{chunk-EWVYQLD3.js → chunk-E4F7YE7P.js} +1 -1
- package/dist/chunk-ER6KQCVJ.js +18 -0
- package/dist/{chunk-2EGCI2VW.js → chunk-GFTWZSOW.js} +10 -10
- package/dist/{chunk-AU346ENF.js → chunk-HUBRTT2C.js} +4 -4
- package/dist/{chunk-QPWFVE3X.js → chunk-JB4R5IFJ.js} +2 -2
- package/dist/{chunk-W2AEL4BJ.js → chunk-LHKWWEQZ.js} +1 -1
- package/dist/{chunk-G63KT5I5.js → chunk-M24H6JQW.js} +1 -1
- package/dist/{chunk-XL2XWWU7.js → chunk-M3RYZRMH.js} +3 -3
- package/dist/{chunk-RQJMN5TJ.js → chunk-ONUACB5C.js} +2 -2
- package/dist/{chunk-JHTJPQSL.js → chunk-PILHDNHX.js} +4 -4
- package/dist/{chunk-HJYBWJAS.js → chunk-PJI5D2XT.js} +2 -2
- package/dist/{chunk-LJG6EMLJ.js → chunk-PNIFQX73.js} +1 -1
- package/dist/{chunk-FR7HWWCF.js → chunk-QKEOAQ2W.js} +1 -1
- package/dist/{chunk-5DNCFOXA.js → chunk-QQXNS4G2.js} +1 -1
- package/dist/{chunk-XHHTMFCX.js → chunk-RLAKWXDK.js} +1 -1
- package/dist/{chunk-2N67C6IV.js → chunk-T4S2DVOA.js} +5 -5
- package/dist/{chunk-GFYEMCPP.js → chunk-T4TT76GG.js} +1 -1
- package/dist/{chunk-BKADJZ7F.js → chunk-UAJYWZ2F.js} +3 -3
- package/dist/{chunk-DISTLL2Q.js → chunk-UHFU3CBN.js} +1 -1
- package/dist/{chunk-E6CO3MJE.js → chunk-V7Q5OKN7.js} +12 -1
- package/dist/{chunk-2WMZPJVE.js → chunk-VJGQEASF.js} +4 -4
- package/dist/{chunk-NSPRS7WK.js → chunk-X37GC7DM.js} +1 -1
- package/dist/{chunk-3RIFVLPO.js → chunk-X62C7V4B.js} +12 -12
- package/dist/{chunk-DY7Y7UT4.js → chunk-XMPE5TT4.js} +2 -2
- package/dist/{chunk-T5REPMNL.js → chunk-Z4GLZTDF.js} +2 -2
- package/dist/{claude-launcher-NTYHJUJM.js → claude-launcher-B55NWRH5.js} +5 -5
- package/dist/{cli-K6XGL4EZ.js → cli-5YT5EL2R.js} +146 -103
- package/dist/{command-7FAIIQGF.js → command-DYLRDCGC.js} +19 -19
- package/dist/{config-QXMUWJAQ.js → config-RRY7DIJG.js} +5 -5
- package/dist/{connect-cmd-P5GASVAF.js → connect-cmd-7C6W2SEP.js} +8 -8
- package/dist/{container-start-CVP3NTDG.js → container-start-F7PBMTAL.js} +20 -20
- package/dist/{context-cleanup-BX5Y3AFL.js → context-cleanup-EKTPKJJT.js} +3 -3
- package/dist/{context-show-cmd-3EPJ7CJC.js → context-show-cmd-I5JC7EFH.js} +9 -9
- package/dist/{control-plane-launch-A3YCHQNQ.js → control-plane-launch-NOKM6CBH.js} +6 -6
- package/dist/{dist-ACTRIBVW.js → dist-LLYL5UCV.js} +2 -2
- package/dist/{docker-ANW76BNW.js → docker-W7WWFZ3Q.js} +3 -3
- package/dist/{doctor-GJAHI3KU.js → doctor-LYOFFCQS.js} +20 -20
- package/dist/{emit-artifact-cmd-FDPJKGOO.js → emit-artifact-cmd-RO34UG7A.js} +13 -13
- package/dist/{emit-cmd-OX6LYS6I.js → emit-cmd-GY7Z26DY.js} +6 -6
- package/dist/{emit-criteria-cmd-3SVGCMJN.js → emit-criteria-cmd-Y36QCPAM.js} +10 -10
- package/dist/{emit-demo-cmd-6DF2QIAS.js → emit-demo-cmd-YMWVF5U4.js} +104 -34
- package/dist/{emit-finding-cmd-TLJWBGQE.js → emit-finding-cmd-MH3ZWUNV.js} +10 -10
- package/dist/{emit-result-cmd-CJNVDV5V.js → emit-result-cmd-ZARZZZ3Y.js} +12 -12
- package/dist/{emit-step-cmd-347CZO7Y.js → emit-step-cmd-A6HQJDBY.js} +10 -10
- package/dist/{findings-cmd-LHTYEHYB.js → findings-cmd-OLLBWJXJ.js} +10 -10
- package/dist/{fleet-readiness-KGR5AC2J.js → fleet-readiness-VB5QSFSY.js} +8 -8
- package/dist/{governing-source-JZQZUTMA.js → governing-source-ZX7VXTOA.js} +13 -13
- package/dist/{hook-fast-6WDMNZG5.js → hook-fast-4RYMBFPO.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/{init-ZQ5B3TJZ.js → init-BQCGL7UD.js} +19 -19
- package/dist/{init-XDOKWBL2.js → init-NCCMB2UE.js} +19 -19
- package/dist/{init-cmd-YMGW2WXJ.js → init-cmd-OW2L5HTI.js} +18 -18
- package/dist/{init-update-X7YLZL6H.js → init-update-7LUMOECE.js} +17 -17
- package/dist/{install-QPA7DQHO.js → install-5BQ3TFBT.js} +6 -6
- package/dist/{layout-JGF7MJHG.js → layout-RGVPCX7Y.js} +4 -4
- package/dist/{login-browser-3RIZQJY2.js → login-browser-BC2Z5QKG.js} +2 -2
- package/dist/{loop-UJL2UCUM.js → loop-BU634Y6A.js} +28 -28
- package/dist/{machine-2YPAFFCH.js → machine-RI4KHRYP.js} +19 -19
- package/dist/{machine-skills-AGERT2HN.js → machine-skills-CS5HAQHF.js} +4 -4
- package/dist/{runner-env-HOUXKWIJ.js → runner-env-QSLLOFNF.js} +7 -7
- package/dist/{runner-identity-KPUVTRLS.js → runner-identity-DQAXW4DL.js} +3 -3
- package/dist/{runner-key-FUJD66R6.js → runner-key-UWL475RU.js} +19 -19
- package/dist/{session-sender-7B3ZDWMC.js → session-sender-OGDUJ2KK.js} +13 -13
- package/dist/{spec-cmd-Q26NE22H.js → spec-cmd-WB4UOFPG.js} +11 -11
- package/dist/{starter-pack-UBEC5LTE.js → starter-pack-XHATFTO3.js} +5 -5
- package/dist/{status-2KNTILGQ.js → status-GFZFND6L.js} +24 -24
- package/dist/{sync-cmd-SSUYTMLJ.js → sync-cmd-GPZD2C45.js} +12 -12
- package/dist/{watch-L5NCO46E.js → watch-5I3GURXY.js} +12 -12
- package/dist/{work-claim-cmd-NJNOQFQE.js → work-claim-cmd-J52UVCAF.js} +15 -15
- package/dist/{work-cmd-W7WEHMQJ.js → work-cmd-PQZ5RYDB.js} +10 -10
- package/dist/{work-comment-cmd-SSUOGVDD.js → work-comment-cmd-WI6B3IOS.js} +10 -10
- package/dist/work-demo-cmd-3NDGSQKJ.js +62 -0
- package/dist/{work-logs-cmd-WPMWMDX6.js → work-logs-cmd-QCYZ5X5A.js} +10 -10
- package/dist/{work-open-pr-cmd-NO5YLOXD.js → work-open-pr-cmd-BRDF3ZKL.js} +11 -11
- package/dist/{work-pr-cmd-37B5KECR.js → work-pr-cmd-WY2TBAKW.js} +10 -10
- package/dist/work-proofs-cmd-R25HT6WP.js +111 -0
- package/dist/{work-stop-cmd-3BJLXUJ3.js → work-stop-cmd-C52JPZ4E.js} +10 -10
- package/package.json +1 -1
- package/skill-pack/maintained/outerlayer-amend/SKILL.md +36 -34
- package/skill-pack/maintained/outerlayer-amend/scripts/rerecord-demo.mjs +187 -0
- package/skill-pack/maintained/outerlayer-build/SKILL.md +7 -2
- package/skill-pack/maintained/outerlayer-build/references/agent-briefs.md +11 -2
- package/skill-pack/maintained/outerlayer-build/references/evidence.md +8 -0
- package/skill-pack/maintained/outerlayer-build/references/release.md +3 -2
- package/skill-pack/maintained/outerlayer-evidence/SKILL.md +6 -4
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { WorkCommandError, resolveCredentials, getJson } from './chunk-5FSVA3CO.js';
|
|
4
|
+
import './chunk-IP7BIBXJ.js';
|
|
5
|
+
import './chunk-CCOU4NXE.js';
|
|
6
|
+
import './chunk-VJGQEASF.js';
|
|
7
|
+
import { sanitizeTerminal } from './chunk-E4F7YE7P.js';
|
|
8
|
+
import { resolveRepoIdentity } from './chunk-QQXNS4G2.js';
|
|
9
|
+
import './chunk-PJI5D2XT.js';
|
|
10
|
+
import './chunk-X37GC7DM.js';
|
|
11
|
+
import './chunk-G2JWZDFA.js';
|
|
12
|
+
import { C } from './chunk-ANPNT6PM.js';
|
|
13
|
+
import { defaultFetch } from './chunk-6RKD7YZ6.js';
|
|
14
|
+
import './chunk-PJDQPF5N.js';
|
|
15
|
+
import './chunk-SOI6K5DY.js';
|
|
16
|
+
import './chunk-PA6CK6WQ.js';
|
|
17
|
+
import './chunk-BALIE3BA.js';
|
|
18
|
+
import './chunk-BJ3KTMDH.js';
|
|
19
|
+
import './chunk-M24H6JQW.js';
|
|
20
|
+
import './chunk-5BHIBJDA.js';
|
|
21
|
+
import './chunk-CDDED64E.js';
|
|
22
|
+
import './chunk-RA3O54FC.js';
|
|
23
|
+
import { parseWorkItemEnv } from './chunk-T2Q7ISPE.js';
|
|
24
|
+
import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
|
|
25
|
+
import './chunk-CRB3POP4.js';
|
|
26
|
+
import './chunk-PNIFQX73.js';
|
|
27
|
+
import './chunk-V7Q5OKN7.js';
|
|
28
|
+
import './chunk-VNVZDWO3.js';
|
|
29
|
+
import './chunk-HACGUL52.js';
|
|
30
|
+
import './chunk-MESOOLF4.js';
|
|
31
|
+
import './chunk-Z5GEFMRW.js';
|
|
32
|
+
|
|
33
|
+
createRequire(import.meta.url);
|
|
34
|
+
|
|
35
|
+
// src/work-proofs-cmd.ts
|
|
36
|
+
function classifyProofs(data, checkoutHead) {
|
|
37
|
+
const heads = /* @__PURE__ */ new Set();
|
|
38
|
+
if (checkoutHead !== void 0 && checkoutHead !== "") heads.add(checkoutHead.toLowerCase());
|
|
39
|
+
data.pullRequests?.forEach((pr) => {
|
|
40
|
+
if ((pr.state === "open" || pr.state === "merged") && typeof pr.headSha === "string" && pr.headSha !== "") {
|
|
41
|
+
heads.add(pr.headSha.toLowerCase());
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const entries = [];
|
|
45
|
+
for (const criterion of data.criteria?.items ?? []) {
|
|
46
|
+
if (criterion.proof === null || criterion.proof === "test") continue;
|
|
47
|
+
const newest = criterion.newestProof;
|
|
48
|
+
if (newest === null || newest === void 0) {
|
|
49
|
+
entries.push({ id: criterion.id, proof: criterion.proof, state: "missing", commit: null });
|
|
50
|
+
} else if (newest.commitSha !== null && heads.has(newest.commitSha.toLowerCase())) {
|
|
51
|
+
entries.push({ id: criterion.id, proof: criterion.proof, state: "current", commit: newest.commitSha });
|
|
52
|
+
} else {
|
|
53
|
+
entries.push({ id: criterion.id, proof: criterion.proof, state: "out-of-date", commit: newest.commitSha });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
var STATE_LABEL = { current: "current", "out-of-date": "out of date", missing: "missing" };
|
|
59
|
+
function render(entries, json) {
|
|
60
|
+
if (json) return JSON.stringify(entries);
|
|
61
|
+
if (entries.length === 0) return "No criterion declares a proof other than test.";
|
|
62
|
+
return entries.map((e) => `${sanitizeTerminal(e.id)} ${sanitizeTerminal(e.proof)} ${STATE_LABEL[e.state]} ${sanitizeTerminal(e.commit ?? "no commit")}`).join("\n");
|
|
63
|
+
}
|
|
64
|
+
async function runWorkProofs(opts) {
|
|
65
|
+
const env = opts.env ?? process.env;
|
|
66
|
+
let item;
|
|
67
|
+
if (opts.item !== void 0) {
|
|
68
|
+
item = parseTrackerNumber(opts.item);
|
|
69
|
+
if (item === void 0) throw new WorkCommandError(trackerNumberError("--item", opts.item));
|
|
70
|
+
} else if (env.OUTERLAYER_WORK !== void 0) {
|
|
71
|
+
const parsed = parseWorkItemEnv(env.OUTERLAYER_WORK);
|
|
72
|
+
if (!("number" in parsed)) throw new WorkCommandError(parsed.reason);
|
|
73
|
+
item = parsed.number;
|
|
74
|
+
}
|
|
75
|
+
if (item === void 0) {
|
|
76
|
+
throw new WorkCommandError("no work item. Pass --item <number>, or set OUTERLAYER_WORK to the item's number.");
|
|
77
|
+
}
|
|
78
|
+
const creds = resolveCredentials(opts);
|
|
79
|
+
const { data } = await getJson(creds.url, `/v1/work-items/${item}`, creds.apiKey, creds.appId, opts.fetchImpl ?? defaultFetch);
|
|
80
|
+
const body = data;
|
|
81
|
+
const items = body.criteria?.items ?? [];
|
|
82
|
+
if (items.some((c) => c.proof !== null && c.proof !== "test" && !("newestProof" in c))) {
|
|
83
|
+
throw new WorkCommandError(
|
|
84
|
+
"the gateway does not report each criterion's newest proof. Update the gateway, then run this again."
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const checkoutHead = opts.checkoutHead ?? resolveRepoIdentity(opts.cwd ?? process.cwd()).commitSha;
|
|
88
|
+
if (!checkoutHead) {
|
|
89
|
+
throw new WorkCommandError("cannot read the checkout's HEAD. Run this inside the git checkout, or pass the head explicitly.");
|
|
90
|
+
}
|
|
91
|
+
const entries = classifyProofs(body, checkoutHead);
|
|
92
|
+
const output = render(entries, opts.json === true);
|
|
93
|
+
if (!opts.quiet) process.stdout.write(output + "\n");
|
|
94
|
+
return { entries, output, exitCode: entries.every((e) => e.state === "current") ? 0 : 1 };
|
|
95
|
+
}
|
|
96
|
+
async function runWorkProofsCommand(opts) {
|
|
97
|
+
try {
|
|
98
|
+
const result = await runWorkProofs(opts);
|
|
99
|
+
process.exitCode = result.exitCode;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (err instanceof WorkCommandError) {
|
|
102
|
+
process.stderr.write(`${C.red}\u2717${C.reset} ${err.message}
|
|
103
|
+
`);
|
|
104
|
+
process.exitCode = 2;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { classifyProofs, runWorkProofs, runWorkProofsCommand };
|
|
@@ -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-5FSVA3CO.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-CCOU4NXE.js';
|
|
6
|
+
import './chunk-VJGQEASF.js';
|
|
7
|
+
import './chunk-E4F7YE7P.js';
|
|
8
|
+
import './chunk-QQXNS4G2.js';
|
|
9
|
+
import './chunk-PJI5D2XT.js';
|
|
10
|
+
import './chunk-X37GC7DM.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-M24H6JQW.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-PNIFQX73.js';
|
|
26
|
+
import './chunk-V7Q5OKN7.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-next.
|
|
3
|
+
"version": "0.33.0-next.20261023030517",
|
|
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.",
|
|
@@ -6,7 +6,7 @@ description: >
|
|
|
6
6
|
reply on the thread with a replacement artifact or a sentence, and push. Also resolve a
|
|
7
7
|
merge conflict with the base branch. Use when a work item has threads waiting on an
|
|
8
8
|
agent or a conflicting pull request, or when asked to "answer the review" or "fix what
|
|
9
|
-
the reviewer failed".
|
|
9
|
+
the reviewer failed".
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
Run the CLI as `outerlayer`. If that command is not on PATH, run `npx -y @outerlayer/cli` with the same arguments. Every `outerlayer ...` command below follows this rule. Write the full command each time. Do not put it in a shell variable: zsh runs `$VAR args` as one command name, not as a command with arguments.
|
|
@@ -22,7 +22,7 @@ or fails a thread. A session can only reply.
|
|
|
22
22
|
A session never records a pass or fail on an artifact.
|
|
23
23
|
|
|
24
24
|
`OUTERLAYER_WORK` is the work item's number. Check out the pull request's
|
|
25
|
-
branch first
|
|
25
|
+
branch first, unless the working tree is on it.
|
|
26
26
|
|
|
27
27
|
## Arguments
|
|
28
28
|
|
|
@@ -31,17 +31,16 @@ branch first if the working tree is not already on it.
|
|
|
31
31
|
|
|
32
32
|
## Builds and local sessions
|
|
33
33
|
|
|
34
|
-
A
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
A runner build sets `OUTERLAYER_JOB_DIR`; no other session does. A build's
|
|
35
|
+
GitHub token cannot comment on the issue or the pull request, and its pushes
|
|
36
|
+
go through the runner. A local session uses the person's own git remote and
|
|
37
|
+
`gh` login.
|
|
38
38
|
|
|
39
39
|
## Several repositories
|
|
40
40
|
|
|
41
|
-
`OUTERLAYER_REPOSITORIES` lists a build's repositories, each with
|
|
42
|
-
`branch` and
|
|
43
|
-
repository
|
|
44
|
-
change, to its own branch only.
|
|
41
|
+
`OUTERLAYER_REPOSITORIES` lists a build's repositories, each with `path`,
|
|
42
|
+
`branch` and `primary`. Fix each thread in the checkout of its pull request's
|
|
43
|
+
repository. Commit and push in each checkout you change, to its own branch only.
|
|
45
44
|
|
|
46
45
|
## Steps
|
|
47
46
|
|
|
@@ -59,7 +58,7 @@ change, to its own branch only.
|
|
|
59
58
|
**Hand off a conflict you cannot resolve.** If the resolution needs a
|
|
60
59
|
product decision, or the gate stays red after one fix, abort the merge.
|
|
61
60
|
Push nothing. Post on the general thread with `--ready`. Name the
|
|
62
|
-
conflicting files,
|
|
61
|
+
conflicting files, their base commits and the decision needed.
|
|
63
62
|
Tell the person to fail the general thread with their answer.
|
|
64
63
|
2. **Read the threads.**
|
|
65
64
|
`outerlayer work threads --item "$OUTERLAYER_WORK" --json` prints
|
|
@@ -69,26 +68,24 @@ change, to its own branch only.
|
|
|
69
68
|
`body`, `action` and `artifact`. Keep the threads whose `waitingOn` is
|
|
70
69
|
`agent`.
|
|
71
70
|
If none is waiting and step 1 resolved no conflict, say so and stop. Take
|
|
72
|
-
the pull request (`repository`, `prNumber`) from
|
|
73
|
-
`pullRequests`.
|
|
71
|
+
the pull request (`repository`, `prNumber`) from step 1's `pullRequests`.
|
|
74
72
|
3. **Re-orient.** The status JSON's `issue.body` is OuterLayer's copy of the
|
|
75
73
|
issue. It is the spec: find each thread's criterion there. Read the pull
|
|
76
|
-
request
|
|
77
|
-
comment
|
|
74
|
+
request and its evidence comment through the git host. Read every person's
|
|
75
|
+
comment since the last agent comment, literally.
|
|
78
76
|
Then read `checks` in the status JSON. A failing check lists its check
|
|
79
|
-
items under `items`,
|
|
80
|
-
|
|
81
|
-
each failing check item by its reason.
|
|
77
|
+
items under `items`, each with a `result` and a `reason`. When a person asks you to fix failing
|
|
78
|
+
checks or criteria, fix each failing check item by its reason.
|
|
82
79
|
4. **Decide each thread.** Reply on every criterion thread waiting on an
|
|
83
80
|
agent, with `--criterion`. Read each note and pick one:
|
|
84
|
-
- **Answer it.** A question gets an answer.
|
|
85
|
-
|
|
81
|
+
- **Answer it.** A question gets an answer. Change nothing. Say which
|
|
82
|
+
file or test you looked at.
|
|
86
83
|
- **Fix it.** A requested change gets a fix. Change the code, tests, docs or
|
|
87
|
-
evidence so the criterion is met as written, and attach new proof when
|
|
88
|
-
|
|
84
|
+
evidence so the criterion is met as written, and attach new proof when it
|
|
85
|
+
declares one.
|
|
89
86
|
- **The spec is wrong.** The criterion cannot be met as written, for
|
|
90
|
-
example
|
|
91
|
-
|
|
87
|
+
example one that names a path a repository rule forbids. Never rewrite it:
|
|
88
|
+
hand it off (step 7) with the wording change.
|
|
92
89
|
- **Cannot tell.** Hand an ambiguous comment off with `--ready`: quote it
|
|
93
90
|
and ask one question.
|
|
94
91
|
5. **Fix, prove, reply.** An answer needs no commit: reply on its criterion
|
|
@@ -107,13 +104,12 @@ change, to its own branch only.
|
|
|
107
104
|
Note the id it prints. Run `outerlayer sync`, so the attach can find the
|
|
108
105
|
artifact. Then reply, which supersedes the old proof:
|
|
109
106
|
`outerlayer work comment --item "$OUTERLAYER_WORK" --criterion <criterion> --body "<what changed>" --artifact <uuid> --attach`.
|
|
110
|
-
A replacement
|
|
111
|
-
|
|
107
|
+
A replacement with the current proof's bytes is flagged `unchanged`, and
|
|
108
|
+
the thread still waits on you.
|
|
112
109
|
- **A check the factory host records**, such as `acceptance-criteria`, is
|
|
113
110
|
judged again after this session ends. Name each failing check item and
|
|
114
111
|
your fix. Never claim the check passes.
|
|
115
|
-
- **Otherwise
|
|
116
|
-
reply with a sentence naming what changed and the commit:
|
|
112
|
+
- **Otherwise**, reply with a sentence naming what changed and the commit:
|
|
117
113
|
`outerlayer work comment --item "$OUTERLAYER_WORK" [--criterion <criterion>] --body "<what changed>" --ready`.
|
|
118
114
|
Omit `--criterion` on the general thread.
|
|
119
115
|
6. **Watch CI.** Skip this step when you pushed nothing. Run
|
|
@@ -128,14 +124,20 @@ change, to its own branch only.
|
|
|
128
124
|
sentence of fact, then the decision a person must make. For a spec problem,
|
|
129
125
|
quote the criterion and give the replacement wording. `--ready` hands the
|
|
130
126
|
thread back to a person.
|
|
131
|
-
8. **Check nothing is left waiting.**
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
9. **
|
|
127
|
+
8. **Check nothing is left waiting.** Reread threads. A thread still waiting
|
|
128
|
+
on an agent has a reply that did not land or was flagged `unchanged`. Fix
|
|
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.
|
|
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
|
-
with the reason),
|
|
138
|
-
|
|
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
|
|
139
|
+
out of date.
|
|
140
|
+
Post the lines once on the general thread:
|
|
139
141
|
`outerlayer work comment --item "$OUTERLAYER_WORK" --body "<the lines>"`.
|
|
140
142
|
A plain comment from an agent leaves the general thread waiting on a person.
|
|
141
143
|
A local session also posts them on the pull request. End with the same lines.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Records an item's demo again at the pull request's new head, after an
|
|
3
|
+
// amend pushed. It reads the item's newest demo record, and records again
|
|
4
|
+
// only a demo that was recorded at an earlier commit and names the test that
|
|
5
|
+
// recorded it. Needs only Node.
|
|
6
|
+
//
|
|
7
|
+
// node rerecord-demo.mjs --item <number> --pr <number> [--repository <owner/name>] [--project <playwright project>]
|
|
8
|
+
//
|
|
9
|
+
// Run it after the amend's last push, with the app running as the
|
|
10
|
+
// repository's instructions say. It prints one line for the amend's report:
|
|
11
|
+
// recorded again, current, or why not. It posts one comment on the item's
|
|
12
|
+
// general thread when the demo is out of date and cannot be recorded again,
|
|
13
|
+
// when a chapter failed its checks, or when the recording failed. It posts
|
|
14
|
+
// nothing when the item has no demo, or the demo is current.
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { basename, dirname, isAbsolute, join, relative } from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
|
|
21
|
+
const USAGE = "usage: rerecord-demo.mjs --item <number> --pr <number> [--repository <owner/name>] [--project <playwright project>]";
|
|
22
|
+
const CONFIG_NAMES = ["ts", "js", "mjs", "cjs", "mts", "cts"].map((extension) => `playwright.config.${extension}`);
|
|
23
|
+
|
|
24
|
+
/** The `spawnSync` that runs a program in `cwd`; tests pass a stand-in. */
|
|
25
|
+
function runProgram(command, args, options) {
|
|
26
|
+
return spawnSync(command, args, { encoding: "utf8", maxBuffer: 256 * 1024 * 1024, ...options });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `owner/name` of a repository in any spelling the factory stores, lower-case. */
|
|
30
|
+
function repositoryName(repository) {
|
|
31
|
+
return repository
|
|
32
|
+
.replace(/\.git$/, "")
|
|
33
|
+
.split("/")
|
|
34
|
+
.filter((part) => part !== "")
|
|
35
|
+
.slice(-2)
|
|
36
|
+
.join("/")
|
|
37
|
+
.toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A test path inside the repository, or `null` when the path is absolute or leaves it. */
|
|
41
|
+
function insidePath(test) {
|
|
42
|
+
if (typeof test !== "string" || test === "" || isAbsolute(test) || test.split("/").includes("..")) return null;
|
|
43
|
+
return test;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The nearest folder from the test's own up to the root that holds a Playwright config, else the root. */
|
|
47
|
+
function configFolder(root, testFile) {
|
|
48
|
+
let folder = dirname(testFile);
|
|
49
|
+
while (folder.startsWith(root)) {
|
|
50
|
+
if (CONFIG_NAMES.some((name) => existsSync(join(folder, name)))) return folder;
|
|
51
|
+
if (folder === root) break;
|
|
52
|
+
folder = dirname(folder);
|
|
53
|
+
}
|
|
54
|
+
return root;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Every `demo/manifest.json` under `folder`. */
|
|
58
|
+
function findManifests(folder) {
|
|
59
|
+
const found = [];
|
|
60
|
+
for (const entry of readdirSync(folder, { withFileTypes: true })) {
|
|
61
|
+
const path = join(folder, entry.name);
|
|
62
|
+
if (entry.isDirectory()) found.push(...findManifests(path));
|
|
63
|
+
else if (entry.name === "manifest.json" && basename(folder) === "demo") found.push(path);
|
|
64
|
+
}
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The last line of a command's output that parses as a JSON object, or `null`. */
|
|
69
|
+
function lastJson(text) {
|
|
70
|
+
for (const line of String(text).split("\n").reverse()) {
|
|
71
|
+
try {
|
|
72
|
+
const value = JSON.parse(line);
|
|
73
|
+
if (value !== null && typeof value === "object") return value;
|
|
74
|
+
} catch {
|
|
75
|
+
// not the JSON line
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Records the demo again when it is out of date and names its test.
|
|
83
|
+
* `spawn(command, args, { cwd })` returns `{ status, stdout, stderr }`.
|
|
84
|
+
* Returns the outcome and the line for the amend's report.
|
|
85
|
+
*/
|
|
86
|
+
export function rerecordDemo({ item, pr, repository, project, cwd, spawn = runProgram }) {
|
|
87
|
+
const cli = (args) => {
|
|
88
|
+
const result = spawn("outerlayer", args, { cwd });
|
|
89
|
+
// Without `outerlayer` on PATH, the CLI runs through npx.
|
|
90
|
+
return result.error?.code === "ENOENT" ? spawn("npx", ["-y", "@outerlayer/cli", ...args], { cwd }) : result;
|
|
91
|
+
};
|
|
92
|
+
// The line for the amend's report says so when a comment did not post, so nobody waits for it.
|
|
93
|
+
const comment = (body) => (cli(["work", "comment", "--item", String(item), "--body", body]).status === 0 ? "" : " The comment on the item's thread was not posted.");
|
|
94
|
+
|
|
95
|
+
const read = cli(["work", "demo", "--item", String(item), "--json"]);
|
|
96
|
+
const answer = read.status === 0 ? lastJson(read.stdout) : null;
|
|
97
|
+
if (answer === null) {
|
|
98
|
+
const detail = String(read.stderr ?? "").trim();
|
|
99
|
+
return { outcome: "error", line: `Demo: could not read the item's demo${detail === "" ? "" : ` (${detail})`}.` };
|
|
100
|
+
}
|
|
101
|
+
const demo = answer.demo ?? null;
|
|
102
|
+
if (demo === null) return { outcome: "none", line: "Demo: the item has no demo to record again." };
|
|
103
|
+
if (repository !== undefined && repositoryName(String(demo.repository ?? "")) !== repositoryName(repository)) {
|
|
104
|
+
return { outcome: "other-repository", line: `Demo: the item's demo is in ${demo.repository}, not ${repository}.` };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const head = String(spawn("git", ["rev-parse", "HEAD"], { cwd }).stdout ?? "").trim();
|
|
108
|
+
const short = (sha) => (sha === "" ? "an unknown commit" : sha.slice(0, 7));
|
|
109
|
+
if (demo.commitSha === head) return { outcome: "current", line: `Demo: current, recorded at ${short(head)}.` };
|
|
110
|
+
|
|
111
|
+
const byHand = `outerlayer emit demo <manifest> --item ${item} --pr ${pr} --replaces ${demo.artifactId}`;
|
|
112
|
+
const stale = `The demo is out of date: it was recorded at ${short(String(demo.commitSha ?? ""))}, and the branch is now at ${short(head)}.`;
|
|
113
|
+
const root = String(spawn("git", ["rev-parse", "--show-toplevel"], { cwd }).stdout ?? "").trim() || cwd;
|
|
114
|
+
if (demo.record === null) {
|
|
115
|
+
const why = "Its record cannot be read, so the amend cannot tell which test to run.";
|
|
116
|
+
const unposted = comment(`${stale} ${why} To record it again, run the demo test, then run \`${byHand}\`.`);
|
|
117
|
+
return { outcome: "not-recorded", line: `Demo: out of date at ${short(String(demo.commitSha ?? ""))}; ${why.charAt(0).toLowerCase()}${why.slice(1)}${unposted}` };
|
|
118
|
+
}
|
|
119
|
+
const test = insidePath(demo.record?.test);
|
|
120
|
+
const testFile = test === null ? null : join(root, test);
|
|
121
|
+
|
|
122
|
+
if (test === null || !existsSync(testFile)) {
|
|
123
|
+
const why =
|
|
124
|
+
demo.record?.test === undefined || demo.record?.test === null
|
|
125
|
+
? "Its record names no test, so the amend cannot record it again."
|
|
126
|
+
: `The test ${demo.record.test} is not in the checkout, so the amend cannot record it again.`;
|
|
127
|
+
const unposted = comment(`${stale} ${why} To record it again, run the demo test, then run \`${byHand}\`.`);
|
|
128
|
+
return { outcome: "not-recorded", line: `Demo: out of date at ${short(String(demo.commitSha ?? ""))}; ${why.charAt(0).toLowerCase()}${why.slice(1)}${unposted}` };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const output = mkdtempSync(join(tmpdir(), "outerlayer-rerecord-"));
|
|
132
|
+
try {
|
|
133
|
+
const folder = configFolder(root, testFile);
|
|
134
|
+
// Playwright reads the filter as a regular expression, so a path such as app/[id]/x.spec.ts needs its characters escaped.
|
|
135
|
+
const filter = relative(folder, testFile).split("\\").join("/").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
136
|
+
const run = spawn("npx", ["playwright", "test", filter, "--output", output, ...(project === undefined ? [] : ["--project", project])], { cwd: folder });
|
|
137
|
+
// A failed check still writes the manifest, and emit demo reports the failed chapter. Only a missing manifest stops here.
|
|
138
|
+
const manifests = existsSync(output) ? findManifests(output) : [];
|
|
139
|
+
if (manifests.length !== 1) {
|
|
140
|
+
const why =
|
|
141
|
+
manifests.length === 0
|
|
142
|
+
? `Running ${test} left no demo manifest (exit ${run.status}).`
|
|
143
|
+
: `Running ${test} left ${manifests.length} demo manifests, so the amend cannot tell which to store. Pass --project <name> so the test runs once.`;
|
|
144
|
+
const unposted = comment(`${stale} ${why} To record it again, run the demo test, then run \`${byHand}\`.`);
|
|
145
|
+
return { outcome: "error", line: `Demo: out of date at ${short(String(demo.commitSha ?? ""))}; ${why}${unposted}` };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const emit = cli(["emit", "demo", manifests[0], "--item", String(item), "--pr", String(pr), "--replaces", demo.artifactId, "--json"]);
|
|
149
|
+
const result = emit.status === 0 || emit.status === 1 ? lastJson(emit.stdout) : null;
|
|
150
|
+
if (result === null) {
|
|
151
|
+
const why = `Storing the new demo failed: ${String(emit.stderr ?? "").trim().split("\n")[0] || `exit ${emit.status}`}.`;
|
|
152
|
+
const unposted = comment(`${stale} ${why} To record it again, run \`${byHand}\` on the manifest of a new run.`);
|
|
153
|
+
return { outcome: "error", line: `Demo: out of date at ${short(String(demo.commitSha ?? ""))}; ${why}${unposted}` };
|
|
154
|
+
}
|
|
155
|
+
const synced = cli(["sync"]);
|
|
156
|
+
const unsynced = synced.status === 0 ? "" : ` The sync failed (exit ${synced.status}), so the new demo is stored but the session record is not uploaded. Run \`outerlayer sync\`.`;
|
|
157
|
+
const failures = Array.isArray(result.failures) ? result.failures : [];
|
|
158
|
+
if (failures.length > 0) {
|
|
159
|
+
const unposted = comment(`The demo was recorded again at ${short(head)}, but a chapter failed its checks. A failed chapter is stored but not bound to its criterion:\n${failures.map((line) => `- ${line}`).join("\n")}`);
|
|
160
|
+
return { outcome: "failed-chapters", line: `Demo: recorded again at ${short(head)}, but ${failures.join("; ")}.${unsynced}${unposted}` };
|
|
161
|
+
}
|
|
162
|
+
if (unsynced !== "") return { outcome: "sync-failed", line: `Demo: recorded again at ${short(head)}.${unsynced}` };
|
|
163
|
+
return { outcome: "recorded", line: `Demo: recorded again at ${short(head)}.` };
|
|
164
|
+
} finally {
|
|
165
|
+
rmSync(output, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The value after `flag`, or `undefined`. */
|
|
170
|
+
function flagValue(argv, flag) {
|
|
171
|
+
const at = argv.indexOf(flag);
|
|
172
|
+
return at === -1 ? undefined : argv[at + 1];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function main(argv) {
|
|
176
|
+
const item = Number(flagValue(argv, "--item"));
|
|
177
|
+
const pr = Number(flagValue(argv, "--pr"));
|
|
178
|
+
if (!Number.isInteger(item) || item <= 0 || !Number.isInteger(pr) || pr <= 0) {
|
|
179
|
+
process.stderr.write(`${USAGE}\n`);
|
|
180
|
+
return 2;
|
|
181
|
+
}
|
|
182
|
+
const { line } = rerecordDemo({ item, pr, repository: flagValue(argv, "--repository"), project: flagValue(argv, "--project"), cwd: process.cwd() });
|
|
183
|
+
process.stdout.write(`${line}\n`);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) process.exitCode = main(process.argv.slice(2));
|
|
@@ -334,7 +334,8 @@ Run them in order.
|
|
|
334
334
|
6. **Evidence.** Run `outerlayer emit step "Evidence" --item "$OUTERLAYER_WORK"`,
|
|
335
335
|
then apply the team's changes for this phase. Read [references/evidence.md](references/evidence.md)
|
|
336
336
|
when the pull request opens, and run it. Capture the proof each criterion
|
|
337
|
-
declares, emit it.
|
|
337
|
+
declares, emit it. Then check the proofs with `outerlayer work proofs`, as
|
|
338
|
+
that file says.
|
|
338
339
|
|
|
339
340
|
Optional steps a team may turn off: the manual test and screenshots in the
|
|
340
341
|
Evidence phase, for a repository with no user interface.
|
|
@@ -342,7 +343,11 @@ Evidence phase, for a repository with no user interface.
|
|
|
342
343
|
## Ending the run
|
|
343
344
|
|
|
344
345
|
Run this last, after the pull request is open and the evidence is emitted, and
|
|
345
|
-
run no other tool call after it
|
|
346
|
+
run no other tool call after it. Before it, run `outerlayer sync` and then
|
|
347
|
+
`outerlayer work proofs --item "$OUTERLAYER_WORK"`. End the run only when it
|
|
348
|
+
exits 0, or when `build-reports/evidence.md` names each proof it still lists
|
|
349
|
+
and the reason. Exit 2 means it could not decide: fix the reason it prints and
|
|
350
|
+
run it again:
|
|
346
351
|
|
|
347
352
|
outerlayer work release --item "$OUTERLAYER_WORK"
|
|
348
353
|
|
|
@@ -253,7 +253,14 @@ application's own paths. When a state cannot be reached that way, the criterion
|
|
|
253
253
|
is not evidenced, and `evidence.md` says why. Before emitting, look at each
|
|
254
254
|
capture: it shows the application's navigation and real rows.
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
Write the demo, when a criterion needs one, as a Playwright test that is part
|
|
257
|
+
of the change. Put it where the repository keeps its Playwright tests, in the
|
|
258
|
+
folder the instructions name, else in `e2e/demo/`. Name it `*.demo.spec.ts`.
|
|
259
|
+
Commit it in the pull request. The demo record names this test, so an amend
|
|
260
|
+
can run it again at a new head. If the repository's CI cannot start the app,
|
|
261
|
+
mark the file so that CI skips it, as `apps/e2e` does.
|
|
262
|
+
|
|
263
|
+
For each other capture, put the capture in a script under
|
|
257
264
|
`build-reports/evidence/` and run the script. A later fix can invalidate what
|
|
258
265
|
you captured, and regenerating it must be one command. The scripts go when the
|
|
259
266
|
run's report directory goes. The emitted artifacts outlive the run.
|
|
@@ -266,7 +273,9 @@ criteria's order. Run it, then run `outerlayer emit demo <manifest> --item
|
|
|
266
273
|
emit artifact ... --for <criterion id> --pr <number>`. Always pass `--pr`.
|
|
267
274
|
When the tree invalidates a video, record and emit the whole demo again: the
|
|
268
275
|
newer demo replaces the older one. Regenerate any other invalidated evidence
|
|
269
|
-
with `--replaces`. Then run `outerlayer sync
|
|
276
|
+
with `--replaces`. Then run `outerlayer sync`, and run `outerlayer work
|
|
277
|
+
proofs --item "$OUTERLAYER_WORK"`. For each proof it lists as out of date or
|
|
278
|
+
missing, capture and emit that criterion again, and run the command once more.
|
|
270
279
|
|
|
271
280
|
Write `build-reports/evidence.md` naming every criterion with its artifact, or
|
|
272
281
|
with the reason none could be produced. A criterion you could not evidence is
|
|
@@ -19,6 +19,14 @@ id. One that declares `proof: video` needs a video: its chapter of the
|
|
|
19
19
|
item's one demo. A screenshot never satisfies `proof: video`. A criterion with no proof form is proven by its
|
|
20
20
|
test, and needs no artifact.
|
|
21
21
|
|
|
22
|
+
After the evidence agent returns, and after every later commit, run
|
|
23
|
+
`outerlayer sync`, then `outerlayer work proofs --item "$OUTERLAYER_WORK"`. It
|
|
24
|
+
lists each declared proof as current, out of date or missing at the checkout's
|
|
25
|
+
head. For each criterion it lists as out of date or missing, run the evidence
|
|
26
|
+
step again for that criterion only, then run the command once more before the
|
|
27
|
+
run ends. A criterion still listed goes in `build-reports/evidence.md` with the
|
|
28
|
+
reason. The command leaves out criteria that declare a test proof.
|
|
29
|
+
|
|
22
30
|
When a commit lands on the branch after the evidence agent captured, decide for
|
|
23
31
|
each capture from that commit's diff. A capture is stale when the diff touches
|
|
24
32
|
a file the capture's subject runs through: the feature's own files, the shared
|
|
@@ -90,8 +90,9 @@ report. With several judges, run it on the merged report:
|
|
|
90
90
|
Then run `outerlayer sync`. List every unsatisfied criterion in the final
|
|
91
91
|
report. A criterion that declares a proof form such as a screenshot or video is
|
|
92
92
|
proven only by an artifact bound to it on the pull request. The judge cannot
|
|
93
|
-
see that
|
|
94
|
-
|
|
93
|
+
see that. Run `outerlayer work proofs --item "$OUTERLAYER_WORK"`, as the
|
|
94
|
+
evidence phase says, and emit again any proof it lists as out of date or
|
|
95
|
+
missing.
|
|
95
96
|
|
|
96
97
|
## The criteria verdict
|
|
97
98
|
|
|
@@ -16,7 +16,7 @@ spec asks for — a reviewer should see the change working, not a gallery.
|
|
|
16
16
|
|
|
17
17
|
## When to capture
|
|
18
18
|
|
|
19
|
-
Capture AFTER the state exists
|
|
19
|
+
Capture AFTER the state exists: run the app, the test, or the
|
|
20
20
|
flow first, and shoot the working result. A screenshot of code is not
|
|
21
21
|
evidence; a screenshot of the rendered page is. If the state takes setup
|
|
22
22
|
(seed data, a logged-in user), finish the setup, verify by eye, then capture.
|
|
@@ -24,14 +24,14 @@ evidence; a screenshot of the rendered page is. If the state takes setup
|
|
|
24
24
|
## Per-kind mechanics
|
|
25
25
|
|
|
26
26
|
Kind is inferred from the file's media type — name files honestly. Every
|
|
27
|
-
artifact shares one upload cap of 8 MiB
|
|
27
|
+
artifact shares one upload cap of 8 MiB:
|
|
28
28
|
|
|
29
29
|
- **screenshot** (`.png`, `.jpg`) — one focused window or region showing the
|
|
30
30
|
proven state. Crop noise; keep enough chrome (URL bar, test summary line)
|
|
31
31
|
to show it is real.
|
|
32
32
|
- **video** (`.webm`, `.mp4`) — a recording of the flow, start to outcome.
|
|
33
|
-
Record it as a demo chapter (see "Record a demo"). Video
|
|
34
|
-
|
|
33
|
+
Record it as a demo chapter (see "Record a demo"). Video most likely
|
|
34
|
+
hits the 8 MiB cap: trim dead time.
|
|
35
35
|
- **report** (`.html`, `.pdf`) — generated reports: coverage, benchmark,
|
|
36
36
|
audit output. Emit the file the tool produced, unedited.
|
|
37
37
|
- **log** (`.txt`, `.log`) — command output proving a run happened: test
|
|
@@ -99,6 +99,8 @@ for each criterion:
|
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
Commit the test, so an amend can run it again.
|
|
103
|
+
|
|
102
104
|
Install it with `npm install --save-dev @outerlayer/playwright`. Use the
|
|
103
105
|
recorded criterion id as the first argument, or `null` for an opening scene.
|
|
104
106
|
Keep each narration line short enough to say in the chapter's time. Put the
|