@outerlayer/cli 0.13.0 → 0.14.1
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 +19 -0
- package/dist/agent-setup-CN2HUBEJ.js +26 -0
- package/dist/{build-LELVCWHV.js → build-BG2YENGK.js} +2 -2
- package/dist/build-info.json +1 -1
- package/dist/{builder-UUS4ORDH.js → builder-2D3WSOOY.js} +4 -4
- package/dist/{check-63IDCEEV.js → check-SRGR36KI.js} +30 -30
- package/dist/{chunk-GI4XXD4I.js → chunk-3NLOVYJQ.js} +4 -4
- package/dist/{chunk-C6IV5BV7.js → chunk-4OGPT6I2.js} +11 -11
- package/dist/{chunk-DXQ5MHCO.js → chunk-FOJGZYFC.js} +11 -5
- package/dist/{chunk-HDTVTA6I.js → chunk-HFKKFTXU.js} +11 -15
- package/dist/{chunk-DPKX26LT.js → chunk-HFKQIZXL.js} +9 -6
- package/dist/{chunk-6S3H3J6H.js → chunk-JJEGKEFV.js} +5 -7
- package/dist/{chunk-P6FX4D43.js → chunk-JYIEHQIF.js} +1 -1
- package/dist/{chunk-XJKL4BNU.js → chunk-KFRM23WD.js} +2 -2
- package/dist/{chunk-3KTA5UU2.js → chunk-KMDLFI4P.js} +1 -1
- package/dist/{chunk-3D4OYFCD.js → chunk-MSASO5O4.js} +42 -6
- package/dist/chunk-MTF3PHVZ.js +161 -0
- package/dist/{chunk-SVEP7PVH.js → chunk-NHDVD7DU.js} +9 -9
- package/dist/{chunk-EFZLAYPE.js → chunk-NPQWKGUA.js} +1 -1
- package/dist/{chunk-SFFHGX3G.js → chunk-PQUM37BK.js} +1 -1
- package/dist/{chunk-MJXXRX4M.js → chunk-Q2UK3C4P.js} +2 -2
- package/dist/{chunk-MZN7AQOB.js → chunk-RBKIH5ST.js} +11 -11
- package/dist/{chunk-TOPDNXHZ.js → chunk-SMHOCDCI.js} +4 -4
- package/dist/{chunk-P4MM66C6.js → chunk-SWHQ4LUM.js} +4 -4
- package/dist/{agent-setup-QE6PSCEZ.js → chunk-SXWS25JC.js} +6 -19
- package/dist/{chunk-CNFVHDHG.js → chunk-WUYTWNMA.js} +3 -1
- package/dist/{chunk-R7OQTKWT.js → chunk-XCK2YAY5.js} +12 -3
- package/dist/{chunk-4QVZCVWT.js → chunk-YDSMROTB.js} +1 -1
- package/dist/{chunk-TCIW27FM.js → chunk-ZA5CHAFY.js} +1 -1
- package/dist/{cli-WSBIAVZU.js → cli-UW2H3UUW.js} +79 -62
- package/dist/{command-N7BAB4XJ.js → command-5LD7G2QQ.js} +22 -22
- package/dist/{connect-cmd-FGYRLLXL.js → connect-cmd-JUAW6MU4.js} +2 -2
- package/dist/{context-adopt-B3PWGFHL.js → context-adopt-PJUMW2EZ.js} +5 -5
- package/dist/{context-materialize-7S6NLLRO.js → context-materialize-FDHYKNDT.js} +5 -5
- package/dist/{docker-BZ55BMBJ.js → docker-4J77Q2BF.js} +5 -5
- package/dist/{doctor-3H25Q7WT.js → doctor-H7REBSLQ.js} +24 -24
- package/dist/{emit-artifact-cmd-IQCRXOH7.js → emit-artifact-cmd-FOGNJXVR.js} +15 -18
- package/dist/{emit-cmd-KDSM5VAO.js → emit-cmd-ZEYWLC2C.js} +3 -2
- package/dist/{emit-criteria-cmd-EYUXME26.js → emit-criteria-cmd-HBP6XS5C.js} +5 -8
- package/dist/{emit-finding-cmd-P4RKBPBB.js → emit-finding-cmd-DLI4MKMN.js} +5 -8
- package/dist/{emit-result-cmd-6XYENU6H.js → emit-result-cmd-MUSL7GQA.js} +9 -12
- package/dist/{hook-fast-PXWKQPN6.js → hook-fast-WYQD6D7T.js} +1 -1
- package/dist/{import-capture-cmd-2BKM5I2N.js → import-capture-cmd-6QVCOIHK.js} +8 -11
- package/dist/{import-ruler-cmd-IXR7YK47.js → import-ruler-cmd-KLRGRBCP.js} +5 -8
- package/dist/index.js +3 -3
- package/dist/{init-2LWVS4AH.js → init-NTXA3SN7.js} +23 -23
- package/dist/{init-ZL7ORPTF.js → init-UBXK6MF7.js} +23 -23
- package/dist/{init-cmd-JXOBCOIB.js → init-cmd-NARDUPIG.js} +13 -8
- package/dist/init-update-O5YYFGF4.js +165 -0
- package/dist/{loop-XOAE6ZIY.js → loop-SW2M5VDD.js} +30 -30
- package/dist/{machine-G6PY2J2J.js → machine-U66FKVDP.js} +21 -21
- package/dist/mcp-install-cmd-FDTPQQS6.js +9 -0
- package/dist/{runner-key-TDTLNAKJ.js → runner-key-3JSMDRJA.js} +23 -23
- package/dist/{session-sender-XVGZFWRK.js → session-sender-4M247XSV.js} +5 -5
- package/dist/{starter-pack-B7JPGEDO.js → starter-pack-IB4UUIX3.js} +2 -2
- package/dist/{status-MRTPG34B.js → status-STZYPU3W.js} +26 -26
- package/dist/{sync-cmd-UBUZMMDN.js → sync-cmd-UWD5JOFU.js} +4 -4
- package/dist/{watch-STNPEWD6.js → watch-DBNB7R6X.js} +4 -4
- package/dist/{work-claim-cmd-CWGYEIJE.js → work-claim-cmd-LY4U6QHN.js} +11 -11
- package/dist/{work-cmd-X7NMJDFL.js → work-cmd-RVRQ32EH.js} +4 -4
- package/dist/{work-comment-cmd-FI2TMAYQ.js → work-comment-cmd-FFUM4TJU.js} +7 -10
- package/dist/{work-open-pr-cmd-OVHJ5I5H.js → work-open-pr-cmd-HLDWP4T2.js} +7 -10
- package/dist/{work-pr-cmd-Y5TDMQ4O.js → work-pr-cmd-L77K32WK.js} +4 -4
- package/package.json +1 -1
- package/skill-pack/maintained/emitting-evidence/SKILL.md +8 -2
- package/skill-pack/shipped.json +75 -0
- package/skill-pack/template/build/SKILL.md +8 -3
- package/skill-pack/template/build/references/release.md +42 -11
- package/skill-pack/template/build/references/review-loop.md +1 -1
- package/skill-pack/template/build/scripts/criterion-citations.mjs +184 -0
- package/skill-pack/template/spec/SKILL.md +33 -22
- package/skill-pack/template/writing-specs/SKILL.md +36 -3
- package/skill-pack/template/writing-specs/references/agents-snippet.md +57 -0
- package/skill-pack/template/writing-tests/SKILL.md +117 -0
- package/skill-pack/validators/acceptance-criteria.yaml +35 -0
- package/dist/mcp-install-cmd-VXVIH7PH.js +0 -9
- package/dist/{chunk-NX57IBNK.js → chunk-4NPZVMJU.js} +2 -2
- package/dist/{chunk-3FMPZPJQ.js → chunk-R7PF5OOU.js} +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { resolveCredentials, postJson, getJson } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { resolveCredentials, postJson, getJson } from './chunk-SWHQ4LUM.js';
|
|
4
|
+
import './chunk-NPQWKGUA.js';
|
|
5
5
|
import { detectActiveSession } from './chunk-IP7BIBXJ.js';
|
|
6
|
-
import './chunk-
|
|
7
|
-
import { sanitizeTerminal } from './chunk-
|
|
6
|
+
import './chunk-YDSMROTB.js';
|
|
7
|
+
import { sanitizeTerminal } from './chunk-WUYTWNMA.js';
|
|
8
8
|
import { resolveRepoIdentity } from './chunk-JXYVA45C.js';
|
|
9
9
|
import './chunk-4KSQKQXE.js';
|
|
10
10
|
import './chunk-N6LURUEF.js';
|
|
11
|
-
import './chunk-YRYMNBGQ.js';
|
|
11
|
+
import { C } from './chunk-YRYMNBGQ.js';
|
|
12
12
|
import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
|
|
13
13
|
import { defaultFetch } from './chunk-YO4PSLVB.js';
|
|
14
14
|
import './chunk-PLMTLGYH.js';
|
|
@@ -22,9 +22,6 @@ import './chunk-Z5GEFMRW.js';
|
|
|
22
22
|
import { randomUUID } from 'crypto';
|
|
23
23
|
|
|
24
24
|
createRequire(import.meta.url);
|
|
25
|
-
var GREEN = "\x1B[32m";
|
|
26
|
-
var DIM = "\x1B[2m";
|
|
27
|
-
var RESET = "\x1B[0m";
|
|
28
25
|
var WorkCommentCommandError = class extends Error {
|
|
29
26
|
};
|
|
30
27
|
function resolveAction(opts) {
|
|
@@ -78,7 +75,7 @@ async function runWorkComment(opts) {
|
|
|
78
75
|
const thread = data.thread;
|
|
79
76
|
const waitingOn = thread?.waitingOn ?? null;
|
|
80
77
|
const criterionLabel = opts.criterion ? `criterion ${opts.criterion}` : "the general thread";
|
|
81
|
-
const output = opts.json ? JSON.stringify(data) : `${
|
|
78
|
+
const output = opts.json ? JSON.stringify(data) : `${C.green}\u2713${C.reset} commented on ${criterionLabel} of item #${itemNumber} ${C.dim}\xB7${C.reset} waiting on ${waitingOn ?? "no one"}`;
|
|
82
79
|
if (!opts.quiet) process.stdout.write(output + "\n");
|
|
83
80
|
return { data, output };
|
|
84
81
|
}
|
|
@@ -102,7 +99,7 @@ async function runWorkThreads(opts) {
|
|
|
102
99
|
return { data, output };
|
|
103
100
|
}
|
|
104
101
|
function renderThread(label, thread) {
|
|
105
|
-
const header = `${
|
|
102
|
+
const header = `${C.dim}${label === "general" ? "general" : `criterion ${label}`} \xB7 waiting on ${thread.waitingOn ?? "no one"}${C.reset}`;
|
|
106
103
|
const comments = thread.comments.map(
|
|
107
104
|
(c) => ` ${c.author.kind === "person" ? "person" : "agent"} ${sanitizeTerminal(c.author.name)}: ${sanitizeTerminal(c.body)}${c.action ? ` (${c.action})` : ""}`
|
|
108
105
|
);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { itemLabel, runWorkPr, WorkPrCommandError } from './chunk-
|
|
4
|
-
import { resolveCredentials } from './chunk-
|
|
5
|
-
import './chunk-
|
|
3
|
+
import { itemLabel, runWorkPr, WorkPrCommandError } from './chunk-JJEGKEFV.js';
|
|
4
|
+
import { resolveCredentials } from './chunk-SWHQ4LUM.js';
|
|
5
|
+
import './chunk-NPQWKGUA.js';
|
|
6
6
|
import './chunk-IP7BIBXJ.js';
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
7
|
+
import './chunk-YDSMROTB.js';
|
|
8
|
+
import './chunk-WUYTWNMA.js';
|
|
9
9
|
import './chunk-JXYVA45C.js';
|
|
10
10
|
import './chunk-4KSQKQXE.js';
|
|
11
11
|
import './chunk-N6LURUEF.js';
|
|
12
|
-
import './chunk-YRYMNBGQ.js';
|
|
12
|
+
import { C } from './chunk-YRYMNBGQ.js';
|
|
13
13
|
import { parseWorkItemEnv } from './chunk-GQD22KKN.js';
|
|
14
14
|
import './chunk-WZBHBH3J.js';
|
|
15
15
|
import { defaultFetch } from './chunk-YO4PSLVB.js';
|
|
@@ -25,9 +25,6 @@ import { spawnSync } from 'child_process';
|
|
|
25
25
|
import { readFileSync } from 'fs';
|
|
26
26
|
|
|
27
27
|
createRequire(import.meta.url);
|
|
28
|
-
var GREEN = "\x1B[32m";
|
|
29
|
-
var DIM = "\x1B[2m";
|
|
30
|
-
var RESET = "\x1B[0m";
|
|
31
28
|
var ITEM_KEY_PREFIX = "olitem_";
|
|
32
29
|
var WorkOpenPrCommandError = class extends Error {
|
|
33
30
|
};
|
|
@@ -163,7 +160,7 @@ async function runWorkOpenPr(opts) {
|
|
|
163
160
|
} else {
|
|
164
161
|
data = await openWithGh({ ...opts, env }, opts.title, opts.bodyFile, cwd);
|
|
165
162
|
}
|
|
166
|
-
const output = opts.json ? JSON.stringify(data) : `${
|
|
163
|
+
const output = opts.json ? JSON.stringify(data) : `${C.green}\u2713${C.reset} ${data.created ? "opened" : "edited"} ${C.dim}\xB7${C.reset} ${data.repository}#${data.number} ${C.dim}\xB7${C.reset} ${data.url}${data.workItemNumber == null ? "" : ` ${C.dim}\u2192${C.reset} ${itemLabel({ workItemId: "", ...data })}`}`;
|
|
167
164
|
if (!opts.quiet) process.stdout.write(output + "\n");
|
|
168
165
|
return { data, output };
|
|
169
166
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
export { WorkPrCommandError, itemLabel, runWorkPr } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
export { WorkPrCommandError, itemLabel, runWorkPr } from './chunk-JJEGKEFV.js';
|
|
4
|
+
import './chunk-NPQWKGUA.js';
|
|
5
5
|
import './chunk-IP7BIBXJ.js';
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import './chunk-YDSMROTB.js';
|
|
7
|
+
import './chunk-WUYTWNMA.js';
|
|
8
8
|
import './chunk-JXYVA45C.js';
|
|
9
9
|
import './chunk-4KSQKQXE.js';
|
|
10
10
|
import './chunk-N6LURUEF.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outerlayer/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "OuterLayer CLI — commands: init, connect, doctor, login, logout, sync, daemon (alias: watch), emit, work, runner, hooks, mcp, import, 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.",
|
|
@@ -104,9 +104,15 @@ Then run `outerlayer sync`, and copy the report's deep link from the pull reques
|
|
|
104
104
|
|
|
105
105
|
Use `--result fail` when the review left a finding unfixed. Record it once. Outside a build, pass the item's number to `--item`.
|
|
106
106
|
|
|
107
|
-
## The criteria report
|
|
107
|
+
## The criteria report and the `acceptance-criteria` check
|
|
108
108
|
|
|
109
|
-
Bind the judge's report on each criterion, an HTML file, with `--for acceptance-criteria`.
|
|
109
|
+
Bind the judge's report on each criterion, an HTML file, with `--for acceptance-criteria`. The item page links it at the top of its Criteria tab for the reviewer. Emit it only when the item has recorded criteria.
|
|
110
|
+
|
|
111
|
+
Then run `outerlayer sync`, copy the report's deep link from the evidence comment the same way, and record the judge's verdict on the `acceptance-criteria` check that `outerlayer init` writes:
|
|
112
|
+
|
|
113
|
+
outerlayer emit acceptance-criteria --item "$OUTERLAYER_WORK" --result pass --link <the report's deep link>
|
|
114
|
+
|
|
115
|
+
Use `--result pass` only when the judge found every criterion it judged satisfied, and `--result fail` when it found one unmet. When there is no criterion to judge, record nothing: the row reads "not proven", which is true. Skip the emit in a repository with no `.outerlayer/validators/acceptance-criteria.yaml`.
|
|
110
116
|
|
|
111
117
|
A session never records a pass or fail on an artifact. It emits the artifact, and a person or a check decides.
|
|
112
118
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"files": {
|
|
4
|
+
".outerlayer/policy.yaml": [
|
|
5
|
+
"1d874f1f521a79fc286ed5400c20022921e017f48e60e474015fedc29a1130a2",
|
|
6
|
+
"78c95632b51600df0cb2e206d843e75789ccba2f80fd8dfab36d16239062b008"
|
|
7
|
+
],
|
|
8
|
+
".outerlayer/skills/build/SKILL.md": [
|
|
9
|
+
"5cf9bd75fb1f984f6e6b470d1f1581dc06168abf062448e6f43711fed8f6632a",
|
|
10
|
+
"6177ea457be0d238659640e6d29807708d60d68533ae3ff93af10fb513139ced",
|
|
11
|
+
"77cab8b446fb1aa865710f7cb8356c5334301a942f58f9243766bf1acf1377ad",
|
|
12
|
+
"93953397c31d47a6f1c06edb8ae2190e1d8a3fca20c616deb33991311cee39a8",
|
|
13
|
+
"a6743050c5c387e962ad4264526d9a3c10edf36823e4b6829f200bf79a4e45e7",
|
|
14
|
+
"b0aa9b6962675f772b67a4390c15b6b0100390983d958c656a826a066399f3d0",
|
|
15
|
+
"f4e1661c6f87d7ff6d23c6182530fa0a9cb87c4dc3f31e48f2be11f32ebb2c00"
|
|
16
|
+
],
|
|
17
|
+
".outerlayer/skills/build/references/agent-briefs.md": [
|
|
18
|
+
"3c875a4f287f574eea954c6daa52b17529cdeba8ec4007d78084d911350d1218",
|
|
19
|
+
"7c2a651e7c827d8e3fb9df889d3776b552eb02d315db5ace27d2baad78aab6a5"
|
|
20
|
+
],
|
|
21
|
+
".outerlayer/skills/build/references/criteria-judge.md": [
|
|
22
|
+
"62db8f6d77f31ca25a9071c05429e0f639f9b2bdd7f7cb04cb14a1b0db509537"
|
|
23
|
+
],
|
|
24
|
+
".outerlayer/skills/build/references/evidence.md": [
|
|
25
|
+
"ceff5488bae31198ed4738ae1a230c0119f973ced439dac360eba6e24e1604f4",
|
|
26
|
+
"f27502610358e436e7ffb61d4fd281289e162f6de6b2c11396f17b2fa63802c9"
|
|
27
|
+
],
|
|
28
|
+
".outerlayer/skills/build/references/release.md": [
|
|
29
|
+
"0b0af40c82473cf192f697537ed5a84801a14e79bb11790d72226d7f7f49cca0",
|
|
30
|
+
"1113259bdf7d8c28edcbdb9b4c83c2f2bd4c5eaf817cf30bf428478ff91694e9",
|
|
31
|
+
"1f4fe97ca8f11d6681db5734f3103af5ea175a9472ff1d38b10cb463f4956a6a",
|
|
32
|
+
"4687f56bcec766f1926d58a208418f800a1727c073b5e6047b40e3aaac2bd6c9",
|
|
33
|
+
"81a764eb317fcb4672bedd35e7b9aa68d1c0b73f82fc2dfa03e5a78758db8f14",
|
|
34
|
+
"985e02b98105b00c356014e003b2d60e9c458c3dcbeef7378274ccb84af031fc"
|
|
35
|
+
],
|
|
36
|
+
".outerlayer/skills/build/references/review-briefs.md": [
|
|
37
|
+
"2c8b436795b583a61aa6f98f48611db554f0dd52b27a9ab748a7db5f96526aec"
|
|
38
|
+
],
|
|
39
|
+
".outerlayer/skills/build/references/review-loop.md": [
|
|
40
|
+
"155ab732ccf4c25f000853e98a27a3d2f3a022c75979601bb71d86f07983216d",
|
|
41
|
+
"5f30b56d226b279ec5f65ad73b823e3aeeac0c14f2ee539fb79cf7ba7b2cac6d",
|
|
42
|
+
"6f93e2ae74d3c02a0d4c14a6b2c805f523d234a8c56789f40a7c7c59700ef277",
|
|
43
|
+
"9d4d2e5e0307ea8c38cf885cd67d06a939b7fc2bed65f5080ca9610178949d74"
|
|
44
|
+
],
|
|
45
|
+
".outerlayer/skills/build/scripts/criterion-citations.mjs": [
|
|
46
|
+
"0902118ec216182f9d0ba0f6c7966306ad6891d8e846022123d3f86aaf175fce"
|
|
47
|
+
],
|
|
48
|
+
".outerlayer/skills/build/scripts/record-criteria.mjs": [
|
|
49
|
+
"36f19e730bc220fa21622b3bb0a306999f038b8220a0a07a1efe21ec0f748729",
|
|
50
|
+
"596e9a78e62cbea040a3bf428cd54bf59687e9e89001fad719304f88d03ca2ea",
|
|
51
|
+
"64de9445087b247b0781a4e6e025ef97b1259ce1c91819bf541decc26b8f8400"
|
|
52
|
+
],
|
|
53
|
+
".outerlayer/skills/spec/SKILL.md": [
|
|
54
|
+
"71e81fabf353ad6117c54e061868538bf00066b78c2528f5a8364445d1def02e",
|
|
55
|
+
"9039ae30d4e836b5c5cfba4a14344e46fd13694ae9e4d88e750abce34dfcd9e6"
|
|
56
|
+
],
|
|
57
|
+
".outerlayer/skills/writing-specs/SKILL.md": [
|
|
58
|
+
"a956aac4249118a058c4327c5751472883588720950d4a96f80c7e91f454bef9",
|
|
59
|
+
"eb8aa95f5e76a46ae9bf340989fb40e470fd750ded791ba80b5f8c5c325513e5"
|
|
60
|
+
],
|
|
61
|
+
".outerlayer/skills/writing-specs/references/agents-snippet.md": [
|
|
62
|
+
"fc47325f27cfce406a14ad71009d3687d3720deb572d40936e29fd091ca6c71d"
|
|
63
|
+
],
|
|
64
|
+
".outerlayer/skills/writing-tests/SKILL.md": [
|
|
65
|
+
"b00f5970b568159ca0bfbc75c3834938749ad5e2e73a80769d88bbe3c8c26737"
|
|
66
|
+
],
|
|
67
|
+
".outerlayer/validators/acceptance-criteria.yaml": [
|
|
68
|
+
"3b2054aa1d4bd9f7441e88eec1a3bdf61b61ad00efa79736e26a5dd601d43910"
|
|
69
|
+
],
|
|
70
|
+
".outerlayer/validators/code-review.yaml": [
|
|
71
|
+
"05041976e4d84d6256ed88995ea4fd1566abc6d3a9574863065cf855dcafcf70",
|
|
72
|
+
"fbdf0baca0064b09e47f5984afc322d582accd142c3377c06622bc6567b0e98c"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -84,8 +84,9 @@ These bind the orchestrator in every phase. Rule 5 also binds every spawned agen
|
|
|
84
84
|
"$OUTERLAYER_WORK" --json`. Each thread with `waitingOn` of `agent` holds a
|
|
85
85
|
person's comment. Answer each before the run ends, as the `amend` skill does.
|
|
86
86
|
5. **A session never records a pass or fail on an artifact.** It emits
|
|
87
|
-
artifacts. A person decides pass or fail.
|
|
88
|
-
|
|
87
|
+
artifacts. A person decides pass or fail. A build records two results,
|
|
88
|
+
both verdicts of its own agents: its review's, on the `code-review`
|
|
89
|
+
check, and its criteria judge's, on the `acceptance-criteria` check.
|
|
89
90
|
6. **Observed status only.** Every status claim in the final report (a gate
|
|
90
91
|
result, a CI conclusion, mergeability) quotes command output observed
|
|
91
92
|
after the last change it describes. No quoted output means unknown, never
|
|
@@ -214,10 +215,14 @@ Run them in order.
|
|
|
214
215
|
which links it, and a local session with `gh pr create` and then
|
|
215
216
|
`outerlayer work pr <number>`. When the build recorded criteria, from the issue or drafted, a fresh agent judges them with
|
|
216
217
|
[references/criteria-judge.md](references/criteria-judge.md), and the
|
|
217
|
-
judge's HTML report is emitted:
|
|
218
|
+
judge's HTML report is linked to its tests and emitted:
|
|
218
219
|
|
|
220
|
+
node .outerlayer/skills/build/scripts/criterion-citations.mjs --report <judge-report>.html
|
|
219
221
|
outerlayer emit artifact <judge-report>.html --for acceptance-criteria --caption "Each criterion judged against its test"
|
|
220
222
|
|
|
223
|
+
Its verdict is recorded on the `acceptance-criteria` check, as release.md
|
|
224
|
+
says. When the state file holds no criteria, no judge runs and that
|
|
225
|
+
check gets no result, so it reads "not proven", never passed.
|
|
221
226
|
Watch CI until every check has concluded.
|
|
222
227
|
6. **Manual test and evidence.** Read [references/evidence.md](references/evidence.md)
|
|
223
228
|
when the pull request opens, and run it. Capture the proof each criterion
|
|
@@ -61,23 +61,54 @@ the final report. Never record the result with another link.
|
|
|
61
61
|
|
|
62
62
|
## Judge the criteria
|
|
63
63
|
|
|
64
|
-
When the
|
|
65
|
-
[criteria-judge.md](criteria-judge.md), with the worktree path,
|
|
66
|
-
the pull request number, the path of the build's state file,
|
|
67
|
-
path under `build-reports/`. Above forty
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
The judge writes an HTML report with one verdict per criterion.
|
|
72
|
-
|
|
64
|
+
When the state file holds criteria, from the issue or drafted, spawn one
|
|
65
|
+
fresh agent on [criteria-judge.md](criteria-judge.md), with the worktree path,
|
|
66
|
+
the base ref, the pull request number, the path of the build's state file,
|
|
67
|
+
and a report path under `build-reports/`. Above forty criteria, spawn one
|
|
68
|
+
judge per group of about forty in the same wave, each with its own report
|
|
69
|
+
path, and merge their results by concatenation into one report.
|
|
70
|
+
|
|
71
|
+
The judge writes an HTML report with one verdict per criterion. Link each
|
|
72
|
+
criterion to the tests that cite it, which the Criteria tab lists. With several
|
|
73
|
+
judges, run it on the merged report. Then emit it:
|
|
74
|
+
|
|
75
|
+
node .outerlayer/skills/build/scripts/criterion-citations.mjs --report <judge-report>.html
|
|
73
76
|
outerlayer emit artifact <judge-report>.html --for acceptance-criteria --caption "Each criterion judged against the test that cites it"
|
|
74
77
|
|
|
75
78
|
Then run `outerlayer sync`. List every unsatisfied criterion in the final
|
|
76
79
|
report. A criterion that declares a proof form such as a screenshot or video is
|
|
77
80
|
proven only by an artifact bound to it on the pull request. The judge cannot
|
|
78
81
|
see that, so confirm each has an artifact of the declared kind, and emit any
|
|
79
|
-
that is missing in the evidence phase.
|
|
80
|
-
|
|
82
|
+
that is missing in the evidence phase.
|
|
83
|
+
|
|
84
|
+
## Record the criteria verdict
|
|
85
|
+
|
|
86
|
+
Once the judge's report is synced, copy its deep link from the pull request's
|
|
87
|
+
evidence comment. The link can take up to a minute to appear after the sync.
|
|
88
|
+
If it is not there yet, wait a minute and read the comment again. Never build
|
|
89
|
+
the link by hand, and never link a pull request comment. Then record the
|
|
90
|
+
judge's verdict on the `acceptance-criteria` check, once:
|
|
91
|
+
|
|
92
|
+
outerlayer emit acceptance-criteria --item "$OUTERLAYER_WORK" --result pass --link <the judge report's deep link>
|
|
93
|
+
|
|
94
|
+
The result is the judge's own last line. Record `--result pass` only when it
|
|
95
|
+
reads `result: pass`: every criterion the judge judged is satisfied. Record
|
|
96
|
+
`--result fail` when it reads `result: fail`, because a criterion is not
|
|
97
|
+
satisfied. A fail is recorded as fail, never withheld. With several judges,
|
|
98
|
+
record fail when any one of them failed. A criterion the judge marked
|
|
99
|
+
`not-judged` neither passes nor fails this check: its declared artifact
|
|
100
|
+
proves it on its own row. Drafted criteria are judged and recorded here like
|
|
101
|
+
the issue's own.
|
|
102
|
+
|
|
103
|
+
When the state file holds no criteria at all (`criteria.source` is `none`,
|
|
104
|
+
for example because drafting found nothing to draft), no judge runs, no
|
|
105
|
+
judge report is emitted, and nothing is recorded on this check. The row
|
|
106
|
+
then reads "not proven" once the build's claim ends. That is deliberate: no
|
|
107
|
+
criterion was judged, so the row must not read as passed, and a fail would
|
|
108
|
+
claim the judge found an unmet criterion. Name the unjudged check in the
|
|
109
|
+
final report. When the link has not appeared after a sync and a second look,
|
|
110
|
+
stop and name it as a blocker in the final report. Never record the result
|
|
111
|
+
with another link.
|
|
81
112
|
|
|
82
113
|
## Watch CI
|
|
83
114
|
|
|
@@ -26,7 +26,7 @@ whatever its size.
|
|
|
26
26
|
| Floor, or larger | full | generalist + up to three | when criteria exist |
|
|
27
27
|
|
|
28
28
|
An item with no criteria recorded is `small` whatever its diff, and no judge
|
|
29
|
-
runs. That happens only when the build could not draft a single criterion, since
|
|
29
|
+
runs, so the `acceptance-criteria` check gets no result. That happens only when the build could not draft a single criterion, since
|
|
30
30
|
an issue with no criteria gets a drafted list. A drafted list
|
|
31
31
|
(`criteria.source` is `drafted`) is tiered and judged like criteria the issue
|
|
32
32
|
wrote.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Links each recorded criterion to the tests that cite it, by writing a
|
|
3
|
+
// `criterion-citations` JSON block into the criteria judge's HTML report. The
|
|
4
|
+
// work item's Criteria tab reads that block to list a criterion's tests. Run
|
|
5
|
+
// it on the report just before emitting it. Needs only Node and git.
|
|
6
|
+
//
|
|
7
|
+
// node criterion-citations.mjs --report <path> [--criteria-file <path>]
|
|
8
|
+
//
|
|
9
|
+
// The criteria come from the file `record-criteria.mjs` wrote, by default
|
|
10
|
+
// `<git dir>/build-reports/criteria.json`. A test cites a criterion when the
|
|
11
|
+
// criterion's id appears as a whole token in a test's title or in a comment
|
|
12
|
+
// above it. Only git-tracked test files are scanned, so every link names code
|
|
13
|
+
// that a commit contains. The link names the HEAD commit and is written only
|
|
14
|
+
// when the origin remote is on GitHub; otherwise the test is listed unlinked.
|
|
15
|
+
// A criterion no test cites gets an empty list. An existing block in the
|
|
16
|
+
// report is replaced.
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { pathToFileURL } from "node:url";
|
|
21
|
+
|
|
22
|
+
/** A tracked file that holds tests: JS and TS `.test`/`.spec`, Python `test_*.py`/`*_test.py`, Go `*_test.go`, Ruby `*_spec.rb`. */
|
|
23
|
+
export const TEST_FILE = /(?:\.(?:test|spec)\.[cm]?[jt]sx?|(?:^|\/)test_[^/]*\.py|_test\.(?:py|go)|_spec\.rb)$/;
|
|
24
|
+
/** A line that is only a comment: `//`, `#`, `*`, `/*`, `--` or `<!--` first. */
|
|
25
|
+
const COMMENT_LINE = /^\s*(?:\/\/|#|\*|\/\*|--|<!--)/;
|
|
26
|
+
const BLOCK_ID = /<script type="application\/json" id="criterion-citations">[\s\S]*?<\/script>/;
|
|
27
|
+
|
|
28
|
+
/** A criteria file the agent must fix before anything is written. */
|
|
29
|
+
export class CitationInputError extends Error {}
|
|
30
|
+
|
|
31
|
+
function escapeRegExp(text) {
|
|
32
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A pattern that finds `id` as a whole token: not inside a longer id such as `WC-10` for `WC-1`, and still found before a sentence's full stop. */
|
|
36
|
+
export function idPattern(id) {
|
|
37
|
+
return new RegExp(`(?<![A-Za-z0-9_:-])${escapeRegExp(id)}(?![A-Za-z0-9_:-]|\\.[A-Za-z0-9])`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The ids of a criteria file shaped `{ "criteria": [{ "id" }] }`. */
|
|
41
|
+
export function criterionIds(input) {
|
|
42
|
+
if (typeof input !== "object" || input === null || !Array.isArray(input.criteria)) {
|
|
43
|
+
throw new CitationInputError('the criteria file must be { "criteria": [ ... ] }');
|
|
44
|
+
}
|
|
45
|
+
return input.criteria.map((entry, n) => {
|
|
46
|
+
if (typeof entry !== "object" || entry === null || typeof entry.id !== "string" || entry.id === "") {
|
|
47
|
+
throw new CitationInputError(`criteria[${n}] has no id`);
|
|
48
|
+
}
|
|
49
|
+
return entry.id;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The 1-based lines of one file's tests that cite `id`. A citation in a
|
|
55
|
+
* comment belongs to the first line after the comment that is not blank or
|
|
56
|
+
* another comment line (the test call); a citation anywhere else, such as a
|
|
57
|
+
* title, belongs to its own line. A comment with nothing after it keeps its
|
|
58
|
+
* own line.
|
|
59
|
+
*/
|
|
60
|
+
export function citingLines(contents, id) {
|
|
61
|
+
const pattern = idPattern(id);
|
|
62
|
+
const lines = contents.split("\n");
|
|
63
|
+
const found = new Set();
|
|
64
|
+
lines.forEach((text, index) => {
|
|
65
|
+
if (!pattern.test(text)) return;
|
|
66
|
+
if (!COMMENT_LINE.test(text)) {
|
|
67
|
+
found.add(index + 1);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let next = index + 1;
|
|
71
|
+
while (next < lines.length && (lines[next].trim() === "" || COMMENT_LINE.test(lines[next]))) next += 1;
|
|
72
|
+
found.add(next < lines.length ? next + 1 : index + 1);
|
|
73
|
+
});
|
|
74
|
+
return [...found];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `owner/repo` for a github.com remote in ssh or https form, else `null`. */
|
|
78
|
+
export function parseGithubRemote(remoteUrl) {
|
|
79
|
+
const trimmed = (remoteUrl ?? "").trim();
|
|
80
|
+
const match =
|
|
81
|
+
/^git@github\.com:([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed) ??
|
|
82
|
+
/^(?:https?|ssh):\/\/(?:[^@/]+@)?(?:www\.)?github\.com(?::\d+)?\/([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
|
|
83
|
+
return match ? `${match[1]}/${match[2]}` : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A link to `path` at line `line` of commit `sha`, or `null` when there is no GitHub repository to link to. */
|
|
87
|
+
export function testHref(repository, sha, path, line) {
|
|
88
|
+
if (!repository || !sha) return null;
|
|
89
|
+
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
90
|
+
return `https://github.com/${repository}/blob/${sha}/${encoded}#L${line}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Each criterion's citing tests, as `{ [id]: [{ path, line, href }] }` in
|
|
95
|
+
* the order the ids are given. `files` is `[{ path, contents }]` for the
|
|
96
|
+
* tracked test files; tests sort by path then line.
|
|
97
|
+
*/
|
|
98
|
+
export function buildCitations({ ids, files, repository = null, sha = null }) {
|
|
99
|
+
const sorted = [...files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
100
|
+
const result = {};
|
|
101
|
+
for (const id of ids) {
|
|
102
|
+
result[id] = sorted.flatMap(({ path, contents }) =>
|
|
103
|
+
citingLines(contents, id).map((line) => ({ path, line, href: testHref(repository, sha, path, line) })),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The block as HTML. `<` is escaped so no cited path or title can end the script element early. */
|
|
110
|
+
export function renderBlock(citations) {
|
|
111
|
+
const json = JSON.stringify(citations).replace(/</g, "\\u003c");
|
|
112
|
+
return `<script type="application/json" id="criterion-citations">${json}</script>`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** `html` with the block set: an existing one replaced, else added before `</body>`, else appended. */
|
|
116
|
+
export function withBlock(html, citations) {
|
|
117
|
+
const block = renderBlock(citations);
|
|
118
|
+
if (BLOCK_ID.test(html)) return html.replace(BLOCK_ID, () => block);
|
|
119
|
+
const at = html.lastIndexOf("</body>");
|
|
120
|
+
return at === -1 ? `${html}\n${block}\n` : `${html.slice(0, at)}${block}\n${html.slice(at)}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function git(cwd, args) {
|
|
124
|
+
const run = spawnSync("git", args, { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
125
|
+
return run.status === 0 ? run.stdout : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Reads the repository's tracked test files and its GitHub identity. `null` outside a git checkout. */
|
|
129
|
+
function readRepository(cwd) {
|
|
130
|
+
const top = git(cwd, ["rev-parse", "--show-toplevel"])?.trim();
|
|
131
|
+
const tracked = top ? git(top, ["ls-files", "-z"]) : null;
|
|
132
|
+
if (!top || tracked === null) return null;
|
|
133
|
+
const files = [];
|
|
134
|
+
for (const path of tracked.split("\0").filter((p) => p !== "" && TEST_FILE.test(p))) {
|
|
135
|
+
try {
|
|
136
|
+
files.push({ path, contents: readFileSync(join(top, path), "utf8") });
|
|
137
|
+
} catch {
|
|
138
|
+
// A tracked file deleted from the working tree has no tests to read.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const sha = git(top, ["rev-parse", "HEAD"])?.trim() || null;
|
|
142
|
+
const repository = parseGithubRemote(git(top, ["remote", "get-url", "origin"]) ?? "");
|
|
143
|
+
return { files, sha, repository };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function main(argv) {
|
|
147
|
+
const arg = (name) => {
|
|
148
|
+
const at = argv.indexOf(name);
|
|
149
|
+
return at === -1 ? undefined : argv[at + 1];
|
|
150
|
+
};
|
|
151
|
+
const report = arg("--report");
|
|
152
|
+
if (!report) {
|
|
153
|
+
process.stderr.write("usage: criterion-citations.mjs --report <path> [--criteria-file <path>]\n");
|
|
154
|
+
return 2;
|
|
155
|
+
}
|
|
156
|
+
const repo = readRepository(process.cwd());
|
|
157
|
+
if (!repo) {
|
|
158
|
+
process.stderr.write("not inside a git repository\n");
|
|
159
|
+
return 2;
|
|
160
|
+
}
|
|
161
|
+
let criteriaFile = arg("--criteria-file");
|
|
162
|
+
if (!criteriaFile) {
|
|
163
|
+
const gitDir = git(process.cwd(), ["rev-parse", "--absolute-git-dir"])?.trim();
|
|
164
|
+
criteriaFile = join(gitDir ?? "", "build-reports", "criteria.json");
|
|
165
|
+
}
|
|
166
|
+
let ids;
|
|
167
|
+
try {
|
|
168
|
+
ids = criterionIds(JSON.parse(readFileSync(criteriaFile, "utf8")));
|
|
169
|
+
} catch (err) {
|
|
170
|
+
process.stderr.write(`cannot read the criteria from "${criteriaFile}": ${err.message}\n`);
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
if (!existsSync(report)) {
|
|
174
|
+
process.stderr.write(`the report "${report}" does not exist\n`);
|
|
175
|
+
return 2;
|
|
176
|
+
}
|
|
177
|
+
const citations = buildCitations({ ids, files: repo.files, repository: repo.repository, sha: repo.sha });
|
|
178
|
+
writeFileSync(report, withBlock(readFileSync(report, "utf8"), citations));
|
|
179
|
+
const cited = ids.filter((id) => citations[id].length > 0).length;
|
|
180
|
+
process.stdout.write(`linked ${cited} of ${ids.length} criteria to the tests that cite them in ${report}\n`);
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) process.exitCode = main(process.argv.slice(2));
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
criteria with their proof forms,
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Turns an idea into an approved issue: searches for duplicates, drafts
|
|
5
|
+
acceptance criteria with their proof forms, decides the docs and structural
|
|
6
|
+
verdicts, splits work that is too big for one story, and files it after you
|
|
7
|
+
approve it. Then offers to start the build. Use when you want to write up a
|
|
8
|
+
feature or change before building it. Invoke with
|
|
8
9
|
/spec <what you want built>.
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
Take one idea from a sentence to an issue a person can build from. This file
|
|
12
13
|
is the procedure. The content discipline is the `writing-specs` skill: summary
|
|
13
|
-
block, problem before solution, non-goals
|
|
14
|
-
it, and do not restate it here.
|
|
14
|
+
block, problem before solution, non-goals, Given/When/Then criteria and the
|
|
15
|
+
two verdicts. Follow it, and do not restate it here.
|
|
15
16
|
|
|
16
17
|
This skill files the issue. It does not branch, write code or run tests.
|
|
17
18
|
`/build` does that, on a work item that names the issue.
|
|
@@ -41,31 +42,41 @@ Run them in order. Do not skip the approval.
|
|
|
41
42
|
only where nothing visible changes. A criterion nobody can prove is a
|
|
42
43
|
criterion nobody can build to, so give every one either a proof form or a
|
|
43
44
|
named test.
|
|
44
|
-
3. **
|
|
45
|
+
3. **Verdicts.** Decide the docs verdict and the structural verdict as
|
|
46
|
+
`writing-specs` defines them. Give each a one-line reason. A docs-required
|
|
47
|
+
verdict adds a criterion naming the page to change or add and its proof
|
|
48
|
+
form. Record both verdicts in the issue body, so whoever builds it can
|
|
49
|
+
compare them with the finished change.
|
|
50
|
+
4. **Size.** More than twenty-four criteria is not one story. Above that, show
|
|
45
51
|
the work already split into slices that each stand alone. Slice one carries
|
|
46
52
|
the criteria the others depend on, and the rest become their own specs. The
|
|
47
53
|
user may redraw the split. Skipping it is not on offer.
|
|
48
|
-
|
|
54
|
+
5. **Reader pass.** Send the title and body through one cold rewrite agent. Its
|
|
49
55
|
only inputs are the draft and one writing rule: write for a reader who was
|
|
50
56
|
not in this conversation, lead with the outcome, keep sentences under about
|
|
51
|
-
twenty words, and use plain words.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
twenty words, and use plain words. When the instructions file carries the
|
|
58
|
+
writing rules from the `writing-specs` skill's
|
|
59
|
+
`references/agents-snippet.md`, pass that section verbatim instead. The
|
|
60
|
+
agent never sees this conversation. Criteria keep their exact wording.
|
|
61
|
+
Check that every path, identifier, number and link survived, and restore
|
|
62
|
+
anything the rewrite dropped.
|
|
63
|
+
6. **Checkpoint.** Show the drafted title and body, the criteria with their
|
|
64
|
+
proof forms, and both verdicts with their reasons. Wait for explicit
|
|
65
|
+
approval. Before showing them, check every criterion: it has an id, a
|
|
66
|
+
Given, When and Then, and a proof form or a named test. Check that both
|
|
67
|
+
verdicts are there, each with its reason. A criterion that fails the check
|
|
68
|
+
goes back to step 2, and a missing verdict to step 3. Apply requested
|
|
58
69
|
edits, run the reader pass again over the edited text, and show it again
|
|
59
|
-
until approved. Nothing is filed before this point. A spec the user
|
|
60
|
-
leaves no issue behind.
|
|
61
|
-
|
|
62
|
-
and full criteria. Then file the slices from step
|
|
70
|
+
until approved. Nothing is filed before this point. A spec the user
|
|
71
|
+
rejects leaves no issue behind.
|
|
72
|
+
7. **File it.** Through the team's tracker tool, file the approved title, body
|
|
73
|
+
and full criteria. Then file the slices from step 4, and only the ones the
|
|
63
74
|
user named. The issue is the source of truth from here on. Note its
|
|
64
75
|
reference exactly as the tracker gives it: a number, or a Linear or Jira key.
|
|
65
|
-
|
|
76
|
+
8. **Ask whether to build.** Ask the user exactly one question: build it now, or
|
|
66
77
|
not yet. Never start work unasked, because the user may want to hold the
|
|
67
78
|
issue.
|
|
68
|
-
|
|
79
|
+
9. **Start the build.** On a yes, run `outerlayer work build --issue <reference>`,
|
|
69
80
|
where the reference is the issue number or the Linear or Jira key exactly as
|
|
70
81
|
the user typed it. Print the item number the command reports and the launch
|
|
71
82
|
line:
|
|
@@ -80,5 +91,5 @@ Run them in order. Do not skip the approval.
|
|
|
80
91
|
|
|
81
92
|
## Scope limits
|
|
82
93
|
|
|
83
|
-
One `/spec` files one issue, plus the slices the user approves in step
|
|
94
|
+
One `/spec` files one issue, plus the slices the user approves in step 4. It
|
|
84
95
|
files nothing in another repository. It never creates a branch or edits code.
|