friction-log 0.0.0-stage → 0.6.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 (107) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/LICENSE +21 -0
  3. package/README.md +98 -2
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +504 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/bilanz.d.ts +23 -0
  8. package/dist/commands/bilanz.js +106 -0
  9. package/dist/commands/bilanz.js.map +1 -0
  10. package/dist/commands/digest.d.ts +36 -0
  11. package/dist/commands/digest.js +288 -0
  12. package/dist/commands/digest.js.map +1 -0
  13. package/dist/commands/export.d.ts +36 -0
  14. package/dist/commands/export.js +118 -0
  15. package/dist/commands/export.js.map +1 -0
  16. package/dist/commands/file.d.ts +18 -0
  17. package/dist/commands/file.js +51 -0
  18. package/dist/commands/file.js.map +1 -0
  19. package/dist/commands/import.d.ts +31 -0
  20. package/dist/commands/import.js +283 -0
  21. package/dist/commands/import.js.map +1 -0
  22. package/dist/commands/init.d.ts +44 -0
  23. package/dist/commands/init.js +256 -0
  24. package/dist/commands/init.js.map +1 -0
  25. package/dist/commands/list.d.ts +16 -0
  26. package/dist/commands/list.js +57 -0
  27. package/dist/commands/list.js.map +1 -0
  28. package/dist/commands/log.d.ts +18 -0
  29. package/dist/commands/log.js +52 -0
  30. package/dist/commands/log.js.map +1 -0
  31. package/dist/commands/rm.d.ts +9 -0
  32. package/dist/commands/rm.js +22 -0
  33. package/dist/commands/rm.js.map +1 -0
  34. package/dist/commands/scan.d.ts +25 -0
  35. package/dist/commands/scan.js +73 -0
  36. package/dist/commands/scan.js.map +1 -0
  37. package/dist/commands/search.d.ts +15 -0
  38. package/dist/commands/search.js +37 -0
  39. package/dist/commands/search.js.map +1 -0
  40. package/dist/commands/sync-export.d.ts +46 -0
  41. package/dist/commands/sync-export.js +80 -0
  42. package/dist/commands/sync-export.js.map +1 -0
  43. package/dist/commands/update.d.ts +12 -0
  44. package/dist/commands/update.js +24 -0
  45. package/dist/commands/update.js.map +1 -0
  46. package/dist/config.d.ts +31 -0
  47. package/dist/config.js +145 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/db.d.ts +81 -0
  50. package/dist/db.js +548 -0
  51. package/dist/db.js.map +1 -0
  52. package/dist/index.d.ts +15 -0
  53. package/dist/index.js +16 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/paths.d.ts +12 -0
  56. package/dist/paths.js +34 -0
  57. package/dist/paths.js.map +1 -0
  58. package/dist/scanners/claude-code.d.ts +6 -0
  59. package/dist/scanners/claude-code.js +202 -0
  60. package/dist/scanners/claude-code.js.map +1 -0
  61. package/dist/scanners/index.d.ts +4 -0
  62. package/dist/scanners/index.js +12 -0
  63. package/dist/scanners/index.js.map +1 -0
  64. package/dist/sinks/agent-tasks.d.ts +36 -0
  65. package/dist/sinks/agent-tasks.js +153 -0
  66. package/dist/sinks/agent-tasks.js.map +1 -0
  67. package/dist/sinks/github-issues.d.ts +20 -0
  68. package/dist/sinks/github-issues.js +98 -0
  69. package/dist/sinks/github-issues.js.map +1 -0
  70. package/dist/sinks/index.d.ts +11 -0
  71. package/dist/sinks/index.js +48 -0
  72. package/dist/sinks/index.js.map +1 -0
  73. package/dist/sinks/linear.d.ts +25 -0
  74. package/dist/sinks/linear.js +157 -0
  75. package/dist/sinks/linear.js.map +1 -0
  76. package/dist/sinks/markdown-file.d.ts +7 -0
  77. package/dist/sinks/markdown-file.js +54 -0
  78. package/dist/sinks/markdown-file.js.map +1 -0
  79. package/dist/sinks/stdout-json.d.ts +15 -0
  80. package/dist/sinks/stdout-json.js +49 -0
  81. package/dist/sinks/stdout-json.js.map +1 -0
  82. package/dist/templates/auth-expiry.yml +30 -0
  83. package/dist/templates/doc-gap.yml +30 -0
  84. package/dist/templates/output-overflow.yml +25 -0
  85. package/dist/templates/schema-drift.yml +26 -0
  86. package/dist/templates/tool-error.yml +34 -0
  87. package/dist/templates/tool-missing-capability.yml +26 -0
  88. package/dist/templates/workflow-friction.yml +26 -0
  89. package/dist/templates.d.ts +5 -0
  90. package/dist/templates.js +88 -0
  91. package/dist/templates.js.map +1 -0
  92. package/dist/types.d.ts +79 -0
  93. package/dist/types.js +2 -0
  94. package/dist/types.js.map +1 -0
  95. package/docs/commands.md +74 -0
  96. package/docs/design.md +25 -0
  97. package/docs/sinks.md +79 -0
  98. package/docs/storage.md +20 -0
  99. package/docs/sync-export.md +23 -0
  100. package/package.json +64 -4
  101. package/templates/auth-expiry.yml +30 -0
  102. package/templates/doc-gap.yml +30 -0
  103. package/templates/output-overflow.yml +25 -0
  104. package/templates/schema-drift.yml +26 -0
  105. package/templates/tool-error.yml +34 -0
  106. package/templates/tool-missing-capability.yml +26 -0
  107. package/templates/workflow-friction.yml +26 -0
