@webappwiz/cli 0.0.7 → 0.0.9

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/progress.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { type Timer } from "webappwiz/time";
2
+ /**
3
+ * Where live progress draws. `tty` is whether a line can be redrawn in
4
+ * place: without one, judge stays line-by-line and never writes here.
5
+ */
6
+ export interface Screen {
7
+ tty: boolean;
8
+ write(text: string): void;
9
+ }
10
+ /** The terminal this process writes to. */
11
+ export declare const terminal: () => Screen;
12
+ /** A run as the status line shows it: how far along, what it is on, what it
13
+ * has spent, and what it has found. */
14
+ export interface RunView {
15
+ /** Calls finished. */
16
+ done: number;
17
+ /** Calls the run will make in all. */
18
+ total: number;
19
+ /** Files the calls out right now are reading. Every review names at least
20
+ * one, so zero here is the same as nothing running. */
21
+ files: number;
22
+ /** Tokens spent so far, when any agent has reported usage. */
23
+ tokens?: number;
24
+ /** Violations found so far; said aloud once the first call is home. */
25
+ problems: number;
26
+ }
27
+ /** The spinner's walk, one step per tick while any call is out. */
28
+ export declare const FRAMES: string[];
29
+ /**
30
+ * The status line. Pure, so what it says is testable without a terminal:
31
+ * the `Progress` around it only draws and redraws it. `frame` indexes the
32
+ * spinner's walk; with nothing running there is nothing to spin.
33
+ */
34
+ export declare function render(view: RunView, frame?: number): string;
35
+ /** What a `Progress` paces its spinner with; the real one by default. */
36
+ export interface ProgressOptions {
37
+ timer?: Timer;
38
+ }
39
+ /**
40
+ * The live line a run draws while agents are out: progress over the calls
41
+ * and the tokens they have spent, redrawn on every event and spun on a
42
+ * tick between them. `stop` takes the line down, and whatever prints next
43
+ * lands where it was.
44
+ */
45
+ export declare class Progress {
46
+ private screen;
47
+ private total;
48
+ private done;
49
+ private files;
50
+ private problems;
51
+ private tokens;
52
+ private drawn;
53
+ private frame;
54
+ private ticking;
55
+ constructor(screen: Screen, total: number, opts?: ProgressOptions);
56
+ /** A call went out over this many files. */
57
+ started(files: number): void;
58
+ /** A call came home: the files it read, what it spent, what it found. */
59
+ finished(files: number, spent?: number, problems?: number): void;
60
+ /** Takes the line down for good; call it before printing the report. */
61
+ stop(): void;
62
+ private erase;
63
+ private draw;
64
+ }
package/report.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { Violation } from "@webappwiz/rules";
2
2
  import type { Duration } from "webappwiz/time";
3
- import { type Overheads } from "./cost.js";
4
3
  export declare const count: (total: number, word: string) => string;
5
4
  /**
6
5
  * Where one printed document stops and the next starts, named so a reader
@@ -11,7 +10,8 @@ export declare const count: (total: number, word: string) => string;
11
10
  * the last document wants as much as the others want an opening one.
12
11
  */
13
12
  export declare const divider: (name?: string) => string;
14
- export declare const usd: (amount: number) => string;
13
+ /** How every token figure prints: "14K", not "14,000". */
14
+ export declare const compact: Intl.NumberFormat;
15
15
  /**
16
16
  * What a plan costs to read, at the four-bytes-a-token rule of thumb. Rough on
17
17
  * purpose: an estimate that needed a tokenizer, or an API call to count, would
@@ -19,28 +19,10 @@ export declare const usd: (amount: number) => string;
19
19
  * decision it informs is only ever "is this the order of magnitude I meant".
20
20
  */
21
21
  export declare const tokens: (bytes: number) => number;
