@webappwiz/cli 0.0.8 → 0.0.10
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 +6 -31
- package/{index-rz4spnkb.js → index-8xj4zxqp.js} +4 -4
- package/{index-hz6bwqfy.js → index-htwb54c6.js} +2 -8
- package/{index-r14q0cmx.js → index-pyjg1rtk.js} +145 -34
- package/index.js +5 -35
- package/judge.d.ts +3 -11
- package/judge.js +1 -2
- package/package.json +4 -8
- package/rules.d.ts +0 -10
- package/rules.js +2 -4
- package/webappwiz.js +5 -35
- package/index-4xj59vmx.js +0 -135
- package/index-fh8js68z.js +0 -116
- package/signoff.d.ts +0 -57
- package/signoff.js +0 -7
package/README.md
CHANGED
|
@@ -9,49 +9,24 @@ bunx @webappwiz/cli skills add arbor # install an agent skill
|
|
|
9
9
|
bunx @webappwiz/cli skills update # refresh the ones already installed
|
|
10
10
|
bunx @webappwiz/cli rules ls # every rule there is
|
|
11
11
|
bunx @webappwiz/cli judge . # check a directory against them
|
|
12
|
-
bunx @webappwiz/cli signoff # does this change need a person?
|
|
13
12
|
```
|
|
14
13
|
|
|
15
14
|
## rules
|
|
16
15
|
|
|
17
16
|
Every rule webappwiz judges itself by is named in [`rules.ts`](./rules.ts), as
|
|
18
|
-
`JUDGE_RULES
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
`JUDGE_RULES`, off the classes [`@webappwiz/rules`](../rules/rules) ships.
|
|
18
|
+
There is no config file and no preset: a rule is in that list or it does not
|
|
19
|
+
exist.
|
|
21
20
|
|
|
22
21
|
```
|
|
23
|
-
ID RULE
|
|
24
|
-
no-em-dashes No em dashes
|
|
25
|
-
one-class-per-file One class per file
|
|
26
|
-
visual-work-tested Visual work is tested signoff
|
|
22
|
+
ID RULE LEVEL FILES
|
|
23
|
+
no-em-dashes No em dashes error **/*.ts
|
|
24
|
+
one-class-per-file One class per file error **/*.ts
|
|
27
25
|
```
|
|
28
26
|
|
|
29
27
|
`rules show <id>` prints one in full: its glob, its level, and the document an
|
|
30
28
|
agent is handed verbatim.
|
|
31
29
|
|
|
32
|
-
The `SET` column is which of the two lists a rule is in. `judge` rules are what
|
|
33
|
-
`judge` checks files against, file by file. `signoff` rules have no glob and no
|
|
34
|
-
level because they are about a change rather than a file: `signoff` weighs them,
|
|
35
|
-
and what they answer is whether it needs a person rather than where the code is
|
|
36
|
-
wrong.
|
|
37
|
-
|
|
38
|
-
## signoff
|
|
39
|
-
|
|
40
|
-
Weighs a change against the signoff rules and exits 1 with a reason when one of
|
|
41
|
-
them wants a person to look before it merges. One agent call over the whole
|
|
42
|
-
diff, since that is what these rules are about.
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
bunx @webappwiz/cli signoff # everything since main
|
|
46
|
-
bunx @webappwiz/cli signoff --since HEAD~3 # measured against another ref
|
|
47
|
-
bunx @webappwiz/cli signoff --print # the rules, to apply yourself
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
`--print` is the cheapest signoff there is: the agent about to merge reads the
|
|
51
|
-
rules and weighs its own change, spawning nothing. A project points its agent
|
|
52
|
-
instructions at that rather than at a list of rule ids, which goes stale the
|
|
53
|
-
next time a rule is added.
|
|
54
|
-
|
|
55
30
|
## judge
|
|
56
31
|
|
|
57
32
|
Runs the rules over a directory, one agent call per set of rules sharing a set
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
table
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-pyjg1rtk.js";
|
|
4
4
|
// package.json
|
|
5
|
-
var version = "0.0.
|
|
5
|
+
var version = "0.0.10";
|
|
6
6
|
|
|
7
7
|
// skills/add.ts
|
|
8
8
|
import { ConsoleLogger } from "webappwiz/log";
|
|
@@ -15,7 +15,7 @@ import { dirname } from "node:path";
|
|
|
15
15
|
var arbor_skill_default = `---
|
|
16
16
|
name: arbor
|
|
17
17
|
description: Use the @webappwiz/arbor CLI to land your work on trunk, or a base branch given as an argument, from an isolated git worktree without pull requests. Read this before making any code change in an arbor repository, since it decides where the work happens, and whenever you need to add, claim, merge, remove, list, show, locate, or escalate a task.
|
|
18
|
-
version: 0.0.
|
|
18
|
+
version: 0.0.10
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# Using arbor
|
|
@@ -184,7 +184,7 @@ not squashing.
|
|
|
184
184
|
var webappwiz_skill_default = `---
|
|
185
185
|
name: webappwiz
|
|
186
186
|
description: Check whether the webappwiz package already covers a piece of infrastructure before writing it by hand or adding a dependency for it. Read this before writing any of: time, clocks, durations or timers; logging; id generation; HTTP serving; CLI argument parsing; background tasks or queues; web workers; markdown parsing; typed event emitters; 2D geometry or spatial indexes; filesystem, env or process access; typed RPC over fetch; schema validation; AbortSignal plumbing; disposable resources; browser scroll, animation frames or visibility. Also use whenever the user says webappwiz.
|
|
187
|
-
version: 0.0.
|
|
187
|
+
version: 0.0.10
|
|
188
188
|
---
|
|
189
189
|
|
|
190
190
|
# Using webappwiz
|
|
@@ -17,9 +17,7 @@ import {
|
|
|
17
17
|
ReactiveOverUseState,
|
|
18
18
|
ResourcesAreDisposable,
|
|
19
19
|
SimpleTestSetup,
|
|
20
|
-
|
|
21
|
-
TestsOwnTheirState,
|
|
22
|
-
VisualWorkTested
|
|
20
|
+
TestsOwnTheirState
|
|
23
21
|
} from "@webappwiz/rules/catalog";
|
|
24
22
|
var JUDGE_RULES = defineRules({
|
|
25
23
|
rules: [
|
|
@@ -42,9 +40,5 @@ var JUDGE_RULES = defineRules({
|
|
|
42
40
|
new ResourcesAreDisposable
|
|
43
41
|
]
|
|
44
42
|
});
|
|
45
|
-
var SIGNOFF_RULES = [
|
|
46
|
-
new TestsNotWeakened,
|
|
47
|
-
new VisualWorkTested
|
|
48
|
-
];
|
|
49
43
|
|
|
50
|
-
export { JUDGE_RULES
|
|
44
|
+
export { JUDGE_RULES };
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
changed,
|
|
3
|
-
compact,
|
|
4
|
-
count,
|
|
5
|
-
divider,
|
|
6
|
-
finished,
|
|
7
|
-
mode,
|
|
8
|
-
planned,
|
|
9
|
-
summary,
|
|
10
|
-
table,
|
|
11
|
-
tokens
|
|
12
|
-
} from "./index-4xj59vmx.js";
|
|
13
|
-
|
|
14
1
|
// judge.ts
|
|
15
2
|
import {
|
|
16
3
|
agentCommand,
|
|
@@ -18,17 +5,148 @@ import {
|
|
|
18
5
|
Harness,
|
|
19
6
|
prompt as reviewPrompt
|
|
20
7
|
} from "@webappwiz/rules";
|
|
21
|
-
import { ConsoleLogger, color as
|
|
8
|
+
import { ConsoleLogger, color as color4 } from "webappwiz/log";
|
|
22
9
|
import {
|
|
23
10
|
NodeFs,
|
|
24
11
|
NodeGlob,
|
|
25
|
-
NodePs
|
|
12
|
+
NodePs as NodePs2
|
|
26
13
|
} from "webappwiz/system";
|
|
27
14
|
import { SystemClock } from "webappwiz/time";
|
|
28
15
|
|
|
16
|
+
// changed.ts
|
|
17
|
+
import { NodePs } from "webappwiz/system";
|
|
18
|
+
async function changed(dir, ref, opts = {}) {
|
|
19
|
+
const ps = opts.ps ?? new NodePs;
|
|
20
|
+
const files = new Set;
|
|
21
|
+
for (const argv of [
|
|
22
|
+
["diff", "--name-only", "--diff-filter=d", "--relative", ref],
|
|
23
|
+
["ls-files", "--others", "--exclude-standard"]
|
|
24
|
+
]) {
|
|
25
|
+
for (const line of (await git(ps, dir, argv)).split(`
|
|
26
|
+
`)) {
|
|
27
|
+
if (line !== "") {
|
|
28
|
+
files.add(line);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
async function git(ps, dir, argv) {
|
|
35
|
+
const { exitCode, stdout, stderr } = await ps.spawnCapture([
|
|
36
|
+
"git",
|
|
37
|
+
"-C",
|
|
38
|
+
dir,
|
|
39
|
+
...argv
|
|
40
|
+
]);
|
|
41
|
+
if (exitCode !== 0) {
|
|
42
|
+
throw new Error(`git ${argv[0]} failed in ${dir}: ${stderr.trim() || `exit ${exitCode}`}`);
|
|
43
|
+
}
|
|
44
|
+
return stdout;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// mode.ts
|
|
48
|
+
function mode({ print, agent, exec }) {
|
|
49
|
+
const named = [
|
|
50
|
+
print === true ? "--print" : undefined,
|
|
51
|
+
agent === undefined ? undefined : "--agent",
|
|
52
|
+
exec === undefined ? undefined : "--exec"
|
|
53
|
+
].filter((flag) => flag !== undefined);
|
|
54
|
+
if (named.length > 1) {
|
|
55
|
+
throw new Error(`${named.join(" and ")} are different things to do with one run, so pass one`);
|
|
56
|
+
}
|
|
57
|
+
return print === true ? "print" : "run";
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
// progress.ts
|
|
30
|
-
import { color } from "webappwiz/log";
|
|
61
|
+
import { color as color3 } from "webappwiz/log";
|
|
31
62
|
import { Duration, SystemTimer } from "webappwiz/time";
|
|
63
|
+
|
|
64
|
+
// report.ts
|
|
65
|
+
import { color as color2 } from "webappwiz/log";
|
|
66
|
+
|
|
67
|
+
// table.ts
|
|
68
|
+
import { color } from "webappwiz/log";
|
|
69
|
+
var table = (rows) => {
|
|
70
|
+
const width = (cell) => color.strip(cell).length;
|
|
71
|
+
const widths = rows[0]?.map((_, i) => Math.max(...rows.map((row) => width(row[i] ?? ""))));
|
|
72
|
+
return rows.map((row) => row.map((cell, i) => cell.padEnd((widths?.[i] ?? 0) + cell.length - width(cell))).join(" ").trimEnd());
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// report.ts
|
|
76
|
+
var count = (total, word) => `${total} ${word}${total === 1 ? "" : "s"}`;
|
|
77
|
+
var divider = (name) => {
|
|
78
|
+
if (name === undefined) {
|
|
79
|
+
return color2.dim("-".repeat(72));
|
|
80
|
+
}
|
|
81
|
+
const opening = `--- ${name} `;
|
|
82
|
+
return color2.dim(opening.padEnd(Math.max(72, opening.length + 3), "-"));
|
|
83
|
+
};
|
|
84
|
+
var compact = new Intl.NumberFormat("en", { notation: "compact" });
|
|
85
|
+
var tokens = (bytes) => Math.ceil(bytes / 4);
|
|
86
|
+
function planned({
|
|
87
|
+
files,
|
|
88
|
+
rules,
|
|
89
|
+
calls,
|
|
90
|
+
estimate,
|
|
91
|
+
concurrency,
|
|
92
|
+
agent
|
|
93
|
+
}) {
|
|
94
|
+
const rows = [
|
|
95
|
+
[color2.dim("files"), String(files)],
|
|
96
|
+
[color2.dim("rules"), String(rules)],
|
|
97
|
+
[color2.dim("calls"), String(calls)],
|
|
98
|
+
...concurrency === undefined ? [] : [[color2.dim("workers"), String(concurrency)]],
|
|
99
|
+
[color2.dim("reading"), `${compact.format(estimate)}+ tokens`]
|
|
100
|
+
];
|
|
101
|
+
if (agent !== undefined) {
|
|
102
|
+
rows.push([color2.dim("agent"), agent]);
|
|
103
|
+
}
|
|
104
|
+
return ["", ...table(rows).map((line) => ` ${line}`), ""];
|
|
105
|
+
}
|
|
106
|
+
function finished({
|
|
107
|
+
rules,
|
|
108
|
+
files,
|
|
109
|
+
violations,
|
|
110
|
+
took,
|
|
111
|
+
tokens: tokens2,
|
|
112
|
+
worker,
|
|
113
|
+
workerTokens,
|
|
114
|
+
done,
|
|
115
|
+
total
|
|
116
|
+
}) {
|
|
117
|
+
const heading = `${color2.gray(`[${done}/${total}]`)} ` + color2.gray(`(${count(rules.length, "rule")}, ${count(files, "file")})`);
|
|
118
|
+
const spent = tokens2 === undefined ? "" : ` ${compact.format(tokens2)} tokens` + (workerTokens === undefined ? "" : ` (w${worker + 1}: ${compact.format(workerTokens)})`);
|
|
119
|
+
const tail = `${color2.gray(`in ${took.human()}${spent}`)}`;
|
|
120
|
+
if (violations.length === 0) {
|
|
121
|
+
return [`${color2.green("✓")} ${heading}: clean ${tail}`];
|
|
122
|
+
}
|
|
123
|
+
return [
|
|
124
|
+
`${color2.red("✗")} ${heading}: ${count(violations.length, "problem")} ${tail}`,
|
|
125
|
+
...violations.flatMap(finding)
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
function finding(violation) {
|
|
129
|
+
const level = violation.level === "error" ? color2.red("error") : color2.yellow(violation.level);
|
|
130
|
+
const lines = [
|
|
131
|
+
` ${color2.bold(`${violation.file}:${violation.line}`)} ${level} ${violation.message} ${color2.gray(`(${violation.id})`)}`
|
|
132
|
+
];
|
|
133
|
+
if (violation.code !== "") {
|
|
134
|
+
lines.push(color2.gray(` │ ${violation.code}`));
|
|
135
|
+
}
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
function summary(violations, took, tokens2) {
|
|
139
|
+
const spent = tokens2 === undefined ? "" : ` ${compact.format(tokens2)} tokens total`;
|
|
140
|
+
const elapsed = color2.gray(`in ${took.human()}${spent}`);
|
|
141
|
+
if (violations.length === 0) {
|
|
142
|
+
return `${color2.green("✓ no violations")} ${elapsed}`;
|
|
143
|
+
}
|
|
144
|
+
const errors = violations.filter((violation) => violation.level === "error").length;
|
|
145
|
+
const line = `✖ ${count(violations.length, "problem")} (${count(errors, "error")}, ${count(violations.length - errors, "warning")})`;
|
|
146
|
+
return `${errors > 0 ? color2.red(line) : color2.yellow(line)} ${elapsed}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// progress.ts
|
|
32
150
|
var terminal = () => ({
|
|
33
151
|
tty: process.stdout.isTTY === true,
|
|
34
152
|
write: (text) => process.stdout.write(text)
|
|
@@ -38,11 +156,11 @@ var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "
|
|
|
38
156
|
function render(view, frame = 0) {
|
|
39
157
|
const spin = view.files === 0 ? " " : FRAMES[frame % FRAMES.length] ?? " ";
|
|
40
158
|
const filled = Math.round(BAR * view.done / Math.max(1, view.total));
|
|
41
|
-
const bar =
|
|
159
|
+
const bar = color3.green("█".repeat(filled)) + color3.dim("░".repeat(BAR - filled));
|
|
42
160
|
const judging = view.files === 0 ? "" : ` · judging ${count(view.files, "file")}`;
|
|
43
161
|
const spent = view.tokens === undefined ? "" : ` · ${compact.format(view.tokens)} tokens`;
|
|
44
162
|
const found = view.done === 0 ? "" : ` · ${view.problems === 0 ? "clean so far" : count(view.problems, "problem")}`;
|
|
45
|
-
return `${
|
|
163
|
+
return `${color3.green(spin)} ${bar} ${color3.gray(`${view.done}/${view.total} calls${judging}${spent}${found}`)}`;
|
|
46
164
|
}
|
|
47
165
|
|
|
48
166
|
class Progress {
|
|
@@ -104,12 +222,10 @@ class Progress {
|
|
|
104
222
|
|
|
105
223
|
// judge.ts
|
|
106
224
|
var estimated = (reviews) => tokens(reviews.reduce((bytes, review) => bytes + review.bytes, 0));
|
|
107
|
-
var isFileRule = (rule) => ("files" in rule);
|
|
108
225
|
var title = (rule) => /^#\s+(.+)$/m.exec(rule.document)?.[1]?.trim() ?? rule.id;
|
|
109
226
|
|
|
110
227
|
class JudgeCommands {
|
|
111
228
|
rules;
|
|
112
|
-
signoffRules;
|
|
113
229
|
screen;
|
|
114
230
|
log;
|
|
115
231
|
fs;
|
|
@@ -118,38 +234,33 @@ class JudgeCommands {
|
|
|
118
234
|
glob;
|
|
119
235
|
constructor(rules, opts = {}) {
|
|
120
236
|
this.rules = rules;
|
|
121
|
-
this.signoffRules = opts.signoffRules ?? [];
|
|
122
237
|
this.screen = opts.screen ?? terminal();
|
|
123
238
|
this.log = opts.log ?? new ConsoleLogger;
|
|
124
239
|
this.fs = opts.fs ?? new NodeFs;
|
|
125
|
-
this.ps = opts.ps ?? new
|
|
240
|
+
this.ps = opts.ps ?? new NodePs2;
|
|
126
241
|
this.clock = opts.clock ?? new SystemClock;
|
|
127
242
|
this.glob = opts.glob ?? new NodeGlob;
|
|
128
243
|
}
|
|
129
244
|
ls() {
|
|
130
|
-
const rows = [["id", "rule", "
|
|
245
|
+
const rows = [["id", "rule", "level", "files"].map(color4.dim)];
|
|
131
246
|
for (const rule of this.rules.rules) {
|
|
132
|
-
rows.push([rule.id, title(rule),
|
|
133
|
-
}
|
|
134
|
-
for (const rule of this.signoffRules) {
|
|
135
|
-
rows.push([rule.id, title(rule), "signoff", "", ""]);
|
|
247
|
+
rows.push([rule.id, title(rule), rule.level, rule.files]);
|
|
136
248
|
}
|
|
137
249
|
this.log.info(table(rows).join(`
|
|
138
250
|
`));
|
|
139
251
|
}
|
|
140
252
|
show(opts) {
|
|
141
|
-
const all =
|
|
253
|
+
const all = this.rules.rules;
|
|
142
254
|
const rule = all.find((candidate) => candidate.id === opts.id);
|
|
143
255
|
if (!rule) {
|
|
144
256
|
throw new Error(`no rule "${opts.id}". Known ids: ${all.map((candidate) => candidate.id).join(", ")}`);
|
|
145
257
|
}
|
|
146
258
|
const rows = [
|
|
147
|
-
[
|
|
148
|
-
[
|
|
259
|
+
[color4.dim("id"), rule.id],
|
|
260
|
+
[color4.dim("rule"), title(rule)],
|
|
261
|
+
[color4.dim("level"), rule.level],
|
|
262
|
+
[color4.dim("files"), rule.files]
|
|
149
263
|
];
|
|
150
|
-
if (isFileRule(rule)) {
|
|
151
|
-
rows.push([color2.dim("level"), rule.level], [color2.dim("files"), rule.files]);
|
|
152
|
-
}
|
|
153
264
|
this.log.info(table(rows).join(`
|
|
154
265
|
`));
|
|
155
266
|
this.log.info("");
|
|
@@ -259,4 +370,4 @@ ${divider()}`);
|
|
|
259
370
|
}
|
|
260
371
|
}
|
|
261
372
|
|
|
262
|
-
export { JudgeCommands };
|
|
373
|
+
export { table, JudgeCommands };
|
package/index.js
CHANGED
|
@@ -6,18 +6,13 @@ import {
|
|
|
6
6
|
update,
|
|
7
7
|
update1 as update2,
|
|
8
8
|
version
|
|
9
|
-
} from "./index-
|
|
9
|
+
} from "./index-8xj4zxqp.js";
|
|
10
10
|
import {
|
|
11
|
-
JUDGE_RULES
|
|
12
|
-
|
|
13
|
-
} from "./index-hz6bwqfy.js";
|
|
11
|
+
JUDGE_RULES
|
|
12
|
+
} from "./index-htwb54c6.js";
|
|
14
13
|
import {
|
|
15
14
|
JudgeCommands
|
|
16
|
-
} from "./index-
|
|
17
|
-
import {
|
|
18
|
-
Signoff
|
|
19
|
-
} from "./index-fh8js68z.js";
|
|
20
|
-
import"./index-4xj59vmx.js";
|
|
15
|
+
} from "./index-pyjg1rtk.js";
|
|
21
16
|
|
|
22
17
|
// index.ts
|
|
23
18
|
import { NodeFs, NodeGlob } from "webappwiz/system";
|
|
@@ -35,14 +30,7 @@ webappwiz.command("update").description("pin every webappwiz dependency in a tre
|
|
|
35
30
|
default: version,
|
|
36
31
|
description: "version to pin to"
|
|
37
32
|
}).action((opts, { log, fs }) => update2({ ...opts, log, fs }));
|
|
38
|
-
var judge = ({ log, fs, ps, clock, glob }) => new JudgeCommands(JUDGE_RULES, {
|
|
39
|
-
signoffRules: SIGNOFF_RULES,
|
|
40
|
-
log,
|
|
41
|
-
fs,
|
|
42
|
-
ps,
|
|
43
|
-
clock,
|
|
44
|
-
glob
|
|
45
|
-
});
|
|
33
|
+
var judge = ({ log, fs, ps, clock, glob }) => new JudgeCommands(JUDGE_RULES, { log, fs, ps, clock, glob });
|
|
46
34
|
webappwiz.command("judge").description("check a directory against the config, one agent per glob").arg("dir", t.string(), {
|
|
47
35
|
default: ".",
|
|
48
36
|
description: "directory to judge (default: .)"
|
|
@@ -64,24 +52,6 @@ webappwiz.command("judge").description("check a directory against the config, on
|
|
|
64
52
|
default: false,
|
|
65
53
|
description: "line-by-line output with no live progress block"
|
|
66
54
|
}).action((opts, deps) => judge(deps).judge(opts));
|
|
67
|
-
webappwiz.command("signoff").description("weigh a change against the rules that ask for a person").arg("dir", t.string(), {
|
|
68
|
-
default: ".",
|
|
69
|
-
description: "directory whose change is weighed (default: .)"
|
|
70
|
-
}).option("agent", t.optional(t.enum(Object.keys(AGENTS))), {
|
|
71
|
-
description: "model to weigh it with (default: the config's agent)"
|
|
72
|
-
}).option("exec", t.optional(t.string()), {
|
|
73
|
-
description: "command the prompt is passed to, instead of --agent"
|
|
74
|
-
}).option("print", t.boolean(), {
|
|
75
|
-
default: false,
|
|
76
|
-
description: "print the rules to apply yourself, and run no agent"
|
|
77
|
-
}).option("since", t.string(), {
|
|
78
|
-
default: "main",
|
|
79
|
-
description: "the ref the change is measured against"
|
|
80
|
-
}).action((opts, { log, ps, clock }) => new Signoff(SIGNOFF_RULES, JUDGE_RULES.agent, {
|
|
81
|
-
log,
|
|
82
|
-
ps,
|
|
83
|
-
clock
|
|
84
|
-
}).run(opts));
|
|
85
55
|
var rules = webappwiz.group("rules").description("list and print the rules, to run or to read yourself");
|
|
86
56
|
rules.command("ls").description("list the rules").action((_opts, deps) => judge(deps).ls());
|
|
87
57
|
rules.command("show").description("print one rule in full, by the id `rules ls` gives it").arg("id", t.string(), { description: "rule id" }).action((opts, deps) => judge(deps).show(opts));
|
package/judge.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type RuleSet } from "@webappwiz/rules";
|
|
2
2
|
import { type Logger } from "webappwiz/log";
|
|
3
3
|
import { type Fs, type Glob, type Ps } from "webappwiz/system";
|
|
4
4
|
import { type Clock } from "webappwiz/time";
|
|
@@ -25,8 +25,6 @@ export interface JudgeOptions {
|
|
|
25
25
|
}
|
|
26
26
|
/** What a `JudgeCommands` runs through, and what else it lists. */
|
|
27
27
|
export interface JudgeCommandsOptions {
|
|
28
|
-
/** Rules only a reader applies, listed beside the ones a run checks. */
|
|
29
|
-
signoffRules?: Rule[];
|
|
30
28
|
/** Where live progress draws; this process's terminal by default. */
|
|
31
29
|
screen?: Screen;
|
|
32
30
|
log?: Logger;
|
|
@@ -37,7 +35,6 @@ export interface JudgeCommandsOptions {
|
|
|
37
35
|
}
|
|
38
36
|
export declare class JudgeCommands {
|
|
39
37
|
private rules;
|
|
40
|
-
private signoffRules;
|
|
41
38
|
private screen;
|
|
42
39
|
private log;
|
|
43
40
|
private fs;
|
|
@@ -45,16 +42,11 @@ export declare class JudgeCommands {
|
|
|
45
42
|
private clock;
|
|
46
43
|
private glob;
|
|
47
44
|
constructor(rules: RuleSet, opts?: JudgeCommandsOptions);
|
|
48
|
-
/**
|
|
49
|
-
* Lists every rule there is, one row each, ids first for citing. The rules a
|
|
50
|
-
* run checks and the ones only a reader applies are one list with a SET
|
|
51
|
-
* column, because "what rules are there" is one question.
|
|
52
|
-
*/
|
|
45
|
+
/** Lists every rule there is, one row each, ids first for citing. */
|
|
53
46
|
ls(): void;
|
|
54
47
|
/**
|
|
55
48
|
* Prints one rule in full: what it covers, and the document an agent is
|
|
56
|
-
* given, verbatim. Take the id from `rules ls` or from a finding.
|
|
57
|
-
* how a reader applies a rule nothing runs for them.
|
|
49
|
+
* given, verbatim. Take the id from `rules ls` or from a finding.
|
|
58
50
|
*/
|
|
59
51
|
show(opts: ShowOptions): void;
|
|
60
52
|
/**
|
package/judge.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webappwiz/cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "The webappwiz CLI: judge code against rules
|
|
3
|
+
"version": "0.0.10",
|
|
4
|
+
"description": "The webappwiz CLI: judge code against rules and manage agent skills",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jared Johnson",
|
|
7
7
|
"repository": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@webappwiz/rules": "^0.0.
|
|
21
|
-
"webappwiz": "^0.0.
|
|
20
|
+
"@webappwiz/rules": "^0.0.10",
|
|
21
|
+
"webappwiz": "^0.0.10"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"typescript": "^7"
|
|
@@ -41,10 +41,6 @@
|
|
|
41
41
|
"./judge": {
|
|
42
42
|
"types": "./judge.d.ts",
|
|
43
43
|
"default": "./judge.js"
|
|
44
|
-
},
|
|
45
|
-
"./signoff": {
|
|
46
|
-
"types": "./signoff.d.ts",
|
|
47
|
-
"default": "./signoff.js"
|
|
48
44
|
}
|
|
49
45
|
},
|
|
50
46
|
"bin": {
|
package/rules.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type Rule } from "@webappwiz/rules";
|
|
2
1
|
/**
|
|
3
2
|
* Every rule webappwiz judges itself by, named one by one. There is no preset
|
|
4
3
|
* to spread and nothing runs implicitly: a rule is here or it does not run.
|
|
@@ -9,12 +8,3 @@ import { type Rule } from "@webappwiz/rules";
|
|
|
9
8
|
* module for one of these to import.
|
|
10
9
|
*/
|
|
11
10
|
export declare const JUDGE_RULES: import("@webappwiz/rules").RuleSet;
|
|
12
|
-
/**
|
|
13
|
-
* What an agent weighs before merging a change, rather than what it checks a
|
|
14
|
-
* file against: whether the change needs a person to look at it.
|
|
15
|
-
*
|
|
16
|
-
* No command runs these. They are documents an agent reads and applies itself,
|
|
17
|
-
* which is why they carry no glob and no check, and `wiz rules show` prints
|
|
18
|
-
* them the same as any other.
|
|
19
|
-
*/
|
|
20
|
-
export declare const SIGNOFF_RULES: Rule[];
|
package/rules.js
CHANGED
package/webappwiz.js
CHANGED
|
@@ -4,18 +4,13 @@ import {
|
|
|
4
4
|
update,
|
|
5
5
|
update1 as update2,
|
|
6
6
|
version
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-8xj4zxqp.js";
|
|
8
8
|
import {
|
|
9
|
-
JUDGE_RULES
|
|
10
|
-
|
|
11
|
-
} from "./index-hz6bwqfy.js";
|
|
9
|
+
JUDGE_RULES
|
|
10
|
+
} from "./index-htwb54c6.js";
|
|
12
11
|
import {
|
|
13
12
|
JudgeCommands
|
|
14
|
-
} from "./index-
|
|
15
|
-
import {
|
|
16
|
-
Signoff
|
|
17
|
-
} from "./index-fh8js68z.js";
|
|
18
|
-
import"./index-4xj59vmx.js";
|
|
13
|
+
} from "./index-pyjg1rtk.js";
|
|
19
14
|
|
|
20
15
|
// webappwiz.ts
|
|
21
16
|
import { AGENTS } from "@webappwiz/rules";
|
|
@@ -29,14 +24,7 @@ webappwiz.command("update").description("pin every webappwiz dependency in a tre
|
|
|
29
24
|
default: version,
|
|
30
25
|
description: "version to pin to"
|
|
31
26
|
}).action((opts, { log, fs }) => update2({ ...opts, log, fs }));
|
|
32
|
-
var judge = ({ log, fs, ps, clock, glob }) => new JudgeCommands(JUDGE_RULES, {
|
|
33
|
-
signoffRules: SIGNOFF_RULES,
|
|
34
|
-
log,
|
|
35
|
-
fs,
|
|
36
|
-
ps,
|
|
37
|
-
clock,
|
|
38
|
-
glob
|
|
39
|
-
});
|
|
27
|
+
var judge = ({ log, fs, ps, clock, glob }) => new JudgeCommands(JUDGE_RULES, { log, fs, ps, clock, glob });
|
|
40
28
|
webappwiz.command("judge").description("check a directory against the config, one agent per glob").arg("dir", t.string(), {
|
|
41
29
|
default: ".",
|
|
42
30
|
description: "directory to judge (default: .)"
|
|
@@ -58,24 +46,6 @@ webappwiz.command("judge").description("check a directory against the config, on
|
|
|
58
46
|
default: false,
|
|
59
47
|
description: "line-by-line output with no live progress block"
|
|
60
48
|
}).action((opts, deps) => judge(deps).judge(opts));
|
|
61
|
-
webappwiz.command("signoff").description("weigh a change against the rules that ask for a person").arg("dir", t.string(), {
|
|
62
|
-
default: ".",
|
|
63
|
-
description: "directory whose change is weighed (default: .)"
|
|
64
|
-
}).option("agent", t.optional(t.enum(Object.keys(AGENTS))), {
|
|
65
|
-
description: "model to weigh it with (default: the config's agent)"
|
|
66
|
-
}).option("exec", t.optional(t.string()), {
|
|
67
|
-
description: "command the prompt is passed to, instead of --agent"
|
|
68
|
-
}).option("print", t.boolean(), {
|
|
69
|
-
default: false,
|
|
70
|
-
description: "print the rules to apply yourself, and run no agent"
|
|
71
|
-
}).option("since", t.string(), {
|
|
72
|
-
default: "main",
|
|
73
|
-
description: "the ref the change is measured against"
|
|
74
|
-
}).action((opts, { log, ps, clock }) => new Signoff(SIGNOFF_RULES, JUDGE_RULES.agent, {
|
|
75
|
-
log,
|
|
76
|
-
ps,
|
|
77
|
-
clock
|
|
78
|
-
}).run(opts));
|
|
79
49
|
var rules = webappwiz.group("rules").description("list and print the rules, to run or to read yourself");
|
|
80
50
|
rules.command("ls").description("list the rules").action((_opts, deps) => judge(deps).ls());
|
|
81
51
|
rules.command("show").description("print one rule in full, by the id `rules ls` gives it").arg("id", t.string(), { description: "rule id" }).action((opts, deps) => judge(deps).show(opts));
|
package/index-4xj59vmx.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
// changed.ts
|
|
2
|
-
import { NodePs } from "webappwiz/system";
|
|
3
|
-
async function changed(dir, ref, opts = {}) {
|
|
4
|
-
const ps = opts.ps ?? new NodePs;
|
|
5
|
-
const files = new Set;
|
|
6
|
-
for (const argv of [
|
|
7
|
-
["diff", "--name-only", "--diff-filter=d", "--relative", ref],
|
|
8
|
-
["ls-files", "--others", "--exclude-standard"]
|
|
9
|
-
]) {
|
|
10
|
-
for (const line of (await git(ps, dir, argv)).split(`
|
|
11
|
-
`)) {
|
|
12
|
-
if (line !== "") {
|
|
13
|
-
files.add(line);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return files;
|
|
18
|
-
}
|
|
19
|
-
async function diff(dir, ref, opts = {}) {
|
|
20
|
-
const ps = opts.ps ?? new NodePs;
|
|
21
|
-
const patch = await git(ps, dir, ["diff", "--relative", ref]);
|
|
22
|
-
const added = (await git(ps, dir, ["ls-files", "--others", "--exclude-standard"])).split(`
|
|
23
|
-
`).filter((line) => line !== "");
|
|
24
|
-
return { patch: patch.trim(), added };
|
|
25
|
-
}
|
|
26
|
-
async function git(ps, dir, argv) {
|
|
27
|
-
const { exitCode, stdout, stderr } = await ps.spawnCapture([
|
|
28
|
-
"git",
|
|
29
|
-
"-C",
|
|
30
|
-
dir,
|
|
31
|
-
...argv
|
|
32
|
-
]);
|
|
33
|
-
if (exitCode !== 0) {
|
|
34
|
-
throw new Error(`git ${argv[0]} failed in ${dir}: ${stderr.trim() || `exit ${exitCode}`}`);
|
|
35
|
-
}
|
|
36
|
-
return stdout;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// mode.ts
|
|
40
|
-
function mode({ print, agent, exec }) {
|
|
41
|
-
const named = [
|
|
42
|
-
print === true ? "--print" : undefined,
|
|
43
|
-
agent === undefined ? undefined : "--agent",
|
|
44
|
-
exec === undefined ? undefined : "--exec"
|
|
45
|
-
].filter((flag) => flag !== undefined);
|
|
46
|
-
if (named.length > 1) {
|
|
47
|
-
throw new Error(`${named.join(" and ")} are different things to do with one run, so pass one`);
|
|
48
|
-
}
|
|
49
|
-
return print === true ? "print" : "run";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// table.ts
|
|
53
|
-
import { color } from "webappwiz/log";
|
|
54
|
-
var table = (rows) => {
|
|
55
|
-
const width = (cell) => color.strip(cell).length;
|
|
56
|
-
const widths = rows[0]?.map((_, i) => Math.max(...rows.map((row) => width(row[i] ?? ""))));
|
|
57
|
-
return rows.map((row) => row.map((cell, i) => cell.padEnd((widths?.[i] ?? 0) + cell.length - width(cell))).join(" ").trimEnd());
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// report.ts
|
|
61
|
-
import { color as color2 } from "webappwiz/log";
|
|
62
|
-
var count = (total, word) => `${total} ${word}${total === 1 ? "" : "s"}`;
|
|
63
|
-
var divider = (name) => {
|
|
64
|
-
if (name === undefined) {
|
|
65
|
-
return color2.dim("-".repeat(72));
|
|
66
|
-
}
|
|
67
|
-
const opening = `--- ${name} `;
|
|
68
|
-
return color2.dim(opening.padEnd(Math.max(72, opening.length + 3), "-"));
|
|
69
|
-
};
|
|
70
|
-
var compact = new Intl.NumberFormat("en", { notation: "compact" });
|
|
71
|
-
var tokens = (bytes) => Math.ceil(bytes / 4);
|
|
72
|
-
function planned({
|
|
73
|
-
files,
|
|
74
|
-
rules,
|
|
75
|
-
calls,
|
|
76
|
-
estimate,
|
|
77
|
-
concurrency,
|
|
78
|
-
agent
|
|
79
|
-
}) {
|
|
80
|
-
const rows = [
|
|
81
|
-
[color2.dim("files"), String(files)],
|
|
82
|
-
[color2.dim("rules"), String(rules)],
|
|
83
|
-
[color2.dim("calls"), String(calls)],
|
|
84
|
-
...concurrency === undefined ? [] : [[color2.dim("workers"), String(concurrency)]],
|
|
85
|
-
[color2.dim("reading"), `${compact.format(estimate)}+ tokens`]
|
|
86
|
-
];
|
|
87
|
-
if (agent !== undefined) {
|
|
88
|
-
rows.push([color2.dim("agent"), agent]);
|
|
89
|
-
}
|
|
90
|
-
return ["", ...table(rows).map((line) => ` ${line}`), ""];
|
|
91
|
-
}
|
|
92
|
-
function finished({
|
|
93
|
-
rules,
|
|
94
|
-
files,
|
|
95
|
-
violations,
|
|
96
|
-
took,
|
|
97
|
-
tokens: tokens2,
|
|
98
|
-
worker,
|
|
99
|
-
workerTokens,
|
|
100
|
-
done,
|
|
101
|
-
total
|
|
102
|
-
}) {
|
|
103
|
-
const heading = `${color2.gray(`[${done}/${total}]`)} ` + color2.gray(`(${count(rules.length, "rule")}, ${count(files, "file")})`);
|
|
104
|
-
const spent = tokens2 === undefined ? "" : ` ${compact.format(tokens2)} tokens` + (workerTokens === undefined ? "" : ` (w${worker + 1}: ${compact.format(workerTokens)})`);
|
|
105
|
-
const tail = `${color2.gray(`in ${took.human()}${spent}`)}`;
|
|
106
|
-
if (violations.length === 0) {
|
|
107
|
-
return [`${color2.green("✓")} ${heading}: clean ${tail}`];
|
|
108
|
-
}
|
|
109
|
-
return [
|
|
110
|
-
`${color2.red("✗")} ${heading}: ${count(violations.length, "problem")} ${tail}`,
|
|
111
|
-
...violations.flatMap(finding)
|
|
112
|
-
];
|
|
113
|
-
}
|
|
114
|
-
function finding(violation) {
|
|
115
|
-
const level = violation.level === "error" ? color2.red("error") : color2.yellow(violation.level);
|
|
116
|
-
const lines = [
|
|
117
|
-
` ${color2.bold(`${violation.file}:${violation.line}`)} ${level} ${violation.message} ${color2.gray(`(${violation.id})`)}`
|
|
118
|
-
];
|
|
119
|
-
if (violation.code !== "") {
|
|
120
|
-
lines.push(color2.gray(` │ ${violation.code}`));
|
|
121
|
-
}
|
|
122
|
-
return lines;
|
|
123
|
-
}
|
|
124
|
-
function summary(violations, took, tokens2) {
|
|
125
|
-
const spent = tokens2 === undefined ? "" : ` ${compact.format(tokens2)} tokens total`;
|
|
126
|
-
const elapsed = color2.gray(`in ${took.human()}${spent}`);
|
|
127
|
-
if (violations.length === 0) {
|
|
128
|
-
return `${color2.green("✓ no violations")} ${elapsed}`;
|
|
129
|
-
}
|
|
130
|
-
const errors = violations.filter((violation) => violation.level === "error").length;
|
|
131
|
-
const line = `✖ ${count(violations.length, "problem")} (${count(errors, "error")}, ${count(violations.length - errors, "warning")})`;
|
|
132
|
-
return `${errors > 0 ? color2.red(line) : color2.yellow(line)} ${elapsed}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export { changed, diff, mode, table, count, divider, compact, tokens, planned, finished, summary };
|
package/index-fh8js68z.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
count,
|
|
3
|
-
diff,
|
|
4
|
-
divider,
|
|
5
|
-
mode,
|
|
6
|
-
tokens
|
|
7
|
-
} from "./index-4xj59vmx.js";
|
|
8
|
-
|
|
9
|
-
// signoff.ts
|
|
10
|
-
import {
|
|
11
|
-
agentCommand,
|
|
12
|
-
Harness,
|
|
13
|
-
prompt as reviewPrompt
|
|
14
|
-
} from "@webappwiz/rules";
|
|
15
|
-
import { ConsoleLogger, color } from "webappwiz/log";
|
|
16
|
-
import { NodePs } from "webappwiz/system";
|
|
17
|
-
import { SystemClock } from "webappwiz/time";
|
|
18
|
-
class Signoff {
|
|
19
|
-
rules;
|
|
20
|
-
defaultAgent;
|
|
21
|
-
log;
|
|
22
|
-
ps;
|
|
23
|
-
clock;
|
|
24
|
-
constructor(rules, defaultAgent, opts = {}) {
|
|
25
|
-
this.rules = rules;
|
|
26
|
-
this.defaultAgent = defaultAgent;
|
|
27
|
-
this.log = opts.log ?? new ConsoleLogger;
|
|
28
|
-
this.ps = opts.ps ?? new NodePs;
|
|
29
|
-
this.clock = opts.clock ?? new SystemClock;
|
|
30
|
-
}
|
|
31
|
-
async run(opts) {
|
|
32
|
-
if (mode(opts) === "print") {
|
|
33
|
-
this.print();
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const dir = opts.dir.replace(/\/+$/, "") || "/";
|
|
37
|
-
const { patch, added } = await diff(dir, opts.since, { ps: this.ps });
|
|
38
|
-
if (patch === "" && added.length === 0) {
|
|
39
|
-
this.log.info(`nothing has changed since ${opts.since}`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const agent = agentCommand(opts.exec === undefined ? { agent: opts.agent ?? this.defaultAgent } : opts);
|
|
43
|
-
const review = this.review(patch, added, opts.since);
|
|
44
|
-
this.log.info(`weighing ${opts.since}..working tree against ` + `${count(this.rules.length, "rule")}, reading ` + `${tokens(review.bytes ?? 0)}+ tokens with ${agent.label}`);
|
|
45
|
-
this.say(await this.judge(review, agent, dir));
|
|
46
|
-
}
|
|
47
|
-
async judge(review, agent, dir) {
|
|
48
|
-
const harness = new Harness({
|
|
49
|
-
log: this.log,
|
|
50
|
-
ps: this.ps,
|
|
51
|
-
clock: this.clock
|
|
52
|
-
});
|
|
53
|
-
harness.events.on("finished", ({ took, tokens: read }) => {
|
|
54
|
-
const spent = read === undefined ? "" : ` ${read} tokens`;
|
|
55
|
-
this.log.info(color.gray(`read in ${took.human()}${spent}`));
|
|
56
|
-
});
|
|
57
|
-
return await harness.run([review], agent, { cwd: dir });
|
|
58
|
-
}
|
|
59
|
-
say(findings) {
|
|
60
|
-
if (findings.length === 0) {
|
|
61
|
-
this.log.info(color.green("✓ nothing in this change needs a person"));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
for (const finding of findings) {
|
|
65
|
-
const where = finding.file === undefined ? "" : `${color.bold(finding.file + (finding.line === undefined ? "" : `:${finding.line}`))} `;
|
|
66
|
-
this.log.info(` ${where}${finding.message} ${color.gray(`(${finding.rule})`)}`);
|
|
67
|
-
}
|
|
68
|
-
throw new Error(`${count(findings.length, "reason")} to escalate rather than merge`);
|
|
69
|
-
}
|
|
70
|
-
review(patch, added, ref) {
|
|
71
|
-
const draft = {
|
|
72
|
-
rules: this.rules,
|
|
73
|
-
label: "signoff",
|
|
74
|
-
context: [
|
|
75
|
-
`The change, as \`git diff ${ref}\` prints it:`,
|
|
76
|
-
`\`\`\`diff
|
|
77
|
-
${patch}
|
|
78
|
-
\`\`\``,
|
|
79
|
-
...added.length === 0 ? [] : [
|
|
80
|
-
"These files are new and are in no diff yet. Read each one from " + "your working directory:",
|
|
81
|
-
added.map((file) => `- ${file}`).join(`
|
|
82
|
-
`)
|
|
83
|
-
]
|
|
84
|
-
].join(`
|
|
85
|
-
|
|
86
|
-
`),
|
|
87
|
-
instructions: WEIGHING
|
|
88
|
-
};
|
|
89
|
-
return { ...draft, bytes: Buffer.byteLength(reviewPrompt(draft)) };
|
|
90
|
-
}
|
|
91
|
-
print() {
|
|
92
|
-
if (this.rules.length === 0) {
|
|
93
|
-
this.log.info("no signoff rules");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
this.log.info("Weigh your change against each rule below. Anything that needs " + "review goes to a person instead of trunk.");
|
|
97
|
-
for (const rule of this.rules) {
|
|
98
|
-
this.log.info(`
|
|
99
|
-
${divider(rule.id)}
|
|
100
|
-
`);
|
|
101
|
-
this.log.info(rule.document.trim());
|
|
102
|
-
}
|
|
103
|
-
this.log.info(`
|
|
104
|
-
${divider()}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
var WEIGHING = [
|
|
108
|
-
"These rules decide one thing: whether this change can merge on its own, " + "or needs a person to look at it first.",
|
|
109
|
-
"",
|
|
110
|
-
"Each rule's `Ships` section is what merges with nobody looking, and its " + "`Needs review` section is what does not. Report a violation only for " + "what the `Needs review` side covers, and only where the change " + "actually does it.",
|
|
111
|
-
"",
|
|
112
|
-
"These rules are about the change as a whole, so leave `file` and `line` " + "out unless one place in the change is the whole of the answer."
|
|
113
|
-
].join(`
|
|
114
|
-
`);
|
|
115
|
-
|
|
116
|
-
export { Signoff };
|
package/signoff.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { type Rule } from "@webappwiz/rules";
|
|
2
|
-
import { type Logger } from "webappwiz/log";
|
|
3
|
-
import { type Ps } from "webappwiz/system";
|
|
4
|
-
import { type Clock } from "webappwiz/time";
|
|
5
|
-
export interface SignoffRunOptions {
|
|
6
|
-
/** The directory whose change is being weighed. */
|
|
7
|
-
dir: string;
|
|
8
|
-
agent?: string;
|
|
9
|
-
exec?: string;
|
|
10
|
-
/** Print the rules to the logger and spawn nothing, for the reader who is
|
|
11
|
-
* going to apply them itself. */
|
|
12
|
-
print?: boolean;
|
|
13
|
-
/** The ref the change is measured against: everything since it is the
|
|
14
|
-
* change. */
|
|
15
|
-
since: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Weighs a change against the rules that decide whether it can merge on its
|
|
19
|
-
* own or needs a person to look at it first.
|
|
20
|
-
*
|
|
21
|
-
* One agent call over the diff, rather than one per file like `judge`: these
|
|
22
|
-
* rules are about the change as a whole, and a run that saw one file at a time
|
|
23
|
-
* could not answer them. Exits 1 with a reason when the change needs a person,
|
|
24
|
-
* so it reads the same to a merge gate as to whoever ran it.
|
|
25
|
-
*/
|
|
26
|
-
/** What a `Signoff` runs through. */
|
|
27
|
-
export interface SignoffOptions {
|
|
28
|
-
log?: Logger;
|
|
29
|
-
ps?: Ps;
|
|
30
|
-
clock?: Clock;
|
|
31
|
-
}
|
|
32
|
-
export declare class Signoff {
|
|
33
|
-
private rules;
|
|
34
|
-
private defaultAgent;
|
|
35
|
-
private log;
|
|
36
|
-
private ps;
|
|
37
|
-
private clock;
|
|
38
|
-
constructor(rules: Rule[], defaultAgent: string, opts?: SignoffOptions);
|
|
39
|
-
run(opts: SignoffRunOptions): Promise<void>;
|
|
40
|
-
/** The one call, and what it says about the change. */
|
|
41
|
-
private judge;
|
|
42
|
-
/**
|
|
43
|
-
* The verdict, and a reason for each rule that wants a person. Throws on any
|
|
44
|
-
* of them: an agent that ran this before merging should escalate rather than
|
|
45
|
-
* merge, and an exit code is what says so whoever is reading.
|
|
46
|
-
*/
|
|
47
|
-
private say;
|
|
48
|
-
/** The change as the agent reads it, priced by the prompt: what a new file
|
|
49
|
-
* costs to open is the agent's business and unknowable from here. */
|
|
50
|
-
private review;
|
|
51
|
-
/**
|
|
52
|
-
* The rules in full, for the reader who is going to apply them. Nothing has
|
|
53
|
-
* to run these: an agent about to merge its own work can weigh it against
|
|
54
|
-
* them itself, and that is the cheapest signoff there is.
|
|
55
|
-
*/
|
|
56
|
-
private print;
|
|
57
|
-
}
|