@@ -0,0 +1,256 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
6
+ import { defaultConfigPath } from "../paths.js";
7
+ export async function runInit(input = {}) {
8
+ const configPath = input.configPath ?? defaultConfigPath();
9
+ const detected = (input.detect ?? defaultDetect)();
10
+ const interactive = input.yes !== true;
11
+ const prompt = input.prompt ?? readlinePrompter();
12
+ const defaultSink = input.sink ??
13
+ (interactive
14
+ ? (await prompt.select("Default sink", [
15
+ "markdown-file",
16
+ "stdout-json",
17
+ "github-issues",
18
+ "agent-tasks",
19
+ "linear",
20
+ ], suggestSink(detected)))
21
+ : suggestSink(detected));
22
+ const configExistedBefore = existsSync(configPath);
23
+ const existing = configExistedBefore ? readFileSync(configPath, "utf8") : "";
24
+ const newConfig = mergeConfigYaml(existing, defaultSink, input.syncExport);
25
+ // Only write when the content actually changes; idempotency matters when
26
+ // init is run multiple times during onboarding.
27
+ let configWritten = false;
28
+ if (newConfig !== existing) {
29
+ mkdirSync(dirname(configPath), { recursive: true });
30
+ writeFileSync(configPath, newConfig, "utf8");
31
+ configWritten = true;
32
+ }
33
+ let stopHookWrittenTo = null;
34
+ const wantHook = input.installStopHook ??
35
+ (detected.claudeCodeSettingsPath !== null &&
36
+ (interactive
37
+ ? await prompt.confirm("Install friction-log scan as a Claude Code Stop-hook?", true)
38
+ : false));
39
+ if (wantHook && detected.claudeCodeSettingsPath) {
40
+ const path = detected.claudeCodeSettingsPath;
41
+ const before = existsSync(path) ? readFileSync(path, "utf8") : "{}";
42
+ const after = mergeStopHook(before);
43
+ if (after !== before) {
44
+ mkdirSync(dirname(path), { recursive: true });
45
+ writeFileSync(path, after, "utf8");
46
+ }
47
+ stopHookWrittenTo = path;
48
+ }
49
+ return {
50
+ configPath,
51
+ configWritten,
52
+ configExistedBefore,
53
+ defaultSink,
54
+ stopHookWrittenTo,
55
+ detected,
56
+ nextSteps: buildNextSteps(defaultSink, configPath, stopHookWrittenTo, input.syncExport),
57
+ };
58
+ }
59
+ function suggestSink(d) {
60
+ if (d.agentTasksTokenPresent)
61
+ return "agent-tasks";
62
+ if (d.linearKeyPresent)
63
+ return "linear";
64
+ if (d.ghAvailable)
65
+ return "github-issues";
66
+ return "markdown-file";
67
+ }
68
+ function defaultDetect() {
69
+ const claudeHome = process.env.CLAUDE_HOME ?? join(homedir(), ".claude");
70
+ const settingsPath = join(claudeHome, "settings.json");
71
+ const claudeCodeSettingsPath = existsSync(claudeHome) ? settingsPath : null;
72
+ const which = spawnSync("which", ["gh"], { encoding: "utf8" });
73
+ const ghAvailable = which.status === 0 && Boolean((which.stdout ?? "").trim());
74
+ return {
75
+ claudeCodeSettingsPath,
76
+ ghAvailable,
77
+ linearKeyPresent: Boolean(process.env.LINEAR_API_KEY),
78
+ agentTasksTokenPresent: Boolean(process.env.AGENT_TASKS_TOKEN),
79
+ };
80
+ }
81
+ export function mergeConfigYaml(existingYaml, defaultSink, syncExport) {
82
+ // Parse as best-effort; if existing config is corrupt we still want init
83
+ // to land a clean default scaffolding rather than wedging the user.
84
+ let parsed = {};
85
+ if (existingYaml.trim()) {
86
+ try {
87
+ const tmp = parseYaml(existingYaml);
88
+ if (tmp && typeof tmp === "object" && !Array.isArray(tmp)) {
89
+ parsed = tmp;
90
+ }
91
+ }
92
+ catch {
93
+ parsed = {};
94
+ }
95
+ }
96
+ const sinks = parsed.sinks &&
97
+ typeof parsed.sinks === "object" &&
98
+ !Array.isArray(parsed.sinks)
99
+ ? parsed.sinks
100
+ : {};
101
+ if (!sinks[defaultSink]) {
102
+ sinks[defaultSink] = scaffoldForSink(defaultSink);
103
+ }
104
+ parsed.sinks = sinks;
105
+ parsed.default_sink = defaultSink;
106
+ // Opt-in only, and this is the ONLY place that writes sync_export: when
107
+ // `syncExport` is omitted (the default `init` path), any existing
108
+ // sync_export block in `parsed` is passed through untouched below since we
109
+ // never delete or overwrite the key. This is the fix for the drift bug
110
+ // where init.ts wrote `default_sink` but loadConfig never read it back:
111
+ // config.ts's loadConfig reads this exact `sync_export` shape (see
112
+ // parseSyncExportBlock).
113
+ if (syncExport) {
114
+ parsed.sync_export = {
115
+ path: syncExport.path,
116
+ origin: syncExport.origin,
117
+ ...(syncExport.peerPaths && syncExport.peerPaths.length
118
+ ? { peer_paths: syncExport.peerPaths }
119
+ : {}),
120
+ };
121
+ }
122
+ return stringifyYaml(parsed);
123
+ }
124
+ function scaffoldForSink(sink) {
125
+ switch (sink) {
126
+ case "markdown-file":
127
+ return {};
128
+ case "stdout-json":
129
+ return {};
130
+ case "github-issues":
131
+ return { repo: "owner/repo", labels: ["friction"] };
132
+ case "agent-tasks":
133
+ return {
134
+ mode: "rest",
135
+ apiBase: "https://agent-tasks.example.test",
136
+ projectId: "<uuid>",
137
+ };
138
+ case "linear":
139
+ return { teamId: "<team-uuid>", state: "Backlog" };
140
+ }
141
+ }
142
+ const STOP_HOOK = {
143
+ type: "command",
144
+ command: "friction-log scan --silent --stdin-payload",
145
+ };
146
+ export function mergeStopHook(existingJson) {
147
+ let parsed = {};
148
+ if (existingJson.trim()) {
149
+ try {
150
+ const tmp = JSON.parse(existingJson);
151
+ if (tmp && typeof tmp === "object" && !Array.isArray(tmp)) {
152
+ parsed = tmp;
153
+ }
154
+ }
155
+ catch {
156
+ parsed = {};
157
+ }
158
+ }
159
+ const hooks = parsed.hooks &&
160
+ typeof parsed.hooks === "object" &&
161
+ !Array.isArray(parsed.hooks)
162
+ ? parsed.hooks
163
+ : {};
164
+ const stopEntries = Array.isArray(hooks.Stop)
165
+ ? hooks.Stop
166
+ : [];
167
+ let group = stopEntries.find((e) => e && typeof e === "object" && (!("matcher" in e) || e.matcher === ""));
168
+ if (!group) {
169
+ group = { matcher: "", hooks: [] };
170
+ stopEntries.push(group);
171
+ }
172
+ const groupHooks = Array.isArray(group.hooks)
173
+ ? group.hooks
174
+ : [];
175
+ if (!groupHooks.some((h) => h?.command === STOP_HOOK.command)) {
176
+ groupHooks.push({ ...STOP_HOOK });
177
+ }
178
+ group.hooks = groupHooks;
179
+ hooks.Stop = stopEntries;
180
+ parsed.hooks = hooks;
181
+ return JSON.stringify(parsed, null, 2) + "\n";
182
+ }
183
+ function buildNextSteps(sink, configPath, stopHook, syncExport) {
184
+ const out = [];
185
+ out.push(`Configuration written to ${configPath}.`);
186
+ out.push(`Default sink: ${sink}.`);
187
+ if (stopHook) {
188
+ out.push(`Stop-hook installed at ${stopHook}; new sessions auto-scan on close.`);
189
+ }
190
+ else {
191
+ out.push(`Stop-hook not installed. Run "friction-log scan" manually at end of session, or re-run "init" later.`);
192
+ }
193
+ if (syncExport) {
194
+ out.push(`sync-export configured: origin=${syncExport.origin}, writes to ${syncExport.path} after every mutation.`);
195
+ }
196
+ out.push("Quick smoke:");
197
+ out.push(` friction-log log --title "first friction" --tool "demo" --category tool-error`);
198
+ out.push(` friction-log list`);
199
+ out.push(` friction-log file 1 --sink ${sink}`);
200
+ return out;
201
+ }
202
+ /** Minimal readline-based prompter so init has no new runtime dep. */
203
+ function readlinePrompter() {
204
+ return {
205
+ async select(question, choices, def) {
206
+ const rl = await loadReadline();
207
+ try {
208
+ const list = choices
209
+ .map((c, i) => ` ${i + 1}) ${c}${c === def ? " (default)" : ""}`)
210
+ .join("\n");
211
+ const prompt = `${question}\n${list}\nChoice [${def ?? choices[0]}]: `;
212
+ const raw = (await rl.question(prompt)).trim();
213
+ if (!raw)
214
+ return def ?? choices[0];
215
+ const asIdx = Number(raw);
216
+ if (Number.isInteger(asIdx) && asIdx >= 1 && asIdx <= choices.length) {
217
+ return choices[asIdx - 1];
218
+ }
219
+ if (choices.includes(raw))
220
+ return raw;
221
+ process.stderr.write(`(invalid choice "${raw}"; using ${def ?? choices[0]})\n`);
222
+ return def ?? choices[0];
223
+ }
224
+ finally {
225
+ rl.close();
226
+ }
227
+ },
228
+ async confirm(question, def = true) {
229
+ const rl = await loadReadline();
230
+ try {
231
+ const hint = def ? "Y/n" : "y/N";
232
+ const raw = (await rl.question(`${question} [${hint}]: `))
233
+ .trim()
234
+ .toLowerCase();
235
+ if (!raw)
236
+ return def;
237
+ if (raw === "y" || raw === "yes")
238
+ return true;
239
+ if (raw === "n" || raw === "no")
240
+ return false;
241
+ return def;
242
+ }
243
+ finally {
244
+ rl.close();
245
+ }
246
+ },
247
+ };
248
+ }
249
+ async function loadReadline() {
250
+ const readline = await import("node:readline/promises");
251
+ return readline.createInterface({
252
+ input: process.stdin,
253
+ output: process.stdout,
254
+ });
255
+ }
256
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Friction, FrictionSource, FrictionStatus } from "../types.js";
2
+ export interface ListCommandInput {
3
+ status?: FrictionStatus;
4
+ tool?: string;
5
+ category?: string;
6
+ source?: FrictionSource;
7
+ age?: string;
8
+ limit?: number;
9
+ dbPath?: string;
10
+ }
11
+ export interface ListCommandOutput {
12
+ frictions: Friction[];
13
+ }
14
+ export declare function parseAge(age: string | undefined, now?: Date): string | undefined;
15
+ export declare function runList(input: ListCommandInput): ListCommandOutput;
16
+ export declare function formatTable(frictions: Friction[]): string;
@@ -0,0 +1,57 @@
1
+ import { FrictionDb } from "../db.js";
2
+ import { defaultDbPath } from "../paths.js";
3
+ const AGE_PATTERN = /^(\d+)([dwh])$/;
4
+ export function parseAge(age, now = new Date()) {
5
+ if (!age)
6
+ return undefined;
7
+ const match = AGE_PATTERN.exec(age);
8
+ if (!match) {
9
+ throw new Error(`friction-log: invalid --age "${age}". Use e.g. 14d, 4w, 12h`);
10
+ }
11
+ const n = Number(match[1]);
12
+ const unit = match[2];
13
+ const ms = unit === "h"
14
+ ? n * 3600_000
15
+ : unit === "d"
16
+ ? n * 86_400_000
17
+ : n * 7 * 86_400_000;
18
+ return new Date(now.getTime() - ms).toISOString();
19
+ }
20
+ export function runList(input) {
21
+ const db = new FrictionDb(input.dbPath ?? defaultDbPath());
22
+ try {
23
+ const filter = {
24
+ status: input.status,
25
+ tool: input.tool,
26
+ category: input.category,
27
+ source: input.source,
28
+ sinceIso: parseAge(input.age),
29
+ limit: input.limit,
30
+ };
31
+ return { frictions: db.listFrictions(filter) };
32
+ }
33
+ finally {
34
+ db.close();
35
+ }
36
+ }
37
+ export function formatTable(frictions) {
38
+ if (frictions.length === 0)
39
+ return "(no frictions match)";
40
+ const header = ["id", "status", "tool", "category", "captured", "title"];
41
+ const rows = frictions.map((f) => [
42
+ String(f.id),
43
+ f.status,
44
+ f.toolSurface ?? "-",
45
+ f.category ?? "-",
46
+ f.capturedAt.slice(0, 16).replace("T", " "),
47
+ truncate(f.title, 60),
48
+ ]);
49
+ const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
50
+ const sep = widths.map((w) => "-".repeat(w)).join(" ");
51
+ const fmt = (row) => row.map((c, i) => c.padEnd(widths[i])).join(" ");
52
+ return [fmt(header), sep, ...rows.map(fmt)].join("\n");
53
+ }
54
+ function truncate(s, max) {
55
+ return s.length <= max ? s : s.slice(0, max - 1) + "…";
56
+ }
57
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Severity } from "../types.js";
2
+ export interface LogCommandInput {
3
+ title: string;
4
+ description?: string;
5
+ tool?: string;
6
+ category?: string;
7
+ severity?: Severity;
8
+ sessionId?: string;
9
+ recurrenceOfId?: number;
10
+ dbPath?: string;
11
+ configPath?: string;
12
+ }
13
+ export interface LogCommandOutput {
14
+ id: number;
15
+ capturedAt: string;
16
+ recurrenceOfId: number | null;
17
+ }
18
+ export declare function runLog(input: LogCommandInput): LogCommandOutput;
@@ -0,0 +1,52 @@
1
+ import { FrictionDb } from "../db.js";
2
+ import { defaultDbPath } from "../paths.js";
3
+ import { maybeSyncExport } from "./sync-export.js";
4
+ export function runLog(input) {
5
+ const dbPath = input.dbPath ?? defaultDbPath();
6
+ const db = new FrictionDb(dbPath);
7
+ try {
8
+ if (input.recurrenceOfId !== undefined) {
9
+ const parent = db.getFriction(input.recurrenceOfId);
10
+ if (!parent) {
11
+ throw new Error(`friction-log: --recurrence-of ${input.recurrenceOfId} does not match any friction`);
12
+ }
13
+ }
14
+ // Normalise empty string to null so a `--session ''` (or a wrapper
15
+ // that always passes the flag with a possibly-empty value) does not
16
+ // sneak past the upsert and then trip the FK on the friction insert.
17
+ const sessionId = typeof input.sessionId === "string" && input.sessionId !== ""
18
+ ? input.sessionId
19
+ : null;
20
+ // Sessions row must exist before the friction insert, otherwise the
21
+ // frictions.session_id FK violates and the log path exits 1 with a
22
+ // raw SQLite error. The common case is an agent attributing a
23
+ // mid-session friction to its live runtime session id (e.g.
24
+ // $CLAUDE_CODE_SESSION_ID) which was never imported. Upsert a
25
+ // placeholder so the call just works; a later `friction-log import`
26
+ // or `scan` overwrites it with full metadata via `upsertSession`.
27
+ if (sessionId !== null) {
28
+ db.ensureSession(sessionId);
29
+ }
30
+ const insert = {
31
+ title: input.title,
32
+ description: input.description ?? null,
33
+ toolSurface: input.tool ?? null,
34
+ category: input.category ?? null,
35
+ severity: input.severity ?? null,
36
+ sessionId,
37
+ recurrenceOfId: input.recurrenceOfId ?? null,
38
+ source: "manual",
39
+ };
40
+ const f = db.insertFriction(insert);
41
+ maybeSyncExport({ dbPath, configPath: input.configPath });
42
+ return {
43
+ id: f.id,
44
+ capturedAt: f.capturedAt,
45
+ recurrenceOfId: f.recurrenceOfId,
46
+ };
47
+ }
48
+ finally {
49
+ db.close();
50
+ }
51
+ }
52
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/commands/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA4B,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAoBnD,MAAM,UAAU,MAAM,CAAC,KAAsB;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;IAC/C,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YACpD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CACb,iCAAiC,KAAK,CAAC,cAAc,8BAA8B,CACpF,CAAC;YACJ,CAAC;QACH,CAAC;QACD,mEAAmE;QACnE,oEAAoE;QACpE,qEAAqE;QACrE,MAAM,SAAS,GACb,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,SAAS,KAAK,EAAE;YAC3D,CAAC,CAAC,KAAK,CAAC,SAAS;YACjB,CAAC,CAAC,IAAI,CAAC;QACX,oEAAoE;QACpE,mEAAmE;QACnE,8DAA8D;QAC9D,4DAA4D;QAC5D,8DAA8D;QAC9D,oEAAoE;QACpE,kEAAkE;QAClE,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;QACD,MAAM,MAAM,GAAwB;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,IAAI;YACtC,WAAW,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI;YAC/B,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;YAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;YAChC,SAAS;YACT,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,IAAI;YAC5C,MAAM,EAAE,QAAQ;SACjB,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACpC,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QAC1D,OAAO;YACL,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,cAAc,EAAE,CAAC,CAAC,cAAc;SACjC,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface RmCommandInput {
2
+ frictionId: number;
3
+ dbPath?: string;
4
+ configPath?: string;
5
+ }
6
+ export interface RmCommandOutput {
7
+ removed: boolean;
8
+ }
9
+ export declare function runRm(input: RmCommandInput): RmCommandOutput;
@@ -0,0 +1,22 @@
1
+ import { FrictionDb } from "../db.js";
2
+ import { defaultDbPath } from "../paths.js";
3
+ import { maybeSyncExport } from "./sync-export.js";
4
+ export function runRm(input) {
5
+ const dbPath = input.dbPath ?? defaultDbPath();
6
+ const db = new FrictionDb(dbPath);
7
+ try {
8
+ const friction = db.getFriction(input.frictionId);
9
+ if (!friction) {
10
+ throw new Error(`friction-log: friction id=${input.frictionId} not found`);
11
+ }
12
+ const removed = db.deleteFriction(input.frictionId);
13
+ if (removed) {
14
+ maybeSyncExport({ dbPath, configPath: input.configPath });
15
+ }
16
+ return { removed };
17
+ }
18
+ finally {
19
+ db.close();
20
+ }
21
+ }
22
+ //# sourceMappingURL=rm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rm.js","sourceRoot":"","sources":["../../src/commands/rm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAYnD,MAAM,UAAU,KAAK,CAAC,KAAqB;IACzC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;IAC/C,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CACb,6BAA6B,KAAK,CAAC,UAAU,YAAY,CAC1D,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,OAAO,EAAE,CAAC;YACZ,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { Friction } from "../types.js";
2
+ export interface ScanCommandInput {
3
+ sessionId?: string;
4
+ transcriptPath?: string;
5
+ adapter?: string;
6
+ dbPath?: string;
7
+ configPath?: string;
8
+ }
9
+ export interface ScanCommandOutput {
10
+ sessionId: string;
11
+ candidatesFound: number;
12
+ inserted: number;
13
+ skippedDuplicates: number;
14
+ adapter: string;
15
+ }
16
+ export declare function runScan(input: ScanCommandInput): Promise<ScanCommandOutput>;
17
+ export interface StopHookPayload {
18
+ session_id?: string;
19
+ sessionId?: string;
20
+ transcript_path?: string;
21
+ transcriptPath?: string;
22
+ }
23
+ export declare function payloadToScanInput(payload: StopHookPayload, adapter?: string): ScanCommandInput;
24
+ export declare function summarize(output: ScanCommandOutput, sessionId: string): string;
25
+ export { Friction };
@@ -0,0 +1,73 @@
1
+ import { FrictionDb } from "../db.js";
2
+ import { defaultDbPath } from "../paths.js";
3
+ import { loadScanner } from "../scanners/index.js";
4
+ import { maybeSyncExport } from "./sync-export.js";
5
+ export async function runScan(input) {
6
+ const adapterName = input.adapter ?? "claude-code";
7
+ const scanner = loadScanner(adapterName);
8
+ const result = await scanner.scan({
9
+ sessionId: input.sessionId,
10
+ transcriptPath: input.transcriptPath,
11
+ });
12
+ const dbPath = input.dbPath ?? defaultDbPath();
13
+ const db = new FrictionDb(dbPath);
14
+ try {
15
+ db.upsertSession({
16
+ id: result.session.id,
17
+ startedAt: result.session.startedAt,
18
+ endedAt: result.session.endedAt,
19
+ projectPaths: result.session.projectPaths ?? null,
20
+ transcriptPath: result.session.transcriptPath ?? null,
21
+ adapter: result.session.adapter ?? adapterName,
22
+ });
23
+ let inserted = 0;
24
+ let skipped = 0;
25
+ for (const c of result.frictionCandidates) {
26
+ const existing = db.findFrictionByTriple(result.session.id, c.toolSurface ?? null, c.title);
27
+ if (existing) {
28
+ skipped++;
29
+ continue;
30
+ }
31
+ const insert = {
32
+ sessionId: result.session.id,
33
+ toolSurface: c.toolSurface ?? null,
34
+ title: c.title,
35
+ description: c.description ?? null,
36
+ severity: c.severity ?? null,
37
+ category: c.category ?? null,
38
+ source: "scan",
39
+ };
40
+ db.insertFriction(insert);
41
+ inserted++;
42
+ }
43
+ // Skip write-through entirely when nothing was actually inserted (all
44
+ // candidates were duplicates, or there were none): the export payload
45
+ // only carries frictions, so a session-only upsert with zero new
46
+ // frictions can never change its content anyway.
47
+ if (inserted > 0) {
48
+ maybeSyncExport({ dbPath, configPath: input.configPath });
49
+ }
50
+ return {
51
+ sessionId: result.session.id,
52
+ candidatesFound: result.frictionCandidates.length,
53
+ inserted,
54
+ skippedDuplicates: skipped,
55
+ adapter: adapterName,
56
+ };
57
+ }
58
+ finally {
59
+ db.close();
60
+ }
61
+ }
62
+ export function payloadToScanInput(payload, adapter) {
63
+ return {
64
+ sessionId: payload.session_id ?? payload.sessionId,
65
+ transcriptPath: payload.transcript_path ?? payload.transcriptPath,
66
+ adapter,
67
+ };
68
+ }
69
+ export function summarize(output, sessionId) {
70
+ return (`scanned session=${sessionId} adapter=${output.adapter} ` +
71
+ `candidates=${output.candidatesFound} inserted=${output.inserted} skipped=${output.skippedDuplicates}`);
72
+ }
73
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Friction, FrictionSource, FrictionStatus } from "../types.js";
2
+ export interface SearchCommandInput {
3
+ query: string;
4
+ status?: FrictionStatus;
5
+ tool?: string;
6
+ category?: string;
7
+ source?: FrictionSource;
8
+ age?: string;
9
+ limit?: number;
10
+ dbPath?: string;
11
+ }
12
+ export interface SearchCommandOutput {
13
+ frictions: Friction[];
14
+ }
15
+ export declare function runSearch(input: SearchCommandInput): SearchCommandOutput;
@@ -0,0 +1,37 @@
1
+ import { FrictionDb } from "../db.js";
2
+ import { defaultDbPath } from "../paths.js";
3
+ import { parseAge } from "./list.js";
4
+ export function runSearch(input) {
5
+ if (!input.query || !input.query.trim()) {
6
+ throw new Error("friction-log: search query must not be empty");
7
+ }
8
+ const db = new FrictionDb(input.dbPath ?? defaultDbPath());
9
+ try {
10
+ const filter = {
11
+ status: input.status,
12
+ tool: input.tool,
13
+ category: input.category,
14
+ source: input.source,
15
+ sinceIso: parseAge(input.age),
16
+ limit: input.limit,
17
+ };
18
+ try {
19
+ return { frictions: db.searchFrictions(input.query, filter) };
20
+ }
21
+ catch (err) {
22
+ const msg = err instanceof Error ? err.message : String(err);
23
+ // better-sqlite3 surfaces FTS5 parse failures as opaque SQLite errors
24
+ // ("unterminated string", "fts5: syntax error near ...", etc). The
25
+ // surrounding query is parameterized, so any error here is the user's
26
+ // MATCH expression. Turn it into a hint pointing at the FTS5 docs.
27
+ if (/fts5|MATCH|unterminated|syntax error|near "/i.test(msg)) {
28
+ throw new Error(`friction-log: invalid FTS5 query "${input.query}". See https://sqlite.org/fts5.html#full_text_query_syntax`);
29
+ }
30
+ throw err;
31
+ }
32
+ }
33
+ finally {
34
+ db.close();
35
+ }
36
+ }
37
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA4B,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAiBrC,MAAM,UAAU,SAAS,CAAC,KAAyB;IACjD,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,MAAM,GAAwB;YAClC,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;YAC7B,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC;QACF,IAAI,CAAC;YACH,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,sEAAsE;YACtE,mEAAmE;YACnE,sEAAsE;YACtE,mEAAmE;YACnE,IAAI,8CAA8C,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7D,MAAM,IAAI,KAAK,CACb,qCAAqC,KAAK,CAAC,KAAK,4DAA4D,CAC7G,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}