@tyvm/knowhow 0.0.128 → 0.0.130

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 (183) hide show
  1. package/package.json +5 -4
  2. package/scripts/cache-miss-debug.ts +365 -0
  3. package/src/agents/base/base.ts +159 -59
  4. package/src/agents/tools/execCommand.ts +26 -6
  5. package/src/agents/tools/index.ts +1 -0
  6. package/src/agents/tools/list.ts +2 -0
  7. package/src/agents/tools/minimal/callTool.ts +45 -0
  8. package/src/agents/tools/minimal/definitions.ts +50 -0
  9. package/src/agents/tools/minimal/index.ts +3 -0
  10. package/src/agents/tools/minimal/inspectTools.ts +27 -0
  11. package/src/agents/tools/skills.ts +79 -0
  12. package/src/chat/modules/AgentModule.ts +111 -9
  13. package/src/cli.ts +4 -0
  14. package/src/clients/anthropic.ts +59 -1
  15. package/src/clients/contextLimits.ts +9 -0
  16. package/src/clients/index.ts +6 -0
  17. package/src/clients/meta.ts +18 -0
  18. package/src/clients/openai.ts +28 -5
  19. package/src/clients/pricing/anthropic.ts +13 -0
  20. package/src/clients/pricing/fireworks.ts +18 -12
  21. package/src/clients/pricing/index.ts +1 -0
  22. package/src/clients/pricing/meta.ts +12 -0
  23. package/src/clients/pricing/openai.ts +16 -0
  24. package/src/clients/pricing/xai.ts +3 -0
  25. package/src/clients/types.ts +27 -4
  26. package/src/clients/xai.ts +2 -2
  27. package/src/commands/agent.ts +19 -0
  28. package/src/commands/behaviors.ts +47 -0
  29. package/src/commands/services.ts +7 -3
  30. package/src/commands/skills.ts +71 -0
  31. package/src/index.ts +2 -0
  32. package/src/plugins/PluginBase.ts +0 -2
  33. package/src/plugins/SkillsPlugin.ts +11 -124
  34. package/src/plugins/language.ts +0 -1
  35. package/src/plugins/plugins.ts +24 -13
  36. package/src/plugins/types.ts +8 -2
  37. package/src/processors/MinimalToolsMessageProcessor.ts +113 -0
  38. package/src/processors/TokenCompressor.ts +21 -4
  39. package/src/processors/index.ts +7 -1
  40. package/src/services/AgentSyncFs.ts +49 -0
  41. package/src/services/AgentSyncKnowhowWeb.ts +1 -1
  42. package/src/services/BehaviorsService.ts +432 -0
  43. package/src/services/KnowhowClient.ts +62 -1
  44. package/src/services/LazyToolsService.ts +12 -0
  45. package/src/services/MinimalToolsService.ts +167 -0
  46. package/src/services/SkillsService.ts +622 -0
  47. package/src/services/Tools.ts +11 -0
  48. package/src/services/index.ts +9 -2
  49. package/src/services/modules/types.ts +2 -0
  50. package/src/worker.ts +7 -0
  51. package/tests/processors/TokenCompressor.test.ts +45 -0
  52. package/tests/unit/services/MinimalToolsService.test.ts +424 -0
  53. package/ts_build/package.json +5 -4
  54. package/ts_build/src/agents/base/base.d.ts +13 -2
  55. package/ts_build/src/agents/base/base.js +91 -35
  56. package/ts_build/src/agents/base/base.js.map +1 -1
  57. package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
  58. package/ts_build/src/agents/tools/execCommand.js +18 -5
  59. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  60. package/ts_build/src/agents/tools/index.d.ts +1 -0
  61. package/ts_build/src/agents/tools/index.js +1 -0
  62. package/ts_build/src/agents/tools/index.js.map +1 -1
  63. package/ts_build/src/agents/tools/list.js +2 -0
  64. package/ts_build/src/agents/tools/list.js.map +1 -1
  65. package/ts_build/src/agents/tools/minimal/callTool.d.ts +5 -0
  66. package/ts_build/src/agents/tools/minimal/callTool.js +28 -0
  67. package/ts_build/src/agents/tools/minimal/callTool.js.map +1 -0
  68. package/ts_build/src/agents/tools/minimal/definitions.d.ts +4 -0
  69. package/ts_build/src/agents/tools/minimal/definitions.js +46 -0
  70. package/ts_build/src/agents/tools/minimal/definitions.js.map +1 -0
  71. package/ts_build/src/agents/tools/minimal/index.d.ts +3 -0
  72. package/ts_build/src/agents/tools/minimal/index.js +20 -0
  73. package/ts_build/src/agents/tools/minimal/index.js.map +1 -0
  74. package/ts_build/src/agents/tools/minimal/inspectTools.d.ts +2 -0
  75. package/ts_build/src/agents/tools/minimal/inspectTools.js +15 -0
  76. package/ts_build/src/agents/tools/minimal/inspectTools.js.map +1 -0
  77. package/ts_build/src/agents/tools/skills.d.ts +33 -0
  78. package/ts_build/src/agents/tools/skills.js +62 -0
  79. package/ts_build/src/agents/tools/skills.js.map +1 -0
  80. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
  81. package/ts_build/src/chat/modules/AgentModule.js +77 -2
  82. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  83. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
  84. package/ts_build/src/cli.js +4 -0
  85. package/ts_build/src/cli.js.map +1 -1
  86. package/ts_build/src/clients/anthropic.d.ts +2 -0
  87. package/ts_build/src/clients/anthropic.js +35 -0
  88. package/ts_build/src/clients/anthropic.js.map +1 -1
  89. package/ts_build/src/clients/contextLimits.js +8 -0
  90. package/ts_build/src/clients/contextLimits.js.map +1 -1
  91. package/ts_build/src/clients/index.d.ts +1 -0
  92. package/ts_build/src/clients/index.js +6 -0
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/meta.d.ts +4 -0
  95. package/ts_build/src/clients/meta.js +15 -0
  96. package/ts_build/src/clients/meta.js.map +1 -0
  97. package/ts_build/src/clients/openai.d.ts +1 -0
  98. package/ts_build/src/clients/openai.js +25 -5
  99. package/ts_build/src/clients/openai.js.map +1 -1
  100. package/ts_build/src/clients/pricing/anthropic.d.ts +4 -0
  101. package/ts_build/src/clients/pricing/anthropic.js +12 -0
  102. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/pricing/fireworks.d.ts +2 -5
  104. package/ts_build/src/clients/pricing/fireworks.js +13 -11
  105. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  106. package/ts_build/src/clients/pricing/index.d.ts +1 -0
  107. package/ts_build/src/clients/pricing/index.js +5 -2
  108. package/ts_build/src/clients/pricing/index.js.map +1 -1
  109. package/ts_build/src/clients/pricing/meta.d.ts +6 -0
  110. package/ts_build/src/clients/pricing/meta.js +8 -0
  111. package/ts_build/src/clients/pricing/meta.js.map +1 -0
  112. package/ts_build/src/clients/pricing/openai.d.ts +3 -0
  113. package/ts_build/src/clients/pricing/openai.js +15 -0
  114. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  115. package/ts_build/src/clients/pricing/xai.d.ts +1 -0
  116. package/ts_build/src/clients/pricing/xai.js +3 -0
  117. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  118. package/ts_build/src/clients/types.d.ts +6 -1
  119. package/ts_build/src/clients/xai.js +1 -1
  120. package/ts_build/src/clients/xai.js.map +1 -1
  121. package/ts_build/src/commands/agent.js +16 -0
  122. package/ts_build/src/commands/agent.js.map +1 -1
  123. package/ts_build/src/commands/behaviors.d.ts +2 -0
  124. package/ts_build/src/commands/behaviors.js +43 -0
  125. package/ts_build/src/commands/behaviors.js.map +1 -0
  126. package/ts_build/src/commands/services.js +4 -2
  127. package/ts_build/src/commands/services.js.map +1 -1
  128. package/ts_build/src/commands/skills.d.ts +2 -0
  129. package/ts_build/src/commands/skills.js +61 -0
  130. package/ts_build/src/commands/skills.js.map +1 -0
  131. package/ts_build/src/index.d.ts +2 -0
  132. package/ts_build/src/index.js +5 -1
  133. package/ts_build/src/index.js.map +1 -1
  134. package/ts_build/src/plugins/PluginBase.d.ts +0 -2
  135. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  136. package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
  137. package/ts_build/src/plugins/SkillsPlugin.js +6 -135
  138. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
  139. package/ts_build/src/plugins/language.js.map +1 -1
  140. package/ts_build/src/plugins/plugins.d.ts +4 -1
  141. package/ts_build/src/plugins/plugins.js +17 -11
  142. package/ts_build/src/plugins/plugins.js.map +1 -1
  143. package/ts_build/src/plugins/types.d.ts +3 -2
  144. package/ts_build/src/processors/MinimalToolsMessageProcessor.d.ts +7 -0
  145. package/ts_build/src/processors/MinimalToolsMessageProcessor.js +61 -0
  146. package/ts_build/src/processors/MinimalToolsMessageProcessor.js.map +1 -0
  147. package/ts_build/src/processors/TokenCompressor.js +5 -2
  148. package/ts_build/src/processors/TokenCompressor.js.map +1 -1
  149. package/ts_build/src/processors/index.d.ts +2 -1
  150. package/ts_build/src/processors/index.js +3 -1
  151. package/ts_build/src/processors/index.js.map +1 -1
  152. package/ts_build/src/services/AgentSyncFs.d.ts +2 -0
  153. package/ts_build/src/services/AgentSyncFs.js +30 -0
  154. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  155. package/ts_build/src/services/AgentSyncKnowhowWeb.js +1 -1
  156. package/ts_build/src/services/BehaviorsService.d.ts +46 -0
  157. package/ts_build/src/services/BehaviorsService.js +358 -0
  158. package/ts_build/src/services/BehaviorsService.js.map +1 -0
  159. package/ts_build/src/services/KnowhowClient.d.ts +9 -1
  160. package/ts_build/src/services/KnowhowClient.js +20 -0
  161. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  162. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  163. package/ts_build/src/services/MinimalToolsService.d.ts +14 -0
  164. package/ts_build/src/services/MinimalToolsService.js +80 -0
  165. package/ts_build/src/services/MinimalToolsService.js.map +1 -0
  166. package/ts_build/src/services/SkillsService.d.ts +53 -0
  167. package/ts_build/src/services/SkillsService.js +478 -0
  168. package/ts_build/src/services/SkillsService.js.map +1 -0
  169. package/ts_build/src/services/Tools.d.ts +2 -0
  170. package/ts_build/src/services/Tools.js.map +1 -1
  171. package/ts_build/src/services/index.d.ts +4 -0
  172. package/ts_build/src/services/index.js +9 -2
  173. package/ts_build/src/services/index.js.map +1 -1
  174. package/ts_build/src/services/modules/index.d.ts +4 -2
  175. package/ts_build/src/services/modules/types.d.ts +2 -0
  176. package/ts_build/src/types.d.ts +8 -0
  177. package/ts_build/src/worker.js +5 -0
  178. package/ts_build/src/worker.js.map +1 -1
  179. package/ts_build/tests/processors/TokenCompressor.test.js +27 -0
  180. package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
  181. package/ts_build/tests/unit/services/MinimalToolsService.test.d.ts +1 -0
  182. package/ts_build/tests/unit/services/MinimalToolsService.test.js +320 -0
  183. package/ts_build/tests/unit/services/MinimalToolsService.test.js.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.128",
