whodecided 0.1.0

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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/dist/checks/gate.d.ts +10 -0
  4. package/dist/checks/gate.js +19 -0
  5. package/dist/checks/gate.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +79 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/apply.d.ts +1 -0
  10. package/dist/commands/apply.js +68 -0
  11. package/dist/commands/apply.js.map +1 -0
  12. package/dist/commands/board.d.ts +6 -0
  13. package/dist/commands/board.js +57 -0
  14. package/dist/commands/board.js.map +1 -0
  15. package/dist/commands/distill.d.ts +18 -0
  16. package/dist/commands/distill.js +116 -0
  17. package/dist/commands/distill.js.map +1 -0
  18. package/dist/commands/gate.d.ts +2 -0
  19. package/dist/commands/gate.js +27 -0
  20. package/dist/commands/gate.js.map +1 -0
  21. package/dist/commands/hook.d.ts +1 -0
  22. package/dist/commands/hook.js +189 -0
  23. package/dist/commands/hook.js.map +1 -0
  24. package/dist/commands/recall.d.ts +19 -0
  25. package/dist/commands/recall.js +58 -0
  26. package/dist/commands/recall.js.map +1 -0
  27. package/dist/commands/report.d.ts +1 -0
  28. package/dist/commands/report.js +18 -0
  29. package/dist/commands/report.js.map +1 -0
  30. package/dist/commands/review.d.ts +1 -0
  31. package/dist/commands/review.js +211 -0
  32. package/dist/commands/review.js.map +1 -0
  33. package/dist/commands/scope.d.ts +17 -0
  34. package/dist/commands/scope.js +51 -0
  35. package/dist/commands/scope.js.map +1 -0
  36. package/dist/commands/share.d.ts +6 -0
  37. package/dist/commands/share.js +73 -0
  38. package/dist/commands/share.js.map +1 -0
  39. package/dist/commands/sync.d.ts +23 -0
  40. package/dist/commands/sync.js +214 -0
  41. package/dist/commands/sync.js.map +1 -0
  42. package/dist/config.d.ts +28 -0
  43. package/dist/config.js +80 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/distill/distiller.d.ts +15 -0
  46. package/dist/distill/distiller.js +92 -0
  47. package/dist/distill/distiller.js.map +1 -0
  48. package/dist/distill/prompt.d.ts +18 -0
  49. package/dist/distill/prompt.js +53 -0
  50. package/dist/distill/prompt.js.map +1 -0
  51. package/dist/distill/watermark.d.ts +5 -0
  52. package/dist/distill/watermark.js +22 -0
  53. package/dist/distill/watermark.js.map +1 -0
  54. package/dist/extract/candidates.d.ts +25 -0
  55. package/dist/extract/candidates.js +136 -0
  56. package/dist/extract/candidates.js.map +1 -0
  57. package/dist/git.d.ts +22 -0
  58. package/dist/git.js +78 -0
  59. package/dist/git.js.map +1 -0
  60. package/dist/jsonl.d.ts +14 -0
  61. package/dist/jsonl.js +30 -0
  62. package/dist/jsonl.js.map +1 -0
  63. package/dist/ledger/io.d.ts +30 -0
  64. package/dist/ledger/io.js +85 -0
  65. package/dist/ledger/io.js.map +1 -0
  66. package/dist/ledger/schema.d.ts +31 -0
  67. package/dist/ledger/schema.js +57 -0
  68. package/dist/ledger/schema.js.map +1 -0
  69. package/dist/patch/apply.d.ts +15 -0
  70. package/dist/patch/apply.js +63 -0
  71. package/dist/patch/apply.js.map +1 -0
  72. package/dist/patch/schema.d.ts +11 -0
  73. package/dist/patch/schema.js +21 -0
  74. package/dist/patch/schema.js.map +1 -0
  75. package/dist/render/html.d.ts +33 -0
  76. package/dist/render/html.js +593 -0
  77. package/dist/render/html.js.map +1 -0
  78. package/dist/render/md.d.ts +3 -0
  79. package/dist/render/md.js +39 -0
  80. package/dist/render/md.js.map +1 -0
  81. package/dist/render/term.d.ts +2 -0
  82. package/dist/render/term.js +40 -0
  83. package/dist/render/term.js.map +1 -0
  84. package/dist/trace/schema.d.ts +14 -0
  85. package/dist/trace/schema.js +25 -0
  86. package/dist/trace/schema.js.map +1 -0
  87. package/dist/transcript/locate.d.ts +10 -0
  88. package/dist/transcript/locate.js +30 -0
  89. package/dist/transcript/locate.js.map +1 -0
  90. package/dist/transcript/parser.d.ts +18 -0
  91. package/dist/transcript/parser.js +130 -0
  92. package/dist/transcript/parser.js.map +1 -0
  93. package/dist/validate.d.ts +4 -0
  94. package/dist/validate.js +13 -0
  95. package/dist/validate.js.map +1 -0
  96. package/package.json +48 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kai Zhu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # whodecided
