opencode-autoresearch 3.1.0-beta.2 → 3.3.0

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 (45) hide show
  1. package/.opencode-plugin/plugin.json +1 -1
  2. package/AGENTS.md +42 -0
  3. package/README.md +246 -30
  4. package/VERSION +1 -0
  5. package/dist/cli.js +508 -15
  6. package/dist/cli.js.map +1 -1
  7. package/dist/constants.d.ts +1 -5
  8. package/dist/constants.d.ts.map +1 -1
  9. package/dist/constants.js +1 -5
  10. package/dist/constants.js.map +1 -1
  11. package/dist/helpers.d.ts +1 -2
  12. package/dist/helpers.d.ts.map +1 -1
  13. package/dist/helpers.js +19 -10
  14. package/dist/helpers.js.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/run-manager.d.ts +2 -2
  18. package/dist/run-manager.d.ts.map +1 -1
  19. package/dist/run-manager.js +18 -16
  20. package/dist/run-manager.js.map +1 -1
  21. package/dist/subagent-pool.d.ts +6 -0
  22. package/dist/subagent-pool.d.ts.map +1 -1
  23. package/dist/subagent-pool.js +12 -2
  24. package/dist/subagent-pool.js.map +1 -1
  25. package/dist/types.d.ts +15 -38
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/wizard.d.ts.map +1 -1
  28. package/dist/wizard.js +2 -1
  29. package/dist/wizard.js.map +1 -1
  30. package/docs/ARCHITECTURE.md +134 -28
  31. package/docs/RELEASE.md +54 -25
  32. package/hooks/init.sh +6 -2
  33. package/hooks/status.sh +4 -3
  34. package/hooks/stop.sh +10 -6
  35. package/hooks/verify-package.sh +78 -0
  36. package/package.json +34 -14
  37. package/skills/autoresearch/SKILL.md +29 -4
  38. package/skills/autoresearch/references/core-principles.md +3 -3
  39. package/skills/autoresearch/references/interaction-wizard.md +1 -1
  40. package/skills/autoresearch/references/loop-workflow.md +4 -4
  41. package/skills/autoresearch/references/plan-workflow.md +2 -2
  42. package/skills/autoresearch/references/results-logging.md +1 -1
  43. package/skills/autoresearch/references/self-improve-loop.md +255 -0
  44. package/skills/autoresearch/references/state-management.md +3 -3
  45. package/skills/autoresearch/references/subagent-orchestration.md +1 -1