22
- /**
23
- * Why the estimate is a floor: each call pays for the agent's own system
24
- * prompt and for whatever it re-reads as it works, none of which is knowable
25
- * from here.
26
- */
27
- export declare function overBudget(estimate: number, budget: number, cost?: number): string;
28
- /**
29
- * The whole of what `--estimate` prints: the plan, then what it costs on each
30
- * agent that has a price.
31
- *
32
- * Two columns, because the floor alone is misleading. It prices the files a
33
- * plan can see, and a call also pays for the agent's own system prompt every
34
- * time, which on a small repo is most of the bill. `overheads` carries what
35
- * past runs measured that per-call charge to be, so an agent that has been run
36
- * here gets a second figure worth trusting.
37
- */
38
- export declare function estimate(files: number, rules: number, calls: number, tokens: number, overheads: Overheads): string[];
39
22
  /**
40
23
  * The plan, before the first agent starts. Counts calls rather than reviews
41
- * because a call is what a run is billed for, and it is the denominator of the
42
- * `[n/total]` headings below. Without an `agent` it is the whole of what
43
- * `--estimate` prints, so it names no command it is not going to run.
24
+ * because a call is what a run spawns, and it is the denominator of the
25
+ * `[n/total]` headings below.
44
26
  */
45
27
  export interface Planned {
46
28
  files: number;
@@ -50,14 +32,12 @@ export interface Planned {
50
32
  estimate: number;
51
33
  /** Agent calls in flight at once, which is what the wall clock turns on. */
52
34
  concurrency?: number;
53
- /** What those calls are predicted to cost, when the agent has a price. */
54
- cost?: number;
55
35
  /** The command the run will spawn. */
56
36
  agent?: string;
57
37
  }
58
- export declare function planned({ files, rules, calls, estimate, concurrency, cost, agent, }: Planned): string[];
38
+ export declare function planned({ files, rules, calls, estimate, concurrency, agent, }: Planned): string[];
59
39
  /** A finished review as the report prints it: what the call covered, what it
60
- * found, and what it cost. */
40
+ * found, and what it read. */
61
41
  export interface Finished {
62
42
  /** The ids of the rules the review checked. */
63
43
  rules: string[];
@@ -66,8 +46,12 @@ export interface Finished {
66
46
  violations: Violation[];
67
47
  /** How long this review's agent took. */
68
48
  took: Duration;
69
- /** Dollars this review's call was billed, when the agent reported a figure. */
70
- cost?: number;
49
+ /** Tokens this review's call touched, when the agent reported usage. */
50
+ tokens?: number;
51
+ /** Which worker ran the call, 0-based. */
52
+ worker: number;
53
+ /** Tokens that worker has touched so far, this review included. */
54
+ workerTokens?: number;
71
55
  done: number;
72
56
  total: number;
73
57
  }
@@ -75,11 +59,11 @@ export interface Finished {
75
59
  * A finished review, as it should print the moment its agent returns: a status
76
60
  * line sizing the call, then one finding per violation.
77
61
  */
78
- export declare function finished({ rules, files, violations, took, cost, done, total, }: Finished): string[];
62
+ export declare function finished({ rules, files, violations, took, tokens, worker, workerTokens, done, total, }: Finished): string[];
79
63
  /**
80
64
  * One violation: a location a reader can click, what the code does that the
81
65
  * rule forbids, and the line it happens on. The heading above names only the
82
66
  * glob, so the finding says which of its rules this one breaks.
83
67
  */
84
68
  export declare function finding(violation: Violation): string[];
85
- export declare function summary(violations: Violation[], took: Duration, cost?: number): string;
69
+ export declare function summary(violations: Violation[], took: Duration, tokens?: number): string;
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
@@ -1,8 +1,6 @@
1
1
  import {
2
- JUDGE_RULES,
3
- SIGNOFF_RULES
4
- } from "./index-3p0t2exn.js";
2
+ JUDGE_RULES
3
+ } from "./index-htwb54c6.js";
5
4
  export {
6
- SIGNOFF_RULES,
7
5
  JUDGE_RULES
8
6
  };
package/webappwiz.js CHANGED
@@ -4,17 +4,13 @@ import {
4
4
  update,
5
5
  update1 as update2,
6
6
  version
7
- } from "./index-6km6e845.js";
7
+ } from "./index-g81gg9gz.js";
8
8
  import {
9
- JUDGE_RULES,
10
- SIGNOFF_RULES
11
- } from "./index-3p0t2exn.js";
12
- import {
13
- Signoff
14
- } from "./index-3k3rtw49.js";
9
+ JUDGE_RULES
10
+ } from "./index-htwb54c6.js";
15
11
  import {
16
12
  JudgeCommands
17
- } from "./index-jbrs6gqt.js";
13
+ } from "./index-pyjg1rtk.js";
18
14
 
