ccjk 13.6.4 → 13.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/chunks/agents.mjs +1 -1
  2. package/dist/chunks/api-config-selector.mjs +6 -4
  3. package/dist/chunks/auto-updater.mjs +100 -2
  4. package/dist/chunks/banner.mjs +0 -16
  5. package/dist/chunks/ccjk-mcp.mjs +2 -2
  6. package/dist/chunks/ccr.mjs +6 -4
  7. package/dist/chunks/check-updates.mjs +28 -17
  8. package/dist/chunks/claude-code-config-manager.mjs +3 -1
  9. package/dist/chunks/claude-code-incremental-manager.mjs +46 -20
  10. package/dist/chunks/claude-config.mjs +52 -2
  11. package/dist/chunks/claude-wrapper.mjs +1 -1
  12. package/dist/chunks/cli-hook.mjs +25 -83
  13. package/dist/chunks/codex-config-switch.mjs +3 -2
  14. package/dist/chunks/codex-provider-manager.mjs +1 -0
  15. package/dist/chunks/codex.mjs +3 -359
  16. package/dist/chunks/config-switch.mjs +23 -10
  17. package/dist/chunks/config.mjs +1 -1
  18. package/dist/chunks/config2.mjs +3 -3
  19. package/dist/chunks/constants.mjs +179 -3
  20. package/dist/chunks/doctor.mjs +1 -1
  21. package/dist/chunks/features.mjs +76 -11
  22. package/dist/chunks/index10.mjs +55 -36
  23. package/dist/chunks/init.mjs +120 -61
  24. package/dist/chunks/installer.mjs +80 -19
  25. package/dist/chunks/mcp-cli.mjs +17 -16
  26. package/dist/chunks/mcp.mjs +8 -7
  27. package/dist/chunks/memory-check.mjs +1 -1
  28. package/dist/chunks/package.mjs +1 -1
  29. package/dist/chunks/platform.mjs +5 -1
  30. package/dist/chunks/quick-setup.mjs +13 -11
  31. package/dist/chunks/research.mjs +1177 -0
  32. package/dist/chunks/sessions.mjs +1 -1
  33. package/dist/chunks/smart-defaults.mjs +42 -14
  34. package/dist/chunks/uninstall.mjs +2 -2
  35. package/dist/chunks/update.mjs +14 -13
  36. package/dist/chunks/version-checker.mjs +11 -1
  37. package/dist/cli.mjs +32 -0
  38. package/dist/i18n/locales/en/cli.json +0 -4
  39. package/dist/i18n/locales/en/menu.json +3 -3
  40. package/dist/i18n/locales/en/notification.json +2 -2
  41. package/dist/i18n/locales/zh-CN/cli.json +0 -4
  42. package/dist/i18n/locales/zh-CN/menu.json +3 -3
  43. package/dist/i18n/locales/zh-CN/notification.json +2 -2
  44. package/dist/index.d.mts +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.mjs +8 -174
  47. package/dist/shared/{ccjk.DvAP4XfP.mjs → ccjk.B4aXNclK.mjs} +2 -2
  48. package/dist/shared/ccjk.BI-hdI7P.mjs +30 -0
  49. package/dist/shared/{ccjk.DwSebGy0.mjs → ccjk.BOO14f66.mjs} +1 -1
  50. package/dist/shared/ccjk.BnsY5WxD.mjs +171 -0
  51. package/dist/shared/{ccjk.C4m4ypdk.mjs → ccjk.DHaUdzX3.mjs} +4 -3
  52. package/dist/shared/ccjk.DKXs7Fbm.mjs +361 -0
  53. package/dist/shared/{ccjk.BP5hsTZQ.mjs → ccjk.Dz0ssUQx.mjs} +1 -1
  54. package/dist/shared/ccjk.yYQMbHH3.mjs +115 -0
  55. package/package.json +70 -65
  56. package/templates/common/workflow/essential/en/feat.md +68 -291
  57. package/templates/common/workflow/sixStep/en/workflow.md +56 -330
  58. package/dist/shared/ccjk.CiKtBUW_.mjs +0 -54