2
+
3
+ > *because you rubber-stamped 5 decisions yesterday and don't know it.*
4
+
5
+ Your AI coding agent makes dozens of silent decisions per session - it stored the token in localStorage, skipped an edge case, defaulted page size to 20. You never saw the choice, and the diff won't show it. And half the time *you* "decided" by hitting `y` in 1.2 seconds.
6
+
7
+ **whodecided** distills those decisions out of your Claude Code / Codex sessions into a one-screen ledger you rule on like a judge. Approve or reject each; the ones you approve become precedent your agents follow next time instead of guessing.
8
+
9
+ It's a thin CLI + a `.wdd/` folder of plain text in your repo. No server, no database, no API keys - the distiller shells out to your already-authenticated `claude` / `codex` CLI.
10
+
11
+ ## Quick start
12
+
13
+ ```bash
14
+ cd your-project
15
+ npx whodecided review --distill-only # read-only, zero config: run over your EXISTING sessions
16
+ ```
17
+
18
+ That's the demo: it reads `~/.claude/projects/*.jsonl` and lists the decisions you didn't know you made. If a line makes your spine tingle, install it for real:
19
+
20
+ ```bash
21
+ npm i -g whodecided # gives you `wdd`
22
+ wdd hook install # trace test runs, pick a memory file for rules
23
+ wdd review # distill + open the workbench; Approve/Reject saves to the ledger
24
+ ```
25
+
26
+ ## The loop
27
+
28
+ ```
29
+ A decision happens (agent session / your keypress)
30
+ │ hooks auto-trace · transcript kept
31
+
32
+ distill claude -p / codex exec → ledger (≤10 per branch, the "why" recovered from the transcript)
33
+
34
+ review one screen, three columns:
35
+ ⚠ AI decided, never asked
36
+ ⚠ you didn't read (the 1.2s y)
37
+ ✓ you decided
38
+ Approve / Reject each · pin the core ones as rules
39
+
40
+ recall ratified rules → injected into the next task (CLAUDE.md / AGENTS.md + /recall)
41
+ └──→ the same decision never gets guessed twice
42
+ ```
43
+
44
+ The audit unit is the **branch** - `wdd review` covers the decisions inside the current PR.
45
+
46
+ ## Commands
47
+
48
+ | Command | What it does |
49
+ |---|---|
50
+ | `wdd review` | Distill, open the workbench, rule (Approve/Reject saves straight to the ledger). `--term` terminal flow, `--distill-only` read-only demo, `--html` static snapshot |
51
+ | `wdd recall <term>` | Search ruled precedent (only what you've ratified) |
52
+ | `wdd gate` | Merge gate as an exit code (ledger fresh + all silent decisions ruled); for pre-push / CI |
53
+ | `wdd report --md` | Audit report for a PR description |
54
+ | `wdd board [dirs...]` | Read-only multi-repo overview page |
55
+ | `wdd hook install` | Install hooks + `/recall` skill, and choose where rules are written |
56
+ | `wdd share <repo\|local>` | Whether `.wdd/` is tracked by git or kept local |
57
+ | `wdd sync` | Change / refresh the memory-file target for rules |
58
+
59
+ ## How it works
60
+
61
+ **What counts as a decision:** a reasonable alternative existed, and choosing it would change behavior, security, or maintenance cost. Mechanical actions don't count.
62
+
63
+ **One ledger entry** (append-only jsonl; your ruling is a separate appended line, entries are never rewritten):
64
+
65
+ ```json
66
+ {"id":"D1","what":"token → localStorage","why":"simplest, no SSR","by":"agent","aware":false,
67
+ "alternatives":["httpOnly cookie"],"files":["src/auth.ts"],"ref":"tx:a1b2:1042"}
68
+ ```
69
+
70
+ `ref` points back into the transcript, so the ledger never re-narrates the session. Only decisions you *ruled on* become precedent - unreviewed guesses never leak into agent memory.
71
+
72
+ **Review before merge, not after.** Overturning a decision pre-merge is a cheap fix in the worktree; post-merge it's a new PR or a migration.
73
+
74
+ ## Design constraints
75
+
76
+ - **No server, no database, no daemon.** The truth is plain text in your repo; `rm -rf` the tool and it all stays readable with cat/jq/grep.
77
+ - **It never commits for you** by default - it writes files and leaves your git history alone (opt in with `commit: on`).
78
+ - **Zero API keys.** The one LLM consumer shells out to your `claude` / `codex` CLI; model follows your CLI's default.
79
+ - **One screen.** Every default output fits a screen; ≤10 decisions per branch.
80
+ - Single runtime dependency (`yaml`); the HTML workbench is a self-contained file (embedded JSON, vanilla JS, no CDN, no build).
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,10 @@
1
+ import type { DecisionCandidate } from "../extract/candidates.js";
2
+ import type { DecisionEntry, RatifyEntry } from "../ledger/schema.js";
3
+ import type { Watermark } from "../distill/watermark.js";
4
+ export interface GateStatus {
5
+ fresh: boolean;
6
+ staleSessions: string[];
7
+ pendingSilent: string[];
8
+ green: boolean;
9
+ }
10
+ export declare function gateStatus(entries: DecisionEntry[], ratifies: RatifyEntry[], candidates: DecisionCandidate[], watermark: Watermark): GateStatus;
@@ -0,0 +1,19 @@
1
+ import { verdictOf } from "../ledger/io.js";
2
+ export function gateStatus(entries, ratifies, candidates, watermark) {
3
+ const stale = new Set();
4
+ for (const c of candidates) {
5
+ const [, session, line] = c.ref.split(":");
6
+ if (session && Number(line) > (watermark[session] ?? 0))
7
+ stale.add(session);
8
+ }
9
+ const pendingSilent = entries
10
+ .filter((e) => !e.aware && verdictOf(ratifies, e.id) === undefined)
11
+ .map((e) => e.id);
12
+ return {
13
+ fresh: stale.size === 0,
14
+ staleSessions: [...stale],
15
+ pendingSilent,
16
+ green: stale.size === 0 && pendingSilent.length === 0,
17
+ };
18
+ }
19
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/checks/gate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAU5C,MAAM,UAAU,UAAU,CACxB,OAAwB,EACxB,QAAuB,EACvB,UAA+B,EAC/B,SAAoB;IAEpB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,aAAa,GAAG,OAAO;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,SAAS,CAAC;SAClE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpB,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC;QACvB,aAAa,EAAE,CAAC,GAAG,KAAK,CAAC;QACzB,aAAa;QACb,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;KACtD,CAAC;AACJ,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ const USAGE = `whodecided (wdd) - common law for agentic coding
3
+
4
+ Usage: wdd <command> [args]
5
+
6
+ Everyday:
7
+ review Distill, open the workbench, rule (Approve/Reject save straight to the ledger).
8
+ --term terminal flow · --distill-only zero-config read-only demo · --html static snapshot
9
+ recall <term> Search ruled precedent (only decisions you've ratified)
10
+ gate Two-green merge gate (exit 0 = all green); for pre-push / CI
11
+
12
+ Setup (once):
13
+ hook install Install CC hooks + /recall skill, and pick the memory file for precedents
14
+ share <repo|local> Audit travels with the repo (committed) or stays local
15
+ sync Change/refresh the memory-file target (auto-syncs on each ruling otherwise)
16
+
17
+ Output & collaboration:
18
+ report [--md] Audit report for a PR description
19
+ board [dirs...] Read-only multi-repo overview page
20
+ apply [patch] Land a static-snapshot patch (the live workbench saves directly)
21
+
22
+ Internal (run by hooks):
23
+ distill Headless incremental distill
24
+ hook trace | autodistill PostToolUse / Stop receivers
25
+ `;
26
+ const commands = ["review", "apply", "recall", "gate", "report", "board", "share", "sync", "distill", "hook"];
27
+ const [command, ...rest] = process.argv.slice(2);
28
+ if (!command || command === "--help" || command === "-h") {
29
+ process.stdout.write(USAGE);
30
+ process.exit(command ? 0 : 1);
31
+ }
32
+ if (!commands.includes(command)) {
33
+ process.stderr.write(`wdd: unknown command "${command}"\n\n${USAGE}`);
34
+ process.exit(1);
35
+ }
36
+ if (command === "review") {
37
+ const { review } = await import("./commands/review.js");
38
+ process.exit(await review(rest));
39
+ }
40
+ if (command === "apply") {
41
+ const { apply } = await import("./commands/apply.js");
42
+ process.exit(apply(rest));
43
+ }
44
+ if (command === "recall") {
45
+ const { recall } = await import("./commands/recall.js");
46
+ process.exit(recall(rest));
47
+ }
48
+ if (command === "gate") {
49
+ const { gate } = await import("./commands/gate.js");
50
+ process.exit(gate(rest));
51
+ }
52
+ if (command === "report") {
53
+ const { report } = await import("./commands/report.js");
54
+ process.exit(report(rest));
55
+ }
56
+ if (command === "board") {
57
+ const { board } = await import("./commands/board.js");
58
+ process.exit(board(rest));
59
+ }
60
+ if (command === "share") {
61
+ const { share } = await import("./commands/share.js");
62
+ process.exit(share(rest));
63
+ }
64
+ if (command === "sync") {
65
+ const { sync } = await import("./commands/sync.js");
66
+ process.exit(await sync(rest));
67
+ }
68
+ if (command === "distill") {
69
+ const { distillCmd } = await import("./commands/distill.js");
70
+ process.exit(await distillCmd(rest));
71
+ }
72
+ if (command === "hook") {
73
+ const { hook } = await import("./commands/hook.js");
74
+ process.exit(await hook(rest));
75
+ }
76
+ process.stderr.write(`wdd ${command}: not implemented yet (see SPEC.md section 8)\n`);
77
+ process.exit(1);
78
+ export {};
79
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function apply(args: string[]): number;
@@ -0,0 +1,68 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { repoRoot } from "../git.js";
5
+ import { applyOps } from "../patch/apply.js";
6
+ import { parsePatch } from "../patch/schema.js";
7
+ import { IGNORED_MSG } from "./distill.js";
8
+ export function apply(args) {
9
+ const root = repoRoot(process.cwd());
10
+ if (!root) {
11
+ process.stderr.write("wdd apply: not inside a git repo\n");
12
+ return 1;
13
+ }
14
+ // With no path, pick up the patch the workbench just exported (newest in Downloads).
15
+ const file = args.find((a) => !a.startsWith("--")) ?? findExportedPatch();
16
+ if (!file) {
17
+ process.stderr.write("wdd apply: no patch given and none found in Downloads.\n" +
18
+ "In the workbench, rule on decisions and click Export, then run `wdd apply` again.\n");
19
+ return 1;
20
+ }
21
+ let text;
22
+ try {
23
+ text = readFileSync(file, "utf8");
24
+ }
25
+ catch {
26
+ process.stderr.write(`cannot read ${file}\n`);
27
+ return 1;
28
+ }
29
+ if (!args.some((a) => !a.startsWith("--")))
30
+ process.stdout.write(`applying ${file}\n`);
31
+ const { values, errors } = parsePatch(text);
32
+ if (errors.length > 0) {
33
+ for (const e of errors)
34
+ process.stderr.write(`patch line ${e.line}: ${e.message}\n`);
35
+ process.stderr.write("patch rejected, nothing written\n");
36
+ return 1;
37
+ }
38
+ const result = applyOps(root, values);
39
+ if (typeof result === "string") {
40
+ process.stderr.write(`patch rejected, nothing written: ${result}\n`);
41
+ return 1;
42
+ }
43
+ process.stdout.write(`saved ${result.applied} ruling(s)${result.skipped ? `, ${result.skipped} unchanged` : ""}${result.commit ? ` → commit ${result.commit}` : ""}\n`);
44
+ if (result.uncommitted)
45
+ process.stderr.write(IGNORED_MSG + "\n");
46
+ if (result.overturned > 0)
47
+ process.stdout.write(`↳ ${result.overturned} rejected - AI won't repeat these; the code fix is separate work\n`);
48
+ return 0;
49
+ }
50
+ /** The workbench downloads review.patch.jsonl; grab the newest one from Downloads. */
51
+ function findExportedPatch() {
52
+ const dir = join(homedir(), "Downloads");
53
+ let names;
54
+ try {
55
+ names = readdirSync(dir);
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ const cutoff = Date.now() - 60 * 60 * 1000; // only very recent exports, avoid stale
61
+ return names
62
+ .filter((n) => n.endsWith(".patch.jsonl"))
63
+ .map((n) => ({ path: join(dir, n), mtime: statSync(join(dir, n)).mtimeMs }))
64
+ .filter((f) => f.mtime >= cutoff)
65
+ .sort((a, b) => b.mtime - a.mtime)
66
+ .map((f) => f.path)[0];
67
+ }
68
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Read-only multi-repo overview: one static page aggregating every repo's
3
+ * ledger, traces, and gate status. No distilling, no LLM calls; rulings
4
+ * happen inside each repo via `wdd review`.
5
+ */
6
+ export declare function board(args: string[]): number;
@@ -0,0 +1,57 @@
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { basename, join, resolve } from "node:path";
3
+ import { gateStatus } from "../checks/gate.js";
4
+ import { loadWatermark, watermarkPath } from "../distill/watermark.js";
5
+ import { ledgerPath, readLedger, verdictOf } from "../ledger/io.js";
6
+ import { renderWorkbench } from "../render/html.js";
7
+ import { parseTrace } from "../trace/schema.js";
8
+ import { resolveScope } from "./scope.js";
9
+ /**
10
+ * Read-only multi-repo overview: one static page aggregating every repo's
11
+ * ledger, traces, and gate status. No distilling, no LLM calls; rulings
12
+ * happen inside each repo via `wdd review`.
13
+ */
14
+ export function board(args) {
15
+ const explicit = args.filter((a) => !a.startsWith("--") && args[args.indexOf(a) - 1] !== "--out");
16
+ const outIdx = args.indexOf("--out");
17
+ const out = outIdx >= 0 ? (args[outIdx + 1] ?? "wdd-board.html") : "wdd-board.html";
18
+ const roots = (explicit.length > 0 ? explicit.map((p) => resolve(p)) : discover(process.cwd())).filter((p) => existsSync(ledgerPath(p)));
19
+ if (roots.length === 0) {
20
+ process.stderr.write("no repos with a .wdd/ledger.jsonl found (pass paths explicitly or run in their parent dir)\n");
21
+ return 1;
22
+ }
23
+ const repos = roots.map((root) => {
24
+ const scope = resolveScope([], root);
25
+ const { entries, ratifies } = readLedger(ledgerPath(root));
26
+ const scoped = scope.branch ? entries.filter((e) => !e.branch || e.branch === scope.branch) : entries;
27
+ const tracePath = join(root, ".wdd", "trace.jsonl");
28
+ const slices = {};
29
+ for (const c of scope.candidates)
30
+ slices[c.ref] = c.slice.slice(0, 4000);
31
+ return {
32
+ repo: basename(root),
33
+ root,
34
+ branch: scope.branch,
35
+ entries: scoped.map((e) => ({ ...e, verdict: verdictOf(ratifies, e.id) })),
36
+ traces: existsSync(tracePath) ? parseTrace(readFileSync(tracePath, "utf8")).values : [],
37
+ slices,
38
+ gate: gateStatus(scoped, ratifies, scope.candidates, loadWatermark(watermarkPath(root))),
39
+ };
40
+ });
41
+ const pending = repos.reduce((s, r) => s + r.entries.filter((e) => !e.verdict).length, 0);
42
+ writeFileSync(out, renderWorkbench({ title: `board · ${repos.length} repos`, interactive: false, repos }));
43
+ process.stdout.write(`board written: ${out} (${repos.length} repos, ${pending} pending rulings)\n`);
44
+ return 0;
45
+ }
46
+ /** Default discovery: immediate subdirectories of cwd that carry a ledger. */
47
+ function discover(cwd) {
48
+ try {
49
+ return readdirSync(cwd, { withFileTypes: true })
50
+ .filter((d) => d.isDirectory() && !d.name.startsWith("."))
51
+ .map((d) => join(cwd, d.name));
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ //# sourceMappingURL=board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/commands/board.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type Scope } from "./scope.js";
2
+ export interface DistillRunResult {
3
+ added: number;
4
+ degraded?: string;
5
+ uncommitted?: boolean;
6
+ }
7
+ /**
8
+ * Incremental distill: everything past the watermark, deduped by ref,
9
+ * appended and committed. Shared by `wdd review` and headless `wdd distill`
10
+ * (which the Stop hook spawns for near-real-time logging).
11
+ */
12
+ export declare function runIncrementalDistill(scope: Scope, root: string, progress?: boolean): Promise<DistillRunResult>;
13
+ /** Shown when .wdd is gitignored: data is safe on disk, just not versioned. */
14
+ export declare const IGNORED_MSG: string;
15
+ /** `wdd distill`: headless incremental distill, safe to run from hooks. */
16
+ export declare function distillCmd(args: string[]): Promise<number>;
17
+ /** Minimal stderr spinner while a blocking step runs; returns a stop function. */
18
+ export declare function spinner(label: string): () => void;
@@ -0,0 +1,116 @@
1
+ import { relative } from "node:path";
2
+ import { distill } from "../distill/distiller.js";
3
+ import { loadWatermark, saveWatermark, watermarkPath } from "../distill/watermark.js";
4
+ import { commitFiles } from "../git.js";
5
+ import { appendLedgerLines, ledgerPath, nextDecisionNumber, readLedger } from "../ledger/io.js";
6
+ import { resolveScope } from "./scope.js";
7
+ /**
8
+ * Incremental distill: everything past the watermark, deduped by ref,
9
+ * appended and committed. Shared by `wdd review` and headless `wdd distill`
10
+ * (which the Stop hook spawns for near-real-time logging).
11
+ */
12
+ export async function runIncrementalDistill(scope, root, progress = false) {
13
+ const wmFile = watermarkPath(root);
14
+ const watermark = loadWatermark(wmFile);
15
+ const fresh = scope.candidates.filter((c) => lineOf(c.ref) > (watermark[sessionOf(c.ref)] ?? 0));
16
+ if (fresh.length === 0) {
17
+ if (progress)
18
+ process.stderr.write("no new decisions since last review\n");
19
+ return { added: 0 };
20
+ }
21
+ const stop = progress
22
+ ? spinner(`distilling ${fresh.length} new candidate(s) with ${scope.config.distill.cmd.join(" ")} — can take ~10-60s`)
23
+ : () => { };
24
+ const { entries, degraded } = await distill(fresh, scope.config.distill);
25
+ stop();
26
+ if (degraded)
27
+ return { added: 0, degraded };
28
+ let added = 0;
29
+ if (entries.length > 0) {
30
+ const file = ledgerPath(root);
31
+ const existing = readLedger(file).entries;
32
+ const existingRefs = new Set(existing.map((e) => e.ref));
33
+ const novel = entries.filter((e) => !existingRefs.has(e.ref));
34
+ let n = nextDecisionNumber(existing);
35
+ const byRef = new Map(scope.candidates.map((c) => [c.ref, c]));
36
+ const numbered = novel.map((e) => {
37
+ const c = byRef.get(e.ref);
38
+ return {
39
+ ...e,
40
+ id: `D${n++}`,
41
+ ...(scope.branch ? { branch: scope.branch } : {}),
42
+ ...(c?.t ? { t: c.t } : {}),
43
+ ...(c?.cwd ? { cwd: c.cwd } : {}),
44
+ // The model is the decider only for agent decisions; for a user
45
+ // decision the human decided, so recording a model would mislead.
46
+ ...(c?.model && e.by === "agent" ? { model: c.model } : {}),
47
+ ...(c?.latencyMs !== undefined && e.by === "user" ? { latencyMs: c.latencyMs } : {}),
48
+ ...(c?.files && c.files.length ? { files: c.files } : {}),
49
+ };
50
+ });
51
+ appendLedgerLines(file, numbered);
52
+ added = numbered.length;
53
+ // Auto-commit only when the user opted in; otherwise leave it in the tree.
54
+ if (scope.config.share === "repo" && scope.config.commit === "on") {
55
+ const commit = commitFiles(root, [relative(root, file)], `wdd distill: +${numbered.length} decisions`);
56
+ if (commit.ignored) {
57
+ advanceWatermark(watermark, scope.candidates);
58
+ saveWatermark(wmFile, watermark);
59
+ return { added, uncommitted: true };
60
+ }
61
+ }
62
+ }
63
+ advanceWatermark(watermark, scope.candidates);
64
+ saveWatermark(wmFile, watermark);
65
+ return { added };
66
+ }
67
+ /** Shown when .wdd is gitignored: data is safe on disk, just not versioned. */
68
+ export const IGNORED_MSG = "⚠ .wdd is gitignored, so the audit trail is written to disk but NOT committed.\n" +
69
+ " The audit is meant to travel with your code - remove `.wdd` from .gitignore to version it.\n" +
70
+ " (Or keep it local-only; your ledger stays readable at .wdd/ledger.jsonl either way.)";
71
+ /** `wdd distill`: headless incremental distill, safe to run from hooks. */
72
+ export async function distillCmd(args) {
73
+ const quiet = args.includes("--quiet");
74
+ const scope = resolveScope(args, process.cwd());
75
+ if (!scope.root) {
76
+ if (!quiet)
77
+ process.stderr.write("wdd distill: not inside a git repo\n");
78
+ return quiet ? 0 : 1;
79
+ }
80
+ const result = await runIncrementalDistill(scope, scope.root);
81
+ if (!quiet) {
82
+ if (result.degraded)
83
+ process.stderr.write(`distiller degraded: ${result.degraded}\n`);
84
+ if (result.uncommitted)
85
+ process.stderr.write(IGNORED_MSG + "\n");
86
+ process.stdout.write(`+${result.added} decisions\n`);
87
+ }
88
+ return 0;
89
+ }
90
+ /** Minimal stderr spinner while a blocking step runs; returns a stop function. */
91
+ export function spinner(label) {
92
+ if (!process.stderr.isTTY) {
93
+ process.stderr.write(label + "\n");
94
+ return () => { };
95
+ }
96
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
97
+ let i = 0;
98
+ const timer = setInterval(() => process.stderr.write(`\r${frames[i++ % frames.length]} ${label}`), 90);
99
+ return () => {
100
+ clearInterval(timer);
101
+ process.stderr.write("\r\x1b[K"); // clear the spinner line
102
+ };
103
+ }
104
+ function sessionOf(ref) {
105
+ return ref.split(":")[1] ?? "";
106
+ }
107
+ function lineOf(ref) {
108
+ return Number(ref.split(":")[2] ?? 0);
109
+ }
110
+ function advanceWatermark(watermark, candidates) {
111
+ for (const c of candidates) {
112
+ const s = sessionOf(c.ref);
113
+ watermark[s] = Math.max(watermark[s] ?? 0, lineOf(c.ref));
114
+ }
115
+ }
116
+ //# sourceMappingURL=distill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distill.js","sourceRoot":"","sources":["../../src/commands/distill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAkB,MAAM,yBAAyB,CAAC;AAEtG,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAc,MAAM,YAAY,CAAC;AAQtD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAAY,EAAE,IAAY,EAAE,QAAQ,GAAG,KAAK;IACtF,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,IAAI,QAAQ;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC3E,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACtB,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ;QACnB,CAAC,CAAC,OAAO,CAAC,cAAc,KAAK,CAAC,MAAM,0BAA0B,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,qBAAqB,CAAC;QACtH,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;IACb,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzE,IAAI,EAAE,CAAC;IACP,IAAI,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;IAE5C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;QAC1C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC/B,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC3B,OAAO;gBACL,GAAG,CAAC;gBACJ,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;gBACb,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjD,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjC,gEAAgE;gBAChE,kEAAkE;gBAClE,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3D,GAAG,CAAC,CAAC,EAAE,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpF,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1D,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClC,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;QACxB,2EAA2E;QAC3E,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAClE,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,iBAAiB,QAAQ,CAAC,MAAM,YAAY,CAAC,CAAC;YACvG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC9C,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;gBACjC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YACtC,CAAC;QACH,CAAC;IACH,CAAC;IACD,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC9C,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACjC,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,MAAM,WAAW,GACtB,kFAAkF;IAClF,gGAAgG;IAChG,wFAAwF,CAAC;AAE3F,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAc;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,QAAQ;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACtF,IAAI,MAAM,CAAC,WAAW;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,cAAc,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QACnC,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACvG,OAAO,GAAG,EAAE;QACV,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,yBAAyB;IAC7D,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACjC,CAAC;AAED,SAAS,MAAM,CAAC,GAAW;IACzB,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAoB,EAAE,UAA+B;IAC7E,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3B,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** Deterministic, read-only, no LLM: safe and free to run in CI. */
2
+ export declare function gate(args: string[]): number;
@@ -0,0 +1,27 @@
1
+ import { loadWatermark, watermarkPath } from "../distill/watermark.js";
2
+ import { gateStatus } from "../checks/gate.js";
3
+ import { ledgerPath, readLedger } from "../ledger/io.js";
4
+ import { resolveScope } from "./scope.js";
5
+ /** Deterministic, read-only, no LLM: safe and free to run in CI. */
6
+ export function gate(args) {
7
+ const scope = resolveScope(args, process.cwd());
8
+ if (!scope.root) {
9
+ process.stderr.write("wdd gate: not inside a git repo\n");
10
+ return 1;
11
+ }
12
+ const { entries, ratifies } = readLedger(ledgerPath(scope.root));
13
+ const scoped = scope.branch ? entries.filter((e) => !e.branch || e.branch === scope.branch) : entries;
14
+ const status = gateStatus(scoped, ratifies, scope.candidates, loadWatermark(watermarkPath(scope.root)));
15
+ if (status.green) {
16
+ process.stdout.write("gate: green (ledger fresh, all silent decisions ruled)\n");
17
+ return 0;
18
+ }
19
+ if (!status.fresh) {
20
+ process.stdout.write(`✗ ledger stale: undistilled activity in session(s) ${status.staleSessions.join(", ")} → run wdd review\n`);
21
+ }
22
+ for (const id of status.pendingSilent) {
23
+ process.stdout.write(`✗ silent decision pending ruling: ${id}\n`);
24
+ }
25
+ return 1;
26
+ }
27
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/commands/gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,oEAAoE;AACpE,MAAM,UAAU,IAAI,CAAC,IAAc;IACjC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAC1D,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACtG,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,aAAa,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxG,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;QACjF,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sDAAsD,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACnI,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACtC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,EAAE,IAAI,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function hook(args: string[]): Promise<number>;