19
15
  // webappwiz.ts
20
16
  import { AGENTS } from "@webappwiz/rules";
@@ -28,14 +24,7 @@ webappwiz.command("update").description("pin every webappwiz dependency in a tre
28
24
  default: version,
29
25
  description: "version to pin to"
30
26
  }).action((opts, { log, fs }) => update2({ ...opts, log, fs }));
31
- var judge = ({ log, fs, ps, clock, glob }) => new JudgeCommands(JUDGE_RULES, {
32
- signoffRules: SIGNOFF_RULES,
33
- log,
34
- fs,
35
- ps,
36
- clock,
37
- glob
38
- });
27
+ var judge = ({ log, fs, ps, clock, glob }) => new JudgeCommands(JUDGE_RULES, { log, fs, ps, clock, glob });
39
28
  webappwiz.command("judge").description("check a directory against the config, one agent per glob").arg("dir", t.string(), {
40
29
  default: ".",
41
30
  description: "directory to judge (default: .)"
@@ -46,39 +35,17 @@ webappwiz.command("judge").description("check a directory against the config, on
46
35
  }).option("print", t.boolean(), {
47
36
  default: false,
48
37
  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
38
  }).option("chunk", t.number(), {
53
39
  default: 25,
54
40
  description: "files per review"
55
41
  }).option("since", t.optional(t.string()), {
56
42
  description: "only check files added or changed since this git ref"
57
- }).option("budget", t.number(), {
58
- default: 200000,
59
- description: "confirm before reading more than this many tokens"
60
- }).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(), {
43
+ }).option("concurrency-override", t.optional(t.number()), {
44
+ description: "agent calls in flight at once, over the config's concurrency"
45
+ }).option("ci", t.boolean(), {
69
46
  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
- }).option("budget", t.number(), {
75
- default: 200000,
76
- description: "confirm before reading more than this many tokens"
77
- }).action((opts, { log, ps, clock }) => new Signoff(SIGNOFF_RULES, JUDGE_RULES.agent, {
78
- log,
79
- ps,
80
- clock
81
- }).run(opts));
47
+ description: "line-by-line output with no live progress block"
48
+ }).action((opts, deps) => judge(deps).judge(opts));
82
49
  var rules = webappwiz.group("rules").description("list and print the rules, to run or to read yourself");
83
50
  rules.command("ls").description("list the rules").action((_opts, deps) => judge(deps).ls());
