ccjk 13.6.3 → 13.6.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/dist/chunks/agents.mjs +1 -1
- package/dist/chunks/api-config-selector.mjs +0 -27
- package/dist/chunks/ccr.mjs +1 -1
- package/dist/chunks/check-updates.mjs +16 -16
- package/dist/chunks/claude-code-config-manager.mjs +1 -1
- package/dist/chunks/claude-config.mjs +18 -1
- package/dist/chunks/cli-hook.mjs +21 -78
- package/dist/chunks/config2.mjs +2 -2
- package/dist/chunks/constants.mjs +178 -2
- package/dist/chunks/index10.mjs +40 -21
- package/dist/chunks/init.mjs +108 -33
- package/dist/chunks/installer.mjs +80 -19
- package/dist/chunks/mcp.mjs +1 -1
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/platform.mjs +4 -0
- package/dist/chunks/quick-setup.mjs +1 -1
- package/dist/chunks/research.mjs +979 -0
- package/dist/chunks/sessions.mjs +1 -1
- package/dist/chunks/smart-defaults.mjs +41 -13
- package/dist/chunks/uninstall.mjs +1 -1
- package/dist/cli.mjs +28 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +8 -174
- package/dist/shared/{ccjk.DwSebGy0.mjs → ccjk.BOO14f66.mjs} +1 -1
- package/dist/shared/ccjk.BnsY5WxD.mjs +171 -0
- package/dist/shared/ccjk.yYQMbHH3.mjs +115 -0
- package/package.json +68 -65
- package/dist/shared/ccjk.CiKtBUW_.mjs +0 -54
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
import a from './index5.mjs';
|
|
2
|
+
import { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { n as nanoid } from '../shared/ccjk.BoApaI4j.mjs';
|
|
5
|
+
import { T as TaskPersistence } from '../shared/ccjk.BOO14f66.mjs';
|
|
6
|
+
import { e as executeCommand } from '../shared/ccjk.BnsY5WxD.mjs';
|
|
7
|
+
import { j as join, d as dirname } from '../shared/ccjk.bQ7Dh1g4.mjs';
|
|
8
|
+
import '../shared/ccjk.BAGoDD49.mjs';
|
|
9
|
+
import 'node:crypto';
|
|
10
|
+
import 'better-sqlite3';
|
|
11
|
+
import 'node:child_process';
|
|
12
|
+
import 'node:util';
|
|
13
|
+
|
|
14
|
+
class TaskQueue {
|
|
15
|
+
queue = [];
|
|
16
|
+
runningTasks = /* @__PURE__ */ new Set();
|
|
17
|
+
stats;
|
|
18
|
+
options;
|
|
19
|
+
taskIdCounter = 0;
|
|
20
|
+
executionTimes = [];
|
|
21
|
+
processing = false;
|
|
22
|
+
paused = false;
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.options = {
|
|
25
|
+
concurrency: options.concurrency ?? Number.POSITIVE_INFINITY,
|
|
26
|
+
defaultTimeout: options.defaultTimeout ?? 3e4,
|
|
27
|
+
defaultMaxRetries: options.defaultMaxRetries ?? 0,
|
|
28
|
+
defaultRetryDelay: options.defaultRetryDelay ?? 1e3,
|
|
29
|
+
autoStart: options.autoStart ?? true
|
|
30
|
+
};
|
|
31
|
+
this.stats = {
|
|
32
|
+
totalTasks: 0,
|
|
33
|
+
pendingTasks: 0,
|
|
34
|
+
runningTasks: 0,
|
|
35
|
+
completedTasks: 0,
|
|
36
|
+
failedTasks: 0,
|
|
37
|
+
timeoutTasks: 0,
|
|
38
|
+
averageExecutionTime: 0,
|
|
39
|
+
createdAt: Date.now()
|
|
40
|
+
};
|
|
41
|
+
if (this.options.autoStart) {
|
|
42
|
+
this.start();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Add a task to the queue
|
|
47
|
+
*/
|
|
48
|
+
add(execute, options = {}) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const task = {
|
|
51
|
+
id: this.generateTaskId(),
|
|
52
|
+
execute,
|
|
53
|
+
options: {
|
|
54
|
+
priority: options.priority ?? "normal",
|
|
55
|
+
timeout: options.timeout ?? this.options.defaultTimeout,
|
|
56
|
+
maxRetries: options.maxRetries ?? this.options.defaultMaxRetries,
|
|
57
|
+
retryDelay: options.retryDelay ?? this.options.defaultRetryDelay,
|
|
58
|
+
retryBackoff: options.retryBackoff ?? 1,
|
|
59
|
+
metadata: options.metadata ?? {}
|
|
60
|
+
},
|
|
61
|
+
createdAt: Date.now(),
|
|
62
|
+
retryCount: 0,
|
|
63
|
+
status: "pending",
|
|
64
|
+
resolve,
|
|
65
|
+
reject
|
|
66
|
+
};
|
|
67
|
+
this.queue.push(task);
|
|
68
|
+
this.stats.totalTasks++;
|
|
69
|
+
this.stats.pendingTasks++;
|
|
70
|
+
this.sortQueue();
|
|
71
|
+
if (!this.paused) {
|
|
72
|
+
this.processQueue();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Start queue processing
|
|
78
|
+
*/
|
|
79
|
+
start() {
|
|
80
|
+
this.paused = false;
|
|
81
|
+
this.processQueue();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Pause queue processing
|
|
85
|
+
*/
|
|
86
|
+
pause() {
|
|
87
|
+
this.paused = true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resume queue processing
|
|
91
|
+
*/
|
|
92
|
+
resume() {
|
|
93
|
+
this.start();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Clear all pending tasks
|
|
97
|
+
*/
|
|
98
|
+
clear() {
|
|
99
|
+
const pendingTasks = this.queue.filter((task) => task.status === "pending");
|
|
100
|
+
pendingTasks.forEach((task) => {
|
|
101
|
+
task.status = "failed";
|
|
102
|
+
task.reject(new Error("Task cancelled: queue cleared"));
|
|
103
|
+
});
|
|
104
|
+
this.queue = this.queue.filter((task) => task.status === "running");
|
|
105
|
+
this.stats.pendingTasks = 0;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Wait for all tasks to complete
|
|
109
|
+
*/
|
|
110
|
+
async drain() {
|
|
111
|
+
while (this.queue.length > 0 || this.runningTasks.size > 0) {
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get queue statistics
|
|
117
|
+
*/
|
|
118
|
+
getStats() {
|
|
119
|
+
return { ...this.stats };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get current queue size
|
|
123
|
+
*/
|
|
124
|
+
size() {
|
|
125
|
+
return this.queue.length;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if queue is empty
|
|
129
|
+
*/
|
|
130
|
+
isEmpty() {
|
|
131
|
+
return this.queue.length === 0 && this.runningTasks.size === 0;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if queue is paused
|
|
135
|
+
*/
|
|
136
|
+
isPaused() {
|
|
137
|
+
return this.paused;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Process tasks in the queue
|
|
141
|
+
*/
|
|
142
|
+
async processQueue() {
|
|
143
|
+
if (this.processing || this.paused) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.processing = true;
|
|
147
|
+
while (!this.paused && this.queue.length > 0 && this.runningTasks.size < this.options.concurrency) {
|
|
148
|
+
const task = this.queue.find((t) => t.status === "pending");
|
|
149
|
+
if (!task) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
task.status = "running";
|
|
153
|
+
this.stats.pendingTasks--;
|
|
154
|
+
this.stats.runningTasks++;
|
|
155
|
+
this.runningTasks.add(task.id);
|
|
156
|
+
this.executeTask(task).catch(() => {
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
this.processing = false;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Execute a single task with timeout and retry support
|
|
163
|
+
*/
|
|
164
|
+
async executeTask(task) {
|
|
165
|
+
const startTime = Date.now();
|
|
166
|
+
try {
|
|
167
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
reject(new Error(`Task timeout after ${task.options.timeout}ms`));
|
|
170
|
+
}, task.options.timeout);
|
|
171
|
+
});
|
|
172
|
+
const result = await Promise.race([
|
|
173
|
+
task.execute(),
|
|
174
|
+
timeoutPromise
|
|
175
|
+
]);
|
|
176
|
+
const executionTime = Date.now() - startTime;
|
|
177
|
+
this.recordExecutionTime(executionTime);
|
|
178
|
+
task.status = "completed";
|
|
179
|
+
this.stats.completedTasks++;
|
|
180
|
+
task.resolve(result);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const isTimeout = error instanceof Error && error.message.includes("timeout");
|
|
183
|
+
if (isTimeout) {
|
|
184
|
+
task.status = "timeout";
|
|
185
|
+
this.stats.timeoutTasks++;
|
|
186
|
+
}
|
|
187
|
+
if (task.retryCount < task.options.maxRetries) {
|
|
188
|
+
task.retryCount++;
|
|
189
|
+
task.status = "pending";
|
|
190
|
+
const delay = task.options.retryDelay * task.options.retryBackoff ** (task.retryCount - 1);
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
this.stats.pendingTasks++;
|
|
193
|
+
this.sortQueue();
|
|
194
|
+
this.processQueue();
|
|
195
|
+
}, delay);
|
|
196
|
+
} else {
|
|
197
|
+
task.status = "failed";
|
|
198
|
+
this.stats.failedTasks++;
|
|
199
|
+
task.reject(error instanceof Error ? error : new Error(String(error)));
|
|
200
|
+
}
|
|
201
|
+
} finally {
|
|
202
|
+
this.runningTasks.delete(task.id);
|
|
203
|
+
this.stats.runningTasks--;
|
|
204
|
+
if (task.status === "completed" || task.status === "failed" || task.status === "timeout") {
|
|
205
|
+
this.queue = this.queue.filter((t) => t.id !== task.id);
|
|
206
|
+
}
|
|
207
|
+
this.processQueue();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Sort queue by priority
|
|
212
|
+
*/
|
|
213
|
+
sortQueue() {
|
|
214
|
+
const priorityOrder = {
|
|
215
|
+
critical: 0,
|
|
216
|
+
high: 1,
|
|
217
|
+
normal: 2,
|
|
218
|
+
low: 3
|
|
219
|
+
};
|
|
220
|
+
this.queue.sort((a, b) => {
|
|
221
|
+
const priorityDiff = priorityOrder[a.options.priority] - priorityOrder[b.options.priority];
|
|
222
|
+
if (priorityDiff !== 0) {
|
|
223
|
+
return priorityDiff;
|
|
224
|
+
}
|
|
225
|
+
return a.createdAt - b.createdAt;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Generate unique task ID
|
|
230
|
+
*/
|
|
231
|
+
generateTaskId() {
|
|
232
|
+
return `task_${++this.taskIdCounter}_${Date.now()}`;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Record task execution time for statistics
|
|
236
|
+
*/
|
|
237
|
+
recordExecutionTime(time) {
|
|
238
|
+
this.executionTimes.push(time);
|
|
239
|
+
if (this.executionTimes.length > 100) {
|
|
240
|
+
this.executionTimes.shift();
|
|
241
|
+
}
|
|
242
|
+
const sum = this.executionTimes.reduce((acc, t) => acc + t, 0);
|
|
243
|
+
this.stats.averageExecutionTime = sum / this.executionTimes.length;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const DEFAULT_BUDGET_MS = 5 * 60 * 1e3;
|
|
248
|
+
const DEFAULT_SESSION_LIMIT = 10;
|
|
249
|
+
const RESEARCH_SESSION_KIND = "research";
|
|
250
|
+
const RESULTS_LEDGER_FILE = "research-results.tsv";
|
|
251
|
+
const RESULTS_LEDGER_HEADERS = [
|
|
252
|
+
"timestamp",
|
|
253
|
+
"sessionId",
|
|
254
|
+
"taskId",
|
|
255
|
+
"name",
|
|
256
|
+
"phase",
|
|
257
|
+
"verdict",
|
|
258
|
+
"status",
|
|
259
|
+
"exitCode",
|
|
260
|
+
"metricName",
|
|
261
|
+
"metricValue",
|
|
262
|
+
"durationMs",
|
|
263
|
+
"cwd",
|
|
264
|
+
"command"
|
|
265
|
+
];
|
|
266
|
+
class ResearchCommandError extends Error {
|
|
267
|
+
constructor(result) {
|
|
268
|
+
super(result.error || `Command exited with code ${result.exitCode}`);
|
|
269
|
+
this.result = result;
|
|
270
|
+
this.name = "ResearchCommandError";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function createPersistence(dbPath) {
|
|
274
|
+
return new TaskPersistence(dbPath);
|
|
275
|
+
}
|
|
276
|
+
function getResearchDataDir(dbPath) {
|
|
277
|
+
const resolvedDbPath = dbPath || join(homedir(), ".ccjk", "brain.db");
|
|
278
|
+
const dir = dirname(resolvedDbPath);
|
|
279
|
+
if (!existsSync(dir)) {
|
|
280
|
+
mkdirSync(dir, { recursive: true });
|
|
281
|
+
}
|
|
282
|
+
return dir;
|
|
283
|
+
}
|
|
284
|
+
function getResultsLedgerPath(dbPath) {
|
|
285
|
+
return join(getResearchDataDir(dbPath), RESULTS_LEDGER_FILE);
|
|
286
|
+
}
|
|
287
|
+
function escapeRegex(value) {
|
|
288
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
289
|
+
}
|
|
290
|
+
function extractMetricValue(output, metricName) {
|
|
291
|
+
if (!metricName) {
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
const matcher = new RegExp(`(?:^|\\b)${escapeRegex(metricName)}\\s*[:=]\\s*(-?\\d+(?:\\.\\d+)?)`, "im");
|
|
295
|
+
const match = output.match(matcher);
|
|
296
|
+
if (!match) {
|
|
297
|
+
return void 0;
|
|
298
|
+
}
|
|
299
|
+
const value = Number.parseFloat(match[1]);
|
|
300
|
+
return Number.isFinite(value) ? value : void 0;
|
|
301
|
+
}
|
|
302
|
+
function getFailureStatus(result, error) {
|
|
303
|
+
const message = [result.error, result.stderr, error?.message].filter(Boolean).join(" ");
|
|
304
|
+
return /timed out/i.test(message) ? "timeout" : "failed";
|
|
305
|
+
}
|
|
306
|
+
function inferResearchPhase(name) {
|
|
307
|
+
return /baseline/i.test(name) ? "baseline" : "experiment";
|
|
308
|
+
}
|
|
309
|
+
function buildPhaseHistory(runPhase) {
|
|
310
|
+
return ["brief", runPhase, "verify", "report"];
|
|
311
|
+
}
|
|
312
|
+
function determineVerdict(success, metricName, metricValue) {
|
|
313
|
+
if (!success) {
|
|
314
|
+
return "FAIL";
|
|
315
|
+
}
|
|
316
|
+
if (metricName && metricValue === void 0) {
|
|
317
|
+
return "PARTIAL";
|
|
318
|
+
}
|
|
319
|
+
return "PASS";
|
|
320
|
+
}
|
|
321
|
+
function sanitizeLedgerField(value) {
|
|
322
|
+
if (value === void 0 || value === null) {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
return String(value).replace(/\t/g, " ").replace(/\r?\n/g, " ").trim();
|
|
326
|
+
}
|
|
327
|
+
function ensureResultsLedger(dbPath) {
|
|
328
|
+
const ledgerPath = getResultsLedgerPath(dbPath);
|
|
329
|
+
if (!existsSync(ledgerPath)) {
|
|
330
|
+
writeFileSync(ledgerPath, `${RESULTS_LEDGER_HEADERS.join(" ")}
|
|
331
|
+
`, "utf8");
|
|
332
|
+
}
|
|
333
|
+
return ledgerPath;
|
|
334
|
+
}
|
|
335
|
+
function serializeResultRow(row) {
|
|
336
|
+
const values = {
|
|
337
|
+
timestamp: sanitizeLedgerField(row.timestamp),
|
|
338
|
+
sessionId: sanitizeLedgerField(row.sessionId),
|
|
339
|
+
taskId: sanitizeLedgerField(row.taskId),
|
|
340
|
+
name: sanitizeLedgerField(row.name),
|
|
341
|
+
phase: sanitizeLedgerField(row.phase),
|
|
342
|
+
verdict: sanitizeLedgerField(row.verdict),
|
|
343
|
+
status: sanitizeLedgerField(row.status),
|
|
344
|
+
exitCode: sanitizeLedgerField(row.exitCode),
|
|
345
|
+
metricName: sanitizeLedgerField(row.metricName),
|
|
346
|
+
metricValue: sanitizeLedgerField(row.metricValue),
|
|
347
|
+
durationMs: sanitizeLedgerField(row.durationMs),
|
|
348
|
+
cwd: sanitizeLedgerField(row.cwd),
|
|
349
|
+
command: sanitizeLedgerField(row.command)
|
|
350
|
+
};
|
|
351
|
+
return RESULTS_LEDGER_HEADERS.map((header) => values[header]).join(" ");
|
|
352
|
+
}
|
|
353
|
+
function parseResultRow(line) {
|
|
354
|
+
if (!line.trim()) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const columns = line.split(" ");
|
|
358
|
+
if (columns.length < RESULTS_LEDGER_HEADERS.length) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const [
|
|
362
|
+
timestamp,
|
|
363
|
+
sessionId,
|
|
364
|
+
taskId,
|
|
365
|
+
name,
|
|
366
|
+
phase,
|
|
367
|
+
verdict,
|
|
368
|
+
status,
|
|
369
|
+
exitCode,
|
|
370
|
+
metricName,
|
|
371
|
+
metricValue,
|
|
372
|
+
durationMs,
|
|
373
|
+
cwd,
|
|
374
|
+
command
|
|
375
|
+
] = columns;
|
|
376
|
+
const parsedMetric = metricValue ? Number.parseFloat(metricValue) : void 0;
|
|
377
|
+
return {
|
|
378
|
+
timestamp,
|
|
379
|
+
sessionId,
|
|
380
|
+
taskId,
|
|
381
|
+
name,
|
|
382
|
+
phase: phase || "experiment",
|
|
383
|
+
verdict: verdict || "PARTIAL",
|
|
384
|
+
status,
|
|
385
|
+
exitCode: Number.parseInt(exitCode, 10) || 0,
|
|
386
|
+
metricName: metricName || void 0,
|
|
387
|
+
metricValue: Number.isFinite(parsedMetric) ? parsedMetric : void 0,
|
|
388
|
+
durationMs: Number.parseInt(durationMs, 10) || 0,
|
|
389
|
+
cwd,
|
|
390
|
+
command
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function appendResultRow(row, dbPath) {
|
|
394
|
+
const ledgerPath = ensureResultsLedger(dbPath);
|
|
395
|
+
appendFileSync(ledgerPath, `${serializeResultRow(row)}
|
|
396
|
+
`, "utf8");
|
|
397
|
+
}
|
|
398
|
+
function readAllResultRows(dbPath) {
|
|
399
|
+
const ledgerPath = getResultsLedgerPath(dbPath);
|
|
400
|
+
if (!existsSync(ledgerPath)) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
const content = readFileSync(ledgerPath, "utf8");
|
|
404
|
+
const lines = content.split(/\r?\n/).filter(Boolean);
|
|
405
|
+
if (lines.length <= 1) {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
return lines.slice(1).map(parseResultRow).filter((row) => Boolean(row));
|
|
409
|
+
}
|
|
410
|
+
function metricDirection(metricName) {
|
|
411
|
+
const normalized = (metricName || "").toLowerCase();
|
|
412
|
+
if (!normalized) {
|
|
413
|
+
return "desc";
|
|
414
|
+
}
|
|
415
|
+
const lowerIsBetter = ["loss", "error", "bpb", "perplexity", "ppl", "latency", "duration", "time", "cost", "price", "wer", "cer"];
|
|
416
|
+
if (lowerIsBetter.some((keyword) => normalized.includes(keyword))) {
|
|
417
|
+
return "asc";
|
|
418
|
+
}
|
|
419
|
+
return "desc";
|
|
420
|
+
}
|
|
421
|
+
function selectBestByMetric(rows) {
|
|
422
|
+
const newestMetricRow = [...rows].reverse().find((row) => row.metricName && row.metricValue !== void 0);
|
|
423
|
+
if (!newestMetricRow?.metricName) {
|
|
424
|
+
return [...rows].reverse().find((row) => row.verdict === "PASS") || rows[rows.length - 1];
|
|
425
|
+
}
|
|
426
|
+
const candidates = rows.filter((row) => row.metricName === newestMetricRow.metricName && row.metricValue !== void 0);
|
|
427
|
+
if (candidates.length === 0) {
|
|
428
|
+
return newestMetricRow;
|
|
429
|
+
}
|
|
430
|
+
const direction = metricDirection(newestMetricRow.metricName);
|
|
431
|
+
return candidates.reduce((best, current) => {
|
|
432
|
+
if (!best.metricValue || current.metricValue === void 0) {
|
|
433
|
+
return best;
|
|
434
|
+
}
|
|
435
|
+
if (direction === "asc") {
|
|
436
|
+
return current.metricValue < best.metricValue ? current : best;
|
|
437
|
+
}
|
|
438
|
+
return current.metricValue > best.metricValue ? current : best;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
function buildResearchTask(taskId, options) {
|
|
442
|
+
return {
|
|
443
|
+
id: taskId,
|
|
444
|
+
name: options.name,
|
|
445
|
+
description: `Run research command: ${options.command}`,
|
|
446
|
+
type: "research-run",
|
|
447
|
+
priority: "normal",
|
|
448
|
+
status: "pending",
|
|
449
|
+
requiredCapabilities: [],
|
|
450
|
+
input: {
|
|
451
|
+
parameters: {
|
|
452
|
+
command: options.command,
|
|
453
|
+
cwd: options.cwd,
|
|
454
|
+
metricName: options.metricName,
|
|
455
|
+
budgetMs: options.budgetMs
|
|
456
|
+
},
|
|
457
|
+
instructions: "Execute the configured research command and capture its metric output."
|
|
458
|
+
},
|
|
459
|
+
dependencies: [],
|
|
460
|
+
maxRetries: options.maxRetries,
|
|
461
|
+
retryCount: 0,
|
|
462
|
+
timeout: options.budgetMs,
|
|
463
|
+
metadata: {
|
|
464
|
+
tags: ["research", "experiment"],
|
|
465
|
+
category: "research",
|
|
466
|
+
createdBy: "ccjk research",
|
|
467
|
+
custom: {
|
|
468
|
+
metricName: options.metricName,
|
|
469
|
+
cwd: options.cwd
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
473
|
+
progress: 0
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function normalizeRunOptions(options) {
|
|
477
|
+
return {
|
|
478
|
+
command: options.command,
|
|
479
|
+
name: options.name || `research-${Date.now()}`,
|
|
480
|
+
cwd: options.cwd || process.cwd(),
|
|
481
|
+
budgetMs: options.budgetMs || DEFAULT_BUDGET_MS,
|
|
482
|
+
maxRetries: options.maxRetries ?? 0,
|
|
483
|
+
metricName: options.metricName,
|
|
484
|
+
dbPath: options.dbPath
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
async function runResearchExperiment(options) {
|
|
488
|
+
const normalized = normalizeRunOptions(options);
|
|
489
|
+
const persistence = createPersistence(normalized.dbPath);
|
|
490
|
+
const queue = new TaskQueue({
|
|
491
|
+
concurrency: 1,
|
|
492
|
+
defaultTimeout: normalized.budgetMs,
|
|
493
|
+
defaultMaxRetries: normalized.maxRetries
|
|
494
|
+
});
|
|
495
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
496
|
+
const runPhase = inferResearchPhase(normalized.name);
|
|
497
|
+
const phaseHistory = buildPhaseHistory(runPhase);
|
|
498
|
+
const sessionId = `research-${Date.now()}-${nanoid(6)}`;
|
|
499
|
+
const taskId = `research-task-${nanoid(8)}`;
|
|
500
|
+
const task = buildResearchTask(taskId, normalized);
|
|
501
|
+
persistence.saveSession(sessionId, {
|
|
502
|
+
kind: RESEARCH_SESSION_KIND,
|
|
503
|
+
name: normalized.name,
|
|
504
|
+
command: normalized.command,
|
|
505
|
+
cwd: normalized.cwd,
|
|
506
|
+
metricName: normalized.metricName,
|
|
507
|
+
budgetMs: normalized.budgetMs,
|
|
508
|
+
currentPhase: runPhase,
|
|
509
|
+
phaseHistory: ["brief", runPhase],
|
|
510
|
+
createdAt
|
|
511
|
+
});
|
|
512
|
+
persistence.saveTask(task, sessionId);
|
|
513
|
+
let attempts = 0;
|
|
514
|
+
let commandResult;
|
|
515
|
+
let error;
|
|
516
|
+
const startedAt = Date.now();
|
|
517
|
+
persistence.updateTaskStatus(taskId, "running");
|
|
518
|
+
try {
|
|
519
|
+
commandResult = await queue.add(async () => {
|
|
520
|
+
attempts += 1;
|
|
521
|
+
const result = await executeCommand(normalized.command, [], {
|
|
522
|
+
cwd: normalized.cwd,
|
|
523
|
+
timeout: normalized.budgetMs,
|
|
524
|
+
shell: true
|
|
525
|
+
});
|
|
526
|
+
if (!result.success) {
|
|
527
|
+
throw new ResearchCommandError(result);
|
|
528
|
+
}
|
|
529
|
+
return result;
|
|
530
|
+
}, {
|
|
531
|
+
timeout: normalized.budgetMs,
|
|
532
|
+
maxRetries: normalized.maxRetries,
|
|
533
|
+
metadata: {
|
|
534
|
+
sessionId,
|
|
535
|
+
command: normalized.command
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
} catch (caughtError) {
|
|
539
|
+
error = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
|
|
540
|
+
if (caughtError instanceof ResearchCommandError) {
|
|
541
|
+
commandResult = caughtError.result;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const durationMs = Date.now() - startedAt;
|
|
545
|
+
const finalizedResult = commandResult || {
|
|
546
|
+
success: false,
|
|
547
|
+
stdout: "",
|
|
548
|
+
stderr: "",
|
|
549
|
+
exitCode: 1,
|
|
550
|
+
error: error?.message || "Research command failed"
|
|
551
|
+
};
|
|
552
|
+
const status = finalizedResult.success ? "completed" : getFailureStatus(finalizedResult, error);
|
|
553
|
+
const combinedOutput = [finalizedResult.stdout, finalizedResult.stderr].filter(Boolean).join("\n");
|
|
554
|
+
const metricValue = extractMetricValue(combinedOutput, normalized.metricName);
|
|
555
|
+
const verdict = determineVerdict(finalizedResult.success, normalized.metricName, metricValue);
|
|
556
|
+
const output = {
|
|
557
|
+
data: {
|
|
558
|
+
command: normalized.command,
|
|
559
|
+
cwd: normalized.cwd,
|
|
560
|
+
status,
|
|
561
|
+
exitCode: finalizedResult.exitCode,
|
|
562
|
+
stdout: finalizedResult.stdout,
|
|
563
|
+
stderr: finalizedResult.stderr,
|
|
564
|
+
metricName: normalized.metricName,
|
|
565
|
+
metricValue,
|
|
566
|
+
durationMs,
|
|
567
|
+
attempts,
|
|
568
|
+
phase: runPhase,
|
|
569
|
+
verdict
|
|
570
|
+
},
|
|
571
|
+
logs: combinedOutput ? combinedOutput.split(/\r?\n/) : [],
|
|
572
|
+
metadata: {
|
|
573
|
+
status,
|
|
574
|
+
success: finalizedResult.success,
|
|
575
|
+
verdict
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
persistence.updateTaskStatus(
|
|
579
|
+
taskId,
|
|
580
|
+
finalizedResult.success ? "completed" : "failed",
|
|
581
|
+
output,
|
|
582
|
+
finalizedResult.success ? void 0 : error
|
|
583
|
+
);
|
|
584
|
+
persistence.recordMetrics({
|
|
585
|
+
taskId,
|
|
586
|
+
sessionId,
|
|
587
|
+
executionTime: durationMs,
|
|
588
|
+
retryCount: Math.max(0, attempts - 1),
|
|
589
|
+
success: finalizedResult.success,
|
|
590
|
+
errorType: finalizedResult.success ? void 0 : status
|
|
591
|
+
});
|
|
592
|
+
persistence.logDecision({
|
|
593
|
+
id: `decision-${nanoid(8)}`,
|
|
594
|
+
sessionId,
|
|
595
|
+
taskId,
|
|
596
|
+
decision: "record research run",
|
|
597
|
+
reasoning: normalized.metricName ? `Executed the research command and tracked metric ${normalized.metricName}.` : "Executed the research command without a configured metric parser.",
|
|
598
|
+
context: JSON.stringify({
|
|
599
|
+
command: normalized.command,
|
|
600
|
+
cwd: normalized.cwd,
|
|
601
|
+
budgetMs: normalized.budgetMs,
|
|
602
|
+
metricName: normalized.metricName,
|
|
603
|
+
phase: runPhase
|
|
604
|
+
}),
|
|
605
|
+
outcome: finalizedResult.success ? metricValue === void 0 ? "completed without metric" : `${normalized.metricName}=${metricValue}` : finalizedResult.error || error?.message || "failed"
|
|
606
|
+
});
|
|
607
|
+
persistence.saveSession(sessionId, {
|
|
608
|
+
kind: RESEARCH_SESSION_KIND,
|
|
609
|
+
name: normalized.name,
|
|
610
|
+
command: normalized.command,
|
|
611
|
+
cwd: normalized.cwd,
|
|
612
|
+
metricName: normalized.metricName,
|
|
613
|
+
budgetMs: normalized.budgetMs,
|
|
614
|
+
currentPhase: "report",
|
|
615
|
+
phaseHistory,
|
|
616
|
+
runPhase,
|
|
617
|
+
verdict,
|
|
618
|
+
status,
|
|
619
|
+
exitCode: finalizedResult.exitCode,
|
|
620
|
+
metricValue,
|
|
621
|
+
durationMs,
|
|
622
|
+
createdAt
|
|
623
|
+
});
|
|
624
|
+
appendResultRow({
|
|
625
|
+
timestamp: createdAt,
|
|
626
|
+
sessionId,
|
|
627
|
+
taskId,
|
|
628
|
+
name: normalized.name,
|
|
629
|
+
phase: runPhase,
|
|
630
|
+
verdict,
|
|
631
|
+
status,
|
|
632
|
+
exitCode: finalizedResult.exitCode,
|
|
633
|
+
metricName: normalized.metricName,
|
|
634
|
+
metricValue,
|
|
635
|
+
durationMs,
|
|
636
|
+
cwd: normalized.cwd,
|
|
637
|
+
command: normalized.command
|
|
638
|
+
}, normalized.dbPath);
|
|
639
|
+
return {
|
|
640
|
+
sessionId,
|
|
641
|
+
taskId,
|
|
642
|
+
name: normalized.name,
|
|
643
|
+
command: normalized.command,
|
|
644
|
+
cwd: normalized.cwd,
|
|
645
|
+
metricName: normalized.metricName,
|
|
646
|
+
metricValue,
|
|
647
|
+
success: finalizedResult.success,
|
|
648
|
+
status,
|
|
649
|
+
exitCode: finalizedResult.exitCode,
|
|
650
|
+
stdout: finalizedResult.stdout,
|
|
651
|
+
stderr: finalizedResult.stderr,
|
|
652
|
+
durationMs,
|
|
653
|
+
phase: runPhase,
|
|
654
|
+
verdict,
|
|
655
|
+
phaseHistory,
|
|
656
|
+
createdAt
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function listResearchSessions(limit = DEFAULT_SESSION_LIMIT, dbPath) {
|
|
660
|
+
const persistence = createPersistence(dbPath);
|
|
661
|
+
return persistence.listSessions(Math.max(limit * 5, 50)).filter((session) => session.metadata?.kind === RESEARCH_SESSION_KIND).slice(0, limit).map((session) => ({
|
|
662
|
+
id: session.id,
|
|
663
|
+
createdAt: session.createdAt,
|
|
664
|
+
name: session.metadata.name || session.id,
|
|
665
|
+
command: session.metadata.command || "",
|
|
666
|
+
cwd: session.metadata.cwd || process.cwd(),
|
|
667
|
+
metricName: session.metadata.metricName,
|
|
668
|
+
budgetMs: session.metadata.budgetMs || DEFAULT_BUDGET_MS,
|
|
669
|
+
currentPhase: session.metadata.currentPhase || "experiment",
|
|
670
|
+
verdict: session.metadata.verdict
|
|
671
|
+
}));
|
|
672
|
+
}
|
|
673
|
+
function getLatestResearchSession(dbPath) {
|
|
674
|
+
return listResearchSessions(1, dbPath)[0];
|
|
675
|
+
}
|
|
676
|
+
function getResearchSessionStatus(sessionId, dbPath) {
|
|
677
|
+
const persistence = createPersistence(dbPath);
|
|
678
|
+
const restored = persistence.restoreContext(sessionId);
|
|
679
|
+
if (!restored) {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
sessionId,
|
|
684
|
+
metadata: restored.metadata,
|
|
685
|
+
tasks: restored.tasks,
|
|
686
|
+
metrics: persistence.getSessionMetrics(sessionId),
|
|
687
|
+
decisions: persistence.getDecisionLog(sessionId),
|
|
688
|
+
recovery: persistence.recoverExecutionState(sessionId)
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function listResearchResults(limit = DEFAULT_SESSION_LIMIT, dbPath) {
|
|
692
|
+
return readAllResultRows(dbPath).reverse().slice(0, limit);
|
|
693
|
+
}
|
|
694
|
+
function getBestResearchResult(dbPath) {
|
|
695
|
+
const rows = readAllResultRows(dbPath);
|
|
696
|
+
if (rows.length === 0) {
|
|
697
|
+
return void 0;
|
|
698
|
+
}
|
|
699
|
+
return selectBestByMetric(rows);
|
|
700
|
+
}
|
|
701
|
+
function getResearchReport(sessionId, dbPath) {
|
|
702
|
+
const resolvedSessionId = sessionId || getLatestResearchSession(dbPath)?.id;
|
|
703
|
+
if (!resolvedSessionId) {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
const status = getResearchSessionStatus(resolvedSessionId, dbPath);
|
|
707
|
+
if (!status) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
const latestTask = status.tasks[status.tasks.length - 1];
|
|
711
|
+
const outputData = latestTask?.output?.data || {};
|
|
712
|
+
const phaseHistory = status.metadata.phaseHistory || [];
|
|
713
|
+
const verdict = status.metadata.verdict || determineVerdict(status.metrics.failedTasks === 0, status.metadata.metricName, outputData.metricValue);
|
|
714
|
+
const currentPhase = status.metadata.currentPhase || "report";
|
|
715
|
+
const createdAt = String(status.metadata.createdAt || "");
|
|
716
|
+
const outcome = String(
|
|
717
|
+
status.decisions[status.decisions.length - 1]?.outcome || status.decisions[status.decisions.length - 1]?.decision || outputData.stderr || outputData.status || "unknown"
|
|
718
|
+
).replace(/\s+/g, " ").trim().slice(0, 160);
|
|
719
|
+
const lines = [];
|
|
720
|
+
lines.push("# CCJK Research Report");
|
|
721
|
+
lines.push("");
|
|
722
|
+
lines.push(`**Session**: ${resolvedSessionId}`);
|
|
723
|
+
lines.push(`**Name**: ${status.metadata.name || resolvedSessionId}`);
|
|
724
|
+
lines.push(`**Created**: ${createdAt || "unknown"}`);
|
|
725
|
+
lines.push(`**Phase**: ${currentPhase}`);
|
|
726
|
+
lines.push(`**Verdict**: ${verdict}`);
|
|
727
|
+
lines.push(`**Command**: ${status.metadata.command || outputData.command || "unknown"}`);
|
|
728
|
+
lines.push(`**CWD**: ${status.metadata.cwd || outputData.cwd || process.cwd()}`);
|
|
729
|
+
lines.push(`**Exit Code**: ${outputData.exitCode ?? "unknown"}`);
|
|
730
|
+
lines.push(`**Status**: ${outputData.status || status.metadata.status || "unknown"}`);
|
|
731
|
+
lines.push(`**Metric**: ${status.metadata.metricName ? `${status.metadata.metricName}=${outputData.metricValue ?? "not found"}` : "not configured"}`);
|
|
732
|
+
lines.push(`**Duration**: ${outputData.durationMs ?? status.metadata.durationMs ?? 0}ms`);
|
|
733
|
+
lines.push(`**Outcome**: ${outcome}`);
|
|
734
|
+
if (phaseHistory.length > 0) {
|
|
735
|
+
lines.push("");
|
|
736
|
+
lines.push("## Phase History");
|
|
737
|
+
lines.push("");
|
|
738
|
+
lines.push(`- ${phaseHistory.join(" \u2192 ")}`);
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
sessionId: resolvedSessionId,
|
|
742
|
+
name: status.metadata.name || resolvedSessionId,
|
|
743
|
+
createdAt,
|
|
744
|
+
currentPhase,
|
|
745
|
+
verdict,
|
|
746
|
+
command: status.metadata.command || outputData.command || "unknown",
|
|
747
|
+
cwd: status.metadata.cwd || outputData.cwd || process.cwd(),
|
|
748
|
+
status: String(outputData.status || status.metadata.status || "unknown"),
|
|
749
|
+
exitCode: Number(outputData.exitCode ?? 0),
|
|
750
|
+
metricName: status.metadata.metricName,
|
|
751
|
+
metricValue: typeof outputData.metricValue === "number" ? outputData.metricValue : void 0,
|
|
752
|
+
durationMs: Number(outputData.durationMs ?? status.metadata.durationMs ?? 0),
|
|
753
|
+
phaseHistory,
|
|
754
|
+
outcome,
|
|
755
|
+
content: lines.join("\n")
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function printDivider() {
|
|
760
|
+
console.log(a.dim("\u2500".repeat(60)));
|
|
761
|
+
}
|
|
762
|
+
function formatMetric(metricName, metricValue) {
|
|
763
|
+
if (!metricName) {
|
|
764
|
+
return "not configured";
|
|
765
|
+
}
|
|
766
|
+
if (metricValue === void 0) {
|
|
767
|
+
return `${metricName} (not found)`;
|
|
768
|
+
}
|
|
769
|
+
return `${metricName}=${metricValue}`;
|
|
770
|
+
}
|
|
771
|
+
function formatTimestamp(value) {
|
|
772
|
+
if (!value) {
|
|
773
|
+
return "unknown";
|
|
774
|
+
}
|
|
775
|
+
const date = new Date(value);
|
|
776
|
+
return Number.isNaN(date.getTime()) ? value : date.toISOString();
|
|
777
|
+
}
|
|
778
|
+
function previewCommand(command, maxLength = 48) {
|
|
779
|
+
if (command.length <= maxLength) {
|
|
780
|
+
return command;
|
|
781
|
+
}
|
|
782
|
+
return `${command.slice(0, maxLength - 1)}\u2026`;
|
|
783
|
+
}
|
|
784
|
+
function getLatestTaskData(status) {
|
|
785
|
+
if (!status || status.tasks.length === 0) {
|
|
786
|
+
return {};
|
|
787
|
+
}
|
|
788
|
+
const latestTask = status.tasks[status.tasks.length - 1];
|
|
789
|
+
return latestTask?.output?.data || {};
|
|
790
|
+
}
|
|
791
|
+
async function researchCommand(action, args = [], options = {}) {
|
|
792
|
+
switch (action) {
|
|
793
|
+
case "run":
|
|
794
|
+
await runResearchCommand(options);
|
|
795
|
+
return;
|
|
796
|
+
case "status":
|
|
797
|
+
await showResearchStatus(args[0] || void 0, options);
|
|
798
|
+
return;
|
|
799
|
+
case "sessions":
|
|
800
|
+
case "list":
|
|
801
|
+
await listResearchCommand(options);
|
|
802
|
+
return;
|
|
803
|
+
case "results":
|
|
804
|
+
await showResearchResults(options);
|
|
805
|
+
return;
|
|
806
|
+
case "report":
|
|
807
|
+
await showResearchReport(args[0] || void 0, options);
|
|
808
|
+
return;
|
|
809
|
+
case "help":
|
|
810
|
+
case "":
|
|
811
|
+
showResearchHelp();
|
|
812
|
+
return;
|
|
813
|
+
default:
|
|
814
|
+
console.error(a.red(`Unknown research action: ${action}`));
|
|
815
|
+
showResearchHelp();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function runResearchCommand(options) {
|
|
819
|
+
if (!options.cmd) {
|
|
820
|
+
console.error(a.red("Error: --cmd is required for `ccjk research run`"));
|
|
821
|
+
console.log(a.dim('Example: ccjk research run --name baseline --cmd "python train.py" --metric val_bpb'));
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const result = await runResearchExperiment({
|
|
825
|
+
name: options.name,
|
|
826
|
+
command: options.cmd,
|
|
827
|
+
metricName: options.metric,
|
|
828
|
+
budgetMs: options.budgetMs,
|
|
829
|
+
cwd: options.cwd,
|
|
830
|
+
dbPath: options.dbPath
|
|
831
|
+
});
|
|
832
|
+
console.log("");
|
|
833
|
+
console.log(a.bold.cyan("\u{1F52C} Research Run"));
|
|
834
|
+
printDivider();
|
|
835
|
+
console.log(a.gray(`Session: ${result.sessionId}`));
|
|
836
|
+
console.log(a.gray(`Task: ${result.taskId}`));
|
|
837
|
+
console.log(a.gray(`Name: ${result.name}`));
|
|
838
|
+
console.log(a.gray(`Phase: ${result.phase}`));
|
|
839
|
+
console.log(a.gray(`Verdict: ${result.verdict}`));
|
|
840
|
+
console.log(a.gray(`Command: ${result.command}`));
|
|
841
|
+
console.log(a.gray(`CWD: ${result.cwd}`));
|
|
842
|
+
console.log(a.gray(`Status: ${result.status}`));
|
|
843
|
+
console.log(a.gray(`Exit code: ${result.exitCode}`));
|
|
844
|
+
console.log(a.gray(`Metric: ${formatMetric(result.metricName, result.metricValue)}`));
|
|
845
|
+
console.log(a.gray(`Duration: ${result.durationMs}ms`));
|
|
846
|
+
if (result.stderr) {
|
|
847
|
+
console.log("");
|
|
848
|
+
console.log(a.bold("stderr"));
|
|
849
|
+
console.log(a.dim(result.stderr));
|
|
850
|
+
}
|
|
851
|
+
if (result.stdout) {
|
|
852
|
+
console.log("");
|
|
853
|
+
console.log(a.bold("stdout"));
|
|
854
|
+
console.log(a.dim(result.stdout));
|
|
855
|
+
}
|
|
856
|
+
console.log("");
|
|
857
|
+
}
|
|
858
|
+
async function showResearchStatus(sessionId, options) {
|
|
859
|
+
const resolvedSessionId = sessionId || getLatestResearchSession(options.dbPath)?.id;
|
|
860
|
+
if (!resolvedSessionId) {
|
|
861
|
+
console.log(a.yellow("No research sessions found."));
|
|
862
|
+
console.log("");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const status = getResearchSessionStatus(resolvedSessionId, options.dbPath);
|
|
866
|
+
if (!status) {
|
|
867
|
+
console.log(a.yellow(`Research session not found: ${resolvedSessionId}`));
|
|
868
|
+
console.log("");
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const outputData = getLatestTaskData(status);
|
|
872
|
+
const metricValue = typeof outputData.metricValue === "number" ? outputData.metricValue : typeof status.metadata.metricValue === "number" ? status.metadata.metricValue : void 0;
|
|
873
|
+
console.log("");
|
|
874
|
+
console.log(a.bold.cyan("\u{1F9EA} Research Status"));
|
|
875
|
+
printDivider();
|
|
876
|
+
console.log(a.gray(`Session: ${status.sessionId}`));
|
|
877
|
+
console.log(a.gray(`Name: ${status.metadata.name || status.sessionId}`));
|
|
878
|
+
console.log(a.gray(`Phase: ${status.metadata.currentPhase || "unknown"}`));
|
|
879
|
+
console.log(a.gray(`Verdict: ${status.metadata.verdict || "unknown"}`));
|
|
880
|
+
console.log(a.gray(`Command: ${status.metadata.command || "unknown"}`));
|
|
881
|
+
console.log(a.gray(`Metric: ${formatMetric(status.metadata.metricName, metricValue)}`));
|
|
882
|
+
console.log(a.gray(`Status: ${outputData.status || status.metadata.status || "unknown"}`));
|
|
883
|
+
console.log(a.gray(`Exit code: ${outputData.exitCode ?? status.metadata.exitCode ?? "unknown"}`));
|
|
884
|
+
console.log(a.gray(`Duration: ${outputData.durationMs ?? status.metadata.durationMs ?? "unknown"}ms`));
|
|
885
|
+
console.log(a.gray(`Tasks: ${status.metrics.completedTasks}/${status.metrics.totalTasks} completed`));
|
|
886
|
+
console.log(a.gray(`Success: ${Math.round((status.metrics.successRate || 0) * 100)}%`));
|
|
887
|
+
console.log(a.gray(`Avg ms: ${status.metrics.avgExecutionTime}`));
|
|
888
|
+
console.log(a.gray(`Next: ${status.recovery.nextExecutable.length}`));
|
|
889
|
+
if (status.decisions.length > 0) {
|
|
890
|
+
const lastDecision = status.decisions[status.decisions.length - 1];
|
|
891
|
+
console.log(a.gray(`Decision: ${lastDecision.outcome || lastDecision.decision}`));
|
|
892
|
+
}
|
|
893
|
+
console.log("");
|
|
894
|
+
}
|
|
895
|
+
async function listResearchCommand(options) {
|
|
896
|
+
const sessions = listResearchSessions(options.limit || 10, options.dbPath);
|
|
897
|
+
console.log("");
|
|
898
|
+
console.log(a.bold.cyan("\u{1F4DA} Research Sessions"));
|
|
899
|
+
printDivider();
|
|
900
|
+
if (sessions.length === 0) {
|
|
901
|
+
console.log(a.yellow("No research sessions found."));
|
|
902
|
+
console.log("");
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
for (const session of sessions) {
|
|
906
|
+
console.log(a.bold(session.name));
|
|
907
|
+
console.log(a.gray(` ${session.id}`));
|
|
908
|
+
console.log(a.gray(` phase: ${session.currentPhase}`));
|
|
909
|
+
console.log(a.gray(` verdict: ${session.verdict || "unknown"}`));
|
|
910
|
+
console.log(a.gray(` cmd: ${session.command}`));
|
|
911
|
+
console.log(a.gray(` metric: ${session.metricName || "not configured"}`));
|
|
912
|
+
console.log(a.gray(` cwd: ${session.cwd}`));
|
|
913
|
+
}
|
|
914
|
+
console.log("");
|
|
915
|
+
}
|
|
916
|
+
async function showResearchResults(options) {
|
|
917
|
+
const rows = listResearchResults(options.limit || 10, options.dbPath);
|
|
918
|
+
const best = getBestResearchResult(options.dbPath);
|
|
919
|
+
console.log("");
|
|
920
|
+
console.log(a.bold.cyan("\u{1F4C8} Research Results"));
|
|
921
|
+
printDivider();
|
|
922
|
+
if (rows.length === 0) {
|
|
923
|
+
console.log(a.yellow("No research results found."));
|
|
924
|
+
console.log("");
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (best) {
|
|
928
|
+
console.log(a.bold("Best"));
|
|
929
|
+
console.log(a.gray(` ${best.name} \xB7 ${formatMetric(best.metricName, best.metricValue)} \xB7 ${best.status} \xB7 ${best.durationMs}ms`));
|
|
930
|
+
console.log(a.gray(` ${previewCommand(best.command)}`));
|
|
931
|
+
console.log("");
|
|
932
|
+
}
|
|
933
|
+
console.log(a.bold("Recent"));
|
|
934
|
+
for (const row of rows) {
|
|
935
|
+
console.log(a.gray(`${formatTimestamp(row.timestamp)} \xB7 ${row.name} \xB7 ${row.status} \xB7 ${formatMetric(row.metricName, row.metricValue)} \xB7 ${row.durationMs}ms \xB7 ${previewCommand(row.command)}`));
|
|
936
|
+
}
|
|
937
|
+
console.log("");
|
|
938
|
+
}
|
|
939
|
+
async function showResearchReport(sessionId, options) {
|
|
940
|
+
const report = getResearchReport(sessionId, options.dbPath);
|
|
941
|
+
if (!report) {
|
|
942
|
+
console.log(a.yellow(sessionId ? `Research session not found: ${sessionId}` : "No research sessions found."));
|
|
943
|
+
console.log("");
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
console.log("");
|
|
947
|
+
console.log(a.bold.cyan("\u{1F4DD} Research Report"));
|
|
948
|
+
printDivider();
|
|
949
|
+
console.log(a.gray(`Session: ${report.sessionId}`));
|
|
950
|
+
console.log(a.gray(`Name: ${report.name}`));
|
|
951
|
+
console.log(a.gray(`Created: ${formatTimestamp(report.createdAt)}`));
|
|
952
|
+
console.log(a.gray(`Command: ${report.command}`));
|
|
953
|
+
console.log(a.gray(`CWD: ${report.cwd}`));
|
|
954
|
+
console.log(a.gray(`Status: ${report.status}`));
|
|
955
|
+
console.log(a.gray(`Exit code: ${report.exitCode}`));
|
|
956
|
+
console.log(a.gray(`Duration: ${report.durationMs}ms`));
|
|
957
|
+
console.log(a.gray(`Metric: ${formatMetric(report.metricName, report.metricValue)}`));
|
|
958
|
+
console.log(a.gray(`Outcome: ${report.outcome}`));
|
|
959
|
+
if (report.phaseHistory.length > 0) {
|
|
960
|
+
console.log(a.gray(`Phases: ${report.phaseHistory.join(" \u2192 ")}`));
|
|
961
|
+
}
|
|
962
|
+
console.log("");
|
|
963
|
+
}
|
|
964
|
+
function showResearchHelp() {
|
|
965
|
+
console.log("");
|
|
966
|
+
console.log(a.bold.cyan("ccjk research"));
|
|
967
|
+
printDivider();
|
|
968
|
+
console.log(" run Run a single persisted research experiment");
|
|
969
|
+
console.log(" status Show the latest or selected research session status");
|
|
970
|
+
console.log(" sessions List recent research sessions");
|
|
971
|
+
console.log(" results Show recent result rows and the current best run");
|
|
972
|
+
console.log(" report Render a compact persisted research report");
|
|
973
|
+
console.log("");
|
|
974
|
+
console.log(a.dim('Example: ccjk research run --name baseline --cmd "python train.py" --metric val_bpb'));
|
|
975
|
+
console.log(a.dim("Example: ccjk research report research-123"));
|
|
976
|
+
console.log("");
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
export { researchCommand, showResearchHelp };
|