package/dist/cli.js CHANGED
@@ -1,12 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import { printJson } from "./helpers.js";
3
- function usage() {
2
+ import { existsSync, readFileSync, readdirSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { printJson, resolveRepo } from "./helpers.js";
5
+ const VERSION_FLAGS = ["--version", "-v"];
6
+ const HELP_FLAGS = ["--help", "-h", "help"];
7
+ const usage = () => {
4
8
  console.error("Usage: autoresearch <command> [options]");
5
9
  console.error("");
6
10
  console.error("Commands:");
7
11
  console.error(" init Initialize a run");
8
12
  console.error(" wizard Generate a setup summary");
9
13
  console.error(" status Print run status");
14
+ console.error(" explain Human-readable run state");
15
+ console.error(" history Show recent iteration log");
16
+ console.error(" config Show runtime configuration");
17
+ console.error(" summary Aggregate stats across runs");
18
+ console.error(" suggest Suggest next goal from memory");
10
19
  console.error(" launch Launch a background run");
11
20
  console.error(" complete Mark a run complete");
12
21
  console.error(" stop Request a background run stop");
@@ -21,25 +30,49 @@ function usage() {
21
30
  console.error(" --metric Metric name to track");
22
31
  console.error(" --direction lower or higher");
23
32
  console.error(" --verify Mechanical verification command");
33
+ console.error(" --guard Guard command for regression catch");
24
34
  console.error(" --mode foreground or background");
25
35
  console.error(" --scope In-scope files or subsystem");
26
36
  console.error(" --iterations Iteration cap");
27
37
  console.error(" --duration Wall-clock cap (e.g., 5h or 300m)");
38
+ console.error(" --json Output raw JSON (default: human-readable)");
28
39
  console.error(" --results-path Custom results TSV path");
29
40
  console.error(" --state-path Custom state JSON path");
30
41
  console.error(" --fresh-start Archive previous artifacts before starting");
31
42
  console.error("");
43
+ console.error("Flags:");
44
+ console.error(" -h, --help Show this help");
45
+ console.error(" -v, --version Show version");
46
+ console.error(" --verbose Enable verbose output");
47
+ console.error(" --dry-run Preview changes without executing");
48
+ console.error("");
32
49
  console.error("Examples:");
33
50
  console.error(" autoresearch wizard --goal \"optimize response time\"");
34
51
  console.error(" autoresearch init --goal \"reduce errors\" --metric errors --direction lower --verify \"npm test\"");
35
52
  console.error(" autoresearch status");
36
- }
37
- function parseArgs(args) {
53
+ console.error(" autoresearch explain");
54
+ console.error(" autoresearch history");
55
+ };
56
+ const parseArgs = (args) => {
38
57
  const result = {};
39
58
  for (let i = 0; i < args.length; i++) {
40
59
  if (args[i].startsWith("--")) {
41
60
  const key = args[i].slice(2);
42
- if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
61
+ if (i + 1 < args.length && !args[i + 1].startsWith("--") && !args[i + 1].startsWith("-")) {
62
+ result[key] = args[++i];
63
+ }
64
+ else {
65
+ result[key] = "true";
66
+ }
67
+ }
68
+ else if (args[i].startsWith("-") && args[i].length === 2 && args[i] !== "--") {
69
+ const shortToLong = {
70
+ r: "repo", g: "goal", m: "metric", d: "direction",
71
+ v: "verify", n: "guard", o: "mode", s: "scope",
72
+ i: "iterations", t: "duration",
73
+ };
74
+ const key = shortToLong[args[i][1]] ?? args[i].slice(1);
75
+ if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
43
76
  result[key] = args[++i];
44
77
  }
45
78
  else {
@@ -48,16 +81,44 @@ function parseArgs(args) {
48
81
  }
49
82
  }
50
83
  return result;
51
- }
52
- async function main() {
84
+ };
85
+ const formatMetricValue = (val) => {
86
+ if (val === undefined || val === null)
87
+ return "—";
88
+ return String(val);
89
+ };
90
+ const formatTimestamp = (ts) => {
91
+ try {
92
+ const d = new Date(ts);
93
+ return d.toLocaleString();
94
+ }
95
+ catch {
96
+ return ts;
97
+ }
98
+ };
99
+ const main = async () => {
53
100
  const args = process.argv.slice(2);
54
- if (args.length === 0 || args[0] === "--help" || args[0] === "-h" || args[0] === "help") {
101
+ // Handle standalone flags
102
+ if (args.length === 0) {
103
+ usage();
104
+ return 0;
105
+ }
106
+ const first = args[0];
107
+ if (VERSION_FLAGS.includes(first)) {
108
+ const { VERSION, PACKAGE_NAME, SKILL_NAME } = await import("./constants.js");
109
+ console.log(`${SKILL_NAME} ${VERSION} (${PACKAGE_NAME})`);
110
+ console.log("Runtime: Node.js " + process.version);
111
+ return 0;
112
+ }
113
+ if (HELP_FLAGS.includes(first)) {
55
114
  usage();
56
115
  return 0;
57
116
  }
58
117
  const [cmd, ...cmdArgs] = args;
59
118
  const pargs = parseArgs(cmdArgs);
60
- // Group args by collecting multi-word options (like required-keep-labels)
119
+ const useJson = pargs.json === "true";
120
+ const verbose = pargs.verbose === "true";
121
+ const dryRun = pargs["dry-run"] === "true";
61
122
  const grouped = {};
62
123
  for (const [k, v] of Object.entries(pargs)) {
63
124
  if (k === "required-keep-labels" || k === "required-stop-labels" || k === "labels") {
@@ -91,6 +152,18 @@ async function main() {
91
152
  break;
92
153
  }
93
154
  case "init": {
155
+ if (verbose)
156
+ console.error(`[verbose] Initializing run with goal: ${grouped.goal}`);
157
+ if (dryRun) {
158
+ console.log("[dry-run] Would initialize run with config:");
159
+ console.log(JSON.stringify({
160
+ goal: grouped.goal,
161
+ metric: grouped.metric,
162
+ direction: grouped.direction || "lower",
163
+ mode: grouped.mode || "foreground",
164
+ }, null, 2));
165
+ return 0;
166
+ }
94
167
  const { initializeRun } = await import("./run-manager.js");
95
168
  const config = {
96
169
  goal: grouped.goal,
@@ -116,7 +189,402 @@ async function main() {
116
189
  case "status": {
117
190
  const { buildSupervisorSnapshot } = await import("./run-manager.js");
118
191
  const snapshot = await buildSupervisorSnapshot(grouped.repo, grouped["results-path"], grouped["state-path"]);
119
- printJson(snapshot);
192
+ if (useJson) {
193
+ printJson(snapshot);
194
+ }
195
+ else {
196
+ const s = snapshot;
197
+ const stats = s.stats;
198
+ console.log(`Run: ${s.run_id}`);
199
+ console.log(`Status: ${s.status}`);
200
+ console.log(`Mode: ${s.mode}`);
201
+ console.log(`Goal: ${s.goal}`);
202
+ if (s.metric) {
203
+ const m = s.metric;
204
+ console.log(`Metric: ${m.name} (${m.direction})`);
205
+ console.log(` best: ${formatMetricValue(m.best)}`);
206
+ console.log(` latest: ${formatMetricValue(m.latest)}`);
207
+ }
208
+ if (stats) {
209
+ console.log(`Stats: ${stats.total_iterations} iterations, ${stats.kept} kept, ${stats.discarded} discarded`);
210
+ }
211
+ console.log(`Results: ${s.results_rows} rows`);
212
+ const lastIter = s.last_iteration;
213
+ if (lastIter && lastIter.iteration) {
214
+ console.log(`Last: iter ${lastIter.iteration} — ${lastIter.decision} (${lastIter.metric_value})`);
215
+ }
216
+ const flags = s.flags;
217
+ if (flags?.needs_human)
218
+ console.log("⚠ Needs human input");
219
+ if (flags?.stop_requested)
220
+ console.log("⏹ Stop requested");
221
+ }
222
+ break;
223
+ }
224
+ case "explain": {
225
+ const { buildSupervisorSnapshot } = await import("./run-manager.js");
226
+ const snapshot = await buildSupervisorSnapshot(grouped.repo, grouped["results-path"], grouped["state-path"]);
227
+ const s = snapshot;
228
+ const stats = s.stats;
229
+ const lastIter = s.last_iteration;
230
+ const flags = s.flags;
231
+ if (useJson) {
232
+ printJson(snapshot);
233
+ break;
234
+ }
235
+ const statusEmoji = {
236
+ running: "🔄", completed: "✅", initialized: "📋", stopping: "⏹", stopped: "⏸",
237
+ };
238
+ console.log(`${statusEmoji[s.status] ?? "⚪"} Auto Research Run: ${s.run_id}`);
239
+ console.log(` Goal: ${s.goal ?? "—"}`);
240
+ console.log(` Status: ${s.status}`);
241
+ console.log(` Mode: ${s.mode}`);
242
+ if (s.metric) {
243
+ const m = s.metric;
244
+ console.log(` Metric: ${m.name} → ${formatMetricValue(m.latest)} (best: ${formatMetricValue(m.best)}, dir: ${m.direction})`);
245
+ }
246
+ if (stats) {
247
+ console.log(` Progress: ${stats.total_iterations} iterations | ${stats.kept} kept | ${stats.discarded} discarded`);
248
+ }
249
+ if (lastIter && lastIter.iteration) {
250
+ console.log(` Last iter: #${lastIter.iteration} — ${lastIter.decision}`);
251
+ if (lastIter.change_summary)
252
+ console.log(` Change: ${lastIter.change_summary}`);
253
+ }
254
+ if (flags?.needs_human)
255
+ console.log(" ⚠ Needs human review");
256
+ if (flags?.stop_requested)
257
+ console.log(" ⏹ Stop was requested");
258
+ if (flags?.background_active)
259
+ console.log(" 📡 Background active — `autoresearch status` to check");
260
+ break;
261
+ }
262
+ case "history": {
263
+ const { resolvePath } = await import("./helpers.js");
264
+ const { RESULTS_DEFAULT } = await import("./constants.js");
265
+ const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
266
+ if (!existsSync(resultsPath)) {
267
+ console.log("No results file found.");
268
+ break;
269
+ }
270
+ const content = readFileSync(resultsPath, "utf-8");
271
+ const lines = content.trim().split("\n");
272
+ if (lines.length <= 1) {
273
+ console.log("No iteration records yet.");
274
+ break;
275
+ }
276
+ const limit = grouped.limit ? parseInt(grouped.limit) : 10;
277
+ const records = lines.slice(1).reverse().slice(0, limit);
278
+ if (useJson) {
279
+ const headers = lines[0].split("\t");
280
+ const parsed = records.map((r) => {
281
+ const cols = r.split("\t");
282
+ const obj = {};
283
+ for (let i = 0; i < headers.length; i++) {
284
+ obj[headers[i]] = cols[i] ?? "";
285
+ }
286
+ return obj;
287
+ });
288
+ printJson({ count: records.length, records: parsed });
289
+ break;
290
+ }
291
+ for (const r of records) {
292
+ const cols = r.split("\t");
293
+ if (cols.length >= 8) {
294
+ const emoji = cols[2] === "keep" ? "✓" : cols[2] === "discard" ? "✗" : "⚠";
295
+ console.log(`${emoji} #${cols[1]} ${cols[2]} (${formatMetricValue(cols[3])}) ${cols[7].substring(0, 60)}`);
296
+ }
297
+ }
298
+ console.log(`\nShowing ${Math.min(limit, records.length)} of ${lines.length - 1} records.`);
299
+ break;
300
+ }
301
+ case "config": {
302
+ const { resolvePath, readJsonFile } = await import("./helpers.js");
303
+ const { STATE_DEFAULT } = await import("./constants.js");
304
+ const statePath = resolvePath(grouped.repo, grouped["state-path"], STATE_DEFAULT);
305
+ if (!existsSync(statePath)) {
306
+ console.log("No run state found. Run 'autoresearch init' first.");
307
+ break;
308
+ }
309
+ const state = readJsonFile(statePath);
310
+ if (useJson) {
311
+ printJson({
312
+ goal: state.goal,
313
+ mode: state.mode,
314
+ metric: state.metric,
315
+ scope: state.scope,
316
+ iterations_cap: state.iterations_cap,
317
+ deadline_at: state.deadline_at,
318
+ verify: state.verify,
319
+ guard: state.guard,
320
+ subagent_pool: state.subagent_pool ? "configured" : "none",
321
+ label_requirements: state.label_requirements,
322
+ });
323
+ break;
324
+ }
325
+ console.log("Run Configuration:");
326
+ console.log(` Goal: ${state.goal ?? "—"}`);
327
+ console.log(` Mode: ${state.mode ?? "—"}`);
328
+ if (state.metric) {
329
+ const m = state.metric;
330
+ console.log(` Metric: ${m.name} (${m.direction})`);
331
+ }
332
+ console.log(` Scope: ${state.scope ?? "—"}`);
333
+ console.log(` Iter cap: ${state.iterations_cap ?? "—"}`);
334
+ console.log(` Deadline: ${state.deadline_at ? formatTimestamp(state.deadline_at) : "—"}`);
335
+ console.log(` Verify: ${state.verify ?? "—"}`);
336
+ console.log(` Guard: ${state.guard ?? "—"}`);
337
+ console.log(` Pool: ${state.subagent_pool ? "configured" : "none"}`);
338
+ break;
339
+ }
340
+ case "summary": {
341
+ const { resolvePath } = await import("./helpers.js");
342
+ const { RESULTS_DEFAULT } = await import("./constants.js");
343
+ const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
344
+ if (!existsSync(resultsPath)) {
345
+ console.log("No results file found. No runs completed yet.");
346
+ break;
347
+ }
348
+ const content = readFileSync(resultsPath, "utf-8");
349
+ const lines = content.trim().split("\n");
350
+ const records = lines.slice(1).filter(Boolean);
351
+ let totalKept = 0, totalDiscarded = 0, totalNeedsHuman = 0;
352
+ const runIds = new Set();
353
+ for (const r of records) {
354
+ const cols = r.split("\t");
355
+ const dec = cols[2];
356
+ if (dec === "keep")
357
+ totalKept++;
358
+ else if (dec === "discard")
359
+ totalDiscarded++;
360
+ else if (dec === "needs_human")
361
+ totalNeedsHuman++;
362
+ const iterTags = cols[1].split(":");
363
+ if (iterTags.length >= 2)
364
+ runIds.add(iterTags[0]);
365
+ }
366
+ if (useJson) {
367
+ printJson({
368
+ total_records: records.length,
369
+ total_kept: totalKept,
370
+ total_discarded: totalDiscarded,
371
+ total_needs_human: totalNeedsHuman,
372
+ keep_rate: records.length > 0 ? (totalKept / records.length * 100).toFixed(1) + "%" : "0%",
373
+ distinct_run_ids: Array.from(runIds),
374
+ });
375
+ break;
376
+ }
377
+ console.log("Auto Research Summary");
378
+ console.log(` Total iterations: ${records.length}`);
379
+ console.log(` Kept: ${totalKept}`);
380
+ console.log(` Discarded: ${totalDiscarded}`);
381
+ console.log(` Needs human: ${totalNeedsHuman}`);
382
+ console.log(` Keep rate: ${records.length > 0 ? (totalKept / records.length * 100).toFixed(1) : 0}%`);
383
+ console.log(` Distinct runs: ${runIds.size}`);
384
+ break;
385
+ }
386
+ case "validate": {
387
+ const { normalizeDirection, normalizeMode, inferVerifyCommand } = await import("./helpers.js");
388
+ const errors = [];
389
+ if (!grouped.goal)
390
+ errors.push("Missing required: --goal");
391
+ if (!grouped.metric)
392
+ errors.push("Missing required: --metric");
393
+ try {
394
+ if (grouped.direction)
395
+ normalizeDirection(grouped.direction);
396
+ }
397
+ catch (e) {
398
+ errors.push(`Invalid direction: ${e.message}`);
399
+ }
400
+ try {
401
+ if (grouped.mode)
402
+ normalizeMode(grouped.mode);
403
+ }
404
+ catch (e) {
405
+ errors.push(`Invalid mode: ${e.message}`);
406
+ }
407
+ const verify = grouped.verify || inferVerifyCommand(grouped.repo);
408
+ if (verify === "<set verify command>") {
409
+ errors.push("Cannot infer verify command. Provide --verify explicitly.");
410
+ }
411
+ if (useJson) {
412
+ printJson({ valid: errors.length === 0, errors });
413
+ return errors.length > 0 ? 1 : 0;
414
+ }
415
+ if (errors.length === 0) {
416
+ console.log("✓ Configuration is valid");
417
+ console.log(` Goal: ${grouped.goal}`);
418
+ console.log(` Metric: ${grouped.metric} (${grouped.direction || "lower"})`);
419
+ console.log(` Verify: ${verify}`);
420
+ console.log(` Mode: ${grouped.mode || "foreground"}`);
421
+ }
422
+ else {
423
+ console.error("✗ Configuration errors:");
424
+ for (const err of errors) {
425
+ console.error(` - ${err}`);
426
+ }
427
+ return 1;
428
+ }
429
+ break;
430
+ }
431
+ case "report": {
432
+ const { resolvePath, readJsonFile } = await import("./helpers.js");
433
+ const { STATE_DEFAULT, RESULTS_DEFAULT } = await import("./constants.js");
434
+ const statePath = resolvePath(grouped.repo, grouped["state-path"], STATE_DEFAULT);
435
+ const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
436
+ if (!existsSync(statePath)) {
437
+ console.log("No run state found. Run 'autoresearch init' first.");
438
+ break;
439
+ }
440
+ const state = readJsonFile(statePath);
441
+ let results = [];
442
+ if (existsSync(resultsPath)) {
443
+ const content = readFileSync(resultsPath, "utf-8");
444
+ results = content.trim().split("\n").slice(1).filter(Boolean);
445
+ }
446
+ if (useJson) {
447
+ printJson({ state, results_count: results.length });
448
+ break;
449
+ }
450
+ console.log(`# Auto Research Report`);
451
+ console.log(`\n**Run:** ${state.run_id}`);
452
+ console.log(`**Goal:** ${state.goal}`);
453
+ console.log(`**Status:** ${state.status}`);
454
+ console.log(`**Mode:** ${state.mode}`);
455
+ if (state.metric) {
456
+ const m = state.metric;
457
+ console.log(`**Metric:** ${m.name} (${m.direction})`);
458
+ console.log(`**Best:** ${m.best} | **Latest:** ${m.latest}`);
459
+ }
460
+ if (state.stats) {
461
+ const s = state.stats;
462
+ console.log(`\n## Stats`);
463
+ console.log(`- Iterations: ${s.total_iterations}`);
464
+ console.log(`- Kept: ${s.kept}`);
465
+ console.log(`- Discarded: ${s.discarded}`);
466
+ console.log(`- Needs human: ${s.needs_human}`);
467
+ }
468
+ if (results.length > 0) {
469
+ console.log(`\n## Iterations`);
470
+ for (const r of results) {
471
+ const cols = r.split("\t");
472
+ if (cols.length >= 8) {
473
+ console.log(`- ${cols[1]}: ${cols[2]} (${cols[3]}) — ${cols[7].substring(0, 60)}`);
474
+ }
475
+ }
476
+ }
477
+ break;
478
+ }
479
+ case "suggest": {
480
+ const { resolvePath } = await import("./helpers.js");
481
+ const { MEMORY_DEFAULT } = await import("./constants.js");
482
+ const memoryPath = resolvePath(grouped.repo, grouped["memory-path"], MEMORY_DEFAULT);
483
+ if (!existsSync(memoryPath)) {
484
+ console.log("No memory file found. Run a self-improvement cycle first.");
485
+ break;
486
+ }
487
+ const memory = readFileSync(memoryPath, "utf-8");
488
+ const patterns = memory.match(/### Pattern: [^\n]+/g) ?? [];
489
+ if (useJson) {
490
+ printJson({ patterns_found: patterns.length, suggestions: patterns.map((p) => p.replace("### Pattern: ", "")) });
491
+ break;
492
+ }
493
+ console.log("Memory Patterns — candidate next goals:");
494
+ for (const p of patterns) {
495
+ console.log(` → ${p.replace("### Pattern: ", "")}`);
496
+ }
497
+ console.log(`\n${patterns.length} patterns available. Use 'autoresearch init --goal "..."' to start a new run.`);
498
+ break;
499
+ }
500
+ case "export": {
501
+ const { resolvePath } = await import("./helpers.js");
502
+ const { RESULTS_DEFAULT, STATE_DEFAULT } = await import("./constants.js");
503
+ const resultsPath = resolvePath(grouped.repo, grouped["results-path"], RESULTS_DEFAULT);
504
+ const statePath = resolvePath(grouped.repo, grouped["state-path"], STATE_DEFAULT);
505
+ const format = grouped.format || "json";
506
+ if (!existsSync(resultsPath) || !existsSync(statePath)) {
507
+ console.error("No run data found. Run 'autoresearch init' first.");
508
+ return 1;
509
+ }
510
+ const results = readFileSync(resultsPath, "utf-8");
511
+ const state = readFileSync(statePath, "utf-8");
512
+ const lines = results.trim().split("\n");
513
+ const headers = lines[0].split("\t");
514
+ const records = lines.slice(1).filter(Boolean).map((r) => {
515
+ const cols = r.split("\t");
516
+ const obj = {};
517
+ for (let i = 0; i < headers.length; i++) {
518
+ obj[headers[i]] = cols[i] ?? "";
519
+ }
520
+ return obj;
521
+ });
522
+ const exportData = {
523
+ exported_at: new Date().toISOString(),
524
+ state: JSON.parse(state),
525
+ iterations: records,
526
+ summary: {
527
+ total: records.length,
528
+ kept: records.filter((r) => r.decision === "keep").length,
529
+ discarded: records.filter((r) => r.decision === "discard").length,
530
+ },
531
+ };
532
+ if (format === "json") {
533
+ console.log(JSON.stringify(exportData, null, 2));
534
+ }
535
+ else if (format === "md" || format === "markdown") {
536
+ console.log(`# Auto Research Export`);
537
+ console.log(`\n**Run:** ${exportData.state.run_id}`);
538
+ console.log(`**Goal:** ${exportData.state.goal}`);
539
+ console.log(`**Exported:** ${exportData.exported_at}`);
540
+ console.log(`\n## Summary`);
541
+ console.log(`- Total iterations: ${exportData.summary.total}`);
542
+ console.log(`- Kept: ${exportData.summary.kept}`);
543
+ console.log(`- Discarded: ${exportData.summary.discarded}`);
544
+ console.log(`\n## Iterations`);
545
+ console.log(`| # | Decision | Metric | Summary |`);
546
+ console.log(`|---|----------|--------|---------|`);
547
+ for (const r of records) {
548
+ console.log(`| ${r.iteration} | ${r.decision} | ${r.metric_value || "—"} | ${r.change_summary?.substring(0, 50) || "—"} |`);
549
+ }
550
+ }
551
+ else {
552
+ console.error(`Unknown format: ${format}. Supported: json, md`);
553
+ return 1;
554
+ }
555
+ break;
556
+ }
557
+ case "completion": {
558
+ const shell = grouped.shell || "bash";
559
+ const commands = ["init", "wizard", "status", "explain", "history", "config", "summary", "suggest", "launch", "complete", "stop", "resume", "record", "doctor", "export", "completion", "help"];
560
+ const options = ["--repo", "--goal", "--metric", "--direction", "--verify", "--guard", "--mode", "--scope", "--iterations", "--duration", "--json", "--results-path", "--state-path", "--fresh-start", "--memory-path", "--format", "--shell"];
561
+ if (shell === "bash" || shell === "zsh") {
562
+ console.log(`# Auto Research CLI completion for ${shell}`);
563
+ console.log(`_autoresearch() {`);
564
+ console.log(` local cur="\${COMP_WORDS[COMP_CWORD]}"`);
565
+ console.log(` local cmds="${commands.join(" ")}"`);
566
+ console.log(` local opts="${options.join(" ")}"`);
567
+ console.log(` if [ $COMP_CWORD -eq 1 ]; then`);
568
+ console.log(` COMPREPLY=($(compgen -W "$cmds" -- "$cur"))`);
569
+ console.log(` else`);
570
+ console.log(` COMPREPLY=($(compgen -W "$opts" -- "$cur"))`);
571
+ console.log(` fi`);
572
+ console.log(`}`);
573
+ console.log(`complete -F _autoresearch autoresearch`);
574
+ }
575
+ else if (shell === "fish") {
576
+ console.log(`# Auto Research CLI completion for fish`);
577
+ for (const cmd of commands) {
578
+ console.log(`complete -c autoresearch -n '__fish_use_subcommand' -a '${cmd}'`);
579
+ }
580
+ for (const opt of options) {
581
+ console.log(`complete -c autoresearch -n '__fish_seen_subcommand_from ${commands.join(" ")}' -l ${opt.slice(2)}`);
582
+ }
583
+ }
584
+ else {
585
+ console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
586
+ return 1;
587
+ }
120
588
  break;
121
589
  }
122
590
  case "launch": {
@@ -178,15 +646,40 @@ async function main() {
178
646
  const { VERSION, PACKAGE_NAME, SKILL_NAME } = await import("./constants.js");
179
647
  console.log(`${SKILL_NAME} ${VERSION} (${PACKAGE_NAME})`);
180
648
  console.log("Runtime: Node.js " + process.version);
181
- console.log("Commands: OK");
182
- console.log("Skills: OK");
183
- console.log("Hooks: OK");
649
+ const base = resolveRepo(grouped.repo);
650
+ const checks = [];
651
+ const cmdDir = resolve(base, "commands");
652
+ const skillsDir = resolve(base, "skills/autoresearch");
653
+ const hooksDir = resolve(base, "hooks");
654
+ const cmdFiles = existsSync(cmdDir) ? readdirSync(cmdDir).filter((f) => f.endsWith(".md")) : [];
655
+ const skillFiles = existsSync(skillsDir) ? readdirSync(skillsDir) : [];
656
+ const hookFiles = existsSync(hooksDir) ? readdirSync(hooksDir).filter((f) => f.endsWith(".sh")) : [];
657
+ checks.push({ name: "commands", ok: cmdFiles.length > 0, detail: `${cmdFiles.length} command files` });
658
+ checks.push({ name: "skills", ok: skillFiles.length > 0, detail: `${skillFiles.length} skill files` });
659
+ checks.push({ name: "hooks", ok: hookFiles.length > 0, detail: `${hookFiles.length} hook scripts` });
660
+ checks.push({ name: "dist", ok: existsSync(resolve(base, "dist/cli.js")), detail: "dist/cli.js" });
661
+ checks.push({ name: "plugin", ok: existsSync(resolve(base, ".opencode-plugin/plugin.json")), detail: "plugin manifest" });
662
+ checks.push({ name: "VERSION", ok: existsSync(resolve(base, "VERSION")), detail: "version marker" });
663
+ let maxNameLen = 0;
664
+ for (const c of checks)
665
+ maxNameLen = Math.max(maxNameLen, c.name.length);
666
+ for (const c of checks) {
667
+ const padded = c.name.padEnd(maxNameLen + 2);
668
+ console.log(` ${c.ok ? "✓" : "✗"} ${padded}${c.detail ?? (c.ok ? "present" : "missing")}`);
669
+ }
670
+ const failed = checks.filter((c) => !c.ok).length;
671
+ if (failed > 0) {
672
+ console.error(`\n${failed} check(s) failed. Reinstall with 'npm install -g opencode-autoresearch'.`);
673
+ return 1;
674
+ }
675
+ console.log(`\nAll ${checks.length} checks passed.`);
184
676
  break;
185
677
  }
186
- default:
678
+ default: {
187
679
  console.error(`Unknown command: ${cmd}`);
188
680
  console.error("Run 'autoresearch --help' for usage.");
189
681
  return 1;
682
+ }
190
683
  }
191
684
  }
192
685
  catch (exc) {
@@ -194,7 +687,7 @@ async function main() {
194
687
  return 2;
195
688
  }
196
689
  return 0;
197
- }
690
+ };
198
691
  main().then((code) => process.exit(code)).catch((err) => {
199
692
  console.error(err);
200
693
  process.exit(1);