vibe-roast 0.98.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 (164) hide show
  1. package/README.md +418 -0
  2. package/assests/README.md +43 -0
  3. package/assests/badges/.gitkeep +0 -0
  4. package/assests/badges/01-builder/builder-badge.png +0 -0
  5. package/assests/badges/01-builder/builder-badge.svg +4 -0
  6. package/assests/badges/02-debugger/debugger-badge.png +0 -0
  7. package/assests/badges/02-debugger/debugger-badge.svg +4 -0
  8. package/assests/badges/03-architect/architect-badge.png +0 -0
  9. package/assests/badges/03-architect/architect-badge.svg +4 -0
  10. package/assests/badges/04-codex-believer/codex-believer-badge.png +0 -0
  11. package/assests/badges/04-codex-believer/codex-believer-badge.svg +4 -0
  12. package/assests/badges/05-prompt-priest/prompt-priest-badge.png +0 -0
  13. package/assests/badges/05-prompt-priest/prompt-priest-badge.svg +4 -0
  14. package/assests/badges/06-tab-hoarder/tab-hoarder-badge.png +0 -0
  15. package/assests/badges/06-tab-hoarder/tab-hoarder-badge.svg +4 -0
  16. package/assests/badges/07-context-maxxer/context-maxxer-badge.png +0 -0
  17. package/assests/badges/07-context-maxxer/context-maxxer-badge.svg +4 -0
  18. package/assests/badges/08-yolo-shipper/yolo-shipper-badge.png +0 -0
  19. package/assests/badges/08-yolo-shipper/yolo-shipper-badge.svg +4 -0
  20. package/assests/banners/.gitkeep +0 -0
  21. package/assests/banners/home-hero.png +0 -0
  22. package/assests/banners/home-hero.svg +3 -0
  23. package/assests/banners/result-header.png +0 -0
  24. package/assests/banners/result-header.svg +3 -0
  25. package/assests/banners/share-strip.png +0 -0
  26. package/assests/banners/share-strip.svg +3 -0
  27. package/assests/characters/01-builder/builder-card.sbti-archive.svg +5 -0
  28. package/assests/characters/01-builder/builder-card.svg +3 -0
  29. package/assests/characters/01-builder/builder-character.sbti-archive.svg +3 -0
  30. package/assests/characters/01-builder/builder-character.svg +3 -0
  31. package/assests/characters/01-builder/builder-figure.png +0 -0
  32. package/assests/characters/01-builder/builder-mascot.png +0 -0
  33. package/assests/characters/02-debugger/debugger-card.sbti-archive.svg +5 -0
  34. package/assests/characters/02-debugger/debugger-card.svg +3 -0
  35. package/assests/characters/02-debugger/debugger-character.sbti-archive.svg +3 -0
  36. package/assests/characters/02-debugger/debugger-character.svg +3 -0
  37. package/assests/characters/02-debugger/debugger-figure.png +0 -0
  38. package/assests/characters/02-debugger/debugger-mascot.png +0 -0
  39. package/assests/characters/03-architect/architect-card.sbti-archive.svg +5 -0
  40. package/assests/characters/03-architect/architect-card.svg +3 -0
  41. package/assests/characters/03-architect/architect-character.sbti-archive.svg +3 -0
  42. package/assests/characters/03-architect/architect-character.svg +3 -0
  43. package/assests/characters/03-architect/architect-figure.png +0 -0
  44. package/assests/characters/03-architect/architect-mascot.png +0 -0
  45. package/assests/characters/04-codex-believer/codex-believer-card.sbti-archive.svg +5 -0
  46. package/assests/characters/04-codex-believer/codex-believer-card.svg +3 -0
  47. package/assests/characters/04-codex-believer/codex-believer-character.sbti-archive.svg +3 -0
  48. package/assests/characters/04-codex-believer/codex-believer-character.svg +3 -0
  49. package/assests/characters/04-codex-believer/codex-believer-figure.png +0 -0
  50. package/assests/characters/04-codex-believer/codex-believer-mascot.png +0 -0
  51. package/assests/characters/05-prompt-priest/prompt-priest-card.sbti-archive.svg +5 -0
  52. package/assests/characters/05-prompt-priest/prompt-priest-card.svg +3 -0
  53. package/assests/characters/05-prompt-priest/prompt-priest-character.sbti-archive.svg +3 -0
  54. package/assests/characters/05-prompt-priest/prompt-priest-character.svg +3 -0
  55. package/assests/characters/05-prompt-priest/prompt-priest-figure.png +0 -0
  56. package/assests/characters/05-prompt-priest/prompt-priest-mascot.png +0 -0
  57. package/assests/characters/06-tab-hoarder/tab-hoarder-card.sbti-archive.svg +5 -0
  58. package/assests/characters/06-tab-hoarder/tab-hoarder-card.svg +3 -0
  59. package/assests/characters/06-tab-hoarder/tab-hoarder-character.sbti-archive.svg +3 -0
  60. package/assests/characters/06-tab-hoarder/tab-hoarder-character.svg +3 -0
  61. package/assests/characters/06-tab-hoarder/tab-hoarder-figure.png +0 -0
  62. package/assests/characters/06-tab-hoarder/tab-hoarder-mascot.png +0 -0
  63. package/assests/characters/07-context-maxxer/context-maxxer-card.sbti-archive.svg +5 -0
  64. package/assests/characters/07-context-maxxer/context-maxxer-card.svg +3 -0
  65. package/assests/characters/07-context-maxxer/context-maxxer-character.sbti-archive.svg +3 -0
  66. package/assests/characters/07-context-maxxer/context-maxxer-character.svg +3 -0
  67. package/assests/characters/07-context-maxxer/context-maxxer-figure.png +0 -0
  68. package/assests/characters/07-context-maxxer/context-maxxer-mascot.png +0 -0
  69. package/assests/characters/08-yolo-shipper/yolo-shipper-card.sbti-archive.svg +5 -0
  70. package/assests/characters/08-yolo-shipper/yolo-shipper-card.svg +3 -0
  71. package/assests/characters/08-yolo-shipper/yolo-shipper-character.sbti-archive.svg +3 -0
  72. package/assests/characters/08-yolo-shipper/yolo-shipper-character.svg +3 -0
  73. package/assests/characters/08-yolo-shipper/yolo-shipper-figure.png +0 -0
  74. package/assests/characters/08-yolo-shipper/yolo-shipper-mascot.png +0 -0
  75. package/assests/characters-vibe-types/aosf-strategy-shipper/strategy-shipper-figure.png +0 -0
  76. package/assests/characters-vibe-types/aosx-agent-believer/agent-believer-figure.png +0 -0
  77. package/assests/characters-vibe-types/aovf-systems-architect/systems-architect-figure.png +0 -0
  78. package/assests/characters-vibe-types/aovx-context-cartographer/context-cartographer-figure.png +0 -0
  79. package/assests/characters-vibe-types/apsf-diagram-sprinter/diagram-sprinter-figure.png +0 -0
  80. package/assests/characters-vibe-types/apsx-infinite-planner/infinite-planner-figure.png +0 -0
  81. package/assests/characters-vibe-types/apvf-architect/architect-figure.png +0 -0
  82. package/assests/characters-vibe-types/apvx-prompt-priest/prompt-priest-figure.png +0 -0
  83. package/assests/characters-vibe-types/mosf-yolo-shipper/yolo-shipper-figure.png +0 -0
  84. package/assests/characters-vibe-types/mosx-agent-commander/agent-commander-figure.png +0 -0
  85. package/assests/characters-vibe-types/movf-agent-engineer/agent-engineer-figure.png +0 -0
  86. package/assests/characters-vibe-types/movx-systems-wrangler/systems-wrangler-figure.png +0 -0
  87. package/assests/characters-vibe-types/mpsf-builder/builder-figure.png +0 -0
  88. package/assests/characters-vibe-types/mpsx-tab-hoarder/tab-hoarder-figure.png +0 -0
  89. package/assests/characters-vibe-types/mpvf-debugger/debugger-figure.png +0 -0
  90. package/assests/characters-vibe-types/mpvx-context-maxxer/context-maxxer-figure.png +0 -0
  91. package/assests/live-report.html +1403 -0
  92. package/assests/notes/live-inspect-summary.json +3487 -0
  93. package/assests/scripts/compose-banners.py +148 -0
  94. package/assests/scripts/live-report-boot.js +2 -0
  95. package/assests/scripts/profiles.json +104 -0
  96. package/assests/scripts/qa-faces.mjs +72 -0
  97. package/assests/scripts/roast-extras.js +619 -0
  98. package/assests/scripts/score-engine.js +62 -0
  99. package/assests/scripts/split-badges.py +49 -0
  100. package/assests/scripts/split-reference.py +38 -0
  101. package/assests/scripts/sync-banners.mjs +45 -0
  102. package/assests/scripts/sync-cards.mjs +50 -0
  103. package/assests/scripts/sync-raster.mjs +118 -0
  104. package/assests/scripts/vibe-types.json +22 -0
  105. package/assests/source/design-system.md +50 -0
  106. package/assests/source/prompts/01-builder.md +19 -0
  107. package/assests/source/prompts/02-debugger.md +17 -0
  108. package/assests/source/prompts/03-architect.md +16 -0
  109. package/assests/source/prompts/04-codex-believer.md +13 -0
  110. package/assests/source/prompts/05-prompt-priest.md +13 -0
  111. package/assests/source/prompts/06-tab-hoarder.md +13 -0
  112. package/assests/source/prompts/07-context-maxxer.md +13 -0
  113. package/assests/source/prompts/08-yolo-shipper.md +13 -0
  114. package/assests/source/references/sbti-style-reference-board.png +0 -0
  115. package/assests/source/references/vibe-badges-v6-board.png +0 -0
  116. package/assests/source/references/vibe-mascots-v4-reference.png +0 -0
  117. package/assests/source/references/vibe-profiles-v5-board-a.png +0 -0
  118. package/assests/source/references/vibe-profiles-v5-board-b.png +0 -0
  119. package/assests/source/vibe-types-visual-brief.md +38 -0
  120. package/bin/hook.js +121 -0
  121. package/bin/vibe-roast.js +98 -0
  122. package/bin/vibe-wrapper.js +4 -0
  123. package/dashboard/dist/assets/index-DlBycTcO.css +1 -0
  124. package/dashboard/dist/assets/index-t8m64Hm9.js +168 -0
  125. package/dashboard/dist/index.html +32 -0
  126. package/package.json +58 -0
  127. package/src/extract/environment.js +160 -0
  128. package/src/extract/phrase-stats.js +684 -0
  129. package/src/extract/prompt-analysis.js +368 -0
  130. package/src/extract/text.js +30 -0
  131. package/src/hooks/install.js +184 -0
  132. package/src/inspect.js +261 -0
  133. package/src/lib/activity-metrics.js +238 -0
  134. package/src/lib/agent-score.js +187 -0
  135. package/src/lib/ai-roast.js +446 -0
  136. package/src/lib/dates.js +43 -0
  137. package/src/lib/hashtags.js +150 -0
  138. package/src/lib/jsonl.js +48 -0
  139. package/src/lib/load-env.js +59 -0
  140. package/src/lib/roast-evidence.js +186 -0
  141. package/src/lib/roast-snapshot.js +58 -0
  142. package/src/server.js +644 -0
  143. package/src/sources/aider.js +150 -0
  144. package/src/sources/amazonq.js +113 -0
  145. package/src/sources/antigravity.js +147 -0
  146. package/src/sources/claude.js +262 -0
  147. package/src/sources/cline.js +11 -0
  148. package/src/sources/codex.js +222 -0
  149. package/src/sources/common.js +133 -0
  150. package/src/sources/continue.js +67 -0
  151. package/src/sources/copilot.js +170 -0
  152. package/src/sources/cursor.js +238 -0
  153. package/src/sources/gemini.js +75 -0
  154. package/src/sources/index.js +61 -0
  155. package/src/sources/roo.js +11 -0
  156. package/src/sources/token-tracker.js +171 -0
  157. package/src/sources/vibe-tracker.js +79 -0
  158. package/src/sources/vscode-tasks.js +142 -0
  159. package/src/sources/windsurf.js +109 -0
  160. package/worker/README.md +96 -0
  161. package/worker/package-lock.json +1547 -0
  162. package/worker/package.json +20 -0
  163. package/worker/src/index.mjs +694 -0
  164. package/worker/wrangler.jsonc +40 -0