3
+ "version": "0.0.130",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -22,6 +22,7 @@
22
22
  "lint:deps": "depcheck --config=.depcheckrc",
23
23
  "lint:all": "npm run lint && npm run lint:deps",
24
24
  "check:model-pricing": "ts-node scripts/check-model-pricing.ts",
25
+ "debug:cache-miss": "ts-node scripts/cache-miss-debug.ts",
25
26
  "version:nightly": "bash scripts/publish.sh version:nightly",
26
27
  "version:dev": "bash scripts/publish.sh version:dev",
27
28
  "publish:nightly": "bash scripts/publish.sh nightly",
@@ -34,7 +35,6 @@
34
35
  "license": "MIT",
35
36
  "devDependencies": {
36
37
  "@babel/preset-typescript": "^7.23.3",
37
- "typescript": "^5.0.0",
38
38
  "@types/diff": "^5.2.1",
39
39
  "@types/express": "^4.17.13",
40
40
  "@types/jest": "^29.5.13",
@@ -46,10 +46,11 @@
46
46
  "ts-jest": "^29.1.1",
47
47
  "ts-node": "^10.7.0",
48
48
  "tslint": "^6.1.3",
49
- "tslint-config-prettier": "^1.18.0"
49
+ "tslint-config-prettier": "^1.18.0",
50
+ "typescript": "^5.0.0"
50
51
  },
