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,157 @@
1
+ const ISSUE_CREATE_MUTATION = `
2
+ mutation IssueCreate($input: IssueCreateInput!) {
3
+ issueCreate(input: $input) {
4
+ success
5
+ issue { id identifier url }
6
+ }
7
+ }`.trim();
8
+ /**
9
+ * Create a Linear issue via GraphQL. LINEAR_API_KEY env var supplies the
10
+ * personal access token by default; teamId is required.
11
+ *
12
+ * The `state` option is matched case-insensitively against the team's
13
+ * workflow-state names so callers can write "Backlog" instead of a UUID.
14
+ * Looking up the workflow state requires one extra query, kept minimal.
15
+ */
16
+ export class LinearSink {
17
+ fetchImpl;
18
+ name = "linear";
19
+ constructor(fetchImpl = (url, init) => fetch(url, init)) {
20
+ this.fetchImpl = fetchImpl;
21
+ }
22
+ async file(friction, rendered, opts) {
23
+ const parsed = parseOpts(opts);
24
+ const apiKey = parsed.apiKey ?? process.env.LINEAR_API_KEY;
25
+ if (!apiKey) {
26
+ throw new Error(`friction-log: linear sink requires an API key. Set LINEAR_API_KEY env var or sinks.linear.apiKey.`);
27
+ }
28
+ const endpoint = parsed.endpoint ?? "https://api.linear.app/graphql";
29
+ const stateId = parsed.state
30
+ ? isUuid(parsed.state)
31
+ ? parsed.state
32
+ : await resolveStateId(this.fetchImpl, endpoint, apiKey, parsed.teamId, parsed.state)
33
+ : undefined;
34
+ const input = {
35
+ teamId: parsed.teamId,
36
+ title: rendered.title,
37
+ description: renderDescription(friction, rendered),
38
+ labelIds: undefined,
39
+ };
40
+ if (stateId)
41
+ input.stateId = stateId;
42
+ if (parsed.assignee)
43
+ input.assigneeId = parsed.assignee;
44
+ const variables = { input };
45
+ const response = await this.fetchImpl(endpoint, {
46
+ method: "POST",
47
+ headers: {
48
+ "content-type": "application/json",
49
+ authorization: apiKey,
50
+ },
51
+ body: JSON.stringify({ query: ISSUE_CREATE_MUTATION, variables }),
52
+ });
53
+ if (!response.ok) {
54
+ const body = await safeText(response);
55
+ throw new Error(`friction-log: linear API returned ${response.status}: ${body.slice(0, 400)}`);
56
+ }
57
+ const json = (await response.json());
58
+ if (json.errors?.length) {
59
+ throw new Error(`friction-log: linear API errors: ${json.errors.map((e) => e.message ?? "(no message)").join("; ")}`);
60
+ }
61
+ const issue = json.data?.issueCreate?.issue;
62
+ if (!json.data?.issueCreate?.success ||
63
+ !issue?.id ||
64
+ !issue.identifier ||
65
+ !issue.url) {
66
+ throw new Error(`friction-log: linear issueCreate did not return a complete issue: ${JSON.stringify(json).slice(0, 400)}`);
67
+ }
68
+ return {
69
+ ok: true,
70
+ sinkTarget: `linear:${parsed.teamId}`,
71
+ externalRef: issue.url,
72
+ message: `created ${issue.identifier} (${issue.url})`,
73
+ };
74
+ }
75
+ }
76
+ const WORKFLOW_STATES_QUERY = `
77
+ query WorkflowStates($teamId: String!) {
78
+ team(id: $teamId) {
79
+ states { nodes { id name } }
80
+ }
81
+ }`.trim();
82
+ async function resolveStateId(fetchImpl, endpoint, apiKey, teamId, stateName) {
83
+ const response = await fetchImpl(endpoint, {
84
+ method: "POST",
85
+ headers: {
86
+ "content-type": "application/json",
87
+ authorization: apiKey,
88
+ },
89
+ body: JSON.stringify({
90
+ query: WORKFLOW_STATES_QUERY,
91
+ variables: { teamId },
92
+ }),
93
+ });
94
+ if (!response.ok) {
95
+ const body = await safeText(response);
96
+ throw new Error(`friction-log: linear state lookup ${response.status}: ${body.slice(0, 400)}`);
97
+ }
98
+ const json = (await response.json());
99
+ if (json.errors?.length) {
100
+ throw new Error(`friction-log: linear state lookup errors: ${json.errors.map((e) => e.message ?? "(no message)").join("; ")}`);
101
+ }
102
+ const nodes = json.data?.team?.states?.nodes ?? [];
103
+ const wanted = stateName.toLowerCase();
104
+ const match = nodes.find((n) => n.name?.toLowerCase() === wanted);
105
+ if (!match?.id) {
106
+ const known = nodes
107
+ .map((n) => n.name)
108
+ .filter(Boolean)
109
+ .join(", ");
110
+ throw new Error(`friction-log: linear team ${teamId} has no state matching "${stateName}". Known: ${known || "(none)"}`);
111
+ }
112
+ return match.id;
113
+ }
114
+ function parseOpts(opts) {
115
+ const raw = opts.sinkOpts ?? {};
116
+ const teamId = raw.teamId;
117
+ if (typeof teamId !== "string" || !teamId) {
118
+ throw new Error(`friction-log: linear sink requires "teamId". Set sinks.linear.teamId in config.yml or pass --sink-opt teamId=...`);
119
+ }
120
+ const state = optionalString(raw.state, "state");
121
+ const assignee = optionalString(raw.assignee, "assignee");
122
+ const apiKey = optionalString(raw.apiKey, "apiKey");
123
+ const endpoint = optionalString(raw.endpoint, "endpoint");
124
+ return { teamId, state, assignee, apiKey, endpoint };
125
+ }
126
+ function isUuid(s) {
127
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
128
+ }
129
+ function optionalString(value, name) {
130
+ if (value == null)
131
+ return undefined;
132
+ if (typeof value === "string")
133
+ return value;
134
+ throw new Error(`friction-log: linear "${name}" must be a string`);
135
+ }
136
+ function renderDescription(friction, rendered) {
137
+ const meta = [];
138
+ meta.push(`friction-log id ${friction.id} captured ${friction.capturedAt}`);
139
+ if (friction.toolSurface)
140
+ meta.push(`tool: \`${friction.toolSurface}\``);
141
+ if (friction.category)
142
+ meta.push(`category: ${friction.category}`);
143
+ if (friction.severity)
144
+ meta.push(`severity: ${friction.severity}`);
145
+ if (friction.recurrenceOfId != null)
146
+ meta.push(`recurrence of friction #${friction.recurrenceOfId}`);
147
+ return `${rendered.body}\n\n---\n${meta.join(" \n")}\n`;
148
+ }
149
+ async function safeText(response) {
150
+ try {
151
+ return await response.text();
152
+ }
153
+ catch {
154
+ return "(no body)";
155
+ }
156
+ }
157
+ //# sourceMappingURL=linear.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear.js","sourceRoot":"","sources":["../../src/sinks/linear.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { FileOptions, FileResult, Friction, RenderedTemplate, Sink } from "../types.js";
2
+ export declare class MarkdownFileSink implements Sink {
3
+ private readonly baseDir;
4
+ readonly name = "markdown-file";
5
+ constructor(baseDir?: string);
6
+ file(friction: Friction, rendered: RenderedTemplate, opts: FileOptions): Promise<FileResult>;
7
+ }
@@ -0,0 +1,54 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { defaultMarkdownSinkDir } from "../paths.js";
4
+ export class MarkdownFileSink {
5
+ baseDir;
6
+ name = "markdown-file";
7
+ constructor(baseDir = defaultMarkdownSinkDir()) {
8
+ this.baseDir = baseDir;
9
+ }
10
+ async file(friction, rendered, opts) {
11
+ const targetDir = opts.sinkTarget ? resolve(opts.sinkTarget) : this.baseDir;
12
+ mkdirSync(targetDir, { recursive: true });
13
+ const filename = buildFilename(friction);
14
+ const fullPath = join(targetDir, filename);
15
+ const content = renderMarkdown(friction, rendered);
16
+ mkdirSync(dirname(fullPath), { recursive: true });
17
+ writeFileSync(fullPath, content, "utf8");
18
+ return {
19
+ ok: true,
20
+ sinkTarget: fullPath,
21
+ message: `wrote ${fullPath}`,
22
+ };
23
+ }
24
+ }
25
+ function buildFilename(friction) {
26
+ const date = friction.capturedAt.slice(0, 10);
27
+ const slug = friction.title
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9]+/g, "-")
30
+ .replace(/^-+|-+$/g, "")
31
+ .slice(0, 60) || `friction-${friction.id}`;
32
+ return `${date}-${friction.id}-${slug}.md`;
33
+ }
34
+ function renderMarkdown(friction, rendered) {
35
+ const frontmatter = [
36
+ "---",
37
+ `friction_id: ${friction.id}`,
38
+ `captured_at: ${friction.capturedAt}`,
39
+ `priority: ${rendered.priority}`,
40
+ `labels: [${rendered.labels.map((l) => JSON.stringify(l)).join(", ")}]`,
41
+ ];
42
+ if (friction.toolSurface)
43
+ frontmatter.push(`tool_surface: ${friction.toolSurface}`);
44
+ if (friction.category)
45
+ frontmatter.push(`category: ${friction.category}`);
46
+ if (friction.severity)
47
+ frontmatter.push(`severity: ${friction.severity}`);
48
+ if (friction.sessionId)
49
+ frontmatter.push(`session_id: ${friction.sessionId}`);
50
+ frontmatter.push(`source: ${friction.source}`);
51
+ frontmatter.push("---");
52
+ return `${frontmatter.join("\n")}\n\n# ${rendered.title}\n\n${rendered.body}\n`;
53
+ }
54
+ //# sourceMappingURL=markdown-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-file.js","sourceRoot":"","sources":["../../src/sinks/markdown-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AASrD,MAAM,OAAO,gBAAgB;IAGE;IAFpB,IAAI,GAAG,eAAe,CAAC;IAEhC,YAA6B,UAAkB,sBAAsB,EAAE;QAA1C,YAAO,GAAP,OAAO,CAAmC;IAAG,CAAC;IAE3E,KAAK,CAAC,IAAI,CACR,QAAkB,EAClB,QAA0B,EAC1B,IAAiB;QAEjB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QAC5E,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACnD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACzC,OAAO;YACL,EAAE,EAAE,IAAI;YACR,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,SAAS,QAAQ,EAAE;SAC7B,CAAC;IACJ,CAAC;CACF;AAED,SAAS,aAAa,CAAC,QAAkB;IACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C,MAAM,IAAI,GACR,QAAQ,CAAC,KAAK;SACX,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,QAAQ,CAAC,EAAE,EAAE,CAAC;IAC/C,OAAO,GAAG,IAAI,IAAI,QAAQ,CAAC,EAAE,IAAI,IAAI,KAAK,CAAC;AAC7C,CAAC;AAED,SAAS,cAAc,CACrB,QAAkB,EAClB,QAA0B;IAE1B,MAAM,WAAW,GAAa;QAC5B,KAAK;QACL,gBAAgB,QAAQ,CAAC,EAAE,EAAE;QAC7B,gBAAgB,QAAQ,CAAC,UAAU,EAAE;QACrC,aAAa,QAAQ,CAAC,QAAQ,EAAE;QAChC,YAAY,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KACxE,CAAC;IACF,IAAI,QAAQ,CAAC,WAAW;QACtB,WAAW,CAAC,IAAI,CAAC,iBAAiB,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5D,IAAI,QAAQ,CAAC,QAAQ;QAAE,WAAW,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1E,IAAI,QAAQ,CAAC,QAAQ;QAAE,WAAW,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1E,IAAI,QAAQ,CAAC,SAAS;QAAE,WAAW,CAAC,IAAI,CAAC,eAAe,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9E,WAAW,CAAC,IAAI,CAAC,WAAW,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/C,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,KAAK,OAAO,QAAQ,CAAC,IAAI,IAAI,CAAC;AAClF,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { FileOptions, FileResult, Friction, RenderedTemplate, Sink } from "../types.js";
2
+ /**
3
+ * Emit a structured JSON record to stdout. No side effects beyond stdout.
4
+ * Useful for piping into custom workflows:
5
+ *
6
+ * friction-log file 7 --sink stdout-json | jq '.rendered.body'
7
+ *
8
+ * The record shape is stable: any future field is additive.
9
+ */
10
+ export declare class StdoutJsonSink implements Sink {
11
+ private readonly write;
12
+ readonly name = "stdout-json";
13
+ constructor(write?: (line: string) => void);
14
+ file(friction: Friction, rendered: RenderedTemplate, _opts: FileOptions): Promise<FileResult>;
15
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Emit a structured JSON record to stdout. No side effects beyond stdout.
3
+ * Useful for piping into custom workflows:
4
+ *
5
+ * friction-log file 7 --sink stdout-json | jq '.rendered.body'
6
+ *
7
+ * The record shape is stable: any future field is additive.
8
+ */
9
+ export class StdoutJsonSink {
10
+ write;
11
+ name = "stdout-json";
12
+ // Tests inject a fake writer so we don't pollute the test runner's stdout.
13
+ constructor(write = (line) => process.stdout.write(line)) {
14
+ this.write = write;
15
+ }
16
+ async file(friction, rendered, _opts) {
17
+ const record = {
18
+ sink: this.name,
19
+ emittedAt: new Date().toISOString(),
20
+ friction: {
21
+ id: friction.id,
22
+ sessionId: friction.sessionId,
23
+ toolSurface: friction.toolSurface,
24
+ title: friction.title,
25
+ description: friction.description,
26
+ capturedAt: friction.capturedAt,
27
+ severity: friction.severity,
28
+ category: friction.category,
29
+ status: friction.status,
30
+ recurrenceOfId: friction.recurrenceOfId,
31
+ source: friction.source,
32
+ },
33
+ rendered: {
34
+ title: rendered.title,
35
+ body: rendered.body,
36
+ labels: rendered.labels,
37
+ priority: rendered.priority,
38
+ metadata: rendered.metadata,
39
+ },
40
+ };
41
+ this.write(JSON.stringify(record) + "\n");
42
+ return {
43
+ ok: true,
44
+ sinkTarget: "stdout",
45
+ message: "emitted JSON record to stdout",
46
+ };
47
+ }
48
+ }
49
+ //# sourceMappingURL=stdout-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdout-json.js","sourceRoot":"","sources":["../../src/sinks/stdout-json.ts"],"names":[],"mappings":"AAQA;;;;;;;GAOG;AACH,MAAM,OAAO,cAAc;IAKN;IAJV,IAAI,GAAG,aAAa,CAAC;IAE9B,2EAA2E;IAC3E,YACmB,QAAgC,CAAC,IAAI,EAAE,EAAE,CACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QADX,UAAK,GAAL,KAAK,CACM;IAC3B,CAAC;IAEJ,KAAK,CAAC,IAAI,CACR,QAAkB,EAClB,QAA0B,EAC1B,KAAkB;QAElB,MAAM,MAAM,GAAG;YACb,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,QAAQ,EAAE;gBACR,EAAE,EAAE,QAAQ,CAAC,EAAE;gBACf,SAAS,EAAE,QAAQ,CAAC,SAAS;gBAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,cAAc,EAAE,QAAQ,CAAC,cAAc;gBACvC,MAAM,EAAE,QAAQ,CAAC,MAAM;aACxB;YACD,QAAQ,EAAE;gBACR,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;aAC5B;SACF,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1C,OAAO;YACL,EAAE,EAAE,IAAI;YACR,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,+BAA+B;SACzC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,30 @@
1
+ name: auth-expiry
2
+ title: "{{tool}}: auth / token lifecycle issue"
3
+ priority: HIGH
4
+ labels:
5
+ - tooling
6
+ - friction
7
+ - auth-expiry
8
+ body: |
9
+ ## Symptom
10
+
11
+ {{description}}
12
+
13
+ ## Context
14
+
15
+ - Tool surface: `{{tool}}`
16
+ - Captured: {{capturedAt}}
17
+ - Session: `{{sessionId}}`
18
+ - Source: `{{source}}`
19
+
20
+ ## Suspected cause
21
+
22
+ Token, JWT, session, or OAuth refresh lifecycle. Common shapes: skew at the issuer, expiry window too tight, refresh not wired, scope drift.
23
+
24
+ ## Reproduction
25
+
26
+ _Capture the failing request, the credential's iat / exp, and the wall-clock at failure time. Without those three the next debug pass restarts from scratch._
27
+
28
+ ## Suggested next step
29
+
30
+ Check the credential's exp vs `date -u +%s` at failure, then widen the issuance window or wire a refresh. If skew is in play, document the deployment environment's clock source.
@@ -0,0 +1,30 @@
1
+ name: doc-gap
2
+ title: "{{tool}}: behavior contradicts documentation"
3
+ priority: LOW
4
+ labels:
5
+ - tooling
6
+ - friction
7
+ - doc-gap
8
+ body: |
9
+ ## Symptom
10
+
11
+ {{description}}
12
+
13
+ ## Context
14
+
15
+ - Tool surface: `{{tool}}`
16
+ - Captured: {{capturedAt}}
17
+ - Session: `{{sessionId}}`
18
+ - Source: `{{source}}`
19
+
20
+ ## What the docs claim
21
+
22
+ _Quote the exact passage that misled the workflow. Link to the URL or path._
23
+
24
+ ## What actually happened
25
+
26
+ See symptom. The gap is small enough to fix in docs alone, no tool change needed.
27
+
28
+ ## Suggested next step
29
+
30
+ Send a one-line doc PR upstream, or pin the corrected note in the team's internal playbook so future runs do not pay this cost again.
@@ -0,0 +1,25 @@
1
+ name: output-overflow
2
+ title: "{{tool}}: output overflows agent context"
3
+ priority: HIGH
4
+ labels:
5
+ - tooling
6
+ - friction
7
+ - output-overflow
8
+ body: |
9
+ ## Symptom
10
+
11
+ `{{tool}}` returned output large enough to overflow the agent's context window or significantly degrade performance.
12
+
13
+ {{description}}
14
+
15
+ ## Context
16
+
17
+ - Tool surface: `{{tool}}`
18
+ - Captured: {{capturedAt}}
19
+ - Session: `{{sessionId}}`
20
+
21
+ ## Suggested fixes
22
+
23
+ - Add server-side pagination or filtering defaults.
24
+ - Add a `--summary` or `--ids-only` mode that returns a compact representation.
25
+ - Document a recommended page size in the tool's README.
@@ -0,0 +1,26 @@
1
+ name: schema-drift
2
+ title: "{{tool}}: schema contradicts workflow docs"
3
+ priority: MEDIUM
4
+ labels:
5
+ - tooling
6
+ - friction
7
+ - schema-drift
8
+ body: |
9
+ ## Symptom
10
+
11
+ {{description}}
12
+
13
+ ## Context
14
+
15
+ - Tool surface: `{{tool}}`
16
+ - Captured: {{capturedAt}}
17
+ - Session: `{{sessionId}}`
18
+ - Source: `{{source}}`
19
+
20
+ ## Drift detected
21
+
22
+ The schema returned (or accepted) by `{{tool}}` does not match what the workflow docs / call-site expects. This is the class of bug that quietly corrupts downstream code paths until something explodes far from the cause.
23
+
24
+ ## Suggested next step
25
+
26
+ Pin both sides: snapshot the actual schema (`--help`, sample response, OpenAPI dump) and the expected schema (workflow contract / type declaration). File the diff so the next contributor finds it before they hit the same wall.
@@ -0,0 +1,34 @@
1
+ name: tool-error
2
+ title: "{{tool}}: {{title}}"
3
+ priority: MEDIUM
4
+ labels:
5
+ - tooling
6
+ - friction
7
+ - tool-error
8
+ body: |
9
+ ## Symptom
10
+
11
+ {{description}}
12
+
13
+ ## Context
14
+
15
+ - Tool surface: `{{tool}}`
16
+ - Captured: {{capturedAt}}
17
+ - Session: `{{sessionId}}`
18
+ - Source: `{{source}}`
19
+
20
+ ## Expected
21
+
22
+ Behaviour documented for `{{tool}}`.
23
+
24
+ ## Actual
25
+
26
+ See symptom above.
27
+
28
+ ## Reproduction
29
+
30
+ _Fill in the exact invocation that produced this. Friction-log capture is the receipt, not the repro._
31
+
32
+ ## Suggested next step
33
+
34
+ Triage whether this is a documentation gap (update docs), a real defect (fix tool), or expected behaviour the workflow needs to adapt to.
@@ -0,0 +1,26 @@
1
+ name: tool-missing-capability
2
+ title: "{{tool}}: missing capability for workflow"
3
+ priority: MEDIUM
4
+ labels:
5
+ - tooling
6
+ - friction
7
+ - tool-missing-capability
8
+ body: |
9
+ ## Symptom
10
+
11
+ {{description}}
12
+
13
+ ## What the workflow needs
14
+
15
+ `{{tool}}` is the natural place for this capability, but the surface does not expose it. The workflow currently has to either route around the gap, file a manual ticket, or stop early.
16
+
17
+ ## Context
18
+
19
+ - Tool surface: `{{tool}}`
20
+ - Captured: {{capturedAt}}
21
+ - Session: `{{sessionId}}`
22
+ - Source: `{{source}}`
23
+
24
+ ## Suggested next step
25
+
26
+ Decide whether to: (a) request the capability upstream, (b) ship a sibling tool that fills the gap, or (c) document the workaround so future runs do not pay this cost again.
@@ -0,0 +1,26 @@
1
+ name: workflow-friction
2
+ title: "{{title}}"
3
+ priority: MEDIUM
4
+ labels:
5
+ - tooling
6
+ - friction
7
+ body: |
8
+ ## What happened
9
+
10
+ {{description}}
11
+
12
+ ## Context
13
+
14
+ - Tool surface: `{{tool}}`
15
+ - Category: `{{category}}`
16
+ - Captured: {{capturedAt}}
17
+ - Session: `{{sessionId}}`
18
+ - Source: `{{source}}`
19
+
20
+ ## Why this matters
21
+
22
+ _Describe how this friction slows down or breaks the workflow._
23
+
24
+ ## Possible next steps
25
+
26
+ _List the smallest change that would remove the friction._
@@ -0,0 +1,5 @@
1
+ import type { Friction, RenderedTemplate, Template } from "./types.js";
2
+ export declare function loadTemplate(name: string): Template;
3
+ export declare function listTemplates(): string[];
4
+ export declare function pickTemplateForCategory(category: string | null | undefined): string;
5
+ export declare function render(template: Template, friction: Friction, extra?: Record<string, string>): RenderedTemplate;
@@ -0,0 +1,88 @@
1
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parse as parseYaml } from "yaml";
5
+ const DEFAULT_TEMPLATE_NAME = "workflow-friction";
6
+ function templatesDir() {
7
+ const here = dirname(fileURLToPath(import.meta.url));
8
+ const candidates = [
9
+ resolve(here, "..", "templates"),
10
+ resolve(here, "..", "..", "templates"),
11
+ ];
12
+ for (const c of candidates) {
13
+ if (existsSync(c))
14
+ return c;
15
+ }
16
+ throw new Error(`friction-log: cannot locate bundled templates (looked in ${candidates.join(", ")})`);
17
+ }
18
+ export function loadTemplate(name) {
19
+ const path = join(templatesDir(), `${name}.yml`);
20
+ if (!existsSync(path)) {
21
+ throw new Error(`friction-log: unknown template "${name}" (no file at ${path})`);
22
+ }
23
+ const raw = readFileSync(path, "utf8");
24
+ const parsed = parseYaml(raw);
25
+ if (!parsed || typeof parsed !== "object") {
26
+ throw new Error(`friction-log: template "${name}" is not a valid YAML object`);
27
+ }
28
+ const title = typeof parsed.title === "string" ? parsed.title : "";
29
+ const body = typeof parsed.body === "string" ? parsed.body : "";
30
+ if (!title || !body) {
31
+ throw new Error(`friction-log: template "${name}" must define both title and body`);
32
+ }
33
+ const labels = Array.isArray(parsed.labels)
34
+ ? parsed.labels.filter((l) => typeof l === "string")
35
+ : [];
36
+ const priority = parsed.priority === "LOW" ||
37
+ parsed.priority === "MEDIUM" ||
38
+ parsed.priority === "HIGH" ||
39
+ parsed.priority === "CRITICAL"
40
+ ? parsed.priority
41
+ : "MEDIUM";
42
+ return {
43
+ name,
44
+ title,
45
+ body,
46
+ labels,
47
+ priority,
48
+ metadata: parsed.metadata,
49
+ };
50
+ }
51
+ export function listTemplates() {
52
+ return readdirSync(templatesDir())
53
+ .filter((f) => f.endsWith(".yml"))
54
+ .map((f) => f.replace(/\.yml$/, ""))
55
+ .sort();
56
+ }
57
+ export function pickTemplateForCategory(category) {
58
+ if (!category)
59
+ return DEFAULT_TEMPLATE_NAME;
60
+ return listTemplates().includes(category) ? category : DEFAULT_TEMPLATE_NAME;
61
+ }
62
+ export function render(template, friction, extra = {}) {
63
+ const vars = {
64
+ id: String(friction.id),
65
+ title: friction.title,
66
+ description: friction.description ?? "",
67
+ tool: friction.toolSurface ?? "",
68
+ category: friction.category ?? "",
69
+ severity: friction.severity ?? "",
70
+ capturedAt: friction.capturedAt,
71
+ sessionId: friction.sessionId ?? "",
72
+ source: friction.source,
73
+ ...extra,
74
+ };
75
+ return {
76
+ title: substitute(template.title, vars),
77
+ body: substitute(template.body, vars),
78
+ labels: template.labels,
79
+ priority: template.priority,
80
+ metadata: template.metadata,
81
+ };
82
+ }
83
+ function substitute(input, vars) {
84
+ return input.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g, (_match, key) => {
85
+ return vars[key] ?? "";
86
+ });
87
+ }
88
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"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"}