@outerlayer/cli 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +9 -4
- package/dist/{agent-setup-52LRZ6AY.js → agent-setup-QE6PSCEZ.js} +8 -8
- package/dist/build-info.json +1 -1
- package/dist/{builder-OF67QKDS.js → builder-UUS4ORDH.js} +3 -3
- package/dist/{check-RJOJU2WA.js → check-63IDCEEV.js} +27 -27
- package/dist/{chunk-XR3BJN33.js → chunk-22RMZBGW.js} +20 -1
- package/dist/{chunk-AHZQBGD2.js → chunk-2M63GG56.js} +1 -1
- package/dist/{chunk-27Y7AX2O.js → chunk-3D4OYFCD.js} +4 -4
- package/dist/{chunk-GGMRZPEA.js → chunk-3KTA5UU2.js} +1 -1
- package/dist/{chunk-USO2DKBD.js → chunk-4KSQKQXE.js} +8 -2
- package/dist/{chunk-C7N4TDHW.js → chunk-4QVZCVWT.js} +5 -5
- package/dist/{chunk-NPXQXYVA.js → chunk-6S3H3J6H.js} +2 -2
- package/dist/{chunk-DVT73QJO.js → chunk-C6IV5BV7.js} +10 -10
- package/dist/{chunk-JOXYKGUV.js → chunk-CNFVHDHG.js} +1 -1
- package/dist/{chunk-ANW7VHMH.js → chunk-DPKX26LT.js} +92 -16
- package/dist/{chunk-ZQUCTFXC.js → chunk-DXQ5MHCO.js} +1 -1
- package/dist/{chunk-GATMPAMK.js → chunk-EFZLAYPE.js} +2 -2
- package/dist/{chunk-SZOVKFWB.js → chunk-GI4XXD4I.js} +3 -3
- package/dist/{chunk-G5XY6CVG.js → chunk-HDTVTA6I.js} +2 -2
- package/dist/{chunk-325272HM.js → chunk-J5M7BSJQ.js} +11 -4
- package/dist/{chunk-3ZPYPFNM.js → chunk-JXYVA45C.js} +1 -1
- package/dist/{chunk-G5IEVZGI.js → chunk-LCEXHCHD.js} +1 -1
- package/dist/{chunk-GEBQXUFY.js → chunk-MJXXRX4M.js} +24 -10
- package/dist/{chunk-SKHN72JO.js → chunk-MZN7AQOB.js} +8 -8
- package/dist/{chunk-QEESCZ26.js → chunk-NX57IBNK.js} +1 -1
- package/dist/{chunk-6FH2AHJY.js → chunk-P4MM66C6.js} +30 -17
- package/dist/{chunk-IBB4D4KE.js → chunk-P6FX4D43.js} +1 -1
- package/dist/{chunk-A56PYPDF.js → chunk-R7OQTKWT.js} +2 -1
- package/dist/{chunk-2KLBKHT4.js → chunk-RLYZLUQ7.js} +1 -1
- package/dist/chunk-RMWLBUCJ.js +50 -0
- package/dist/{chunk-YEQL54G5.js → chunk-SFFHGX3G.js} +1 -1
- package/dist/{chunk-G4RXNF2L.js → chunk-SVEP7PVH.js} +12 -12
- package/dist/{chunk-VK25O2ZM.js → chunk-T5VPP6H7.js} +1 -1
- package/dist/{chunk-FO7KKPYG.js → chunk-TCIW27FM.js} +1 -1
- package/dist/{chunk-PDRYXERC.js → chunk-TOPDNXHZ.js} +5 -5
- package/dist/{chunk-5TGQHOUX.js → chunk-XJKL4BNU.js} +3 -3
- package/dist/{cli-JSSRGA6V.js → cli-WSBIAVZU.js} +116 -80
- package/dist/{command-3WYZ2ML5.js → command-N7BAB4XJ.js} +18 -18
- package/dist/{config-MKBX2ZQD.js → config-N3C6DFQA.js} +1 -1
- package/dist/{connect-cmd-A5NGODCJ.js → connect-cmd-FGYRLLXL.js} +5 -5
- package/dist/{context-adopt-BAHGXJZV.js → context-adopt-B3PWGFHL.js} +5 -5
- package/dist/{context-materialize-45OVRRK2.js → context-materialize-7S6NLLRO.js} +5 -5
- package/dist/{dist-EQDW7MZW.js → dist-LKDWQWZ7.js} +2 -2
- package/dist/{docker-VGWEJWI7.js → docker-BZ55BMBJ.js} +4 -4
- package/dist/{doctor-4FFXTQJ2.js → doctor-3H25Q7WT.js} +20 -20
- package/dist/{emit-artifact-cmd-QS7XNKBT.js → emit-artifact-cmd-IQCRXOH7.js} +8 -8
- package/dist/{emit-cmd-LXEIDKLT.js → emit-cmd-KDSM5VAO.js} +4 -4
- package/dist/{emit-criteria-cmd-XN6AKA53.js → emit-criteria-cmd-EYUXME26.js} +6 -6
- package/dist/{emit-finding-cmd-SQJSEHYS.js → emit-finding-cmd-P4RKBPBB.js} +6 -6
- package/dist/{emit-result-cmd-56RHPHOA.js → emit-result-cmd-6XYENU6H.js} +8 -8
- package/dist/{hook-fast-HLESEJAL.js → hook-fast-PXWKQPN6.js} +1 -1
- package/dist/{import-capture-cmd-NHMAFDM6.js → import-capture-cmd-2BKM5I2N.js} +3 -3
- package/dist/{import-ruler-cmd-UYB32YUT.js → import-ruler-cmd-IXR7YK47.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{init-UZKD4RY7.js → init-2LWVS4AH.js} +19 -19
- package/dist/{init-6AY5T3P6.js → init-ZL7ORPTF.js} +38 -28
- package/dist/{init-cmd-XGSX2BCC.js → init-cmd-JXOBCOIB.js} +14 -12
- package/dist/{install-VXXI7UR6.js → install-HDBIPHTX.js} +2 -2
- package/dist/{login-browser-7XGSV7MA.js → login-browser-QZCS3WW6.js} +2 -1
- package/dist/{loop-4NMI2USY.js → loop-XOAE6ZIY.js} +27 -27
- package/dist/{machine-4JX2D4TS.js → machine-G6PY2J2J.js} +15 -15
- package/dist/{mcp-install-cmd-XZARXNYT.js → mcp-install-cmd-VXVIH7PH.js} +1 -1
- package/dist/{runner-env-WAXTJXZK.js → runner-env-XTC53CHN.js} +3 -3
- package/dist/runner-key-TDTLNAKJ.js +154 -0
- package/dist/{session-sender-T7PN4YRC.js → session-sender-XVGZFWRK.js} +6 -6
- package/dist/{starter-pack-Z22VRFPF.js → starter-pack-B7JPGEDO.js} +4 -4
- package/dist/{status-OXBIRWJ7.js → status-MRTPG34B.js} +20 -20
- package/dist/{sync-cmd-EVXNR47N.js → sync-cmd-UBUZMMDN.js} +5 -5
- package/dist/{watch-7NSFOAL5.js → watch-STNPEWD6.js} +5 -5
- package/dist/{work-claim-cmd-KR7UR2AB.js → work-claim-cmd-CWGYEIJE.js} +10 -10
- package/dist/{work-cmd-JPM37MLF.js → work-cmd-X7NMJDFL.js} +7 -7
- package/dist/{work-comment-cmd-3DC6BC34.js → work-comment-cmd-FI2TMAYQ.js} +7 -7
- package/dist/{work-open-pr-cmd-XZQRJBNZ.js → work-open-pr-cmd-OVHJ5I5H.js} +8 -8
- package/dist/{work-pr-cmd-WHGZOP22.js → work-pr-cmd-Y5TDMQ4O.js} +7 -7
- package/package.json +1 -1
- package/skill-pack/template/build/SKILL.md +60 -33
- package/skill-pack/template/build/references/agent-briefs.md +4 -2
- package/skill-pack/template/build/references/release.md +1 -1
- package/skill-pack/template/build/references/review-loop.md +6 -2
- package/skill-pack/template/build/scripts/record-criteria.mjs +109 -143
- package/skill-pack/validators/code-review.yaml +4 -2
- package/dist/chunk-T6OIJQRT.js +0 -31
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Records the criteria the build read from the issue, or drafted for an issue
|
|
3
|
+
// that has none, and notes the outcome in the build's state file. The agent
|
|
4
|
+
// reads the issue and writes the criteria file; this script checks it, gives
|
|
5
|
+
// the criteria ids, runs `outerlayer emit criteria`, and handles its answer.
|
|
6
|
+
// Needs only Node.
|
|
5
7
|
//
|
|
6
|
-
// node record-criteria.mjs --item <number> --
|
|
8
|
+
// node record-criteria.mjs --item <number> --criteria-file <path> [--drafted]
|
|
7
9
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// the
|
|
10
|
+
// The criteria file is `{ "criteria": [{ "id"?, "text", "proof"? }] }`, in
|
|
11
|
+
// the order the issue lists them. The ids are kept only when every criterion
|
|
12
|
+
// carries one that fits the id grammar; otherwise every criterion is numbered
|
|
13
|
+
// `AC-<item>-NN` in order. A drafted list is always numbered and is recorded
|
|
14
|
+
// with `drafted: true`. An empty list records nothing. The script never edits
|
|
15
|
+
// the issue, and never replaces a list the item already has (the gateway
|
|
16
|
+
// answers 409 and the item's list stands).
|
|
11
17
|
import { spawnSync } from "node:child_process";
|
|
12
18
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
19
|
import { dirname, join } from "node:path";
|
|
@@ -15,118 +21,64 @@ import { pathToFileURL } from "node:url";
|
|
|
15
21
|
|
|
16
22
|
/** Mirrors ARTIFACT_KINDS in @outerlayer/session-schema (a test pins them equal); this script imports no packages. */
|
|
17
23
|
export const PROOF_KINDS = ["video", "screenshot", "report", "log", "test", "file"];
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const
|
|
24
|
+
/** Mirrors ArtifactCriterionIdSchema in @outerlayer/session-schema (a test pins them equal). */
|
|
25
|
+
export const CRITERION_ID = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
26
|
+
/** Mirrors CHECK_BOUND_ARTIFACT_IDS in @outerlayer/session-schema (a test pins them equal): ids that name a check, not a criterion. */
|
|
27
|
+
export const RESERVED_IDS = ["code-review-ran", "acceptance-criteria"];
|
|
28
|
+
const PROOF_IN_TEXT = /\(proof:\s*([a-z]+)\s*\)/i;
|
|
22
29
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
let fence = null;
|
|
26
|
-
return lines.map((line) => {
|
|
27
|
-
const opener = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
|
|
28
|
-
if (fence === null) {
|
|
29
|
-
if (opener === null) return line;
|
|
30
|
-
fence = opener[1];
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
if (opener !== null && opener[1][0] === fence[0] && opener[1].length >= fence.length) fence = null;
|
|
34
|
-
return "";
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** A bullet and the indented lines continuing it, as one string. */
|
|
39
|
-
function collectItems(lines, start) {
|
|
40
|
-
const items = [];
|
|
41
|
-
let i = start;
|
|
42
|
-
for (; i < lines.length; i++) {
|
|
43
|
-
const line = lines[i];
|
|
44
|
-
const bullet = BULLET.exec(line);
|
|
45
|
-
if (bullet) {
|
|
46
|
-
items.push(bullet[1].trim());
|
|
47
|
-
} else if (line.trim() === "") {
|
|
48
|
-
// a blank line ends the list unless another bullet follows
|
|
49
|
-
const next = lines.slice(i + 1).find((l) => l.trim() !== "");
|
|
50
|
-
if (next === undefined || !BULLET.test(next)) break;
|
|
51
|
-
} else if (/^\s+\S/.test(line) && items.length > 0) {
|
|
52
|
-
items[items.length - 1] += ` ${line.trim()}`;
|
|
53
|
-
} else {
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return items;
|
|
58
|
-
}
|
|
30
|
+
/** A criteria file the agent must fix before anything is recorded. */
|
|
31
|
+
export class CriteriaFileError extends Error {}
|
|
59
32
|
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
/** The proof a criterion declares: the file's `proof` when it is an artifact
|
|
34
|
+
* kind, else a `(proof: <kind>)` written in its text, else `null`. */
|
|
35
|
+
function proofOf(entry) {
|
|
36
|
+
if (PROOF_KINDS.includes(entry.proof)) return entry.proof;
|
|
37
|
+
const written = PROOF_IN_TEXT.exec(entry.text)?.[1]?.toLowerCase();
|
|
38
|
+
return PROOF_KINDS.includes(written) ? written : null;
|
|
62
39
|
}
|
|
63
40
|
|
|
64
|
-
/**
|
|
65
|
-
function
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (bullet) items.push(bullet[1].trim());
|
|
77
|
-
else if (/^\s+\S/.test(lines[i]) && items.length > 0) items[items.length - 1] += ` ${lines[i].trim()}`;
|
|
78
|
-
}
|
|
79
|
-
if (items.length === 0) return null;
|
|
80
|
-
const parsed = items.map((item) => ID_ITEM.exec(item));
|
|
81
|
-
const withId = parsed.filter((m) => m !== null && m[2].trim() !== "").length;
|
|
82
|
-
if (withId === 0) return null;
|
|
83
|
-
if (withId < parsed.length) return { mixed: true };
|
|
84
|
-
return {
|
|
85
|
-
criteria: parsed.map((m) => {
|
|
86
|
-
const kind = /\(proof:\s*([a-z]+)\s*\)/i.exec(m[2])?.[1]?.toLowerCase();
|
|
87
|
-
return { id: m[1], text: m[2].trim(), proof: PROOF_KINDS.includes(kind) ? kind : null };
|
|
88
|
-
}),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** The plain shape: an Acceptance / Acceptance criteria / Done when label, then a bullet list. */
|
|
93
|
-
function readLabelledBullets(lines) {
|
|
94
|
-
for (let i = 0; i < lines.length; i++) {
|
|
95
|
-
if (!LABEL.test(lines[i])) continue;
|
|
96
|
-
let start = i + 1;
|
|
97
|
-
while (start < lines.length && lines[start].trim() === "") start++;
|
|
98
|
-
const items = collectItems(lines, start).map(stripCheckbox).filter((t) => t !== "");
|
|
99
|
-
if (items.length > 0) return items;
|
|
100
|
-
}
|
|
41
|
+
/** Why the written ids cannot be kept, or `null` when they can. */
|
|
42
|
+
function idProblem(entries) {
|
|
43
|
+
const ids = entries.map((entry) => entry.id);
|
|
44
|
+
const missing = ids.filter((id) => typeof id !== "string" || id.trim() === "").length;
|
|
45
|
+
if (missing === ids.length) return "no criterion carried an id";
|
|
46
|
+
if (missing > 0) return "only some criteria carried an id";
|
|
47
|
+
const bad = ids.find((id) => !CRITERION_ID.test(id));
|
|
48
|
+
if (bad !== undefined) return `the id ${JSON.stringify(bad)} does not fit the id grammar`;
|
|
49
|
+
const reserved = ids.find((id) => RESERVED_IDS.includes(id));
|
|
50
|
+
if (reserved !== undefined) return `the id ${JSON.stringify(reserved)} names a check, not a criterion`;
|
|
51
|
+
const repeated = ids.find((id, n) => ids.indexOf(id) !== n);
|
|
52
|
+
if (repeated !== undefined) return `the id ${JSON.stringify(repeated)} appears twice`;
|
|
101
53
|
return null;
|
|
102
54
|
}
|
|
103
55
|
|
|
104
56
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
57
|
+
* Turns the agent's criteria file into the list `outerlayer emit criteria`
|
|
58
|
+
* takes. Throws {@link CriteriaFileError} for a file the agent must fix.
|
|
59
|
+
* @returns {{criteria: Array<{id:string,text:string,proof:string|null}>, numbered: boolean, numberedBecause?: string}}
|
|
107
60
|
*/
|
|
108
|
-
export function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (ids?.criteria) return { shape: "ids", criteria: ids.criteria };
|
|
112
|
-
const bullets = readLabelledBullets(lines);
|
|
113
|
-
if (ids?.mixed && bullets) {
|
|
114
|
-
return {
|
|
115
|
-
shape: "none",
|
|
116
|
-
criteria: [],
|
|
117
|
-
note: "some criteria carry an id and some do not; recording nothing rather than renumbering the written ids",
|
|
118
|
-
};
|
|
61
|
+
export function readCriteria(input, item, { drafted = false } = {}) {
|
|
62
|
+
if (typeof input !== "object" || input === null || !Array.isArray(input.criteria)) {
|
|
63
|
+
throw new CriteriaFileError('the criteria file must be { "criteria": [ ... ] }');
|
|
119
64
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
65
|
+
const entries = input.criteria.map((entry, n) => {
|
|
66
|
+
if (typeof entry !== "object" || entry === null || typeof entry.text !== "string" || entry.text.trim() === "") {
|
|
67
|
+
throw new CriteriaFileError(`criteria[${n}] needs the criterion's text`);
|
|
68
|
+
}
|
|
69
|
+
return { id: entry.id, text: entry.text.trim(), proof: entry.proof };
|
|
70
|
+
});
|
|
71
|
+
const problem = drafted ? "the build drafted them" : idProblem(entries);
|
|
72
|
+
const numbered = entries.length > 0 && problem !== null;
|
|
73
|
+
const criteria = entries.map((entry, n) => ({
|
|
74
|
+
id: numbered ? `AC-${item}-${String(n + 1).padStart(2, "0")}` : entry.id,
|
|
75
|
+
text: entry.text,
|
|
76
|
+
proof: proofOf(entry),
|
|
77
|
+
}));
|
|
78
|
+
return numbered ? { criteria, numbered, numberedBecause: problem } : { criteria, numbered };
|
|
127
79
|
}
|
|
128
80
|
|
|
129
|
-
/** Sorts a failed `emit criteria` into "the item already has a
|
|
81
|
+
/** Sorts a failed `emit criteria` into "the item already has a list" and any other refusal. */
|
|
130
82
|
export function classifyEmitFailure(message) {
|
|
131
83
|
// Only the CLI's replace refusal reads "refused (409):". Other answers that
|
|
132
84
|
// carry a 409, such as a lost race, read "emit failed (409):".
|
|
@@ -153,40 +105,39 @@ function readState(path) {
|
|
|
153
105
|
}
|
|
154
106
|
|
|
155
107
|
/**
|
|
156
|
-
* Records the
|
|
157
|
-
* `emit(file)` runs `outerlayer emit criteria
|
|
158
|
-
* `{ ok: true }` or `{ ok: false, message }`.
|
|
108
|
+
* Records the criteria and notes the outcome in the state file. `input` is
|
|
109
|
+
* the parsed criteria file. `emit(file)` runs `outerlayer emit criteria
|
|
110
|
+
* <file>` and returns `{ ok: true }` or `{ ok: false, message }`.
|
|
159
111
|
*/
|
|
160
|
-
export async function recordCriteria({
|
|
161
|
-
const
|
|
112
|
+
export async function recordCriteria({ input, item, drafted = false, reportsDir, statePath, emit }) {
|
|
113
|
+
const read = readCriteria(input, item, { drafted });
|
|
114
|
+
const numbering = read.numbered ? { numbered: true, numberedBecause: read.numberedBecause } : { numbered: false };
|
|
162
115
|
let outcome;
|
|
163
|
-
if (
|
|
164
|
-
outcome = { source: "none", recorded: false, count: 0, list: []
|
|
116
|
+
if (read.criteria.length === 0) {
|
|
117
|
+
outcome = { source: "none", recorded: false, count: 0, list: [] };
|
|
165
118
|
} else {
|
|
166
119
|
mkdirSync(reportsDir, { recursive: true });
|
|
167
120
|
const file = join(reportsDir, "criteria.json");
|
|
168
|
-
|
|
121
|
+
const list = drafted ? { criteria: read.criteria, drafted: true } : { criteria: read.criteria };
|
|
122
|
+
writeFileSync(file, `${JSON.stringify(list, null, 2)}\n`);
|
|
169
123
|
const result = await emit(file);
|
|
170
|
-
const
|
|
124
|
+
const source = drafted ? "drafted" : "issue";
|
|
125
|
+
const base = { count: read.criteria.length, list: read.criteria, ...numbering };
|
|
171
126
|
if (result.ok) {
|
|
172
|
-
outcome = { source
|
|
127
|
+
outcome = { source, recorded: true, ...base };
|
|
173
128
|
} else if (classifyEmitFailure(result.message) === "item-list-stands") {
|
|
174
|
-
// No command returns the item's own list, so `list` stays empty; the
|
|
129
|
+
// No command returns the item's own list, so `list` stays empty; the build's reading goes under `issueReading`.
|
|
175
130
|
outcome = {
|
|
176
131
|
source: "item",
|
|
177
132
|
recorded: false,
|
|
178
|
-
count:
|
|
133
|
+
count: read.criteria.length,
|
|
179
134
|
list: [],
|
|
180
|
-
issueReading:
|
|
135
|
+
issueReading: read.criteria,
|
|
136
|
+
...numbering,
|
|
181
137
|
note: "the item already has a recorded list; it stands",
|
|
182
138
|
};
|
|
183
139
|
} else {
|
|
184
|
-
outcome = {
|
|
185
|
-
source: found.shape === "ids" ? "issue-ids" : "issue-bullets",
|
|
186
|
-
recorded: false,
|
|
187
|
-
...base,
|
|
188
|
-
refusal: result.message,
|
|
189
|
-
};
|
|
140
|
+
outcome = { source, recorded: false, ...base, refusal: result.message };
|
|
190
141
|
}
|
|
191
142
|
}
|
|
192
143
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
@@ -197,12 +148,13 @@ export async function recordCriteria({ body, item, reportsDir, statePath, emit }
|
|
|
197
148
|
export function summarize(outcome) {
|
|
198
149
|
const n = outcome.count;
|
|
199
150
|
const noun = n === 1 ? "criterion" : "criteria";
|
|
200
|
-
|
|
201
|
-
if (outcome.source === "
|
|
202
|
-
|
|
151
|
+
const ids = outcome.numbered ? `, numbered AC-<item>-NN because ${outcome.numberedBecause}` : ", with the issue's own ids";
|
|
152
|
+
if (outcome.source === "none") return "no criteria in the file; none recorded";
|
|
153
|
+
if (outcome.source === "item") return `the item already has a recorded list; it stands, and the ${n} ${noun} read here were not recorded`;
|
|
154
|
+
const what = outcome.source === "drafted" ? `drafted ${n} ${noun} from the issue's description` : `read ${n} ${noun} from the issue`;
|
|
203
155
|
return outcome.recorded
|
|
204
|
-
?
|
|
205
|
-
:
|
|
156
|
+
? `${what} and recorded them${outcome.source === "drafted" ? " as drafted" : ids}`
|
|
157
|
+
: `${what} but the record was refused: ${outcome.refusal}`;
|
|
206
158
|
}
|
|
207
159
|
|
|
208
160
|
async function main(argv) {
|
|
@@ -211,9 +163,16 @@ async function main(argv) {
|
|
|
211
163
|
return at === -1 ? undefined : argv[at + 1];
|
|
212
164
|
};
|
|
213
165
|
const item = arg("--item");
|
|
214
|
-
const
|
|
215
|
-
if (!item || !/^\d+$/.test(item) || !
|
|
216
|
-
process.stderr.write("usage: record-criteria.mjs --item <number> --
|
|
166
|
+
const criteriaFile = arg("--criteria-file");
|
|
167
|
+
if (!item || !/^\d+$/.test(item) || !criteriaFile) {
|
|
168
|
+
process.stderr.write("usage: record-criteria.mjs --item <number> --criteria-file <path> [--drafted]\n");
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
171
|
+
let input;
|
|
172
|
+
try {
|
|
173
|
+
input = JSON.parse(readFileSync(criteriaFile, "utf8"));
|
|
174
|
+
} catch (err) {
|
|
175
|
+
process.stderr.write(`cannot read the criteria file "${criteriaFile}": ${err.message}\n`);
|
|
217
176
|
return 2;
|
|
218
177
|
}
|
|
219
178
|
const git = spawnSync("git", ["rev-parse", "--absolute-git-dir"], { encoding: "utf8" });
|
|
@@ -222,15 +181,22 @@ async function main(argv) {
|
|
|
222
181
|
return 2;
|
|
223
182
|
}
|
|
224
183
|
const gitDir = git.stdout.trim();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
184
|
+
try {
|
|
185
|
+
const outcome = await recordCriteria({
|
|
186
|
+
input,
|
|
187
|
+
item,
|
|
188
|
+
drafted: argv.includes("--drafted"),
|
|
189
|
+
reportsDir: join(gitDir, "build-reports"),
|
|
190
|
+
statePath: join(gitDir, "build-state.json"),
|
|
191
|
+
emit: makeEmit(spawnSync, item),
|
|
192
|
+
});
|
|
193
|
+
process.stdout.write(`${summarize(outcome)}\n`);
|
|
194
|
+
return 0;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (!(err instanceof CriteriaFileError)) throw err;
|
|
197
|
+
process.stderr.write(`${err.message}; fix the criteria file and run again\n`);
|
|
198
|
+
return 2;
|
|
199
|
+
}
|
|
234
200
|
}
|
|
235
201
|
|
|
236
202
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) process.exitCode = await main(process.argv.slice(2));
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
# clean bill) as an .html file. Emit it as a report artifact with no --for:
|
|
6
6
|
# outerlayer emit artifact review.html --caption "Review of the change and what it found"
|
|
7
7
|
# Then run `outerlayer sync`, and copy the report's deep link from the pull
|
|
8
|
-
# request's evidence comment.
|
|
9
|
-
#
|
|
8
|
+
# request's evidence comment. The link can take up to a minute to appear
|
|
9
|
+
# after the sync. If it is not there yet, wait a minute and read the comment
|
|
10
|
+
# again; do not give up after one look. Never build the link by hand, and
|
|
11
|
+
# never link a pull request comment: a comment can be edited or deleted later.
|
|
10
12
|
#
|
|
11
13
|
# Then record the verdict once, with that link as its proof:
|
|
12
14
|
# outerlayer emit code-review --item "$OUTERLAYER_WORK" --result pass --link <the report's deep link>
|
package/dist/chunk-T6OIJQRT.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
|
|
4
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
5
|
-
import { dirname } from 'path';
|
|
6
|
-
|
|
7
|
-
createRequire(import.meta.url);
|
|
8
|
-
function readJobFile(path) {
|
|
9
|
-
if (!existsSync(path)) return null;
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
-
} catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function readJobFileChecked(path) {
|
|
17
|
-
if (!existsSync(path)) return { kind: "missing" };
|
|
18
|
-
try {
|
|
19
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
20
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return { kind: "unreadable" };
|
|
21
|
-
return { kind: "ok", job: parsed };
|
|
22
|
-
} catch {
|
|
23
|
-
return { kind: "unreadable" };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function writeJobFile(path, job) {
|
|
27
|
-
mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
|
|
28
|
-
writeFileSync(path, JSON.stringify(job, null, 2), { mode: RUNNER_FILE_MODE });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export { readJobFile, readJobFileChecked, writeJobFile };
|