thumbgate 1.4.3 → 1.4.4

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 (266) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/adapters/README.md +1 -1
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/codex/config.toml +2 -2
  7. package/adapters/mcp/server-stdio.js +1 -1
  8. package/adapters/opencode/opencode.json +1 -1
  9. package/package.json +157 -9
  10. package/scripts/statusline.sh +1 -0
  11. package/src/api/server.js +113 -16
  12. package/src/index.js +3 -0
  13. package/.claude-plugin/bundle/icon.png +0 -0
  14. package/.claude-plugin/bundle/icon.svg +0 -18
  15. package/.claude-plugin/bundle/server/index.js +0 -24
  16. package/adapters/chatgpt/INSTALL.md +0 -158
  17. package/adapters/perplexity/.mcp.json +0 -36
  18. package/adapters/perplexity/config.toml +0 -16
  19. package/adapters/perplexity/opencode.json +0 -29
  20. package/bin/memory.sh +0 -64
  21. package/bin/obsidian-sync.sh +0 -20
  22. package/plugins/amp-skill/INSTALL.md +0 -52
  23. package/plugins/amp-skill/SKILL.md +0 -64
  24. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
  25. package/plugins/claude-codex-bridge/.mcp.json +0 -14
  26. package/plugins/claude-codex-bridge/INSTALL.md +0 -43
  27. package/plugins/claude-codex-bridge/README.md +0 -46
  28. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
  29. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
  30. package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
  31. package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
  32. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
  33. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
  34. package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
  35. package/plugins/claude-skill/INSTALL.md +0 -55
  36. package/plugins/claude-skill/SKILL.md +0 -46
  37. package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
  38. package/plugins/codex-profile/.mcp.json +0 -14
  39. package/plugins/codex-profile/AGENTS.md +0 -20
  40. package/plugins/codex-profile/INSTALL.md +0 -89
  41. package/plugins/codex-profile/README.md +0 -61
  42. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
  43. package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
  44. package/plugins/cursor-marketplace/LICENSE +0 -21
  45. package/plugins/cursor-marketplace/README.md +0 -124
  46. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
  47. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  48. package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
  49. package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
  50. package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
  51. package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
  52. package/plugins/cursor-marketplace/mcp.json +0 -14
  53. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
  54. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
  55. package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
  56. package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
  57. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
  58. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
  59. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
  60. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
  61. package/plugins/gemini-extension/INSTALL.md +0 -92
  62. package/plugins/gemini-extension/gemini_prompt.txt +0 -14
  63. package/plugins/gemini-extension/tool_contract.json +0 -45
  64. package/plugins/opencode-profile/INSTALL.md +0 -57
  65. package/public/assets/instagram-card.png +0 -0
  66. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  67. package/public/blog.html +0 -474
  68. package/public/compare/mem0.html +0 -189
  69. package/public/compare/speclock.html +0 -180
  70. package/public/compare.html +0 -310
  71. package/public/dashboard.html +0 -1100
  72. package/public/guide.html +0 -317
  73. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  74. package/public/guides/codex-cli-guardrails.html +0 -158
  75. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  76. package/public/guides/pre-action-gates.html +0 -162
  77. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
  78. package/public/index.html +0 -1225
  79. package/public/js/buyer-intent.js +0 -252
  80. package/public/learn/agent-harness-pattern.html +0 -180
  81. package/public/learn/ai-agent-persistent-memory.html +0 -203
  82. package/public/learn/learn.css +0 -45
  83. package/public/learn/mcp-pre-action-gates-explained.html +0 -172
  84. package/public/learn/stop-ai-agent-force-push.html +0 -134
  85. package/public/learn/vibe-coding-safety-net.html +0 -142
  86. package/public/learn.html +0 -274
  87. package/public/lessons.html +0 -967
  88. package/public/llm-context.md +0 -156
  89. package/public/pro.html +0 -1087
  90. package/public/vercel.json +0 -8
  91. package/scripts/a2ui-engine.js +0 -73
  92. package/scripts/adk-consolidator.js +0 -274
  93. package/scripts/agent-security-hardening.js +0 -225
  94. package/scripts/ai-search-visibility.js +0 -116
  95. package/scripts/autonomous-sales-agent.js +0 -39
  96. package/scripts/autoresearch-runner.js +0 -216
  97. package/scripts/background-agent-governance.js +0 -229
  98. package/scripts/behavioral-extraction.js +0 -93
  99. package/scripts/budget-enforcer.js +0 -173
  100. package/scripts/budget-guard.js +0 -173
  101. package/scripts/build-claude-mcpb.js +0 -255
  102. package/scripts/build-codex-plugin.js +0 -152
  103. package/scripts/capture-railway-diagnostics.sh +0 -97
  104. package/scripts/changeset-check.js +0 -372
  105. package/scripts/check-congruence.js +0 -443
  106. package/scripts/computer-use-firewall.js +0 -280
  107. package/scripts/content-engine/linkedin-content-generator.js +0 -154
  108. package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
  109. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
  110. package/scripts/content-engine/reddit-thread-finder.js +0 -154
  111. package/scripts/context-engine.js +0 -710
  112. package/scripts/daily-digest.js +0 -11
  113. package/scripts/data-governance.js +0 -173
  114. package/scripts/deploy-gcp.sh +0 -44
  115. package/scripts/deploy-policy.js +0 -249
  116. package/scripts/disagreement-mining.js +0 -315
  117. package/scripts/dpo-optimizer.js +0 -206
  118. package/scripts/ensure-repo-bootstrap.js +0 -130
  119. package/scripts/ephemeral-agent-store.js +0 -212
  120. package/scripts/eval-harness.js +0 -56
  121. package/scripts/export-kto-pairs.js +0 -309
  122. package/scripts/export-training.js +0 -446
  123. package/scripts/feedback-fallback.js +0 -111
  124. package/scripts/feedback-inbox-read.js +0 -162
  125. package/scripts/feedback-root-consolidator.js +0 -233
  126. package/scripts/feedback-to-memory.js +0 -185
  127. package/scripts/gate-satisfy.js +0 -42
  128. package/scripts/generate-paperbanana-diagrams.sh +0 -99
  129. package/scripts/generate-pretool-hook.sh +0 -40
  130. package/scripts/github-about.js +0 -430
  131. package/scripts/github-outreach.js +0 -65
  132. package/scripts/gtm-revenue-loop.js +0 -535
  133. package/scripts/hallucination-detector.js +0 -226
  134. package/scripts/hf-papers.js +0 -317
  135. package/scripts/hook-auto-capture.sh +0 -100
  136. package/scripts/hook-stop-pr-thread-check.sh +0 -68
  137. package/scripts/hook-stop-self-score.sh +0 -51
  138. package/scripts/hook-stop-verify-deploy.sh +0 -31
  139. package/scripts/hook-verify-before-done.sh +0 -20
  140. package/scripts/managed-dpo-export.js +0 -91
  141. package/scripts/markdown-escape.js +0 -12
  142. package/scripts/marketing-experiment.js +0 -657
  143. package/scripts/memalign-recall.js +0 -111
  144. package/scripts/memory-migration.js +0 -296
  145. package/scripts/meta-policy.js +0 -190
  146. package/scripts/metered-billing.js +0 -16
  147. package/scripts/model-tier-router.js +0 -310
  148. package/scripts/money-watcher.js +0 -218
  149. package/scripts/multi-hop-recall.js +0 -240
  150. package/scripts/per-step-scoring.js +0 -163
  151. package/scripts/perplexity-command-center.js +0 -644
  152. package/scripts/perplexity-marketing.js +0 -454
  153. package/scripts/pii-scanner.js +0 -153
  154. package/scripts/plan-gate.js +0 -154
  155. package/scripts/post-everywhere.js +0 -341
  156. package/scripts/post-to-x-retry.sh +0 -22
  157. package/scripts/post-to-x.js +0 -369
  158. package/scripts/pr-manager.js +0 -421
  159. package/scripts/principle-extractor.js +0 -162
  160. package/scripts/pro-features.js +0 -41
  161. package/scripts/prompt-dlp.js +0 -222
  162. package/scripts/prove-adapters.js +0 -860
  163. package/scripts/prove-attribution.js +0 -361
  164. package/scripts/prove-automation.js +0 -651
  165. package/scripts/prove-autoresearch.js +0 -304
  166. package/scripts/prove-claim-verification.js +0 -277
  167. package/scripts/prove-cloudflare-sandbox.js +0 -161
  168. package/scripts/prove-data-pipeline.js +0 -408
  169. package/scripts/prove-data-quality.js +0 -227
  170. package/scripts/prove-evolution.js +0 -352
  171. package/scripts/prove-harnesses.js +0 -287
  172. package/scripts/prove-intelligence.js +0 -257
  173. package/scripts/prove-lancedb.js +0 -425
  174. package/scripts/prove-local-intelligence.js +0 -340
  175. package/scripts/prove-loop-closure.js +0 -263
  176. package/scripts/prove-packaged-runtime.js +0 -327
  177. package/scripts/prove-predictive-insights.js +0 -355
  178. package/scripts/prove-runtime.js +0 -363
  179. package/scripts/prove-seo-gsd.js +0 -234
  180. package/scripts/prove-settings.js +0 -279
  181. package/scripts/prove-subway-upgrades.js +0 -277
  182. package/scripts/prove-tessl.js +0 -229
  183. package/scripts/prove-training-export.js +0 -325
  184. package/scripts/prove-workflow-contract.js +0 -112
  185. package/scripts/prove-xmemory.js +0 -332
  186. package/scripts/publish-decision.js +0 -159
  187. package/scripts/ralph-loop.js +0 -376
  188. package/scripts/ralph-mode-ci.js +0 -434
  189. package/scripts/reddit-dm-outreach.js +0 -192
  190. package/scripts/reddit-monitor-cron.sh +0 -26
  191. package/scripts/reminder-engine.js +0 -132
  192. package/scripts/revenue-status.js +0 -472
  193. package/scripts/rotate-stripe-webhook-secret.js +0 -314
  194. package/scripts/schedule-manager.js +0 -249
  195. package/scripts/self-healing-check.js +0 -193
  196. package/scripts/session-analyzer.js +0 -533
  197. package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
  198. package/scripts/skill-exporter.js +0 -260
  199. package/scripts/skill-materializer.js +0 -134
  200. package/scripts/skill-packs.js +0 -136
  201. package/scripts/skill-proposer.js +0 -99
  202. package/scripts/skill-quality-tracker.js +0 -282
  203. package/scripts/slow-loop.js +0 -72
  204. package/scripts/social-analytics/db/marketing-db.js +0 -179
  205. package/scripts/social-analytics/db/schema.sql +0 -55
  206. package/scripts/social-analytics/digest.js +0 -256
  207. package/scripts/social-analytics/engagement-audit.js +0 -185
  208. package/scripts/social-analytics/generate-instagram-card.js +0 -123
  209. package/scripts/social-analytics/generate-slides.js +0 -268
  210. package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
  211. package/scripts/social-analytics/install-growth-automation.js +0 -114
  212. package/scripts/social-analytics/load-env.js +0 -77
  213. package/scripts/social-analytics/mcp-server.js +0 -289
  214. package/scripts/social-analytics/normalizer.js +0 -580
  215. package/scripts/social-analytics/notify.js +0 -162
  216. package/scripts/social-analytics/poll-all.js +0 -107
  217. package/scripts/social-analytics/pollers/github.js +0 -195
  218. package/scripts/social-analytics/pollers/instagram.js +0 -253
  219. package/scripts/social-analytics/pollers/linkedin.js +0 -340
  220. package/scripts/social-analytics/pollers/plausible.js +0 -245
  221. package/scripts/social-analytics/pollers/reddit.js +0 -306
  222. package/scripts/social-analytics/pollers/threads.js +0 -233
  223. package/scripts/social-analytics/pollers/tiktok.js +0 -203
  224. package/scripts/social-analytics/pollers/x.js +0 -227
  225. package/scripts/social-analytics/pollers/youtube.js +0 -304
  226. package/scripts/social-analytics/pollers/zernio.js +0 -183
  227. package/scripts/social-analytics/post-video.js +0 -316
  228. package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
  229. package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
  230. package/scripts/social-analytics/publishers/devto.js +0 -122
  231. package/scripts/social-analytics/publishers/instagram.js +0 -317
  232. package/scripts/social-analytics/publishers/linkedin.js +0 -294
  233. package/scripts/social-analytics/publishers/reddit.js +0 -385
  234. package/scripts/social-analytics/publishers/threads.js +0 -275
  235. package/scripts/social-analytics/publishers/tiktok.js +0 -217
  236. package/scripts/social-analytics/publishers/x.js +0 -259
  237. package/scripts/social-analytics/publishers/youtube.js +0 -223
  238. package/scripts/social-analytics/publishers/zernio.js +0 -568
  239. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
  240. package/scripts/social-analytics/run-digest.js +0 -34
  241. package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
  242. package/scripts/social-analytics/store.js +0 -455
  243. package/scripts/social-analytics/sync-launch-assets.js +0 -185
  244. package/scripts/social-analytics/utm.js +0 -143
  245. package/scripts/social-pipeline.js +0 -2626
  246. package/scripts/social-post-hourly.js +0 -228
  247. package/scripts/social-quality-gate.js +0 -134
  248. package/scripts/social-reply-monitor.js +0 -592
  249. package/scripts/status-dashboard.js +0 -155
  250. package/scripts/stripe-live-status.js +0 -115
  251. package/scripts/subagent-profiles.js +0 -79
  252. package/scripts/sync-branch-protection.js +0 -340
  253. package/scripts/sync-gh-secrets-from-env.sh +0 -70
  254. package/scripts/sync-github-about.js +0 -55
  255. package/scripts/sync-version.js +0 -479
  256. package/scripts/synthetic-dpo.js +0 -234
  257. package/scripts/tessl-export.js +0 -369
  258. package/scripts/test-coverage.js +0 -128
  259. package/scripts/thumbgate-bench.js +0 -494
  260. package/scripts/thumbgate_session_start.sh +0 -32
  261. package/scripts/train_from_feedback.py +0 -929
  262. package/scripts/validate-feedback.js +0 -581
  263. package/scripts/verify-obsidian-setup.sh +0 -269
  264. package/scripts/verify-run.js +0 -269
  265. package/scripts/weekly-auto-post.js +0 -124
  266. package/scripts/x-autonomous-marketing.js +0 -139
