@statforge/claudestat 1.6.1 → 1.7.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.
- package/README.md +3 -1
- package/dashboard/dist/assets/AnalyticsView-DDGLDoCN.js +7 -0
- package/dashboard/dist/assets/HistoryView-DkPfrNrv.js +1 -0
- package/dashboard/dist/assets/LineChart-BOWYkkEW.js +2 -0
- package/dashboard/dist/assets/ProjectsView-VRoRiEL4.js +6 -0
- package/dashboard/dist/assets/SystemView-B2zbIxhY.js +1 -0
- package/dashboard/dist/assets/TopView-C2qdsy0Y.js +1 -0
- package/dashboard/dist/assets/index-CMhe3KaT.js +84 -0
- package/dashboard/dist/assets/shared-BbBtsdh1.js +1 -0
- package/dashboard/dist/assets/{vendor-lucide-Cym0q5l_.js → vendor-lucide-ClCW-axQ.js} +79 -64
- package/dashboard/dist/assets/{vendor-react-B_Jzs0gY.js → vendor-react-gHSHIE2L.js} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/dist/daemon.js +57 -1
- package/dist/db.d.ts +76 -2
- package/dist/db.js +295 -65
- package/dist/doctor.js +1 -1
- package/dist/enricher.d.ts +3 -2
- package/dist/enricher.js +10 -5
- package/dist/index.js +12 -1
- package/dist/intelligence.d.ts +55 -0
- package/dist/intelligence.js +163 -1
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +8 -0
- package/dist/pricing.d.ts +2 -0
- package/dist/pricing.js +12 -1
- package/dist/routes/events.js +136 -5
- package/dist/routes/history.js +6 -2
- package/dist/routes/intents.d.ts +1 -0
- package/dist/routes/intents.js +155 -0
- package/dist/routes/misc.js +131 -3
- package/dist/routes/opencode-reader.js +39 -3
- package/dist/routes/projects.js +10 -1
- package/dist/routes/replay.d.ts +1 -0
- package/dist/routes/replay.js +29 -0
- package/dist/routes/reports.js +7 -0
- package/dist/routes/top.js +8 -1
- package/dist/watchers/adapter.d.ts +1 -0
- package/dist/watchers/claude-code.d.ts +16 -1
- package/dist/watchers/claude-code.js +201 -76
- package/dist/watchers/opencode.d.ts +1 -0
- package/dist/watchers/opencode.js +152 -14
- package/package.json +1 -1
- package/dashboard/dist/assets/AnalyticsView-5bUM3UHp.js +0 -8
- package/dashboard/dist/assets/HistoryView-C-AsEqos.js +0 -1
- package/dashboard/dist/assets/ProjectsView-D9bZBdY2.js +0 -6
- package/dashboard/dist/assets/SystemView-DIYDCCF3.js +0 -1
- package/dashboard/dist/assets/TopView-DhdLpsiA.js +0 -1
- package/dashboard/dist/assets/index-DgbWvj42.js +0 -84
package/dist/daemon.js
CHANGED
|
@@ -51,9 +51,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
51
51
|
};
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
53
|
exports.startDaemon = startDaemon;
|
|
54
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
54
55
|
const express_1 = __importDefault(require("express"));
|
|
55
56
|
const path_1 = __importDefault(require("path"));
|
|
56
57
|
const fs_1 = __importDefault(require("fs"));
|
|
58
|
+
const os_1 = __importDefault(require("os"));
|
|
57
59
|
const db_1 = require("./db");
|
|
58
60
|
const enricher_1 = require("./enricher");
|
|
59
61
|
const config_1 = require("./config");
|
|
@@ -67,6 +69,8 @@ const misc_1 = require("./routes/misc");
|
|
|
67
69
|
const reports_1 = require("./routes/reports");
|
|
68
70
|
const top_1 = require("./routes/top");
|
|
69
71
|
const opencode_reader_1 = require("./routes/opencode-reader");
|
|
72
|
+
const replay_1 = require("./routes/replay");
|
|
73
|
+
const intents_1 = require("./routes/intents");
|
|
70
74
|
const projects_cache_1 = require("./cache/projects-cache");
|
|
71
75
|
const rate_limiter_1 = require("./middleware/rate-limiter");
|
|
72
76
|
const summarizer_1 = require("./summarizer");
|
|
@@ -91,6 +95,8 @@ app.use(misc_1.miscRouter);
|
|
|
91
95
|
app.use(reports_1.reportsRouter);
|
|
92
96
|
app.use(top_1.topRouter);
|
|
93
97
|
app.use(opencode_reader_1.opencodeReaderRouter);
|
|
98
|
+
app.use(replay_1.replayRouter);
|
|
99
|
+
app.use(intents_1.intentsRouter);
|
|
94
100
|
// ─── GET /health — necesita acceso al tamaño del pool SSE ─────────────────────
|
|
95
101
|
app.get('/health', (_req, res) => {
|
|
96
102
|
res.json({ status: 'ok', port: PORT, clients: (0, stream_1.getSseClientsSize)() });
|
|
@@ -112,9 +118,10 @@ app.get('*', (_req, res) => {
|
|
|
112
118
|
// ─── Migración de arranque: etiquetar sesiones históricas ────────────────────
|
|
113
119
|
function migrateSessionProjects() {
|
|
114
120
|
const sessions = db_1.dbOps.getAllSessions();
|
|
121
|
+
const HOME = os_1.default.homedir();
|
|
115
122
|
let tagged = 0;
|
|
116
123
|
for (const session of sessions) {
|
|
117
|
-
if (session?.project_path)
|
|
124
|
+
if (session?.project_path && session.project_path !== HOME)
|
|
118
125
|
continue;
|
|
119
126
|
const events = db_1.dbOps.getSessionEvents(session.id);
|
|
120
127
|
const projectCwd = (0, projects_1.inferProjectCwd)(events);
|
|
@@ -149,10 +156,14 @@ async function migrateSessionSummaries(limit = 5) {
|
|
|
149
156
|
}
|
|
150
157
|
}
|
|
151
158
|
}
|
|
159
|
+
// ─── Previous hashes map for external change detection ──────────────────────────
|
|
160
|
+
let _prevHashes = new Map();
|
|
152
161
|
// ─── Interval refs for cleanup ────────────────────────────────────────────────
|
|
153
162
|
let projectCacheInterval = null;
|
|
154
163
|
let reportInterval = null;
|
|
155
164
|
let alertInterval = null;
|
|
165
|
+
let intentCleanupInterval = null;
|
|
166
|
+
let intentWatchInterval = null;
|
|
156
167
|
function shutdown(server) {
|
|
157
168
|
(0, enricher_1.stopEnricher)();
|
|
158
169
|
(0, rate_limiter_1.stopRateLimiter)();
|
|
@@ -168,6 +179,15 @@ function shutdown(server) {
|
|
|
168
179
|
clearInterval(alertInterval);
|
|
169
180
|
alertInterval = null;
|
|
170
181
|
}
|
|
182
|
+
if (intentCleanupInterval) {
|
|
183
|
+
clearInterval(intentCleanupInterval);
|
|
184
|
+
intentCleanupInterval = null;
|
|
185
|
+
}
|
|
186
|
+
if (intentWatchInterval) {
|
|
187
|
+
clearInterval(intentWatchInterval);
|
|
188
|
+
intentWatchInterval = null;
|
|
189
|
+
}
|
|
190
|
+
_prevHashes.clear();
|
|
171
191
|
cleanPid();
|
|
172
192
|
server.close(() => { });
|
|
173
193
|
}
|
|
@@ -293,6 +313,42 @@ function startDaemon() {
|
|
|
293
313
|
db_1.dbOps.insertWeeklyReport(dateLabel, markdown);
|
|
294
314
|
console.log(`[daemon] Report auto-generated: ${dateLabel}`);
|
|
295
315
|
}, 60000);
|
|
316
|
+
// Al arrancar: liberar intents activos de sesiones anteriores (ya no hay nadie que los use)
|
|
317
|
+
db_1.dbOps.releaseOrphanedIntents();
|
|
318
|
+
// Cleanup de intents — cada 2min marca stale los sin heartbeat > 10min, borra viejos > 1h
|
|
319
|
+
intentCleanupInterval = setInterval(() => {
|
|
320
|
+
db_1.dbOps.markStaleIntents();
|
|
321
|
+
db_1.dbOps.deleteOldStaleIntents();
|
|
322
|
+
}, 2 * 60000);
|
|
323
|
+
// Watcher de cambios externos — cada 10s compara hashes de archivos en intents activos
|
|
324
|
+
intentWatchInterval = setInterval(() => {
|
|
325
|
+
const active = db_1.dbOps.getActiveIntents();
|
|
326
|
+
if (active.length === 0)
|
|
327
|
+
return;
|
|
328
|
+
const fileSet = new Set(active.map(a => a.file_path));
|
|
329
|
+
for (const filePath of fileSet) {
|
|
330
|
+
try {
|
|
331
|
+
if (!fs_1.default.existsSync(filePath))
|
|
332
|
+
continue;
|
|
333
|
+
const content = fs_1.default.readFileSync(filePath);
|
|
334
|
+
const hash = crypto_1.default.createHash('sha256').update(content).digest('hex');
|
|
335
|
+
const prev = _prevHashes.get(filePath);
|
|
336
|
+
if (prev && prev !== hash) {
|
|
337
|
+
// Check if any active intent covers this file
|
|
338
|
+
const coveringIntent = active.find(a => a.file_path === filePath);
|
|
339
|
+
const tool = coveringIntent?.tool ?? 'unknown';
|
|
340
|
+
(0, stream_1.broadcast)({ type: 'external_change', payload: { file: filePath, tool, hash_prev: prev, hash_now: hash } });
|
|
341
|
+
}
|
|
342
|
+
_prevHashes.set(filePath, hash);
|
|
343
|
+
}
|
|
344
|
+
catch { }
|
|
345
|
+
}
|
|
346
|
+
// Cleanup stale entries from prevHashes
|
|
347
|
+
for (const [fp] of _prevHashes) {
|
|
348
|
+
if (!fileSet.has(fp))
|
|
349
|
+
_prevHashes.delete(fp);
|
|
350
|
+
}
|
|
351
|
+
}, 10000);
|
|
296
352
|
// Summaries IA solo si opt-in explícito (CLAUDESTAT_AI_SUMMARY=true)
|
|
297
353
|
if (process.env.CLAUDESTAT_AI_SUMMARY === 'true') {
|
|
298
354
|
migrateSessionSummaries(5).catch(() => { });
|
package/dist/db.d.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* El warning "ExperimentalWarning" se suprime en index.ts.
|
|
10
10
|
*/
|
|
11
11
|
export declare const CLAUDESTAT_DIR: string;
|
|
12
|
+
export declare const TOOL_RESPONSE_MAX_BYTES = 8192;
|
|
13
|
+
export declare function capToolResponse(raw: string): string;
|
|
12
14
|
export interface SessionRow {
|
|
13
15
|
id: string;
|
|
14
16
|
cwd?: string;
|
|
@@ -26,6 +28,22 @@ export interface SessionRow {
|
|
|
26
28
|
dominant_model?: string;
|
|
27
29
|
parent_session_id?: string;
|
|
28
30
|
source?: string;
|
|
31
|
+
exact_retries?: number;
|
|
32
|
+
error_rate?: number;
|
|
33
|
+
file_churn_score?: number;
|
|
34
|
+
seq_cycle_count?: number;
|
|
35
|
+
avg_output_chars?: number;
|
|
36
|
+
error_block_count?: number;
|
|
37
|
+
semantic_loop_count?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface AssistantTurnRow {
|
|
40
|
+
turn_index: number;
|
|
41
|
+
ts?: number;
|
|
42
|
+
text_preview?: string;
|
|
43
|
+
tool_calls?: string[];
|
|
44
|
+
error_count: number;
|
|
45
|
+
output_chars: number;
|
|
46
|
+
context_used: number;
|
|
29
47
|
}
|
|
30
48
|
export interface EventRow {
|
|
31
49
|
id?: number;
|
|
@@ -58,17 +76,20 @@ export interface CostUpdate {
|
|
|
58
76
|
lastEntry?: BlockCostEntry;
|
|
59
77
|
lastModel?: string;
|
|
60
78
|
firstTs?: number;
|
|
79
|
+
lastTs?: number;
|
|
61
80
|
}
|
|
62
81
|
export declare const dbOps: {
|
|
63
82
|
upsertSession(s: SessionRow): void;
|
|
64
83
|
insertEvent(e: EventRow): number;
|
|
84
|
+
insertOcEvent(sessionId: string, toolName: string, ts: number, externalId: string): void;
|
|
85
|
+
insertSessionIfAbsent(s: SessionRow): void;
|
|
65
86
|
/**
|
|
66
87
|
* Al llegar PostToolUse, actualizamos el PreToolUse pendiente más reciente
|
|
67
88
|
* del mismo tool para esta sesión. Esto convierte el par Pre+Post en
|
|
68
89
|
* un único registro de tipo 'Done' con duration_ms calculado.
|
|
69
90
|
*/
|
|
70
91
|
pairPostWithPre(sessionId: string, toolName: string, response: string, postTs: number): number | null;
|
|
71
|
-
updateSessionCost(sessionId: string, cost: CostUpdate, efficiencyScore: number, loopsDetected: number): void;
|
|
92
|
+
updateSessionCost(sessionId: string, cost: CostUpdate, efficiencyScore: number, loopsDetected: number, exactRetries?: number, errorRate?: number, fileChurnScore?: number, seqCycleCount?: number): void;
|
|
72
93
|
getSessionEvents(sessionId: string): EventRow[];
|
|
73
94
|
getSession(sessionId: string): SessionRow | undefined;
|
|
74
95
|
getLatestSession(): SessionRow | undefined;
|
|
@@ -83,6 +104,11 @@ export declare const dbOps: {
|
|
|
83
104
|
count: number;
|
|
84
105
|
}[];
|
|
85
106
|
getProjectSessionStats(projectPath: string): any;
|
|
107
|
+
getProjectCliHours(): {
|
|
108
|
+
project_path: string;
|
|
109
|
+
source: string;
|
|
110
|
+
total_ms: number;
|
|
111
|
+
}[];
|
|
86
112
|
updateSessionSummary(sessionId: string, summary: string): void;
|
|
87
113
|
updateSessionParent(sessionId: string, parentId: string): void;
|
|
88
114
|
getChildSessions(parentSessionId: string): {
|
|
@@ -125,8 +151,16 @@ export declare const dbOps: {
|
|
|
125
151
|
getAnalyticsDaily(since: number): any[];
|
|
126
152
|
getAnalyticsByModel(since: number): any[];
|
|
127
153
|
getProjectHours(since: number): any[];
|
|
128
|
-
|
|
154
|
+
getCoachEvents(sessionIds: string[]): {
|
|
155
|
+
session_id: string;
|
|
156
|
+
type: string;
|
|
157
|
+
tool_name: string | null;
|
|
158
|
+
tool_input: string | null;
|
|
159
|
+
ts: number;
|
|
160
|
+
}[];
|
|
161
|
+
getTopTools(days?: number, by?: "cost" | "count" | "duration", limit?: number, source?: "all" | "claude-code" | "opencode"): {
|
|
129
162
|
tool_name: string;
|
|
163
|
+
source: string;
|
|
130
164
|
count: number;
|
|
131
165
|
total_duration_ms: number;
|
|
132
166
|
total_cost_usd: number;
|
|
@@ -158,6 +192,9 @@ export declare const dbOps: {
|
|
|
158
192
|
hour: number;
|
|
159
193
|
session_count: number;
|
|
160
194
|
}[];
|
|
195
|
+
updateSessionSemantic(sessionId: string, avgOutputChars: number, errorBlockCount: number, semanticLoopCount?: number): void;
|
|
196
|
+
upsertAssistantTurns(sessionId: string, turns: AssistantTurnRow[]): void;
|
|
197
|
+
getAssistantTurns(sessionId: string): AssistantTurnRow[];
|
|
161
198
|
getCacheReadByModel(days: number): {
|
|
162
199
|
model: string;
|
|
163
200
|
cache_read: number;
|
|
@@ -167,4 +204,41 @@ export declare const dbOps: {
|
|
|
167
204
|
total_cost: number;
|
|
168
205
|
session_count: number;
|
|
169
206
|
}[];
|
|
207
|
+
insertIntent(tool: string, sessionId: string, taskDesc: string | undefined): number;
|
|
208
|
+
insertIntentFile(intentId: number, filePath: string, operation: string, lineStart?: number, lineEnd?: number): void;
|
|
209
|
+
getWriteConflicts(filePaths: string[], excludeTool: string): {
|
|
210
|
+
file_path: string;
|
|
211
|
+
tool: string;
|
|
212
|
+
session_id: string;
|
|
213
|
+
task_desc: string | null;
|
|
214
|
+
acquired_at: number;
|
|
215
|
+
}[];
|
|
216
|
+
getIntent(id: number): {
|
|
217
|
+
tool: string;
|
|
218
|
+
session_id: string;
|
|
219
|
+
task_desc: string | null;
|
|
220
|
+
} | undefined;
|
|
221
|
+
releaseIntent(id: number): void;
|
|
222
|
+
heartbeatIntent(id: number): void;
|
|
223
|
+
getIntentFiles(id: number): {
|
|
224
|
+
file_path: string;
|
|
225
|
+
operation: string;
|
|
226
|
+
}[];
|
|
227
|
+
getActiveToolsInProject(projectPath: string, excludeTool: string): {
|
|
228
|
+
source: string;
|
|
229
|
+
last_event_at: number;
|
|
230
|
+
}[];
|
|
231
|
+
releaseOrphanedIntents(): void;
|
|
232
|
+
markStaleIntents(): {
|
|
233
|
+
id: number;
|
|
234
|
+
tool: string;
|
|
235
|
+
task_desc: string | null;
|
|
236
|
+
}[];
|
|
237
|
+
deleteOldStaleIntents(): void;
|
|
238
|
+
hasActiveIntent(tool: string, filePath: string): boolean;
|
|
239
|
+
getActiveIntents(): {
|
|
240
|
+
id: number;
|
|
241
|
+
tool: string;
|
|
242
|
+
file_path: string;
|
|
243
|
+
}[];
|
|
170
244
|
};
|