opencode-autoresearch 3.18.0 → 3.18.2

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/cli.js CHANGED
@@ -1,27 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync, openSync, readFileSync, readSync, readdirSync } from "fs";
3
- import { resolve } from "path";
4
- import { execSync } from "child_process";
5
2
  import { MAX_DRAFTS } from "./constants.js";
6
- import { printJson, printJsonEnvelope, resolveRepo, parseRunState, parsePositiveInt, sanitizeForTerminal, getInstalledPackagePath, getInstalledPackageInfo, readUpdateCache, getGlobalNpmPrefix, readGoalDoc, atomicWriteTextInRepo } from "./helpers.js";
7
- const VERSION_FLAGS = ["--version", "-v"];
8
- const HELP_FLAGS = ["--help", "-h", "help"];
9
- const BRANCH_POLICIES = ["best", "roulette", "diverse"];
10
- const shouldSkipUpdateCheck = (args) => {
11
- if (args.length > 0 && VERSION_FLAGS.includes(args[0])) {
12
- return { skip: true, reason: "version_flag" };
13
- }
14
- if (args.length > 0 && HELP_FLAGS.includes(args[0])) {
15
- return { skip: true, reason: "help_flag" };
16
- }
17
- if (process.env.AUTORESEARCH_NO_UPDATE === "1") {
18
- return { skip: true, reason: "env_opt_out" };
19
- }
20
- if (process.env.CI === "true" || process.env.CI === "1") {
21
- return { skip: true, reason: "ci_environment" };
22
- }
23
- return { skip: false, reason: null };
24
- };
3
+ import { VERSION_FLAGS, HELP_FLAGS, parseArgs } from "./cli-helpers.js";
4
+ import { handleWizard, handleInit, handleStatus, handleExplain, handleHistory, handleScores, handleScore, handleConfig, handleContract, handleSummary, handleValidate, handleReport, handleSuggest, handleExport, handleCompletion, handleLaunch, handleComplete, handleStop, handleResume, handleRecord, handleDigest, handleDoctor, handleGoal, handleQueue, handlePack, handleLeaderboard, handleWorker, } from "./cli-commands.js";
25
5
  const usage = () => {
26
6
  console.error("Usage: autoresearch <command> [options]");
27
7
  console.error("");
@@ -49,6 +29,8 @@ const usage = () => {
49
29
  console.error(" pack Export and inspect strategy packs");
50
30
  console.error(" leaderboard Show local leaderboard across runs");
51
31
  console.error(" doctor Verify package installation and version");
32
+ console.error(" export Export run data in JSON or markdown");
33
+ console.error(" validate Validate run configuration");
52
34
  console.error(" help Show this help");
53
35
  console.error("");
54
36
  console.error("Options:");
@@ -74,7 +56,6 @@ const usage = () => {
74
56
  console.error(" --duration Wall-clock cap (e.g., 5h or 300m)");
75
57
  console.error(` --num-drafts Number of parallel drafts (default: 1, max: ${MAX_DRAFTS})`);
76
58
  console.error(" --branch-policy Branch selection policy: best, roulette, diverse");
77
- console.error(' --branch-policy-overrides JSON object mapping draft IDs to policies (e.g. {"draft-0":"diverse"})');
78
59
  console.error(" --max-debug-depth Max debug experiment depth before stop");
79
60
  console.error(" --branch-failure-budget Per-branch failure budget before stop");
80
61
  console.error(" --json Output raw JSON (default: human-readable)");
@@ -82,7 +63,7 @@ const usage = () => {
82
63
  console.error(" --state-path Custom state JSON path");
83
64
  console.error(" --fresh-start Archive previous artifacts before starting");
84
65
  console.error(" --goal-path Output path for GOAL.md (used by goal init)");
85
- console.error(" --template Goal template: performance, quality, coverage, custom (used by goal init)");
66
+ console.error(" --template Goal template: performance, quality, coverage, custom");
86
67
  console.error("");
87
68
  console.error("Flags:");
88
69
  console.error(" -h, --help Show this help");
@@ -100,248 +81,20 @@ const usage = () => {
100
81
  console.error(" autoresearch history");
101
82
  console.error(" autoresearch score --scorer \"node score.js\"");
102
83
  };
103
- const parseArgs = (args) => {
104
- const result = {};
105
- for (let i = 0; i < args.length; i++) {
106
- if (args[i].startsWith("--")) {
107
- const longArg = args[i];
108
- const equalsIndex = longArg.indexOf("=");
109
- if (equalsIndex > 2) {
110
- result[longArg.slice(2, equalsIndex)] = longArg.slice(equalsIndex + 1);
111
- continue;
112
- }
113
- const key = longArg.slice(2);
114
- if (i + 1 < args.length && !args[i + 1].startsWith("--") && !args[i + 1].startsWith("-")) {
115
- result[key] = args[++i];
116
- }
117
- else {
118
- result[key] = "true";
119
- }
120
- }
121
- else if (args[i].startsWith("-") && args[i].length === 2 && args[i] !== "--") {
122
- const shortToLong = {
123
- r: "repo", g: "goal", m: "metric", d: "direction",
124
- v: "verify", n: "guard", o: "mode", s: "scope",
125
- i: "iterations", t: "duration",
126
- f: "num-drafts", b: "branch-policy",
127
- p: "max-no-progress",
128
- };
129
- const key = shortToLong[args[i][1]] ?? args[i].slice(1);
130
- if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
131
- result[key] = args[++i];
132
- }
133
- else {
134
- result[key] = "true";
135
- }
136
- }
137
- }
138
- return result;
139
- };
140
- const tsvField = (headers, cols, field, legacyIndex) => {
141
- const fieldIndex = headers.indexOf(field);
142
- if (fieldIndex >= 0)
143
- return cols[fieldIndex] ?? "";
144
- return cols[legacyIndex] ?? "";
145
- };
146
- const markdownInlineEscapes = {
147
- "\\": "\\\\",
148
- "`": "\\`",
149
- "*": "\\*",
150
- "_": "\\_",
151
- "{": "\\{",
152
- "}": "\\}",
153
- "[": "\\[",
154
- "]": "\\]",
155
- "(": "\\(",
156
- ")": "\\)",
157
- "#": "\\#",
158
- "+": "\\+",
159
- "-": "\\-",
160
- ".": "\\.",
161
- "!": "\\!",
162
- "|": "\\|",
163
- };
164
- const markdownHtmlEscapes = {
165
- "&": "&amp;",
166
- "<": "&lt;",
167
- ">": "&gt;",
168
- '"': "&quot;",
169
- };
170
- const escapeMarkdownInline = (value) => {
171
- return sanitizeForTerminal(value ?? "")
172
- .replace(/[&<>"]/g, (char) => markdownHtmlEscapes[char])
173
- .replace(/[\r\n\t]+/g, " ")
174
- .replace(/\s{2,}/g, " ")
175
- .trim()
176
- .replace(/[\\`*_{}\[\]()#+\-.!|]/g, (char) => markdownInlineEscapes[char]);
177
- };
178
- const escapeMarkdownTableCell = (value) => {
179
- const escaped = escapeMarkdownInline(value);
180
- return escaped.length > 0 ? escaped : "—";
181
- };
182
- const formatDisplayValue = (val) => {
183
- if (val === undefined || val === null)
184
- return "—";
185
- return sanitizeForTerminal(val);
186
- };
187
- const parseMemoryPatternHeading = (heading) => {
188
- const raw = heading.replace(/^### Pattern: /, "");
189
- const trimmed = raw.trimEnd();
190
- if (trimmed.startsWith('"')) {
191
- try {
192
- const parsed = JSON.parse(trimmed);
193
- if (typeof parsed === "string")
194
- return parsed;
195
- }
196
- catch {
197
- // Fall through to the raw heading text for backward compatibility.
198
- }
199
- }
200
- return trimmed;
201
- };
202
- const formatMetricValue = formatDisplayValue;
203
- const formatTimestamp = (ts) => {
204
- try {
205
- const d = new Date(ts);
206
- return d.toLocaleString();
207
- }
208
- catch {
209
- return ts;
210
- }
211
- };
212
- const MAX_SCORE_HISTORY_BYTES = 10 * 1024 * 1024;
213
- const assertRegularBoundedFile = (filePath) => {
214
- const linkStats = lstatSync(filePath);
215
- if (linkStats.isSymbolicLink()) {
216
- throw new Error(`Refusing to read score history symlink: ${filePath}`);
217
- }
218
- if (!linkStats.isFile()) {
219
- throw new Error(`Refusing to read non-regular score history file: ${filePath}`);
220
- }
221
- if (linkStats.size > MAX_SCORE_HISTORY_BYTES) {
222
- throw new Error(`Score history is too large to read safely (${linkStats.size} bytes; max ${MAX_SCORE_HISTORY_BYTES} bytes): ${filePath}`);
223
- }
224
- };
225
- const readScoreHistoryFile = (filePath) => {
226
- assertRegularBoundedFile(filePath);
227
- if (typeof fsConstants.O_NOFOLLOW !== "number") {
228
- throw new Error(`Refusing to read score history because this platform does not support O_NOFOLLOW: ${filePath}`);
229
- }
230
- const fd = openSync(filePath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
231
- try {
232
- const fileStats = fstatSync(fd);
233
- if (!fileStats.isFile()) {
234
- throw new Error(`Refusing to read non-regular score history file: ${filePath}`);
235
- }
236
- if (fileStats.size > MAX_SCORE_HISTORY_BYTES) {
237
- throw new Error(`Score history is too large to read safely (${fileStats.size} bytes; max ${MAX_SCORE_HISTORY_BYTES} bytes): ${filePath}`);
238
- }
239
- return readFileSync(fd, "utf-8");
240
- }
241
- finally {
242
- closeSync(fd);
243
- }
244
- };
245
- const readTailLines = (filePath, limit) => {
246
- if (limit <= 0)
247
- return [];
248
- const fd = openSync(filePath, "r");
249
- try {
250
- const size = fstatSync(fd).size;
251
- if (size === 0)
252
- return [];
253
- const chunkSize = 64 * 1024;
254
- const lines = [];
255
- let position = size;
256
- let remainder = Buffer.alloc(0);
257
- while (position > 0 && lines.length < limit) {
258
- const bytesToRead = Math.min(chunkSize, position);
259
- position -= bytesToRead;
260
- const chunk = Buffer.alloc(bytesToRead);
261
- const bytesRead = readSync(fd, chunk, 0, bytesToRead, position);
262
- const data = Buffer.concat([chunk.subarray(0, bytesRead), remainder]);
263
- let end = data.length;
264
- for (let i = data.length - 1; i >= 0 && lines.length < limit; i -= 1) {
265
- if (data[i] === 0x0a) {
266
- const line = data.subarray(i + 1, end).toString("utf-8").trim();
267
- if (line.length > 0)
268
- lines.push(line);
269
- end = i;
270
- }
271
- }
272
- remainder = data.subarray(0, end);
273
- }
274
- if (lines.length < limit) {
275
- const line = remainder.toString("utf-8").trim();
276
- if (line.length > 0)
277
- lines.push(line);
278
- }
279
- return lines.reverse();
280
- }
281
- finally {
282
- closeSync(fd);
283
- }
284
- };
285
- const markdownEscapePattern = /([\\`*_{}[\]()#+\-.!|>])/g;
286
- const terminalControlPattern = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
287
- const controlCharacterPattern = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g;
288
- const sanitizeMarkdownText = (value) => {
289
- if (value === undefined || value === null)
290
- return "—";
291
- return String(value)
292
- .replace(terminalControlPattern, "")
293
- .replace(controlCharacterPattern, "")
294
- .replace(/\r?\n|\r/g, " ")
295
- .replace(/\t/g, " ");
296
- };
297
- const formatMarkdownField = (value) => {
298
- return sanitizeMarkdownText(value).replace(markdownEscapePattern, "\\$1");
299
- };
300
- const normalizeBranchPolicy = (value) => {
301
- if (value == null || value === "")
302
- return "best";
303
- if (BRANCH_POLICIES.includes(value))
304
- return value;
305
- throw new Error(`Invalid branch policy: ${value}. Expected one of: ${BRANCH_POLICIES.join(", ")}`);
306
- };
307
- const PROTO_POISON_KEYS = new Set(["__proto__", "constructor", "prototype"]);
308
- const normalizeOverrideBranchPolicy = (branchId, value) => {
309
- const trimmed = value.trim();
310
- if (trimmed === "") {
311
- throw new Error(`Invalid branch policy override for ${branchId}: value must not be empty`);
312
- }
313
- if (BRANCH_POLICIES.includes(trimmed))
314
- return trimmed;
315
- throw new Error(`Invalid branch policy override for ${branchId}: "${trimmed}" is not one of: ${BRANCH_POLICIES.join(", ")}`);
316
- };
317
- const parseBranchPolicyOverrides = (value) => {
318
- if (value == null || value === "")
319
- return undefined;
320
- let parsed;
321
- try {
322
- parsed = JSON.parse(value);
323
- }
324
- catch {
325
- throw new Error("Invalid branch policy overrides: expected a JSON object mapping draft IDs to branch policies");
326
- }
327
- if (parsed == null || Array.isArray(parsed) || typeof parsed !== "object") {
328
- throw new Error("Invalid branch policy overrides: expected a JSON object mapping draft IDs to branch policies");
329
- }
330
- const overrides = Object.create(null);
331
- for (const [branchId, branchPolicy] of Object.entries(parsed)) {
332
- if (PROTO_POISON_KEYS.has(branchId)) {
333
- throw new Error(`Invalid branch policy override key: "${branchId}" is not a valid draft ID`);
84
+ const groupArgs = (pargs) => {
85
+ const grouped = {};
86
+ for (const [k, v] of Object.entries(pargs)) {
87
+ if (k === "required-keep-labels" || k === "required-stop-labels" || k === "labels") {
88
+ grouped[k] = v.split(/\s+/).filter(Boolean);
334
89
  }
335
- if (typeof branchPolicy !== "string") {
336
- throw new Error(`Invalid branch policy override for ${branchId}: expected a string policy`);
90
+ else {
91
+ grouped[k] = v;
337
92
  }
338
- overrides[branchId] = normalizeOverrideBranchPolicy(branchId, branchPolicy);
339
93
  }
340
- return overrides;
94
+ return grouped;
341
95
  };
342
96
  const main = async () => {
343
97
  const args = process.argv.slice(2);
344
- // Handle standalone flags
345
98
  if (args.length === 0) {
346
99
  usage();
347
100
  return 0;
@@ -362,1657 +115,40 @@ const main = async () => {
362
115
  const useJson = pargs.json === "true";
363
116
  const verbose = pargs.verbose === "true";
364
117
  const dryRun = pargs["dry-run"] === "true";
365
- const grouped = {};
366
- for (const [k, v] of Object.entries(pargs)) {
367
- if (k === "required-keep-labels" || k === "required-stop-labels" || k === "labels") {
368
- grouped[k] = v.split(/\s+/).filter(Boolean);
369
- }
370
- else {
371
- grouped[k] = v;
372
- }
373
- }
118
+ const grouped = groupArgs(pargs);
374
119
  try {
375
120
  switch (cmd) {
376
- case "wizard": {
377
- const { buildSetupSummary } = await import("./wizard.js");
378
- const config = {
379
- goal: grouped.goal,
380
- scope: grouped.scope,
381
- metric: grouped.metric,
382
- direction: grouped.direction,
383
- verify: grouped.verify,
384
- guard: grouped.guard,
385
- mode: grouped.mode,
386
- iterations: parsePositiveInt(grouped.iterations, "iterations"),
387
- max_no_progress: parsePositiveInt(grouped["max-no-progress"], "max-no-progress"),
388
- duration: grouped.duration,
389
- memory_path: grouped["memory-path"],
390
- required_keep_labels: grouped["required-keep-labels"],
391
- required_stop_labels: grouped["required-stop-labels"],
392
- stop_condition: grouped["stop-condition"],
393
- rollback_strategy: grouped["rollback-strategy"],
394
- };
395
- printJsonEnvelope("wizard", buildSetupSummary(grouped.repo, config));
396
- break;
397
- }
398
- case "init": {
399
- if (verbose)
400
- console.error(`[verbose] Initializing run with goal: ${formatDisplayValue(grouped.goal)}`);
401
- if (dryRun) {
402
- console.log("[dry-run] Would initialize run with config:");
403
- console.log(JSON.stringify({
404
- goal: grouped.goal,
405
- metric: grouped.metric,
406
- direction: grouped.direction || "lower",
407
- mode: grouped.mode || "foreground",
408
- }, null, 2));
409
- return 0;
410
- }
411
- const { initializeRun } = await import("./run-manager.js");
412
- const config = {
413
- goal: grouped.goal,
414
- metric: (grouped.metric || grouped["outcome-metric"]),
415
- direction: (grouped.direction || grouped["outcome-direction"]) || "lower",
416
- verify: grouped.verify,
417
- mode: grouped.mode || "foreground",
418
- scope: grouped.scope,
419
- guard: grouped.guard,
420
- scorer: grouped.scorer,
421
- iterations: parsePositiveInt(grouped.iterations, "iterations"),
422
- max_no_progress: parsePositiveInt(grouped["max-no-progress"], "max-no-progress"),
423
- duration: grouped.duration,
424
- memory_path: grouped["memory-path"],
425
- required_keep_labels: grouped["required-keep-labels"],
426
- required_stop_labels: grouped["required-stop-labels"],
427
- run_tag: grouped["run-tag"],
428
- stop_condition: grouped["stop-condition"],
429
- baseline: grouped.baseline,
430
- num_drafts: parsePositiveInt(grouped["num-drafts"], "num_drafts", { max: MAX_DRAFTS }) ?? 1,
431
- branch_selection_policy: normalizeBranchPolicy(grouped["branch-policy"]),
432
- branch_policy_overrides: parseBranchPolicyOverrides(grouped["branch-policy-overrides"]),
433
- outcome_metric: grouped["outcome-metric"],
434
- outcome_direction: grouped["outcome-direction"],
435
- instrument_metric: grouped["instrument-metric"],
436
- instrument_direction: grouped["instrument-direction"],
437
- max_debug_depth: parsePositiveInt(grouped["max-debug-depth"], "max_debug_depth"),
438
- branch_failure_budget: parsePositiveInt(grouped["branch-failure-budget"], "branch_failure_budget"),
439
- };
440
- const state = await initializeRun(grouped.repo, grouped["results-path"], grouped["state-path"], config, grouped["fresh-start"] === "true");
441
- printJson(state);
442
- break;
443
- }
444
- case "status": {
445
- const { buildSupervisorSnapshot } = await import("./run-manager.js");
446
- const snapshot = await buildSupervisorSnapshot(grouped.repo, grouped["results-path"], grouped["state-path"]);
447
- if (useJson) {
448
- printJsonEnvelope("status", snapshot);
449
- }
450
- else {
451
- const s = snapshot;
452
- const stats = s.stats;
453
- console.log(`Run: ${formatDisplayValue(s.run_id)}`);
454
- console.log(`Status: ${formatDisplayValue(s.status)}`);
455
- console.log(`Mode: ${formatDisplayValue(s.mode)}`);
456
- console.log(`Op Mode: ${formatDisplayValue(s.operating_mode)}`);
457
- console.log(`Goal: ${formatDisplayValue(s.goal)}`);
458
- if (s.metric) {
459
- const m = s.metric;
460
- console.log(`Metric: ${formatDisplayValue(m.name)} (${formatDisplayValue(m.direction)})`);
461
- console.log(` best: ${formatMetricValue(m.best)}`);
462
- console.log(` latest: ${formatMetricValue(m.latest)}`);
463
- }
464
- if (stats) {
465
- console.log(`Stats: ${stats.total_iterations} iterations, ${stats.kept} kept, ${stats.discarded} discarded`);
466
- }
467
- console.log(`Results: ${s.results_rows} rows`);
468
- const lastIter = s.last_iteration;
469
- if (lastIter && lastIter.iteration) {
470
- const stageTag = lastIter.stage ? ` [${formatDisplayValue(lastIter.stage)}]` : "";
471
- console.log(`Last: iter ${formatDisplayValue(lastIter.iteration)}${stageTag} — ${formatDisplayValue(lastIter.decision)} (${formatMetricValue(lastIter.metric_value)})`);
472
- if (lastIter.score_components != null && typeof lastIter.score_components === "object") {
473
- const parts = Object.entries(lastIter.score_components)
474
- .map(([k, v]) => `${formatDisplayValue(k)}:${typeof v === "number" ? v.toFixed(4) : formatDisplayValue(v)}`)
475
- .join(", ");
476
- if (parts.length > 0)
477
- console.log(` Components: [${parts}]`);
478
- }
479
- if (lastIter.selected_action)
480
- console.log(` Action: ${formatDisplayValue(lastIter.selected_action)}`);
481
- }
482
- const flags = s.flags;
483
- if (flags?.needs_human)
484
- console.log("⚠ Needs human input");
485
- if (flags?.stop_requested)
486
- console.log("⏹ Stop requested");
487
- if (flags?.context_pressure) {
488
- const cp = flags.context_pressure;
489
- const warnings = Array.isArray(cp.warnings) ? cp.warnings : [];
490
- if (warnings.length > 0) {
491
- for (const w of warnings)
492
- console.log(`🆘 Context pressure: ${formatDisplayValue(w)}`);
493
- }
494
- }
495
- }
496
- break;
497
- }
498
- case "explain": {
499
- const { buildSupervisorSnapshot } = await import("./run-manager.js");
500
- const snapshot = await buildSupervisorSnapshot(grouped.repo, grouped["results-path"], grouped["state-path"]);
501
- const s = snapshot;
502
- const stats = s.stats;
503
- const lastIter = s.last_iteration;
504
- const flags = s.flags;
505
- if (useJson) {
506
- printJsonEnvelope("explain", snapshot);
507
- break;
508
- }
509
- const statusEmoji = {
510
- running: "🔄", completed: "✅", initialized: "📋", stopping: "⏹", stopped: "⏸",
511
- };
512
- const statusKey = typeof s.status === "string" ? s.status : "";
513
- const candidateEmoji = Object.prototype.hasOwnProperty.call(statusEmoji, statusKey)
514
- ? statusEmoji[statusKey]
515
- : undefined;
516
- const emoji = typeof candidateEmoji === "string" ? candidateEmoji : "⚪";
517
- console.log(`${emoji} Auto Research Run: ${formatDisplayValue(s.run_id)}`);
518
- console.log(` Goal: ${formatDisplayValue(s.goal)}`);
519
- console.log(` Status: ${formatDisplayValue(s.status)}`);
520
- console.log(` Mode: ${formatDisplayValue(s.mode)}`);
521
- console.log(` Op Mode: ${formatDisplayValue(s.operating_mode)}`);
522
- if (s.metric) {
523
- const m = s.metric;
524
- console.log(` Metric: ${formatDisplayValue(m.name)} → ${formatMetricValue(m.latest)} (best: ${formatMetricValue(m.best)}, dir: ${formatDisplayValue(m.direction)})`);
525
- }
526
- if (stats) {
527
- console.log(` Progress: ${stats.total_iterations} iterations | ${stats.kept} kept | ${stats.discarded} discarded`);
528
- }
529
- if (lastIter && lastIter.iteration) {
530
- console.log(` Last iter: #${formatDisplayValue(lastIter.iteration)} — ${formatDisplayValue(lastIter.decision)}`);
531
- if (lastIter.change_summary)
532
- console.log(` Change: ${formatDisplayValue(lastIter.change_summary)}`);
533
- if (lastIter.score_components != null && typeof lastIter.score_components === "object") {
534
- const parts = Object.entries(lastIter.score_components)
535
- .map(([k, v]) => `${formatDisplayValue(k)}:${typeof v === "number" ? v.toFixed(4) : formatDisplayValue(v)}`)
536
- .join(", ");
537
- if (parts.length > 0)
538
- console.log(` Components: [${parts}]`);
539
- }
540
- }
541
- if (flags?.needs_human)
542
- console.log(" ⚠ Needs human review");
543
- if (flags?.stop_requested)
544
- console.log(" ⏹ Stop was requested");
545
- if (flags?.background_active)
546
- console.log(" 📡 Background active — `autoresearch status` to check");
547
- break;
548
- }
549
- case "history": {
550
- const { resolvePath } = await import("./helpers.js");
551
- const { RESULTS_DEFAULT } = await import("./constants.js");
552
- const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
553
- if (!existsSync(resultsPath)) {
554
- console.log("No results file found.");
555
- break;
556
- }
557
- const content = readFileSync(resultsPath, "utf-8");
558
- const lines = content.trim().split("\n");
559
- if (lines.length <= 1) {
560
- console.log("No iteration records yet.");
561
- break;
562
- }
563
- const limit = parsePositiveInt(grouped.limit, "limit") ?? 10;
564
- const headers = lines[0].split("\t");
565
- const records = lines.slice(1).reverse().slice(0, limit);
566
- if (useJson) {
567
- const parsed = records.map((r) => {
568
- const cols = r.split("\t");
569
- const obj = {};
570
- for (let i = 0; i < headers.length; i++) {
571
- obj[headers[i]] = cols[i] ?? "";
572
- }
573
- return obj;
574
- });
575
- printJsonEnvelope("history", { count: records.length, records: parsed });
576
- break;
577
- }
578
- for (const r of records) {
579
- const cols = r.split("\t");
580
- if (cols.length >= 4) {
581
- const decision = tsvField(headers, cols, "decision", 2);
582
- const metricValue = tsvField(headers, cols, "metric_value", 3);
583
- const emoji = decision === "keep" ? "✓" : decision === "discard" ? "✗" : "⚠";
584
- const changeSummary = tsvField(headers, cols, "change_summary", 8);
585
- console.log(`${emoji} #${formatDisplayValue(cols[1])} ${formatDisplayValue(decision)} (${formatMetricValue(metricValue)}) ${formatDisplayValue(changeSummary.substring(0, 60))}`);
586
- }
587
- }
588
- console.log(`\nShowing ${Math.min(limit, records.length)} of ${lines.length - 1} records.`);
589
- break;
590
- }
591
- case "scores": {
592
- const { resolvePath } = await import("./helpers.js");
593
- const { SCORE_HISTORY_DEFAULT } = await import("./constants.js");
594
- const scoreHistoryPath = resolvePath(grouped.repo, grouped["score-history-path"], SCORE_HISTORY_DEFAULT);
595
- if (!existsSync(scoreHistoryPath)) {
596
- console.log("No score history found.");
597
- break;
598
- }
599
- const limit = parsePositiveInt(grouped.limit, "limit") ?? 10;
600
- const showTopComponents = grouped["top-components"] === "true";
601
- if (showTopComponents) {
602
- const allLines = readScoreHistoryFile(scoreHistoryPath)
603
- .split("\n")
604
- .map((l) => l.trim())
605
- .filter(Boolean);
606
- const allParsed = allLines.map((r) => {
607
- try {
608
- return JSON.parse(r);
609
- }
610
- catch {
611
- return null;
612
- }
613
- }).filter(Boolean);
614
- if (allParsed.length === 0) {
615
- console.log("No score records yet.");
616
- break;
617
- }
618
- const { rankComponents } = await import("./score-parser.js");
619
- const ranking = rankComponents(allParsed);
620
- if (useJson) {
621
- printJsonEnvelope("scores", { count: allParsed.length, scores: allParsed.slice(-limit), ranking });
622
- break;
623
- }
624
- console.log("Component Rankings:");
625
- if (ranking.top_positive.length > 0) {
626
- console.log(" Top improving components:");
627
- for (const c of ranking.top_positive) {
628
- console.log(` + ${formatDisplayValue(c.name)} Δ+${c.delta.toFixed(4)}`);
629
- }
630
- }
631
- if (ranking.top_negative.length > 0) {
632
- console.log(" Top declining components:");
633
- for (const c of ranking.top_negative) {
634
- console.log(` - ${formatDisplayValue(c.name)} Δ${c.delta.toFixed(4)}`);
635
- }
636
- }
637
- if (ranking.top_positive.length === 0 && ranking.top_negative.length === 0) {
638
- console.log(" No component data found in score history.");
639
- }
640
- console.log(`\nAnalyzed ${allParsed.length} score records.`);
641
- break;
642
- }
643
- const records = readTailLines(scoreHistoryPath, limit);
644
- if (records.length === 0) {
645
- console.log("No score records yet.");
646
- break;
647
- }
648
- if (useJson) {
649
- const parsed = records.map((r) => {
650
- try {
651
- return JSON.parse(r);
652
- }
653
- catch {
654
- return null;
655
- }
656
- }).filter(Boolean);
657
- printJsonEnvelope("scores", { count: parsed.length, scores: parsed });
658
- break;
659
- }
660
- console.log("Score History (latest " + Math.min(limit, records.length) + "):");
661
- const recordsOrdered = records.slice().reverse();
662
- const parseMetricNumber = (value) => {
663
- if (typeof value === "number") {
664
- return Number.isFinite(value) ? value : null;
665
- }
666
- if (typeof value === "string") {
667
- const parsed = Number(value);
668
- return Number.isFinite(parsed) ? parsed : null;
669
- }
670
- return null;
671
- };
672
- for (let i = 0; i < recordsOrdered.length; i += 1) {
673
- const r = recordsOrdered[i];
674
- try {
675
- const rec = JSON.parse(r);
676
- let trend = "";
677
- if (i + 1 < recordsOrdered.length) {
678
- try {
679
- const previousRec = JSON.parse(recordsOrdered[i + 1]);
680
- const currentMetricValue = parseMetricNumber(rec.metric_value);
681
- const previousMetricValue = parseMetricNumber(previousRec.metric_value);
682
- if (currentMetricValue !== null && previousMetricValue !== null) {
683
- if (currentMetricValue === previousMetricValue) {
684
- trend = "→";
685
- }
686
- else if (rec.metric_direction === "higher") {
687
- trend = currentMetricValue > previousMetricValue ? "↑" : "↓";
688
- }
689
- else {
690
- trend = currentMetricValue < previousMetricValue ? "↑" : "↓";
691
- }
692
- }
693
- }
694
- catch {
695
- trend = "";
696
- }
697
- }
698
- let componentLine = "";
699
- if (rec.score_components != null && typeof rec.score_components === "object") {
700
- const parts = Object.entries(rec.score_components)
701
- .map(([k, v]) => `${formatDisplayValue(k)}:${typeof v === "number" ? v.toFixed(4) : formatDisplayValue(v)}`)
702
- .join(", ");
703
- if (parts.length > 0)
704
- componentLine = ` [${parts}]`;
705
- }
706
- let componentDeltaLine = "";
707
- if (componentLine && i + 1 < recordsOrdered.length) {
708
- try {
709
- const prevRec = JSON.parse(recordsOrdered[i + 1]);
710
- if (prevRec.score_components != null && typeof prevRec.score_components === "object") {
711
- const deltas = [];
712
- for (const [k, v] of Object.entries(rec.score_components)) {
713
- const prev = prevRec.score_components[k];
714
- if (typeof prev === "number" && typeof v === "number") {
715
- const d = v - prev;
716
- if (d !== 0) {
717
- deltas.push(`${formatDisplayValue(k)}:${d > 0 ? "+" : ""}${d.toFixed(4)}`);
718
- }
719
- }
720
- }
721
- if (deltas.length > 0)
722
- componentDeltaLine = ` Δ[${deltas.join(", ")}]`;
723
- }
724
- }
725
- catch {
726
- // ignore delta parse errors
727
- }
728
- }
729
- console.log(` #${rec.iteration} ${trend} ${rec.metric_value ?? "—"} (${rec.decision}) ${rec.verify_status}${componentLine}${componentDeltaLine}`);
730
- }
731
- catch {
732
- console.log(` [parse error]`);
733
- }
734
- }
735
- console.log(`\nShowing ${records.length} score records.`);
736
- break;
737
- }
738
- case "score": {
739
- const { AutoresearchError: AErr } = await import("./helpers.js");
740
- const { parseScoreOutput } = await import("./score-parser.js");
741
- const scorerCmd = grouped.scorer;
742
- if (!scorerCmd) {
743
- throw new AErr("No scorer provided. Pass --scorer <cmd> to run a scorer explicitly.");
744
- }
745
- const repoBase = resolveRepo(grouped.repo);
746
- let rawOutput;
747
- try {
748
- rawOutput = execSync(scorerCmd, { encoding: "utf-8", cwd: repoBase, stdio: ["ignore", "pipe", "pipe"] });
749
- }
750
- catch (err) {
751
- const e = err;
752
- const stderr = typeof e.stderr === "string" ? e.stderr.trim() : (Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf-8").trim() : "");
753
- const errMsg = stderr || (err instanceof Error ? err.message : String(err));
754
- throw new AErr(`Scorer command failed: ${errMsg}`);
755
- }
756
- const scored = parseScoreOutput(rawOutput);
757
- const normalized = scored.score / scored.max;
758
- const percent = (normalized * 100).toFixed(1) + "%";
759
- if (useJson) {
760
- printJsonEnvelope("score", {
761
- score: scored.score,
762
- max: scored.max,
763
- normalized,
764
- percent,
765
- components: scored.components ?? null,
766
- diagnostics: scored.diagnostics ?? null,
767
- details: scored.details ?? null,
768
- });
769
- break;
770
- }
771
- console.log(`Score: ${scored.score} / ${scored.max} (${percent})`);
772
- if (scored.components && Object.keys(scored.components).length > 0) {
773
- console.log("Components:");
774
- for (const [key, val] of Object.entries(scored.components)) {
775
- console.log(` ${formatDisplayValue(key)}: ${formatDisplayValue(val)}`);
776
- }
777
- }
778
- if (scored.diagnostics && Object.keys(scored.diagnostics).length > 0) {
779
- console.log("Diagnostics:");
780
- for (const [key, val] of Object.entries(scored.diagnostics)) {
781
- console.log(` ${formatDisplayValue(key)}: ${formatDisplayValue(val)}`);
782
- }
783
- }
784
- break;
785
- }
786
- case "config": {
787
- const { resolvePath, readJsonFile } = await import("./helpers.js");
788
- const { STATE_DEFAULT } = await import("./constants.js");
789
- const statePath = resolvePath(grouped.repo, grouped["state-path"], STATE_DEFAULT);
790
- if (!existsSync(statePath)) {
791
- console.log("No run state found. Run 'autoresearch init' first.");
792
- break;
793
- }
794
- const state = parseRunState(readJsonFile(statePath));
795
- if (useJson) {
796
- printJsonEnvelope("config", {
797
- goal: state.goal,
798
- mode: state.mode,
799
- metric: state.metric,
800
- scope: state.scope,
801
- iterations_cap: state.iterations_cap,
802
- deadline_at: state.deadline_at,
803
- verify: state.verify,
804
- guard: state.guard,
805
- scorer: state.scorer ?? null,
806
- subagent_pool: state.subagent_pool ? "configured" : "none",
807
- label_requirements: state.label_requirements,
808
- });
809
- break;
810
- }
811
- console.log("Run Configuration:");
812
- console.log(` Goal: ${formatDisplayValue(state.goal)}`);
813
- console.log(` Mode: ${formatDisplayValue(state.mode)}`);
814
- console.log(` Op Mode: ${formatDisplayValue(state.operating_mode)}`);
815
- if (state.metric) {
816
- const m = state.metric;
817
- console.log(` Metric: ${formatDisplayValue(m.name)} (${formatDisplayValue(m.direction)})`);
818
- }
819
- console.log(` Scope: ${formatDisplayValue(state.scope)}`);
820
- console.log(` Iter cap: ${formatDisplayValue(state.iterations_cap)}`);
821
- console.log(` Deadline: ${formatDisplayValue(state.deadline_at ? formatTimestamp(state.deadline_at) : "—")}`);
822
- console.log(` Verify: ${formatDisplayValue(state.verify)}`);
823
- console.log(` Guard: ${formatDisplayValue(state.guard)}`);
824
- console.log(` Scorer: ${formatDisplayValue(state.scorer ?? "—")}`);
825
- console.log(` Pool: ${state.subagent_pool ? "configured" : "none"}`);
826
- break;
827
- }
828
- case "contract": {
829
- const schemas = {
830
- schema_version: "1.0.0",
831
- description: "Auto Research runtime contract schemas",
832
- state: {
833
- type: "object",
834
- required: ["schema_version", "run_id", "created_at", "updated_at", "status", "mode", "operating_mode", "goal", "scope", "metric", "verify", "label_requirements", "artifact_paths", "stats", "flags"],
835
- properties: {
836
- schema_version: { type: "number", description: "State schema version" },
837
- run_id: { type: "string", description: "Unique run identifier" },
838
- created_at: { type: "string", format: "date-time", description: "Run creation timestamp" },
839
- updated_at: { type: "string", format: "date-time", description: "Last update timestamp" },
840
- status: { type: "string", enum: ["initialized", "running", "stopping", "stopped", "completed", "needs_human"], description: "Run status" },
841
- mode: { type: "string", enum: ["foreground", "background"], description: "Execution mode" },
842
- operating_mode: { type: "string", enum: ["converge", "continuous", "supervised"], description: "Operating mode" },
843
- goal: { type: "string", description: "Run goal description" },
844
- scope: { type: "string", description: "Target scope" },
845
- metric: {
846
- type: "object",
847
- required: ["name", "direction"],
848
- properties: {
849
- name: { type: "string" },
850
- direction: { type: "string", enum: ["higher", "lower"] },
851
- baseline: { type: "string" },
852
- best: { type: "string" },
853
- latest: { type: "string" },
854
- },
855
- },
856
- instrument_metric: { type: "object", description: "Optional secondary metric" },
857
- verify: { type: "string", description: "Verification command" },
858
- guard: { type: "string", description: "Guard command" },
859
- scorer: { type: "string", description: "Scorer command" },
860
- iterations_cap: { type: "number", description: "Maximum iterations" },
861
- duration: { type: "string", description: "Duration limit" },
862
- duration_seconds: { type: "number" },
863
- deadline_at: { type: "string", format: "date-time" },
864
- label_requirements: {
865
- type: "object",
866
- required: ["keep", "stop"],
867
- properties: {
868
- keep: { type: "array", items: { type: "string" } },
869
- stop: { type: "array", items: { type: "string" } },
870
- },
871
- },
872
- artifact_paths: {
873
- type: "object",
874
- required: ["results", "state"],
875
- properties: {
876
- results: { type: "string" },
877
- state: { type: "string" },
878
- },
879
- },
880
- stats: {
881
- type: "object",
882
- required: ["total_iterations", "kept", "discarded", "needs_human"],
883
- properties: {
884
- total_iterations: { type: "number" },
885
- kept: { type: "number" },
886
- discarded: { type: "number" },
887
- needs_human: { type: "number" },
888
- consecutive_discards: { type: "number" },
889
- best_iteration: { type: "number" },
890
- debug_depth: { type: "number" },
891
- },
892
- },
893
- flags: {
894
- type: "object",
895
- required: ["stop_requested", "needs_human", "background_active", "stop_ready"],
896
- properties: {
897
- stop_requested: { type: "boolean" },
898
- needs_human: { type: "boolean" },
899
- background_active: { type: "boolean" },
900
- stop_ready: { type: "boolean" },
901
- },
902
- },
903
- last_iteration: {
904
- type: "object",
905
- properties: {
906
- iteration: { type: "number" },
907
- decision: { type: "string", enum: ["keep", "discard", "needs_human"] },
908
- metric_value: { type: "string" },
909
- change_summary: { type: "string" },
910
- labels: { type: "array", items: { type: "string" } },
911
- timestamp: { type: "string", format: "date-time" },
912
- },
913
- },
914
- draft_pool: { type: "object", description: "Draft pool configuration" },
915
- lineage: { type: "object", description: "Experiment lineage" },
916
- budget_exhausted: { type: "boolean" },
917
- budget_blocker_reason: { type: "string" },
918
- },
919
- },
920
- result_row: {
921
- type: "object",
922
- description: "Single iteration result row in TSV format",
923
- properties: {
924
- iteration: { type: "number" },
925
- decision: { type: "string" },
926
- metric_value: { type: "string" },
927
- verify_status: { type: "string" },
928
- guard_status: { type: "string" },
929
- change_summary: { type: "string" },
930
- labels: { type: "array", items: { type: "string" } },
931
- timestamp: { type: "string" },
932
- note: { type: "string" },
933
- },
934
- },
935
- goal_doc: {
936
- type: "object",
937
- required: ["goal", "metric", "direction", "verify"],
938
- properties: {
939
- goal: { type: "string" },
940
- metric: { type: "string" },
941
- direction: { type: "string", enum: ["higher", "lower"] },
942
- verify: { type: "string" },
943
- guard: { type: "string" },
944
- constraints: { type: "string" },
945
- file_map: { type: "string" },
946
- stop_conditions: { type: "string" },
947
- },
948
- },
949
- };
950
- if (useJson) {
951
- printJsonEnvelope("contract", schemas);
952
- break;
953
- }
954
- console.log("Auto Research Contract Schemas");
955
- console.log("==============================");
956
- console.log("");
957
- console.log("State Schema:");
958
- console.log(` Version: ${schemas.state.properties.schema_version.type}`);
959
- console.log(` Required: ${schemas.state.required.join(", ")}`);
960
- console.log("");
961
- console.log("Result Row Schema:");
962
- console.log(` Properties: ${Object.keys(schemas.result_row.properties).join(", ")}`);
963
- console.log("");
964
- console.log("Goal Doc Schema:");
965
- console.log(` Required: ${schemas.goal_doc.required.join(", ")}`);
966
- console.log("");
967
- console.log("Use --json for full machine-readable schema output.");
968
- break;
969
- }
970
- case "summary": {
971
- const { resolvePath } = await import("./helpers.js");
972
- const { RESULTS_DEFAULT } = await import("./constants.js");
973
- const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
974
- if (!existsSync(resultsPath)) {
975
- console.log("No results file found. No runs completed yet.");
976
- break;
977
- }
978
- const content = readFileSync(resultsPath, "utf-8");
979
- const lines = content.trim().split("\n");
980
- const records = lines.slice(1).filter(Boolean);
981
- let totalKept = 0, totalDiscarded = 0, totalNeedsHuman = 0;
982
- const runIds = new Set();
983
- for (const r of records) {
984
- const cols = r.split("\t");
985
- const dec = cols[2];
986
- if (dec === "keep")
987
- totalKept++;
988
- else if (dec === "discard")
989
- totalDiscarded++;
990
- else if (dec === "needs_human")
991
- totalNeedsHuman++;
992
- const iterTags = cols[1].split(":");
993
- if (iterTags.length >= 2)
994
- runIds.add(iterTags[0]);
995
- }
996
- if (useJson) {
997
- printJsonEnvelope("summary", {
998
- total_records: records.length,
999
- total_kept: totalKept,
1000
- total_discarded: totalDiscarded,
1001
- total_needs_human: totalNeedsHuman,
1002
- keep_rate: records.length > 0 ? (totalKept / records.length * 100).toFixed(1) + "%" : "0%",
1003
- distinct_run_ids: Array.from(runIds),
1004
- });
1005
- break;
1006
- }
1007
- console.log("Auto Research Summary");
1008
- console.log(` Total iterations: ${records.length}`);
1009
- console.log(` Kept: ${totalKept}`);
1010
- console.log(` Discarded: ${totalDiscarded}`);
1011
- console.log(` Needs human: ${totalNeedsHuman}`);
1012
- console.log(` Keep rate: ${records.length > 0 ? (totalKept / records.length * 100).toFixed(1) : 0}%`);
1013
- console.log(` Distinct runs: ${runIds.size}`);
1014
- break;
1015
- }
1016
- case "validate": {
1017
- const { normalizeDirection, normalizeMode } = await import("./helpers.js");
1018
- const errors = [];
1019
- if (!grouped.goal)
1020
- errors.push("Missing required: --goal");
1021
- if (!grouped.metric && !grouped["outcome-metric"])
1022
- errors.push("Missing required: --metric or --outcome-metric");
1023
- try {
1024
- if (grouped.direction)
1025
- normalizeDirection(grouped.direction);
1026
- }
1027
- catch (e) {
1028
- errors.push(`Invalid direction: ${e.message}`);
1029
- }
1030
- try {
1031
- if (grouped.mode)
1032
- normalizeMode(grouped.mode);
1033
- }
1034
- catch (e) {
1035
- errors.push(`Invalid mode: ${e.message}`);
1036
- }
1037
- if (!grouped.verify)
1038
- errors.push("Missing required: --verify");
1039
- if (useJson) {
1040
- printJsonEnvelope("validate", { valid: errors.length === 0, errors });
1041
- return errors.length > 0 ? 1 : 0;
1042
- }
1043
- if (errors.length === 0) {
1044
- console.log("✓ Configuration is valid");
1045
- console.log(` Goal: ${grouped.goal}`);
1046
- console.log(` Metric: ${grouped.metric || grouped["outcome-metric"]} (${grouped.direction || grouped["outcome-direction"] || "lower"})`);
1047
- console.log(` Verify: ${grouped.verify}`);
1048
- console.log(` Mode: ${grouped.mode || "foreground"}`);
1049
- }
1050
- else {
1051
- console.error("✗ Configuration errors:");
1052
- for (const err of errors) {
1053
- console.error(` - ${formatDisplayValue(err)}`);
1054
- }
1055
- return 1;
1056
- }
1057
- break;
1058
- }
1059
- case "report": {
1060
- const { resolvePath, readJsonFile } = await import("./helpers.js");
1061
- const { STATE_DEFAULT, RESULTS_DEFAULT } = await import("./constants.js");
1062
- const statePath = resolvePath(grouped.repo, grouped["state-path"], STATE_DEFAULT);
1063
- const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
1064
- if (!existsSync(statePath)) {
1065
- console.log("No run state found. Run 'autoresearch init' first.");
1066
- break;
1067
- }
1068
- const state = parseRunState(readJsonFile(statePath));
1069
- let results = [];
1070
- let resultHeaders = [];
1071
- if (existsSync(resultsPath)) {
1072
- const content = readFileSync(resultsPath, "utf-8");
1073
- const resultLines = content.trim().split("\n");
1074
- resultHeaders = resultLines[0]?.split("\t") ?? [];
1075
- results = resultLines.slice(1).filter(Boolean);
1076
- }
1077
- if (useJson) {
1078
- printJsonEnvelope("report", { state, results_count: results.length });
1079
- break;
1080
- }
1081
- console.log(`# Auto Research Report`);
1082
- console.log(`\n**Run:** ${formatMarkdownField(state.run_id)}`);
1083
- console.log(`**Goal:** ${formatMarkdownField(state.goal)}`);
1084
- console.log(`**Status:** ${formatMarkdownField(state.status)}`);
1085
- console.log(`**Mode:** ${formatMarkdownField(state.mode)}`);
1086
- console.log(`**Op Mode:** ${formatMarkdownField(state.operating_mode)}`);
1087
- if (state.metric) {
1088
- const m = state.metric;
1089
- console.log(`**Metric:** ${formatMarkdownField(m.name)} (${formatMarkdownField(m.direction)})`);
1090
- console.log(`**Best:** ${formatMarkdownField(m.best)} | **Latest:** ${formatMarkdownField(m.latest)}`);
1091
- }
1092
- if (state.stats) {
1093
- const s = state.stats;
1094
- console.log(`\n## Stats`);
1095
- console.log(`- Iterations: ${formatMarkdownField(s.total_iterations)}`);
1096
- console.log(`- Kept: ${formatMarkdownField(s.kept)}`);
1097
- console.log(`- Discarded: ${formatMarkdownField(s.discarded)}`);
1098
- console.log(`- Needs human: ${formatMarkdownField(s.needs_human)}`);
1099
- // Best attempt details
1100
- if (s.best_iteration !== undefined && results.length > 0) {
1101
- const bestIterationResults = results.filter(r => {
1102
- const cols = r.split("\t");
1103
- return cols[1] === String(s.best_iteration);
1104
- });
1105
- if (bestIterationResults.length > 0) {
1106
- const bestCols = bestIterationResults[0].split("\t");
1107
- const bestChangeSummary = tsvField(resultHeaders, bestCols, "change_summary", 7);
1108
- console.log(`- Best attempt: iteration ${formatMarkdownField(String(s.best_iteration))} — ${formatMarkdownField(bestChangeSummary.substring(0, 60))}`);
1109
- }
1110
- }
1111
- }
1112
- // Milestone Progress
1113
- console.log(`\n## Milestone Progress`);
1114
- if (state.stats) {
1115
- const s = state.stats;
1116
- const total = s.total_iterations;
1117
- const successRate = total > 0 ? ((s.kept / total) * 100).toFixed(1) : "0";
1118
- console.log(`- **Progress:** ${formatMarkdownField(s.kept)} kept / ${formatMarkdownField(total)} total iterations (${formatMarkdownField(successRate)}% success rate)`);
1119
- if (state.iterations_cap) {
1120
- const progressPct = ((total / state.iterations_cap) * 100).toFixed(1);
1121
- console.log(`- **Cap:** ${formatMarkdownField(total)} / ${formatMarkdownField(state.iterations_cap)} iterations (${formatMarkdownField(progressPct)}% of cap)`);
1122
- }
1123
- if (state.created_at) {
1124
- const startedAtMs = Date.parse(state.created_at);
1125
- const endedAtMs = state.updated_at ? Date.parse(state.updated_at) : Date.now();
1126
- if (!Number.isNaN(startedAtMs) && !Number.isNaN(endedAtMs) && endedAtMs >= startedAtMs) {
1127
- const elapsedMin = Math.round((endedAtMs - startedAtMs) / 1000 / 60);
1128
- console.log(`- **Elapsed:** ${formatMarkdownField(elapsedMin)} minutes`);
1129
- }
1130
- }
1131
- // Next candidate
1132
- if (state.last_iteration && state.last_iteration.decision === "keep") {
1133
- console.log(`- **Next candidate:** Iteration ${formatMarkdownField(state.last_iteration.iteration)} (kept)`);
1134
- }
1135
- else if (s.best_iteration) {
1136
- console.log(`- **Best candidate:** Iteration ${formatMarkdownField(s.best_iteration)}`);
1137
- }
1138
- }
1139
- // Artifact pointers
1140
- console.log(`\n## Artifacts`);
1141
- console.log(`- State: ${formatMarkdownField(state.artifact_paths?.state || ".autoresearch/state.json")}`);
1142
- console.log(`- Results: ${formatMarkdownField(state.artifact_paths?.results || "autoresearch-results.tsv")}`);
1143
- if (grouped.repo) {
1144
- console.log(`- Repository: ${formatMarkdownField(grouped.repo)}`);
1145
- }
1146
- // Failed branches information
1147
- if (state.draft_pool && state.draft_pool.active_drafts) {
1148
- const failedBranches = state.draft_pool.active_drafts.filter(draft => draft.status === "discarded");
1149
- if (failedBranches.length > 0) {
1150
- console.log(`\n## Failed Branches`);
1151
- for (const branch of failedBranches.slice(0, 5)) { // Limit to 5 branches
1152
- console.log(`- Branch ${formatMarkdownField(branch.branch_id)}: iteration ${formatMarkdownField(String(branch.iteration))} (parent: ${formatMarkdownField(String(branch.parent_iteration))}) — ${formatMarkdownField(branch.metric_value ?? "no metric")}`);
1153
- }
1154
- if (failedBranches.length > 5) {
1155
- console.log(` ... and ${formatMarkdownField(String(failedBranches.length - 5))} more failed branches`);
1156
- }
1157
- }
1158
- }
1159
- // Blockers information
1160
- if (state.flags.needs_human) {
1161
- console.log(`\n## Blockers`);
1162
- console.log(`- Human input required: ${formatMarkdownField(state.last_iteration?.change_summary ?? "awaiting user decision")}`);
1163
- // Add more blocker details if available in note
1164
- if (state.last_iteration?.note) {
1165
- console.log(`- Details: ${formatMarkdownField(state.last_iteration.note.substring(0, 100))}${state.last_iteration.note.length > 100 ? "..." : ""}`);
1166
- }
1167
- }
1168
- // Next actions information
1169
- console.log(`\n## Next Actions`);
1170
- if (state.status === "running") {
1171
- if (state.flags.needs_human) {
1172
- console.log(`- Awaiting human input to continue`);
1173
- }
1174
- else if (state.flags.stop_requested) {
1175
- console.log(`- Stop requested, will complete current iteration then stop`);
1176
- }
1177
- else {
1178
- console.log(`- Continue with next iteration`);
1179
- }
1180
- }
1181
- else if (state.status === "completed") {
1182
- console.log(`- Run completed successfully`);
1183
- }
1184
- else if (state.status === "stopped" || state.status === "stopping") {
1185
- console.log(`- Run stopped; use 'autoresearch resume' to continue`);
1186
- }
1187
- else {
1188
- console.log(`- Initialize a new run with 'autoresearch init'`);
1189
- }
1190
- if (results.length > 0) {
1191
- console.log(`\n## Iterations`);
1192
- for (const r of results) {
1193
- const cols = r.split("\t");
1194
- if (cols.length >= 4) {
1195
- const decision = tsvField(resultHeaders, cols, "decision", 2);
1196
- const metricValue = tsvField(resultHeaders, cols, "metric_value", 3);
1197
- const changeSummary = tsvField(resultHeaders, cols, "change_summary", 8);
1198
- console.log(`- ${formatMarkdownField(cols[1])}: ${formatMarkdownField(decision)} (${formatMarkdownField(metricValue)}) — ${formatMarkdownField(changeSummary).substring(0, 60)}`);
1199
- }
1200
- }
1201
- }
1202
- break;
1203
- }
1204
- case "suggest": {
1205
- const evidenceGated = grouped["evidence"] === "true";
1206
- if (evidenceGated) {
1207
- const { generateIssueCandidate } = await import("./evidence.js");
1208
- const candidate = generateIssueCandidate(grouped.repo, grouped.goal, grouped.metric, grouped.verify, grouped["score-history-path"]);
1209
- if (!candidate) {
1210
- if (useJson) {
1211
- printJsonEnvelope("suggest", { candidates: [], reason: "insufficient_evidence" });
1212
- }
1213
- else {
1214
- console.log("No evidence-gated issue candidates found.");
1215
- console.log("Insufficient failure clusters or score history not available.");
1216
- }
1217
- break;
1218
- }
1219
- if (useJson) {
1220
- printJsonEnvelope("suggest", { candidates: [candidate], evidence_gated: true });
1221
- }
1222
- else {
1223
- console.log(`Evidence-Gated Issue Candidate:`);
1224
- console.log(` Title: ${candidate.title}`);
1225
- console.log(` Goal: ${candidate.goal}`);
1226
- console.log(` Metric: ${candidate.metric}`);
1227
- console.log(` Evidence: ${candidate.evidence.total_discards} discards in ${candidate.evidence.total_runs} cluster(s)`);
1228
- console.log(``);
1229
- console.log(` Suggested command:`);
1230
- console.log(` ${candidate.suggest_command}`);
1231
- console.log(``);
1232
- console.log(`Review before opening. This candidate is NOT auto-submitted.`);
1233
- }
1234
- break;
1235
- }
1236
- const { resolvePath } = await import("./helpers.js");
1237
- const { MEMORY_DEFAULT } = await import("./constants.js");
1238
- const memoryPath = resolvePath(grouped.repo, grouped["memory-path"], MEMORY_DEFAULT);
1239
- if (!existsSync(memoryPath)) {
1240
- console.log("No memory file found. Run a self-improvement cycle first.");
1241
- break;
1242
- }
1243
- const memory = readFileSync(memoryPath, "utf-8");
1244
- const patterns = memory.match(/^### Pattern: [^\n]+/gm) ?? [];
1245
- const suggestions = patterns.map(parseMemoryPatternHeading);
1246
- if (useJson) {
1247
- printJsonEnvelope("suggest", { patterns_found: suggestions.length, suggestions });
1248
- break;
1249
- }
1250
- console.log("Memory Patterns — candidate next goals:");
1251
- for (const suggestion of suggestions) {
1252
- console.log(` → ${formatDisplayValue(suggestion)}`);
1253
- }
1254
- console.log(`\n${suggestions.length} patterns available. Use 'autoresearch init --goal "..."' to start a new run.`);
1255
- break;
1256
- }
1257
- case "export": {
1258
- const { resolvePath } = await import("./helpers.js");
1259
- const { RESULTS_DEFAULT, STATE_DEFAULT } = await import("./constants.js");
1260
- const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
1261
- const statePath = resolvePath(grouped.repo, grouped["state-path"], STATE_DEFAULT);
1262
- const format = grouped.format || "json";
1263
- if (!existsSync(resultsPath) || !existsSync(statePath)) {
1264
- console.error("No run data found. Run 'autoresearch init' first.");
1265
- return 1;
1266
- }
1267
- const results = readFileSync(resultsPath, "utf-8");
1268
- const state = readFileSync(statePath, "utf-8");
1269
- const lines = results.trim().split("\n");
1270
- const headers = lines[0].split("\t");
1271
- const records = lines.slice(1).filter(Boolean).map((r) => {
1272
- const cols = r.split("\t");
1273
- const obj = {};
1274
- for (let i = 0; i < headers.length; i++) {
1275
- obj[headers[i]] = cols[i] ?? "";
1276
- }
1277
- return obj;
1278
- });
1279
- const exportData = {
1280
- exported_at: new Date().toISOString(),
1281
- state: JSON.parse(state),
1282
- iterations: records,
1283
- summary: {
1284
- total: records.length,
1285
- kept: records.filter((r) => r.decision === "keep").length,
1286
- discarded: records.filter((r) => r.decision === "discard").length,
1287
- },
1288
- };
1289
- if (format === "json") {
1290
- printJsonEnvelope("export", exportData);
1291
- }
1292
- else if (format === "md" || format === "markdown") {
1293
- console.log(`# Auto Research Export`);
1294
- console.log(`\n**Run:** ${escapeMarkdownInline(exportData.state.run_id) || "—"}`);
1295
- console.log(`**Goal:** ${escapeMarkdownInline(exportData.state.goal) || "—"}`);
1296
- console.log(`**Exported:** ${escapeMarkdownInline(exportData.exported_at)}`);
1297
- console.log(`\n## Summary`);
1298
- console.log(`- Total iterations: ${exportData.summary.total}`);
1299
- console.log(`- Kept: ${exportData.summary.kept}`);
1300
- console.log(`- Discarded: ${exportData.summary.discarded}`);
1301
- console.log(`\n## Iterations`);
1302
- console.log(`| # | Decision | Metric | Summary |`);
1303
- console.log(`|---|----------|--------|---------|`);
1304
- for (const r of records) {
1305
- console.log(`| ${escapeMarkdownTableCell(r.iteration)} | ${escapeMarkdownTableCell(r.decision)} | ${escapeMarkdownTableCell(r.metric_value)} | ${escapeMarkdownTableCell(r.change_summary?.substring(0, 50))} |`);
1306
- }
1307
- }
1308
- else {
1309
- console.error(`Unknown format: ${format}. Supported: json, md`);
1310
- return 1;
1311
- }
1312
- break;
1313
- }
1314
- case "completion": {
1315
- const shell = grouped.shell || "bash";
1316
- const commands = ["init", "goal", "wizard", "status", "explain", "history", "config", "summary", "suggest", "launch", "complete", "stop", "resume", "record", "doctor", "pack", "export", "completion", "help"];
1317
- const options = ["--repo", "--goal", "--metric", "--direction", "--verify", "--guard", "--mode", "--scope", "--iterations", "--duration", "--num-drafts", "--branch-policy", "--branch-policy-overrides", "--json", "--results-path", "--state-path", "--fresh-start", "--memory-path", "--format", "--shell", "--goal-path", "--template"];
1318
- if (shell === "bash" || shell === "zsh") {
1319
- console.log(`# Auto Research CLI completion for ${shell}`);
1320
- console.log(`_autoresearch() {`);
1321
- console.log(` local cur="\${COMP_WORDS[COMP_CWORD]}"`);
1322
- console.log(` local cmds="${commands.join(" ")}"`);
1323
- console.log(` local opts="${options.join(" ")}"`);
1324
- console.log(` if [ $COMP_CWORD -eq 1 ]; then`);
1325
- console.log(` COMPREPLY=($(compgen -W "$cmds" -- "$cur"))`);
1326
- console.log(` else`);
1327
- console.log(` COMPREPLY=($(compgen -W "$opts" -- "$cur"))`);
1328
- console.log(` fi`);
1329
- console.log(`}`);
1330
- console.log(`complete -F _autoresearch autoresearch`);
1331
- }
1332
- else if (shell === "fish") {
1333
- console.log(`# Auto Research CLI completion for fish`);
1334
- for (const cmd of commands) {
1335
- console.log(`complete -c autoresearch -n '__fish_use_subcommand' -a '${cmd}'`);
1336
- }
1337
- for (const opt of options) {
1338
- console.log(`complete -c autoresearch -n '__fish_seen_subcommand_from ${commands.join(" ")}' -l ${opt.slice(2)}`);
1339
- }
1340
- }
1341
- else {
1342
- console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
1343
- return 1;
1344
- }
1345
- break;
1346
- }
1347
- case "launch": {
1348
- const { resolvePath } = await import("./helpers.js");
1349
- const { LAUNCH_DEFAULT } = await import("./constants.js");
1350
- const config = {
1351
- goal: grouped.goal,
1352
- metric: (grouped.metric || grouped["outcome-metric"]),
1353
- direction: (grouped.direction || grouped["outcome-direction"]) || "lower",
1354
- verify: grouped.verify,
1355
- mode: "background",
1356
- scope: grouped.scope,
1357
- guard: grouped.guard,
1358
- scorer: grouped.scorer,
1359
- iterations: parsePositiveInt(grouped.iterations, "iterations"),
1360
- max_no_progress: parsePositiveInt(grouped["max-no-progress"], "max-no-progress"),
1361
- max_debug_depth: parsePositiveInt(grouped["max-debug-depth"], "max_debug_depth"),
1362
- branch_failure_budget: parsePositiveInt(grouped["branch-failure-budget"], "branch_failure_budget"),
1363
- duration: grouped.duration,
1364
- memory_path: grouped["memory-path"],
1365
- required_keep_labels: grouped["required-keep-labels"],
1366
- required_stop_labels: grouped["required-stop-labels"],
1367
- run_tag: grouped["run-tag"],
1368
- stop_condition: grouped["stop-condition"],
1369
- baseline: grouped.baseline,
1370
- num_drafts: parsePositiveInt(grouped["num-drafts"], "num_drafts", { max: MAX_DRAFTS }) ?? 1,
1371
- branch_selection_policy: normalizeBranchPolicy(grouped["branch-policy"]),
1372
- branch_policy_overrides: parseBranchPolicyOverrides(grouped["branch-policy-overrides"]),
1373
- outcome_metric: grouped["outcome-metric"],
1374
- outcome_direction: grouped["outcome-direction"],
1375
- instrument_metric: grouped["instrument-metric"],
1376
- instrument_direction: grouped["instrument-direction"],
1377
- };
1378
- const launchPath = resolvePath(grouped.repo, grouped["launch-path"], LAUNCH_DEFAULT);
1379
- if (dryRun) {
1380
- console.log("[dry-run] Would launch background run with config:");
1381
- console.log(JSON.stringify({ ...config, launch_path: launchPath }, null, 2));
1382
- return 0;
1383
- }
1384
- const { initializeRun } = await import("./run-manager.js");
1385
- const { writeFileSync } = await import("fs");
1386
- const state = await initializeRun(grouped.repo, grouped["results-path"], grouped["state-path"], config, grouped["fresh-start"] === "true");
1387
- writeFileSync(launchPath, JSON.stringify({ run_id: state.run_id, goal: state.goal, mode: "background" }, null, 2) + "\n", "utf-8");
1388
- printJsonEnvelope("launch", { status: "launched", run_id: state.run_id, launch_path: launchPath });
1389
- break;
1390
- }
1391
- case "complete": {
1392
- if (dryRun) {
1393
- console.log("[dry-run] Would mark run complete");
1394
- return 0;
1395
- }
1396
- const { completeRun } = await import("./run-manager.js");
1397
- const state = await completeRun(grouped.repo, grouped["state-path"]);
1398
- printJsonEnvelope("complete", { status: "completed", run_id: state.run_id });
1399
- break;
1400
- }
1401
- case "stop": {
1402
- if (dryRun) {
1403
- console.log("[dry-run] Would request background run stop");
1404
- return 0;
1405
- }
1406
- const { setStopRequested } = await import("./run-manager.js");
1407
- const state = await setStopRequested(grouped.repo, grouped["state-path"]);
1408
- printJsonEnvelope("stop", { status: "stop_requested", run_id: state.run_id });
1409
- break;
1410
- }
1411
- case "resume": {
1412
- if (dryRun) {
1413
- console.log("[dry-run] Would resume background run");
1414
- return 0;
1415
- }
1416
- const { resumeBackgroundRun } = await import("./run-manager.js");
1417
- const state = await resumeBackgroundRun(grouped.repo, grouped["state-path"]);
1418
- printJsonEnvelope("resume", { status: "resumed", run_id: state.run_id });
1419
- break;
1420
- }
1421
- case "record": {
1422
- const { normalizeResultStatus, normalizeScorerStatus } = await import("./helpers.js");
1423
- const vs = grouped["verify-status"] || "pass";
1424
- const gs = grouped["guard-status"] || "skip";
1425
- const scorerStatus = normalizeScorerStatus(grouped["scorer-status"]);
1426
- const iteration = parsePositiveInt(grouped.iteration, "iteration");
1427
- let scoreComponents;
1428
- if (grouped["score-components"]) {
1429
- try {
1430
- const parsed = JSON.parse(grouped["score-components"]);
1431
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1432
- throw new Error('score-components must be a JSON object with string keys and numeric values, e.g., {"accuracy": 0.8, "coverage": 0.6}');
1433
- }
1434
- scoreComponents = parsed;
1435
- }
1436
- catch (e) {
1437
- console.error(`Invalid --score-components: ${e.message}`);
1438
- return 1;
1439
- }
1440
- }
1441
- if (dryRun) {
1442
- console.log("[dry-run] Would record experiment result:");
1443
- console.log(JSON.stringify({
1444
- decision: grouped.decision,
1445
- metric_value: grouped["metric-value"],
1446
- instrument_value: grouped["instrument-value"],
1447
- scorer_status: scorerStatus,
1448
- verify_status: normalizeResultStatus(vs, "verify_status"),
1449
- guard_status: normalizeResultStatus(gs, "guard_status"),
1450
- hypothesis: grouped.hypothesis,
1451
- change_summary: grouped["change-summary"],
1452
- labels: grouped.labels ? (Array.isArray(grouped.labels) ? grouped.labels : [grouped.labels]) : undefined,
1453
- note: grouped.note,
1454
- iteration,
1455
- score_components: scoreComponents,
1456
- stage: grouped.stage || "improve",
1457
- selected_action: grouped["selected-action"],
1458
- }, null, 2));
1459
- return 0;
1460
- }
1461
- const { appendIteration } = await import("./run-manager.js");
1462
- const lineage = {};
1463
- const stage = grouped.stage;
1464
- if (stage)
1465
- lineage.stage = stage;
1466
- const selectedAction = grouped["selected-action"];
1467
- if (selectedAction)
1468
- lineage.selected_action = selectedAction;
1469
- const state = await appendIteration(grouped.repo, grouped["results-path"], grouped["state-path"], grouped.decision, grouped["metric-value"], grouped["instrument-value"], normalizeResultStatus(vs, "verify_status"), normalizeResultStatus(gs, "guard_status"), grouped.hypothesis, grouped["change-summary"], grouped.labels ? (Array.isArray(grouped.labels) ? grouped.labels : [grouped.labels]) : undefined, grouped.note, iteration, undefined, scorerStatus, scoreComponents, Object.keys(lineage).length > 0 ? lineage : undefined);
1470
- printJsonEnvelope("record", state);
1471
- break;
1472
- }
1473
- case "digest": {
1474
- if (dryRun) {
1475
- console.log("[dry-run] Would generate run digest");
1476
- return 0;
1477
- }
1478
- const { buildRunDigest } = await import("./run-manager.js");
1479
- const digest = await buildRunDigest(grouped.repo, grouped["results-path"], grouped["state-path"]);
1480
- if (useJson) {
1481
- printJsonEnvelope("digest", digest);
1482
- }
1483
- else {
1484
- console.log(`# Auto Research Digest`);
1485
- console.log(`\n**Run ID:** ${formatMarkdownField(digest.run_id || "—")}`);
1486
- console.log(`**Status:** ${sanitizeForTerminal(digest.status || "—")}`);
1487
- console.log(`**Mode:** ${formatMarkdownField(digest.mode || "—")}`);
1488
- console.log(`**Goal:** ${formatMarkdownField(digest.goal || "—")}`);
1489
- if (digest.metric) {
1490
- const m = digest.metric;
1491
- console.log(`**Metric:** ${sanitizeForTerminal(m.name)} (${sanitizeForTerminal(m.direction)})`);
1492
- console.log(` Best: ${formatMarkdownField(m.best || "—")} | Latest: ${formatMarkdownField(m.latest || "—")}`);
1493
- }
1494
- if (digest.stats) {
1495
- const s = digest.stats;
1496
- console.log(`\n## Stats`);
1497
- console.log(`- Iterations: ${formatMarkdownField(s.total_iterations || "—")}`);
1498
- console.log(`- Kept: ${formatMarkdownField(s.kept || "—")}`);
1499
- console.log(`- Discarded: ${formatMarkdownField(s.discarded || "—")}`);
1500
- console.log(`- Needs human: ${formatMarkdownField(s.needs_human || "—")}`);
1501
- }
1502
- if (digest.last_iteration) {
1503
- const li = digest.last_iteration;
1504
- console.log(`\n## Last Iteration`);
1505
- console.log(`- #${formatMarkdownField(li.iteration || "—")}: ${formatMarkdownField(li.decision || "—")} (${formatMarkdownField(li.metric_value || "—")})`);
1506
- if (li.change_summary) {
1507
- console.log(`- Change: ${formatMarkdownField(li.change_summary.substring(0, 100))}${li.change_summary.length > 100 ? "..." : ""}`);
1508
- }
1509
- }
1510
- console.log(`\n## Next Action`);
1511
- console.log(`${sanitizeForTerminal(digest.next_action || "No specific next action recommended")}`);
1512
- if (digest.blockers && digest.blockers.length > 0) {
1513
- console.log(`\n## Blockers`);
1514
- for (const blocker of digest.blockers) {
1515
- console.log(`- ${formatMarkdownField(blocker)}`);
1516
- }
1517
- }
1518
- else {
1519
- console.log(`\n## Blockers`);
1520
- console.log(`None identified`);
1521
- }
1522
- if (digest.flags && Object.keys(digest.flags).length > 0) {
1523
- console.log(`\n## Flags`);
1524
- for (const [key, value] of Object.entries(digest.flags)) {
1525
- console.log(`- ${formatMarkdownField(key)}: ${formatMarkdownField(value)}`);
1526
- }
1527
- }
1528
- }
1529
- break;
1530
- }
1531
- case "doctor": {
1532
- const { VERSION, PACKAGE_NAME, SKILL_NAME } = await import("./constants.js");
1533
- const base = resolveRepo(grouped.repo);
1534
- const checks = [];
1535
- const cmdDir = resolve(base, "commands");
1536
- const skillsDir = resolve(base, "skills/autoresearch");
1537
- const hooksDir = resolve(base, "hooks");
1538
- const cmdFiles = existsSync(cmdDir) ? readdirSync(cmdDir).filter((f) => f.endsWith(".md")) : [];
1539
- const skillFiles = existsSync(skillsDir) ? readdirSync(skillsDir) : [];
1540
- const hookFiles = existsSync(hooksDir) ? readdirSync(hooksDir).filter((f) => f.endsWith(".sh")) : [];
1541
- checks.push({ name: "commands", ok: cmdFiles.length > 0, detail: `${cmdFiles.length} command files` });
1542
- checks.push({ name: "skills", ok: skillFiles.length > 0, detail: `${skillFiles.length} skill files` });
1543
- checks.push({ name: "hooks", ok: hookFiles.length > 0, detail: `${hookFiles.length} hook scripts` });
1544
- checks.push({ name: "dist", ok: existsSync(resolve(base, "dist/cli.js")), detail: "dist/cli.js" });
1545
- checks.push({ name: "plugin", ok: existsSync(resolve(base, ".opencode-plugin/plugin.json")), detail: "plugin manifest" });
1546
- checks.push({ name: "VERSION", ok: existsSync(resolve(base, "VERSION")), detail: "version marker" });
1547
- const globalPrefix = getGlobalNpmPrefix();
1548
- const installedPath = getInstalledPackagePath(PACKAGE_NAME);
1549
- const installedInfo = installedPath ? getInstalledPackageInfo(PACKAGE_NAME) : null;
1550
- const updateCache = readUpdateCache();
1551
- const { skip: updateSkipped, reason: skipReason } = shouldSkipUpdateCheck(process.argv.slice(2));
1552
- const updateStatus = {
1553
- cache_exists: updateCache !== null,
1554
- last_check: updateCache?.last_check || null,
1555
- current_version: updateCache?.current_version || null,
1556
- latest_version: updateCache?.latest_version || null,
1557
- update_available: updateCache?.update_available || false,
1558
- update_disabled: process.env.AUTORESEARCH_NO_UPDATE === "1",
1559
- skipped: updateSkipped,
1560
- skip_reason: skipReason,
1561
- };
1562
- if (useJson) {
1563
- const { getWhatsNew } = await import("./whats-new.js");
1564
- const wn = getWhatsNew(base);
1565
- printJsonEnvelope("doctor", {
1566
- version: VERSION,
1567
- skill_name: SKILL_NAME,
1568
- runtime: `Node.js ${process.version}`,
1569
- source: {
1570
- package_name: PACKAGE_NAME,
1571
- global_path: installedPath || null,
1572
- global_prefix: globalPrefix || null,
1573
- installed_version: installedInfo?.version || null,
1574
- installed_description: installedInfo?.description || null,
1575
- installed_repository: installedInfo?.repository || null,
1576
- },
1577
- update: updateStatus,
1578
- checks: checks,
1579
- checks_passed: checks.filter((c) => !c.ok).length === 0,
1580
- whats_new: wn ? { features: wn.features, fixes: wn.fixes } : null,
1581
- });
1582
- break;
1583
- }
1584
- console.log(`${SKILL_NAME} ${VERSION} (${PACKAGE_NAME})`);
1585
- console.log(`Runtime: Node.js ${process.version}`);
1586
- console.log("");
1587
- console.log("Source:");
1588
- console.log(` Package: ${PACKAGE_NAME}`);
1589
- if (installedPath) {
1590
- console.log(` Global: ${installedPath}`);
1591
- if (globalPrefix)
1592
- console.log(` Prefix: ${globalPrefix}`);
1593
- if (installedInfo?.repository)
1594
- console.log(` Repo: ${installedInfo.repository}`);
1595
- }
1596
- else {
1597
- console.log(" Global: not found via npm -g");
1598
- }
1599
- console.log("");
1600
- console.log("Update:");
1601
- if (updateSkipped) {
1602
- console.log(` Skipped: yes (${skipReason})`);
1603
- }
1604
- else if (updateCache) {
1605
- console.log(` Last check: ${updateCache.last_check}`);
1606
- console.log(` Current: ${updateCache.current_version}`);
1607
- console.log(` Latest: ${updateCache.latest_version}`);
1608
- console.log(` Available: ${updateCache.update_available ? "yes" : "no"}`);
1609
- }
1610
- else {
1611
- console.log(" Cache: no update check recorded");
1612
- }
1613
- console.log(` Disabled: ${process.env.AUTORESEARCH_NO_UPDATE === "1" ? "yes (AUTORESEARCH_NO_UPDATE=1)" : "no"}`);
1614
- console.log("");
1615
- console.log("Installation Checks:");
1616
- let maxNameLen = 0;
1617
- for (const c of checks)
1618
- maxNameLen = Math.max(maxNameLen, c.name.length);
1619
- for (const c of checks) {
1620
- const padded = c.name.padEnd(maxNameLen + 2);
1621
- console.log(` ${c.ok ? "✓" : "✗"} ${padded}${c.detail ?? (c.ok ? "present" : "missing")}`);
1622
- }
1623
- const failed = checks.filter((c) => !c.ok).length;
1624
- if (failed > 0) {
1625
- console.error(`\n${failed} check(s) failed. Reinstall with 'npm install -g opencode-autoresearch'.`);
1626
- return 1;
1627
- }
1628
- console.log(`\nAll ${checks.length} checks passed.`);
1629
- const showWhatsNew = grouped["whats-new"] === "true";
1630
- if (showWhatsNew || useJson) {
1631
- const { getWhatsNew, formatWhatsNew } = await import("./whats-new.js");
1632
- const wn = getWhatsNew(base);
1633
- if (wn) {
1634
- if (useJson) {
1635
- // Already displayed in JSON envelope — add it for next re-entry
1636
- }
1637
- else {
1638
- console.log("");
1639
- console.log(formatWhatsNew(wn));
1640
- }
1641
- }
1642
- }
1643
- break;
1644
- }
1645
- case "goal": {
1646
- const rawSubCmd = cmdArgs[0];
1647
- const subCmd = rawSubCmd && !rawSubCmd.startsWith("-") ? rawSubCmd : undefined;
1648
- if ((!subCmd && cmdArgs.length === 0) || subCmd === "help" || (subCmd && HELP_FLAGS.includes(subCmd))) {
1649
- console.error("Usage: autoresearch goal <subcommand> [options]");
1650
- console.error("");
1651
- console.error("Subcommands:");
1652
- console.error(" init Create a GOAL.md goal definition file");
1653
- console.error("");
1654
- console.error("Options (goal init):");
1655
- console.error(" --goal Goal description");
1656
- console.error(" --metric Metric name to track");
1657
- console.error(" --direction lower or higher (default: lower)");
1658
- console.error(" --verify Mechanical verification command");
1659
- console.error(" --guard Guard command for regression catch");
1660
- console.error(" --mode foreground or background (default: foreground)");
1661
- console.error(" --scope In-scope files or subsystem");
1662
- console.error(" --iterations Iteration cap");
1663
- console.error(" --duration Wall-clock cap (e.g., 5h or 300m)");
1664
- console.error(" --template Preset template: performance, quality, coverage, custom");
1665
- console.error(" --goal-path Output file path (default: .autoresearch/goal.md)");
1666
- console.error(" --dry-run Preview without writing the file");
1667
- console.error(" --json Output result as JSON");
1668
- console.error("");
1669
- console.error("Examples:");
1670
- console.error(" autoresearch goal init --goal \"reduce errors\" --metric failures --direction lower --verify \"npm test\"");
1671
- console.error(" autoresearch goal init --template performance");
1672
- console.error(" autoresearch goal init # interactive wizard");
1673
- return 0;
1674
- }
1675
- if (!subCmd) {
1676
- const { GOAL_DEFAULT } = await import("./constants.js");
1677
- const { resolvePath } = await import("./helpers.js");
1678
- const goalPath = resolvePath(grouped.repo, grouped["goal-path"], GOAL_DEFAULT);
1679
- if (!existsSync(goalPath)) {
1680
- console.log("No goal document found. Run 'autoresearch init' first.");
1681
- break;
1682
- }
1683
- const doc = readGoalDoc(goalPath);
1684
- if (useJson) {
1685
- printJsonEnvelope("goal", doc);
1686
- break;
1687
- }
1688
- console.log(`Goal: ${formatDisplayValue(doc.goal)}`);
1689
- console.log(`Metric: ${formatDisplayValue(doc.metric)} (${formatDisplayValue(doc.direction)})`);
1690
- console.log(`Verify: ${formatDisplayValue(doc.verify)}`);
1691
- if (doc.guard)
1692
- console.log(`Guard: ${formatDisplayValue(doc.guard)}`);
1693
- if (doc.file_map)
1694
- console.log(`File map: ${formatDisplayValue(doc.file_map)}`);
1695
- if (doc.constraints)
1696
- console.log(`Constraints: ${formatDisplayValue(doc.constraints)}`);
1697
- if (doc.stop_conditions)
1698
- console.log(`Stop conditions: ${formatDisplayValue(doc.stop_conditions)}`);
1699
- break;
1700
- }
1701
- if (subCmd !== "init") {
1702
- console.error(`Unknown goal subcommand: ${subCmd}`);
1703
- console.error("Run 'autoresearch goal help' for usage.");
1704
- return 1;
1705
- }
1706
- const goalArgs = cmdArgs.slice(1);
1707
- const goalParsed = parseArgs(goalArgs);
1708
- const goalGrouped = {};
1709
- for (const [k, v] of Object.entries(goalParsed)) {
1710
- goalGrouped[k] = v;
1711
- }
1712
- const useGoalJson = goalGrouped.json === "true";
1713
- const isGoalDryRun = goalGrouped["dry-run"] === "true";
1714
- const { GOAL_TEMPLATES, getGoalTemplate, buildGoalDocument, buildGoalInitResult } = await import("./goal-init.js");
1715
- const { GOAL_DEFAULT } = await import("./constants.js");
1716
- const { resolvePath } = await import("./helpers.js");
1717
- const { existsSync: goalExistsSync } = await import("fs");
1718
- const templateId = goalGrouped.template ?? "custom";
1719
- if (!GOAL_TEMPLATES.find((t) => t.id === templateId)) {
1720
- console.error(`Unknown template: ${templateId}. Valid templates: ${GOAL_TEMPLATES.map((t) => t.id).join(", ")}`);
1721
- return 1;
1722
- }
1723
- const template = getGoalTemplate(templateId);
1724
- const templateDefaults = template?.defaults ?? {};
1725
- let config = {
1726
- goal: goalGrouped.goal ?? templateDefaults.goal,
1727
- metric: goalGrouped.metric ?? templateDefaults.metric,
1728
- direction: goalGrouped.direction ?? templateDefaults.direction,
1729
- verify: goalGrouped.verify ?? templateDefaults.verify,
1730
- guard: goalGrouped.guard ?? templateDefaults.guard,
1731
- mode: goalGrouped.mode ?? templateDefaults.mode,
1732
- scope: goalGrouped.scope ?? templateDefaults.scope,
1733
- iterations: goalGrouped.iterations ? parsePositiveInt(goalGrouped.iterations, "iterations") : templateDefaults.iterations,
1734
- duration: goalGrouped.duration ?? templateDefaults.duration,
1735
- stop_condition: goalGrouped["stop-condition"] ?? templateDefaults.stop_condition,
1736
- rollback_strategy: goalGrouped["rollback-strategy"] ?? templateDefaults.rollback_strategy,
1737
- template: templateId,
1738
- };
1739
- const isTTY = process.stdin.isTTY === true;
1740
- const hasRequiredFlags = Boolean(config.goal && config.metric && config.verify);
1741
- if (!hasRequiredFlags && !isTTY) {
1742
- // Non-interactive stdin: try to read JSON from stdin
1743
- let stdinData = "";
1744
- try {
1745
- stdinData = await new Promise((resolve, reject) => {
1746
- const chunks = [];
1747
- process.stdin.on("data", (chunk) => chunks.push(chunk));
1748
- process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
1749
- process.stdin.on("error", reject);
1750
- // Resolve immediately if stdin is closed / empty
1751
- setTimeout(() => resolve(""), 200);
1752
- });
1753
- stdinData = stdinData.trim();
1754
- }
1755
- catch {
1756
- stdinData = "";
1757
- }
1758
- if (stdinData) {
1759
- try {
1760
- const parsed = JSON.parse(stdinData);
1761
- config = { ...config, ...parsed, template: templateId };
1762
- }
1763
- catch {
1764
- console.error("Failed to parse stdin as JSON. Provide valid JSON or use --goal, --metric, --verify flags.");
1765
- return 1;
1766
- }
1767
- }
1768
- }
1769
- if (!config.goal && isTTY) {
1770
- // Interactive wizard
1771
- const readline = await import("readline");
1772
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
1773
- const ask = (prompt, defaultVal) => new Promise((resolve) => {
1774
- const suffix = defaultVal ? ` [${defaultVal}]` : "";
1775
- rl.question(`${prompt}${suffix}: `, (answer) => {
1776
- resolve(answer.trim() || defaultVal || "");
1777
- });
1778
- });
1779
- process.stderr.write("\nAutoresearch Goal Init — Interactive Wizard\n");
1780
- process.stderr.write("Press Enter to accept default values shown in brackets.\n\n");
1781
- if (!config.goal)
1782
- config.goal = await ask("Goal (what outcome should this run optimize?)", config.goal);
1783
- if (!config.metric)
1784
- config.metric = await ask("Metric name", config.metric ?? "primary_metric");
1785
- if (!config.direction)
1786
- config.direction = await ask("Direction (lower/higher)", config.direction ?? "lower");
1787
- if (!config.verify)
1788
- config.verify = await ask("Verify command", config.verify);
1789
- if (!config.guard) {
1790
- const guard = await ask("Guard command (optional, press Enter to skip)");
1791
- if (guard)
1792
- config.guard = guard;
1793
- }
1794
- if (!config.scope)
1795
- config.scope = await ask("Scope (files or subsystem)", config.scope ?? "current repository");
1796
- if (!config.mode)
1797
- config.mode = await ask("Mode (foreground/background)", config.mode ?? "foreground");
1798
- rl.close();
1799
- }
1800
- const goalPath = resolvePath(goalGrouped.repo, goalGrouped["goal-path"], GOAL_DEFAULT);
1801
- const document = buildGoalDocument(config);
1802
- const result = buildGoalInitResult(goalPath, config, !hasRequiredFlags && isTTY);
1803
- if (isGoalDryRun) {
1804
- if (useGoalJson) {
1805
- printJsonEnvelope("goal", { ...result, dry_run: true });
1806
- }
1807
- else {
1808
- console.log("[dry-run] Would write goal document to: " + goalPath);
1809
- console.log("");
1810
- console.log(document);
1811
- }
1812
- return 0;
1813
- }
1814
- if (goalExistsSync(goalPath) && !goalGrouped["force"]) {
1815
- // Overwrite allowed by default (like init), but warn
1816
- if (verbose)
1817
- console.error(`[verbose] Overwriting existing ${goalPath}`);
1818
- }
1819
- atomicWriteTextInRepo(goalGrouped.repo, goalPath, document);
1820
- if (useGoalJson) {
1821
- printJsonEnvelope("goal", result);
1822
- }
1823
- else {
1824
- console.log(`✓ Goal definition written to ${goalPath}`);
1825
- console.log(` Goal: ${result.goal ?? "(unset)"}`);
1826
- console.log(` Metric: ${result.metric ?? "(unset)"} (${result.direction})`);
1827
- console.log(` Verify: ${result.verify ?? "(unset)"}`);
1828
- console.log(` Mode: ${result.mode}`);
1829
- if (result.template !== "custom")
1830
- console.log(` Template: ${result.template}`);
1831
- console.log("");
1832
- console.log(`Run 'autoresearch init --goal "..." --metric "..." --verify "..."' to start a run.`);
1833
- }
1834
- break;
1835
- }
1836
- case "queue": {
1837
- const subCmd = cmdArgs[0] || "list";
1838
- if (subCmd === "help") {
1839
- console.error("Usage: autoresearch queue <subcommand> [options]");
1840
- console.error("");
1841
- console.error("Subcommands:");
1842
- console.error(" list List tasks in the queue (default)");
1843
- console.error(" enqueue Enqueue a new task");
1844
- console.error(" clean Remove completed and failed tasks");
1845
- break;
1846
- }
1847
- if (subCmd === "enqueue") {
1848
- if (!grouped.goal || !grouped.metric || !grouped.verify) {
1849
- console.error("--goal, --metric, and --verify are required for enqueue");
1850
- return 1;
1851
- }
1852
- const { enqueueTasks } = await import("./task-queue.js");
1853
- const tasks = await enqueueTasks(grouped.repo, [{ goal: grouped.goal, metric: grouped.metric, verify: grouped.verify }]);
1854
- if (useJson) {
1855
- printJson({ enqueued: tasks });
1856
- }
1857
- else {
1858
- for (const t of tasks) {
1859
- console.log(`Enqueued: ${t.id} - ${t.goal}`);
1860
- }
1861
- }
1862
- break;
1863
- }
1864
- if (subCmd === "clean") {
1865
- const { listTasks, writeManifest, resolveQueuePath } = await import("./task-queue.js");
1866
- const queuePath = resolveQueuePath(grouped.repo);
1867
- const manifest = await listTasks(grouped.repo);
1868
- const before = manifest.tasks.length;
1869
- manifest.tasks = manifest.tasks.filter((t) => t.status === "pending" || t.status === "leased");
1870
- manifest.updated_at = new Date().toISOString();
1871
- const removed = before - manifest.tasks.length;
1872
- await writeManifest(queuePath, manifest, grouped.repo);
1873
- if (useJson) {
1874
- printJson({ removed });
1875
- }
1876
- else {
1877
- console.log(`Cleaned ${removed} completed/failed tasks. ${manifest.tasks.length} remain.`);
1878
- }
1879
- break;
1880
- }
1881
- const { listTasks } = await import("./task-queue.js");
1882
- const manifest = await listTasks(grouped.repo);
1883
- if (useJson) {
1884
- printJson(manifest);
1885
- }
1886
- else {
1887
- if (manifest.tasks.length === 0) {
1888
- console.log("No tasks in queue.");
1889
- }
1890
- else {
1891
- console.log(`Task Queue (${manifest.tasks.length} tasks):`);
1892
- for (const task of manifest.tasks) {
1893
- const icon = task.status === "completed" ? "v" : task.status === "failed" ? "x" : task.status === "leased" ? ">" : "*";
1894
- console.log(` ${icon} ${task.id} [${task.status}] ${task.goal}`);
1895
- }
1896
- }
1897
- }
1898
- break;
1899
- }
1900
- case "pack": {
1901
- const subCmd = cmdArgs[0] || "help";
1902
- if (subCmd === "help" || (subCmd !== "export" && subCmd !== "list" && subCmd !== "inspect")) {
1903
- console.error("Usage: autoresearch pack <subcommand> [options]");
1904
- console.error("Subcommands:");
1905
- console.error(" export Export validated run as a strategy pack");
1906
- console.error(" list List available strategy packs");
1907
- console.error(" inspect View a specific strategy pack");
1908
- break;
1909
- }
1910
- if (subCmd === "export") {
1911
- const { exportPack } = await import("./strategy-pack.js");
1912
- const result = exportPack(grouped.repo, grouped["state-path"], grouped["goal-path"]);
1913
- if (!result) {
1914
- console.error("No run state found. Complete a run first.");
1915
- return 1;
1916
- }
1917
- if (useJson) {
1918
- printJsonEnvelope("pack", { exported: result.path, pack: result.pack });
1919
- }
1920
- else {
1921
- console.log(`Strategy pack exported: ${result.path}`);
1922
- console.log(` Goal: ${result.pack.goal}`);
1923
- console.log(` Metric: ${result.pack.metric}`);
1924
- console.log(` Success: ${result.pack.evidence.success_rate}`);
1925
- }
1926
- break;
1927
- }
1928
- if (subCmd === "list") {
1929
- const { listPacks } = await import("./strategy-pack.js");
1930
- const packs = listPacks(grouped.repo);
1931
- if (useJson) {
1932
- printJsonEnvelope("pack", { packs });
1933
- }
1934
- else if (packs.length === 0) {
1935
- console.log("No strategy packs found.");
1936
- }
1937
- else {
1938
- console.log(`Strategy Packs (${packs.length}):`);
1939
- for (const p of packs)
1940
- console.log(` ${p.name}`);
1941
- }
1942
- break;
1943
- }
1944
- if (subCmd === "inspect") {
1945
- const name = cmdArgs[1];
1946
- if (!name) {
1947
- console.error("Usage: autoresearch pack inspect <name>");
1948
- return 1;
1949
- }
1950
- const { readPack } = await import("./strategy-pack.js");
1951
- const content = readPack(grouped.repo, name);
1952
- if (!content) {
1953
- console.error(`Pack not found: ${name}`);
1954
- return 1;
1955
- }
1956
- console.log(content);
1957
- break;
1958
- }
1959
- break;
1960
- }
1961
- case "leaderboard": {
1962
- const { generateLeaderboard, formatLeaderboardMarkdown, formatLeaderboardText } = await import("./leaderboard.js");
1963
- const { resolveRepo } = await import("./helpers.js");
1964
- const repo = resolveRepo(grouped.repo);
1965
- const leaderboard = generateLeaderboard(repo);
1966
- if (useJson) {
1967
- printJson(leaderboard);
1968
- break;
1969
- }
1970
- if (leaderboard.entries.length === 0) {
1971
- console.log("No runs found. Complete some runs to see the leaderboard.");
1972
- break;
1973
- }
1974
- if (grouped.format === "markdown") {
1975
- console.log(formatLeaderboardMarkdown(leaderboard));
1976
- }
1977
- else {
1978
- console.log(formatLeaderboardText(leaderboard));
1979
- }
1980
- break;
1981
- }
1982
- case "worker": {
1983
- const { workerOnce } = await import("./worker.js");
1984
- const once = grouped["once"] === "true";
1985
- if (!once) {
1986
- console.error("worker requires --once flag");
1987
- console.error("Usage: autoresearch worker --once [--json] [--repo <path>]");
1988
- return 1;
1989
- }
1990
- const result = workerOnce(grouped.repo, grouped["state-path"], grouped["results-path"]);
1991
- if (useJson) {
1992
- printJsonEnvelope("worker", result);
1993
- }
1994
- else {
1995
- if (result.ready) {
1996
- console.log(`✓ Ready for iteration ${result.iteration}`);
1997
- console.log(` Run ID: ${result.run_id}`);
1998
- console.log(` Status: ${result.status}`);
1999
- console.log(` Goal: ${result.goal}`);
2000
- if (result.metric)
2001
- console.log(` Metric: ${result.metric}`);
2002
- }
2003
- else {
2004
- console.log(`✗ Not ready: ${result.reason || "unknown"}`);
2005
- console.log(` Run ID: ${result.run_id}`);
2006
- console.log(` Iter: ${result.iteration}`);
2007
- }
2008
- }
2009
- return result.ready ? 0 : 1;
2010
- }
2011
- default: {
121
+ case "wizard": return handleWizard(grouped, useJson);
122
+ case "init": return handleInit(grouped, verbose, dryRun, useJson);
123
+ case "status": return handleStatus(grouped, useJson);
124
+ case "explain": return handleExplain(grouped, useJson);
125
+ case "history": return handleHistory(grouped, useJson);
126
+ case "scores": return handleScores(grouped, useJson);
127
+ case "score": return handleScore(grouped, useJson);
128
+ case "config": return handleConfig(grouped, useJson);
129
+ case "contract": return handleContract(useJson);
130
+ case "summary": return handleSummary(grouped, useJson);
131
+ case "validate": return handleValidate(grouped, useJson);
132
+ case "report": return handleReport(grouped, useJson);
133
+ case "suggest": return handleSuggest(grouped, useJson);
134
+ case "export": return handleExport(grouped);
135
+ case "completion": return handleCompletion(grouped);
136
+ case "launch": return handleLaunch(grouped, dryRun);
137
+ case "complete": return handleComplete(grouped, dryRun);
138
+ case "stop": return handleStop(grouped, dryRun);
139
+ case "resume": return handleResume(grouped, dryRun);
140
+ case "record": return handleRecord(grouped, dryRun);
141
+ case "digest": return handleDigest(grouped, useJson, dryRun);
142
+ case "doctor": return handleDoctor(grouped, useJson);
143
+ case "goal": return handleGoal(grouped, cmdArgs, useJson, verbose, dryRun);
144
+ case "queue": return handleQueue(grouped, cmdArgs, useJson, dryRun);
145
+ case "pack": return handlePack(grouped, cmdArgs, useJson);
146
+ case "leaderboard": return handleLeaderboard(grouped, useJson);
147
+ case "worker": return handleWorker(grouped, useJson);
148
+ default:
2012
149
  console.error(`Unknown command: ${cmd}`);
2013
150
  console.error("Run 'autoresearch --help' for usage.");
2014
151
  return 1;
2015
- }
2016
152
  }
2017
153
  }
2018
154
  catch (exc) {
@@ -2026,7 +162,6 @@ const main = async () => {
2026
162
  }
2027
163
  return 2;
2028
164
  }
2029
- return 0;
2030
165
  };
2031
166
  main().then((code) => process.exit(code)).catch((err) => {
2032
167
  console.error(err);