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
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `claude -p` distiller calls get logged as their own sessions under
3
+ * ~/.claude/projects. This sentinel is embedded in every distiller prompt so
4
+ * locate can skip those sessions - otherwise whodecided audits its own
5
+ * distiller output (a feedback loop that surfaces the prompt as a "decision").
6
+ */
7
+ export const DISTILLER_SENTINEL = "WHODECIDED_DISTILLER_SESSION_v1";
8
+ /**
9
+ * Native language scans faster: when lang is "auto", follow whichever
10
+ * language dominates the user's own messages in the slices.
11
+ */
12
+ export function resolveLang(candidates, lang) {
13
+ if (lang !== "auto")
14
+ return lang;
15
+ const userText = candidates
16
+ .flatMap((c) => c.slice.split("\n"))
17
+ .filter((l) => l.includes("] USER"))
18
+ .join("");
19
+ const cjk = (userText.match(/[一-鿿]/g) ?? []).length;
20
+ return cjk > userText.length * 0.1 ? "zh" : "en";
21
+ }
22
+ export function buildPrompt(candidates, budget, lang = "en") {
23
+ const langRule = lang === "zh"
24
+ ? 'Write "what", "why", "note" and "alternatives" values in Simplified Chinese (keep code identifiers, commands and product names as-is).'
25
+ : 'Write "what", "why", "note" and "alternatives" values in English.';
26
+ const instructions = `[${DISTILLER_SENTINEL}] You are a decision distiller auditing an AI coding session.
27
+
28
+ Definition of a decision (the only test that matters):
29
+ a reasonable alternative existed, and choosing it would change behavior,
30
+ security, or maintenance cost. Purely mechanical actions (fixing typos,
31
+ following existing code style) do NOT count.
32
+
33
+ Compression rules:
34
+ 1. Keep only decisions that changed the outcome; if deleting the entry does not change the understanding of "what was chosen", drop it.
35
+ 2. Merge repetition (e.g. 8 rapid approvals in a row -> one entry).
36
+ 3. Hard budget: at most ${budget} entries total. Exceeding it means your bar is too low.
37
+ 4. "what" is a single line, commit-message style.
38
+
39
+ Input: a JSON array of candidates (provided separately). Each has a "ref" (transcript anchor),
40
+ a "kind", an "aware" flag, and a "slice" (transcript excerpt).
41
+ - kind "question"/"plan"/"veto": an explicit user decision point. Extract WHAT was decided from the slice. For "plan" with bundled=true, unpack the plan into its distinct decisions, one entry each, all sharing that candidate's ref.
42
+ - kind "turn": mine the excerpt for SILENT AGENT decisions (technology picks, defaults chosen, edge cases skipped, tests skipped). by="agent", aware=false unless the slice shows the agent explicitly asked the user first.
43
+
44
+ Output: ONLY a JSON array, no prose, no markdown fence. Each element:
45
+ {"id":"D<n>","what":"<one line>","why":"<short reason if recoverable>","by":"agent"|"user","aware":<boolean>,"alternatives":["..."],"reversibility":"low"|"medium"|"high","ref":"<ref of the source candidate>"}
46
+ - "ref" MUST be copied exactly from one of the input candidates.
47
+ - "aware" for user decisions: copy the candidate's aware flag.
48
+ - Omit "why"/"alternatives"/"reversibility" when unknown; never invent.
49
+ - ${langRule}`;
50
+ const payload = JSON.stringify(candidates.map(({ ref, kind, aware, bundled, latencyMs, slice }) => ({ ref, kind, aware, bundled, latencyMs, slice })));
51
+ return { instructions, payload };
52
+ }
53
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/distill/prompt.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,iCAAiC,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,UAA+B,EAAE,IAA0B;IACrF,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,QAAQ,GAAG,UAAU;SACxB,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SACnC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;SACnC,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACpD,OAAO,GAAG,GAAG,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAA+B,EAAE,MAAc,EAAE,OAAoB,IAAI;IACnG,MAAM,QAAQ,GACZ,IAAI,KAAK,IAAI;QACX,CAAC,CAAC,wIAAwI;QAC1I,CAAC,CAAC,mEAAmE,CAAC;IAC1E,MAAM,YAAY,GAAG,IAAI,kBAAkB;;;;;;;;;;0BAUnB,MAAM;;;;;;;;;;;;;IAa5B,QAAQ,EAAE,CAAC;IACb,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAC5B,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CACvH,CAAC;IACF,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Highest transcript line already distilled, per session. Disposable cache. */
2
+ export type Watermark = Record<string, number>;
3
+ export declare function watermarkPath(repoRoot: string): string;
4
+ export declare function loadWatermark(file: string): Watermark;
5
+ export declare function saveWatermark(file: string, wm: Watermark): void;
@@ -0,0 +1,22 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { parse, stringify } from "yaml";
4
+ export function watermarkPath(repoRoot) {
5
+ return join(repoRoot, ".wdd", ".cache", "watermark.yaml");
6
+ }
7
+ export function loadWatermark(file) {
8
+ if (!existsSync(file))
9
+ return {};
10
+ try {
11
+ const data = parse(readFileSync(file, "utf8"));
12
+ return typeof data === "object" && data !== null ? data : {};
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ export function saveWatermark(file, wm) {
19
+ mkdirSync(dirname(file), { recursive: true });
20
+ writeFileSync(file, stringify(wm));
21
+ }
22
+ //# sourceMappingURL=watermark.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watermark.js","sourceRoot":"","sources":["../../src/distill/watermark.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAKxC,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAE,IAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,EAAa;IACvD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { SessionEvent } from "../transcript/parser.js";
2
+ export interface DecisionCandidate {
3
+ ref: string;
4
+ kind: "question" | "plan" | "veto" | "turn";
5
+ aware: boolean;
6
+ t?: string;
7
+ cwd?: string;
8
+ model?: string;
9
+ latencyMs?: number;
10
+ bundled?: boolean;
11
+ files?: string[];
12
+ summary: string;
13
+ slice: string;
14
+ }
15
+ export interface Heuristics {
16
+ reflexMs: number;
17
+ }
18
+ export declare const DEFAULT_HEURISTICS: Heuristics;
19
+ /**
20
+ * Deterministic candidate extraction, no LLM. Two kinds of output:
21
+ * explicit user decision points (question/plan/veto, with clean latency where
22
+ * the tool blocks on the human), and per-turn slices for the distiller to
23
+ * mine silent agent decisions from.
24
+ */
25
+ export declare function extractCandidates(events: SessionEvent[], heuristics?: Heuristics): DecisionCandidate[];
@@ -0,0 +1,136 @@
1
+ export const DEFAULT_HEURISTICS = { reflexMs: 3000 };
2
+ // File-editing tools whose input is a path (grounds "what" in actual code).
3
+ const FILE_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit", "Update"]);
4
+ function filesTouched(events) {
5
+ const files = [];
6
+ for (const e of events) {
7
+ if (e.kind === "tool_use" && e.toolName && FILE_TOOLS.has(e.toolName) && e.text && !files.includes(e.text)) {
8
+ files.push(e.text);
9
+ }
10
+ }
11
+ return files.slice(0, 8);
12
+ }
13
+ const MAX_SLICE_LINES = 200;
14
+ const MAX_LINE_CHARS = 240;
15
+ /**
16
+ * Deterministic candidate extraction, no LLM. Two kinds of output:
17
+ * explicit user decision points (question/plan/veto, with clean latency where
18
+ * the tool blocks on the human), and per-turn slices for the distiller to
19
+ * mine silent agent decisions from.
20
+ */
21
+ export function extractCandidates(events, heuristics = DEFAULT_HEURISTICS) {
22
+ const candidates = [];
23
+ // Decision anchors are often user events (which carry no model); track the
24
+ // model of the most recent assistant turn so each candidate records which
25
+ // model was acting when the decision was made.
26
+ let activeModel;
27
+ for (const e of events) {
28
+ if (e.model)
29
+ activeModel = e.model;
30
+ if (e.kind === "question_response") {
31
+ candidates.push({
32
+ ref: ref(e),
33
+ kind: "question",
34
+ aware: e.latencyMs === undefined || e.latencyMs >= heuristics.reflexMs,
35
+ ...anchor(e, activeModel),
36
+ latencyMs: e.latencyMs,
37
+ summary: firstLine(e.text),
38
+ slice: sliceAround(events, e),
39
+ });
40
+ }
41
+ else if (e.kind === "plan_approval") {
42
+ // Latency evidence is one-sided: fast proves blind, slow proves nothing
43
+ // (the user may have been away). Approval alone never earns aware:true.
44
+ candidates.push({
45
+ ref: ref(e),
46
+ kind: "plan",
47
+ aware: false,
48
+ ...anchor(e, activeModel),
49
+ latencyMs: e.latencyMs,
50
+ bundled: true,
51
+ summary: firstLine(e.text),
52
+ slice: sliceAround(events, e),
53
+ });
54
+ }
55
+ else if (e.kind === "tool_result" && e.rejected) {
56
+ candidates.push({
57
+ ref: ref(e),
58
+ kind: "veto",
59
+ aware: true,
60
+ ...anchor(e, activeModel),
61
+ summary: `rejected ${e.toolName ?? "tool"}`,
62
+ slice: sliceAround(events, e),
63
+ });
64
+ }
65
+ }
66
+ candidates.push(...turnSlices(events));
67
+ return candidates;
68
+ }
69
+ function ref(e) {
70
+ return `tx:${e.session}:${e.line}`;
71
+ }
72
+ function anchor(e, model) {
73
+ return { ...(e.t ? { t: e.t } : {}), ...(e.cwd ? { cwd: e.cwd } : {}), ...(model ? { model } : {}) };
74
+ }
75
+ function firstLine(text) {
76
+ return (text ?? "").split("\n")[0]?.slice(0, 120) ?? "";
77
+ }
78
+ /** Compact excerpt of the events surrounding one decision point. */
79
+ function sliceAround(events, center) {
80
+ const i = events.indexOf(center);
81
+ return renderSlice(events.slice(Math.max(0, i - 15), i + 3));
82
+ }
83
+ /** One candidate per user turn: the distiller mines agent decisions from it. */
84
+ function turnSlices(events) {
85
+ const turns = [];
86
+ let start = -1;
87
+ const flush = (end) => {
88
+ if (start < 0)
89
+ return;
90
+ const turn = events.slice(start, end);
91
+ const first = turn[0];
92
+ if (!first || turn.length < 2)
93
+ return;
94
+ // The turn's agent decisions come from its assistant events; record the
95
+ // last model that acted within the turn.
96
+ const turnModel = turn.filter((e) => e.model).at(-1)?.model;
97
+ const files = filesTouched(turn);
98
+ turns.push({
99
+ ref: ref(first),
100
+ kind: "turn",
101
+ aware: false,
102
+ ...anchor(first, turnModel),
103
+ ...(files.length ? { files } : {}),
104
+ summary: firstLine(first.text),
105
+ slice: renderSlice(turn),
106
+ });
107
+ };
108
+ events.forEach((e, i) => {
109
+ if (e.kind === "user_text") {
110
+ flush(i);
111
+ start = i;
112
+ }
113
+ });
114
+ flush(events.length);
115
+ return turns;
116
+ }
117
+ function renderSlice(events) {
118
+ const lines = [];
119
+ for (const e of events) {
120
+ if (lines.length >= MAX_SLICE_LINES)
121
+ break;
122
+ const tag = e.kind === "user_text" ? "USER" :
123
+ e.kind === "assistant_text" ? "AGENT" :
124
+ e.kind === "tool_use" ? `TOOL ${e.toolName}` :
125
+ e.kind === "tool_result" && e.rejected ? "USER-REJECTED" :
126
+ e.kind === "question_response" ? "USER-ANSWERED" :
127
+ e.kind === "plan_approval" ? "USER-APPROVED-PLAN" :
128
+ null;
129
+ if (tag === null)
130
+ continue;
131
+ const text = (e.text ?? "").replace(/\s+/g, " ").slice(0, MAX_LINE_CHARS);
132
+ lines.push(`[${e.line}] ${tag}: ${text}`);
133
+ }
134
+ return lines.join("\n");
135
+ }
136
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../src/extract/candidates.ts"],"names":[],"mappings":"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"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export declare function git(cwd: string, ...args: string[]): string;
2
+ export declare function tryGit(cwd: string, ...args: string[]): string | undefined;
3
+ export declare function repoRoot(cwd: string): string | undefined;
4
+ export declare function currentBranch(cwd: string): string | undefined;
5
+ export declare function defaultBranch(cwd: string): string | undefined;
6
+ /** ISO time of the merge-base with the default branch, if on a feature branch. */
7
+ export declare function branchSinceTime(cwd: string): string | undefined;
8
+ /** All worktree paths of this repo (including the main checkout). */
9
+ export declare function worktreePaths(cwd: string): string[];
10
+ /** True if the path is excluded by some .gitignore (git check-ignore exits 0). */
11
+ export declare function pathIgnored(cwd: string, relPath: string): boolean;
12
+ export interface CommitResult {
13
+ sha?: string;
14
+ committed: boolean;
15
+ ignored: boolean;
16
+ }
17
+ /**
18
+ * Stage only the given files and commit. Never throws on the ignored case
19
+ * (the audit's whole premise is being committed, but the file is already on
20
+ * disk either way) - callers surface an actionable message instead.
21
+ */
22
+ export declare function commitFiles(cwd: string, files: string[], message: string): CommitResult;
package/dist/git.js ADDED
@@ -0,0 +1,78 @@
1
+ import { execFileSync } from "node:child_process";
2
+ export function git(cwd, ...args) {
3
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4
+ }
5
+ export function tryGit(cwd, ...args) {
6
+ try {
7
+ return git(cwd, ...args);
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ }
13
+ export function repoRoot(cwd) {
14
+ return tryGit(cwd, "rev-parse", "--show-toplevel");
15
+ }
16
+ export function currentBranch(cwd) {
17
+ const branch = tryGit(cwd, "rev-parse", "--abbrev-ref", "HEAD");
18
+ return branch === "HEAD" ? undefined : branch;
19
+ }
20
+ export function defaultBranch(cwd) {
21
+ const remote = tryGit(cwd, "rev-parse", "--abbrev-ref", "origin/HEAD");
22
+ if (remote)
23
+ return remote.replace("origin/", "");
24
+ for (const name of ["main", "master"]) {
25
+ if (tryGit(cwd, "rev-parse", "--verify", name) !== undefined)
26
+ return name;
27
+ }
28
+ return undefined;
29
+ }
30
+ /** ISO time of the merge-base with the default branch, if on a feature branch. */
31
+ export function branchSinceTime(cwd) {
32
+ const branch = currentBranch(cwd);
33
+ const base = defaultBranch(cwd);
34
+ if (!branch || !base || branch === base)
35
+ return undefined;
36
+ const sha = tryGit(cwd, "merge-base", "HEAD", base);
37
+ return sha ? tryGit(cwd, "show", "-s", "--format=%cI", sha) : undefined;
38
+ }
39
+ /** All worktree paths of this repo (including the main checkout). */
40
+ export function worktreePaths(cwd) {
41
+ const out = tryGit(cwd, "worktree", "list", "--porcelain");
42
+ if (!out)
43
+ return [];
44
+ return out
45
+ .split("\n")
46
+ .filter((l) => l.startsWith("worktree "))
47
+ .map((l) => l.slice("worktree ".length));
48
+ }
49
+ /** True if the path is excluded by some .gitignore (git check-ignore exits 0). */
50
+ export function pathIgnored(cwd, relPath) {
51
+ try {
52
+ execFileSync("git", ["check-ignore", "-q", "--", relPath], { cwd, stdio: "ignore" });
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Stage only the given files and commit. Never throws on the ignored case
61
+ * (the audit's whole premise is being committed, but the file is already on
62
+ * disk either way) - callers surface an actionable message instead.
63
+ */
64
+ export function commitFiles(cwd, files, message) {
65
+ if (files.some((f) => pathIgnored(cwd, f)))
66
+ return { committed: false, ignored: true };
67
+ git(cwd, "add", "--", ...files);
68
+ try {
69
+ git(cwd, "diff", "--cached", "--quiet", "--", ...files);
70
+ return { committed: false, ignored: false };
71
+ }
72
+ catch {
73
+ git(cwd, "commit", "-m", message, "--", ...files);
74
+ const sha = tryGit(cwd, "rev-parse", "--short", "HEAD");
75
+ return { committed: true, ignored: false, ...(sha ? { sha } : {}) };
76
+ }
77
+ }
78
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface LineError {
2
+ line: number;
3
+ message: string;
4
+ }
5
+ export interface JsonlResult<T> {
6
+ values: T[];
7
+ errors: LineError[];
8
+ }
9
+ /**
10
+ * Parse jsonl text line by line. Bad lines are collected as errors and
11
+ * skipped, never aborting the whole file (hand-edited ledgers must survive).
12
+ * `parseValue` returns the parsed value, or an error message string.
13
+ */
14
+ export declare function parseJsonl<T>(text: string, parseValue: (value: unknown) => T | string): JsonlResult<T>;
package/dist/jsonl.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Parse jsonl text line by line. Bad lines are collected as errors and
3
+ * skipped, never aborting the whole file (hand-edited ledgers must survive).
4
+ * `parseValue` returns the parsed value, or an error message string.
5
+ */
6
+ export function parseJsonl(text, parseValue) {
7
+ const values = [];
8
+ const errors = [];
9
+ text.split("\n").forEach((raw, index) => {
10
+ if (raw.trim() === "")
11
+ return;
12
+ let json;
13
+ try {
14
+ json = JSON.parse(raw);
15
+ }
16
+ catch {
17
+ errors.push({ line: index + 1, message: "invalid JSON" });
18
+ return;
19
+ }
20
+ const result = parseValue(json);
21
+ if (typeof result === "string") {
22
+ errors.push({ line: index + 1, message: result });
23
+ }
24
+ else {
25
+ values.push(result);
26
+ }
27
+ });
28
+ return { values, errors };
29
+ }
30
+ //# sourceMappingURL=jsonl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../src/jsonl.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,UAA0C;IAE1C,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;QACtC,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO;QAC9B,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;YAC1D,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { LineError } from "../jsonl.js";
2
+ import { type DecisionEntry, type LedgerLine, type RatifyEntry } from "./schema.js";
3
+ export interface Ledger {
4
+ entries: DecisionEntry[];
5
+ ratifies: RatifyEntry[];
6
+ errors: LineError[];
7
+ }
8
+ export declare function ledgerPath(repoRoot: string): string;
9
+ export declare function readLedger(file: string): Ledger;
10
+ /** Single atomic append; also drops a .gitignore for the cache dir on first write. */
11
+ export declare function appendLedgerLines(file: string, lines: LedgerLine[]): void;
12
+ /** Latest ratify wins per target. */
13
+ export declare function verdictOf(ratifies: RatifyEntry[], id: string): "accept" | "reject" | undefined;
14
+ /** Whether the latest ratify pinned this decision into agent memory. */
15
+ export declare function isPinned(ratifies: RatifyEntry[], id: string): boolean;
16
+ /** The latest ratify's note for this decision (user's supplement to the rule). */
17
+ export declare function noteOf(ratifies: RatifyEntry[], id: string): string | undefined;
18
+ export interface Followup {
19
+ entry: DecisionEntry;
20
+ correction?: string;
21
+ }
22
+ /**
23
+ * The v2 seam. Overturning records "this was the wrong call" but never touches
24
+ * code (honest v1 boundary). This returns the decisions ruled `reject` together
25
+ * with the correction note, i.e. the work still owed. v1 surfaces it (apply
26
+ * reminder, report section); a v2 task layer consumes it to spawn follow-up
27
+ * tickets. Shape is stable so the task adapter binds to this, not to internals.
28
+ */
29
+ export declare function followups(entries: DecisionEntry[], ratifies: RatifyEntry[]): Followup[];
30
+ export declare function nextDecisionNumber(entries: DecisionEntry[]): number;
@@ -0,0 +1,85 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { isRatify, parseLedger } from "./schema.js";
4
+ export function ledgerPath(repoRoot) {
5
+ return join(repoRoot, ".wdd", "ledger.jsonl");
6
+ }
7
+ export function readLedger(file) {
8
+ if (!existsSync(file))
9
+ return { entries: [], ratifies: [], errors: [] };
10
+ const { values, errors } = parseLedger(readFileSync(file, "utf8"));
11
+ return {
12
+ entries: values.filter((l) => !isRatify(l)),
13
+ ratifies: values.filter(isRatify),
14
+ errors,
15
+ };
16
+ }
17
+ /** Single atomic append; also drops a .gitignore for the cache dir on first write. */
18
+ export function appendLedgerLines(file, lines) {
19
+ if (lines.length === 0)
20
+ return;
21
+ const dir = dirname(file);
22
+ if (!existsSync(dir)) {
23
+ mkdirSync(dir, { recursive: true });
24
+ writeFileSync(join(dir, ".gitignore"), ".cache/\n");
25
+ }
26
+ appendFileSync(file, lines.map((l) => JSON.stringify(l)).join("\n") + "\n");
27
+ }
28
+ /** Latest ratify wins per target. */
29
+ export function verdictOf(ratifies, id) {
30
+ for (let i = ratifies.length - 1; i >= 0; i--) {
31
+ if (ratifies[i]?.target === id)
32
+ return ratifies[i]?.verdict;
33
+ }
34
+ return undefined;
35
+ }
36
+ /** Whether the latest ratify pinned this decision into agent memory. */
37
+ export function isPinned(ratifies, id) {
38
+ for (let i = ratifies.length - 1; i >= 0; i--) {
39
+ if (ratifies[i]?.target === id)
40
+ return ratifies[i]?.pin === true;
41
+ }
42
+ return false;
43
+ }
44
+ /** The latest ratify's note for this decision (user's supplement to the rule). */
45
+ export function noteOf(ratifies, id) {
46
+ for (let i = ratifies.length - 1; i >= 0; i--) {
47
+ if (ratifies[i]?.target === id)
48
+ return ratifies[i]?.note;
49
+ }
50
+ return undefined;
51
+ }
52
+ /**
53
+ * The v2 seam. Overturning records "this was the wrong call" but never touches
54
+ * code (honest v1 boundary). This returns the decisions ruled `reject` together
55
+ * with the correction note, i.e. the work still owed. v1 surfaces it (apply
56
+ * reminder, report section); a v2 task layer consumes it to spawn follow-up
57
+ * tickets. Shape is stable so the task adapter binds to this, not to internals.
58
+ */
59
+ export function followups(entries, ratifies) {
60
+ const out = [];
61
+ for (const entry of entries) {
62
+ if (verdictOf(ratifies, entry.id) !== "reject")
63
+ continue;
64
+ const note = lastRejectNote(ratifies, entry.id);
65
+ out.push({ entry, ...(note ? { correction: note } : {}) });
66
+ }
67
+ return out;
68
+ }
69
+ function lastRejectNote(ratifies, id) {
70
+ for (let i = ratifies.length - 1; i >= 0; i--) {
71
+ if (ratifies[i]?.target === id && ratifies[i]?.verdict === "reject")
72
+ return ratifies[i]?.note;
73
+ }
74
+ return undefined;
75
+ }
76
+ export function nextDecisionNumber(entries) {
77
+ let max = 0;
78
+ for (const e of entries) {
79
+ const m = /^D(\d+)$/.exec(e.id);
80
+ if (m)
81
+ max = Math.max(max, Number(m[1]));
82
+ }
83
+ return max + 1;
84
+ }
85
+ //# sourceMappingURL=io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.js","sourceRoot":"","sources":["../../src/ledger/io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type JsonlResult } from "../jsonl.js";
2
+ export interface DecisionEntry {
3
+ id: string;
4
+ what: string;
5
+ by: "agent" | "user";
6
+ aware: boolean;
7
+ ref: string;
8
+ t?: string;
9
+ cwd?: string;
10
+ model?: string;
11
+ latencyMs?: number;
12
+ why?: string;
13
+ note?: string;
14
+ alternatives?: string[];
15
+ reversibility?: "low" | "medium" | "high";
16
+ branch?: string;
17
+ precedent?: string;
18
+ files?: string[];
19
+ }
20
+ export interface RatifyEntry {
21
+ type: "ratify";
22
+ target: string;
23
+ verdict: "accept" | "reject";
24
+ t: string;
25
+ note?: string;
26
+ pin?: boolean;
27
+ }
28
+ export type LedgerLine = DecisionEntry | RatifyEntry;
29
+ export declare function isRatify(line: LedgerLine): line is RatifyEntry;
30
+ export declare function parseLedgerLine(value: unknown): LedgerLine | string;
31
+ export declare function parseLedger(text: string): JsonlResult<LedgerLine>;
@@ -0,0 +1,57 @@
1
+ import { parseJsonl } from "../jsonl.js";
2
+ import { isIsoTimestamp, isNonEmptyString, isOptionalString, isRecord } from "../validate.js";
3
+ const REF_PATTERN = /^tx:[^\s:]+:\d+$/;
4
+ const REVERSIBILITIES = ["low", "medium", "high"];
5
+ export function isRatify(line) {
6
+ return "type" in line && line.type === "ratify";
7
+ }
8
+ export function parseLedgerLine(value) {
9
+ if (!isRecord(value))
10
+ return "not an object";
11
+ return value.type === "ratify" ? parseRatify(value) : parseDecision(value);
12
+ }
13
+ export function parseLedger(text) {
14
+ return parseJsonl(text, parseLedgerLine);
15
+ }
16
+ function parseDecision(v) {
17
+ if (!isNonEmptyString(v.id))
18
+ return "id: non-empty string required";
19
+ if (!isNonEmptyString(v.what) || v.what.includes("\n"))
20
+ return "what: non-empty single line required";
21
+ if (v.by !== "agent" && v.by !== "user")
22
+ return 'by: "agent" or "user" required';
23
+ if (typeof v.aware !== "boolean")
24
+ return "aware: boolean required";
25
+ if (typeof v.ref !== "string" || !REF_PATTERN.test(v.ref))
26
+ return "ref: tx:<session>:<line> required";
27
+ for (const key of ["why", "note", "branch", "precedent", "t", "cwd", "model"]) {
28
+ if (!isOptionalString(v[key]))
29
+ return `${key}: string expected`;
30
+ }
31
+ for (const key of ["alternatives", "files"]) {
32
+ if (v[key] !== undefined && !(Array.isArray(v[key]) && v[key].every((a) => typeof a === "string"))) {
33
+ return `${key}: string array expected`;
34
+ }
35
+ }
36
+ if (v.reversibility !== undefined && !REVERSIBILITIES.includes(v.reversibility)) {
37
+ return 'reversibility: "low" | "medium" | "high" expected';
38
+ }
39
+ if (v.latencyMs !== undefined && typeof v.latencyMs !== "number")
40
+ return "latencyMs: number expected";
41
+ // Extra fields pass through untouched: hand edits and future fields must survive.
42
+ return v;
43
+ }
44
+ function parseRatify(v) {
45
+ if (!isNonEmptyString(v.target))
46
+ return "target: non-empty string required";
47
+ if (v.verdict !== "accept" && v.verdict !== "reject")
48
+ return 'verdict: "accept" or "reject" required';
49
+ if (!isIsoTimestamp(v.t))
50
+ return "t: ISO timestamp required";
51
+ if (!isOptionalString(v.note))
52
+ return "note: string expected";
53
+ if (v.pin !== undefined && typeof v.pin !== "boolean")
54
+ return "pin: boolean expected";
55
+ return v;
56
+ }
57
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/ledger/schema.ts"],"names":[],"mappings":"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"}