51
52
  "dependencies": {
52
- "@anthropic-ai/sdk": "^0.39.0",
53
+ "@anthropic-ai/sdk": "^0.111.0",
53
54
  "@google/genai": "^0.14.1",
54
55
  "@inquirer/editor": "^4.2.18",
55
56
  "@modelcontextprotocol/sdk": "^1.13.3",
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * cache-miss-debug.ts
4
+ *
5
+ * Debug helper for analyzing usage.json files written by AgentSyncFs
6
+ * (`.knowhow/processes/agents/<taskId>/usage.json`).
7
+ *
8
+ * Each entry in usage.json corresponds to one AI completion call and looks
9
+ * like:
10
+ * {
11
+ * timestamp, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens,
12
+ * totalInputTokens, totalOutputTokens, totalCacheReadTokens, totalCacheWriteTokens,
13
+ * messages: [...] // exact message chain sent to the model for that call
14
+ * }
15
+ *
16
+ * This script helps answer: "did a particular call miss the prompt cache?"
17
+ * and "what changed in the message chain between call N and call N+1 that
18
+ * broke the cache prefix?"
19
+ *
20
+ * Usage:
21
+ * npx ts-node scripts/cache-miss-debug.ts <path-to-usage.json> [options]
22
+ *
23
+ * Options:
24
+ * --summary Print a per-call table of token/cache stats + hit/miss verdict (default if no other option given)
25
+ * --diff <a> <b> Print a diff of the `messages` array between call index a and call index b (0-based)
26
+ * --dump <n> Dump the full `messages` array for call index n as JSON
27
+ * --dump-message <n> <m> Dump a single message (index m) from call n's messages array
28
+ * --json Output the summary as JSON instead of a table
29
+ *
30
+ * Examples:
31
+ * npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json
32
+ * npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --diff 1 2
33
+ * npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --dump 2
34
+ */
35
+
36
+ import * as fs from "fs";
37
+ import * as path from "path";
38
+ import { diffJson } from "diff";
39
+
40
+ interface UsageEntry {
41
+ timestamp?: string;
42
+ inputTokens: number;
43
+ outputTokens: number;
44
+ cacheReadTokens: number;
45
+ cacheWriteTokens: number;
46
+ totalInputTokens?: number;
47
+ totalOutputTokens?: number;
48
+ totalCacheReadTokens?: number;
49
+ totalCacheWriteTokens?: number;
50
+ messages?: any[];
51
+ [key: string]: any;
52
+ }
53
+
54
+ interface Verdict {
55
+ index: number;
56
+ timestamp: string;
57
+ inputTokens: number;
58
+ cacheReadTokens: number;
59
+ cacheWriteTokens: number;
60
+ cumulativeWritesBefore: number;
61
+ status: "cold-start" | "hit" | "partial-hit" | "miss";
62
+ note: string;
63
+ }
64
+
65
+ function loadUsage(filePath: string): UsageEntry[] {
66
+ const resolved = path.resolve(filePath);
67
+ if (!fs.existsSync(resolved)) {
68
+ throw new Error(`File not found: ${resolved}`);
69
+ }
70
+ const raw = fs.readFileSync(resolved, "utf8");
71
+ const parsed = JSON.parse(raw);
72
+ if (!Array.isArray(parsed)) {
73
+ throw new Error(
74
+ `Expected usage.json to contain a JSON array of call entries, got: ${typeof parsed}`
75
+ );
76
+ }
77
+ return parsed as UsageEntry[];
78
+ }
79
+
80
+ /**
81
+ * Compute a hit/miss verdict for each call by comparing cacheReadTokens
82
+ * against the cumulative cacheWriteTokens available from all prior calls.
83
+ *
84
+ * - cold-start: first call, no cache could exist yet (cacheReadTokens ~ 0 expected)
85
+ * - hit: cacheReadTokens roughly matches (or exceeds a large portion of) prior cumulative writes
86
+ * - partial-hit: cacheReadTokens is nonzero but noticeably less than what was available
87
+ * - miss: cacheReadTokens is 0 (or near 0) despite prior writes being available
88
+ */
89
+ function computeVerdicts(entries: UsageEntry[]): Verdict[] {
90
+ const verdicts: Verdict[] = [];
91
+ let cumulativeWrites = 0;
92
+
93
+ entries.forEach((entry, index) => {
94
+ const cacheReadTokens = entry.cacheReadTokens ?? 0;
95
+ const cacheWriteTokens = entry.cacheWriteTokens ?? 0;
96
+ const cumulativeWritesBefore = cumulativeWrites;
97
+
98
+ let status: Verdict["status"];
99
+ let note = "";
100
+
101
+ if (index === 0) {
102
+ status = "cold-start";
103
+ note = "First call — no prior cache expected.";
104
+ } else if (cumulativeWritesBefore === 0) {
105
+ status = cacheReadTokens > 0 ? "hit" : "cold-start";
106
+ note = "No prior writes recorded to compare against.";
107
+ } else if (cacheReadTokens === 0) {
108
+ status = "miss";
109
+ note = `Expected up to ${cumulativeWritesBefore} cached tokens available, got 0 reads — full cache miss.`;
110
+ } else if (cacheReadTokens < cumulativeWritesBefore * 0.5) {
111
+ status = "partial-hit";
112
+ note = `Read ${cacheReadTokens} of up to ${cumulativeWritesBefore} available cached tokens — partial miss, prefix likely diverged partway through.`;
113
+ } else {
114
+ status = "hit";
115
+ note = `Read ${cacheReadTokens} tokens, consistent with prior cumulative writes (${cumulativeWritesBefore}).`;
116
+ }
117
+
118
+ verdicts.push({
119
+ index,
120
+ timestamp: entry.timestamp || "unknown",
121
+ inputTokens: entry.inputTokens ?? 0,
122
+ cacheReadTokens,
123
+ cacheWriteTokens,
124
+ cumulativeWritesBefore,
125
+ status,
126
+ note,
127
+ });
128
+
129
+ cumulativeWrites += cacheWriteTokens;
130
+ });
131
+
132
+ return verdicts;
133
+ }
134
+
135
+
136
+ function fmtNum(n: number): string {
137
+ return n.toLocaleString();
138
+ }
139
+
140
+ function statusEmoji(status: Verdict["status"]): string {
141
+ switch (status) {
142
+ case "cold-start":
143
+ return "🧊";
144
+ case "hit":
145
+ return "✅";
146
+ case "partial-hit":
147
+ return "🟡";
148
+ case "miss":
149
+ return "🚨";
150
+ }
151
+ }
152
+
153
+ function printSummaryTable(entries: UsageEntry[], verdicts: Verdict[]) {
154
+ const header = [
155
+ "#",
156
+ "Time",
157
+ "Input",
158
+ "Output",
159
+ "CacheRead",
160
+ "CacheWrite",
161
+ "Status",
162
+ ];
163
+ const rows = verdicts.map((v, i) => [
164
+ String(v.index),
165
+ v.timestamp,
166
+ fmtNum(v.inputTokens),
167
+ fmtNum(entries[i].outputTokens ?? 0),
168
+ fmtNum(v.cacheReadTokens),
169
+ fmtNum(v.cacheWriteTokens),
170
+ `${statusEmoji(v.status)} ${v.status}`,
171
+ ]);
172
+
173
+ const widths = header.map((h, i) =>
174
+ Math.max(h.length, ...rows.map((r) => r[i].length))
175
+ );
176
+
177
+ const printRow = (cols: string[]) =>
178
+ console.log(
179
+ "| " + cols.map((c, i) => c.padEnd(widths[i])).join(" | ") + " |"
180
+ );
181
+
182
+ printRow(header);
183
+ printRow(widths.map((w) => "-".repeat(w)));
184
+ rows.forEach(printRow);
185
+
186
+ console.log("");
187
+ const misses = verdicts.filter((v) => v.status === "miss");
188
+ const partials = verdicts.filter((v) => v.status === "partial-hit");
189
+ if (misses.length === 0 && partials.length === 0) {
190
+ console.log("✅ No cache misses detected — caching looks healthy.");
191
+ } else {
192
+ console.log(
193
+ `🚨 Found ${misses.length} full miss(es) and ${partials.length} partial-hit(s):`
194
+ );
195
+ [...misses, ...partials].forEach((v) => {
196
+ console.log(` - Call #${v.index} (${v.timestamp}): ${v.note}`);
197
+ });
198
+ console.log(
199
+ "\nTip: use --diff <a> <b> to compare the message chains between two calls, e.g.:"
200
+ );
201
+ [...misses, ...partials].forEach((v) => {
202
+ if (v.index > 0) {
203
+ console.log(
204
+ ` npx ts-node scripts/cache-miss-debug.ts <file> --diff ${
205
+ v.index - 1
206
+ } ${v.index}`
207
+ );
208
+ }
209
+ });
210
+ }
211
+ }
212
+
213
+ function printJsonSummary(verdicts: Verdict[]) {
214
+ console.log(JSON.stringify(verdicts, null, 2));
215
+ }
216
+
217
+ /**
218
+ * Diff the messages array of two calls. Uses a structural JSON diff so
219
+ * additions/removals/reordering of individual messages (and fields within
220
+ * them, like tool_call ids or content) are all visible.
221
+ */
222
+ function printMessagesDiff(entries: UsageEntry[], aIdx: number, bIdx: number) {
223
+ if (!entries[aIdx] || !entries[bIdx]) {
224
+ throw new Error(
225
+ `Invalid indices: file has ${entries.length} entries (valid range 0-${
226
+ entries.length - 1
227
+ })`
228
+ );
229
+ }
230
+
231
+ const a = entries[aIdx].messages ?? [];
232
+ const b = entries[bIdx].messages ?? [];
233
+
234
+ console.log(
235
+ `\nDiffing messages: call #${aIdx} (${entries[aIdx].timestamp}) -> call #${bIdx} (${entries[bIdx].timestamp})`
236
+ );
237
+ console.log(` call #${aIdx}: ${a.length} messages`);
238
+ console.log(` call #${bIdx}: ${b.length} messages\n`);
239
+
240
+ const diff = diffJson(a, b);
241
+ diff.forEach((part) => {
242
+ const color = part.added ? "\x1b[32m" : part.removed ? "\x1b[31m" : "";
243
+ const reset = part.added || part.removed ? "\x1b[0m" : "";
244
+ const prefix = part.added ? "+ " : part.removed ? "- " : " ";
245
+ const lines = part.value.split("\n");
246
+ lines.forEach((line, i) => {
247
+ if (line === "" && i === lines.length - 1) return; // trailing empty line from split
248
+ console.log(`${color}${prefix}${line}${reset}`);
249
+ });
250
+ });
251
+ }
252
+
253
+ function dumpMessages(entries: UsageEntry[], idx: number) {
254
+ if (!entries[idx]) {
255
+ throw new Error(
256
+ `Invalid index ${idx}: file has ${entries.length} entries (valid range 0-${
257
+ entries.length - 1
258
+ })`
259
+ );
260
+ }
261
+ console.log(JSON.stringify(entries[idx].messages ?? [], null, 2));
262
+ }
263
+
264
+ function dumpMessage(entries: UsageEntry[], callIdx: number, msgIdx: number) {
265
+ if (!entries[callIdx]) {
266
+ throw new Error(
267
+ `Invalid call index ${callIdx}: file has ${entries.length} entries`
268
+ );
269
+ }
270
+ const messages = entries[callIdx].messages ?? [];
271
+ if (!messages[msgIdx]) {
272
+ throw new Error(
273
+ `Invalid message index ${msgIdx}: call #${callIdx} has ${messages.length} messages`
274
+ );
275
+ }
276
+ console.log(JSON.stringify(messages[msgIdx], null, 2));
277
+ }
278
+
279
+
280
+ function printUsage() {
281
+ console.log(`
282
+ cache-miss-debug.ts — inspect usage.json files for prompt-cache issues
283
+
284
+ Usage:
285
+ npx ts-node scripts/cache-miss-debug.ts <path-to-usage.json> [options]
286
+
287
+ Options:
288
+ --summary Print a per-call table of token/cache stats + hit/miss verdict (default)
289
+ --json Output the summary as JSON instead of a table
290
+ --diff <a> <b> Diff the messages array between call index a and call index b (0-based)
291
+ --dump <n> Dump the full messages array for call index n as JSON
292
+ --dump-message <n> <m> Dump a single message (index m) from call n's messages array
293
+
294
+ Examples:
295
+ npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json
296
+ npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --diff 1 2
297
+ npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --dump 2
298
+ npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --dump-message 2 0
299
+ `);
300
+ }
301
+
302
+ function main() {
303
+ const args = process.argv.slice(2);
304
+ const filePath = args.find((a) => !a.startsWith("--"));
305
+
306
+ if (!filePath || args.includes("--help") || args.includes("-h")) {
307
+ printUsage();
308
+ process.exit(filePath ? 0 : 1);
309
+ }
310
+
311
+ const entries = loadUsage(filePath);
312
+
313
+ if (entries.length === 0) {
314
+ console.log("usage.json contains no entries.");
315
+ return;
316
+ }
317
+
318
+ const diffFlagIdx = args.indexOf("--diff");
319
+ const dumpFlagIdx = args.indexOf("--dump");
320
+ const dumpMessageFlagIdx = args.indexOf("--dump-message");
321
+
322
+ if (diffFlagIdx !== -1) {
323
+ const a = parseInt(args[diffFlagIdx + 1], 10);
324
+ const b = parseInt(args[diffFlagIdx + 2], 10);
325
+ if (Number.isNaN(a) || Number.isNaN(b)) {
326
+ console.error("--diff requires two numeric indices, e.g. --diff 1 2");
327
+ process.exit(1);
328
+ }
329
+ printMessagesDiff(entries, a, b);
330
+ return;
331
+ }
332
+
333
+ if (dumpMessageFlagIdx !== -1) {
334
+ const callIdx = parseInt(args[dumpMessageFlagIdx + 1], 10);
335
+ const msgIdx = parseInt(args[dumpMessageFlagIdx + 2], 10);
336
+ if (Number.isNaN(callIdx) || Number.isNaN(msgIdx)) {
337
+ console.error(
338
+ "--dump-message requires two numeric indices, e.g. --dump-message 2 0"
339
+ );
340
+ process.exit(1);
341
+ }
342
+ dumpMessage(entries, callIdx, msgIdx);
343
+ return;
344
+ }
345
+
346
+ if (dumpFlagIdx !== -1) {
347
+ const idx = parseInt(args[dumpFlagIdx + 1], 10);
348
+ if (Number.isNaN(idx)) {
349
+ console.error("--dump requires a numeric index, e.g. --dump 2");
350
+ process.exit(1);
351
+ }
352
+ dumpMessages(entries, idx);
353
+ return;
354
+ }
355
+
356
+ // Default: summary
357
+ const verdicts = computeVerdicts(entries);
358
+ if (args.includes("--json")) {
359
+ printJsonSummary(verdicts);
360
+ } else {
361
+ printSummaryTable(entries, verdicts);
362
+ }
363
+ }
364
+
365
+ main();