@@ -1,533 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Session Analyzer — reads Claude Code JSONL transcripts and extracts
6
- * actionable intelligence: token usage, waste (duplicate reads), confusion
7
- * signals, and auto-generated lessons for ThumbGate enforcement.
8
- *
9
- * Gives ThumbGate parity with Leo Godin's session analyzer plus enforcement
10
- * integration via lesson-inference.js.
11
- */
12
-
13
- const fs = require('node:fs');
14
- const path = require('node:path');
15
- const os = require('node:os');
16
-
17
- // ---------------------------------------------------------------------------
18
- // 1. JSONL Parsing
19
- // ---------------------------------------------------------------------------
20
-
21
- /**
22
- * Parse a Claude Code session JSONL file into an array of event objects.
23
- * Malformed lines are silently skipped.
24
- * @param {string} sessionPath - absolute path to the .jsonl file
25
- * @returns {Array<Object>}
26
- */
27
- function parseSessionJSONL(sessionPath) {
28
- const raw = fs.readFileSync(sessionPath, 'utf-8');
29
- const lines = raw.split('\n').filter((l) => l.trim().length > 0);
30
- const events = [];
31
- for (const line of lines) {
32
- try {
33
- events.push(JSON.parse(line));
34
- } catch {
35
- // skip malformed lines
36
- }
37
- }
38
- return events;
39
- }
40
-
41
- // ---------------------------------------------------------------------------
42
- // 2. Token Usage Tracking
43
- // ---------------------------------------------------------------------------
44
-
45
- /**
46
- * Extract per-turn and cumulative token usage from assistant messages.
47
- * @param {Array<Object>} events
48
- * @returns {{ turns: Array, totals: Object }}
49
- */
50
- function analyzeTokenUsage(events) {
51
- const turns = [];
52
- let cumulativeInput = 0;
53
- let cumulativeOutput = 0;
54
- let cumulativeCacheRead = 0;
55
- let cumulativeCacheCreation = 0;
56
-
57
- for (const event of events) {
58
- if (event.type !== 'assistant') continue;
59
- const usage = event.message?.usage;
60
- if (!usage) continue;
61
-
62
- const input = usage.input_tokens || 0;
63
- const output = usage.output_tokens || 0;
64
- const cacheRead = usage.cache_read_input_tokens || 0;
65
- const cacheCreation = usage.cache_creation_input_tokens || 0;
66
-
67
- cumulativeInput += input;
68
- cumulativeOutput += output;
69
- cumulativeCacheRead += cacheRead;
70
- cumulativeCacheCreation += cacheCreation;
71
-
72
- turns.push({
73
- timestamp: event.timestamp || null,
74
- input,
75
- output,
76
- cacheRead,
77
- cacheCreation,
78
- cumulativeInput,
79
- cumulativeOutput,
80
- });
81
- }
82
-
83
- return {
84
- turns,
85
- totals: {
86
- input: cumulativeInput,
87
- output: cumulativeOutput,
88
- cacheRead: cumulativeCacheRead,
89
- cacheCreation: cumulativeCacheCreation,
90
- total: cumulativeInput + cumulativeOutput,
91
- },
92
- };
93
- }
94
-
95
- // ---------------------------------------------------------------------------
96
- // 3. Waste Detection — Duplicate File Reads
97
- // ---------------------------------------------------------------------------
98
-
99
- /**
100
- * Find files read more than once in the session.
101
- * @param {Array<Object>} events
102
- * @returns {{ duplicateReads: Object<string, number>, wasteScore: number }}
103
- */
104
- function detectDuplicateReads(events) {
105
- const readCounts = {};
106
-
107
- for (const event of events) {
108
- if (event.type !== 'assistant') continue;
109
- const content = event.message?.content;
110
- if (!Array.isArray(content)) continue;
111
-
112
- for (const block of content) {
113
- if (block.type === 'tool_use' && block.name === 'Read' && block.input?.file_path) {
114
- const fp = block.input.file_path;
115
- readCounts[fp] = (readCounts[fp] || 0) + 1;
116
- }
117
- }
118
- }
119
-
120
- const duplicateReads = {};
121
- for (const [fp, count] of Object.entries(readCounts)) {
122
- if (count >= 2) {
123
- duplicateReads[fp] = count;
124
- }
125
- }
126
-
127
- const totalReads = Object.values(readCounts).reduce((a, b) => a + b, 0);
128
- const wastedReads = Object.values(duplicateReads).reduce((a, b) => a + b - 1, 0);
129
- const wasteScore = totalReads > 0 ? Math.round((wastedReads / totalReads) * 100) : 0;
130
-
131
- return { duplicateReads, wasteScore, totalReads, wastedReads };
132
- }
133
-
134
- // ---------------------------------------------------------------------------
135
- // 4. Confusion Signal Detection
136
- // ---------------------------------------------------------------------------
137
-
138
- const CONFUSION_KEYWORDS = {
139
- backtracking: ['actually', 'wait', 'wrong', 'mistake', 'let me reconsider', 'should have'],
140
- rework: ['revert', 'undo', 'let me try', "didn't work", 'failed'],
141
- workarounds: ['circular', 'workaround', 'hack'],
142
- scopeCreep: ['refactor', 'restructur', 'redesign'],
143
- };
144
-
145
- /**
146
- * Detect confusion signals in assistant message text.
147
- * @param {Array<Object>} events
148
- * @returns {Array<{ category: string, keyword: string, context: string, timestamp: string|null }>}
149
- */
150
- function detectConfusionSignals(events) {
151
- const signals = [];
152
-
153
- for (const event of events) {
154
- for (const block of assistantTextBlocks(event)) {
155
- signals.push(...detectConfusionInText(block.text, event.timestamp || null));
156
- }
157
- }
158
-
159
- return signals;
160
- }
161
-
162
- function assistantTextBlocks(event) {
163
- if (event.type !== 'assistant') return [];
164
- const content = event.message?.content;
165
- if (!Array.isArray(content)) return [];
166
- return content.filter((block) => block.type === 'text' && block.text);
167
- }
168
-
169
- function detectConfusionInText(text, timestamp) {
170
- const signals = [];
171
- const lower = text.toLowerCase();
172
-
173
- for (const [category, keywords] of Object.entries(CONFUSION_KEYWORDS)) {
174
- for (const keyword of keywords) {
175
- for (const idx of keywordIndexes(lower, keyword)) {
176
- signals.push({
177
- category,
178
- keyword,
179
- context: confusionContext(text, idx, keyword),
180
- timestamp,
181
- });
182
- }
183
- }
184
- }
185
-
186
- return signals;
187
- }
188
-
189
- function keywordIndexes(text, keyword) {
190
- const indexes = [];
191
- let idx = 0;
192
- while ((idx = text.indexOf(keyword, idx)) !== -1) {
193
- indexes.push(idx);
194
- idx += keyword.length;
195
- }
196
- return indexes;
197
- }
198
-
199
- function confusionContext(text, idx, keyword) {
200
- const start = Math.max(0, idx - 40);
201
- const end = Math.min(text.length, idx + keyword.length + 40);
202
- return text.slice(start, end).replaceAll('\n', ' ').trim();
203
- }
204
-
205
- // ---------------------------------------------------------------------------
206
- // 5. Session Summary
207
- // ---------------------------------------------------------------------------
208
-
209
- /**
210
- * Tool call counts and files touched.
211
- * @param {Array<Object>} events
212
- * @returns {{ toolCounts: Object, filesTouched: Set<string> }}
213
- */
214
- function extractToolUsage(events) {
215
- const toolCounts = {};
216
- const filesTouched = new Set();
217
-
218
- for (const event of events) {
219
- if (event.type !== 'assistant') continue;
220
- const content = event.message?.content;
221
- if (!Array.isArray(content)) continue;
222
-
223
- for (const block of content) {
224
- if (block.type !== 'tool_use') continue;
225
- toolCounts[block.name] = (toolCounts[block.name] || 0) + 1;
226
-
227
- if (['Read', 'Write', 'Edit'].includes(block.name) && block.input?.file_path) {
228
- filesTouched.add(block.input.file_path);
229
- }
230
- }
231
- }
232
-
233
- return { toolCounts, filesTouched: Array.from(filesTouched) };
234
- }
235
-
236
- /**
237
- * Full session summary.
238
- * @param {string} sessionPath
239
- * @returns {Object}
240
- */
241
- function sessionSummary(sessionPath) {
242
- const events = parseSessionJSONL(sessionPath);
243
- const tokens = analyzeTokenUsage(events);
244
- const waste = detectDuplicateReads(events);
245
- const confusion = detectConfusionSignals(events);
246
- const { toolCounts, filesTouched } = extractToolUsage(events);
247
-
248
- // Duration
249
- const timestamps = events
250
- .map((e) => e.timestamp)
251
- .filter(Boolean)
252
- .map((t) => new Date(t).getTime())
253
- .filter((t) => Number.isFinite(t));
254
-
255
- let durationMs = 0;
256
- let startTime = null;
257
- let endTime = null;
258
- if (timestamps.length >= 2) {
259
- startTime = new Date(Math.min(...timestamps)).toISOString();
260
- endTime = new Date(Math.max(...timestamps)).toISOString();
261
- durationMs = Math.max(...timestamps) - Math.min(...timestamps);
262
- }
263
-
264
- return {
265
- sessionPath,
266
- eventCount: events.length,
267
- duration: {
268
- ms: durationMs,
269
- human: formatDuration(durationMs),
270
- startTime,
271
- endTime,
272
- },
273
- tokens: tokens.totals,
274
- tokenTurns: tokens.turns.length,
275
- toolCounts,
276
- filesTouched,
277
- confusionSignals: confusion.length,
278
- confusionDetails: confusion,
279
- waste: {
280
- duplicateReads: waste.duplicateReads,
281
- wasteScore: waste.wasteScore,
282
- totalReads: waste.totalReads,
283
- wastedReads: waste.wastedReads,
284
- },
285
- };
286
- }
287
-
288
- function formatDuration(ms) {
289
- if (ms < 1000) return `${ms}ms`;
290
- const seconds = Math.floor(ms / 1000);
291
- const minutes = Math.floor(seconds / 60);
292
- const hours = Math.floor(minutes / 60);
293
- if (hours > 0) return `${hours}h ${minutes % 60}m`;
294
- if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
295
- return `${seconds}s`;
296
- }
297
-
298
- // ---------------------------------------------------------------------------
299
- // 6. Integration with ThumbGate Lessons
300
- // ---------------------------------------------------------------------------
301
-
302
- /**
303
- * Analyze a session and create ThumbGate lessons from confusion signals.
304
- * @param {string} sessionPath
305
- * @returns {{ summary: Object, lessonsCreated: Array }}
306
- */
307
- function analyzeAndCreateLessons(sessionPath) {
308
- const summary = sessionSummary(sessionPath);
309
- const lessonsCreated = [];
310
-
311
- // Group confusion signals by keyword
312
- const keywordCounts = {};
313
- for (const signal of summary.confusionDetails) {
314
- const key = `${signal.category}:${signal.keyword}`;
315
- if (!keywordCounts[key]) {
316
- keywordCounts[key] = { category: signal.category, keyword: signal.keyword, count: 0, contexts: [] };
317
- }
318
- keywordCounts[key].count += 1;
319
- if (keywordCounts[key].contexts.length < 3) {
320
- keywordCounts[key].contexts.push(signal.context);
321
- }
322
- }
323
-
324
- // Create lessons for signals that appear 2+ times
325
- const { createLesson } = require('./lesson-inference');
326
-
327
- for (const [, info] of Object.entries(keywordCounts)) {
328
- if (info.count < 2) continue;
329
-
330
- const lessonText = `AVOID: ${info.category} pattern detected — "${info.keyword}" appeared ${info.count} times. Example: "${info.contexts[0]}"`;
331
-
332
- const lesson = createLesson({
333
- signal: 'negative',
334
- inferredLesson: lessonText,
335
- triggerMessage: `Session analysis: confusion signal "${info.keyword}" (${info.category})`,
336
- priorSummary: `Auto-detected from session transcript at ${sessionPath}`,
337
- confidence: Math.min(90, 50 + info.count * 10),
338
- tags: ['session-analysis', info.category, 'auto-learned'],
339
- metadata: {
340
- source: 'session-analyzer',
341
- keyword: info.keyword,
342
- occurrences: info.count,
343
- sessionPath,
344
- },
345
- });
346
-
347
- lessonsCreated.push(lesson);
348
- }
349
-
350
- // Create lesson for high waste score
351
- if (summary.waste.wasteScore > 20 && Object.keys(summary.waste.duplicateReads).length > 0) {
352
- const topDuplicates = Object.entries(summary.waste.duplicateReads)
353
- .sort((a, b) => b[1] - a[1])
354
- .slice(0, 3)
355
- .map(([fp, count]) => `${path.basename(fp)} (${count}x)`)
356
- .join(', ');
357
-
358
- const lesson = createLesson({
359
- signal: 'negative',
360
- inferredLesson: `AVOID: duplicate file reads detected (waste score ${summary.waste.wasteScore}%). Top offenders: ${topDuplicates}`,
361
- triggerMessage: 'Session analysis: duplicate Read tool calls',
362
- priorSummary: `Auto-detected from session transcript at ${sessionPath}`,
363
- confidence: Math.min(85, 40 + summary.waste.wasteScore),
364
- tags: ['session-analysis', 'waste', 'duplicate-reads', 'auto-learned'],
365
- metadata: {
366
- source: 'session-analyzer',
367
- wasteScore: summary.waste.wasteScore,
368
- duplicateReads: summary.waste.duplicateReads,
369
- sessionPath,
370
- },
371
- });
372
-
373
- lessonsCreated.push(lesson);
374
- }
375
-
376
- return { summary, lessonsCreated };
377
- }
378
-
379
- // ---------------------------------------------------------------------------
380
- // 7. Session Discovery
381
- // ---------------------------------------------------------------------------
382
-
383
- /**
384
- * List recent Claude Code sessions from ~/.claude/projects/.
385
- * @param {Object} opts
386
- * @param {number} opts.recent - number of recent sessions to return (default 10)
387
- * @returns {Array<{ path: string, project: string, sessionId: string, modified: Date, size: number }>}
388
- */
389
- function listSessions({ recent = 10 } = {}) {
390
- const projectsDir = path.join(os.homedir(), '.claude', 'projects');
391
- if (!fs.existsSync(projectsDir)) return [];
392
-
393
- const sessions = [];
394
-
395
- try {
396
- const projectDirs = fs.readdirSync(projectsDir, { withFileTypes: true });
397
- for (const pd of projectDirs) {
398
- if (!pd.isDirectory()) continue;
399
- const projectPath = path.join(projectsDir, pd.name);
400
- try {
401
- const files = fs.readdirSync(projectPath);
402
- for (const file of files) {
403
- if (!file.endsWith('.jsonl')) continue;
404
- const fullPath = path.join(projectPath, file);
405
- try {
406
- const stat = fs.statSync(fullPath);
407
- sessions.push({
408
- path: fullPath,
409
- project: pd.name,
410
- sessionId: file.replace('.jsonl', ''),
411
- modified: stat.mtime,
412
- size: stat.size,
413
- });
414
- } catch {
415
- // skip inaccessible files
416
- }
417
- }
418
- } catch {
419
- // skip inaccessible directories
420
- }
421
- }
422
- } catch {
423
- // projects dir not readable
424
- }
425
-
426
- sessions.sort((a, b) => b.modified - a.modified);
427
- return sessions.slice(0, recent);
428
- }
429
-
430
- // ---------------------------------------------------------------------------
431
- // 8. CLI
432
- // ---------------------------------------------------------------------------
433
-
434
- function runCLI() {
435
- const args = process.argv.slice(2);
436
- const command = args[0];
437
-
438
- if (!command) {
439
- console.log(`Usage:
440
- node scripts/session-analyzer.js summary <session-path>
441
- node scripts/session-analyzer.js tokens <session-path>
442
- node scripts/session-analyzer.js waste <session-path>
443
- node scripts/session-analyzer.js confusion <session-path>
444
- node scripts/session-analyzer.js auto-learn <session-path>
445
- node scripts/session-analyzer.js list [--recent N]`);
446
- process.exit(1);
447
- }
448
-
449
- switch (command) {
450
- case 'summary': {
451
- const sp = args[1];
452
- if (!sp) { console.error('Error: session path required'); process.exit(1); }
453
- console.log(JSON.stringify(sessionSummary(sp), null, 2));
454
- break;
455
- }
456
- case 'tokens': {
457
- const sp = args[1];
458
- if (!sp) { console.error('Error: session path required'); process.exit(1); }
459
- const events = parseSessionJSONL(sp);
460
- const tokens = analyzeTokenUsage(events);
461
- console.log(JSON.stringify(tokens, null, 2));
462
- break;
463
- }
464
- case 'waste': {
465
- const sp = args[1];
466
- if (!sp) { console.error('Error: session path required'); process.exit(1); }
467
- const events = parseSessionJSONL(sp);
468
- const waste = detectDuplicateReads(events);
469
- console.log(JSON.stringify(waste, null, 2));
470
- break;
471
- }
472
- case 'confusion': {
473
- const sp = args[1];
474
- if (!sp) { console.error('Error: session path required'); process.exit(1); }
475
- const events = parseSessionJSONL(sp);
476
- const confusion = detectConfusionSignals(events);
477
- console.log(JSON.stringify(confusion, null, 2));
478
- break;
479
- }
480
- case 'auto-learn': {
481
- const sp = args[1];
482
- if (!sp) { console.error('Error: session path required'); process.exit(1); }
483
- const result = analyzeAndCreateLessons(sp);
484
- console.log(JSON.stringify({
485
- confusionSignals: result.summary.confusionSignals,
486
- wasteScore: result.summary.waste.wasteScore,
487
- lessonsCreated: result.lessonsCreated.length,
488
- lessons: result.lessonsCreated.map((l) => ({ id: l.id, lesson: l.lesson })),
489
- }, null, 2));
490
- break;
491
- }
492
- case 'list': {
493
- let recent = 10;
494
- const recentIdx = args.indexOf('--recent');
495
- if (recentIdx !== -1 && args[recentIdx + 1]) {
496
- recent = Number.parseInt(args[recentIdx + 1], 10) || 10;
497
- }
498
- const sessions = listSessions({ recent });
499
- console.log(JSON.stringify(sessions, null, 2));
500
- break;
501
- }
502
- default:
503
- console.error(`Unknown command: ${command}`);
504
- process.exit(1);
505
- }
506
- }
507
-
508
- function isCliEntryPoint() {
509
- return Boolean(process.argv[1]) && path.resolve(process.argv[1]) === __filename;
510
- }
511
-
512
- // ---------------------------------------------------------------------------
513
- // Exports
514
- // ---------------------------------------------------------------------------
515
-
516
- module.exports = {
517
- parseSessionJSONL,
518
- analyzeTokenUsage,
519
- detectDuplicateReads,
520
- detectConfusionSignals,
521
- extractToolUsage,
522
- sessionSummary,
523
- analyzeAndCreateLessons,
524
- listSessions,
525
- formatDuration,
526
- runCLI,
527
- isCliEntryPoint,
528
- CONFUSION_KEYWORDS,
529
- };
530
-
531
- if (isCliEntryPoint()) {
532
- runCLI();
533
- }
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { readFileSync } from 'node:fs';
4
-
5
- async function main() {
6
- try {
7
- const raw = readFileSync(0, 'utf8');
8
- const parsed = raw.trim() ? JSON.parse(raw) : {};
9
- const { record = {}, options = {} } = parsed;
10
-
11
- const { ShieldCortexGuardedMemoryBridge } = await import('shieldcortex');
12
-
13
- const backend = {
14
- name: 'thumbgate-ingress',
15
- async save() {
16
- return { id: 'memory-ingress-probe' };
17
- },
18
- };
19
-
20
- const bridge = new ShieldCortexGuardedMemoryBridge(backend, {
21
- mode: options.mode ?? 'strict',
22
- sourceType: options.sourceType ?? 'hook',
23
- sourceIdentifier: options.sourceIdentifier ?? 'feedback-loop',
24
- blockOnThreat: true,
25
- });
26
-
27
- const result = await bridge.save(record);
28
- const defence = result.defence || {};
29
- const firewall = defence.firewall || {};
30
-
31
- process.stdout.write(JSON.stringify({
32
- available: true,
33
- allowed: Boolean(result.allowed),
34
- provider: 'shieldcortex',
35
- mode: options.mode ?? 'strict',
36
- reason: result.reason || firewall.reason || 'ShieldCortex decision completed.',
37
- threatIndicators: Array.isArray(firewall.threatIndicators) ? firewall.threatIndicators : [],
38
- blockedPatterns: Array.isArray(firewall.blockedPatterns) ? firewall.blockedPatterns : [],
39
- firewallResult: firewall.result || null,
40
- anomalyScore: firewall.anomalyScore ?? null,
41
- sensitivityLevel: defence.sensitivity ? defence.sensitivity.level : null,
42
- trustScore: defence.trust ? defence.trust.score : null,
43
- auditId: defence.auditId ?? null,
44
- }));
45
- } catch (error) {
46
- process.stdout.write(JSON.stringify({
47
- available: false,
48
- error: error.message,
49
- }));
50
- }
51
- }
52
-
53
- await main();