opencode-autoresearch 3.13.2 → 3.14.1

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 (71) hide show
  1. package/.opencode-plugin/plugin.json +1 -1
  2. package/INSTALL.md +52 -2
  3. package/README.md +5 -3
  4. package/VERSION +1 -1
  5. package/dist/cli.js +562 -35
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compaction.d.ts +17 -0
  8. package/dist/compaction.d.ts.map +1 -0
  9. package/dist/compaction.js +175 -0
  10. package/dist/compaction.js.map +1 -0
  11. package/dist/constants.d.ts +3 -1
  12. package/dist/constants.d.ts.map +1 -1
  13. package/dist/constants.js +9 -1
  14. package/dist/constants.js.map +1 -1
  15. package/dist/error-categories.d.ts +12 -0
  16. package/dist/error-categories.d.ts.map +1 -0
  17. package/dist/error-categories.js +137 -0
  18. package/dist/error-categories.js.map +1 -0
  19. package/dist/evidence.d.ts +24 -0
  20. package/dist/evidence.d.ts.map +1 -0
  21. package/dist/evidence.js +82 -0
  22. package/dist/evidence.js.map +1 -0
  23. package/dist/helpers.d.ts +13 -0
  24. package/dist/helpers.d.ts.map +1 -1
  25. package/dist/helpers.js +40 -0
  26. package/dist/helpers.js.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/leaderboard.d.ts +27 -0
  29. package/dist/leaderboard.d.ts.map +1 -0
  30. package/dist/leaderboard.js +195 -0
  31. package/dist/leaderboard.js.map +1 -0
  32. package/dist/memory-manager.d.ts.map +1 -1
  33. package/dist/memory-manager.js +8 -2
  34. package/dist/memory-manager.js.map +1 -1
  35. package/dist/metric-comparator.d.ts +15 -0
  36. package/dist/metric-comparator.d.ts.map +1 -0
  37. package/dist/metric-comparator.js +58 -0
  38. package/dist/metric-comparator.js.map +1 -0
  39. package/dist/run-manager.d.ts +1 -0
  40. package/dist/run-manager.d.ts.map +1 -1
  41. package/dist/run-manager.js +41 -14
  42. package/dist/run-manager.js.map +1 -1
  43. package/dist/serialize.d.ts +8 -0
  44. package/dist/serialize.d.ts.map +1 -0
  45. package/dist/serialize.js +50 -0
  46. package/dist/serialize.js.map +1 -0
  47. package/dist/strategy-pack.d.ts +31 -0
  48. package/dist/strategy-pack.d.ts.map +1 -0
  49. package/dist/strategy-pack.js +90 -0
  50. package/dist/strategy-pack.js.map +1 -0
  51. package/dist/subagent-pool.d.ts.map +1 -1
  52. package/dist/subagent-pool.js +22 -14
  53. package/dist/subagent-pool.js.map +1 -1
  54. package/dist/task-queue.d.ts +36 -0
  55. package/dist/task-queue.d.ts.map +1 -0
  56. package/dist/task-queue.js +65 -0
  57. package/dist/task-queue.js.map +1 -0
  58. package/dist/types.d.ts +45 -2
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/verifier-parser.d.ts.map +1 -1
  61. package/dist/verifier-parser.js +3 -1
  62. package/dist/verifier-parser.js.map +1 -1
  63. package/dist/whats-new.d.ts +12 -0
  64. package/dist/whats-new.d.ts.map +1 -0
  65. package/dist/whats-new.js +106 -0
  66. package/dist/whats-new.js.map +1 -0
  67. package/dist/worker.d.ts +11 -0
  68. package/dist/worker.d.ts.map +1 -0
  69. package/dist/worker.js +75 -0
  70. package/dist/worker.js.map +1 -0
  71. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,12 +1,27 @@
1
1
  #!/usr/bin/env node
2
- import { closeSync, existsSync, fstatSync, openSync, readFileSync, readSync, readdirSync } from "fs";
2
+ import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync, openSync, readFileSync, readSync, readdirSync } from "fs";
3
3
  import { resolve } from "path";
4
4
  import { execSync } from "child_process";
5
5
  import { MAX_DRAFTS } from "./constants.js";
6
- import { printJson, resolveRepo, parseRunState, parsePositiveInt, sanitizeForTerminal, getInstalledPackagePath, getInstalledPackageInfo, readUpdateCache, getGlobalNpmPrefix, readGoalDoc, atomicWriteTextInRepo } from "./helpers.js";
6
+ import { printJson, printJsonEnvelope, resolveRepo, parseRunState, parsePositiveInt, sanitizeForTerminal, getInstalledPackagePath, getInstalledPackageInfo, readUpdateCache, getGlobalNpmPrefix, readGoalDoc, atomicWriteTextInRepo } from "./helpers.js";
7
7
  const VERSION_FLAGS = ["--version", "-v"];
8
8
  const HELP_FLAGS = ["--help", "-h", "help"];
