opencode-autoresearch 3.1.0 → 3.3.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 (78) hide show
  1. package/.opencode-plugin/plugin.json +1 -1
  2. package/README.md +246 -30
  3. package/VERSION +1 -0
  4. package/dist/cli.js +687 -31
  5. package/dist/cli.js.map +1 -1
  6. package/dist/constants.d.ts +3 -7
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +3 -7
  9. package/dist/constants.js.map +1 -1
  10. package/dist/helpers.d.ts +7 -3
  11. package/dist/helpers.d.ts.map +1 -1
  12. package/dist/helpers.js +100 -20
  13. package/dist/helpers.js.map +1 -1
  14. package/dist/index.d.ts +4 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/run-manager.d.ts +2 -2
  19. package/dist/run-manager.d.ts.map +1 -1
  20. package/dist/run-manager.js +24 -22
  21. package/dist/run-manager.js.map +1 -1
  22. package/dist/subagent-pool.d.ts +6 -0
  23. package/dist/subagent-pool.d.ts.map +1 -1
  24. package/dist/subagent-pool.js +12 -2
  25. package/dist/subagent-pool.js.map +1 -1
  26. package/dist/types.d.ts +15 -38
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/wizard.d.ts.map +1 -1
  29. package/dist/wizard.js +3 -2
  30. package/dist/wizard.js.map +1 -1
  31. package/docs/ARCHITECTURE.md +135 -28
  32. package/docs/QUICKSTART.md +103 -0
  33. package/docs/RELEASE.md +54 -25
  34. package/docs/superpowers/plans/2026-05-03-install-release-security.md +855 -0
  35. package/docs/superpowers/specs/2026-05-03-install-release-security-design.md +80 -0
  36. package/hooks/init.sh +6 -2
  37. package/hooks/status.sh +4 -3
  38. package/hooks/stop.sh +10 -6
  39. package/hooks/verify-package.sh +78 -0
  40. package/package.json +35 -14
  41. package/plugins/autoresearch.ts +13 -0
  42. package/skills/autoresearch/SKILL.md +29 -4
  43. package/skills/autoresearch/references/core-principles.md +3 -3
  44. package/skills/autoresearch/references/interaction-wizard.md +1 -1
  45. package/skills/autoresearch/references/loop-workflow.md +4 -4
  46. package/skills/autoresearch/references/plan-workflow.md +3 -3
  47. package/skills/autoresearch/references/results-logging.md +2 -2
  48. package/skills/autoresearch/references/self-improve-loop.md +255 -0
  49. package/skills/autoresearch/references/state-management.md +3 -3
  50. package/skills/autoresearch/references/subagent-orchestration.md +1 -1
  51. package/dist/complete.d.ts +0 -2
  52. package/dist/complete.d.ts.map +0 -1
  53. package/dist/complete.js +0 -36
  54. package/dist/complete.js.map +0 -1
  55. package/dist/init.d.ts +0 -2
  56. package/dist/init.d.ts.map +0 -1
  57. package/dist/init.js +0 -48
  58. package/dist/init.js.map +0 -1
  59. package/dist/launch.d.ts +0 -2
  60. package/dist/launch.d.ts.map +0 -1
  61. package/dist/launch.js +0 -51
  62. package/dist/launch.js.map +0 -1
  63. package/dist/record.d.ts +0 -2
  64. package/dist/record.d.ts.map +0 -1
  65. package/dist/record.js +0 -26
  66. package/dist/record.js.map +0 -1
  67. package/dist/resume.d.ts +0 -2
  68. package/dist/resume.d.ts.map +0 -1
  69. package/dist/resume.js +0 -20
  70. package/dist/resume.js.map +0 -1
  71. package/dist/status.d.ts +0 -2
  72. package/dist/status.d.ts.map +0 -1
  73. package/dist/status.js +0 -20
  74. package/dist/status.js.map +0 -1
  75. package/dist/stop.d.ts +0 -2
  76. package/dist/stop.d.ts.map +0 -1
  77. package/dist/stop.js +0 -20
  78. package/dist/stop.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,37 +1,693 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from "child_process";
