@crewx/dreaming 0.1.0-rc.18 → 0.1.0-rc.180
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/SKILL.md +72 -71
- package/dist/cli.d.ts +5 -1
- package/dist/cli.js +23 -218
- package/dist/src/analyzer.d.ts +10 -4
- package/dist/src/analyzer.js +1 -439
- package/dist/src/renderer.d.ts +3 -2
- package/dist/src/renderer.js +4 -245
- package/dist/src/scrub.d.ts +2 -2
- package/dist/src/scrub.js +1 -58
- package/dist/src/types.d.ts +19 -1
- package/dist/src/types.js +1 -3
- package/package.json +3 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/src/analyzer.d.ts.map +0 -1
- package/dist/src/analyzer.js.map +0 -1
- package/dist/src/renderer.d.ts.map +0 -1
- package/dist/src/renderer.js.map +0 -1
- package/dist/src/scrub.d.ts.map +0 -1
- package/dist/src/scrub.js.map +0 -1
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js.map +0 -1
package/dist/src/analyzer.js
CHANGED
|
@@ -1,439 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeToolName = normalizeToolName;
|
|
4
|
-
exports.extractToolCallsFromLogs = extractToolCallsFromLogs;
|
|
5
|
-
exports.analyzeTask = analyzeTask;
|
|
6
|
-
exports.summarizeAnalysis = summarizeAnalysis;
|
|
7
|
-
const sdk_1 = require("@crewx/sdk");
|
|
8
|
-
const VERIFY_COMMANDS = [
|
|
9
|
-
'tsc', 'npm test', 'npm run test', 'npm run lint', 'eslint',
|
|
10
|
-
'prettier', 'jest', 'vitest', 'biome', 'npx tsc',
|
|
11
|
-
];
|
|
12
|
-
const BASH_ERROR_PATTERNS = [
|
|
13
|
-
/error/i, /failed/i, /exit code [^0]/, /cannot find/i,
|
|
14
|
-
/does not exist/i, /FAILED/, /✗/, /ERR!/,
|
|
15
|
-
];
|
|
16
|
-
const ERROR_CATEGORIES = [
|
|
17
|
-
[/Type .+ is not assignable to type/i, '타입 불일치'],
|
|
18
|
-
[/Cannot find module/i, '모듈 경로 오류'],
|
|
19
|
-
[/Property .+ does not exist/i, '존재하지 않는 프로퍼티'],
|
|
20
|
-
[/Expected \d+ arguments/i, '인자 수 불일치'],
|
|
21
|
-
[/Cannot find name/i, '미선언 변수/함수'],
|
|
22
|
-
[/has no exported member/i, 'export 누락'],
|
|
23
|
-
[/FAIL|AssertionError|expect\(/i, '테스트 실패'],
|
|
24
|
-
[/SyntaxError|Unexpected token/i, '구문 오류'],
|
|
25
|
-
];
|
|
26
|
-
const FILE_PLACEHOLDERS = new Set(['Read File', 'Edit File', 'Write File', 'Terminal', '{}', '']);
|
|
27
|
-
function normalizeToolName(name) {
|
|
28
|
-
if (!name)
|
|
29
|
-
return '';
|
|
30
|
-
const lower = name.toLowerCase();
|
|
31
|
-
if (lower === 'bash' || lower === 'terminal')
|
|
32
|
-
return 'Bash';
|
|
33
|
-
if (lower === 'read' || lower === 'view')
|
|
34
|
-
return 'Read';
|
|
35
|
-
if (lower === 'edit' || lower === 'apply_patch')
|
|
36
|
-
return 'Edit';
|
|
37
|
-
if (lower === 'write')
|
|
38
|
-
return 'Write';
|
|
39
|
-
if (lower === 'grep' || lower === 'rg')
|
|
40
|
-
return 'Grep';
|
|
41
|
-
if (lower === 'glob' || lower === 'find')
|
|
42
|
-
return 'Find';
|
|
43
|
-
return name;
|
|
44
|
-
}
|
|
45
|
-
function shortenPath(p) {
|
|
46
|
-
const segments = p.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
47
|
-
const keep = segments.length <= 3 ? segments : segments.slice(-3);
|
|
48
|
-
return keep.join('/');
|
|
49
|
-
}
|
|
50
|
-
function extractFilePath(toolName, input) {
|
|
51
|
-
const normalized = normalizeToolName(toolName);
|
|
52
|
-
if (normalized !== 'Read' && normalized !== 'Edit' && normalized !== 'Write')
|
|
53
|
-
return null;
|
|
54
|
-
if (!input)
|
|
55
|
-
return null;
|
|
56
|
-
if (FILE_PLACEHOLDERS.has(input))
|
|
57
|
-
return null;
|
|
58
|
-
if (input.includes('*** Update File:') || input.includes('*** Add File:')) {
|
|
59
|
-
const paths = [];
|
|
60
|
-
for (const m of input.matchAll(/\*\*\*\s+(?:Update|Add)\s+File:\s*(.+)/g)) {
|
|
61
|
-
const fp = m[1].trim();
|
|
62
|
-
if (fp)
|
|
63
|
-
paths.push(shortenPath(fp));
|
|
64
|
-
}
|
|
65
|
-
return paths.length ? paths : null;
|
|
66
|
-
}
|
|
67
|
-
let cleaned = input;
|
|
68
|
-
if (/^(Read|Edit|Write|View)\s+/.test(cleaned)) {
|
|
69
|
-
cleaned = cleaned.replace(/^(Read|Edit|Write|View)\s+/, '');
|
|
70
|
-
}
|
|
71
|
-
if (/[*?{}]/.test(cleaned))
|
|
72
|
-
return null;
|
|
73
|
-
if (cleaned.length > 300)
|
|
74
|
-
return null;
|
|
75
|
-
if (!/\.\w+$/.test(cleaned) && !cleaned.includes('/'))
|
|
76
|
-
return null;
|
|
77
|
-
if (cleaned.startsWith('/') || /^[A-Za-z]:[\\/]/.test(cleaned)) {
|
|
78
|
-
return shortenPath(cleaned);
|
|
79
|
-
}
|
|
80
|
-
return cleaned;
|
|
81
|
-
}
|
|
82
|
-
function detectProvider(stdoutLines) {
|
|
83
|
-
const types = new Set();
|
|
84
|
-
for (const line of stdoutLines) {
|
|
85
|
-
try {
|
|
86
|
-
const p = JSON.parse(line);
|
|
87
|
-
if (p.type)
|
|
88
|
-
types.add(p.type);
|
|
89
|
-
}
|
|
90
|
-
catch { }
|
|
91
|
-
}
|
|
92
|
-
if (types.has('turn.started') || types.has('turn.failed') || types.has('thread.started'))
|
|
93
|
-
return 'codex';
|
|
94
|
-
if (types.has('assistant.message_delta') || types.has('tool.execution_complete'))
|
|
95
|
-
return 'copilot';
|
|
96
|
-
if (types.has('step_start') && types.has('step_finish'))
|
|
97
|
-
return 'opencode';
|
|
98
|
-
if (types.has('system') && types.has('result'))
|
|
99
|
-
return 'claude';
|
|
100
|
-
if (types.has('init') && types.has('message'))
|
|
101
|
-
return 'gemini';
|
|
102
|
-
if (types.has('result') && types.has('assistant'))
|
|
103
|
-
return 'claude';
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
function extractToolCallsFromLogs(logs) {
|
|
107
|
-
if (!Array.isArray(logs))
|
|
108
|
-
return [];
|
|
109
|
-
const logEntries = logs;
|
|
110
|
-
const stdoutLines = logEntries
|
|
111
|
-
.filter((e) => e.level === 'stdout' && e.message)
|
|
112
|
-
.map((e) => e.message);
|
|
113
|
-
const provider = detectProvider(stdoutLines);
|
|
114
|
-
const allEntries = [];
|
|
115
|
-
for (const entry of logEntries) {
|
|
116
|
-
if (entry.level === 'info' && entry.message) {
|
|
117
|
-
allEntries.push(...(0, sdk_1.defaultParseEvent)(entry.message, entry.timestamp));
|
|
118
|
-
}
|
|
119
|
-
else if (entry.level === 'stdout' && entry.message && provider) {
|
|
120
|
-
allEntries.push(...(0, sdk_1.parseStdoutEvent)('', entry.message, provider));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (!allEntries.length)
|
|
124
|
-
return [];
|
|
125
|
-
const toolUseMap = new Map();
|
|
126
|
-
const toolUseOrder = [];
|
|
127
|
-
for (const e of allEntries) {
|
|
128
|
-
if (e.type === 'tool_use') {
|
|
129
|
-
const tc = {
|
|
130
|
-
toolUseId: e.toolUseId ?? null,
|
|
131
|
-
name: normalizeToolName(e.toolName),
|
|
132
|
-
input: e.toolInput ?? null,
|
|
133
|
-
isError: null,
|
|
134
|
-
resultPreview: null,
|
|
135
|
-
};
|
|
136
|
-
const id = tc.toolUseId;
|
|
137
|
-
if (id) {
|
|
138
|
-
if (toolUseMap.has(id)) {
|
|
139
|
-
toolUseMap.get(id).input = tc.input;
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
toolUseMap.set(id, tc);
|
|
143
|
-
}
|
|
144
|
-
toolUseOrder.push(tc);
|
|
145
|
-
}
|
|
146
|
-
else if (e.type === 'tool_result') {
|
|
147
|
-
const resultE = e;
|
|
148
|
-
if (resultE.toolUseId && toolUseMap.has(resultE.toolUseId)) {
|
|
149
|
-
const tc = toolUseMap.get(resultE.toolUseId);
|
|
150
|
-
tc.isError = resultE.isError === true;
|
|
151
|
-
tc.resultPreview = resultE.resultPreview ?? null;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
for (let i = toolUseOrder.length - 1; i >= 0; i--) {
|
|
155
|
-
const tc2 = toolUseOrder[i];
|
|
156
|
-
if (tc2.resultPreview === null) {
|
|
157
|
-
tc2.isError = resultE.isError === true;
|
|
158
|
-
tc2.resultPreview = resultE.resultPreview ?? null;
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return toolUseOrder;
|
|
166
|
-
}
|
|
167
|
-
function normalizeSkillName(spanName) {
|
|
168
|
-
const bracket = spanName.match(/^\[skill:([^\]]+)\]/);
|
|
169
|
-
if (bracket) {
|
|
170
|
-
const raw = bracket[1];
|
|
171
|
-
if (raw === 'memory-v2')
|
|
172
|
-
return 'crewx/memory';
|
|
173
|
-
return raw;
|
|
174
|
-
}
|
|
175
|
-
if (spanName.startsWith('skill:'))
|
|
176
|
-
return spanName.slice(6);
|
|
177
|
-
return spanName;
|
|
178
|
-
}
|
|
179
|
-
function extractSkillSubCmd(skillName, input) {
|
|
180
|
-
if (!input)
|
|
181
|
-
return null;
|
|
182
|
-
const tokens = input.trim().split(/\s+/);
|
|
183
|
-
const stripMap = {
|
|
184
|
-
'crewx/memory': 'memory', 'crewx/doc': 'doc', 'crewx/skill': 'skill',
|
|
185
|
-
'crewx/workflow': 'workflow', 'tsserver': 'tsserver',
|
|
186
|
-
};
|
|
187
|
-
if (skillName === 'crewx/search')
|
|
188
|
-
return null;
|
|
189
|
-
let i = 0;
|
|
190
|
-
const prefix = stripMap[skillName];
|
|
191
|
-
if (prefix && tokens[0] === prefix)
|
|
192
|
-
i++;
|
|
193
|
-
const sub = tokens[i];
|
|
194
|
-
if (!sub || sub.startsWith('-') || sub.startsWith('/'))
|
|
195
|
-
return null;
|
|
196
|
-
return sub;
|
|
197
|
-
}
|
|
198
|
-
function aggregateSpans(spans) {
|
|
199
|
-
const bySkill = {};
|
|
200
|
-
for (const s of spans) {
|
|
201
|
-
if (s.input && s.input.includes('summarize-dirty'))
|
|
202
|
-
continue;
|
|
203
|
-
const skill = normalizeSkillName(s.name);
|
|
204
|
-
if (!bySkill[skill])
|
|
205
|
-
bySkill[skill] = { count: 0, subCmds: {} };
|
|
206
|
-
bySkill[skill].count++;
|
|
207
|
-
const sub = extractSkillSubCmd(skill, s.input ?? null);
|
|
208
|
-
if (sub)
|
|
209
|
-
bySkill[skill].subCmds[sub] = (bySkill[skill].subCmds[sub] ?? 0) + 1;
|
|
210
|
-
}
|
|
211
|
-
return bySkill;
|
|
212
|
-
}
|
|
213
|
-
function classifyBashCmd(cmd) {
|
|
214
|
-
if (!cmd)
|
|
215
|
-
return null;
|
|
216
|
-
for (const v of VERIFY_COMMANDS) {
|
|
217
|
-
if (cmd.includes(v))
|
|
218
|
-
return v;
|
|
219
|
-
}
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
function isBashFailed(tc) {
|
|
223
|
-
if (tc.isError === true)
|
|
224
|
-
return true;
|
|
225
|
-
if (!tc.resultPreview)
|
|
226
|
-
return false;
|
|
227
|
-
return BASH_ERROR_PATTERNS.some((p) => p.test(tc.resultPreview));
|
|
228
|
-
}
|
|
229
|
-
function categorizeError(msg) {
|
|
230
|
-
if (!msg)
|
|
231
|
-
return null;
|
|
232
|
-
for (const [pattern, cat] of ERROR_CATEGORIES) {
|
|
233
|
-
if (pattern.test(msg))
|
|
234
|
-
return cat;
|
|
235
|
-
}
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
function buildTimeline(toolCalls) {
|
|
239
|
-
const fileEditCounts = {};
|
|
240
|
-
return toolCalls.map((tc) => {
|
|
241
|
-
const name = normalizeToolName(tc.name);
|
|
242
|
-
const entry = {
|
|
243
|
-
name,
|
|
244
|
-
input: tc.input,
|
|
245
|
-
isError: tc.isError,
|
|
246
|
-
resultPreview: tc.resultPreview,
|
|
247
|
-
filePath: null,
|
|
248
|
-
bashCmd: null,
|
|
249
|
-
bashResult: null,
|
|
250
|
-
bashErrorMsg: null,
|
|
251
|
-
errorCategory: null,
|
|
252
|
-
isVerify: false,
|
|
253
|
-
editCount: null,
|
|
254
|
-
};
|
|
255
|
-
if (name === 'Read' || name === 'Edit' || name === 'Write') {
|
|
256
|
-
entry.filePath = extractFilePath(tc.name, tc.input);
|
|
257
|
-
}
|
|
258
|
-
if ((name === 'Edit' || name === 'Write') && entry.filePath) {
|
|
259
|
-
const paths = Array.isArray(entry.filePath) ? entry.filePath : [entry.filePath];
|
|
260
|
-
for (const p of paths)
|
|
261
|
-
fileEditCounts[p] = (fileEditCounts[p] ?? 0) + 1;
|
|
262
|
-
entry.editCount = fileEditCounts[paths[0]] ?? null;
|
|
263
|
-
}
|
|
264
|
-
if (name === 'Bash') {
|
|
265
|
-
entry.bashCmd = tc.input ? tc.input.split('\n')[0].slice(0, 150) : '';
|
|
266
|
-
entry.isVerify = !!classifyBashCmd(entry.bashCmd);
|
|
267
|
-
const failed = isBashFailed(tc);
|
|
268
|
-
entry.bashResult = failed ? 'fail' : 'pass';
|
|
269
|
-
if (failed && tc.resultPreview) {
|
|
270
|
-
entry.bashErrorMsg = tc.resultPreview.trim().split('\n')[0].slice(0, 100);
|
|
271
|
-
entry.errorCategory = categorizeError(tc.resultPreview);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return entry;
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
function detectPatterns(timeline) {
|
|
278
|
-
const struggles = [];
|
|
279
|
-
const efficients = [];
|
|
280
|
-
for (let i = 0; i < timeline.length; i++) {
|
|
281
|
-
const e = timeline[i];
|
|
282
|
-
if (e.name !== 'Bash' || !e.isVerify || e.bashResult !== 'pass')
|
|
283
|
-
continue;
|
|
284
|
-
const verifyType = classifyBashCmd(e.bashCmd);
|
|
285
|
-
const failIndices = [];
|
|
286
|
-
let j = i - 1;
|
|
287
|
-
while (j >= 0) {
|
|
288
|
-
const prev = timeline[j];
|
|
289
|
-
if (prev.name === 'Bash' && prev.isVerify) {
|
|
290
|
-
if (classifyBashCmd(prev.bashCmd) === verifyType && prev.bashResult === 'fail') {
|
|
291
|
-
failIndices.unshift(j);
|
|
292
|
-
}
|
|
293
|
-
else if (prev.bashResult === 'pass') {
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
j--;
|
|
298
|
-
}
|
|
299
|
-
if (failIndices.length >= 1) {
|
|
300
|
-
const startI = failIndices[0];
|
|
301
|
-
const editFiles = {};
|
|
302
|
-
const errors = [];
|
|
303
|
-
for (let k = startI; k <= i; k++) {
|
|
304
|
-
const t = timeline[k];
|
|
305
|
-
if ((t.name === 'Edit' || t.name === 'Write') && t.filePath) {
|
|
306
|
-
const paths = Array.isArray(t.filePath) ? t.filePath : [t.filePath];
|
|
307
|
-
for (const p of paths)
|
|
308
|
-
editFiles[p] = (editFiles[p] ?? 0) + 1;
|
|
309
|
-
}
|
|
310
|
-
if (t.name === 'Bash' && t.bashResult === 'fail' && t.bashErrorMsg) {
|
|
311
|
-
errors.push(t.bashErrorMsg);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
const errorCategories = errors.map(categorizeError).filter((c) => c !== null);
|
|
315
|
-
struggles.push({
|
|
316
|
-
startIdx: startI,
|
|
317
|
-
endIdx: i,
|
|
318
|
-
failCount: failIndices.length,
|
|
319
|
-
editFiles,
|
|
320
|
-
errors: [...new Set(errors)].slice(0, 3),
|
|
321
|
-
errorCategories: [...new Set(errorCategories)],
|
|
322
|
-
verifyCmd: verifyType,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
let editCount = 0, readCount = 0, k = i - 1;
|
|
327
|
-
while (k >= 0) {
|
|
328
|
-
const prev = timeline[k];
|
|
329
|
-
if (prev.name === 'Edit' || prev.name === 'Write')
|
|
330
|
-
editCount++;
|
|
331
|
-
else if (prev.name === 'Read' || prev.name === 'Grep' || prev.name === 'Find')
|
|
332
|
-
readCount++;
|
|
333
|
-
else
|
|
334
|
-
break;
|
|
335
|
-
k--;
|
|
336
|
-
}
|
|
337
|
-
if (editCount > 0) {
|
|
338
|
-
efficients.push({ startIdx: k + 1, endIdx: i, readCount, editCount });
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return { struggles, efficients };
|
|
343
|
-
}
|
|
344
|
-
function analyzeTask(row, spans) {
|
|
345
|
-
let logs = [];
|
|
346
|
-
try {
|
|
347
|
-
const parsed = JSON.parse(row.logs ?? '[]');
|
|
348
|
-
if (Array.isArray(parsed))
|
|
349
|
-
logs = parsed;
|
|
350
|
-
}
|
|
351
|
-
catch { }
|
|
352
|
-
const toolCalls = extractToolCallsFromLogs(logs);
|
|
353
|
-
const duration = row.duration_ms != null ? Math.round(row.duration_ms / 1000) : null;
|
|
354
|
-
let metaProvider = null;
|
|
355
|
-
if (row.metadata) {
|
|
356
|
-
try {
|
|
357
|
-
metaProvider = JSON.parse(row.metadata).provider ?? null;
|
|
358
|
-
}
|
|
359
|
-
catch { }
|
|
360
|
-
}
|
|
361
|
-
const timeline = buildTimeline(toolCalls);
|
|
362
|
-
const patterns = detectPatterns(timeline);
|
|
363
|
-
const readFiles = new Set();
|
|
364
|
-
const editFiles = new Set();
|
|
365
|
-
let bashCount = 0, verifyRuns = 0;
|
|
366
|
-
for (const e of timeline) {
|
|
367
|
-
const paths = e.filePath
|
|
368
|
-
? (Array.isArray(e.filePath) ? e.filePath : [e.filePath])
|
|
369
|
-
: [];
|
|
370
|
-
if (e.name === 'Read')
|
|
371
|
-
paths.forEach((p) => readFiles.add(p));
|
|
372
|
-
else if (e.name === 'Edit' || e.name === 'Write')
|
|
373
|
-
paths.forEach((p) => editFiles.add(p));
|
|
374
|
-
else if (e.name === 'Bash') {
|
|
375
|
-
bashCount++;
|
|
376
|
-
if (e.isVerify)
|
|
377
|
-
verifyRuns++;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
const skills = aggregateSpans(spans);
|
|
381
|
-
const hasEdits = editFiles.size > 0;
|
|
382
|
-
const hasDoc = !!(skills['crewx/doc'] && skills['crewx/doc'].count > 0);
|
|
383
|
-
const hasMemory = !!(skills['crewx/memory'] && skills['crewx/memory'].count > 0);
|
|
384
|
-
const hasTsserver = !!(skills['tsserver'] && skills['tsserver'].count > 0);
|
|
385
|
-
const hasSearch = !!(skills['crewx/search'] && skills['crewx/search'].count > 0);
|
|
386
|
-
const promptSummary = row.prompt ? row.prompt.split('\n')[0].slice(0, 120) : null;
|
|
387
|
-
const resultSummary = row.result ? row.result.split('\n')[0].slice(0, 120) : null;
|
|
388
|
-
return {
|
|
389
|
-
taskId: row.id,
|
|
390
|
-
agentId: row.agent_id,
|
|
391
|
-
metaProvider,
|
|
392
|
-
status: row.status,
|
|
393
|
-
promptSummary,
|
|
394
|
-
resultSummary,
|
|
395
|
-
duration,
|
|
396
|
-
startedAt: row.started_at ?? null,
|
|
397
|
-
completedAt: row.completed_at ?? null,
|
|
398
|
-
durationMs: row.duration_ms ?? null,
|
|
399
|
-
toolCount: toolCalls.length,
|
|
400
|
-
timeline,
|
|
401
|
-
patterns,
|
|
402
|
-
behavior: {
|
|
403
|
-
readFiles: [...readFiles],
|
|
404
|
-
editFiles: [...editFiles],
|
|
405
|
-
bashCount,
|
|
406
|
-
},
|
|
407
|
-
metrics: {
|
|
408
|
-
struggles: patterns.struggles.length,
|
|
409
|
-
total_retries: patterns.struggles.reduce((s, r) => s + r.failCount, 0),
|
|
410
|
-
efficients: patterns.efficients.length,
|
|
411
|
-
verify_runs: verifyRuns,
|
|
412
|
-
},
|
|
413
|
-
skills,
|
|
414
|
-
skillInsights: { hasEdits, hasDoc, hasMemory, hasTsserver, hasSearch },
|
|
415
|
-
hasStruggles: patterns.struggles.length > 0,
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
function summarizeAnalysis(a) {
|
|
419
|
-
const toolCounts = {};
|
|
420
|
-
for (const e of a.timeline) {
|
|
421
|
-
const name = e.name || '?';
|
|
422
|
-
toolCounts[name] = (toolCounts[name] ?? 0) + 1;
|
|
423
|
-
}
|
|
424
|
-
return {
|
|
425
|
-
taskId: a.taskId,
|
|
426
|
-
agentId: a.agentId,
|
|
427
|
-
status: a.status,
|
|
428
|
-
duration: a.duration,
|
|
429
|
-
startedAt: a.startedAt,
|
|
430
|
-
completedAt: a.completedAt,
|
|
431
|
-
toolCount: a.toolCount,
|
|
432
|
-
toolCounts,
|
|
433
|
-
editFiles: a.behavior.editFiles,
|
|
434
|
-
readFiles: a.behavior.readFiles,
|
|
435
|
-
hasStruggles: a.hasStruggles,
|
|
436
|
-
promptSummary: a.promptSummary,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
//# sourceMappingURL=analyzer.js.map
|
|
1
|
+
"use strict";var T=exports&&exports.__createBinding||(Object.create?(function(t,e,s,r){if(r===void 0)r=s;var n=Object.getOwnPropertyDescriptor(e,s);if(!n||("get"in n?!e.__esModule:n.writable||n.configurable)){n={enumerable:true,get:function(){return e[s]}}}Object.defineProperty(t,r,n)}):(function(t,e,s,r){if(r===void 0)r=s;t[r]=e[s]}));var k=exports&&exports.__setModuleDefault||(Object.create?(function(t,e){Object.defineProperty(t,"default",{enumerable:true,value:e})}):function(t,e){t["default"]=e});var L=exports&&exports.__importStar||(function(){var t=function(e){t=Object.getOwnPropertyNames||function(s){var r=[];for(var n in s)if(Object.prototype.hasOwnProperty.call(s,n))r[r.length]=n;return r};return t(e)};return function(e){if(e&&e.__esModule)return e;var s={};if(e!=null){for(var r=t(e),n=0;n<r.length;n++)if(r[n]!=="default")T(s,e,r[n])}k(s,e);return s}})();Object.defineProperty(exports,"__esModule",{value:true});exports.READ_ONLY_BASH_PREFIXES=exports.READ_ONLY_TOOLS=void 0;exports.normalizeToolName=p;exports.extractToolCallsFromLogs=P;exports.analyzeTask=J;exports.isReadOnlyToolCall=S;exports.checkFileStaleness=x;exports.extractActivityLogEntries=R;exports.buildActivityLog=$;exports.summarizeAnalysis=Q;const M=L(require("fs"));const b=require("@crewx/sdk");const B=["tsc","npm test","npm run test","npm run lint","eslint","prettier","jest","vitest","biome","npx tsc"];const N=[/error/i,/failed/i,/exit code [^0]/,/cannot find/i,/does not exist/i,/FAILED/,/✗/,/ERR!/];const U=[[/Type .+ is not assignable to type/i,"Type mismatch"],[/Cannot find module/i,"Module path error"],[/Property .+ does not exist/i,"Missing property"],[/Expected \d+ arguments/i,"Argument count mismatch"],[/Cannot find name/i,"Undeclared variable/function"],[/has no exported member/i,"Missing export"],[/FAIL|AssertionError|expect\(/i,"Test failure"],[/SyntaxError|Unexpected token/i,"Syntax error"]];const z=new Set(["Read File","Edit File","Write File","Terminal","{}",""]);function p(t){if(!t)return"";const e=t.toLowerCase();if(e==="bash"||e==="terminal")return"Bash";if(e==="read"||e==="view")return"Read";if(e==="edit"||e==="apply_patch")return"Edit";if(e==="write")return"Write";if(e==="grep"||e==="rg")return"Grep";if(e==="glob"||e==="find")return"Find";return t}function w(t){const e=t.replace(/\\/g,"/").split("/").filter(Boolean);const s=e.length<=3?e:e.slice(-3);return s.join("/")}function D(t,e){const s=p(t);if(s!=="Read"&&s!=="Edit"&&s!=="Write")return null;if(!e)return null;if(z.has(e))return null;if(e.includes("*** Update File:")||e.includes("*** Add File:")){const n=[];for(const o of e.matchAll(/\*\*\*\s+(?:Update|Add)\s+File:\s*(.+)/g)){const l=o[1].trim();if(l)n.push(w(l))}return n.length?n:null}let r=e;if(/^(Read|Edit|Write|View)\s+/.test(r)){r=r.replace(/^(Read|Edit|Write|View)\s+/,"")}if(/[*?{}]/.test(r))return null;if(r.length>300)return null;if(!/\.\w+$/.test(r)&&!r.includes("/"))return null;if(r.startsWith("/")||/^[A-Za-z]:[\\/]/.test(r)){return w(r)}return r}function W(t){const e=new Set;for(const s of t){try{const r=JSON.parse(s);if(r.type)e.add(r.type)}catch{}}if(e.has("turn.started")||e.has("turn.failed")||e.has("thread.started"))return"codex";if(e.has("assistant.message_delta")||e.has("tool.execution_complete"))return"copilot";if(e.has("step_start")&&e.has("step_finish"))return"opencode";if(e.has("system")&&e.has("result"))return"claude";if(e.has("init")&&e.has("message"))return"gemini";if(e.has("result")&&e.has("assistant"))return"claude";return null}function j(t,e){const s=t?.split("/").pop();if(s&&s in b.BUILTIN_ADAPTERS)return s;return W(e)}function P(t,e){if(!Array.isArray(t))return[];const s=t;const r=s.filter(i=>i.level==="stdout"&&i.message).map(i=>i.message);const n=j(e,r);const o=[];for(const i of s){if(i.level==="info"&&i.message){o.push(...(0,b.defaultParseEvent)(i.message,i.timestamp))}else if(i.level==="stdout"&&i.message){o.push(...(0,b.parseStdoutEvent)("",i.message,n??void 0))}}if(!o.length)return[];const l=new Map;const c=[];for(const i of o){if(i.type==="tool_use"){const a={toolUseId:i.toolUseId??null,name:p(i.toolName),input:i.toolInput??null,isError:null,resultPreview:null};const u=a.toolUseId;if(u){if(l.has(u)){l.get(u).input=a.input;continue}l.set(u,a)}c.push(a)}else if(i.type==="tool_result"){const a=i;if(a.toolUseId&&l.has(a.toolUseId)){const u=l.get(a.toolUseId);u.isError=a.isError===true;u.resultPreview=a.resultPreview??null}else{for(let u=c.length-1;u>=0;u--){const d=c[u];if(d.resultPreview===null){d.isError=a.isError===true;d.resultPreview=a.resultPreview??null;break}}}}}return c}function V(t){const e=t.match(/^\[skill:([^\]]+)\]/);if(e){const s=e[1];if(s==="memory-v2")return"crewx/memory";return s}if(t.startsWith("skill:"))return t.slice(6);return t}function Y(t,e){if(!e)return null;const s=e.trim().split(/\s+/);const r={"crewx/memory":"memory","crewx/doc":"doc","crewx/skill":"skill","crewx/workflow":"workflow","tsserver":"tsserver"};if(t==="crewx/search")return null;let n=0;const o=r[t];if(o&&s[0]===o)n++;const l=s[n];if(!l||l.startsWith("-")||l.startsWith("/"))return null;return l}function G(t){const e={};for(const s of t){if(s.input&&s.input.includes("summarize-dirty"))continue;const r=V(s.name);if(!e[r])e[r]={count:0,subCmds:{}};e[r].count++;const n=Y(r,s.input??null);if(n)e[r].subCmds[n]=(e[r].subCmds[n]??0)+1}return e}function _(t){if(!t)return null;for(const e of B){if(t.includes(e))return e}return null}function H(t){if(t.isError===true)return true;if(!t.resultPreview)return false;return N.some(e=>e.test(t.resultPreview))}function A(t){if(!t)return null;for(const[e,s]of U){if(e.test(t))return s}return null}function X(t){const e={};return t.map(s=>{const r=p(s.name);const n={name:r,input:s.input,isError:s.isError,resultPreview:s.resultPreview,filePath:null,bashCmd:null,bashResult:null,bashErrorMsg:null,errorCategory:null,isVerify:false,editCount:null};if(r==="Read"||r==="Edit"||r==="Write"){n.filePath=D(s.name,s.input)}if((r==="Edit"||r==="Write")&&n.filePath){const o=Array.isArray(n.filePath)?n.filePath:[n.filePath];for(const l of o)e[l]=(e[l]??0)+1;n.editCount=e[o[0]]??null}if(r==="Bash"){n.bashCmd=s.input?s.input.split("\n")[0].slice(0,150):"";n.isVerify=!!_(n.bashCmd);const o=H(s);n.bashResult=o?"fail":"pass";if(o&&s.resultPreview){n.bashErrorMsg=s.resultPreview.trim().split("\n")[0].slice(0,100);n.errorCategory=A(s.resultPreview)}}return n})}function q(t){const e=[];const s=[];for(let r=0;r<t.length;r++){const n=t[r];if(n.name!=="Bash"||!n.isVerify||n.bashResult!=="pass")continue;const o=_(n.bashCmd);const l=[];let c=r-1;while(c>=0){const i=t[c];if(i.name==="Bash"&&i.isVerify){if(_(i.bashCmd)===o&&i.bashResult==="fail"){l.unshift(c)}else if(i.bashResult==="pass"){break}}c--}if(l.length>=1){const i=l[0];const a={};const u=[];for(let f=i;f<=r;f++){const h=t[f];if((h.name==="Edit"||h.name==="Write")&&h.filePath){const v=Array.isArray(h.filePath)?h.filePath:[h.filePath];for(const g of v)a[g]=(a[g]??0)+1}if(h.name==="Bash"&&h.bashResult==="fail"&&h.bashErrorMsg){u.push(h.bashErrorMsg)}}const d=u.map(A).filter(f=>f!==null);e.push({startIdx:i,endIdx:r,failCount:l.length,editFiles:a,errors:[...new Set(u)].slice(0,3),errorCategories:[...new Set(d)],verifyCmd:o})}else{let i=0,a=0,u=r-1;while(u>=0){const d=t[u];if(d.name==="Edit"||d.name==="Write")i++;else if(d.name==="Read"||d.name==="Grep"||d.name==="Find")a++;else break;u--}if(i>0){s.push({startIdx:u+1,endIdx:r,readCount:a,editCount:i})}}}return{struggles:e,efficients:s}}function J(t,e,s=[]){let r=null;if(t.metadata){try{r=JSON.parse(t.metadata).provider??null}catch{}}const n=P(s,r??void 0);const o=t.duration_ms!=null?Math.round(t.duration_ms/1e3):null;const l=X(n);const c=q(l);const i=new Set;const a=new Set;let u=0,d=0;for(const m of l){const y=m.filePath?Array.isArray(m.filePath)?m.filePath:[m.filePath]:[];if(m.name==="Read")y.forEach(E=>i.add(E));else if(m.name==="Edit"||m.name==="Write")y.forEach(E=>a.add(E));else if(m.name==="Bash"){u++;if(m.isVerify)d++}}const f=G(e);const h=a.size>0;const v=!!(f["crewx/doc"]&&f["crewx/doc"].count>0);const g=!!(f["crewx/memory"]&&f["crewx/memory"].count>0);const C=!!(f["tsserver"]&&f["tsserver"].count>0);const O=!!(f["crewx/search"]&&f["crewx/search"].count>0);const F=t.prompt?t.prompt.split("\n")[0].slice(0,120):null;const I=t.result?(0,b.sanitizeLegacyUiCardText)(t.result).split("\n")[0].slice(0,120):null;return{taskId:t.id,agentId:t.agent_id,metaProvider:r,status:t.status,promptSummary:F,resultSummary:I,duration:o,startedAt:t.started_at??null,completedAt:t.completed_at??null,durationMs:t.duration_ms??null,toolCount:n.length,timeline:l,patterns:c,behavior:{readFiles:[...i],editFiles:[...a],bashCount:u},metrics:{struggles:c.struggles.length,total_retries:c.struggles.reduce((m,y)=>m+y.failCount,0),efficients:c.efficients.length,verify_runs:d},skills:f,skillInsights:{hasEdits:h,hasDoc:v,hasMemory:g,hasTsserver:C,hasSearch:O},hasStruggles:c.struggles.length>0}}exports.READ_ONLY_TOOLS=new Set(["Read","Grep","Find","Glob"]);exports.READ_ONLY_BASH_PREFIXES=["git status","git log","git branch","git diff --stat","git show","git remote","git rev-parse","git ls-files","pwd","ls ","ls\n","echo ","cat ","head ","tail ","npx skill tsserver find","npx skill tsserver refs","npx skill tsserver type","crewx memory index","crewx memory find","crewx memory get","crewx doc toc","crewx doc section","crewx doc status","crewx search","crewx agent ls","crewx agent list","crewx wbs status"];const K=[/\bgit\s+(commit|push|pull|merge|rebase|reset|checkout|switch|add|rm)\b/,/\bnpm\s+(install|ci|publish|run\s+(test|lint|build))\b/,/\bnpx\s+tsc\b/,/\bpnpm\s+(install|run)\b/,/\bvitest\b/,/\bjest\b/];function S(t,e){const s=p(t);if(exports.READ_ONLY_TOOLS.has(s))return true;if(s!=="Bash")return false;if(!e)return false;const r=e.split("\n")[0].trimStart();if(K.some(n=>n.test(r)))return false;return exports.READ_ONLY_BASH_PREFIXES.some(n=>r.startsWith(n))}function x(t,e){try{const s=M.statSync(t);if(e){const r=Date.parse(e);if(!Number.isNaN(r)&&s.mtimeMs>r)return"stale"}return"ok"}catch{return"unknown"}}function Z(t,e){if(!S(t.name,t.input))return null;if(t.isError)return null;let s=null;let r="ok";if(t.name==="Read"&&t.filePath){const o=Array.isArray(t.filePath)?t.filePath[0]??null:t.filePath;s=o;if(o)r=x(o,e)}const n=t.name==="Bash"?t.bashCmd??t.input?.slice(0,150)??null:t.input?.slice(0,150)??null;return{tool:t.name,cmd:n,result:t.resultPreview??null,filePath:s,status:r}}function R(t){const e=[];for(const s of t){for(const r of s.timeline){const n=Z(r,s.completedAt);if(n)e.push(n)}}return e}function $(t,e,s,r,n){const o=R(t);const l=[];let c=0;let i=false;for(const a of o){const u=(a.result?.length??0)+(a.cmd?.length??0)+80;if(c+u>n&&l.length>0){i=true;break}l.push(a);c+=u}return{threadId:e,agentId:s,lookback:r,totalEntries:o.length,entries:l,truncated:i,budgetUsed:c}}function Q(t){const e={};for(const s of t.timeline){const r=s.name||"?";e[r]=(e[r]??0)+1}return{taskId:t.taskId,agentId:t.agentId,status:t.status,duration:t.duration,startedAt:t.startedAt,completedAt:t.completedAt,toolCount:t.toolCount,toolCounts:e,editFiles:t.behavior.editFiles,readFiles:t.behavior.readFiles,hasStruggles:t.hasStruggles,promptSummary:t.promptSummary}}
|
package/dist/src/renderer.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type { ContinueResult, TaskAnalysis, TaskSummaryRow } from './types.js';
|
|
1
|
+
import type { ActivityLog, ContinueResult, TaskAnalysis, TaskSummaryRow } from './types.js';
|
|
2
2
|
export interface RenderDetailOptions {
|
|
3
3
|
maxTimeline?: number | null;
|
|
4
4
|
}
|
|
5
5
|
export declare function renderTaskFull(r: TaskAnalysis, opts?: RenderDetailOptions): string;
|
|
6
6
|
export declare function renderThreadTaskList(rows: TaskSummaryRow[], threadId: string, recent: number): string;
|
|
7
7
|
export declare function renderContinue(result: ContinueResult, opts?: RenderDetailOptions): string;
|
|
8
|
+
export declare function renderActivityLogXml(log: ActivityLog): string;
|
|
8
9
|
export interface CalAction {
|
|
9
10
|
label: string;
|
|
10
11
|
cmd: string;
|
|
11
12
|
}
|
|
12
13
|
export declare function buildCalFooter(actions: CalAction[]): string;
|
|
13
|
-
|
|
14
|
+
|