@webappwiz/cli 0.0.7 → 0.0.8
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 +13 -8
- package/index-4xj59vmx.js +135 -0
- package/{index-3k3rtw49.js → index-fh8js68z.js} +5 -17
- package/{index-3p0t2exn.js → index-hz6bwqfy.js} +4 -0
- package/index-r14q0cmx.js +262 -0
- package/{index-6km6e845.js → index-rz4spnkb.js} +123 -32
- package/index.js +12 -15
- package/judge.d.ts +9 -37
- package/judge.js +3 -4
- package/mode.d.ts +4 -6
- package/package.json +3 -3
- package/progress.d.ts +64 -0
- package/report.d.ts +14 -30
- package/rules.js +1 -1
- package/signoff.d.ts +0 -6
- package/signoff.js +2 -2
- package/webappwiz.js +12 -15
- package/cost.d.ts +0 -45
- package/index-jbrs6gqt.js +0 -418
package/webappwiz.js
CHANGED
|
@@ -4,17 +4,18 @@ import {
|
|
|
4
4
|
update,
|
|
5
5
|
update1 as update2,
|
|
6
6
|
version
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-rz4spnkb.js";
|
|
8
8
|
import {
|
|
9
9
|
JUDGE_RULES,
|
|
10
10
|
SIGNOFF_RULES
|
|
11
|
-
} from "./index-
|
|
12
|
-
import {
|
|
13
|
-
Signoff
|
|
14
|
-
} from "./index-3k3rtw49.js";
|
|
11
|
+
} from "./index-hz6bwqfy.js";
|
|
15
12
|
import {
|
|
16
13
|
JudgeCommands
|
|
17
|
-
} from "./index-
|
|
14
|
+
} from "./index-r14q0cmx.js";
|
|
15
|
+
import {
|
|
16
|
+
Signoff
|
|
17
|
+
} from "./index-fh8js68z.js";
|
|
18
|
+
import"./index-4xj59vmx.js";
|
|
18
19
|
|
|
19
20
|
// webappwiz.ts
|
|
20
21
|
import { AGENTS } from "@webappwiz/rules";
|
|
@@ -46,17 +47,16 @@ webappwiz.command("judge").description("check a directory against the config, on
|
|
|
46
47
|
}).option("print", t.boolean(), {
|
|
47
48
|
default: false,
|
|
48
49
|
description: "print the prompts and run no agent at all"
|
|
49
|
-
}).option("estimate", t.boolean(), {
|
|
50
|
-
default: false,
|
|
51
|
-
description: "print what a run would read, and run nothing"
|
|
52
50
|
}).option("chunk", t.number(), {
|
|
53
51
|
default: 25,
|
|
54
52
|
description: "files per review"
|
|
55
53
|
}).option("since", t.optional(t.string()), {
|
|
56
54
|
description: "only check files added or changed since this git ref"
|
|
57
|
-
}).option("
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
}).option("concurrency-override", t.optional(t.number()), {
|
|
56
|
+
description: "agent calls in flight at once, over the config's concurrency"
|
|
57
|
+
}).option("ci", t.boolean(), {
|
|
58
|
+
default: false,
|
|
59
|
+
description: "line-by-line output with no live progress block"
|
|
60
60
|
}).action((opts, deps) => judge(deps).judge(opts));
|
|
61
61
|
webappwiz.command("signoff").description("weigh a change against the rules that ask for a person").arg("dir", t.string(), {
|
|
62
62
|
default: ".",
|
|
@@ -71,9 +71,6 @@ webappwiz.command("signoff").description("weigh a change against the rules that
|
|
|
71
71
|
}).option("since", t.string(), {
|
|
72
72
|
default: "main",
|
|
73
73
|
description: "the ref the change is measured against"
|
|
74
|
-
}).option("budget", t.number(), {
|
|
75
|
-
default: 200000,
|
|
76
|
-
description: "confirm before reading more than this many tokens"
|
|
77
74
|
}).action((opts, { log, ps, clock }) => new Signoff(SIGNOFF_RULES, JUDGE_RULES.agent, {
|
|
78
75
|
log,
|
|
79
76
|
ps,
|
package/cost.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type Fs } from "webappwiz/system";
|
|
2
|
-
/** The agents an estimate can price, which is every one `--agent` accepts. */
|
|
3
|
-
export declare const priced: () => string[];
|
|
4
|
-
/**
|
|
5
|
-
* What those tokens cost to read on that agent, or undefined for an agent with
|
|
6
|
-
* no published price, which is any `--exec` command.
|
|
7
|
-
*
|
|
8
|
-
* A floor twice over: it counts input only, and only the input a plan can see.
|
|
9
|
-
*/
|
|
10
|
-
export declare function floor(agent: string, tokens: number): number | undefined;
|
|
11
|
-
/**
|
|
12
|
-
* Dollars a single call costs beyond the files it was given, per agent.
|
|
13
|
-
*
|
|
14
|
-
* Everything the floor cannot see is charged per call, not per byte: the agent
|
|
15
|
-
* pays for its own system prompt every time it is spawned, and on this project
|
|
16
|
-
* that is most of a small call's bill. Measuring it per call is what lets a
|
|
17
|
-
* figure taken from a two-call run stand up to a fifteen-call one.
|
|
18
|
-
*/
|
|
19
|
-
export type Overheads = Record<string, number>;
|
|
20
|
-
/**
|
|
21
|
-
* What past runs in `root` measured, empty until one has finished there. A
|
|
22
|
-
* missing or unreadable file is empty rather than an error: a calibration is a
|
|
23
|
-
* convenience, and losing it costs the caller a worse estimate, not a run.
|
|
24
|
-
*/
|
|
25
|
-
export interface OverheadsOptions {
|
|
26
|
-
/** What the record is read through; the real filesystem by default. */
|
|
27
|
-
fs?: Fs;
|
|
28
|
-
}
|
|
29
|
-
export declare function overheads(dir: string, opts?: OverheadsOptions): Promise<Overheads>;
|
|
30
|
-
/**
|
|
31
|
-
* Records what a call on this agent costs over its floor, so the next
|
|
32
|
-
* `--estimate` has something better than one. Other agents are left alone,
|
|
33
|
-
* since each is a separate measurement.
|
|
34
|
-
*/
|
|
35
|
-
export interface CalibrateOptions {
|
|
36
|
-
/** What the record is written through; the real filesystem by default. */
|
|
37
|
-
fs?: Fs;
|
|
38
|
-
}
|
|
39
|
-
export declare function calibrate(dir: string, agent: string, call: number, opts?: CalibrateOptions): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* What a plan costs on one agent: the price of the files it names, plus what
|
|
42
|
-
* every call charges on top. Undefined for an agent with no listed price, and
|
|
43
|
-
* the floor alone until a run has measured one.
|
|
44
|
-
*/
|
|
45
|
-
export declare function predict(agent: string, tokens: number, calls: number, measured: Overheads): number | undefined;
|
package/index-jbrs6gqt.js
DELETED
|
@@ -1,418 +0,0 @@
|
|
|
1
|
-
// judge.ts
|
|
2
|
-
import {
|
|
3
|
-
agentCommand,
|
|
4
|
-
Files,
|
|
5
|
-
Harness,
|
|
6
|
-
prompt as reviewPrompt
|
|
7
|
-
} from "@webappwiz/rules";
|
|
8
|
-
import { ConsoleLogger, color as color3 } from "webappwiz/log";
|
|
9
|
-
import {
|
|
10
|
-
NodeFs as NodeFs2,
|
|
11
|
-
NodeGlob,
|
|
12
|
-
NodePs as NodePs2
|
|
13
|
-
} from "webappwiz/system";
|
|
14
|
-
import { SystemClock } from "webappwiz/time";
|
|
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 diff(dir, ref, opts = {}) {
|
|
35
|
-
const ps = opts.ps ?? new NodePs;
|
|
36
|
-
const patch = await git(ps, dir, ["diff", "--relative", ref]);
|
|
37
|
-
const added = (await git(ps, dir, ["ls-files", "--others", "--exclude-standard"])).split(`
|
|
38
|
-
`).filter((line) => line !== "");
|
|
39
|
-
return { patch: patch.trim(), added };
|
|
40
|
-
}
|
|
41
|
-
async function git(ps, dir, argv) {
|
|
42
|
-
const { exitCode, stdout, stderr } = await ps.spawnCapture([
|
|
43
|
-
"git",
|
|
44
|
-
"-C",
|
|
45
|
-
dir,
|
|
46
|
-
...argv
|
|
47
|
-
]);
|
|
48
|
-
if (exitCode !== 0) {
|
|
49
|
-
throw new Error(`git ${argv[0]} failed in ${dir}: ${stderr.trim() || `exit ${exitCode}`}`);
|
|
50
|
-
}
|
|
51
|
-
return stdout;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// cost.ts
|
|
55
|
-
import { join } from "node:path";
|
|
56
|
-
import { NodeFs } from "webappwiz/system";
|
|
57
|
-
var PRICES = { haiku: 1, sonnet: 3, opus: 5 };
|
|
58
|
-
var priced = () => Object.keys(PRICES);
|
|
59
|
-
function floor(agent, tokens) {
|
|
60
|
-
const price = PRICES[agent];
|
|
61
|
-
return price === undefined ? undefined : tokens / 1e6 * price;
|
|
62
|
-
}
|
|
63
|
-
var FILE = join(".wiz", "judge-cost.json");
|
|
64
|
-
async function overheads(dir, opts = {}) {
|
|
65
|
-
const fs = opts.fs ?? new NodeFs;
|
|
66
|
-
try {
|
|
67
|
-
const parsed = JSON.parse(await fs.read(join(dir, FILE)));
|
|
68
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
69
|
-
return {};
|
|
70
|
-
}
|
|
71
|
-
return Object.fromEntries(Object.entries(parsed).filter(([, call]) => typeof call === "number" && call > 0));
|
|
72
|
-
} catch {
|
|
73
|
-
return {};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async function calibrate(dir, agent, call, opts = {}) {
|
|
77
|
-
const fs = opts.fs ?? new NodeFs;
|
|
78
|
-
const recorded = { ...await overheads(dir, { fs }), [agent]: call };
|
|
79
|
-
await fs.mkdir(join(dir, ".wiz"));
|
|
80
|
-
await fs.write(join(dir, FILE), `${JSON.stringify(recorded, null, "\t")}
|
|
81
|
-
`);
|
|
82
|
-
}
|
|
83
|
-
function predict(agent, tokens, calls, measured) {
|
|
84
|
-
const listed = floor(agent, tokens);
|
|
85
|
-
const call = measured[agent];
|
|
86
|
-
return listed === undefined || call === undefined ? listed : listed + call * calls;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// mode.ts
|
|
90
|
-
function mode({ print, estimate, agent, exec }) {
|
|
91
|
-
const named = [
|
|
92
|
-
print === true ? "--print" : undefined,
|
|
93
|
-
estimate === true ? "--estimate" : undefined,
|
|
94
|
-
agent === undefined ? undefined : "--agent",
|
|
95
|
-
exec === undefined ? undefined : "--exec"
|
|
96
|
-
].filter((flag) => flag !== undefined);
|
|
97
|
-
if (named.length > 1) {
|
|
98
|
-
throw new Error(`${named.join(" and ")} are different things to do with one run, so pass one`);
|
|
99
|
-
}
|
|
100
|
-
if (print === true) {
|
|
101
|
-
return "print";
|
|
102
|
-
}
|
|
103
|
-
return estimate === true ? "estimate" : "run";
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// report.ts
|
|
107
|
-
import { color as color2 } from "webappwiz/log";
|
|
108
|
-
|
|
109
|
-
// table.ts
|
|
110
|
-
import { color } from "webappwiz/log";
|
|
111
|
-
var table = (rows) => {
|
|
112
|
-
const width = (cell) => color.strip(cell).length;
|
|
113
|
-
const widths = rows[0]?.map((_, i) => Math.max(...rows.map((row) => width(row[i] ?? ""))));
|
|
114
|
-
return rows.map((row) => row.map((cell, i) => cell.padEnd((widths?.[i] ?? 0) + cell.length - width(cell))).join(" ").trimEnd());
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// report.ts
|
|
118
|
-
var count = (total, word) => `${total} ${word}${total === 1 ? "" : "s"}`;
|
|
119
|
-
var divider = (name) => {
|
|
120
|
-
if (name === undefined) {
|
|
121
|
-
return color2.dim("-".repeat(72));
|
|
122
|
-
}
|
|
123
|
-
const opening = `--- ${name} `;
|
|
124
|
-
return color2.dim(opening.padEnd(Math.max(72, opening.length + 3), "-"));
|
|
125
|
-
};
|
|
126
|
-
var compact = new Intl.NumberFormat("en", { notation: "compact" });
|
|
127
|
-
var money = new Intl.NumberFormat("en", {
|
|
128
|
-
style: "currency",
|
|
129
|
-
currency: "USD",
|
|
130
|
-
minimumFractionDigits: 2,
|
|
131
|
-
maximumFractionDigits: 4
|
|
132
|
-
});
|
|
133
|
-
var usd = (amount) => money.format(amount);
|
|
134
|
-
var tokens = (bytes) => Math.ceil(bytes / 4);
|
|
135
|
-
function overBudget(estimate, budget, cost) {
|
|
136
|
-
const money2 = cost === undefined ? "" : ` That is ${color2.bold(usd(cost))} or more.`;
|
|
137
|
-
return `${color2.yellow("!")} this run reads at least ` + `${color2.bold(compact.format(estimate))} tokens, over the ` + `${color2.bold(compact.format(budget))} budget, and the real cost will be higher.${money2} ` + `Raise it with ${color2.bold("--budget")}.`;
|
|
138
|
-
}
|
|
139
|
-
function estimate(files, rules, calls, tokens2, overheads2) {
|
|
140
|
-
const agents = priced();
|
|
141
|
-
const measured = agents.some((agent) => overheads2[agent] !== undefined);
|
|
142
|
-
const rows = [
|
|
143
|
-
["agent", "floor", ...measured ? ["measured"] : []].map(color2.dim)
|
|
144
|
-
];
|
|
145
|
-
for (const agent of agents) {
|
|
146
|
-
const least = floor(agent, tokens2) ?? 0;
|
|
147
|
-
const whole = overheads2[agent] === undefined ? undefined : predict(agent, tokens2, calls, overheads2);
|
|
148
|
-
rows.push([
|
|
149
|
-
agent,
|
|
150
|
-
color2.green(`${usd(least)}+`),
|
|
151
|
-
...measured ? [whole === undefined ? "" : color2.green(usd(whole))] : []
|
|
152
|
-
]);
|
|
153
|
-
}
|
|
154
|
-
return [
|
|
155
|
-
...planned({ files, rules, calls, estimate: tokens2 }),
|
|
156
|
-
...table(rows).map((line) => ` ${line}`),
|
|
157
|
-
"",
|
|
158
|
-
color2.gray("floor: the listed input price for the files above, and nothing else. " + `Every call
|
|
159
|
-
also pays for the agent's own system prompt and for whatever it re-reads.`),
|
|
160
|
-
...measured ? [
|
|
161
|
-
color2.gray("measured: that floor plus what a call on the agent cost over it last time.")
|
|
162
|
-
] : [
|
|
163
|
-
color2.gray("Run one of these and the estimate is measured against it next time.")
|
|
164
|
-
]
|
|
165
|
-
];
|
|
166
|
-
}
|
|
167
|
-
function planned({
|
|
168
|
-
files,
|
|
169
|
-
rules,
|
|
170
|
-
calls,
|
|
171
|
-
estimate: estimate2,
|
|
172
|
-
concurrency,
|
|
173
|
-
cost,
|
|
174
|
-
agent
|
|
175
|
-
}) {
|
|
176
|
-
const rows = [
|
|
177
|
-
[color2.dim("files"), String(files)],
|
|
178
|
-
[color2.dim("rules"), String(rules)],
|
|
179
|
-
[
|
|
180
|
-
color2.dim("calls"),
|
|
181
|
-
String(calls) + (concurrency === undefined ? "" : color2.dim(`, ${concurrency} at a time`))
|
|
182
|
-
],
|
|
183
|
-
[color2.dim("reading"), `${compact.format(estimate2)}+ tokens`]
|
|
184
|
-
];
|
|
185
|
-
if (cost !== undefined) {
|
|
186
|
-
rows.push([color2.dim("cost"), color2.green(`${usd(cost)}+`)]);
|
|
187
|
-
}
|
|
188
|
-
if (agent !== undefined) {
|
|
189
|
-
rows.push([color2.dim("agent"), agent]);
|
|
190
|
-
}
|
|
191
|
-
return ["", ...table(rows).map((line) => ` ${line}`), ""];
|
|
192
|
-
}
|
|
193
|
-
function finished({
|
|
194
|
-
rules,
|
|
195
|
-
files,
|
|
196
|
-
violations,
|
|
197
|
-
took,
|
|
198
|
-
cost,
|
|
199
|
-
done,
|
|
200
|
-
total
|
|
201
|
-
}) {
|
|
202
|
-
const heading = `${color2.gray(`[${done}/${total}]`)} ` + color2.gray(`(${count(rules.length, "rule")}, ${count(files, "file")})`);
|
|
203
|
-
const spent = cost === undefined ? "" : ` ${usd(cost)}`;
|
|
204
|
-
const tail = `${color2.gray(`in ${took.human()}${spent}`)}`;
|
|
205
|
-
if (violations.length === 0) {
|
|
206
|
-
return [`${color2.green("✓")} ${heading}: clean ${tail}`];
|
|
207
|
-
}
|
|
208
|
-
return [
|
|
209
|
-
`${color2.red("✗")} ${heading}: ${count(violations.length, "problem")} ${tail}`,
|
|
210
|
-
...violations.flatMap(finding)
|
|
211
|
-
];
|
|
212
|
-
}
|
|
213
|
-
function finding(violation) {
|
|
214
|
-
const level = violation.level === "error" ? color2.red("error") : color2.yellow(violation.level);
|
|
215
|
-
const lines = [
|
|
216
|
-
` ${color2.bold(`${violation.file}:${violation.line}`)} ${level} ${violation.message} ${color2.gray(`(${violation.id})`)}`
|
|
217
|
-
];
|
|
218
|
-
if (violation.code !== "") {
|
|
219
|
-
lines.push(color2.gray(` │ ${violation.code}`));
|
|
220
|
-
}
|
|
221
|
-
return lines;
|
|
222
|
-
}
|
|
223
|
-
function summary(violations, took, cost) {
|
|
224
|
-
const spent = cost === undefined ? "" : ` ${usd(cost)} total`;
|
|
225
|
-
const elapsed = color2.gray(`in ${took.human()}${spent}`);
|
|
226
|
-
if (violations.length === 0) {
|
|
227
|
-
return `${color2.green("✓ no violations")} ${elapsed}`;
|
|
228
|
-
}
|
|
229
|
-
const errors = violations.filter((violation) => violation.level === "error").length;
|
|
230
|
-
const line = `✖ ${count(violations.length, "problem")} (${count(errors, "error")}, ${count(violations.length - errors, "warning")})`;
|
|
231
|
-
return `${errors > 0 ? color2.red(line) : color2.yellow(line)} ${elapsed}`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// judge.ts
|
|
235
|
-
var estimated = (reviews) => tokens(reviews.reduce((bytes, review) => bytes + review.bytes, 0));
|
|
236
|
-
var isFileRule = (rule) => ("files" in rule);
|
|
237
|
-
var title = (rule) => /^#\s+(.+)$/m.exec(rule.document)?.[1]?.trim() ?? rule.id;
|
|
238
|
-
var ask = {
|
|
239
|
-
confirm: (question) => process.stdin.isTTY === true && /^y(es)?$/i.test((prompt(`${question} [y/N]`) ?? "").trim())
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
class JudgeCommands {
|
|
243
|
-
rules;
|
|
244
|
-
signoffRules;
|
|
245
|
-
confirmer;
|
|
246
|
-
log;
|
|
247
|
-
fs;
|
|
248
|
-
ps;
|
|
249
|
-
clock;
|
|
250
|
-
glob;
|
|
251
|
-
constructor(rules, opts = {}) {
|
|
252
|
-
this.rules = rules;
|
|
253
|
-
this.signoffRules = opts.signoffRules ?? [];
|
|
254
|
-
this.confirmer = opts.confirmer ?? ask;
|
|
255
|
-
this.log = opts.log ?? new ConsoleLogger;
|
|
256
|
-
this.fs = opts.fs ?? new NodeFs2;
|
|
257
|
-
this.ps = opts.ps ?? new NodePs2;
|
|
258
|
-
this.clock = opts.clock ?? new SystemClock;
|
|
259
|
-
this.glob = opts.glob ?? new NodeGlob;
|
|
260
|
-
}
|
|
261
|
-
ls() {
|
|
262
|
-
const rows = [["id", "rule", "set", "level", "files"].map(color3.dim)];
|
|
263
|
-
for (const rule of this.rules.rules) {
|
|
264
|
-
rows.push([rule.id, title(rule), "judge", rule.level, rule.files]);
|
|
265
|
-
}
|
|
266
|
-
for (const rule of this.signoffRules) {
|
|
267
|
-
rows.push([rule.id, title(rule), "signoff", "", ""]);
|
|
268
|
-
}
|
|
269
|
-
this.log.info(table(rows).join(`
|
|
270
|
-
`));
|
|
271
|
-
}
|
|
272
|
-
show(opts) {
|
|
273
|
-
const all = [...this.rules.rules, ...this.signoffRules];
|
|
274
|
-
const rule = all.find((candidate) => candidate.id === opts.id);
|
|
275
|
-
if (!rule) {
|
|
276
|
-
throw new Error(`no rule "${opts.id}". Known ids: ${all.map((candidate) => candidate.id).join(", ")}`);
|
|
277
|
-
}
|
|
278
|
-
const rows = [
|
|
279
|
-
[color3.dim("id"), rule.id],
|
|
280
|
-
[color3.dim("rule"), title(rule)]
|
|
281
|
-
];
|
|
282
|
-
if (isFileRule(rule)) {
|
|
283
|
-
rows.push([color3.dim("level"), rule.level], [color3.dim("files"), rule.files]);
|
|
284
|
-
}
|
|
285
|
-
this.log.info(table(rows).join(`
|
|
286
|
-
`));
|
|
287
|
-
this.log.info("");
|
|
288
|
-
this.log.info(rule.document.trim());
|
|
289
|
-
}
|
|
290
|
-
async judge(opts) {
|
|
291
|
-
const how = mode(opts);
|
|
292
|
-
const config = this.rules;
|
|
293
|
-
const rules = config.rules;
|
|
294
|
-
const dir = opts.dir.replace(/\/+$/, "") || "/";
|
|
295
|
-
const only = opts.since === undefined ? undefined : await changed(dir, opts.since, { ps: this.ps });
|
|
296
|
-
if (only?.size === 0) {
|
|
297
|
-
this.log.info(`nothing has changed since ${opts.since}`);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const files = new Files({ log: this.log, fs: this.fs, glob: this.glob });
|
|
301
|
-
const reviews = await files.plan(rules, dir, {
|
|
302
|
-
chunk: opts.chunk,
|
|
303
|
-
only
|
|
304
|
-
});
|
|
305
|
-
if (how === "print") {
|
|
306
|
-
for (const review of reviews) {
|
|
307
|
-
this.log.info(`
|
|
308
|
-
${divider(`${review.label} (${count(review.files.length, "file")})`)}
|
|
309
|
-
`);
|
|
310
|
-
this.log.info(reviewPrompt(review));
|
|
311
|
-
}
|
|
312
|
-
this.log.info(`
|
|
313
|
-
${divider()}`);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const read = new Set(reviews.flatMap((review) => review.files)).size;
|
|
317
|
-
const predicted = estimated(reviews);
|
|
318
|
-
const calls = reviews.length;
|
|
319
|
-
if (how === "estimate") {
|
|
320
|
-
for (const line of estimate(read, rules.length, calls, predicted, await overheads(this.ps.cwd(), { fs: this.fs }))) {
|
|
321
|
-
this.log.info(line);
|
|
322
|
-
}
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const agent = this.agent(config, opts);
|
|
326
|
-
const model = this.model(config, opts);
|
|
327
|
-
const root = this.ps.cwd();
|
|
328
|
-
const measured = await overheads(root, { fs: this.fs });
|
|
329
|
-
const started = this.clock.now();
|
|
330
|
-
const cost = model === undefined ? undefined : predict(model, predicted, calls, measured);
|
|
331
|
-
this.log.info(planned({
|
|
332
|
-
files: read,
|
|
333
|
-
rules: rules.length,
|
|
334
|
-
calls,
|
|
335
|
-
estimate: predicted,
|
|
336
|
-
concurrency: config.concurrency,
|
|
337
|
-
cost,
|
|
338
|
-
agent: agent.label
|
|
339
|
-
}).join(`
|
|
340
|
-
`));
|
|
341
|
-
if (predicted > opts.budget) {
|
|
342
|
-
this.log.info(overBudget(predicted, opts.budget, cost));
|
|
343
|
-
if (!await this.confirmer.confirm("Run anyway?")) {
|
|
344
|
-
throw new Error("over budget");
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
let spent = 0;
|
|
348
|
-
let billed = false;
|
|
349
|
-
const found = [];
|
|
350
|
-
const harness = new Harness({
|
|
351
|
-
log: this.log,
|
|
352
|
-
ps: this.ps,
|
|
353
|
-
clock: this.clock
|
|
354
|
-
});
|
|
355
|
-
harness.events.on("finished", (review) => {
|
|
356
|
-
if (review.cost !== undefined) {
|
|
357
|
-
spent += review.cost;
|
|
358
|
-
billed = true;
|
|
359
|
-
}
|
|
360
|
-
const at = reviews[review.at];
|
|
361
|
-
if (!at) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const violations2 = files.violations(at, review.findings, dir);
|
|
365
|
-
found[review.at] = violations2;
|
|
366
|
-
for (const line of finished({
|
|
367
|
-
rules: review.rules,
|
|
368
|
-
files: at.files.length,
|
|
369
|
-
violations: violations2,
|
|
370
|
-
took: review.took,
|
|
371
|
-
cost: review.cost,
|
|
372
|
-
done: review.done,
|
|
373
|
-
total: review.total
|
|
374
|
-
})) {
|
|
375
|
-
this.log.info(line);
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
await harness.run(reviews, agent, {
|
|
379
|
-
cwd: dir,
|
|
380
|
-
concurrency: config.concurrency
|
|
381
|
-
});
|
|
382
|
-
const violations = found.flat();
|
|
383
|
-
this.log.info("");
|
|
384
|
-
this.log.info(summary(violations, this.clock.now().subtract(started), billed ? spent : undefined));
|
|
385
|
-
await this.record(model, root, predicted, calls, billed ? spent : undefined);
|
|
386
|
-
const errors = violations.filter((violation) => violation.level === "error").length;
|
|
387
|
-
if (errors > 0) {
|
|
388
|
-
throw new Error(count(errors, "error"));
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
async record(agent, root, predicted, calls, spent) {
|
|
392
|
-
if (agent === undefined || spent === undefined || calls <= 0) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const listed = floor(agent, predicted);
|
|
396
|
-
if (listed === undefined) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
const call = (spent - listed) / calls;
|
|
400
|
-
if (call <= 0) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
try {
|
|
404
|
-
await calibrate(root, agent, call, { fs: this.fs });
|
|
405
|
-
} catch (error) {
|
|
406
|
-
this.log.error(`could not record what this run cost: ${error}`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
agent(config, opts) {
|
|
410
|
-
const model = this.model(config, opts);
|
|
411
|
-
return agentCommand(model === undefined ? opts : { agent: model });
|
|
412
|
-
}
|
|
413
|
-
model(config, opts) {
|
|
414
|
-
return opts.exec === undefined ? opts.agent ?? config.agent : undefined;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
export { diff, mode, table, count, divider, usd, tokens, overBudget, ask, JudgeCommands };
|