9
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
+ };
10
25
  const usage = () => {
11
26
  console.error("Usage: autoresearch <command> [options]");
12
27
  console.error("");
@@ -22,6 +37,7 @@ const usage = () => {
22
37
  console.error(" score Run the configured scorer and show normalized output");
23
38
  console.error(" digest Generate re-entry digest for operator handoff");
24
39
  console.error(" config Show runtime configuration");
40
+ console.error(" contract Print runtime contract schemas");
25
41
  console.error(" summary Aggregate stats across runs");
26
42
  console.error(" suggest Suggest next goal from memory");
27
43
  console.error(" launch Launch a background run");
@@ -29,6 +45,9 @@ const usage = () => {
29
45
  console.error(" stop Request a background run stop");
30
46
  console.error(" resume Resume a background run");
31
47
  console.error(" record Record an experiment result");
48
+ console.error(" queue Manage background task queue");
49
+ console.error(" pack Export and inspect strategy packs");
50
+ console.error(" leaderboard Show local leaderboard across runs");
32
51
  console.error(" doctor Verify package installation and version");
33
52
  console.error(" help Show this help");
34
53
  console.error("");
@@ -55,6 +74,7 @@ const usage = () => {
55
74
  console.error(" --duration Wall-clock cap (e.g., 5h or 300m)");
56
75
  console.error(` --num-drafts Number of parallel drafts (default: 1, max: ${MAX_DRAFTS})`);
57
76
  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"})');
58
78
  console.error(" --max-debug-depth Max debug experiment depth before stop");
59
79
  console.error(" --branch-failure-budget Per-branch failure budget before stop");
60
80
  console.error(" --json Output raw JSON (default: human-readable)");
@@ -84,7 +104,13 @@ const parseArgs = (args) => {
84
104
  const result = {};
85
105
  for (let i = 0; i < args.length; i++) {
86
106
  if (args[i].startsWith("--")) {
87
- const key = args[i].slice(2);
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);
88
114
  if (i + 1 < args.length && !args[i + 1].startsWith("--") && !args[i + 1].startsWith("-")) {
89
115
  result[key] = args[++i];
90
116
  }
@@ -183,6 +209,39 @@ const formatTimestamp = (ts) => {
183
209
  return ts;
184
210
  }
185
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
+ };
186
245
  const readTailLines = (filePath, limit) => {
187
246
  if (limit <= 0)
188
247
  return [];
@@ -245,6 +304,41 @@ const normalizeBranchPolicy = (value) => {
245
304
  return value;
246
305
  throw new Error(`Invalid branch policy: ${value}. Expected one of: ${BRANCH_POLICIES.join(", ")}`);
247
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`);
334
+ }
335
+ if (typeof branchPolicy !== "string") {
336
+ throw new Error(`Invalid branch policy override for ${branchId}: expected a string policy`);
337
+ }
338
+ overrides[branchId] = normalizeOverrideBranchPolicy(branchId, branchPolicy);
339
+ }
340
+ return overrides;
341
+ };
248
342
  const main = async () => {
249
343
  const args = process.argv.slice(2);
250
344
  // Handle standalone flags
@@ -298,7 +392,7 @@ const main = async () => {
298
392
  stop_condition: grouped["stop-condition"],
299
393
  rollback_strategy: grouped["rollback-strategy"],
300
394
  };
301
- printJson(buildSetupSummary(grouped.repo, config));
395
+ printJsonEnvelope("wizard", buildSetupSummary(grouped.repo, config));
302
396
  break;
303
397
  }
304
398
  case "init": {
@@ -335,6 +429,7 @@ const main = async () => {
335
429
  baseline: grouped.baseline,
336
430
  num_drafts: parsePositiveInt(grouped["num-drafts"], "num_drafts", { max: MAX_DRAFTS }) ?? 1,
337
431
  branch_selection_policy: normalizeBranchPolicy(grouped["branch-policy"]),
432
+ branch_policy_overrides: parseBranchPolicyOverrides(grouped["branch-policy-overrides"]),
338
433
  outcome_metric: grouped["outcome-metric"],
339
434
  outcome_direction: grouped["outcome-direction"],
340
435
  instrument_metric: grouped["instrument-metric"],
@@ -350,7 +445,7 @@ const main = async () => {
350
445
  const { buildSupervisorSnapshot } = await import("./run-manager.js");
351
446
  const snapshot = await buildSupervisorSnapshot(grouped.repo, grouped["results-path"], grouped["state-path"]);
352
447
  if (useJson) {
353
- printJson(snapshot);
448
+ printJsonEnvelope("status", snapshot);
354
449
  }
355
450
  else {
356
451
  const s = snapshot;
@@ -372,7 +467,8 @@ const main = async () => {
372
467
  console.log(`Results: ${s.results_rows} rows`);
373
468
  const lastIter = s.last_iteration;
374
469
  if (lastIter && lastIter.iteration) {
375
- console.log(`Last: iter ${formatDisplayValue(lastIter.iteration)} ${formatDisplayValue(lastIter.decision)} (${formatMetricValue(lastIter.metric_value)})`);
470
+ const stageTag = lastIter.stage ? ` [${lastIter.stage}]` : "";
471
+ console.log(`Last: iter ${formatDisplayValue(lastIter.iteration)}${stageTag} — ${formatDisplayValue(lastIter.decision)} (${formatMetricValue(lastIter.metric_value)})`);
376
472
  if (lastIter.score_components != null && typeof lastIter.score_components === "object") {
377
473
  const parts = Object.entries(lastIter.score_components)
378
474
  .map(([k, v]) => `${formatDisplayValue(k)}:${typeof v === "number" ? v.toFixed(4) : formatDisplayValue(v)}`)
@@ -380,12 +476,21 @@ const main = async () => {
380
476
  if (parts.length > 0)
381
477
  console.log(` Components: [${parts}]`);
382
478
  }
479
+ if (lastIter.selected_action)
480
+ console.log(` Action: ${formatDisplayValue(lastIter.selected_action)}`);
383
481
  }
384
482
  const flags = s.flags;
385
483
  if (flags?.needs_human)
386
484
  console.log("⚠ Needs human input");
387
485
  if (flags?.stop_requested)
388
486
  console.log("⏹ Stop requested");
487
+ if (flags?.context_pressure) {
488
+ const cp = flags.context_pressure;
489
+ if (cp.warnings.length > 0) {
490
+ for (const w of cp.warnings)
491
+ console.log(`🆘 Context pressure: ${w}`);
492
+ }
493
+ }
389
494
  }
390
495
  break;
391
496
  }
@@ -397,7 +502,7 @@ const main = async () => {
397
502
  const lastIter = s.last_iteration;
398
503
  const flags = s.flags;
399
504
  if (useJson) {
400
- printJson(snapshot);
505
+ printJsonEnvelope("explain", snapshot);
401
506
  break;
402
507
  }
403
508
  const statusEmoji = {
@@ -466,7 +571,7 @@ const main = async () => {
466
571
  }
467
572
  return obj;
468
573
  });
469
- printJson({ count: records.length, records: parsed });
574
+ printJsonEnvelope("history", { count: records.length, records: parsed });
470
575
  break;
471
576
  }
472
577
  for (const r of records) {
@@ -493,7 +598,7 @@ const main = async () => {
493
598
  const limit = parsePositiveInt(grouped.limit, "limit") ?? 10;
494
599
  const showTopComponents = grouped["top-components"] === "true";
495
600
  if (showTopComponents) {
496
- const allLines = readFileSync(scoreHistoryPath, "utf-8")
601
+ const allLines = readScoreHistoryFile(scoreHistoryPath)
497
602
  .split("\n")
498
603
  .map((l) => l.trim())
499
604
  .filter(Boolean);
@@ -512,7 +617,7 @@ const main = async () => {
512
617
  const { rankComponents } = await import("./score-parser.js");
513
618
  const ranking = rankComponents(allParsed);
514
619
  if (useJson) {
515
- printJson({ count: allParsed.length, scores: allParsed.slice(-limit), ranking });
620
+ printJsonEnvelope("scores", { count: allParsed.length, scores: allParsed.slice(-limit), ranking });
516
621
  break;
517
622
  }
518
623
  console.log("Component Rankings:");
@@ -548,7 +653,7 @@ const main = async () => {
548
653
  return null;
549
654
  }
550
655
  }).filter(Boolean);
551
- printJson({ count: parsed.length, scores: parsed });
656
+ printJsonEnvelope("scores", { count: parsed.length, scores: parsed });
552
657
  break;
553
658
  }
554
659
  console.log("Score History (latest " + Math.min(limit, records.length) + "):");
@@ -651,7 +756,7 @@ const main = async () => {
651
756
  const normalized = scored.score / scored.max;
652
757
  const percent = (normalized * 100).toFixed(1) + "%";
653
758
  if (useJson) {
654
- printJson({
759
+ printJsonEnvelope("score", {
655
760
  score: scored.score,
656
761
  max: scored.max,
657
762
  normalized,
@@ -687,7 +792,7 @@ const main = async () => {
687
792
  }
688
793
  const state = parseRunState(readJsonFile(statePath));
689
794
  if (useJson) {
690
- printJson({
795
+ printJsonEnvelope("config", {
691
796
  goal: state.goal,
692
797
  mode: state.mode,
693
798
  metric: state.metric,
@@ -719,6 +824,148 @@ const main = async () => {
719
824
  console.log(` Pool: ${state.subagent_pool ? "configured" : "none"}`);
720
825
  break;
721
826
  }
827
+ case "contract": {
828
+ const schemas = {
829
+ schema_version: "1.0.0",
830
+ description: "Auto Research runtime contract schemas",
831
+ state: {
832
+ type: "object",
833
+ required: ["schema_version", "run_id", "created_at", "updated_at", "status", "mode", "operating_mode", "goal", "scope", "metric", "verify", "label_requirements", "artifact_paths", "stats", "flags"],
834
+ properties: {
835
+ schema_version: { type: "number", description: "State schema version" },
836
+ run_id: { type: "string", description: "Unique run identifier" },
837
+ created_at: { type: "string", format: "date-time", description: "Run creation timestamp" },
838
+ updated_at: { type: "string", format: "date-time", description: "Last update timestamp" },
839
+ status: { type: "string", enum: ["initialized", "running", "stopping", "stopped", "completed", "needs_human"], description: "Run status" },
840
+ mode: { type: "string", enum: ["foreground", "background"], description: "Execution mode" },
841
+ operating_mode: { type: "string", enum: ["converge", "continuous", "supervised"], description: "Operating mode" },
842
+ goal: { type: "string", description: "Run goal description" },
843
+ scope: { type: "string", description: "Target scope" },
844
+ metric: {
845
+ type: "object",
846
+ required: ["name", "direction"],
847
+ properties: {
848
+ name: { type: "string" },
849
+ direction: { type: "string", enum: ["higher", "lower"] },
850
+ baseline: { type: "string" },
851
+ best: { type: "string" },
852
+ latest: { type: "string" },
853
+ },
854
+ },
855
+ instrument_metric: { type: "object", description: "Optional secondary metric" },
856
+ verify: { type: "string", description: "Verification command" },
857
+ guard: { type: "string", description: "Guard command" },
858
+ scorer: { type: "string", description: "Scorer command" },
859
+ iterations_cap: { type: "number", description: "Maximum iterations" },
860
+ duration: { type: "string", description: "Duration limit" },
861
+ duration_seconds: { type: "number" },
862
+ deadline_at: { type: "string", format: "date-time" },
863
+ label_requirements: {
864
+ type: "object",
865
+ required: ["keep", "stop"],
866
+ properties: {
867
+ keep: { type: "array", items: { type: "string" } },
868
+ stop: { type: "array", items: { type: "string" } },
869
+ },
870
+ },
871
+ artifact_paths: {
872
+ type: "object",
873
+ required: ["results", "state"],
874
+ properties: {
875
+ results: { type: "string" },
876
+ state: { type: "string" },
877
+ },
878
+ },
879
+ stats: {
880
+ type: "object",
881
+ required: ["total_iterations", "kept", "discarded", "needs_human"],
882
+ properties: {
883
+ total_iterations: { type: "number" },
884
+ kept: { type: "number" },
885
+ discarded: { type: "number" },
886
+ needs_human: { type: "number" },
887
+ consecutive_discards: { type: "number" },
888
+ best_iteration: { type: "number" },
889
+ debug_depth: { type: "number" },
890
+ },
891
+ },
892
+ flags: {
893
+ type: "object",
894
+ required: ["stop_requested", "needs_human", "background_active", "stop_ready"],
895
+ properties: {
896
+ stop_requested: { type: "boolean" },
897
+ needs_human: { type: "boolean" },
898
+ background_active: { type: "boolean" },
899
+ stop_ready: { type: "boolean" },
900
+ },
901
+ },
902
+ last_iteration: {
903
+ type: "object",
904
+ properties: {
905
+ iteration: { type: "number" },
906
+ decision: { type: "string", enum: ["keep", "discard", "needs_human"] },
907
+ metric_value: { type: "string" },
908
+ change_summary: { type: "string" },
909
+ labels: { type: "array", items: { type: "string" } },
910
+ timestamp: { type: "string", format: "date-time" },
911
+ },
912
+ },
913
+ draft_pool: { type: "object", description: "Draft pool configuration" },
914
+ lineage: { type: "object", description: "Experiment lineage" },
915
+ budget_exhausted: { type: "boolean" },
916
+ budget_blocker_reason: { type: "string" },
917
+ },
918
+ },
919
+ result_row: {
920
+ type: "object",
921
+ description: "Single iteration result row in TSV format",
922
+ properties: {
923
+ iteration: { type: "number" },
924
+ decision: { type: "string" },
925
+ metric_value: { type: "string" },
926
+ verify_status: { type: "string" },
927
+ guard_status: { type: "string" },
928
+ change_summary: { type: "string" },
929
+ labels: { type: "array", items: { type: "string" } },
930
+ timestamp: { type: "string" },
931
+ note: { type: "string" },
932
+ },
933
+ },
934
+ goal_doc: {
935
+ type: "object",
936
+ required: ["goal", "metric", "direction", "verify"],
937
+ properties: {
938
+ goal: { type: "string" },
939
+ metric: { type: "string" },
940
+ direction: { type: "string", enum: ["higher", "lower"] },
941
+ verify: { type: "string" },
942
+ guard: { type: "string" },
943
+ constraints: { type: "string" },
944
+ file_map: { type: "string" },
945
+ stop_conditions: { type: "string" },
946
+ },
947
+ },
948
+ };
949
+ if (useJson) {
950
+ printJsonEnvelope("contract", schemas);
951
+ break;
952
+ }
953
+ console.log("Auto Research Contract Schemas");
954
+ console.log("==============================");
955
+ console.log("");
956
+ console.log("State Schema:");
957
+ console.log(` Version: ${schemas.state.properties.schema_version.type}`);
958
+ console.log(` Required: ${schemas.state.required.join(", ")}`);
959
+ console.log("");
960
+ console.log("Result Row Schema:");
961
+ console.log(` Properties: ${Object.keys(schemas.result_row.properties).join(", ")}`);
962
+ console.log("");
963
+ console.log("Goal Doc Schema:");
964
+ console.log(` Required: ${schemas.goal_doc.required.join(", ")}`);
965
+ console.log("");
966
+ console.log("Use --json for full machine-readable schema output.");
967
+ break;
968
+ }
722
969
  case "summary": {
723
970
  const { resolvePath } = await import("./helpers.js");
724
971
  const { RESULTS_DEFAULT } = await import("./constants.js");
@@ -746,7 +993,7 @@ const main = async () => {
746
993
  runIds.add(iterTags[0]);
747
994
  }
748
995
  if (useJson) {
749
- printJson({
996
+ printJsonEnvelope("summary", {
750
997
  total_records: records.length,
751
998
  total_kept: totalKept,
752
999
  total_discarded: totalDiscarded,
@@ -789,7 +1036,7 @@ const main = async () => {
789
1036
  if (!grouped.verify)
790
1037
  errors.push("Missing required: --verify");
791
1038
  if (useJson) {
792
- printJson({ valid: errors.length === 0, errors });
1039
+ printJsonEnvelope("validate", { valid: errors.length === 0, errors });
793
1040
  return errors.length > 0 ? 1 : 0;
794
1041
  }
795
1042
  if (errors.length === 0) {
@@ -827,7 +1074,7 @@ const main = async () => {
827
1074
  results = resultLines.slice(1).filter(Boolean);
828
1075
  }
829
1076
  if (useJson) {
830
- printJson({ state, results_count: results.length });
1077
+ printJsonEnvelope("report", { state, results_count: results.length });
831
1078
  break;
832
1079
  }
833
1080
  console.log(`# Auto Research Report`);
@@ -861,6 +1108,40 @@ const main = async () => {
861
1108
  }
862
1109
  }
863
1110
  }
1111
+ // Milestone Progress
1112
+ console.log(`\n## Milestone Progress`);
1113
+ if (state.stats) {
1114
+ const s = state.stats;
1115
+ const total = s.total_iterations;
1116
+ const successRate = total > 0 ? ((s.kept / total) * 100).toFixed(1) : "0";
1117
+ console.log(`- **Progress:** ${formatMarkdownField(s.kept)} kept / ${formatMarkdownField(total)} total iterations (${formatMarkdownField(successRate)}% success rate)`);
1118
+ if (state.iterations_cap) {
1119
+ const progressPct = ((total / state.iterations_cap) * 100).toFixed(1);
1120
+ console.log(`- **Cap:** ${formatMarkdownField(total)} / ${formatMarkdownField(state.iterations_cap)} iterations (${formatMarkdownField(progressPct)}% of cap)`);
1121
+ }
1122
+ if (state.created_at) {
1123
+ const startedAtMs = Date.parse(state.created_at);
1124
+ const endedAtMs = state.updated_at ? Date.parse(state.updated_at) : Date.now();
1125
+ if (!Number.isNaN(startedAtMs) && !Number.isNaN(endedAtMs) && endedAtMs >= startedAtMs) {
1126
+ const elapsedMin = Math.round((endedAtMs - startedAtMs) / 1000 / 60);
1127
+ console.log(`- **Elapsed:** ${formatMarkdownField(elapsedMin)} minutes`);
1128
+ }
1129
+ }
1130
+ // Next candidate
1131
+ if (state.last_iteration && state.last_iteration.decision === "keep") {
1132
+ console.log(`- **Next candidate:** Iteration ${formatMarkdownField(state.last_iteration.iteration)} (kept)`);
1133
+ }
1134
+ else if (s.best_iteration) {
1135
+ console.log(`- **Best candidate:** Iteration ${formatMarkdownField(s.best_iteration)}`);
1136
+ }
1137
+ }
1138
+ // Artifact pointers
1139
+ console.log(`\n## Artifacts`);
1140
+ console.log(`- State: ${formatMarkdownField(state.artifact_paths?.state || ".autoresearch/state.json")}`);
1141
+ console.log(`- Results: ${formatMarkdownField(state.artifact_paths?.results || "autoresearch-results.tsv")}`);
1142
+ if (grouped.repo) {
1143
+ console.log(`- Repository: ${formatMarkdownField(grouped.repo)}`);
1144
+ }
864
1145
  // Failed branches information
865
1146
  if (state.draft_pool && state.draft_pool.active_drafts) {
866
1147
  const failedBranches = state.draft_pool.active_drafts.filter(draft => draft.status === "discarded");
@@ -920,6 +1201,37 @@ const main = async () => {
920
1201
  break;
921
1202
  }
922
1203
  case "suggest": {
1204
+ const evidenceGated = grouped["evidence"] === "true";
1205
+ if (evidenceGated) {
1206
+ const { generateIssueCandidate } = await import("./evidence.js");
1207
+ const candidate = generateIssueCandidate(grouped.repo, grouped.goal, grouped.metric, grouped.verify, grouped["score-history-path"]);
1208
+ if (!candidate) {
1209
+ if (useJson) {
1210
+ printJsonEnvelope("suggest", { candidates: [], reason: "insufficient_evidence" });
1211
+ }
1212
+ else {
1213
+ console.log("No evidence-gated issue candidates found.");
1214
+ console.log("Insufficient failure clusters or score history not available.");
1215
+ }
1216
+ break;
1217
+ }
1218
+ if (useJson) {
1219
+ printJsonEnvelope("suggest", { candidates: [candidate], evidence_gated: true });
1220
+ }
1221
+ else {
1222
+ console.log(`Evidence-Gated Issue Candidate:`);
1223
+ console.log(` Title: ${candidate.title}`);
1224
+ console.log(` Goal: ${candidate.goal}`);
1225
+ console.log(` Metric: ${candidate.metric}`);
1226
+ console.log(` Evidence: ${candidate.evidence.total_discards} discards in ${candidate.evidence.total_runs} cluster(s)`);
1227
+ console.log(``);
1228
+ console.log(` Suggested command:`);
1229
+ console.log(` ${candidate.suggest_command}`);
1230
+ console.log(``);
1231
+ console.log(`Review before opening. This candidate is NOT auto-submitted.`);
1232
+ }
1233
+ break;
1234
+ }
923
1235
  const { resolvePath } = await import("./helpers.js");
924
1236
  const { MEMORY_DEFAULT } = await import("./constants.js");
925
1237
  const memoryPath = resolvePath(grouped.repo, grouped["memory-path"], MEMORY_DEFAULT);
@@ -928,10 +1240,10 @@ const main = async () => {
928
1240
  break;
929
1241
  }
930
1242
  const memory = readFileSync(memoryPath, "utf-8");
931
- const patterns = memory.match(/### Pattern: [^\n]+/g) ?? [];
1243
+ const patterns = memory.match(/^### Pattern: [^\n]+/gm) ?? [];
932
1244
  const suggestions = patterns.map(parseMemoryPatternHeading);
933
1245
  if (useJson) {
934
- printJson({ patterns_found: suggestions.length, suggestions });
1246
+ printJsonEnvelope("suggest", { patterns_found: suggestions.length, suggestions });
935
1247
  break;
936
1248
  }
937
1249
  console.log("Memory Patterns — candidate next goals:");
@@ -974,7 +1286,7 @@ const main = async () => {
974
1286
  },
975
1287
  };
976
1288
  if (format === "json") {
977
- console.log(JSON.stringify(exportData, null, 2));
1289
+ printJsonEnvelope("export", exportData);
978
1290
  }
979
1291
  else if (format === "md" || format === "markdown") {
980
1292
  console.log(`# Auto Research Export`);
@@ -1000,8 +1312,8 @@ const main = async () => {
1000
1312
  }
1001
1313
  case "completion": {
1002
1314
  const shell = grouped.shell || "bash";
1003
- const commands = ["init", "goal", "wizard", "status", "explain", "history", "config", "summary", "suggest", "launch", "complete", "stop", "resume", "record", "doctor", "export", "completion", "help"];
1004
- const options = ["--repo", "--goal", "--metric", "--direction", "--verify", "--guard", "--mode", "--scope", "--iterations", "--duration", "--num-drafts", "--branch-policy", "--json", "--results-path", "--state-path", "--fresh-start", "--memory-path", "--format", "--shell", "--goal-path", "--template"];
1315
+ const commands = ["init", "goal", "wizard", "status", "explain", "history", "config", "summary", "suggest", "launch", "complete", "stop", "resume", "record", "doctor", "pack", "export", "completion", "help"];
1316
+ 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"];
1005
1317
  if (shell === "bash" || shell === "zsh") {
1006
1318
  console.log(`# Auto Research CLI completion for ${shell}`);
1007
1319
  console.log(`_autoresearch() {`);
@@ -1056,6 +1368,7 @@ const main = async () => {
1056
1368
  baseline: grouped.baseline,
1057
1369
  num_drafts: parsePositiveInt(grouped["num-drafts"], "num_drafts", { max: MAX_DRAFTS }) ?? 1,
1058
1370
  branch_selection_policy: normalizeBranchPolicy(grouped["branch-policy"]),
1371
+ branch_policy_overrides: parseBranchPolicyOverrides(grouped["branch-policy-overrides"]),
1059
1372
  outcome_metric: grouped["outcome-metric"],
1060
1373
  outcome_direction: grouped["outcome-direction"],
1061
1374
  instrument_metric: grouped["instrument-metric"],
@@ -1071,7 +1384,7 @@ const main = async () => {
1071
1384
  const { writeFileSync } = await import("fs");
1072
1385
  const state = await initializeRun(grouped.repo, grouped["results-path"], grouped["state-path"], config, grouped["fresh-start"] === "true");
1073
1386
  writeFileSync(launchPath, JSON.stringify({ run_id: state.run_id, goal: state.goal, mode: "background" }, null, 2) + "\n", "utf-8");
1074
- printJson({ status: "launched", run_id: state.run_id, launch_path: launchPath });
1387
+ printJsonEnvelope("launch", { status: "launched", run_id: state.run_id, launch_path: launchPath });
1075
1388
  break;
1076
1389
  }
1077
1390
  case "complete": {
@@ -1081,7 +1394,7 @@ const main = async () => {
1081
1394
  }
1082
1395
  const { completeRun } = await import("./run-manager.js");
1083
1396
  const state = await completeRun(grouped.repo, grouped["state-path"]);
1084
- printJson({ status: "completed", run_id: state.run_id });
1397
+ printJsonEnvelope("complete", { status: "completed", run_id: state.run_id });
1085
1398
  break;
1086
1399
  }
1087
1400
  case "stop": {
@@ -1091,7 +1404,7 @@ const main = async () => {
1091
1404
  }
1092
1405
  const { setStopRequested } = await import("./run-manager.js");
1093
1406
  const state = await setStopRequested(grouped.repo, grouped["state-path"]);
1094
- printJson({ status: "stop_requested", run_id: state.run_id });
1407
+ printJsonEnvelope("stop", { status: "stop_requested", run_id: state.run_id });
1095
1408
  break;
1096
1409
  }
1097
1410
  case "resume": {
@@ -1101,7 +1414,7 @@ const main = async () => {
1101
1414
  }
1102
1415
  const { resumeBackgroundRun } = await import("./run-manager.js");
1103
1416
  const state = await resumeBackgroundRun(grouped.repo, grouped["state-path"]);
1104
- printJson({ status: "resumed", run_id: state.run_id });
1417
+ printJsonEnvelope("resume", { status: "resumed", run_id: state.run_id });
1105
1418
  break;
1106
1419
  }
1107
1420
  case "record": {
@@ -1139,12 +1452,21 @@ const main = async () => {
1139
1452
  note: grouped.note,
1140
1453
  iteration,
1141
1454
  score_components: scoreComponents,
1455
+ stage: grouped.stage || "improve",
1456
+ selected_action: grouped["selected-action"],
1142
1457
  }, null, 2));
1143
1458
  return 0;
1144
1459
  }
1145
1460
  const { appendIteration } = await import("./run-manager.js");
1146
- 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);
1147
- printJson(state);
1461
+ const lineage = {};
1462
+ const stage = grouped.stage;
1463
+ if (stage)
1464
+ lineage.stage = stage;
1465
+ const selectedAction = grouped["selected-action"];
1466
+ if (selectedAction)
1467
+ lineage.selected_action = selectedAction;
1468
+ 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);
1469
+ printJsonEnvelope("record", state);
1148
1470
  break;
1149
1471
  }
1150
1472
  case "digest": {
@@ -1155,7 +1477,7 @@ const main = async () => {
1155
1477
  const { buildRunDigest } = await import("./run-manager.js");
1156
1478
  const digest = await buildRunDigest(grouped.repo, grouped["results-path"], grouped["state-path"]);
1157
1479
  if (useJson) {
1158
- printJson(digest);
1480
+ printJsonEnvelope("digest", digest);
1159
1481
  }
1160
1482
  else {
1161
1483
  console.log(`# Auto Research Digest`);
@@ -1199,7 +1521,7 @@ const main = async () => {
1199
1521
  if (digest.flags && Object.keys(digest.flags).length > 0) {
1200
1522
  console.log(`\n## Flags`);
1201
1523
  for (const [key, value] of Object.entries(digest.flags)) {
1202
- console.log(`- ${key}: ${formatMarkdownField(value)}`);
1524
+ console.log(`- ${formatMarkdownField(key)}: ${formatMarkdownField(value)}`);
1203
1525
  }
1204
1526
  }
1205
1527
  }
@@ -1225,6 +1547,7 @@ const main = async () => {
1225
1547
  const installedPath = getInstalledPackagePath(PACKAGE_NAME);
1226
1548
  const installedInfo = installedPath ? getInstalledPackageInfo(PACKAGE_NAME) : null;
1227
1549
  const updateCache = readUpdateCache();
1550
+ const { skip: updateSkipped, reason: skipReason } = shouldSkipUpdateCheck(process.argv.slice(2));
1228
1551
  const updateStatus = {
1229
1552
  cache_exists: updateCache !== null,
1230
1553
  last_check: updateCache?.last_check || null,
@@ -1232,9 +1555,13 @@ const main = async () => {
1232
1555
  latest_version: updateCache?.latest_version || null,
1233
1556
  update_available: updateCache?.update_available || false,
1234
1557
  update_disabled: process.env.AUTORESEARCH_NO_UPDATE === "1",
1558
+ skipped: updateSkipped,
1559
+ skip_reason: skipReason,
1235
1560
  };
1236
1561
  if (useJson) {
1237
- printJson({
1562
+ const { getWhatsNew } = await import("./whats-new.js");
1563
+ const wn = getWhatsNew(base);
1564
+ printJsonEnvelope("doctor", {
1238
1565
  version: VERSION,
1239
1566
  skill_name: SKILL_NAME,
1240
1567
  runtime: `Node.js ${process.version}`,
@@ -1249,6 +1576,7 @@ const main = async () => {
1249
1576
  update: updateStatus,
1250
1577
  checks: checks,
1251
1578
  checks_passed: checks.filter((c) => !c.ok).length === 0,
1579
+ whats_new: wn ? { features: wn.features, fixes: wn.fixes } : null,
1252
1580
  });
1253
1581
  break;
1254
1582
  }
@@ -1269,7 +1597,10 @@ const main = async () => {
1269
1597
  }
1270
1598
  console.log("");
1271
1599
  console.log("Update:");
1272
- if (updateCache) {
1600
+ if (updateSkipped) {
1601
+ console.log(` Skipped: yes (${skipReason})`);
1602
+ }
1603
+ else if (updateCache) {
1273
1604
  console.log(` Last check: ${updateCache.last_check}`);
1274
1605
  console.log(` Current: ${updateCache.current_version}`);
1275
1606
  console.log(` Latest: ${updateCache.latest_version}`);
@@ -1294,6 +1625,20 @@ const main = async () => {
1294
1625
  return 1;
1295
1626
  }
1296
1627
  console.log(`\nAll ${checks.length} checks passed.`);
1628
+ const showWhatsNew = grouped["whats-new"] === "true";
1629
+ if (showWhatsNew || useJson) {
1630
+ const { getWhatsNew, formatWhatsNew } = await import("./whats-new.js");
1631
+ const wn = getWhatsNew(base);
1632
+ if (wn) {
1633
+ if (useJson) {
1634
+ // Already displayed in JSON envelope — add it for next re-entry
1635
+ }
1636
+ else {
1637
+ console.log("");
1638
+ console.log(formatWhatsNew(wn));
1639
+ }
1640
+ }
1641
+ }
1297
1642
  break;
1298
1643
  }
1299
1644
  case "goal": {
@@ -1336,7 +1681,7 @@ const main = async () => {
1336
1681
  }
1337
1682
  const doc = readGoalDoc(goalPath);
1338
1683
  if (useJson) {
1339
- printJson(doc);
1684
+ printJsonEnvelope("goal", doc);
1340
1685
  break;
1341
1686
  }
1342
1687
  console.log(`Goal: ${formatDisplayValue(doc.goal)}`);
@@ -1456,7 +1801,7 @@ const main = async () => {
1456
1801
  const result = buildGoalInitResult(goalPath, config, !hasRequiredFlags && isTTY);
1457
1802
  if (isGoalDryRun) {
1458
1803
  if (useGoalJson) {
1459
- printJson({ ...result, dry_run: true });
1804
+ printJsonEnvelope("goal", { ...result, dry_run: true });
1460
1805
  }
1461
1806
  else {
1462
1807
  console.log("[dry-run] Would write goal document to: " + goalPath);
@@ -1472,7 +1817,7 @@ const main = async () => {
1472
1817
  }
1473
1818
  atomicWriteTextInRepo(goalGrouped.repo, goalPath, document);
1474
1819
  if (useGoalJson) {
1475
- printJson(result);
1820
+ printJsonEnvelope("goal", result);
1476
1821
  }
1477
1822
  else {
1478
1823
  console.log(`✓ Goal definition written to ${goalPath}`);
@@ -1487,6 +1832,181 @@ const main = async () => {
1487
1832
  }
1488
1833
  break;
1489
1834
  }
1835
+ case "queue": {
1836
+ const subCmd = cmdArgs[0] || "list";
1837
+ if (subCmd === "help") {
1838
+ console.error("Usage: autoresearch queue <subcommand> [options]");
1839
+ console.error("");
1840
+ console.error("Subcommands:");
1841
+ console.error(" list List tasks in the queue (default)");
1842
+ console.error(" enqueue Enqueue a new task");
1843
+ console.error(" clean Remove completed and failed tasks");
1844
+ break;
1845
+ }
1846
+ if (subCmd === "enqueue") {
1847
+ if (!grouped.goal || !grouped.metric || !grouped.verify) {
1848
+ console.error("--goal, --metric, and --verify are required for enqueue");
1849
+ return 1;
1850
+ }
1851
+ const { enqueueTasks } = await import("./task-queue.js");
1852
+ const tasks = await enqueueTasks(grouped.repo, [{ goal: grouped.goal, metric: grouped.metric, verify: grouped.verify }]);
1853
+ if (useJson) {
1854
+ printJson({ enqueued: tasks });
1855
+ }
1856
+ else {
1857
+ for (const t of tasks) {
1858
+ console.log(`Enqueued: ${t.id} - ${t.goal}`);
1859
+ }
1860
+ }
1861
+ break;
1862
+ }
1863
+ if (subCmd === "clean") {
1864
+ const { listTasks, writeManifest, resolveQueuePath } = await import("./task-queue.js");
1865
+ const queuePath = resolveQueuePath(grouped.repo);
1866
+ const manifest = await listTasks(grouped.repo);
1867
+ const before = manifest.tasks.length;
1868
+ manifest.tasks = manifest.tasks.filter((t) => t.status === "pending" || t.status === "leased");
1869
+ manifest.updated_at = new Date().toISOString();
1870
+ const removed = before - manifest.tasks.length;
1871
+ await writeManifest(queuePath, manifest);
1872
+ if (useJson) {
1873
+ printJson({ removed });
1874
+ }
1875
+ else {
1876
+ console.log(`Cleaned ${removed} completed/failed tasks. ${manifest.tasks.length} remain.`);
1877
+ }
1878
+ break;
1879
+ }
1880
+ const { listTasks } = await import("./task-queue.js");
1881
+ const manifest = await listTasks(grouped.repo);
1882
+ if (useJson) {
1883
+ printJson(manifest);
1884
+ }
1885
+ else {
1886
+ if (manifest.tasks.length === 0) {
1887
+ console.log("No tasks in queue.");
1888
+ }
1889
+ else {
1890
+ console.log(`Task Queue (${manifest.tasks.length} tasks):`);
1891
+ for (const task of manifest.tasks) {
1892
+ const icon = task.status === "completed" ? "v" : task.status === "failed" ? "x" : task.status === "leased" ? ">" : "*";
1893
+ console.log(` ${icon} ${task.id} [${task.status}] ${task.goal}`);
1894
+ }
1895
+ }
1896
+ }
1897
+ break;
1898
+ }
1899
+ case "pack": {
1900
+ const subCmd = cmdArgs[0] || "help";
1901
+ if (subCmd === "help" || (subCmd !== "export" && subCmd !== "list" && subCmd !== "inspect")) {
1902
+ console.error("Usage: autoresearch pack <subcommand> [options]");
1903
+ console.error("Subcommands:");
1904
+ console.error(" export Export validated run as a strategy pack");
1905
+ console.error(" list List available strategy packs");
1906
+ console.error(" inspect View a specific strategy pack");
1907
+ break;
1908
+ }
1909
+ if (subCmd === "export") {
1910
+ const { exportPack } = await import("./strategy-pack.js");
1911
+ const result = exportPack(grouped.repo, grouped["state-path"], grouped["goal-path"]);
1912
+ if (!result) {
1913
+ console.error("No run state found. Complete a run first.");
1914
+ return 1;
1915
+ }
1916
+ if (useJson) {
1917
+ printJsonEnvelope("pack", { exported: result.path, pack: result.pack });
1918
+ }
1919
+ else {
1920
+ console.log(`Strategy pack exported: ${result.path}`);
1921
+ console.log(` Goal: ${result.pack.goal}`);
1922
+ console.log(` Metric: ${result.pack.metric}`);
1923
+ console.log(` Success: ${result.pack.evidence.success_rate}`);
1924
+ }
1925
+ break;
1926
+ }
1927
+ if (subCmd === "list") {
1928
+ const { listPacks } = await import("./strategy-pack.js");
1929
+ const packs = listPacks(grouped.repo);
1930
+ if (useJson) {
1931
+ printJsonEnvelope("pack", { packs });
1932
+ }
1933
+ else if (packs.length === 0) {
1934
+ console.log("No strategy packs found.");
1935
+ }
1936
+ else {
1937
+ console.log(`Strategy Packs (${packs.length}):`);
1938
+ for (const p of packs)
1939
+ console.log(` ${p.name}`);
1940
+ }
1941
+ break;
1942
+ }
1943
+ if (subCmd === "inspect") {
1944
+ const name = cmdArgs[1];
1945
+ if (!name) {
1946
+ console.error("Usage: autoresearch pack inspect <name>");
1947
+ return 1;
1948
+ }
1949
+ const { readPack } = await import("./strategy-pack.js");
1950
+ const content = readPack(grouped.repo, name);
1951
+ if (!content) {
1952
+ console.error(`Pack not found: ${name}`);
1953
+ return 1;
1954
+ }
1955
+ console.log(content);
1956
+ break;
1957
+ }
1958
+ break;
1959
+ }
1960
+ case "leaderboard": {
1961
+ const { generateLeaderboard, formatLeaderboardMarkdown, formatLeaderboardText } = await import("./leaderboard.js");
1962
+ const { resolveRepo } = await import("./helpers.js");
1963
+ const repo = resolveRepo(grouped.repo);
1964
+ const leaderboard = generateLeaderboard(repo);
1965
+ if (useJson) {
1966
+ printJson(leaderboard);
1967
+ break;
1968
+ }
1969
+ if (leaderboard.entries.length === 0) {
1970
+ console.log("No runs found. Complete some runs to see the leaderboard.");
1971
+ break;
1972
+ }
1973
+ if (grouped.format === "markdown") {
1974
+ console.log(formatLeaderboardMarkdown(leaderboard));
1975
+ }
1976
+ else {
1977
+ console.log(formatLeaderboardText(leaderboard));
1978
+ }
1979
+ break;
1980
+ }
1981
+ case "worker": {
1982
+ const { workerOnce } = await import("./worker.js");
1983
+ const once = grouped["once"] === "true";
1984
+ if (!once) {
1985
+ console.error("worker requires --once flag");
1986
+ console.error("Usage: autoresearch worker --once [--json] [--repo <path>]");
1987
+ return 1;
1988
+ }
1989
+ const result = workerOnce(grouped.repo, grouped["state-path"], grouped["results-path"]);
1990
+ if (useJson) {
1991
+ printJsonEnvelope("worker", result);
1992
+ }
1993
+ else {
1994
+ if (result.ready) {
1995
+ console.log(`✓ Ready for iteration ${result.iteration}`);
1996
+ console.log(` Run ID: ${result.run_id}`);
1997
+ console.log(` Status: ${result.status}`);
1998
+ console.log(` Goal: ${result.goal}`);
1999
+ if (result.metric)
2000
+ console.log(` Metric: ${result.metric}`);
2001
+ }
2002
+ else {
2003
+ console.log(`✗ Not ready: ${result.reason || "unknown"}`);
2004
+ console.log(` Run ID: ${result.run_id}`);
2005
+ console.log(` Iter: ${result.iteration}`);
2006
+ }
2007
+ }
2008
+ return result.ready ? 0 : 1;
2009
+ }
1490
2010
  default: {
1491
2011
  console.error(`Unknown command: ${cmd}`);
1492
2012
  console.error("Run 'autoresearch --help' for usage.");
@@ -1495,7 +2015,14 @@ const main = async () => {
1495
2015
  }
1496
2016
  }
1497
2017
  catch (exc) {
1498
- console.error(exc.message);
2018
+ const { categorizeError, formatStructuredError } = await import("./error-categories.js");
2019
+ const structured = categorizeError(exc);
2020
+ if (useJson) {
2021
+ console.error(formatStructuredError(structured, true));
2022
+ }
2023
+ else {
2024
+ console.error(formatStructuredError(structured, false));
2025
+ }
1499
2026
  return 2;
1500
2027
  }
1501
2028
  return 0;