thumbgate 1.4.3 → 1.4.5
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +12 -8
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +18 -8
- package/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/config/github-about.json +2 -2
- package/package.json +158 -10
- package/scripts/billing.js +5 -2
- package/scripts/statusline.sh +1 -0
- package/src/api/server.js +113 -16
- package/src/index.js +3 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +0 -18
- package/.claude-plugin/bundle/server/index.js +0 -24
- package/adapters/chatgpt/INSTALL.md +0 -158
- package/adapters/perplexity/.mcp.json +0 -36
- package/adapters/perplexity/config.toml +0 -16
- package/adapters/perplexity/opencode.json +0 -29
- package/bin/memory.sh +0 -64
- package/bin/obsidian-sync.sh +0 -20
- package/plugins/amp-skill/INSTALL.md +0 -52
- package/plugins/amp-skill/SKILL.md +0 -64
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
- package/plugins/claude-codex-bridge/.mcp.json +0 -14
- package/plugins/claude-codex-bridge/INSTALL.md +0 -43
- package/plugins/claude-codex-bridge/README.md +0 -46
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
- package/plugins/claude-skill/INSTALL.md +0 -55
- package/plugins/claude-skill/SKILL.md +0 -46
- package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
- package/plugins/codex-profile/.mcp.json +0 -14
- package/plugins/codex-profile/AGENTS.md +0 -20
- package/plugins/codex-profile/INSTALL.md +0 -89
- package/plugins/codex-profile/README.md +0 -61
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
- package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
- package/plugins/cursor-marketplace/LICENSE +0 -21
- package/plugins/cursor-marketplace/README.md +0 -124
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
- package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
- package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
- package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
- package/plugins/cursor-marketplace/mcp.json +0 -14
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
- package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
- package/plugins/gemini-extension/INSTALL.md +0 -92
- package/plugins/gemini-extension/gemini_prompt.txt +0 -14
- package/plugins/gemini-extension/tool_contract.json +0 -45
- package/plugins/opencode-profile/INSTALL.md +0 -57
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +0 -474
- package/public/compare/mem0.html +0 -189
- package/public/compare/speclock.html +0 -180
- package/public/compare.html +0 -310
- package/public/dashboard.html +0 -1100
- package/public/guide.html +0 -317
- package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
- package/public/guides/codex-cli-guardrails.html +0 -158
- package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
- package/public/guides/pre-action-gates.html +0 -162
- package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
- package/public/index.html +0 -1225
- package/public/js/buyer-intent.js +0 -252
- package/public/learn/agent-harness-pattern.html +0 -180
- package/public/learn/ai-agent-persistent-memory.html +0 -203
- package/public/learn/learn.css +0 -45
- package/public/learn/mcp-pre-action-gates-explained.html +0 -172
- package/public/learn/stop-ai-agent-force-push.html +0 -134
- package/public/learn/vibe-coding-safety-net.html +0 -142
- package/public/learn.html +0 -274
- package/public/lessons.html +0 -967
- package/public/llm-context.md +0 -156
- package/public/pro.html +0 -1087
- package/public/vercel.json +0 -8
- package/scripts/a2ui-engine.js +0 -73
- package/scripts/adk-consolidator.js +0 -274
- package/scripts/agent-security-hardening.js +0 -225
- package/scripts/ai-search-visibility.js +0 -116
- package/scripts/autonomous-sales-agent.js +0 -39
- package/scripts/autoresearch-runner.js +0 -216
- package/scripts/background-agent-governance.js +0 -229
- package/scripts/behavioral-extraction.js +0 -93
- package/scripts/budget-enforcer.js +0 -173
- package/scripts/budget-guard.js +0 -173
- package/scripts/build-claude-mcpb.js +0 -255
- package/scripts/build-codex-plugin.js +0 -152
- package/scripts/capture-railway-diagnostics.sh +0 -97
- package/scripts/changeset-check.js +0 -372
- package/scripts/check-congruence.js +0 -443
- package/scripts/computer-use-firewall.js +0 -280
- package/scripts/content-engine/linkedin-content-generator.js +0 -154
- package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
- package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
- package/scripts/content-engine/reddit-thread-finder.js +0 -154
- package/scripts/context-engine.js +0 -710
- package/scripts/daily-digest.js +0 -11
- package/scripts/data-governance.js +0 -173
- package/scripts/deploy-gcp.sh +0 -44
- package/scripts/deploy-policy.js +0 -249
- package/scripts/disagreement-mining.js +0 -315
- package/scripts/dpo-optimizer.js +0 -206
- package/scripts/ensure-repo-bootstrap.js +0 -130
- package/scripts/ephemeral-agent-store.js +0 -212
- package/scripts/eval-harness.js +0 -56
- package/scripts/export-kto-pairs.js +0 -309
- package/scripts/export-training.js +0 -446
- package/scripts/feedback-fallback.js +0 -111
- package/scripts/feedback-inbox-read.js +0 -162
- package/scripts/feedback-root-consolidator.js +0 -233
- package/scripts/feedback-to-memory.js +0 -185
- package/scripts/gate-satisfy.js +0 -42
- package/scripts/generate-paperbanana-diagrams.sh +0 -99
- package/scripts/generate-pretool-hook.sh +0 -40
- package/scripts/github-about.js +0 -430
- package/scripts/github-outreach.js +0 -65
- package/scripts/gtm-revenue-loop.js +0 -535
- package/scripts/hallucination-detector.js +0 -226
- package/scripts/hf-papers.js +0 -317
- package/scripts/hook-auto-capture.sh +0 -100
- package/scripts/hook-stop-pr-thread-check.sh +0 -68
- package/scripts/hook-stop-self-score.sh +0 -51
- package/scripts/hook-stop-verify-deploy.sh +0 -31
- package/scripts/hook-verify-before-done.sh +0 -20
- package/scripts/managed-dpo-export.js +0 -91
- package/scripts/markdown-escape.js +0 -12
- package/scripts/marketing-experiment.js +0 -657
- package/scripts/memalign-recall.js +0 -111
- package/scripts/memory-migration.js +0 -296
- package/scripts/meta-policy.js +0 -190
- package/scripts/metered-billing.js +0 -16
- package/scripts/model-tier-router.js +0 -310
- package/scripts/money-watcher.js +0 -218
- package/scripts/multi-hop-recall.js +0 -240
- package/scripts/per-step-scoring.js +0 -163
- package/scripts/perplexity-command-center.js +0 -644
- package/scripts/perplexity-marketing.js +0 -454
- package/scripts/pii-scanner.js +0 -153
- package/scripts/plan-gate.js +0 -154
- package/scripts/post-everywhere.js +0 -341
- package/scripts/post-to-x-retry.sh +0 -22
- package/scripts/post-to-x.js +0 -369
- package/scripts/pr-manager.js +0 -421
- package/scripts/principle-extractor.js +0 -162
- package/scripts/pro-features.js +0 -41
- package/scripts/prompt-dlp.js +0 -222
- package/scripts/prove-adapters.js +0 -860
- package/scripts/prove-attribution.js +0 -361
- package/scripts/prove-automation.js +0 -651
- package/scripts/prove-autoresearch.js +0 -304
- package/scripts/prove-claim-verification.js +0 -277
- package/scripts/prove-cloudflare-sandbox.js +0 -161
- package/scripts/prove-data-pipeline.js +0 -408
- package/scripts/prove-data-quality.js +0 -227
- package/scripts/prove-evolution.js +0 -352
- package/scripts/prove-harnesses.js +0 -287
- package/scripts/prove-intelligence.js +0 -257
- package/scripts/prove-lancedb.js +0 -425
- package/scripts/prove-local-intelligence.js +0 -340
- package/scripts/prove-loop-closure.js +0 -263
- package/scripts/prove-packaged-runtime.js +0 -327
- package/scripts/prove-predictive-insights.js +0 -355
- package/scripts/prove-runtime.js +0 -363
- package/scripts/prove-seo-gsd.js +0 -234
- package/scripts/prove-settings.js +0 -279
- package/scripts/prove-subway-upgrades.js +0 -277
- package/scripts/prove-tessl.js +0 -229
- package/scripts/prove-training-export.js +0 -325
- package/scripts/prove-workflow-contract.js +0 -112
- package/scripts/prove-xmemory.js +0 -332
- package/scripts/publish-decision.js +0 -159
- package/scripts/ralph-loop.js +0 -376
- package/scripts/ralph-mode-ci.js +0 -434
- package/scripts/reddit-dm-outreach.js +0 -192
- package/scripts/reddit-monitor-cron.sh +0 -26
- package/scripts/reminder-engine.js +0 -132
- package/scripts/revenue-status.js +0 -472
- package/scripts/rotate-stripe-webhook-secret.js +0 -314
- package/scripts/schedule-manager.js +0 -249
- package/scripts/self-healing-check.js +0 -193
- package/scripts/session-analyzer.js +0 -533
- package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
- package/scripts/skill-exporter.js +0 -260
- package/scripts/skill-materializer.js +0 -134
- package/scripts/skill-packs.js +0 -136
- package/scripts/skill-proposer.js +0 -99
- package/scripts/skill-quality-tracker.js +0 -282
- package/scripts/slow-loop.js +0 -72
- package/scripts/social-analytics/db/marketing-db.js +0 -179
- package/scripts/social-analytics/db/schema.sql +0 -55
- package/scripts/social-analytics/digest.js +0 -256
- package/scripts/social-analytics/engagement-audit.js +0 -185
- package/scripts/social-analytics/generate-instagram-card.js +0 -123
- package/scripts/social-analytics/generate-slides.js +0 -268
- package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
- package/scripts/social-analytics/install-growth-automation.js +0 -114
- package/scripts/social-analytics/load-env.js +0 -77
- package/scripts/social-analytics/mcp-server.js +0 -289
- package/scripts/social-analytics/normalizer.js +0 -580
- package/scripts/social-analytics/notify.js +0 -162
- package/scripts/social-analytics/poll-all.js +0 -107
- package/scripts/social-analytics/pollers/github.js +0 -195
- package/scripts/social-analytics/pollers/instagram.js +0 -253
- package/scripts/social-analytics/pollers/linkedin.js +0 -340
- package/scripts/social-analytics/pollers/plausible.js +0 -245
- package/scripts/social-analytics/pollers/reddit.js +0 -306
- package/scripts/social-analytics/pollers/threads.js +0 -233
- package/scripts/social-analytics/pollers/tiktok.js +0 -203
- package/scripts/social-analytics/pollers/x.js +0 -227
- package/scripts/social-analytics/pollers/youtube.js +0 -304
- package/scripts/social-analytics/pollers/zernio.js +0 -183
- package/scripts/social-analytics/post-video.js +0 -316
- package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
- package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
- package/scripts/social-analytics/publishers/devto.js +0 -122
- package/scripts/social-analytics/publishers/instagram.js +0 -317
- package/scripts/social-analytics/publishers/linkedin.js +0 -294
- package/scripts/social-analytics/publishers/reddit.js +0 -385
- package/scripts/social-analytics/publishers/threads.js +0 -275
- package/scripts/social-analytics/publishers/tiktok.js +0 -217
- package/scripts/social-analytics/publishers/x.js +0 -259
- package/scripts/social-analytics/publishers/youtube.js +0 -223
- package/scripts/social-analytics/publishers/zernio.js +0 -568
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
- package/scripts/social-analytics/run-digest.js +0 -34
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
- package/scripts/social-analytics/store.js +0 -455
- package/scripts/social-analytics/sync-launch-assets.js +0 -185
- package/scripts/social-analytics/utm.js +0 -143
- package/scripts/social-pipeline.js +0 -2626
- package/scripts/social-post-hourly.js +0 -228
- package/scripts/social-quality-gate.js +0 -134
- package/scripts/social-reply-monitor.js +0 -592
- package/scripts/status-dashboard.js +0 -155
- package/scripts/stripe-live-status.js +0 -115
- package/scripts/subagent-profiles.js +0 -79
- package/scripts/sync-branch-protection.js +0 -340
- package/scripts/sync-gh-secrets-from-env.sh +0 -70
- package/scripts/sync-github-about.js +0 -55
- package/scripts/sync-version.js +0 -479
- package/scripts/synthetic-dpo.js +0 -234
- package/scripts/tessl-export.js +0 -369
- package/scripts/test-coverage.js +0 -128
- package/scripts/thumbgate-bench.js +0 -494
- package/scripts/thumbgate_session_start.sh +0 -32
- package/scripts/train_from_feedback.py +0 -929
- package/scripts/validate-feedback.js +0 -581
- package/scripts/verify-obsidian-setup.sh +0 -269
- package/scripts/verify-run.js +0 -269
- package/scripts/weekly-auto-post.js +0 -124
- 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();
|