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.
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/checks/gate.d.ts +10 -0
- package/dist/checks/gate.js +19 -0
- package/dist/checks/gate.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +79 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/apply.d.ts +1 -0
- package/dist/commands/apply.js +68 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/board.d.ts +6 -0
- package/dist/commands/board.js +57 -0
- package/dist/commands/board.js.map +1 -0
- package/dist/commands/distill.d.ts +18 -0
- package/dist/commands/distill.js +116 -0
- package/dist/commands/distill.js.map +1 -0
- package/dist/commands/gate.d.ts +2 -0
- package/dist/commands/gate.js +27 -0
- package/dist/commands/gate.js.map +1 -0
- package/dist/commands/hook.d.ts +1 -0
- package/dist/commands/hook.js +189 -0
- package/dist/commands/hook.js.map +1 -0
- package/dist/commands/recall.d.ts +19 -0
- package/dist/commands/recall.js +58 -0
- package/dist/commands/recall.js.map +1 -0
- package/dist/commands/report.d.ts +1 -0
- package/dist/commands/report.js +18 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/review.d.ts +1 -0
- package/dist/commands/review.js +211 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/scope.d.ts +17 -0
- package/dist/commands/scope.js +51 -0
- package/dist/commands/scope.js.map +1 -0
- package/dist/commands/share.d.ts +6 -0
- package/dist/commands/share.js +73 -0
- package/dist/commands/share.js.map +1 -0
- package/dist/commands/sync.d.ts +23 -0
- package/dist/commands/sync.js +214 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +80 -0
- package/dist/config.js.map +1 -0
- package/dist/distill/distiller.d.ts +15 -0
- package/dist/distill/distiller.js +92 -0
- package/dist/distill/distiller.js.map +1 -0
- package/dist/distill/prompt.d.ts +18 -0
- package/dist/distill/prompt.js +53 -0
- package/dist/distill/prompt.js.map +1 -0
- package/dist/distill/watermark.d.ts +5 -0
- package/dist/distill/watermark.js +22 -0
- package/dist/distill/watermark.js.map +1 -0
- package/dist/extract/candidates.d.ts +25 -0
- package/dist/extract/candidates.js +136 -0
- package/dist/extract/candidates.js.map +1 -0
- package/dist/git.d.ts +22 -0
- package/dist/git.js +78 -0
- package/dist/git.js.map +1 -0
- package/dist/jsonl.d.ts +14 -0
- package/dist/jsonl.js +30 -0
- package/dist/jsonl.js.map +1 -0
- package/dist/ledger/io.d.ts +30 -0
- package/dist/ledger/io.js +85 -0
- package/dist/ledger/io.js.map +1 -0
- package/dist/ledger/schema.d.ts +31 -0
- package/dist/ledger/schema.js +57 -0
- package/dist/ledger/schema.js.map +1 -0
- package/dist/patch/apply.d.ts +15 -0
- package/dist/patch/apply.js +63 -0
- package/dist/patch/apply.js.map +1 -0
- package/dist/patch/schema.d.ts +11 -0
- package/dist/patch/schema.js +21 -0
- package/dist/patch/schema.js.map +1 -0
- package/dist/render/html.d.ts +33 -0
- package/dist/render/html.js +593 -0
- package/dist/render/html.js.map +1 -0
- package/dist/render/md.d.ts +3 -0
- package/dist/render/md.js +39 -0
- package/dist/render/md.js.map +1 -0
- package/dist/render/term.d.ts +2 -0
- package/dist/render/term.js +40 -0
- package/dist/render/term.js.map +1 -0
- package/dist/trace/schema.d.ts +14 -0
- package/dist/trace/schema.js +25 -0
- package/dist/trace/schema.js.map +1 -0
- package/dist/transcript/locate.d.ts +10 -0
- package/dist/transcript/locate.js +30 -0
- package/dist/transcript/locate.js.map +1 -0
- package/dist/transcript/parser.d.ts +18 -0
- package/dist/transcript/parser.js +130 -0
- package/dist/transcript/parser.js.map +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.js +13 -0
- package/dist/validate.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse, stringify } from "yaml";
|
|
4
|
+
import { loadConfig } from "../config.js";
|
|
5
|
+
import { pathIgnored, repoRoot, tryGit } from "../git.js";
|
|
6
|
+
import { isRecord } from "../validate.js";
|
|
7
|
+
/**
|
|
8
|
+
* Explicit choice: does the audit travel with the project or stay local?
|
|
9
|
+
* This is the user's call, so it is a deliberate command, not inferred from
|
|
10
|
+
* whether `.wdd` happens to be gitignored.
|
|
11
|
+
*/
|
|
12
|
+
export function share(args) {
|
|
13
|
+
const root = repoRoot(process.cwd());
|
|
14
|
+
if (!root) {
|
|
15
|
+
process.stderr.write("wdd share: not inside a git repo\n");
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
const mode = args.find((a) => a === "repo" || a === "local");
|
|
19
|
+
if (!mode) {
|
|
20
|
+
const current = loadConfig(root).share;
|
|
21
|
+
process.stdout.write(`audit sharing: ${current}\n` +
|
|
22
|
+
(current === "repo"
|
|
23
|
+
? " the ledger is committed and travels with your code (merges with PRs)\n"
|
|
24
|
+
: " the ledger stays on this machine, written to disk but never committed\n") +
|
|
25
|
+
"change with: wdd share repo | wdd share local\n");
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
writeShare(root, mode);
|
|
29
|
+
if (mode === "local") {
|
|
30
|
+
excludeLocally(root);
|
|
31
|
+
process.stdout.write("audit sharing → local\n" +
|
|
32
|
+
" .wdd/ writes to disk but is never committed; added to .git/info/exclude (local, not shared)\n" +
|
|
33
|
+
" your ledger stays readable at .wdd/ledger.jsonl\n");
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
process.stdout.write("audit sharing → repo\n the ledger will be committed and travel with your code\n");
|
|
37
|
+
if (pathIgnored(root, ".wdd/ledger.jsonl")) {
|
|
38
|
+
process.stderr.write("⚠ but .wdd is currently gitignored, so commits will be blocked.\n" +
|
|
39
|
+
" remove the `.wdd` line from your .gitignore (or .git/info/exclude) to let it be versioned.\n");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
function writeShare(root, mode) {
|
|
45
|
+
const dir = join(root, ".wdd");
|
|
46
|
+
const file = join(dir, "config.yaml");
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
let doc = {};
|
|
49
|
+
if (existsSync(file)) {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = parse(readFileSync(file, "utf8"));
|
|
52
|
+
if (isRecord(parsed))
|
|
53
|
+
doc = parsed;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// keep going with a fresh doc rather than clobber silently on parse error
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
doc.share = mode;
|
|
60
|
+
writeFileSync(file, stringify(doc));
|
|
61
|
+
}
|
|
62
|
+
/** git-native local ignore: keeps .wdd out of git without touching the shared .gitignore. */
|
|
63
|
+
function excludeLocally(root) {
|
|
64
|
+
const gitDir = tryGit(root, "rev-parse", "--git-dir");
|
|
65
|
+
if (!gitDir)
|
|
66
|
+
return;
|
|
67
|
+
const exclude = join(gitDir.startsWith("/") ? gitDir : join(root, gitDir), "info", "exclude");
|
|
68
|
+
const existing = existsSync(exclude) ? readFileSync(exclude, "utf8") : "";
|
|
69
|
+
if (/^\.wdd\/?$/m.test(existing))
|
|
70
|
+
return;
|
|
71
|
+
appendFileSync(exclude, (existing.endsWith("\n") || existing === "" ? "" : "\n") + ".wdd/\n");
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=share.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.js","sourceRoot":"","sources":["../../src/commands/share.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Regenerate per the recorded choice. Returns changed repo-relative paths (empty if not opted in). */
|
|
2
|
+
export declare function refreshSync(root: string): string[];
|
|
3
|
+
/**
|
|
4
|
+
* Decide once where precedents sync, and record it. Called at `hook install`
|
|
5
|
+
* so every later ruling auto-syncs with no separate step. Interactive: confirm
|
|
6
|
+
* the detected target; non-interactive: take the smart default silently.
|
|
7
|
+
* Returns the changed files (target written), or [] if the user declined.
|
|
8
|
+
*/
|
|
9
|
+
export declare function configureSync(root: string, interactive: boolean): Promise<string[]>;
|
|
10
|
+
export declare function sync(args: string[]): Promise<number>;
|
|
11
|
+
/**
|
|
12
|
+
* Detect the repo's agent-memory setup and recommend where precedents should
|
|
13
|
+
* go, so one write covers the agents actually in use:
|
|
14
|
+
* - CLAUDE.md imports AGENTS.md -> AGENTS.md alone (covers CC via import + Codex)
|
|
15
|
+
* - only CLAUDE.md -> CLAUDE.md (CC)
|
|
16
|
+
* - only AGENTS.md -> AGENTS.md (Codex, and CC reads it too)
|
|
17
|
+
* - both, not linked -> both (CC and Codex read different files)
|
|
18
|
+
* - neither -> AGENTS.md (cross-agent default)
|
|
19
|
+
*/
|
|
20
|
+
export declare function recommend(root: string): {
|
|
21
|
+
targets: string[];
|
|
22
|
+
reason: string;
|
|
23
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { loadConfig, patchConfig } from "../config.js";
|
|
5
|
+
import { commitFiles, repoRoot, tryGit } from "../git.js";
|
|
6
|
+
import { isPinned, ledgerPath, noteOf, readLedger, verdictOf } from "../ledger/io.js";
|
|
7
|
+
const START = "<!-- whodecided:precedents (auto-generated by `wdd sync`, do not edit) -->";
|
|
8
|
+
const END = "<!-- /whodecided:precedents -->";
|
|
9
|
+
const DOC = join(".wdd", "PRECEDENTS.md");
|
|
10
|
+
const HEADING = "## Decision precedents (whodecided)";
|
|
11
|
+
const INTRO = [
|
|
12
|
+
"Precedents you pinned while reviewing. Follow **DO**, avoid **DON'T**.",
|
|
13
|
+
"Run `wdd recall <term>` for the full decision record.",
|
|
14
|
+
];
|
|
15
|
+
/** Only decisions the user pinned - not every ruling. */
|
|
16
|
+
function precedentLines(entries, ratifies) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
for (const e of entries) {
|
|
19
|
+
if (!isPinned(ratifies, e.id))
|
|
20
|
+
continue;
|
|
21
|
+
const v = verdictOf(ratifies, e.id);
|
|
22
|
+
const where = e.files?.[0] ? ` — ${e.files[0].split("/").pop()}` : "";
|
|
23
|
+
const note = noteOf(ratifies, e.id); // the user's supplement/refinement to the rule
|
|
24
|
+
const supp = note ? ` — ${note}` : "";
|
|
25
|
+
if (v === "accept")
|
|
26
|
+
lines.push(`- DO: ${e.what}${where}${supp} [${e.id}]`);
|
|
27
|
+
else if (v === "reject") {
|
|
28
|
+
const alt = e.alternatives?.[0] ? ` (instead: ${e.alternatives[0]})` : "";
|
|
29
|
+
lines.push(`- DON'T: ${e.what}${alt}${where}${supp} [${e.id}]`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
34
|
+
function inlineBlock(lines) {
|
|
35
|
+
const body = lines.length ? lines.join("\n") : "- (nothing pinned yet - pin the core decisions while reviewing)";
|
|
36
|
+
return [START, HEADING, "", ...INTRO, "", body, END].join("\n");
|
|
37
|
+
}
|
|
38
|
+
/** link mode: file holds only a reference; the list lives in .wdd/PRECEDENTS.md. */
|
|
39
|
+
function linkBlock(targetFile) {
|
|
40
|
+
// CLAUDE.md supports `@path` imports (auto-loaded); other files get a plain
|
|
41
|
+
// link (Codex/AGENTS.md won't auto-load it, so inline is safer there).
|
|
42
|
+
const ref = basename(targetFile) === "CLAUDE.md" ? "@" + DOC : `[decision precedents](${DOC})`;
|
|
43
|
+
return [START, HEADING, "", INTRO[0], "", "See " + ref + " for the ratified DO / DON'T list.", END].join("\n");
|
|
44
|
+
}
|
|
45
|
+
function docFile(lines) {
|
|
46
|
+
const body = lines.length ? lines.join("\n") : "- (nothing pinned yet)";
|
|
47
|
+
return ["# Decision precedents (whodecided)", "", ...INTRO, "", body, ""].join("\n");
|
|
48
|
+
}
|
|
49
|
+
/** Replace the managed block in place; append if absent; keep all other content. */
|
|
50
|
+
function writeBlock(file, block) {
|
|
51
|
+
let text = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
52
|
+
const s = text.indexOf(START);
|
|
53
|
+
const e = text.indexOf(END);
|
|
54
|
+
if (s >= 0 && e > s)
|
|
55
|
+
text = text.slice(0, s) + block + text.slice(e + END.length);
|
|
56
|
+
else
|
|
57
|
+
text = (text.trim() ? text.replace(/\n*$/, "\n\n") : "") + block + "\n";
|
|
58
|
+
writeFileSync(file, text);
|
|
59
|
+
}
|
|
60
|
+
function generate(root, choice) {
|
|
61
|
+
const { entries, ratifies } = readLedger(ledgerPath(root));
|
|
62
|
+
const lines = precedentLines(entries, ratifies);
|
|
63
|
+
const changed = [];
|
|
64
|
+
if (choice.targets.length === 0)
|
|
65
|
+
return changed; // "off" — audit only
|
|
66
|
+
if (choice.mode === "link") {
|
|
67
|
+
writeFileSync(join(root, DOC), docFile(lines));
|
|
68
|
+
changed.push(DOC);
|
|
69
|
+
}
|
|
70
|
+
for (const rel of choice.targets) {
|
|
71
|
+
writeBlock(join(root, rel), choice.mode === "link" ? linkBlock(rel) : inlineBlock(lines));
|
|
72
|
+
// A *.local.md destination is personal: keep it out of git, don't touch shared .gitignore.
|
|
73
|
+
if (/\.local\.md$/.test(rel))
|
|
74
|
+
excludeLocally(root, rel);
|
|
75
|
+
changed.push(rel);
|
|
76
|
+
}
|
|
77
|
+
return changed;
|
|
78
|
+
}
|
|
79
|
+
/** git-native local ignore, so the personal memory file never gets committed. */
|
|
80
|
+
function excludeLocally(root, pattern) {
|
|
81
|
+
const gitDir = tryGit(root, "rev-parse", "--git-dir");
|
|
82
|
+
if (!gitDir)
|
|
83
|
+
return;
|
|
84
|
+
const exclude = join(gitDir.startsWith("/") ? gitDir : join(root, gitDir), "info", "exclude");
|
|
85
|
+
const existing = existsSync(exclude) ? readFileSync(exclude, "utf8") : "";
|
|
86
|
+
if (new RegExp("^" + pattern.replace(/[.]/g, "\\$&") + "$", "m").test(existing))
|
|
87
|
+
return;
|
|
88
|
+
appendFileSync(exclude, (existing.endsWith("\n") || existing === "" ? "" : "\n") + pattern + "\n");
|
|
89
|
+
}
|
|
90
|
+
/** Regenerate per the recorded choice. Returns changed repo-relative paths (empty if not opted in). */
|
|
91
|
+
export function refreshSync(root) {
|
|
92
|
+
const choice = loadConfig(root).sync;
|
|
93
|
+
return choice ? generate(root, choice) : [];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Decide once where precedents sync, and record it. Called at `hook install`
|
|
97
|
+
* so every later ruling auto-syncs with no separate step. Interactive: confirm
|
|
98
|
+
* the detected target; non-interactive: take the smart default silently.
|
|
99
|
+
* Returns the changed files (target written), or [] if the user declined.
|
|
100
|
+
*/
|
|
101
|
+
export async function configureSync(root, interactive) {
|
|
102
|
+
let choice = loadConfig(root).sync;
|
|
103
|
+
if (!choice) {
|
|
104
|
+
choice = interactive ? await ask(root) : { targets: recommend(root).targets, mode: "inline" };
|
|
105
|
+
if (!choice)
|
|
106
|
+
return [];
|
|
107
|
+
// Record even the "off" choice (empty targets) so we never re-prompt.
|
|
108
|
+
patchConfig(root, { sync: choice });
|
|
109
|
+
}
|
|
110
|
+
return generate(root, choice);
|
|
111
|
+
}
|
|
112
|
+
export async function sync(args) {
|
|
113
|
+
const root = repoRoot(process.cwd());
|
|
114
|
+
if (!root) {
|
|
115
|
+
process.stderr.write("wdd sync: not inside a git repo\n");
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
const recorded = loadConfig(root).sync;
|
|
119
|
+
const flagTargets = flag(args, "--target");
|
|
120
|
+
const flagMode = flag(args, "--mode");
|
|
121
|
+
let choice = recorded;
|
|
122
|
+
if (flagTargets || flagMode) {
|
|
123
|
+
choice = {
|
|
124
|
+
targets: flagTargets ? flagTargets.split(",").map((s) => s.trim()).filter(Boolean) : (recorded?.targets ?? recommend(root).targets),
|
|
125
|
+
mode: flagMode === "link" ? "link" : flagMode === "inline" ? "inline" : (recorded?.mode ?? "inline"),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
else if (!recorded) {
|
|
129
|
+
if (!process.stdin.isTTY) {
|
|
130
|
+
process.stderr.write("wdd sync: not configured yet. Run interactively once, or pass --target <file[,file]> --mode <inline|link>.\n");
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
choice = await ask(root);
|
|
134
|
+
if (!choice)
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
if (!choice)
|
|
138
|
+
return 1;
|
|
139
|
+
patchConfig(root, { sync: choice });
|
|
140
|
+
if (choice.targets.length === 0) {
|
|
141
|
+
process.stdout.write("precedents won't sync to any file (audit only). Change with wdd sync.\n");
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
const changed = generate(root, choice);
|
|
145
|
+
const { entries, ratifies } = readLedger(ledgerPath(root));
|
|
146
|
+
const ruled = entries.filter((e) => verdictOf(ratifies, e.id) !== undefined).length;
|
|
147
|
+
const where = `${choice.targets.join(", ")} (${choice.mode})`;
|
|
148
|
+
const cfg = loadConfig(root);
|
|
149
|
+
if (cfg.share === "repo" && cfg.commit === "on") {
|
|
150
|
+
const sha = commitFiles(root, [...changed, join(".wdd", "config.yaml")], `wdd sync: ${ruled} precedent(s) → ${choice.targets.join(", ")}`);
|
|
151
|
+
process.stdout.write(`${where}: ${ruled} precedent(s)${sha.sha ? ` → commit ${sha.sha}` : ""}\n`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
process.stdout.write(`${where}: ${ruled} precedent(s) written (not committed — commit when you're ready)\n`);
|
|
155
|
+
}
|
|
156
|
+
if (choice.mode === "link" && choice.targets.some((t) => basename(t) !== "CLAUDE.md")) {
|
|
157
|
+
process.stderr.write("note: link mode only auto-loads in CC (CLAUDE.md @import); Codex/AGENTS.md need inline\n");
|
|
158
|
+
}
|
|
159
|
+
process.stdout.write("auto-refreshes on every ruling from now on\n");
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Detect the repo's agent-memory setup and recommend where precedents should
|
|
164
|
+
* go, so one write covers the agents actually in use:
|
|
165
|
+
* - CLAUDE.md imports AGENTS.md -> AGENTS.md alone (covers CC via import + Codex)
|
|
166
|
+
* - only CLAUDE.md -> CLAUDE.md (CC)
|
|
167
|
+
* - only AGENTS.md -> AGENTS.md (Codex, and CC reads it too)
|
|
168
|
+
* - both, not linked -> both (CC and Codex read different files)
|
|
169
|
+
* - neither -> AGENTS.md (cross-agent default)
|
|
170
|
+
*/
|
|
171
|
+
export function recommend(root) {
|
|
172
|
+
const read = (n) => (existsSync(join(root, n)) ? readFileSync(join(root, n), "utf8") : null);
|
|
173
|
+
const claude = read("CLAUDE.md");
|
|
174
|
+
const agents = read("AGENTS.md");
|
|
175
|
+
const claudeImportsAgents = claude != null && /@\.?\/?AGENTS\.md/.test(claude);
|
|
176
|
+
if (agents != null && (claudeImportsAgents || claude == null)) {
|
|
177
|
+
return { targets: ["AGENTS.md"], reason: claudeImportsAgents ? "your CLAUDE.md imports AGENTS.md, so this covers CC and Codex" : "covers Codex (and CC reads AGENTS.md too)" };
|
|
178
|
+
}
|
|
179
|
+
if (claude != null && agents == null)
|
|
180
|
+
return { targets: ["CLAUDE.md"], reason: "CLAUDE.md is your only agent file" };
|
|
181
|
+
if (claude != null && agents != null)
|
|
182
|
+
return { targets: ["CLAUDE.md", "AGENTS.md"], reason: "CLAUDE.md does not import AGENTS.md, so both are needed for CC + Codex" };
|
|
183
|
+
return { targets: ["AGENTS.md"], reason: "no agent file yet; AGENTS.md is read by both CC and Codex" };
|
|
184
|
+
}
|
|
185
|
+
async function ask(root) {
|
|
186
|
+
const rec = recommend(root);
|
|
187
|
+
const options = [
|
|
188
|
+
{ targets: rec.targets, label: `${rec.targets.join(" + ")} (recommended, shared — ${rec.reason})` },
|
|
189
|
+
{ targets: ["AGENTS.md"], label: "AGENTS.md only (shared, committed)" },
|
|
190
|
+
{ targets: ["CLAUDE.md"], label: "CLAUDE.md only (shared, committed)" },
|
|
191
|
+
{ targets: ["AGENTS.md", "CLAUDE.md"], label: "AGENTS.md + CLAUDE.md (shared)" },
|
|
192
|
+
{ targets: ["CLAUDE.local.md"], label: "CLAUDE.local.md (local, just me — not committed)" },
|
|
193
|
+
{ targets: [], label: "don't remember to any file — audit only" },
|
|
194
|
+
];
|
|
195
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
196
|
+
try {
|
|
197
|
+
process.stdout.write("Where should pinned precedents be written? (agents read these files)\n");
|
|
198
|
+
options.forEach((o, i) => process.stdout.write(` ${i + 1}) ${o.label}\n`));
|
|
199
|
+
const raw = (await rl.question("choose [1]: ")).trim();
|
|
200
|
+
const idx = raw === "" ? 0 : Number(raw) - 1;
|
|
201
|
+
const pick = options[idx];
|
|
202
|
+
if (!pick)
|
|
203
|
+
return options[0] ? { targets: options[0].targets, mode: "inline" } : undefined;
|
|
204
|
+
return { targets: pick.targets, mode: "inline" };
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
rl.close();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function flag(args, name) {
|
|
211
|
+
const i = args.indexOf(name);
|
|
212
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SyncChoice {
|
|
2
|
+
targets: string[];
|
|
3
|
+
mode: "inline" | "link";
|
|
4
|
+
}
|
|
5
|
+
export interface Config {
|
|
6
|
+
share: "repo" | "local";
|
|
7
|
+
commit: "off" | "on";
|
|
8
|
+
distill: {
|
|
9
|
+
cmd: string[];
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
budget: number;
|
|
12
|
+
lang: "auto" | "zh" | "en";
|
|
13
|
+
auto: "off" | "turn";
|
|
14
|
+
};
|
|
15
|
+
heuristics: {
|
|
16
|
+
reflexMs: number;
|
|
17
|
+
};
|
|
18
|
+
review: {
|
|
19
|
+
days: number;
|
|
20
|
+
sessions: number;
|
|
21
|
+
};
|
|
22
|
+
sync?: SyncChoice;
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_CONFIG: Config;
|
|
25
|
+
/** Merge keys into .wdd/config.yaml, preserving everything else. */
|
|
26
|
+
export declare function patchConfig(root: string, patch: Record<string, unknown>): void;
|
|
27
|
+
/** .wdd/config.yaml is optional; every field has a default (zero-config goal). */
|
|
28
|
+
export declare function loadConfig(root: string | undefined): Config;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { parse, stringify } from "yaml";
|
|
4
|
+
import { isRecord } from "./validate.js";
|
|
5
|
+
export const DEFAULT_CONFIG = {
|
|
6
|
+
share: "repo",
|
|
7
|
+
commit: "off",
|
|
8
|
+
distill: { cmd: ["claude", "-p"], timeoutMs: 120_000, budget: 10, lang: "auto", auto: "off" },
|
|
9
|
+
heuristics: { reflexMs: 3000 },
|
|
10
|
+
review: { days: 7, sessions: 10 },
|
|
11
|
+
};
|
|
12
|
+
/** Merge keys into .wdd/config.yaml, preserving everything else. */
|
|
13
|
+
export function patchConfig(root, patch) {
|
|
14
|
+
const file = join(root, ".wdd", "config.yaml");
|
|
15
|
+
let doc = {};
|
|
16
|
+
if (existsSync(file)) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = parse(readFileSync(file, "utf8"));
|
|
19
|
+
if (isRecord(parsed))
|
|
20
|
+
doc = parsed;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// start fresh rather than clobber silently on parse error
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
Object.assign(doc, patch);
|
|
27
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
28
|
+
writeFileSync(file, stringify(doc));
|
|
29
|
+
}
|
|
30
|
+
/** .wdd/config.yaml is optional; every field has a default (zero-config goal). */
|
|
31
|
+
export function loadConfig(root) {
|
|
32
|
+
const file = root ? join(root, ".wdd", "config.yaml") : undefined;
|
|
33
|
+
if (!file || !existsSync(file))
|
|
34
|
+
return DEFAULT_CONFIG;
|
|
35
|
+
let raw;
|
|
36
|
+
try {
|
|
37
|
+
raw = parse(readFileSync(file, "utf8"));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return DEFAULT_CONFIG;
|
|
41
|
+
}
|
|
42
|
+
if (!isRecord(raw))
|
|
43
|
+
return DEFAULT_CONFIG;
|
|
44
|
+
const share = raw.share === "local" ? "local" : "repo";
|
|
45
|
+
const commit = raw.commit === "on" ? "on" : "off";
|
|
46
|
+
const d = isRecord(raw.distill) ? raw.distill : {};
|
|
47
|
+
const h = isRecord(raw.heuristics) ? raw.heuristics : {};
|
|
48
|
+
const r = isRecord(raw.review) ? raw.review : {};
|
|
49
|
+
return {
|
|
50
|
+
share,
|
|
51
|
+
commit,
|
|
52
|
+
distill: {
|
|
53
|
+
cmd: typeof d.cmd === "string" ? d.cmd.split(/\s+/) : DEFAULT_CONFIG.distill.cmd,
|
|
54
|
+
timeoutMs: num(d.timeout, DEFAULT_CONFIG.distill.timeoutMs / 1000) * 1000,
|
|
55
|
+
budget: num(d.budget, DEFAULT_CONFIG.distill.budget),
|
|
56
|
+
lang: d.lang === "zh" || d.lang === "en" ? d.lang : "auto",
|
|
57
|
+
auto: d.auto === "turn" ? "turn" : "off",
|
|
58
|
+
},
|
|
59
|
+
heuristics: {
|
|
60
|
+
reflexMs: num(h.reflexMs, DEFAULT_CONFIG.heuristics.reflexMs),
|
|
61
|
+
},
|
|
62
|
+
review: {
|
|
63
|
+
days: num(r.days, DEFAULT_CONFIG.review.days),
|
|
64
|
+
sessions: num(r.sessions, DEFAULT_CONFIG.review.sessions),
|
|
65
|
+
},
|
|
66
|
+
...parseSync(raw.sync),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function parseSync(raw) {
|
|
70
|
+
if (!isRecord(raw))
|
|
71
|
+
return {}; // absent = never configured (will prompt)
|
|
72
|
+
const t = raw.targets ?? raw.target; // tolerate the old single-target key
|
|
73
|
+
const targets = Array.isArray(t) ? t.filter((x) => typeof x === "string") : typeof t === "string" ? [t] : [];
|
|
74
|
+
// Present but empty targets = the user chose "don't remember" (configured, off).
|
|
75
|
+
return { sync: { targets, mode: raw.mode === "link" ? "link" : "inline" } };
|
|
76
|
+
}
|
|
77
|
+
function num(value, fallback) {
|
|
78
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DecisionEntry } from "../ledger/schema.js";
|
|
2
|
+
import type { DecisionCandidate } from "../extract/candidates.js";
|
|
3
|
+
export interface DistillOptions {
|
|
4
|
+
cmd: string[];
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
budget: number;
|
|
7
|
+
lang: "auto" | "zh" | "en";
|
|
8
|
+
}
|
|
9
|
+
export declare const DEFAULT_DISTILL: DistillOptions;
|
|
10
|
+
export interface DistillOutcome {
|
|
11
|
+
entries: DecisionEntry[];
|
|
12
|
+
degraded?: string;
|
|
13
|
+
}
|
|
14
|
+
/** One LLM call over all candidates; validate, retry once, degrade never-fatally. */
|
|
15
|
+
export declare function distill(candidates: DecisionCandidate[], opts?: DistillOptions): Promise<DistillOutcome>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { parseLedgerLine, isRatify } from "../ledger/schema.js";
|
|
3
|
+
import { buildPrompt, resolveLang } from "./prompt.js";
|
|
4
|
+
export const DEFAULT_DISTILL = {
|
|
5
|
+
cmd: ["claude", "-p"],
|
|
6
|
+
timeoutMs: 120_000,
|
|
7
|
+
budget: 10,
|
|
8
|
+
lang: "auto",
|
|
9
|
+
};
|
|
10
|
+
/** One LLM call over all candidates; validate, retry once, degrade never-fatally. */
|
|
11
|
+
export async function distill(candidates, opts = DEFAULT_DISTILL) {
|
|
12
|
+
if (candidates.length === 0)
|
|
13
|
+
return { entries: [] };
|
|
14
|
+
const { instructions, payload } = buildPrompt(candidates, opts.budget, resolveLang(candidates, opts.lang));
|
|
15
|
+
const refs = new Set(candidates.map((c) => c.ref));
|
|
16
|
+
let lastError = "";
|
|
17
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
18
|
+
const prompt = attempt === 0
|
|
19
|
+
? instructions
|
|
20
|
+
: `${instructions}\n\nYour previous output was invalid: ${lastError}\nOutput ONLY the corrected JSON array.`;
|
|
21
|
+
let stdout;
|
|
22
|
+
try {
|
|
23
|
+
stdout = await run(opts.cmd, prompt, payload, opts.timeoutMs);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return { entries: [], degraded: error instanceof Error ? error.message : String(error) };
|
|
27
|
+
}
|
|
28
|
+
const parsed = parseEntries(stdout, refs, opts.budget);
|
|
29
|
+
if (typeof parsed === "string") {
|
|
30
|
+
lastError = parsed;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
return { entries: parsed };
|
|
34
|
+
}
|
|
35
|
+
return { entries: [], degraded: `distiller output invalid twice: ${lastError}` };
|
|
36
|
+
}
|
|
37
|
+
function parseEntries(stdout, validRefs, budget) {
|
|
38
|
+
const start = stdout.indexOf("[");
|
|
39
|
+
const end = stdout.lastIndexOf("]");
|
|
40
|
+
if (start < 0 || end <= start)
|
|
41
|
+
return "no JSON array found";
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = JSON.parse(stdout.slice(start, end + 1));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return "JSON parse failed";
|
|
48
|
+
}
|
|
49
|
+
if (!Array.isArray(raw))
|
|
50
|
+
return "not an array";
|
|
51
|
+
const entries = [];
|
|
52
|
+
for (const item of raw) {
|
|
53
|
+
const line = parseLedgerLine(item);
|
|
54
|
+
if (typeof line === "string")
|
|
55
|
+
return line;
|
|
56
|
+
if (isRatify(line))
|
|
57
|
+
return "ratify entries not allowed from distiller";
|
|
58
|
+
if (!validRefs.has(line.ref))
|
|
59
|
+
return `unknown ref: ${line.ref}`;
|
|
60
|
+
entries.push(line);
|
|
61
|
+
}
|
|
62
|
+
return entries.slice(0, budget);
|
|
63
|
+
}
|
|
64
|
+
function run(cmd, prompt, stdinPayload, timeoutMs) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const [bin = "", ...args] = cmd;
|
|
67
|
+
// Payload goes through stdin: argv has a hard size limit (E2BIG).
|
|
68
|
+
const child = spawn(bin, [...args, prompt], { stdio: ["pipe", "pipe", "pipe"] });
|
|
69
|
+
child.stdin.write(stdinPayload);
|
|
70
|
+
child.stdin.end();
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
child.kill("SIGKILL");
|
|
73
|
+
reject(new Error(`distiller timed out after ${timeoutMs / 1000}s`));
|
|
74
|
+
}, timeoutMs);
|
|
75
|
+
let out = "";
|
|
76
|
+
let err = "";
|
|
77
|
+
child.stdout.on("data", (d) => (out += d.toString()));
|
|
78
|
+
child.stderr.on("data", (d) => (err += d.toString()));
|
|
79
|
+
child.on("error", (e) => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
reject(new Error(`cannot run ${bin}: ${e.message}`));
|
|
82
|
+
});
|
|
83
|
+
child.on("close", (code) => {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
if (code === 0)
|
|
86
|
+
resolve(out);
|
|
87
|
+
else
|
|
88
|
+
reject(new Error(`${bin} exited ${code}: ${err.slice(0, 200)}`));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=distiller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distiller.js","sourceRoot":"","sources":["../../src/distill/distiller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DecisionCandidate } from "../extract/candidates.js";
|
|
2
|
+
export interface DistillPrompt {
|
|
3
|
+
instructions: string;
|
|
4
|
+
payload: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* `claude -p` distiller calls get logged as their own sessions under
|
|
8
|
+
* ~/.claude/projects. This sentinel is embedded in every distiller prompt so
|
|
9
|
+
* locate can skip those sessions - otherwise whodecided audits its own
|
|
10
|
+
* distiller output (a feedback loop that surfaces the prompt as a "decision").
|
|
11
|
+
*/
|
|
12
|
+
export declare const DISTILLER_SENTINEL = "WHODECIDED_DISTILLER_SESSION_v1";
|
|
13
|
+
/**
|
|
14
|
+
* Native language scans faster: when lang is "auto", follow whichever
|
|
15
|
+
* language dominates the user's own messages in the slices.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveLang(candidates: DecisionCandidate[], lang: "auto" | "zh" | "en"): "zh" | "en";
|
|
18
|
+
export declare function buildPrompt(candidates: DecisionCandidate[], budget: number, lang?: "zh" | "en"): DistillPrompt;
|