@webappwiz/cli 0.0.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.
@@ -0,0 +1,418 @@
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 };
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
package/index.js ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ import {
4
+ add,
5
+ ls,
6
+ update,
7
+ update1 as update2,
8
+ version
9
+ } from "./index-g2q5rg7s.js";
10
+ import {
11
+ JUDGE_RULES,
12
+ SIGNOFF_RULES
13
+ } from "./index-8jpb8xae.js";
14
+ import {
15
+ Signoff
16
+ } from "./index-3k3rtw49.js";
17
+ import {
18
+ JudgeCommands
19
+ } from "./index-jbrs6gqt.js";
20
+
21
+ // index.ts
22
+ import { NodeFs, NodeGlob } from "webappwiz/system";
23
+ import { SystemClock } from "webappwiz/time";
24
+
25
+ // webappwiz.ts
26
+ import { AGENTS } from "@webappwiz/rules";
27
+ import { cli } from "webappwiz/cmd";
28
+ import { t } from "webappwiz/t";
29
+ var webappwiz = cli("webappwiz");
30
+ webappwiz.command("update").description("pin every webappwiz dependency in a tree to one version").arg("dir", t.string(), {
31
+ default: ".",
32
+ description: "directory to scan recursively (default: .)"
33
+ }).option("version", t.string(), {
34
+ default: version,
35
+ description: "version to pin to"
36
+ }).action((opts, { log, fs }) => update2({ ...opts, log, fs }));
37
+ var judge = ({ log, fs, ps, clock, glob }) => new JudgeCommands(JUDGE_RULES, {
38
+ signoffRules: SIGNOFF_RULES,
39
+ log,
40
+ fs,
41
+ ps,
42
+ clock,
43
+ glob
44
+ });
45
+ webappwiz.command("judge").description("check a directory against the config, one agent per glob").arg("dir", t.string(), {
46
+ default: ".",
47
+ description: "directory to judge (default: .)"
48
+ }).option("agent", t.optional(t.enum(Object.keys(AGENTS))), {
49
+ description: "model to check with (default: the config's agent)"
50
+ }).option("exec", t.optional(t.string()), {
51
+ description: "command the prompt is passed to, instead of --agent"
52
+ }).option("print", t.boolean(), {
53
+ default: false,
54
+ description: "print the prompts and run no agent at all"
55
+ }).option("estimate", t.boolean(), {
56
+ default: false,
57
+ description: "print what a run would read, and run nothing"
58
+ }).option("chunk", t.number(), {
59
+ default: 25,
60
+ description: "files per review"
61
+ }).option("since", t.optional(t.string()), {
62
+ description: "only check files added or changed since this git ref"
63
+ }).option("budget", t.number(), {
64
+ default: 200000,
65
+ description: "confirm before reading more than this many tokens"
66
+ }).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
+ }).option("budget", t.number(), {
81
+ default: 200000,
82
+ description: "confirm before reading more than this many tokens"
83
+ }).action((opts, { log, ps, clock }) => new Signoff(SIGNOFF_RULES, JUDGE_RULES.agent, {
84
+ log,
85
+ ps,
86
+ clock
87
+ }).run(opts));
88
+ var rules = webappwiz.group("rules").description("list and print the rules, to run or to read yourself");
89
+ rules.command("ls").description("list the rules").action((_opts, deps) => judge(deps).ls());
90
+ 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));
91
+ var skillsGroup = webappwiz.group("skills").description("manage webappwiz agent skills in .agents/skills");
92
+ skillsGroup.command("ls").description("list the skills there are, and what the project has of them").arg("dir", t.string(), {
93
+ default: ".",
94
+ description: "project to inspect (default: .)"
95
+ }).action((opts, { log, fs }) => ls({ ...opts, log, fs }));
96
+ skillsGroup.command("add").description("add a skill to a project").arg("skill", t.string(), { description: "skill name" }).arg("dir", t.string(), {
97
+ default: ".",
98
+ description: "project to add it to (default: .)"
99
+ }).action((opts, { log, fs }) => add({ ...opts, log, fs }));
100
+ skillsGroup.command("update").description("refresh the skills a project already has").arg("dir", t.string(), {
101
+ default: ".",
102
+ description: "project to refresh (default: .)"
103
+ }).action((opts, { log, fs }) => update({ ...opts, log, fs }));
104
+
105
+ // index.ts
106
+ await webappwiz.run({
107
+ fs: new NodeFs,
108
+ clock: new SystemClock,
109
+ glob: new NodeGlob
110
+ });
package/judge.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ import { type Rule, type RuleSet } from "@webappwiz/rules";
2
+ import { type Logger } from "webappwiz/log";
3
+ import { type Fs, type Glob, type Ps } from "webappwiz/system";
4
+ import { type Clock } from "webappwiz/time";
5
+ /** Asked before a run spends more than it was allowed to. */
6
+ export interface Confirm {
7
+ confirm(question: string): boolean | Promise<boolean>;
8
+ }
9
+ export interface ShowOptions {
10
+ /** The rule to print, as `rules ls` lists it. */
11
+ id: string;
12
+ }
13
+ export interface JudgeOptions {
14
+ /** The directory to check, and what paths in the report are relative to. */
15
+ dir: string;
16
+ agent?: string;
17
+ exec?: string;
18
+ /** Print the prompts to the logger and spawn nothing. */
19
+ print?: boolean;
20
+ /** Print what a run would read and stop. */
21
+ estimate?: boolean;
22
+ /** Files per review. */
23
+ chunk: number;
24
+ /** Narrows the run to what git says has changed since this ref. */
25
+ since?: string;
26
+ /** Tokens a run may read before it asks whether you meant it. */
27
+ budget: number;
28
+ }
29
+ /**
30
+ * Answers on the terminal, and answers no without one: a run nobody is watching
31
+ * should stop and say the number rather than block forever waiting to be told
32
+ * to go ahead.
33
+ */
34
+ export declare const ask: Confirm;
35
+ /** What a `JudgeCommands` runs through, and what else it lists. */
36
+ export interface JudgeCommandsOptions {
37
+ /** Rules only a reader applies, listed beside the ones a run checks. */
38
+ signoffRules?: Rule[];
39
+ /** Who is asked before a run goes over budget; the terminal by default. */
40
+ confirmer?: Confirm;
41
+ log?: Logger;
42
+ fs?: Fs;
43
+ ps?: Ps;
44
+ clock?: Clock;
45
+ glob?: Glob;
46
+ }
47
+ export declare class JudgeCommands {
48
+ private rules;
49
+ private signoffRules;
50
+ private confirmer;
51
+ private log;
52
+ private fs;
53
+ private ps;
54
+ private clock;
55
+ private glob;
56
+ constructor(rules: RuleSet, opts?: JudgeCommandsOptions);
57
+ /**
58
+ * Lists every rule there is, one row each, ids first for citing. The rules a
59
+ * run checks and the ones only a reader applies are one list with a SET
60
+ * column, because "what rules are there" is one question.
61
+ */
62
+ ls(): void;
63
+ /**
64
+ * Prints one rule in full: what it covers, and the document an agent is
65
+ * given, verbatim. Take the id from `rules ls` or from a finding. This is
66
+ * how a reader applies a rule nothing runs for them.
67
+ */
68
+ show(opts: ShowOptions): void;
69
+ /**
70
+ * Runs the rules over a directory with the agent you name, as `agent` or
71
+ * `exec`, falling back to the config's. Exits 1 on any error. Under `print`
72
+ * it spawns nothing and prints the prompts instead, for an agent that would
73
+ * rather hand them to subagents of its own.
74
+ *
75
+ * `since` narrows the run to what git says has changed, and `budget` caps
76
+ * what it may read before asking whether you meant it. Under `estimate` it
77
+ * prints that size and stops, which is the answer to "what would this cost"
78
+ * without having to guess a budget low enough to be refused.
79
+ */
80
+ judge(opts: JudgeOptions): Promise<void>;
81
+ /**
82
+ * Measures what one call cost over the files it was handed and leaves that
83
+ * behind, so the next `--estimate` on this agent has something better than a
84
+ * floor. Per call rather than per token, because that is how the charge
85
+ * falls: an agent pays for its own system prompt once per spawn, whatever it
86
+ * was asked to read, so a figure taken from a two-call run still holds for a
87
+ * fifteen-call one.
88
+ *
89
+ * A run nobody priced records nothing, and a failed write is said aloud
90
+ * rather than thrown: the agents have already been paid for by this point,
91
+ * and losing the measurement costs the next estimate accuracy, not the run.
92
+ */
93
+ private record;
94
+ /** The agent a command runs with: what it was told, else the config's. */
95
+ private agent;
96
+ /** The model a run asks, or undefined for an `--exec` command, which is a
97
+ * model nothing here can name or price. */
98
+ private model;
99
+ }
package/judge.js ADDED
@@ -0,0 +1,8 @@
1
+ import {
2
+ JudgeCommands,
3
+ ask
4
+ } from "./index-jbrs6gqt.js";
5
+ export {
6
+ ask,
7
+ JudgeCommands
8
+ };
package/mode.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /** What a command does with the plan it has made. */
2
+ export type Mode = "print" | "estimate" | "run";
3
+ /** The flags that choose between the three, on top of the two that name an
4
+ * agent. */
5
+ export interface ModeOptions {
6
+ /** A model to ask. */
7
+ agent?: string;
8
+ /** A command to hand the prompt to instead. */
9
+ exec?: string;
10
+ /** Print to the logger and spawn nothing. */
11
+ print?: boolean;
12
+ /** Print what a run would read, and spawn nothing. */
13
+ estimate?: boolean;
14
+ }
15
+ /**
16
+ * Which of the three a caller asked for, with running the default: a command
17
+ * given nothing but a directory is one somebody means to run.
18
+ *
19
+ * Naming two is an error rather than a quiet winner. All of these say what to
20
+ * do with one plan, and letting one silently beat the other would leave a
21
+ * caller unsure which of the two things they asked for they got.
22
+ */
23
+ export declare function mode({ print, estimate, agent, exec }: ModeOptions): Mode;