@nodii/dev 0.6.24 → 0.6.25
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 +10 -1
- package/dist/cli.js +3 -1
- package/dist/core.js +1 -1
- package/dist/devops-openapi.json +1 -1
- package/dist/lanes.js +57 -5
- package/dist/safety.js +10 -3
- package/package.json +1 -1
package/dist/lanes.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { promisify } from "node:util";
|
|
3
4
|
import { CliError, encode, devRequest, query, readConfig, requireFlag } from "./core.js";
|
|
4
|
-
import { recordReviewReceipt } from "./safety.js";
|
|
5
|
+
import { recordReviewReceipt, reviewCommitContext } from "./safety.js";
|
|
5
6
|
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
const via = { via: "nodii-dev" };
|
|
@@ -53,6 +54,19 @@ function evidence(flags) {
|
|
|
53
54
|
|
|
54
55
|
const list = (value) => String(value).split(",").map((x) => x.trim()).filter(Boolean);
|
|
55
56
|
|
|
57
|
+
export async function reviewBody(flags, read = (path) => readFile(path, "utf8")) {
|
|
58
|
+
const file = requireFlag(flags, "file");
|
|
59
|
+
let body;
|
|
60
|
+
try {
|
|
61
|
+
body = JSON.parse(await read(file));
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new CliError("REVIEW_FILE_INVALID", `Cannot read review JSON from ${file}: ${error.message}`, 2);
|
|
64
|
+
}
|
|
65
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
66
|
+
throw new CliError("REVIEW_FILE_INVALID", "The review file must contain one JSON object.", 2);
|
|
67
|
+
return { ...body, ...via };
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
/**
|
|
57
71
|
* DEC-0011 (MW1-28.1): the body for `lane non-issue`. A re-route needs at
|
|
58
72
|
* least one issue key or an explicit `none`, and the branch work is either
|
|
@@ -99,7 +113,7 @@ export async function branchCommits(lane) {
|
|
|
99
113
|
}
|
|
100
114
|
}
|
|
101
115
|
|
|
102
|
-
export async function laneCommand(action, args, flags, { request = devRequest, config = readConfig, gitContext = inferGitContext, commitsOf = branchCommits, reviewReceipt = recordReviewReceipt } = {}) {
|
|
116
|
+
export async function laneCommand(action, args, flags, { request = devRequest, config = readConfig, gitContext = inferGitContext, commitsOf = branchCommits, read = (path) => readFile(path, "utf8"), reviewContext = reviewCommitContext, reviewReceipt = recordReviewReceipt } = {}) {
|
|
103
117
|
if (action === "list") {
|
|
104
118
|
const c = flags.mine ? await config() : null;
|
|
105
119
|
const payload = await request("GET", `/api/v1/lanes${query({ kind: flags.kind, status: flags.status, machine: flags.machine, agent: flags.mine ? c.agentId : flags.agent, "waiting-on": flags["waiting-on"], stuck: flags.stuck, cursor: flags.cursor, limit: flags.limit })}`);
|
|
@@ -170,12 +184,50 @@ export async function laneCommand(action, args, flags, { request = devRequest, c
|
|
|
170
184
|
}
|
|
171
185
|
if (action === "land") return { payload: await request("POST", `/api/v1/lanes/${encode(id)}/land`, via), human: `${ref}: recorded on main.` };
|
|
172
186
|
if (action === "review") {
|
|
173
|
-
const
|
|
187
|
+
const body = await reviewBody(flags, read);
|
|
188
|
+
const current = await reviewContext();
|
|
189
|
+
if (body.head !== current.commit || body.base !== current.base)
|
|
190
|
+
throw new CliError(
|
|
191
|
+
"REVIEW_FILE_STALE",
|
|
192
|
+
`The review file covers base ${body.base ?? "(missing)"} and head ${body.head ?? "(missing)"}, but this worktree is base ${current.base} and head ${current.commit}. Review the current diff and write a new receipt.`,
|
|
193
|
+
2,
|
|
194
|
+
);
|
|
195
|
+
const payload = await request("POST", `/api/v1/lanes/${encode(id)}/review`, body);
|
|
174
196
|
try { await reviewReceipt(); }
|
|
175
197
|
catch (error) {
|
|
176
|
-
throw new CliError("REVIEW_RECEIPT_FAILED", `${ref}: Dev recorded the review, but the local push receipt could not be written (${error?.message ?? error}). Re-run nodii-dev lane review ${ref} from its worktree.`, 1);
|
|
198
|
+
throw new CliError("REVIEW_RECEIPT_FAILED", `${ref}: Dev recorded the review, but the local push receipt could not be written (${error?.message ?? error}). Re-run nodii-dev lane review ${ref} --file REVIEW.json from its worktree.`, 1);
|
|
177
199
|
}
|
|
178
|
-
return {
|
|
200
|
+
return {
|
|
201
|
+
payload,
|
|
202
|
+
human: (p) => {
|
|
203
|
+
const findings = p.data.findings ?? [];
|
|
204
|
+
const missed = findings.filter((finding) => finding.missedTrigger === true).length;
|
|
205
|
+
return `${ref}: review recorded for the current commit and origin/main (${p.data.exitReason}; ${findings.length} finding(s), ${missed} missed trigger(s)).`;
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (action === "reviews") {
|
|
210
|
+
const payload = await request("GET", `/api/v1/lanes/${encode(id)}`);
|
|
211
|
+
return {
|
|
212
|
+
payload,
|
|
213
|
+
human: (p) => {
|
|
214
|
+
const reviews = p.data.reviews ?? [];
|
|
215
|
+
return reviews.length
|
|
216
|
+
? reviews
|
|
217
|
+
.map((run) => {
|
|
218
|
+
const missed = run.findings.filter((finding) => finding.missedTrigger === true).length;
|
|
219
|
+
return [
|
|
220
|
+
`${run.createdAt}\t${run.head}\t${run.exitReason}\t${run.findings.length} finding(s)\t${missed} missed trigger(s)`,
|
|
221
|
+
...run.findings.map(
|
|
222
|
+
(finding) =>
|
|
223
|
+
` ${finding.severity}\t${finding.perspective}\t${finding.clauseId ?? "-"}\t${finding.summary}${finding.missedTrigger ? "\tMISSED_TRIGGER" : ""}`,
|
|
224
|
+
),
|
|
225
|
+
].join("\n");
|
|
226
|
+
})
|
|
227
|
+
.join("\n")
|
|
228
|
+
: `No reviews recorded for ${ref}.`;
|
|
229
|
+
},
|
|
230
|
+
};
|
|
179
231
|
}
|
|
180
232
|
if (action === "verify") {
|
|
181
233
|
const value = requireFlag(flags, "dev-check");
|
package/dist/safety.js
CHANGED
|
@@ -370,12 +370,19 @@ export async function safetyPostHook(payload) {
|
|
|
370
370
|
return { recorded: false };
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
/**
|
|
373
|
+
/** The exact commit and origin/main base a review and its local receipt cover. */
|
|
374
|
+
export async function reviewCommitContext(cwd = process.cwd()) {
|
|
375
|
+
return {
|
|
376
|
+
commit: await git(cwd, "rev-parse", "HEAD"),
|
|
377
|
+
base: await git(cwd, "rev-parse", "origin/main"),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Pin the exact commit and origin/main base Dev accepted for review. */
|
|
374
382
|
export async function recordReviewReceipt(cwd = process.cwd()) {
|
|
375
383
|
const path = await gitPrivatePath(cwd, REVIEW_RECEIPT);
|
|
376
384
|
const receipt = {
|
|
377
|
-
|
|
378
|
-
base: await git(cwd, "rev-parse", "origin/main"),
|
|
385
|
+
...(await reviewCommitContext(cwd)),
|
|
379
386
|
recordedAt: new Date().toISOString(),
|
|
380
387
|
};
|
|
381
388
|
await mkdir(dirname(path), { recursive: true });
|