84
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/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-3k3rtw49.js DELETED
@@ -1,128 +0,0 @@
1
- import {
2
- ask,
3
- count,
4
- diff,
5
- divider,
6
- mode,
7
- overBudget,
8
- tokens,
9
- usd
10
- } from "./index-jbrs6gqt.js";
11
-
12
- // signoff.ts
13
- import {
14
- agentCommand,
15
- Harness,
16
- prompt as reviewPrompt
17
- } from "@webappwiz/rules";
18
- import { ConsoleLogger, color } from "webappwiz/log";
19
- import { NodePs } from "webappwiz/system";
20
- import { SystemClock } from "webappwiz/time";
21
- class Signoff {
22
- rules;
23
- defaultAgent;
24
- confirmer;
25
- log;
26
- ps;
27
- clock;
28
- constructor(rules, defaultAgent, opts = {}) {
29
- this.rules = rules;
30
- this.defaultAgent = defaultAgent;
31
- this.confirmer = opts.confirmer ?? ask;
32
- this.log = opts.log ?? new ConsoleLogger;
33
- this.ps = opts.ps ?? new NodePs;
34
- this.clock = opts.clock ?? new SystemClock;
35
- }
36
- async run(opts) {
37
- if (mode(opts) === "print") {
38
- this.print();
39
- return;
40
- }
41
- const dir = opts.dir.replace(/\/+$/, "") || "/";
42
- const { patch, added } = await diff(dir, opts.since, { ps: this.ps });
43
- if (patch === "" && added.length === 0) {
44
- this.log.info(`nothing has changed since ${opts.since}`);
45
- return;
46
- }
47
- const agent = agentCommand(opts.exec === undefined ? { agent: opts.agent ?? this.defaultAgent } : opts);
48
- const review = this.review(patch, added, opts.since);
49
- const predicted = tokens(review.bytes ?? 0);
50
- this.log.info(`weighing ${opts.since}..working tree against ` + `${count(this.rules.length, "rule")}, reading ` + `${predicted}+ tokens with ${agent.label}`);
51
- if (predicted > opts.budget) {
52
- this.log.info(overBudget(predicted, opts.budget));
53
- if (!await this.confirmer.confirm("Run anyway?")) {
54
- throw new Error("over budget");
55
- }
56
- }
57
- this.say(await this.judge(review, agent, dir));
58
- }
59
- async judge(review, agent, dir) {
60
- const harness = new Harness({
61
- log: this.log,
62
- ps: this.ps,
63
- clock: this.clock
64
- });
65
- harness.events.on("finished", ({ took, cost }) => {
66
- const spent = cost === undefined ? "" : ` ${usd(cost)}`;
67
- this.log.info(color.gray(`read in ${took.human()}${spent}`));
68
- });
69
- return await harness.run([review], agent, { cwd: dir });
70
- }
71
- say(findings) {
72
- if (findings.length === 0) {
73
- this.log.info(color.green("✓ nothing in this change needs a person"));
74
- return;
75
- }
76
- for (const finding of findings) {
77
- const where = finding.file === undefined ? "" : `${color.bold(finding.file + (finding.line === undefined ? "" : `:${finding.line}`))} `;
78
- this.log.info(` ${where}${finding.message} ${color.gray(`(${finding.rule})`)}`);
79
- }
80
- throw new Error(`${count(findings.length, "reason")} to escalate rather than merge`);
81
- }
82
- review(patch, added, ref) {
83
- const draft = {
84
- rules: this.rules,
85
- label: "signoff",
86
- context: [
87
- `The change, as \`git diff ${ref}\` prints it:`,
88
- `\`\`\`diff
89
- ${patch}
90
- \`\`\``,
91
- ...added.length === 0 ? [] : [
92
- "These files are new and are in no diff yet. Read each one from " + "your working directory:",
93
- added.map((file) => `- ${file}`).join(`
94
- `)
95
- ]
96
- ].join(`
97
-
98
- `),
99
- instructions: WEIGHING
100
- };
101
- return { ...draft, bytes: Buffer.byteLength(reviewPrompt(draft)) };
102
- }
103
- print() {
104
- if (this.rules.length === 0) {
105
- this.log.info("no signoff rules");
106
- return;
107
- }
108
- this.log.info("Weigh your change against each rule below. Anything that needs " + "review goes to a person instead of trunk.");
109
- for (const rule of this.rules) {
110
- this.log.info(`
111
- ${divider(rule.id)}
112
- `);
113
- this.log.info(rule.document.trim());
114
- }
115
- this.log.info(`
116
- ${divider()}`);
117
- }
118
- }
119
- var WEIGHING = [
120
- "These rules decide one thing: whether this change can merge on its own, " + "or needs a person to look at it first.",
121
- "",
122
- "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.",
123
- "",
124
- "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."
125
- ].join(`
126
- `);
127
-
128
- export { Signoff };