3
- import { fileURLToPath } from "url";
4
- import { dirname } from "path";
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- function runCommand(script, args) {
7
- return new Promise((resolve) => {
8
- const child = spawn("node", [script, ...args], { stdio: "inherit" });
9
- child.on("close", (code) => resolve(code ?? 0));
10
- });
11
- }
12
- async function main() {
2
+ import { existsSync, readFileSync, readdirSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { printJson, resolveRepo, parseRunState, parsePositiveInt } from "./helpers.js";
5
+ const VERSION_FLAGS = ["--version", "-v"];
6
+ const HELP_FLAGS = ["--help", "-h", "help"];
7
+ const usage = () => {
8
+ console.error("Usage: autoresearch <command> [options]");
9
+ console.error("");
10
+ console.error("Commands:");
11
+ console.error(" init Initialize a run");
12
+ console.error(" wizard Generate a setup summary");
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");
19
+ console.error(" launch Launch a background run");
20
+ console.error(" complete Mark a run complete");
21
+ console.error(" stop Request a background run stop");
22
+ console.error(" resume Resume a background run");
23
+ console.error(" record Record an experiment result");
24
+ console.error(" doctor Verify package installation and version");
25
+ console.error(" help Show this help");
26
+ console.error("");
27
+ console.error("Options:");
28
+ console.error(" --repo Repository root (default: current directory)");
29
+ console.error(" --goal Desired run outcome");
30
+ console.error(" --metric Metric name to track");
31
+ console.error(" --direction lower or higher");
32
+ console.error(" --verify Mechanical verification command");
33
+ console.error(" --guard Guard command for regression catch");
34
+ console.error(" --mode foreground or background");
35
+ console.error(" --scope In-scope files or subsystem");
36
+ console.error(" --iterations Iteration cap");
37
+ console.error(" --duration Wall-clock cap (e.g., 5h or 300m)");
38
+ console.error(" --json Output raw JSON (default: human-readable)");
39
+ console.error(" --results-path Custom results TSV path");
40
+ console.error(" --state-path Custom state JSON path");
41
+ console.error(" --fresh-start Archive previous artifacts before starting");
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("");
49
+ console.error("Examples:");
50
+ console.error(" autoresearch wizard --goal \"optimize response time\"");
51
+ console.error(" autoresearch init --goal \"reduce errors\" --metric errors --direction lower --verify \"npm test\"");
52
+ console.error(" autoresearch status");
53
+ console.error(" autoresearch explain");
54
+ console.error(" autoresearch history");
55
+ };
56
+ const parseArgs = (args) => {
57
+ const result = {};
58
+ for (let i = 0; i < args.length; i++) {
59
+ if (args[i].startsWith("--")) {
60
+ const key = args[i].slice(2);
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("-")) {
76
+ result[key] = args[++i];
77
+ }
78
+ else {
79
+ result[key] = "true";
80
+ }
81
+ }
82
+ }
83
+ return result;
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 () => {
13
100
  const args = process.argv.slice(2);
101
+ // Handle standalone flags
14
102
  if (args.length === 0) {
15
- console.error("Usage: autoresearch <command> [options]");
16
- console.error("Commands: init, wizard, status, launch, complete, stop, resume, record");
17
- return 1;
18
- }
19
- const [cmd, ...rest] = args;
20
- const distDir = __dirname + "/..";
21
- switch (cmd) {
22
- case "init": return runCommand(distDir + "/dist/init.js", rest);
23
- case "wizard": return runCommand(distDir + "/dist/wizard.js", rest);
24
- case "status": return runCommand(distDir + "/dist/status.js", rest);
25
- case "launch": return runCommand(distDir + "/dist/launch.js", rest);
26
- case "complete": return runCommand(distDir + "/dist/complete.js", rest);
27
- case "stop": return runCommand(distDir + "/dist/stop.js", rest);
28
- case "resume": return runCommand(distDir + "/dist/resume.js", rest);
29
- case "record": return runCommand(distDir + "/dist/record.js", rest);
30
- default:
31
- console.error(`Unknown command: ${cmd}`);
32
- return 1;
33
- }
34
- }
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)) {
114
+ usage();
115
+ return 0;
116
+ }
117
+ const [cmd, ...cmdArgs] = args;
118
+ const pargs = parseArgs(cmdArgs);
119
+ const useJson = pargs.json === "true";
120
+ const verbose = pargs.verbose === "true";
121
+ const dryRun = pargs["dry-run"] === "true";
122
+ const grouped = {};
123
+ for (const [k, v] of Object.entries(pargs)) {
124
+ if (k === "required-keep-labels" || k === "required-stop-labels" || k === "labels") {
125
+ grouped[k] = v.split(/\s+/).filter(Boolean);
126
+ }
127
+ else {
128
+ grouped[k] = v;
129
+ }
130
+ }
131
+ try {
132
+ switch (cmd) {
133
+ case "wizard": {
134
+ const { buildSetupSummary } = await import("./wizard.js");
135
+ const config = {
136
+ goal: grouped.goal,
137
+ scope: grouped.scope,
138
+ metric: grouped.metric,
139
+ direction: grouped.direction,
140
+ verify: grouped.verify,
141
+ guard: grouped.guard,
142
+ mode: grouped.mode,
143
+ iterations: parsePositiveInt(grouped.iterations, "iterations"),
144
+ duration: grouped.duration,
145
+ memory_path: grouped["memory-path"],
146
+ required_keep_labels: grouped["required-keep-labels"],
147
+ required_stop_labels: grouped["required-stop-labels"],
148
+ stop_condition: grouped["stop-condition"],
149
+ rollback_strategy: grouped["rollback-strategy"],
150
+ };
151
+ printJson(buildSetupSummary(grouped.repo, config));
152
+ break;
153
+ }
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
+ }
167
+ const { initializeRun } = await import("./run-manager.js");
168
+ const config = {
169
+ goal: grouped.goal,
170
+ metric: grouped.metric,
171
+ direction: grouped.direction || "lower",
172
+ verify: grouped.verify,
173
+ mode: grouped.mode || "foreground",
174
+ scope: grouped.scope,
175
+ guard: grouped.guard,
176
+ iterations: parsePositiveInt(grouped.iterations, "iterations"),
177
+ duration: grouped.duration,
178
+ memory_path: grouped["memory-path"],
179
+ required_keep_labels: grouped["required-keep-labels"],
180
+ required_stop_labels: grouped["required-stop-labels"],
181
+ run_tag: grouped["run-tag"],
182
+ stop_condition: grouped["stop-condition"],
183
+ baseline: grouped.baseline,
184
+ };
185
+ const state = await initializeRun(grouped.repo, grouped["results-path"], grouped["state-path"], config, grouped["fresh-start"] === "true");
186
+ printJson(state);
187
+ break;
188
+ }
189
+ case "status": {
190
+ const { buildSupervisorSnapshot } = await import("./run-manager.js");
191
+ const snapshot = await buildSupervisorSnapshot(grouped.repo, grouped["results-path"], grouped["state-path"]);
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 = parsePositiveInt(grouped.limit, "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 = parseRunState(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 = parseRunState(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
+ }
588
+ break;
589
+ }
590
+ case "launch": {
591
+ const { resolvePath } = await import("./helpers.js");
592
+ const { initializeRun } = await import("./run-manager.js");
593
+ const { writeFileSync } = await import("fs");
594
+ const { LAUNCH_DEFAULT } = await import("./constants.js");
595
+ const config = {
596
+ goal: grouped.goal,
597
+ metric: grouped.metric,
598
+ direction: grouped.direction || "lower",
599
+ verify: grouped.verify,
600
+ mode: "background",
601
+ scope: grouped.scope,
602
+ guard: grouped.guard,
603
+ iterations: parsePositiveInt(grouped.iterations, "iterations"),
604
+ duration: grouped.duration,
605
+ memory_path: grouped["memory-path"],
606
+ required_keep_labels: grouped["required-keep-labels"],
607
+ required_stop_labels: grouped["required-stop-labels"],
608
+ run_tag: grouped["run-tag"],
609
+ stop_condition: grouped["stop-condition"],
610
+ baseline: grouped.baseline,
611
+ };
612
+ const state = await initializeRun(grouped.repo, grouped["results-path"], grouped["state-path"], config, grouped["fresh-start"] === "true");
613
+ const launchPath = resolvePath(grouped.repo, grouped["launch-path"], LAUNCH_DEFAULT);
614
+ writeFileSync(launchPath, JSON.stringify({ run_id: state.run_id, goal: state.goal, mode: "background" }, null, 2) + "\n", "utf-8");
615
+ printJson({ status: "launched", run_id: state.run_id, launch_path: launchPath });
616
+ break;
617
+ }
618
+ case "complete": {
619
+ const { completeRun } = await import("./run-manager.js");
620
+ const state = await completeRun(grouped.repo, grouped["state-path"]);
621
+ printJson({ status: "completed", run_id: state.run_id });
622
+ break;
623
+ }
624
+ case "stop": {
625
+ const { setStopRequested } = await import("./run-manager.js");
626
+ const state = await setStopRequested(grouped.repo, grouped["state-path"]);
627
+ printJson({ status: "stop_requested", run_id: state.run_id });
628
+ break;
629
+ }
630
+ case "resume": {
631
+ const { resumeBackgroundRun } = await import("./run-manager.js");
632
+ const state = await resumeBackgroundRun(grouped.repo, grouped["state-path"]);
633
+ printJson({ status: "resumed", run_id: state.run_id });
634
+ break;
635
+ }
636
+ case "record": {
637
+ const { appendIteration } = await import("./run-manager.js");
638
+ const { normalizeResultStatus } = await import("./helpers.js");
639
+ const vs = grouped["verify-status"] || "pass";
640
+ const gs = grouped["guard-status"] || "skip";
641
+ const state = await appendIteration(grouped.repo, grouped["results-path"], grouped["state-path"], grouped.decision, grouped["metric-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, parsePositiveInt(grouped.iteration, "iteration"));
642
+ printJson(state);
643
+ break;
644
+ }
645
+ case "doctor": {
646
+ const { VERSION, PACKAGE_NAME, SKILL_NAME } = await import("./constants.js");
647
+ console.log(`${SKILL_NAME} ${VERSION} (${PACKAGE_NAME})`);
648
+ console.log("Runtime: Node.js " + process.version);
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.`);
676
+ break;
677
+ }
678
+ default: {
679
+ console.error(`Unknown command: ${cmd}`);
680
+ console.error("Run 'autoresearch --help' for usage.");
681
+ return 1;
682
+ }
683
+ }
684
+ }
685
+ catch (exc) {
686
+ console.error(exc.message);
687
+ return 2;
688
+ }
689
+ return 0;
690
+ };
35
691
  main().then((code) => process.exit(code)).catch((err) => {
36
692
  console.error(err);
37
693
  process.exit(1);