@@ -0,0 +1,150 @@
1
+ const fs = require("node:fs/promises");
2
+ const os = require("node:os");
3
+ const path = require("node:path");
4
+
5
+ const { isInRange } = require("../lib/dates");
6
+ const { emptyTotals, emptyReport, cleanText, toIsoTimestamp } = require("./common");
7
+
8
+ const DEFAULT_SEARCH_DIRS = ["projects", "dev", "src", "work", "code", "Desktop", "Documents"];
9
+
10
+ async function inspectAider({ root, range, home = os.homedir() } = {}) {
11
+ const historyFiles = await findAiderHistoryFiles(root, home);
12
+ const prompts = [];
13
+ let filesScanned = 0;
14
+
15
+ for (const file of historyFiles) {
16
+ filesScanned += 1;
17
+ let text;
18
+ try {
19
+ text = await fs.readFile(file, "utf8");
20
+ } catch {
21
+ continue;
22
+ }
23
+ for (const entry of parseAiderHistory(text)) {
24
+ if (!isInRange(entry.timestamp, range)) continue;
25
+ prompts.push({
26
+ source: "aider",
27
+ timestamp: entry.timestamp,
28
+ session_file: file,
29
+ text: entry.text,
30
+ });
31
+ }
32
+ }
33
+
34
+ const usedRoot = root || (historyFiles[0] ? path.dirname(historyFiles[0]) : path.join(home, "projects"));
35
+ return {
36
+ ...(emptyReport(
37
+ "aider",
38
+ usedRoot,
39
+ historyFiles.length
40
+ ? []
41
+ : ["No .aider.chat.history.md files found under the search roots."],
42
+ )),
43
+ files_scanned: filesScanned,
44
+ prompt_count: prompts.length,
45
+ token_totals: emptyTotals(),
46
+ prompts,
47
+ };
48
+ }
49
+
50
+ async function findAiderHistoryFiles(root, home) {
51
+ if (root) {
52
+ try {
53
+ const st = await fs.stat(root);
54
+ if (st.isFile()) return path.basename(root).includes("aider") ? [root] : [root];
55
+ } catch {
56
+ return [];
57
+ }
58
+ return walkDepth(root, 4, (name) => name === ".aider.chat.history.md");
59
+ }
60
+
61
+ const out = [];
62
+ const cwdFile = path.join(process.cwd(), ".aider.chat.history.md");
63
+ try {
64
+ await fs.access(cwdFile);
65
+ out.push(cwdFile);
66
+ } catch {
67
+ // ignore
68
+ }
69
+
70
+ for (const dirName of DEFAULT_SEARCH_DIRS) {
71
+ const base = path.join(home, dirName);
72
+ const found = await walkDepth(base, 3, (name) => name === ".aider.chat.history.md");
73
+ out.push(...found);
74
+ }
75
+ return [...new Set(out)].sort((a, b) => a.localeCompare(b));
76
+ }
77
+
78
+ async function walkDepth(rootDir, maxDepth, predicate) {
79
+ const out = [];
80
+ const stack = [{ dir: rootDir, depth: 0 }];
81
+ while (stack.length) {
82
+ const { dir, depth } = stack.pop();
83
+ let entries;
84
+ try {
85
+ entries = await fs.readdir(dir, { withFileTypes: true });
86
+ } catch {
87
+ continue;
88
+ }
89
+ for (const entry of entries) {
90
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
91
+ const filePath = path.join(dir, entry.name);
92
+ if (entry.isDirectory()) {
93
+ if (depth < maxDepth) stack.push({ dir: filePath, depth: depth + 1 });
94
+ } else if (entry.isFile() && predicate(entry.name, filePath)) {
95
+ out.push(filePath);
96
+ }
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+
102
+ function parseAiderHistory(markdown) {
103
+ const entries = [];
104
+ const lines = String(markdown || "").split(/\r?\n/);
105
+ let current = null;
106
+
107
+ const flush = () => {
108
+ if (!current) return;
109
+ const text = cleanText(current.lines.join("\n"));
110
+ if (text) {
111
+ entries.push({
112
+ timestamp: current.timestamp,
113
+ text,
114
+ });
115
+ }
116
+ current = null;
117
+ };
118
+
119
+ for (const line of lines) {
120
+ if (/^#+\s*(?:aider:)?\s*(user|human)\b/i.test(line)) {
121
+ flush();
122
+ const stampMatch = line.match(/\((\d{4}-\d{2}-\d{2}[^)]*)\)/);
123
+ current = {
124
+ timestamp: toIsoTimestamp(stampMatch?.[1]) || null,
125
+ lines: [],
126
+ };
127
+ continue;
128
+ }
129
+
130
+ if (/^>\?\s+/.test(line)) {
131
+ flush();
132
+ current = {
133
+ timestamp: null,
134
+ lines: [line.replace(/^>\?\s+/, "")],
135
+ };
136
+ continue;
137
+ }
138
+
139
+ if (/^#+\s*(?:aider:)?\s*(assistant|model|ai|coder)\b/i.test(line)) {
140
+ flush();
141
+ continue;
142
+ }
143
+
144
+ if (current) current.lines.push(line);
145
+ }
146
+ flush();
147
+ return entries;
148
+ }
149
+
150
+ module.exports = { inspectAider, parseAiderHistory, extractAiderUserText: (md) => parseAiderHistory(md).map((e) => e.text) };
@@ -0,0 +1,113 @@
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+
4
+ const { walkFiles } = require("../lib/jsonl");
5
+ const { isInRange } = require("../lib/dates");
6
+ const {
7
+ emptyTotals,
8
+ emptyReport,
9
+ addTotals,
10
+ usageFromObject,
11
+ cleanText,
12
+ toIsoTimestamp,
13
+ readJsonFile,
14
+ } = require("./common");
15
+
16
+ /**
17
+ * Amazon Q Developer IDE chat history.
18
+ * Default: ~/.aws/amazonq/history/chat-history-*.json (LokiJS dump).
19
+ */
20
+ async function inspectAmazonQ({ root, range, home = os.homedir() } = {}) {
21
+ const historyRoot = root || path.join(home, ".aws", "amazonq", "history");
22
+ const files = await walkFiles(
23
+ historyRoot,
24
+ (_filePath, name) =>
25
+ name.startsWith("chat-history") && name.endsWith(".json"),
26
+ );
27
+ const prompts = [];
28
+ const tokenTotals = emptyTotals();
29
+ const notes = [];
30
+
31
+ for (const file of files) {
32
+ const data = await readJsonFile(file);
33
+ if (!data) continue;
34
+ for (const entry of extractAmazonQEntries(data)) {
35
+ if (!isInRange(entry.timestamp, range)) continue;
36
+ prompts.push({
37
+ source: "amazonq",
38
+ timestamp: entry.timestamp,
39
+ session_file: file,
40
+ text: entry.text,
41
+ });
42
+ addTotals(tokenTotals, entry.tokens);
43
+ }
44
+ }
45
+
46
+ if (!files.length) {
47
+ notes.push("No Amazon Q chat-history-*.json under ~/.aws/amazonq/history.");
48
+ }
49
+
50
+ return {
51
+ ...(emptyReport("amazonq", historyRoot, notes)),
52
+ files_scanned: files.length,
53
+ prompt_count: prompts.length,
54
+ token_totals: tokenTotals,
55
+ prompts,
56
+ };
57
+ }
58
+
59
+ function extractAmazonQEntries(data) {
60
+ const out = [];
61
+ const tabs = collectAmazonQTabs(data);
62
+
63
+ for (const tab of tabs) {
64
+ const tabTs =
65
+ toIsoTimestamp(tab.updatedAt || tab.meta?.updated || tab.meta?.created) || null;
66
+ const conversations = Array.isArray(tab.conversations) ? tab.conversations : [];
67
+ for (const conversation of conversations) {
68
+ const messages = Array.isArray(conversation.messages) ? conversation.messages : [];
69
+ for (const msg of messages) {
70
+ const text = extractAmazonQUserText(msg);
71
+ if (!text) continue;
72
+ out.push({
73
+ text,
74
+ timestamp:
75
+ toIsoTimestamp(msg.timestamp || msg.createdAt || msg.time) || tabTs,
76
+ tokens: usageFromObject(msg),
77
+ });
78
+ }
79
+ }
80
+ }
81
+
82
+ return out;
83
+ }
84
+
85
+ function collectAmazonQTabs(data) {
86
+ if (!data || typeof data !== "object") return [];
87
+ if (Array.isArray(data.collections)) {
88
+ const tabs = [];
89
+ for (const collection of data.collections) {
90
+ if (!collection || collection.name !== "tabs") continue;
91
+ if (Array.isArray(collection.data)) tabs.push(...collection.data);
92
+ }
93
+ return tabs;
94
+ }
95
+ if (Array.isArray(data.tabs)) return data.tabs;
96
+ if (Array.isArray(data.conversations)) {
97
+ return [{ conversations: data.conversations, updatedAt: data.updatedAt }];
98
+ }
99
+ return [];
100
+ }
101
+
102
+ function extractAmazonQUserText(msg) {
103
+ if (!msg || typeof msg !== "object") return "";
104
+ const type = String(msg.type || msg.role || msg.kind || "").toLowerCase();
105
+ if (type && !/(prompt|user|human|request)/.test(type)) return "";
106
+ return cleanText(msg.body ?? msg.content ?? msg.text ?? msg.prompt ?? "");
107
+ }
108
+
109
+ module.exports = {
110
+ inspectAmazonQ,
111
+ extractAmazonQEntries,
112
+ extractAmazonQUserText,
113
+ };
@@ -0,0 +1,147 @@
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+ const fs = require("node:fs/promises");
4
+
5
+ const { walkFiles } = require("../lib/jsonl");
6
+ const { isInRange } = require("../lib/dates");
7
+ const {
8
+ emptyTotals,
9
+ emptyReport,
10
+ addTotals,
11
+ usageFromObject,
12
+ cleanText,
13
+ toIsoTimestamp,
14
+ readJsonFile,
15
+ } = require("./common");
16
+
17
+ /**
18
+ * Google Antigravity IDE — best-effort.
19
+ * Conversations live as encrypted/binary `.pb` under ~/.gemini/antigravity(-ide)/conversations.
20
+ * We only ingest plaintext JSON/JSONL exports if present; otherwise return empty with notes.
21
+ */
22
+ async function inspectAntigravity({ root, range, home = os.homedir(), platform, env } = {}) {
23
+ const roots = root
24
+ ? [root]
25
+ : defaultAntigravityRoots({ home, platform, env });
26
+
27
+ const prompts = [];
28
+ const tokenTotals = emptyTotals();
29
+ let filesScanned = 0;
30
+ let pbSeen = 0;
31
+ let usedRoot = roots[0] || null;
32
+ const notes = [];
33
+
34
+ for (const storeRoot of roots) {
35
+ const files = await walkFiles(storeRoot, () => true);
36
+ if (!files.length) continue;
37
+ usedRoot = storeRoot;
38
+
39
+ for (const file of files) {
40
+ const name = path.basename(file).toLowerCase();
41
+ if (name.endsWith(".pb")) {
42
+ pbSeen += 1;
43
+ continue;
44
+ }
45
+ if (!name.endsWith(".json") && !name.endsWith(".jsonl")) continue;
46
+
47
+ const data = await readJsonFile(file);
48
+ if (!data) continue;
49
+ filesScanned += 1;
50
+ for (const entry of extractAntigravityEntries(data)) {
51
+ if (!isInRange(entry.timestamp, range)) continue;
52
+ prompts.push({
53
+ source: "antigravity",
54
+ timestamp: entry.timestamp,
55
+ session_file: file,
56
+ text: entry.text,
57
+ });
58
+ addTotals(tokenTotals, entry.tokens);
59
+ }
60
+ }
61
+ if (filesScanned > 0 || pbSeen > 0) break;
62
+ }
63
+
64
+ if (pbSeen > 0 && prompts.length === 0) {
65
+ notes.push(
66
+ `Found ${pbSeen} Antigravity conversation .pb file(s); content is protobuf/binary and not parsed.`,
67
+ );
68
+ } else if (!filesScanned && !pbSeen) {
69
+ notes.push(
70
+ "No Antigravity conversation dir found (~/.gemini/antigravity(-ide)/conversations or App Support state).",
71
+ );
72
+ }
73
+
74
+ return {
75
+ ...(emptyReport("antigravity", usedRoot, notes)),
76
+ files_scanned: filesScanned + pbSeen,
77
+ prompt_count: prompts.length,
78
+ token_totals: tokenTotals,
79
+ prompts,
80
+ };
81
+ }
82
+
83
+ function defaultAntigravityRoots({ home, platform = process.platform, env = process.env }) {
84
+ const support =
85
+ platform === "darwin"
86
+ ? path.join(home, "Library", "Application Support")
87
+ : platform === "win32"
88
+ ? path.join(env.APPDATA || path.join(home, "AppData", "Roaming"))
89
+ : path.join(home, ".config");
90
+
91
+ return [
92
+ path.join(home, ".gemini", "antigravity-ide", "conversations"),
93
+ path.join(home, ".gemini", "antigravity", "conversations"),
94
+ path.join(support, "Antigravity IDE", "User", "globalStorage"),
95
+ path.join(support, "Antigravity", "User", "globalStorage"),
96
+ path.join(support, "antigravity", "User", "globalStorage"),
97
+ ];
98
+ }
99
+
100
+ function extractAntigravityEntries(data) {
101
+ const out = [];
102
+ const messages = Array.isArray(data)
103
+ ? data
104
+ : Array.isArray(data.messages)
105
+ ? data.messages
106
+ : Array.isArray(data.history)
107
+ ? data.history
108
+ : Array.isArray(data.conversations)
109
+ ? data.conversations.flatMap((c) => (Array.isArray(c.messages) ? c.messages : []))
110
+ : [];
111
+
112
+ for (const row of messages) {
113
+ const text = extractAntigravityUserText(row);
114
+ if (!text) continue;
115
+ out.push({
116
+ text,
117
+ timestamp: toIsoTimestamp(row.timestamp || row.createdAt || row.time || data.updatedAt),
118
+ tokens: usageFromObject(row),
119
+ });
120
+ }
121
+ return out;
122
+ }
123
+
124
+ function extractAntigravityUserText(row) {
125
+ if (!row || typeof row !== "object") return "";
126
+ const role = String(row.role || row.type || row.author || "").toLowerCase();
127
+ if (role && !/(user|human|prompt|request)/.test(role)) return "";
128
+ return cleanText(row.content ?? row.text ?? row.body ?? row.prompt ?? "");
129
+ }
130
+
131
+ /** Exists helper for tests / docs. */
132
+ async function pathExists(p) {
133
+ try {
134
+ await fs.access(p);
135
+ return true;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+
141
+ module.exports = {
142
+ inspectAntigravity,
143
+ extractAntigravityEntries,
144
+ extractAntigravityUserText,
145
+ defaultAntigravityRoots,
146
+ pathExists,
147
+ };
@@ -0,0 +1,262 @@
1
+ const os = require("node:os");
2
+ const path = require("node:path");
3
+
4
+ const { walkFiles, readJsonl } = require("../lib/jsonl");
5
+ const { isInRange } = require("../lib/dates");
6
+ const { normalizeWhitespace } = require("../extract/text");
7
+
8
+ async function inspectClaude({ root, range } = {}) {
9
+ const projectsRoot = root || path.join(os.homedir(), ".claude", "projects");
10
+ const files = await walkFiles(projectsRoot, (_filePath, name) => name.endsWith(".jsonl"));
11
+ const prompts = [];
12
+ const tokenTotals = emptyTotals();
13
+ const contextByDay = new Map();
14
+ const seenContextMessages = new Set();
15
+
16
+ for (const file of files) {
17
+ const sessionState = { systemPrefixSeen: false };
18
+ const contextMessages = new Map();
19
+ await readJsonl(file, (obj) => {
20
+ const timestamp = obj.timestamp || null;
21
+ if (!isInRange(timestamp, range)) return;
22
+
23
+ const text = extractClaudePrompt(obj);
24
+ if (text) {
25
+ prompts.push({
26
+ source: "claude",
27
+ timestamp,
28
+ session_file: file,
29
+ text,
30
+ });
31
+ }
32
+
33
+ const tokens = extractClaudeTokens(obj);
34
+ if (tokens) addTotals(tokenTotals, tokens);
35
+
36
+ if (tokens?.total_tokens > 0) collectClaudeContextMessage(contextMessages, obj, timestamp);
37
+ });
38
+
39
+ for (const message of contextMessages.values()) {
40
+ if (message.key && seenContextMessages.has(message.key)) continue;
41
+ if (message.key) seenContextMessages.add(message.key);
42
+ addClaudeContext(contextByDay, message.timestamp, {
43
+ message: {
44
+ usage: message.usage,
45
+ content: message.content,
46
+ },
47
+ }, sessionState);
48
+ }
49
+ }
50
+
51
+ return {
52
+ source: "claude",
53
+ root: projectsRoot,
54
+ files_scanned: files.length,
55
+ prompt_count: prompts.length,
56
+ token_totals: tokenTotals,
57
+ context_breakdown_daily: [...contextByDay.values()].sort((a, b) => a.day.localeCompare(b.day)),
58
+ prompts,
59
+ };
60
+ }
61
+
62
+ function extractClaudePrompt(obj) {
63
+ if (obj?.type !== "user" && obj?.message?.role !== "user") return "";
64
+ // Skip synthetic user rows that only wrap tool results / meta.
65
+ if (obj?.isMeta || obj?.isCompactSummary) return "";
66
+ const content = obj?.message?.content ?? obj?.content;
67
+ return normalizeWhitespace(textFromClaudeUserContent(content));
68
+ }
69
+
70
+ function textFromClaudeUserContent(content) {
71
+ if (typeof content === "string") return content;
72
+ if (!Array.isArray(content)) return "";
73
+ return content
74
+ .map((block) => {
75
+ if (!block || typeof block !== "object") return "";
76
+ if (block.type && block.type !== "text") return "";
77
+ return typeof block.text === "string" ? block.text : "";
78
+ })
79
+ .filter(Boolean)
80
+ .join("\n");
81
+ }
82
+
83
+ function extractClaudeTokens(obj) {
84
+ const usage = obj?.message?.usage || obj?.usage;
85
+ if (!usage) return null;
86
+ return {
87
+ input_tokens: number(usage.input_tokens),
88
+ cached_input_tokens: number(usage.cache_read_input_tokens || usage.cached_input_tokens),
89
+ cache_creation_input_tokens: number(usage.cache_creation_input_tokens),
90
+ output_tokens: number(usage.output_tokens),
91
+ reasoning_output_tokens: 0,
92
+ total_tokens:
93
+ number(usage.input_tokens) +
94
+ number(usage.cache_read_input_tokens || usage.cached_input_tokens) +
95
+ number(usage.cache_creation_input_tokens) +
96
+ number(usage.output_tokens),
97
+ };
98
+ }
99
+
100
+ function claudeContextKey(obj) {
101
+ const messageId = obj?.message?.id || "";
102
+ const requestId = obj?.requestId || obj?.request_id || "";
103
+ if (!messageId && !requestId) return "";
104
+ return `${messageId}|${requestId}`;
105
+ }
106
+
107
+ function collectClaudeContextMessage(messages, obj, timestamp) {
108
+ const key = claudeContextKey(obj) || `${timestamp}|${messages.size}`;
109
+ const usage = obj?.message?.usage || obj?.usage;
110
+ const content = Array.isArray(obj?.message?.content) ? obj.message.content : [];
111
+ const current = messages.get(key) || {
112
+ key: claudeContextKey(obj),
113
+ timestamp,
114
+ usage,
115
+ content: [],
116
+ blockKeys: new Set(),
117
+ };
118
+ if (claudeUsageTotal(usage) >= claudeUsageTotal(current.usage)) current.usage = usage;
119
+ current.timestamp = current.timestamp || timestamp;
120
+
121
+ for (const block of content) {
122
+ if (!block || typeof block !== "object") continue;
123
+ const blockKey = block.id
124
+ ? `${block.type || ""}:${block.id}`
125
+ : JSON.stringify(block);
126
+ if (current.blockKeys.has(blockKey)) continue;
127
+ current.blockKeys.add(blockKey);
128
+ current.content.push(block);
129
+ }
130
+ messages.set(key, current);
131
+ }
132
+
133
+ function claudeUsageTotal(usage) {
134
+ return number(usage?.input_tokens)
135
+ + number(usage?.cache_read_input_tokens || usage?.cached_input_tokens)
136
+ + number(usage?.cache_creation_input_tokens)
137
+ + number(usage?.output_tokens);
138
+ }
139
+
140
+ function addClaudeContext(byDay, timestamp, obj, sessionState) {
141
+ if (!timestamp) return;
142
+ const day = String(timestamp).slice(0, 10);
143
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) return;
144
+ const usage = obj?.message?.usage || obj?.usage;
145
+ if (!usage) return;
146
+
147
+ const input = number(usage.input_tokens);
148
+ const cached = number(usage.cache_read_input_tokens || usage.cached_input_tokens);
149
+ const cacheCreation = number(usage.cache_creation_input_tokens);
150
+ const output = number(usage.output_tokens);
151
+ const row = byDay.get(day) || emptyContextRow(day, "content_blocks");
152
+
153
+ row.categories.messages += input + cached;
154
+ if (cacheCreation > 0 && !sessionState.systemPrefixSeen) {
155
+ row.categories.system_prompt += cacheCreation;
156
+ sessionState.systemPrefixSeen = true;
157
+ } else {
158
+ row.categories.messages += cacheCreation;
159
+ }
160
+
161
+ const outputSplit = splitClaudeOutput(output, number(usage.reasoning_output_tokens), obj?.message?.content);
162
+ row.categories.messages += outputSplit.messages;
163
+ row.categories.tool_calls += outputSplit.tool_calls;
164
+ row.categories.reasoning += outputSplit.reasoning;
165
+ row.categories.custom_agents += outputSplit.custom_agents;
166
+ row.total_tokens += input + cached + cacheCreation + output;
167
+ row.input_tokens += input + cached + cacheCreation;
168
+ row.cached_input_tokens += cached;
169
+ row.tool_call_count += outputSplit.tool_call_count;
170
+ row.event_count += 1;
171
+ byDay.set(day, row);
172
+ }
173
+
174
+ function splitClaudeOutput(total, explicitReasoning, content) {
175
+ const blocks = Array.isArray(content) ? content : [];
176
+ const weights = { messages: 0, tool_calls: 0, reasoning: 0, custom_agents: 0 };
177
+ let toolCallCount = 0;
178
+
179
+ for (const block of blocks) {
180
+ if (!block || typeof block !== "object") continue;
181
+ if (block.type === "text") {
182
+ weights.messages += String(block.text || "").length || 1;
183
+ } else if (block.type === "thinking") {
184
+ weights.reasoning += String(block.thinking || block.text || "").length || 1;
185
+ } else if (block.type === "tool_use") {
186
+ const weight = String(block.name || "").length
187
+ + (block.input ? JSON.stringify(block.input).length : 0)
188
+ + 1;
189
+ const category = block.name === "Agent" || block.name === "Task"
190
+ ? "custom_agents"
191
+ : "tool_calls";
192
+ weights[category] += weight;
193
+ toolCallCount += 1;
194
+ }
195
+ }
196
+
197
+ const reasoning = Math.min(total, explicitReasoning);
198
+ const remaining = Math.max(0, total - reasoning);
199
+ if (reasoning > 0) weights.reasoning = 0;
200
+ const weightTotal = Object.values(weights).reduce((sum, value) => sum + value, 0);
201
+ const allocated = { messages: 0, tool_calls: 0, reasoning, custom_agents: 0 };
202
+
203
+ if (weightTotal <= 0) {
204
+ allocated.messages = remaining;
205
+ } else {
206
+ let used = 0;
207
+ for (const key of ["tool_calls", "custom_agents", "reasoning"]) {
208
+ allocated[key] += Math.floor(remaining * (weights[key] / weightTotal));
209
+ used += Math.floor(remaining * (weights[key] / weightTotal));
210
+ }
211
+ allocated.messages = remaining - used;
212
+ }
213
+
214
+ return { ...allocated, tool_call_count: toolCallCount };
215
+ }
216
+
217
+ function emptyContextRow(day, method) {
218
+ return {
219
+ day,
220
+ method,
221
+ categories: {
222
+ messages: 0,
223
+ tool_calls: 0,
224
+ reasoning: 0,
225
+ system_prompt: 0,
226
+ custom_agents: 0,
227
+ },
228
+ total_tokens: 0,
229
+ input_tokens: 0,
230
+ cached_input_tokens: 0,
231
+ tool_call_count: 0,
232
+ event_count: 0,
233
+ };
234
+ }
235
+
236
+ function emptyTotals() {
237
+ return {
238
+ input_tokens: 0,
239
+ cached_input_tokens: 0,
240
+ cache_creation_input_tokens: 0,
241
+ output_tokens: 0,
242
+ reasoning_output_tokens: 0,
243
+ total_tokens: 0,
244
+ };
245
+ }
246
+
247
+ function addTotals(target, delta) {
248
+ for (const key of Object.keys(target)) target[key] += number(delta[key]);
249
+ }
250
+
251
+ function number(value) {
252
+ const n = Number(value || 0);
253
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
254
+ }
255
+
256
+ module.exports = {
257
+ inspectClaude,
258
+ extractClaudePrompt,
259
+ extractClaudeTokens,
260
+ collectClaudeContextMessage,
261
+ splitClaudeOutput,
262
+ };
@@ -0,0 +1,11 @@
1
+ const { inspectVscodeTaskStore, extractTaskUserText } = require("./vscode-tasks");
2
+
3
+ async function inspectCline(options = {}) {
4
+ return inspectVscodeTaskStore({
5
+ source: "cline",
6
+ extensionId: "saoudrizwan.claude-dev",
7
+ ...options,
8
+ });
9
+ }
10
+
11
+ module.exports = { inspectCline, extractTaskUserText };