@@ -0,0 +1,1177 @@
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 sanitizeLedgerField(value) {
313
+ if (value === void 0 || value === null) {
314
+ return "";
315
+ }
316
+ return String(value).replace(/\t/g, " ").replace(/\r?\n/g, " ").trim();
317
+ }
318
+ function ensureResultsLedger(dbPath) {
319
+ const ledgerPath = getResultsLedgerPath(dbPath);
320
+ if (!existsSync(ledgerPath)) {
321
+ writeFileSync(ledgerPath, `${RESULTS_LEDGER_HEADERS.join(" ")}
322
+ `, "utf8");
323
+ }
324
+ return ledgerPath;
325
+ }
326
+ function serializeResultRow(row) {
327
+ const values = {
328
+ timestamp: sanitizeLedgerField(row.timestamp),
329
+ sessionId: sanitizeLedgerField(row.sessionId),
330
+ taskId: sanitizeLedgerField(row.taskId),
331
+ name: sanitizeLedgerField(row.name),
332
+ phase: sanitizeLedgerField(row.phase),
333
+ verdict: sanitizeLedgerField(row.verdict),
334
+ status: sanitizeLedgerField(row.status),
335
+ exitCode: sanitizeLedgerField(row.exitCode),
336
+ metricName: sanitizeLedgerField(row.metricName),
337
+ metricValue: sanitizeLedgerField(row.metricValue),
338
+ durationMs: sanitizeLedgerField(row.durationMs),
339
+ cwd: sanitizeLedgerField(row.cwd),
340
+ command: sanitizeLedgerField(row.command)
341
+ };
342
+ return RESULTS_LEDGER_HEADERS.map((header) => values[header]).join(" ");
343
+ }
344
+ function parseResultRow(line) {
345
+ if (!line.trim()) {
346
+ return null;
347
+ }
348
+ const columns = line.split(" ");
349
+ if (columns.length < RESULTS_LEDGER_HEADERS.length) {
350
+ return null;
351
+ }
352
+ const [
353
+ timestamp,
354
+ sessionId,
355
+ taskId,
356
+ name,
357
+ phase,
358
+ verdict,
359
+ status,
360
+ exitCode,
361
+ metricName,
362
+ metricValue,
363
+ durationMs,
364
+ cwd,
365
+ command
366
+ ] = columns;
367
+ const parsedMetric = metricValue ? Number.parseFloat(metricValue) : void 0;
368
+ return {
369
+ timestamp,
370
+ sessionId,
371
+ taskId,
372
+ name,
373
+ phase: phase || "experiment",
374
+ verdict: verdict || "PARTIAL",
375
+ status,
376
+ exitCode: Number.parseInt(exitCode, 10) || 0,
377
+ metricName: metricName || void 0,
378
+ metricValue: Number.isFinite(parsedMetric) ? parsedMetric : void 0,
379
+ durationMs: Number.parseInt(durationMs, 10) || 0,
380
+ cwd,
381
+ command
382
+ };
383
+ }
384
+ function appendResultRow(row, dbPath) {
385
+ const ledgerPath = ensureResultsLedger(dbPath);
386
+ appendFileSync(ledgerPath, `${serializeResultRow(row)}
387
+ `, "utf8");
388
+ }
389
+ function readAllResultRows(dbPath) {
390
+ const ledgerPath = getResultsLedgerPath(dbPath);
391
+ if (!existsSync(ledgerPath)) {
392
+ return [];
393
+ }
394
+ const content = readFileSync(ledgerPath, "utf8");
395
+ const lines = content.split(/\r?\n/).filter(Boolean);
396
+ if (lines.length <= 1) {
397
+ return [];
398
+ }
399
+ return lines.slice(1).map(parseResultRow).filter((row) => Boolean(row));
400
+ }
401
+ function resolveResearchObjective(metricName, objective = "auto") {
402
+ if (objective === "maximize" || objective === "minimize") {
403
+ return objective;
404
+ }
405
+ const normalized = (metricName || "").toLowerCase();
406
+ if (!normalized) {
407
+ return "maximize";
408
+ }
409
+ const lowerIsBetter = ["loss", "error", "bpb", "perplexity", "ppl", "latency", "duration", "time", "cost", "price", "wer", "cer"];
410
+ if (lowerIsBetter.some((keyword) => normalized.includes(keyword))) {
411
+ return "minimize";
412
+ }
413
+ return "maximize";
414
+ }
415
+ function compareMetricValues(baselineMetricValue, candidateMetricValue, objective) {
416
+ if (baselineMetricValue === void 0 || candidateMetricValue === void 0) {
417
+ return "unknown";
418
+ }
419
+ if (candidateMetricValue === baselineMetricValue) {
420
+ return "equal";
421
+ }
422
+ if (objective === "minimize") {
423
+ return candidateMetricValue < baselineMetricValue ? "better" : "worse";
424
+ }
425
+ return candidateMetricValue > baselineMetricValue ? "better" : "worse";
426
+ }
427
+ function formatSignedNumber(value) {
428
+ if (value === void 0) {
429
+ return "unknown";
430
+ }
431
+ return `${value > 0 ? "+" : ""}${value}`;
432
+ }
433
+ function formatPercent(value) {
434
+ if (value === void 0) {
435
+ return "unknown";
436
+ }
437
+ return `${value > 0 ? "+" : ""}${value.toFixed(2)}%`;
438
+ }
439
+ function buildComparisonSummary(input) {
440
+ const result = compareMetricValues(input.baselineMetricValue, input.candidateMetricValue, input.objective);
441
+ const delta = input.baselineMetricValue !== void 0 && input.candidateMetricValue !== void 0 ? input.candidateMetricValue - input.baselineMetricValue : void 0;
442
+ const percentDelta = input.baselineMetricValue !== void 0 && input.baselineMetricValue !== 0 && delta !== void 0 ? delta / input.baselineMetricValue * 100 : void 0;
443
+ const goal = input.objective === "minimize" ? "lower" : "higher";
444
+ let summary = `Needs ${goal} ${input.objective === "minimize" ? "metric reduction" : "metric lift"} data.`;
445
+ if (result === "better") {
446
+ summary = `Candidate is better than baseline (${input.candidateMetricValue} vs ${input.baselineMetricValue}, \u0394 ${formatSignedNumber(delta)} / ${formatPercent(percentDelta)}).`;
447
+ } else if (result === "worse") {
448
+ summary = `Candidate is worse than baseline (${input.candidateMetricValue} vs ${input.baselineMetricValue}, \u0394 ${formatSignedNumber(delta)} / ${formatPercent(percentDelta)}).`;
449
+ } else if (result === "equal") {
450
+ summary = `Candidate matches baseline (${input.candidateMetricValue}).`;
451
+ }
452
+ return {
453
+ baselineSessionId: input.baselineSessionId,
454
+ baselineName: input.baselineName,
455
+ objective: input.objective,
456
+ result,
457
+ baselineMetricValue: input.baselineMetricValue,
458
+ candidateMetricValue: input.candidateMetricValue,
459
+ delta,
460
+ percentDelta,
461
+ summary
462
+ };
463
+ }
464
+ function determineVerdict(args) {
465
+ if (!args.success) {
466
+ return {
467
+ verdict: "FAIL",
468
+ reason: "Command execution failed."
469
+ };
470
+ }
471
+ if (args.metricName && args.metricValue === void 0) {
472
+ return {
473
+ verdict: "PARTIAL",
474
+ reason: `Metric ${args.metricName} was configured but not found in output.`
475
+ };
476
+ }
477
+ if (args.comparison?.result === "worse") {
478
+ return {
479
+ verdict: "FAIL",
480
+ reason: args.comparison.summary
481
+ };
482
+ }
483
+ if (args.comparison?.result === "unknown") {
484
+ return {
485
+ verdict: "PARTIAL",
486
+ reason: args.comparison.summary
487
+ };
488
+ }
489
+ if (args.comparison?.result === "equal") {
490
+ return {
491
+ verdict: "PARTIAL",
492
+ reason: args.comparison.summary
493
+ };
494
+ }
495
+ if (args.comparison?.result === "better") {
496
+ return {
497
+ verdict: "PASS",
498
+ reason: args.comparison.summary
499
+ };
500
+ }
501
+ if (args.metricName && args.metricValue !== void 0) {
502
+ return {
503
+ verdict: "PASS",
504
+ reason: `Metric ${args.metricName}=${args.metricValue} was captured successfully.`
505
+ };
506
+ }
507
+ return {
508
+ verdict: "PASS",
509
+ reason: "Command completed successfully."
510
+ };
511
+ }
512
+ function selectBestByMetric(rows) {
513
+ const newestMetricRow = [...rows].reverse().find((row) => row.metricName && row.metricValue !== void 0);
514
+ if (!newestMetricRow?.metricName) {
515
+ return [...rows].reverse().find((row) => row.verdict === "PASS") || rows[rows.length - 1];
516
+ }
517
+ const candidates = rows.filter((row) => row.metricName === newestMetricRow.metricName && row.metricValue !== void 0);
518
+ if (candidates.length === 0) {
519
+ return newestMetricRow;
520
+ }
521
+ const objective = resolveResearchObjective(newestMetricRow.metricName);
522
+ return candidates.reduce((best, current) => {
523
+ if (best.metricValue === void 0 || current.metricValue === void 0) {
524
+ return best;
525
+ }
526
+ if (objective === "minimize") {
527
+ return current.metricValue < best.metricValue ? current : best;
528
+ }
529
+ return current.metricValue > best.metricValue ? current : best;
530
+ });
531
+ }
532
+ function buildResearchTask(taskId, options) {
533
+ return {
534
+ id: taskId,
535
+ name: options.name,
536
+ description: `Run research command: ${options.command}`,
537
+ type: "research-run",
538
+ priority: "normal",
539
+ status: "pending",
540
+ requiredCapabilities: [],
541
+ input: {
542
+ parameters: {
543
+ command: options.command,
544
+ cwd: options.cwd,
545
+ metricName: options.metricName,
546
+ budgetMs: options.budgetMs
547
+ },
548
+ instructions: "Execute the configured research command and capture its metric output."
549
+ },
550
+ dependencies: [],
551
+ maxRetries: options.maxRetries,
552
+ retryCount: 0,
553
+ timeout: options.budgetMs,
554
+ metadata: {
555
+ tags: ["research", "experiment"],
556
+ category: "research",
557
+ createdBy: "ccjk research",
558
+ custom: {
559
+ metricName: options.metricName,
560
+ cwd: options.cwd,
561
+ baselineSessionId: options.baselineSessionId
562
+ }
563
+ },
564
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
565
+ progress: 0
566
+ };
567
+ }
568
+ function normalizeRunOptions(options) {
569
+ return {
570
+ command: options.command,
571
+ name: options.name || `research-${Date.now()}`,
572
+ cwd: options.cwd || process.cwd(),
573
+ budgetMs: options.budgetMs || DEFAULT_BUDGET_MS,
574
+ maxRetries: options.maxRetries ?? 0,
575
+ metricName: options.metricName,
576
+ baselineSessionId: options.baselineSessionId,
577
+ objective: options.objective || "auto",
578
+ dbPath: options.dbPath
579
+ };
580
+ }
581
+ async function runResearchExperiment(options) {
582
+ const normalized = normalizeRunOptions(options);
583
+ const persistence = createPersistence(normalized.dbPath);
584
+ const queue = new TaskQueue({
585
+ concurrency: 1,
586
+ defaultTimeout: normalized.budgetMs,
587
+ defaultMaxRetries: normalized.maxRetries
588
+ });
589
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
590
+ const runPhase = inferResearchPhase(normalized.name);
591
+ const phaseHistory = buildPhaseHistory(runPhase);
592
+ const sessionId = `research-${Date.now()}-${nanoid(6)}`;
593
+ const taskId = `research-task-${nanoid(8)}`;
594
+ const task = buildResearchTask(taskId, normalized);
595
+ const objective = resolveResearchObjective(normalized.metricName, normalized.objective);
596
+ let baselineSummary;
597
+ let comparison;
598
+ let baselineMetricValue;
599
+ if (normalized.baselineSessionId) {
600
+ baselineSummary = listResearchSessions(Number.MAX_SAFE_INTEGER, normalized.dbPath).find((session) => session.id === normalized.baselineSessionId);
601
+ const baselineReport = getResearchReport(normalized.baselineSessionId, normalized.dbPath);
602
+ if (baselineReport) {
603
+ baselineMetricValue = baselineReport.metricValue;
604
+ comparison = buildComparisonSummary({
605
+ baselineSessionId: baselineReport.sessionId,
606
+ baselineName: baselineReport.name,
607
+ baselineMetricValue,
608
+ candidateMetricValue: void 0,
609
+ objective
610
+ });
611
+ }
612
+ }
613
+ persistence.saveSession(sessionId, {
614
+ kind: RESEARCH_SESSION_KIND,
615
+ name: normalized.name,
616
+ command: normalized.command,
617
+ cwd: normalized.cwd,
618
+ metricName: normalized.metricName,
619
+ budgetMs: normalized.budgetMs,
620
+ objective,
621
+ baselineSessionId: normalized.baselineSessionId,
622
+ currentPhase: runPhase,
623
+ phaseHistory: ["brief", runPhase],
624
+ createdAt
625
+ });
626
+ persistence.saveTask(task, sessionId);
627
+ let attempts = 0;
628
+ let commandResult;
629
+ let error;
630
+ const startedAt = Date.now();
631
+ persistence.updateTaskStatus(taskId, "running");
632
+ try {
633
+ commandResult = await queue.add(async () => {
634
+ attempts += 1;
635
+ const result = await executeCommand(normalized.command, [], {
636
+ cwd: normalized.cwd,
637
+ timeout: normalized.budgetMs,
638
+ shell: true
639
+ });
640
+ if (!result.success) {
641
+ throw new ResearchCommandError(result);
642
+ }
643
+ return result;
644
+ }, {
645
+ timeout: normalized.budgetMs,
646
+ maxRetries: normalized.maxRetries,
647
+ metadata: {
648
+ sessionId,
649
+ command: normalized.command
650
+ }
651
+ });
652
+ } catch (caughtError) {
653
+ error = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
654
+ if (caughtError instanceof ResearchCommandError) {
655
+ commandResult = caughtError.result;
656
+ }
657
+ }
658
+ const durationMs = Date.now() - startedAt;
659
+ const finalizedResult = commandResult || {
660
+ success: false,
661
+ stdout: "",
662
+ stderr: "",
663
+ exitCode: 1,
664
+ error: error?.message || "Research command failed"
665
+ };
666
+ const status = finalizedResult.success ? "completed" : getFailureStatus(finalizedResult, error);
667
+ const combinedOutput = [finalizedResult.stdout, finalizedResult.stderr].filter(Boolean).join("\n");
668
+ const metricValue = extractMetricValue(combinedOutput, normalized.metricName);
669
+ if (normalized.baselineSessionId) {
670
+ comparison = buildComparisonSummary({
671
+ baselineSessionId: normalized.baselineSessionId,
672
+ baselineName: baselineSummary?.name || normalized.baselineSessionId,
673
+ baselineMetricValue,
674
+ candidateMetricValue: metricValue,
675
+ objective
676
+ });
677
+ }
678
+ const verdictDecision = determineVerdict({
679
+ success: finalizedResult.success,
680
+ metricName: normalized.metricName,
681
+ metricValue,
682
+ comparison
683
+ });
684
+ const verdict = verdictDecision.verdict;
685
+ const verdictReason = verdictDecision.reason;
686
+ const output = {
687
+ data: {
688
+ command: normalized.command,
689
+ cwd: normalized.cwd,
690
+ status,
691
+ exitCode: finalizedResult.exitCode,
692
+ stdout: finalizedResult.stdout,
693
+ stderr: finalizedResult.stderr,
694
+ metricName: normalized.metricName,
695
+ metricValue,
696
+ durationMs,
697
+ attempts,
698
+ phase: runPhase,
699
+ objective,
700
+ verdict,
701
+ verdictReason,
702
+ baselineSessionId: normalized.baselineSessionId,
703
+ comparison
704
+ },
705
+ logs: combinedOutput ? combinedOutput.split(/\r?\n/) : [],
706
+ metadata: {
707
+ status,
708
+ success: finalizedResult.success,
709
+ verdict,
710
+ objective
711
+ }
712
+ };
713
+ persistence.updateTaskStatus(
714
+ taskId,
715
+ finalizedResult.success ? "completed" : "failed",
716
+ output,
717
+ finalizedResult.success ? void 0 : error
718
+ );
719
+ persistence.recordMetrics({
720
+ taskId,
721
+ sessionId,
722
+ executionTime: durationMs,
723
+ retryCount: Math.max(0, attempts - 1),
724
+ success: finalizedResult.success,
725
+ errorType: finalizedResult.success ? void 0 : status
726
+ });
727
+ persistence.logDecision({
728
+ id: `decision-${nanoid(8)}`,
729
+ sessionId,
730
+ taskId,
731
+ decision: "record research run",
732
+ reasoning: normalized.metricName ? `Executed the research command and evaluated metric ${normalized.metricName} with objective ${objective}.` : "Executed the research command without a configured metric parser.",
733
+ context: JSON.stringify({
734
+ command: normalized.command,
735
+ cwd: normalized.cwd,
736
+ budgetMs: normalized.budgetMs,
737
+ metricName: normalized.metricName,
738
+ phase: runPhase,
739
+ objective,
740
+ baselineSessionId: normalized.baselineSessionId
741
+ }),
742
+ outcome: verdictReason
743
+ });
744
+ persistence.saveSession(sessionId, {
745
+ kind: RESEARCH_SESSION_KIND,
746
+ name: normalized.name,
747
+ command: normalized.command,
748
+ cwd: normalized.cwd,
749
+ metricName: normalized.metricName,
750
+ budgetMs: normalized.budgetMs,
751
+ objective,
752
+ baselineSessionId: normalized.baselineSessionId,
753
+ currentPhase: "report",
754
+ phaseHistory,
755
+ runPhase,
756
+ verdict,
757
+ verdictReason,
758
+ status,
759
+ exitCode: finalizedResult.exitCode,
760
+ metricValue,
761
+ comparison,
762
+ durationMs,
763
+ createdAt
764
+ });
765
+ appendResultRow({
766
+ timestamp: createdAt,
767
+ sessionId,
768
+ taskId,
769
+ name: normalized.name,
770
+ phase: runPhase,
771
+ verdict,
772
+ status,
773
+ exitCode: finalizedResult.exitCode,
774
+ metricName: normalized.metricName,
775
+ metricValue,
776
+ durationMs,
777
+ cwd: normalized.cwd,
778
+ command: normalized.command
779
+ }, normalized.dbPath);
780
+ return {
781
+ sessionId,
782
+ taskId,
783
+ name: normalized.name,
784
+ command: normalized.command,
785
+ cwd: normalized.cwd,
786
+ metricName: normalized.metricName,
787
+ metricValue,
788
+ success: finalizedResult.success,
789
+ status,
790
+ exitCode: finalizedResult.exitCode,
791
+ stdout: finalizedResult.stdout,
792
+ stderr: finalizedResult.stderr,
793
+ durationMs,
794
+ phase: runPhase,
795
+ objective,
796
+ verdict,
797
+ verdictReason,
798
+ baselineSessionId: normalized.baselineSessionId,
799
+ comparison,
800
+ phaseHistory,
801
+ createdAt
802
+ };
803
+ }
804
+ function listResearchSessions(limit = DEFAULT_SESSION_LIMIT, dbPath) {
805
+ const persistence = createPersistence(dbPath);
806
+ return persistence.listSessions(Math.max(limit * 5, 50)).filter((session) => session.metadata?.kind === RESEARCH_SESSION_KIND).slice(0, limit).map((session) => ({
807
+ id: session.id,
808
+ createdAt: session.createdAt,
809
+ name: session.metadata.name || session.id,
810
+ command: session.metadata.command || "",
811
+ cwd: session.metadata.cwd || process.cwd(),
812
+ metricName: session.metadata.metricName,
813
+ budgetMs: session.metadata.budgetMs || DEFAULT_BUDGET_MS,
814
+ currentPhase: session.metadata.currentPhase || "experiment",
815
+ objective: session.metadata.objective,
816
+ verdict: session.metadata.verdict,
817
+ baselineSessionId: session.metadata.baselineSessionId
818
+ }));
819
+ }
820
+ function getLatestResearchSession(dbPath) {
821
+ return listResearchSessions(1, dbPath)[0];
822
+ }
823
+ function getResearchSessionStatus(sessionId, dbPath) {
824
+ const persistence = createPersistence(dbPath);
825
+ const restored = persistence.restoreContext(sessionId);
826
+ if (!restored) {
827
+ return null;
828
+ }
829
+ return {
830
+ sessionId,
831
+ metadata: restored.metadata,
832
+ tasks: restored.tasks,
833
+ metrics: persistence.getSessionMetrics(sessionId),
834
+ decisions: persistence.getDecisionLog(sessionId),
835
+ recovery: persistence.recoverExecutionState(sessionId)
836
+ };
837
+ }
838
+ function listResearchResults(limit = DEFAULT_SESSION_LIMIT, dbPath) {
839
+ return readAllResultRows(dbPath).reverse().slice(0, limit);
840
+ }
841
+ function getBestResearchResult(dbPath) {
842
+ const rows = readAllResultRows(dbPath);
843
+ if (rows.length === 0) {
844
+ return void 0;
845
+ }
846
+ return selectBestByMetric(rows);
847
+ }
848
+ function getResearchReport(sessionId, dbPath) {
849
+ const resolvedSessionId = sessionId || getLatestResearchSession(dbPath)?.id;
850
+ if (!resolvedSessionId) {
851
+ return null;
852
+ }
853
+ const status = getResearchSessionStatus(resolvedSessionId, dbPath);
854
+ if (!status) {
855
+ return null;
856
+ }
857
+ const latestTask = status.tasks[status.tasks.length - 1];
858
+ const outputData = latestTask?.output?.data || {};
859
+ const phaseHistory = status.metadata.phaseHistory || [];
860
+ const comparison = status.metadata.comparison;
861
+ const verdictDecision = determineVerdict({
862
+ success: status.metrics.failedTasks === 0,
863
+ metricName: status.metadata.metricName,
864
+ metricValue: outputData.metricValue,
865
+ comparison
866
+ });
867
+ const verdict = status.metadata.verdict || verdictDecision.verdict;
868
+ const verdictReason = String(status.metadata.verdictReason || verdictDecision.reason);
869
+ const currentPhase = status.metadata.currentPhase || "report";
870
+ const createdAt = String(status.metadata.createdAt || "");
871
+ const outcome = String(
872
+ status.decisions[status.decisions.length - 1]?.outcome || status.decisions[status.decisions.length - 1]?.decision || outputData.stderr || outputData.status || "unknown"
873
+ ).replace(/\s+/g, " ").trim().slice(0, 160);
874
+ const lines = [];
875
+ lines.push("# CCJK Research Report");
876
+ lines.push("");
877
+ lines.push(`**Session**: ${resolvedSessionId}`);
878
+ lines.push(`**Name**: ${status.metadata.name || resolvedSessionId}`);
879
+ lines.push(`**Created**: ${createdAt || "unknown"}`);
880
+ lines.push(`**Phase**: ${currentPhase}`);
881
+ lines.push(`**Verdict**: ${verdict}`);
882
+ lines.push(`**Command**: ${status.metadata.command || outputData.command || "unknown"}`);
883
+ lines.push(`**CWD**: ${status.metadata.cwd || outputData.cwd || process.cwd()}`);
884
+ lines.push(`**Exit Code**: ${outputData.exitCode ?? "unknown"}`);
885
+ lines.push(`**Status**: ${outputData.status || status.metadata.status || "unknown"}`);
886
+ lines.push(`**Metric**: ${status.metadata.metricName ? `${status.metadata.metricName}=${outputData.metricValue ?? "not found"}` : "not configured"}`);
887
+ lines.push(`**Objective**: ${status.metadata.objective || outputData.objective || "unknown"}`);
888
+ lines.push(`**Duration**: ${outputData.durationMs ?? status.metadata.durationMs ?? 0}ms`);
889
+ lines.push(`**Reason**: ${verdictReason}`);
890
+ lines.push(`**Outcome**: ${outcome}`);
891
+ if (comparison) {
892
+ lines.push("");
893
+ lines.push("## Comparison");
894
+ lines.push("");
895
+ lines.push(`- Baseline: ${comparison.baselineName} (${comparison.baselineSessionId})`);
896
+ lines.push(`- Objective: ${comparison.objective}`);
897
+ lines.push(`- Result: ${comparison.result}`);
898
+ lines.push(`- Summary: ${comparison.summary}`);
899
+ }
900
+ if (phaseHistory.length > 0) {
901
+ lines.push("");
902
+ lines.push("## Phase History");
903
+ lines.push("");
904
+ lines.push(`- ${phaseHistory.join(" \u2192 ")}`);
905
+ }
906
+ return {
907
+ sessionId: resolvedSessionId,
908
+ name: status.metadata.name || resolvedSessionId,
909
+ createdAt,
910
+ currentPhase,
911
+ verdict,
912
+ verdictReason,
913
+ command: status.metadata.command || outputData.command || "unknown",
914
+ cwd: status.metadata.cwd || outputData.cwd || process.cwd(),
915
+ status: String(outputData.status || status.metadata.status || "unknown"),
916
+ exitCode: Number(outputData.exitCode ?? 0),
917
+ metricName: status.metadata.metricName,
918
+ metricValue: typeof outputData.metricValue === "number" ? outputData.metricValue : void 0,
919
+ objective: status.metadata.objective,
920
+ baselineSessionId: status.metadata.baselineSessionId,
921
+ comparison,
922
+ durationMs: Number(outputData.durationMs ?? status.metadata.durationMs ?? 0),
923
+ phaseHistory,
924
+ outcome,
925
+ content: lines.join("\n")
926
+ };
927
+ }
928
+
929
+ function printDivider() {
930
+ console.log(a.dim("\u2500".repeat(60)));
931
+ }
932
+ function formatMetric(metricName, metricValue) {
933
+ if (!metricName) {
934
+ return "not configured";
935
+ }
936
+ if (metricValue === void 0) {
937
+ return `${metricName} (not found)`;
938
+ }
939
+ return `${metricName}=${metricValue}`;
940
+ }
941
+ function formatTimestamp(value) {
942
+ if (!value) {
943
+ return "unknown";
944
+ }
945
+ const date = new Date(value);
946
+ return Number.isNaN(date.getTime()) ? value : date.toISOString();
947
+ }
948
+ function previewCommand(command, maxLength = 48) {
949
+ if (command.length <= maxLength) {
950
+ return command;
951
+ }
952
+ return `${command.slice(0, maxLength - 1)}\u2026`;
953
+ }
954
+ function getLatestTaskData(status) {
955
+ if (!status || status.tasks.length === 0) {
956
+ return {};
957
+ }
958
+ const latestTask = status.tasks[status.tasks.length - 1];
959
+ return latestTask?.output?.data || {};
960
+ }
961
+ async function researchCommand(action, args = [], options = {}) {
962
+ switch (action) {
963
+ case "run":
964
+ await runResearchCommand(options);
965
+ return;
966
+ case "status":
967
+ await showResearchStatus(args[0] || void 0, options);
968
+ return;
969
+ case "sessions":
970
+ case "list":
971
+ await listResearchCommand(options);
972
+ return;
973
+ case "results":
974
+ await showResearchResults(options);
975
+ return;
976
+ case "report":
977
+ await showResearchReport(args[0] || void 0, options);
978
+ return;
979
+ case "help":
980
+ case "":
981
+ showResearchHelp();
982
+ return;
983
+ default:
984
+ console.error(a.red(`Unknown research action: ${action}`));
985
+ showResearchHelp();
986
+ }
987
+ }
988
+ async function runResearchCommand(options) {
989
+ if (!options.cmd) {
990
+ console.error(a.red("Error: --cmd is required for `ccjk research run`"));
991
+ console.log(a.dim('Example: ccjk research run --name baseline --cmd "python train.py" --metric val_bpb'));
992
+ return;
993
+ }
994
+ const result = await runResearchExperiment({
995
+ name: options.name,
996
+ command: options.cmd,
997
+ metricName: options.metric,
998
+ budgetMs: options.budgetMs,
999
+ cwd: options.cwd,
1000
+ baselineSessionId: options.baseline,
1001
+ objective: options.objective,
1002
+ dbPath: options.dbPath
1003
+ });
1004
+ console.log("");
1005
+ console.log(a.bold.cyan("\u{1F52C} Research Run"));
1006
+ printDivider();
1007
+ console.log(a.gray(`Session: ${result.sessionId}`));
1008
+ console.log(a.gray(`Task: ${result.taskId}`));
1009
+ console.log(a.gray(`Name: ${result.name}`));
1010
+ console.log(a.gray(`Phase: ${result.phase}`));
1011
+ console.log(a.gray(`Objective: ${result.objective}`));
1012
+ console.log(a.gray(`Verdict: ${result.verdict}`));
1013
+ console.log(a.gray(`Reason: ${result.verdictReason}`));
1014
+ console.log(a.gray(`Command: ${result.command}`));
1015
+ console.log(a.gray(`CWD: ${result.cwd}`));
1016
+ console.log(a.gray(`Status: ${result.status}`));
1017
+ console.log(a.gray(`Exit code: ${result.exitCode}`));
1018
+ console.log(a.gray(`Metric: ${formatMetric(result.metricName, result.metricValue)}`));
1019
+ if (result.comparison) {
1020
+ console.log(a.gray(`Baseline: ${result.comparison.baselineName} (${result.comparison.baselineSessionId})`));
1021
+ console.log(a.gray(`Compare: ${result.comparison.result}`));
1022
+ }
1023
+ console.log(a.gray(`Duration: ${result.durationMs}ms`));
1024
+ if (result.stderr) {
1025
+ console.log("");
1026
+ console.log(a.bold("stderr"));
1027
+ console.log(a.dim(result.stderr));
1028
+ }
1029
+ if (result.stdout) {
1030
+ console.log("");
1031
+ console.log(a.bold("stdout"));
1032
+ console.log(a.dim(result.stdout));
1033
+ }
1034
+ console.log("");
1035
+ }
1036
+ async function showResearchStatus(sessionId, options) {
1037
+ const resolvedSessionId = sessionId || getLatestResearchSession(options.dbPath)?.id;
1038
+ if (!resolvedSessionId) {
1039
+ console.log(a.yellow("No research sessions found."));
1040
+ console.log("");
1041
+ return;
1042
+ }
1043
+ const status = getResearchSessionStatus(resolvedSessionId, options.dbPath);
1044
+ if (!status) {
1045
+ console.log(a.yellow(`Research session not found: ${resolvedSessionId}`));
1046
+ console.log("");
1047
+ return;
1048
+ }
1049
+ const outputData = getLatestTaskData(status);
1050
+ const metricValue = typeof outputData.metricValue === "number" ? outputData.metricValue : typeof status.metadata.metricValue === "number" ? status.metadata.metricValue : void 0;
1051
+ console.log("");
1052
+ console.log(a.bold.cyan("\u{1F9EA} Research Status"));
1053
+ printDivider();
1054
+ console.log(a.gray(`Session: ${status.sessionId}`));
1055
+ console.log(a.gray(`Name: ${status.metadata.name || status.sessionId}`));
1056
+ console.log(a.gray(`Phase: ${status.metadata.currentPhase || "unknown"}`));
1057
+ console.log(a.gray(`Objective: ${status.metadata.objective || "unknown"}`));
1058
+ console.log(a.gray(`Verdict: ${status.metadata.verdict || "unknown"}`));
1059
+ console.log(a.gray(`Reason: ${status.metadata.verdictReason || "unknown"}`));
1060
+ console.log(a.gray(`Command: ${status.metadata.command || "unknown"}`));
1061
+ console.log(a.gray(`Metric: ${formatMetric(status.metadata.metricName, metricValue)}`));
1062
+ console.log(a.gray(`Status: ${outputData.status || status.metadata.status || "unknown"}`));
1063
+ console.log(a.gray(`Exit code: ${outputData.exitCode ?? status.metadata.exitCode ?? "unknown"}`));
1064
+ console.log(a.gray(`Duration: ${outputData.durationMs ?? status.metadata.durationMs ?? "unknown"}ms`));
1065
+ console.log(a.gray(`Tasks: ${status.metrics.completedTasks}/${status.metrics.totalTasks} completed`));
1066
+ console.log(a.gray(`Success: ${Math.round((status.metrics.successRate || 0) * 100)}%`));
1067
+ console.log(a.gray(`Avg ms: ${status.metrics.avgExecutionTime}`));
1068
+ console.log(a.gray(`Next: ${status.recovery.nextExecutable.length}`));
1069
+ if (status.metadata.comparison) {
1070
+ const comparison = status.metadata.comparison;
1071
+ console.log(a.gray(`Baseline: ${comparison.baselineName || comparison.baselineSessionId || "unknown"}`));
1072
+ console.log(a.gray(`Compare: ${comparison.result || "unknown"}`));
1073
+ console.log(a.gray(`Summary: ${comparison.summary || "unknown"}`));
1074
+ }
1075
+ if (status.decisions.length > 0) {
1076
+ const lastDecision = status.decisions[status.decisions.length - 1];
1077
+ console.log(a.gray(`Decision: ${lastDecision.outcome || lastDecision.decision}`));
1078
+ }
1079
+ console.log("");
1080
+ }
1081
+ async function listResearchCommand(options) {
1082
+ const sessions = listResearchSessions(options.limit || 10, options.dbPath);
1083
+ console.log("");
1084
+ console.log(a.bold.cyan("\u{1F4DA} Research Sessions"));
1085
+ printDivider();
1086
+ if (sessions.length === 0) {
1087
+ console.log(a.yellow("No research sessions found."));
1088
+ console.log("");
1089
+ return;
1090
+ }
1091
+ for (const session of sessions) {
1092
+ console.log(a.bold(session.name));
1093
+ console.log(a.gray(` ${session.id}`));
1094
+ console.log(a.gray(` phase: ${session.currentPhase}`));
1095
+ console.log(a.gray(` objective: ${session.objective || "unknown"}`));
1096
+ console.log(a.gray(` verdict: ${session.verdict || "unknown"}`));
1097
+ if (session.baselineSessionId) {
1098
+ console.log(a.gray(` baseline: ${session.baselineSessionId}`));
1099
+ }
1100
+ console.log(a.gray(` cmd: ${session.command}`));
1101
+ console.log(a.gray(` metric: ${session.metricName || "not configured"}`));
1102
+ console.log(a.gray(` cwd: ${session.cwd}`));
1103
+ }
1104
+ console.log("");
1105
+ }
1106
+ async function showResearchResults(options) {
1107
+ const rows = listResearchResults(options.limit || 10, options.dbPath);
1108
+ const best = getBestResearchResult(options.dbPath);
1109
+ console.log("");
1110
+ console.log(a.bold.cyan("\u{1F4C8} Research Results"));
1111
+ printDivider();
1112
+ if (rows.length === 0) {
1113
+ console.log(a.yellow("No research results found."));
1114
+ console.log("");
1115
+ return;
1116
+ }
1117
+ if (best) {
1118
+ console.log(a.bold("Best"));
1119
+ console.log(a.gray(` ${best.name} \xB7 ${formatMetric(best.metricName, best.metricValue)} \xB7 ${best.status} \xB7 ${best.durationMs}ms`));
1120
+ console.log(a.gray(` objective: ${best.metricName ? ["loss", "error", "bpb", "perplexity", "ppl", "latency", "duration", "time", "cost", "price", "wer", "cer"].some((keyword) => best.metricName?.toLowerCase().includes(keyword)) ? "minimize" : "maximize" : "unknown"}`));
1121
+ console.log(a.gray(` ${previewCommand(best.command)}`));
1122
+ console.log("");
1123
+ }
1124
+ console.log(a.bold("Recent"));
1125
+ for (const row of rows) {
1126
+ 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)}`));
1127
+ }
1128
+ console.log("");
1129
+ }
1130
+ async function showResearchReport(sessionId, options) {
1131
+ const report = getResearchReport(sessionId, options.dbPath);
1132
+ if (!report) {
1133
+ console.log(a.yellow(sessionId ? `Research session not found: ${sessionId}` : "No research sessions found."));
1134
+ console.log("");
1135
+ return;
1136
+ }
1137
+ console.log("");
1138
+ console.log(a.bold.cyan("\u{1F4DD} Research Report"));
1139
+ printDivider();
1140
+ console.log(a.gray(`Session: ${report.sessionId}`));
1141
+ console.log(a.gray(`Name: ${report.name}`));
1142
+ console.log(a.gray(`Created: ${formatTimestamp(report.createdAt)}`));
1143
+ console.log(a.gray(`Command: ${report.command}`));
1144
+ console.log(a.gray(`CWD: ${report.cwd}`));
1145
+ console.log(a.gray(`Status: ${report.status}`));
1146
+ console.log(a.gray(`Exit code: ${report.exitCode}`));
1147
+ console.log(a.gray(`Duration: ${report.durationMs}ms`));
1148
+ console.log(a.gray(`Metric: ${formatMetric(report.metricName, report.metricValue)}`));
1149
+ console.log(a.gray(`Objective: ${report.objective || "unknown"}`));
1150
+ console.log(a.gray(`Reason: ${report.verdictReason}`));
1151
+ if (report.comparison) {
1152
+ console.log(a.gray(`Baseline: ${report.comparison.baselineName} (${report.comparison.baselineSessionId})`));
1153
+ console.log(a.gray(`Compare: ${report.comparison.result}`));
1154
+ }
1155
+ console.log(a.gray(`Outcome: ${report.outcome}`));
1156
+ if (report.phaseHistory.length > 0) {
1157
+ console.log(a.gray(`Phases: ${report.phaseHistory.join(" \u2192 ")}`));
1158
+ }
1159
+ console.log("");
1160
+ }
1161
+ function showResearchHelp() {
1162
+ console.log("");
1163
+ console.log(a.bold.cyan("ccjk research"));
1164
+ printDivider();
1165
+ console.log(" run Run a persisted research experiment with optional baseline comparison");
1166
+ console.log(" status Show the latest or selected research session status");
1167
+ console.log(" sessions List recent research sessions");
1168
+ console.log(" results Show recent result rows and the current best run");
1169
+ console.log(" report Render a compact persisted research report");
1170
+ console.log("");
1171
+ console.log(a.dim('Example: ccjk research run --name baseline --cmd "python train.py" --metric val_bpb --objective minimize'));
1172
+ console.log(a.dim('Example: ccjk research run --name candidate --cmd "python train.py --lr 1e-4" --metric val_bpb --baseline research-123'));
1173
+ console.log(a.dim("Example: ccjk research report research-123"));
1174
+ console.log("");
1175
+ }
1176
+
1177
+ export { researchCommand, showResearchHelp };