opencode-autoresearch 3.13.1 → 3.14.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.
- package/.opencode-plugin/plugin.json +1 -1
- package/INSTALL.md +52 -2
- package/README.md +5 -3
- package/VERSION +1 -1
- package/dist/cli.js +543 -44
- package/dist/cli.js.map +1 -1
- package/dist/compaction.d.ts +17 -0
- package/dist/compaction.d.ts.map +1 -0
- package/dist/compaction.js +175 -0
- package/dist/compaction.js.map +1 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/error-categories.d.ts +12 -0
- package/dist/error-categories.d.ts.map +1 -0
- package/dist/error-categories.js +137 -0
- package/dist/error-categories.js.map +1 -0
- package/dist/evidence.d.ts +24 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +82 -0
- package/dist/evidence.js.map +1 -0
- package/dist/helpers.d.ts +13 -0
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +40 -0
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/leaderboard.d.ts +27 -0
- package/dist/leaderboard.d.ts.map +1 -0
- package/dist/leaderboard.js +195 -0
- package/dist/leaderboard.js.map +1 -0
- package/dist/memory-manager.d.ts.map +1 -1
- package/dist/memory-manager.js +8 -2
- package/dist/memory-manager.js.map +1 -1
- package/dist/metric-comparator.d.ts +15 -0
- package/dist/metric-comparator.d.ts.map +1 -0
- package/dist/metric-comparator.js +58 -0
- package/dist/metric-comparator.js.map +1 -0
- package/dist/run-manager.d.ts.map +1 -1
- package/dist/run-manager.js +18 -11
- package/dist/run-manager.js.map +1 -1
- package/dist/serialize.d.ts +8 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +50 -0
- package/dist/serialize.js.map +1 -0
- package/dist/strategy-pack.d.ts +31 -0
- package/dist/strategy-pack.d.ts.map +1 -0
- package/dist/strategy-pack.js +90 -0
- package/dist/strategy-pack.js.map +1 -0
- package/dist/subagent-pool.d.ts.map +1 -1
- package/dist/subagent-pool.js +22 -14
- package/dist/subagent-pool.js.map +1 -1
- package/dist/task-queue.d.ts +36 -0
- package/dist/task-queue.d.ts.map +1 -0
- package/dist/task-queue.js +65 -0
- package/dist/task-queue.js.map +1 -0
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/verifier-parser.d.ts.map +1 -1
- package/dist/verifier-parser.js +3 -1
- package/dist/verifier-parser.js.map +1 -1
- package/dist/whats-new.d.ts +12 -0
- package/dist/whats-new.d.ts.map +1 -0
- package/dist/whats-new.js +106 -0
- package/dist/whats-new.js.map +1 -0
- package/dist/worker.d.ts +11 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +75 -0
- package/dist/worker.js.map +1 -0
- 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
|
|
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
|
-
|
|
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
|
-
|
|
448
|
+
printJsonEnvelope("status", snapshot);
|
|
354
449
|
}
|
|
355
450
|
else {
|
|
356
451
|
const s = snapshot;
|
|
@@ -397,7 +492,7 @@ const main = async () => {
|
|
|
397
492
|
const lastIter = s.last_iteration;
|
|
398
493
|
const flags = s.flags;
|
|
399
494
|
if (useJson) {
|
|
400
|
-
|
|
495
|
+
printJsonEnvelope("explain", snapshot);
|
|
401
496
|
break;
|
|
402
497
|
}
|
|
403
498
|
const statusEmoji = {
|
|
@@ -466,7 +561,7 @@ const main = async () => {
|
|
|
466
561
|
}
|
|
467
562
|
return obj;
|
|
468
563
|
});
|
|
469
|
-
|
|
564
|
+
printJsonEnvelope("history", { count: records.length, records: parsed });
|
|
470
565
|
break;
|
|
471
566
|
}
|
|
472
567
|
for (const r of records) {
|
|
@@ -493,7 +588,7 @@ const main = async () => {
|
|
|
493
588
|
const limit = parsePositiveInt(grouped.limit, "limit") ?? 10;
|
|
494
589
|
const showTopComponents = grouped["top-components"] === "true";
|
|
495
590
|
if (showTopComponents) {
|
|
496
|
-
const allLines =
|
|
591
|
+
const allLines = readScoreHistoryFile(scoreHistoryPath)
|
|
497
592
|
.split("\n")
|
|
498
593
|
.map((l) => l.trim())
|
|
499
594
|
.filter(Boolean);
|
|
@@ -512,7 +607,7 @@ const main = async () => {
|
|
|
512
607
|
const { rankComponents } = await import("./score-parser.js");
|
|
513
608
|
const ranking = rankComponents(allParsed);
|
|
514
609
|
if (useJson) {
|
|
515
|
-
|
|
610
|
+
printJsonEnvelope("scores", { count: allParsed.length, scores: allParsed.slice(-limit), ranking });
|
|
516
611
|
break;
|
|
517
612
|
}
|
|
518
613
|
console.log("Component Rankings:");
|
|
@@ -548,7 +643,7 @@ const main = async () => {
|
|
|
548
643
|
return null;
|
|
549
644
|
}
|
|
550
645
|
}).filter(Boolean);
|
|
551
|
-
|
|
646
|
+
printJsonEnvelope("scores", { count: parsed.length, scores: parsed });
|
|
552
647
|
break;
|
|
553
648
|
}
|
|
554
649
|
console.log("Score History (latest " + Math.min(limit, records.length) + "):");
|
|
@@ -630,20 +725,11 @@ const main = async () => {
|
|
|
630
725
|
break;
|
|
631
726
|
}
|
|
632
727
|
case "score": {
|
|
633
|
-
const {
|
|
634
|
-
const { STATE_DEFAULT } = await import("./constants.js");
|
|
728
|
+
const { AutoresearchError: AErr } = await import("./helpers.js");
|
|
635
729
|
const { parseScoreOutput } = await import("./score-parser.js");
|
|
636
|
-
|
|
637
|
-
let scorerCmd = grouped.scorer;
|
|
638
|
-
if (!scorerCmd) {
|
|
639
|
-
const statePath = resolvePath(grouped.repo, grouped["state-path"], STATE_DEFAULT);
|
|
640
|
-
if (existsSync(statePath)) {
|
|
641
|
-
const state = parseRunState(readJsonFile(statePath));
|
|
642
|
-
scorerCmd = state.scorer;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
730
|
+
const scorerCmd = grouped.scorer;
|
|
645
731
|
if (!scorerCmd) {
|
|
646
|
-
throw new AErr("No scorer
|
|
732
|
+
throw new AErr("No scorer provided. Pass --scorer <cmd> to run a scorer explicitly.");
|
|
647
733
|
}
|
|
648
734
|
const repoBase = resolveRepo(grouped.repo);
|
|
649
735
|
let rawOutput;
|
|
@@ -660,7 +746,7 @@ const main = async () => {
|
|
|
660
746
|
const normalized = scored.score / scored.max;
|
|
661
747
|
const percent = (normalized * 100).toFixed(1) + "%";
|
|
662
748
|
if (useJson) {
|
|
663
|
-
|
|
749
|
+
printJsonEnvelope("score", {
|
|
664
750
|
score: scored.score,
|
|
665
751
|
max: scored.max,
|
|
666
752
|
normalized,
|
|
@@ -696,7 +782,7 @@ const main = async () => {
|
|
|
696
782
|
}
|
|
697
783
|
const state = parseRunState(readJsonFile(statePath));
|
|
698
784
|
if (useJson) {
|
|
699
|
-
|
|
785
|
+
printJsonEnvelope("config", {
|
|
700
786
|
goal: state.goal,
|
|
701
787
|
mode: state.mode,
|
|
702
788
|
metric: state.metric,
|
|
@@ -728,6 +814,148 @@ const main = async () => {
|
|
|
728
814
|
console.log(` Pool: ${state.subagent_pool ? "configured" : "none"}`);
|
|
729
815
|
break;
|
|
730
816
|
}
|
|
817
|
+
case "contract": {
|
|
818
|
+
const schemas = {
|
|
819
|
+
schema_version: "1.0.0",
|
|
820
|
+
description: "Auto Research runtime contract schemas",
|
|
821
|
+
state: {
|
|
822
|
+
type: "object",
|
|
823
|
+
required: ["schema_version", "run_id", "created_at", "updated_at", "status", "mode", "operating_mode", "goal", "scope", "metric", "verify", "label_requirements", "artifact_paths", "stats", "flags"],
|
|
824
|
+
properties: {
|
|
825
|
+
schema_version: { type: "number", description: "State schema version" },
|
|
826
|
+
run_id: { type: "string", description: "Unique run identifier" },
|
|
827
|
+
created_at: { type: "string", format: "date-time", description: "Run creation timestamp" },
|
|
828
|
+
updated_at: { type: "string", format: "date-time", description: "Last update timestamp" },
|
|
829
|
+
status: { type: "string", enum: ["initialized", "running", "stopping", "stopped", "completed", "needs_human"], description: "Run status" },
|
|
830
|
+
mode: { type: "string", enum: ["foreground", "background"], description: "Execution mode" },
|
|
831
|
+
operating_mode: { type: "string", enum: ["converge", "continuous", "supervised"], description: "Operating mode" },
|
|
832
|
+
goal: { type: "string", description: "Run goal description" },
|
|
833
|
+
scope: { type: "string", description: "Target scope" },
|
|
834
|
+
metric: {
|
|
835
|
+
type: "object",
|
|
836
|
+
required: ["name", "direction"],
|
|
837
|
+
properties: {
|
|
838
|
+
name: { type: "string" },
|
|
839
|
+
direction: { type: "string", enum: ["higher", "lower"] },
|
|
840
|
+
baseline: { type: "string" },
|
|
841
|
+
best: { type: "string" },
|
|
842
|
+
latest: { type: "string" },
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
instrument_metric: { type: "object", description: "Optional secondary metric" },
|
|
846
|
+
verify: { type: "string", description: "Verification command" },
|
|
847
|
+
guard: { type: "string", description: "Guard command" },
|
|
848
|
+
scorer: { type: "string", description: "Scorer command" },
|
|
849
|
+
iterations_cap: { type: "number", description: "Maximum iterations" },
|
|
850
|
+
duration: { type: "string", description: "Duration limit" },
|
|
851
|
+
duration_seconds: { type: "number" },
|
|
852
|
+
deadline_at: { type: "string", format: "date-time" },
|
|
853
|
+
label_requirements: {
|
|
854
|
+
type: "object",
|
|
855
|
+
required: ["keep", "stop"],
|
|
856
|
+
properties: {
|
|
857
|
+
keep: { type: "array", items: { type: "string" } },
|
|
858
|
+
stop: { type: "array", items: { type: "string" } },
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
artifact_paths: {
|
|
862
|
+
type: "object",
|
|
863
|
+
required: ["results", "state"],
|
|
864
|
+
properties: {
|
|
865
|
+
results: { type: "string" },
|
|
866
|
+
state: { type: "string" },
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
stats: {
|
|
870
|
+
type: "object",
|
|
871
|
+
required: ["total_iterations", "kept", "discarded", "needs_human"],
|
|
872
|
+
properties: {
|
|
873
|
+
total_iterations: { type: "number" },
|
|
874
|
+
kept: { type: "number" },
|
|
875
|
+
discarded: { type: "number" },
|
|
876
|
+
needs_human: { type: "number" },
|
|
877
|
+
consecutive_discards: { type: "number" },
|
|
878
|
+
best_iteration: { type: "number" },
|
|
879
|
+
debug_depth: { type: "number" },
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
flags: {
|
|
883
|
+
type: "object",
|
|
884
|
+
required: ["stop_requested", "needs_human", "background_active", "stop_ready"],
|
|
885
|
+
properties: {
|
|
886
|
+
stop_requested: { type: "boolean" },
|
|
887
|
+
needs_human: { type: "boolean" },
|
|
888
|
+
background_active: { type: "boolean" },
|
|
889
|
+
stop_ready: { type: "boolean" },
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
last_iteration: {
|
|
893
|
+
type: "object",
|
|
894
|
+
properties: {
|
|
895
|
+
iteration: { type: "number" },
|
|
896
|
+
decision: { type: "string", enum: ["keep", "discard", "needs_human"] },
|
|
897
|
+
metric_value: { type: "string" },
|
|
898
|
+
change_summary: { type: "string" },
|
|
899
|
+
labels: { type: "array", items: { type: "string" } },
|
|
900
|
+
timestamp: { type: "string", format: "date-time" },
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
draft_pool: { type: "object", description: "Draft pool configuration" },
|
|
904
|
+
lineage: { type: "object", description: "Experiment lineage" },
|
|
905
|
+
budget_exhausted: { type: "boolean" },
|
|
906
|
+
budget_blocker_reason: { type: "string" },
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
result_row: {
|
|
910
|
+
type: "object",
|
|
911
|
+
description: "Single iteration result row in TSV format",
|
|
912
|
+
properties: {
|
|
913
|
+
iteration: { type: "number" },
|
|
914
|
+
decision: { type: "string" },
|
|
915
|
+
metric_value: { type: "string" },
|
|
916
|
+
verify_status: { type: "string" },
|
|
917
|
+
guard_status: { type: "string" },
|
|
918
|
+
change_summary: { type: "string" },
|
|
919
|
+
labels: { type: "array", items: { type: "string" } },
|
|
920
|
+
timestamp: { type: "string" },
|
|
921
|
+
note: { type: "string" },
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
goal_doc: {
|
|
925
|
+
type: "object",
|
|
926
|
+
required: ["goal", "metric", "direction", "verify"],
|
|
927
|
+
properties: {
|
|
928
|
+
goal: { type: "string" },
|
|
929
|
+
metric: { type: "string" },
|
|
930
|
+
direction: { type: "string", enum: ["higher", "lower"] },
|
|
931
|
+
verify: { type: "string" },
|
|
932
|
+
guard: { type: "string" },
|
|
933
|
+
constraints: { type: "string" },
|
|
934
|
+
file_map: { type: "string" },
|
|
935
|
+
stop_conditions: { type: "string" },
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
};
|
|
939
|
+
if (useJson) {
|
|
940
|
+
printJsonEnvelope("contract", schemas);
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
console.log("Auto Research Contract Schemas");
|
|
944
|
+
console.log("==============================");
|
|
945
|
+
console.log("");
|
|
946
|
+
console.log("State Schema:");
|
|
947
|
+
console.log(` Version: ${schemas.state.properties.schema_version.type}`);
|
|
948
|
+
console.log(` Required: ${schemas.state.required.join(", ")}`);
|
|
949
|
+
console.log("");
|
|
950
|
+
console.log("Result Row Schema:");
|
|
951
|
+
console.log(` Properties: ${Object.keys(schemas.result_row.properties).join(", ")}`);
|
|
952
|
+
console.log("");
|
|
953
|
+
console.log("Goal Doc Schema:");
|
|
954
|
+
console.log(` Required: ${schemas.goal_doc.required.join(", ")}`);
|
|
955
|
+
console.log("");
|
|
956
|
+
console.log("Use --json for full machine-readable schema output.");
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
731
959
|
case "summary": {
|
|
732
960
|
const { resolvePath } = await import("./helpers.js");
|
|
733
961
|
const { RESULTS_DEFAULT } = await import("./constants.js");
|
|
@@ -755,7 +983,7 @@ const main = async () => {
|
|
|
755
983
|
runIds.add(iterTags[0]);
|
|
756
984
|
}
|
|
757
985
|
if (useJson) {
|
|
758
|
-
|
|
986
|
+
printJsonEnvelope("summary", {
|
|
759
987
|
total_records: records.length,
|
|
760
988
|
total_kept: totalKept,
|
|
761
989
|
total_discarded: totalDiscarded,
|
|
@@ -798,7 +1026,7 @@ const main = async () => {
|
|
|
798
1026
|
if (!grouped.verify)
|
|
799
1027
|
errors.push("Missing required: --verify");
|
|
800
1028
|
if (useJson) {
|
|
801
|
-
|
|
1029
|
+
printJsonEnvelope("validate", { valid: errors.length === 0, errors });
|
|
802
1030
|
return errors.length > 0 ? 1 : 0;
|
|
803
1031
|
}
|
|
804
1032
|
if (errors.length === 0) {
|
|
@@ -836,7 +1064,7 @@ const main = async () => {
|
|
|
836
1064
|
results = resultLines.slice(1).filter(Boolean);
|
|
837
1065
|
}
|
|
838
1066
|
if (useJson) {
|
|
839
|
-
|
|
1067
|
+
printJsonEnvelope("report", { state, results_count: results.length });
|
|
840
1068
|
break;
|
|
841
1069
|
}
|
|
842
1070
|
console.log(`# Auto Research Report`);
|
|
@@ -870,6 +1098,40 @@ const main = async () => {
|
|
|
870
1098
|
}
|
|
871
1099
|
}
|
|
872
1100
|
}
|
|
1101
|
+
// Milestone Progress
|
|
1102
|
+
console.log(`\n## Milestone Progress`);
|
|
1103
|
+
if (state.stats) {
|
|
1104
|
+
const s = state.stats;
|
|
1105
|
+
const total = s.total_iterations;
|
|
1106
|
+
const successRate = total > 0 ? ((s.kept / total) * 100).toFixed(1) : "0";
|
|
1107
|
+
console.log(`- **Progress:** ${formatMarkdownField(s.kept)} kept / ${formatMarkdownField(total)} total iterations (${formatMarkdownField(successRate)}% success rate)`);
|
|
1108
|
+
if (state.iterations_cap) {
|
|
1109
|
+
const progressPct = ((total / state.iterations_cap) * 100).toFixed(1);
|
|
1110
|
+
console.log(`- **Cap:** ${formatMarkdownField(total)} / ${formatMarkdownField(state.iterations_cap)} iterations (${formatMarkdownField(progressPct)}% of cap)`);
|
|
1111
|
+
}
|
|
1112
|
+
if (state.created_at) {
|
|
1113
|
+
const startedAtMs = Date.parse(state.created_at);
|
|
1114
|
+
const endedAtMs = state.updated_at ? Date.parse(state.updated_at) : Date.now();
|
|
1115
|
+
if (!Number.isNaN(startedAtMs) && !Number.isNaN(endedAtMs) && endedAtMs >= startedAtMs) {
|
|
1116
|
+
const elapsedMin = Math.round((endedAtMs - startedAtMs) / 1000 / 60);
|
|
1117
|
+
console.log(`- **Elapsed:** ${formatMarkdownField(elapsedMin)} minutes`);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
// Next candidate
|
|
1121
|
+
if (state.last_iteration && state.last_iteration.decision === "keep") {
|
|
1122
|
+
console.log(`- **Next candidate:** Iteration ${formatMarkdownField(state.last_iteration.iteration)} (kept)`);
|
|
1123
|
+
}
|
|
1124
|
+
else if (s.best_iteration) {
|
|
1125
|
+
console.log(`- **Best candidate:** Iteration ${formatMarkdownField(s.best_iteration)}`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
// Artifact pointers
|
|
1129
|
+
console.log(`\n## Artifacts`);
|
|
1130
|
+
console.log(`- State: ${formatMarkdownField(state.artifact_paths?.state || ".autoresearch/state.json")}`);
|
|
1131
|
+
console.log(`- Results: ${formatMarkdownField(state.artifact_paths?.results || "autoresearch-results.tsv")}`);
|
|
1132
|
+
if (grouped.repo) {
|
|
1133
|
+
console.log(`- Repository: ${formatMarkdownField(grouped.repo)}`);
|
|
1134
|
+
}
|
|
873
1135
|
// Failed branches information
|
|
874
1136
|
if (state.draft_pool && state.draft_pool.active_drafts) {
|
|
875
1137
|
const failedBranches = state.draft_pool.active_drafts.filter(draft => draft.status === "discarded");
|
|
@@ -929,6 +1191,37 @@ const main = async () => {
|
|
|
929
1191
|
break;
|
|
930
1192
|
}
|
|
931
1193
|
case "suggest": {
|
|
1194
|
+
const evidenceGated = grouped["evidence"] === "true";
|
|
1195
|
+
if (evidenceGated) {
|
|
1196
|
+
const { generateIssueCandidate } = await import("./evidence.js");
|
|
1197
|
+
const candidate = generateIssueCandidate(grouped.repo, grouped.goal, grouped.metric, grouped.verify, grouped["score-history-path"]);
|
|
1198
|
+
if (!candidate) {
|
|
1199
|
+
if (useJson) {
|
|
1200
|
+
printJsonEnvelope("suggest", { candidates: [], reason: "insufficient_evidence" });
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
console.log("No evidence-gated issue candidates found.");
|
|
1204
|
+
console.log("Insufficient failure clusters or score history not available.");
|
|
1205
|
+
}
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
if (useJson) {
|
|
1209
|
+
printJsonEnvelope("suggest", { candidates: [candidate], evidence_gated: true });
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
console.log(`Evidence-Gated Issue Candidate:`);
|
|
1213
|
+
console.log(` Title: ${candidate.title}`);
|
|
1214
|
+
console.log(` Goal: ${candidate.goal}`);
|
|
1215
|
+
console.log(` Metric: ${candidate.metric}`);
|
|
1216
|
+
console.log(` Evidence: ${candidate.evidence.total_discards} discards in ${candidate.evidence.total_runs} cluster(s)`);
|
|
1217
|
+
console.log(``);
|
|
1218
|
+
console.log(` Suggested command:`);
|
|
1219
|
+
console.log(` ${candidate.suggest_command}`);
|
|
1220
|
+
console.log(``);
|
|
1221
|
+
console.log(`Review before opening. This candidate is NOT auto-submitted.`);
|
|
1222
|
+
}
|
|
1223
|
+
break;
|
|
1224
|
+
}
|
|
932
1225
|
const { resolvePath } = await import("./helpers.js");
|
|
933
1226
|
const { MEMORY_DEFAULT } = await import("./constants.js");
|
|
934
1227
|
const memoryPath = resolvePath(grouped.repo, grouped["memory-path"], MEMORY_DEFAULT);
|
|
@@ -937,10 +1230,10 @@ const main = async () => {
|
|
|
937
1230
|
break;
|
|
938
1231
|
}
|
|
939
1232
|
const memory = readFileSync(memoryPath, "utf-8");
|
|
940
|
-
const patterns = memory.match(
|
|
1233
|
+
const patterns = memory.match(/^### Pattern: [^\n]+/gm) ?? [];
|
|
941
1234
|
const suggestions = patterns.map(parseMemoryPatternHeading);
|
|
942
1235
|
if (useJson) {
|
|
943
|
-
|
|
1236
|
+
printJsonEnvelope("suggest", { patterns_found: suggestions.length, suggestions });
|
|
944
1237
|
break;
|
|
945
1238
|
}
|
|
946
1239
|
console.log("Memory Patterns — candidate next goals:");
|
|
@@ -983,7 +1276,7 @@ const main = async () => {
|
|
|
983
1276
|
},
|
|
984
1277
|
};
|
|
985
1278
|
if (format === "json") {
|
|
986
|
-
|
|
1279
|
+
printJsonEnvelope("export", exportData);
|
|
987
1280
|
}
|
|
988
1281
|
else if (format === "md" || format === "markdown") {
|
|
989
1282
|
console.log(`# Auto Research Export`);
|
|
@@ -1010,7 +1303,7 @@ const main = async () => {
|
|
|
1010
1303
|
case "completion": {
|
|
1011
1304
|
const shell = grouped.shell || "bash";
|
|
1012
1305
|
const commands = ["init", "goal", "wizard", "status", "explain", "history", "config", "summary", "suggest", "launch", "complete", "stop", "resume", "record", "doctor", "export", "completion", "help"];
|
|
1013
|
-
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"];
|
|
1306
|
+
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"];
|
|
1014
1307
|
if (shell === "bash" || shell === "zsh") {
|
|
1015
1308
|
console.log(`# Auto Research CLI completion for ${shell}`);
|
|
1016
1309
|
console.log(`_autoresearch() {`);
|
|
@@ -1065,6 +1358,7 @@ const main = async () => {
|
|
|
1065
1358
|
baseline: grouped.baseline,
|
|
1066
1359
|
num_drafts: parsePositiveInt(grouped["num-drafts"], "num_drafts", { max: MAX_DRAFTS }) ?? 1,
|
|
1067
1360
|
branch_selection_policy: normalizeBranchPolicy(grouped["branch-policy"]),
|
|
1361
|
+
branch_policy_overrides: parseBranchPolicyOverrides(grouped["branch-policy-overrides"]),
|
|
1068
1362
|
outcome_metric: grouped["outcome-metric"],
|
|
1069
1363
|
outcome_direction: grouped["outcome-direction"],
|
|
1070
1364
|
instrument_metric: grouped["instrument-metric"],
|
|
@@ -1080,7 +1374,7 @@ const main = async () => {
|
|
|
1080
1374
|
const { writeFileSync } = await import("fs");
|
|
1081
1375
|
const state = await initializeRun(grouped.repo, grouped["results-path"], grouped["state-path"], config, grouped["fresh-start"] === "true");
|
|
1082
1376
|
writeFileSync(launchPath, JSON.stringify({ run_id: state.run_id, goal: state.goal, mode: "background" }, null, 2) + "\n", "utf-8");
|
|
1083
|
-
|
|
1377
|
+
printJsonEnvelope("launch", { status: "launched", run_id: state.run_id, launch_path: launchPath });
|
|
1084
1378
|
break;
|
|
1085
1379
|
}
|
|
1086
1380
|
case "complete": {
|
|
@@ -1090,7 +1384,7 @@ const main = async () => {
|
|
|
1090
1384
|
}
|
|
1091
1385
|
const { completeRun } = await import("./run-manager.js");
|
|
1092
1386
|
const state = await completeRun(grouped.repo, grouped["state-path"]);
|
|
1093
|
-
|
|
1387
|
+
printJsonEnvelope("complete", { status: "completed", run_id: state.run_id });
|
|
1094
1388
|
break;
|
|
1095
1389
|
}
|
|
1096
1390
|
case "stop": {
|
|
@@ -1100,7 +1394,7 @@ const main = async () => {
|
|
|
1100
1394
|
}
|
|
1101
1395
|
const { setStopRequested } = await import("./run-manager.js");
|
|
1102
1396
|
const state = await setStopRequested(grouped.repo, grouped["state-path"]);
|
|
1103
|
-
|
|
1397
|
+
printJsonEnvelope("stop", { status: "stop_requested", run_id: state.run_id });
|
|
1104
1398
|
break;
|
|
1105
1399
|
}
|
|
1106
1400
|
case "resume": {
|
|
@@ -1110,7 +1404,7 @@ const main = async () => {
|
|
|
1110
1404
|
}
|
|
1111
1405
|
const { resumeBackgroundRun } = await import("./run-manager.js");
|
|
1112
1406
|
const state = await resumeBackgroundRun(grouped.repo, grouped["state-path"]);
|
|
1113
|
-
|
|
1407
|
+
printJsonEnvelope("resume", { status: "resumed", run_id: state.run_id });
|
|
1114
1408
|
break;
|
|
1115
1409
|
}
|
|
1116
1410
|
case "record": {
|
|
@@ -1153,7 +1447,7 @@ const main = async () => {
|
|
|
1153
1447
|
}
|
|
1154
1448
|
const { appendIteration } = await import("./run-manager.js");
|
|
1155
1449
|
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);
|
|
1156
|
-
|
|
1450
|
+
printJsonEnvelope("record", state);
|
|
1157
1451
|
break;
|
|
1158
1452
|
}
|
|
1159
1453
|
case "digest": {
|
|
@@ -1164,7 +1458,7 @@ const main = async () => {
|
|
|
1164
1458
|
const { buildRunDigest } = await import("./run-manager.js");
|
|
1165
1459
|
const digest = await buildRunDigest(grouped.repo, grouped["results-path"], grouped["state-path"]);
|
|
1166
1460
|
if (useJson) {
|
|
1167
|
-
|
|
1461
|
+
printJsonEnvelope("digest", digest);
|
|
1168
1462
|
}
|
|
1169
1463
|
else {
|
|
1170
1464
|
console.log(`# Auto Research Digest`);
|
|
@@ -1208,7 +1502,7 @@ const main = async () => {
|
|
|
1208
1502
|
if (digest.flags && Object.keys(digest.flags).length > 0) {
|
|
1209
1503
|
console.log(`\n## Flags`);
|
|
1210
1504
|
for (const [key, value] of Object.entries(digest.flags)) {
|
|
1211
|
-
console.log(`- ${key}: ${formatMarkdownField(value)}`);
|
|
1505
|
+
console.log(`- ${formatMarkdownField(key)}: ${formatMarkdownField(value)}`);
|
|
1212
1506
|
}
|
|
1213
1507
|
}
|
|
1214
1508
|
}
|
|
@@ -1234,6 +1528,7 @@ const main = async () => {
|
|
|
1234
1528
|
const installedPath = getInstalledPackagePath(PACKAGE_NAME);
|
|
1235
1529
|
const installedInfo = installedPath ? getInstalledPackageInfo(PACKAGE_NAME) : null;
|
|
1236
1530
|
const updateCache = readUpdateCache();
|
|
1531
|
+
const { skip: updateSkipped, reason: skipReason } = shouldSkipUpdateCheck(process.argv.slice(2));
|
|
1237
1532
|
const updateStatus = {
|
|
1238
1533
|
cache_exists: updateCache !== null,
|
|
1239
1534
|
last_check: updateCache?.last_check || null,
|
|
@@ -1241,9 +1536,13 @@ const main = async () => {
|
|
|
1241
1536
|
latest_version: updateCache?.latest_version || null,
|
|
1242
1537
|
update_available: updateCache?.update_available || false,
|
|
1243
1538
|
update_disabled: process.env.AUTORESEARCH_NO_UPDATE === "1",
|
|
1539
|
+
skipped: updateSkipped,
|
|
1540
|
+
skip_reason: skipReason,
|
|
1244
1541
|
};
|
|
1245
1542
|
if (useJson) {
|
|
1246
|
-
|
|
1543
|
+
const { getWhatsNew } = await import("./whats-new.js");
|
|
1544
|
+
const wn = getWhatsNew(base);
|
|
1545
|
+
printJsonEnvelope("doctor", {
|
|
1247
1546
|
version: VERSION,
|
|
1248
1547
|
skill_name: SKILL_NAME,
|
|
1249
1548
|
runtime: `Node.js ${process.version}`,
|
|
@@ -1258,6 +1557,7 @@ const main = async () => {
|
|
|
1258
1557
|
update: updateStatus,
|
|
1259
1558
|
checks: checks,
|
|
1260
1559
|
checks_passed: checks.filter((c) => !c.ok).length === 0,
|
|
1560
|
+
whats_new: wn ? { features: wn.features, fixes: wn.fixes } : null,
|
|
1261
1561
|
});
|
|
1262
1562
|
break;
|
|
1263
1563
|
}
|
|
@@ -1278,7 +1578,10 @@ const main = async () => {
|
|
|
1278
1578
|
}
|
|
1279
1579
|
console.log("");
|
|
1280
1580
|
console.log("Update:");
|
|
1281
|
-
if (
|
|
1581
|
+
if (updateSkipped) {
|
|
1582
|
+
console.log(` Skipped: yes (${skipReason})`);
|
|
1583
|
+
}
|
|
1584
|
+
else if (updateCache) {
|
|
1282
1585
|
console.log(` Last check: ${updateCache.last_check}`);
|
|
1283
1586
|
console.log(` Current: ${updateCache.current_version}`);
|
|
1284
1587
|
console.log(` Latest: ${updateCache.latest_version}`);
|
|
@@ -1303,6 +1606,20 @@ const main = async () => {
|
|
|
1303
1606
|
return 1;
|
|
1304
1607
|
}
|
|
1305
1608
|
console.log(`\nAll ${checks.length} checks passed.`);
|
|
1609
|
+
const showWhatsNew = grouped["whats-new"] === "true";
|
|
1610
|
+
if (showWhatsNew || useJson) {
|
|
1611
|
+
const { getWhatsNew, formatWhatsNew } = await import("./whats-new.js");
|
|
1612
|
+
const wn = getWhatsNew(base);
|
|
1613
|
+
if (wn) {
|
|
1614
|
+
if (useJson) {
|
|
1615
|
+
// Already displayed in JSON envelope — add it for next re-entry
|
|
1616
|
+
}
|
|
1617
|
+
else {
|
|
1618
|
+
console.log("");
|
|
1619
|
+
console.log(formatWhatsNew(wn));
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1306
1623
|
break;
|
|
1307
1624
|
}
|
|
1308
1625
|
case "goal": {
|
|
@@ -1345,7 +1662,7 @@ const main = async () => {
|
|
|
1345
1662
|
}
|
|
1346
1663
|
const doc = readGoalDoc(goalPath);
|
|
1347
1664
|
if (useJson) {
|
|
1348
|
-
|
|
1665
|
+
printJsonEnvelope("goal", doc);
|
|
1349
1666
|
break;
|
|
1350
1667
|
}
|
|
1351
1668
|
console.log(`Goal: ${formatDisplayValue(doc.goal)}`);
|
|
@@ -1465,7 +1782,7 @@ const main = async () => {
|
|
|
1465
1782
|
const result = buildGoalInitResult(goalPath, config, !hasRequiredFlags && isTTY);
|
|
1466
1783
|
if (isGoalDryRun) {
|
|
1467
1784
|
if (useGoalJson) {
|
|
1468
|
-
|
|
1785
|
+
printJsonEnvelope("goal", { ...result, dry_run: true });
|
|
1469
1786
|
}
|
|
1470
1787
|
else {
|
|
1471
1788
|
console.log("[dry-run] Would write goal document to: " + goalPath);
|
|
@@ -1481,7 +1798,7 @@ const main = async () => {
|
|
|
1481
1798
|
}
|
|
1482
1799
|
atomicWriteTextInRepo(goalGrouped.repo, goalPath, document);
|
|
1483
1800
|
if (useGoalJson) {
|
|
1484
|
-
|
|
1801
|
+
printJsonEnvelope("goal", result);
|
|
1485
1802
|
}
|
|
1486
1803
|
else {
|
|
1487
1804
|
console.log(`✓ Goal definition written to ${goalPath}`);
|
|
@@ -1496,6 +1813,181 @@ const main = async () => {
|
|
|
1496
1813
|
}
|
|
1497
1814
|
break;
|
|
1498
1815
|
}
|
|
1816
|
+
case "queue": {
|
|
1817
|
+
const subCmd = cmdArgs[0] || "list";
|
|
1818
|
+
if (subCmd === "help") {
|
|
1819
|
+
console.error("Usage: autoresearch queue <subcommand> [options]");
|
|
1820
|
+
console.error("");
|
|
1821
|
+
console.error("Subcommands:");
|
|
1822
|
+
console.error(" list List tasks in the queue (default)");
|
|
1823
|
+
console.error(" enqueue Enqueue a new task");
|
|
1824
|
+
console.error(" clean Remove completed and failed tasks");
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
if (subCmd === "enqueue") {
|
|
1828
|
+
if (!grouped.goal || !grouped.metric || !grouped.verify) {
|
|
1829
|
+
console.error("--goal, --metric, and --verify are required for enqueue");
|
|
1830
|
+
return 1;
|
|
1831
|
+
}
|
|
1832
|
+
const { enqueueTasks } = await import("./task-queue.js");
|
|
1833
|
+
const tasks = await enqueueTasks(grouped.repo, [{ goal: grouped.goal, metric: grouped.metric, verify: grouped.verify }]);
|
|
1834
|
+
if (useJson) {
|
|
1835
|
+
printJson({ enqueued: tasks });
|
|
1836
|
+
}
|
|
1837
|
+
else {
|
|
1838
|
+
for (const t of tasks) {
|
|
1839
|
+
console.log(`Enqueued: ${t.id} - ${t.goal}`);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
if (subCmd === "clean") {
|
|
1845
|
+
const { listTasks, writeManifest, resolveQueuePath } = await import("./task-queue.js");
|
|
1846
|
+
const queuePath = resolveQueuePath(grouped.repo);
|
|
1847
|
+
const manifest = await listTasks(grouped.repo);
|
|
1848
|
+
const before = manifest.tasks.length;
|
|
1849
|
+
manifest.tasks = manifest.tasks.filter((t) => t.status === "pending" || t.status === "leased");
|
|
1850
|
+
manifest.updated_at = new Date().toISOString();
|
|
1851
|
+
const removed = before - manifest.tasks.length;
|
|
1852
|
+
await writeManifest(queuePath, manifest);
|
|
1853
|
+
if (useJson) {
|
|
1854
|
+
printJson({ removed });
|
|
1855
|
+
}
|
|
1856
|
+
else {
|
|
1857
|
+
console.log(`Cleaned ${removed} completed/failed tasks. ${manifest.tasks.length} remain.`);
|
|
1858
|
+
}
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
const { listTasks } = await import("./task-queue.js");
|
|
1862
|
+
const manifest = await listTasks(grouped.repo);
|
|
1863
|
+
if (useJson) {
|
|
1864
|
+
printJson(manifest);
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
if (manifest.tasks.length === 0) {
|
|
1868
|
+
console.log("No tasks in queue.");
|
|
1869
|
+
}
|
|
1870
|
+
else {
|
|
1871
|
+
console.log(`Task Queue (${manifest.tasks.length} tasks):`);
|
|
1872
|
+
for (const task of manifest.tasks) {
|
|
1873
|
+
const icon = task.status === "completed" ? "v" : task.status === "failed" ? "x" : task.status === "leased" ? ">" : "*";
|
|
1874
|
+
console.log(` ${icon} ${task.id} [${task.status}] ${task.goal}`);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
break;
|
|
1879
|
+
}
|
|
1880
|
+
case "pack": {
|
|
1881
|
+
const subCmd = cmdArgs[0] || "help";
|
|
1882
|
+
if (subCmd === "help" || (subCmd !== "export" && subCmd !== "list" && subCmd !== "inspect")) {
|
|
1883
|
+
console.error("Usage: autoresearch pack <subcommand> [options]");
|
|
1884
|
+
console.error("Subcommands:");
|
|
1885
|
+
console.error(" export Export validated run as a strategy pack");
|
|
1886
|
+
console.error(" list List available strategy packs");
|
|
1887
|
+
console.error(" inspect View a specific strategy pack");
|
|
1888
|
+
break;
|
|
1889
|
+
}
|
|
1890
|
+
if (subCmd === "export") {
|
|
1891
|
+
const { exportPack } = await import("./strategy-pack.js");
|
|
1892
|
+
const result = exportPack(grouped.repo, grouped["state-path"], grouped["goal-path"]);
|
|
1893
|
+
if (!result) {
|
|
1894
|
+
console.error("No run state found. Complete a run first.");
|
|
1895
|
+
return 1;
|
|
1896
|
+
}
|
|
1897
|
+
if (useJson) {
|
|
1898
|
+
printJson({ exported: result.path, pack: result.pack });
|
|
1899
|
+
}
|
|
1900
|
+
else {
|
|
1901
|
+
console.log(`Strategy pack exported: ${result.path}`);
|
|
1902
|
+
console.log(` Goal: ${result.pack.goal}`);
|
|
1903
|
+
console.log(` Metric: ${result.pack.metric}`);
|
|
1904
|
+
console.log(` Success: ${result.pack.evidence.success_rate}`);
|
|
1905
|
+
}
|
|
1906
|
+
break;
|
|
1907
|
+
}
|
|
1908
|
+
if (subCmd === "list") {
|
|
1909
|
+
const { listPacks } = await import("./strategy-pack.js");
|
|
1910
|
+
const packs = listPacks(grouped.repo);
|
|
1911
|
+
if (useJson) {
|
|
1912
|
+
printJson({ packs });
|
|
1913
|
+
}
|
|
1914
|
+
else if (packs.length === 0) {
|
|
1915
|
+
console.log("No strategy packs found.");
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
console.log(`Strategy Packs (${packs.length}):`);
|
|
1919
|
+
for (const p of packs)
|
|
1920
|
+
console.log(` ${p.name}`);
|
|
1921
|
+
}
|
|
1922
|
+
break;
|
|
1923
|
+
}
|
|
1924
|
+
if (subCmd === "inspect") {
|
|
1925
|
+
const name = cmdArgs[1];
|
|
1926
|
+
if (!name) {
|
|
1927
|
+
console.error("Usage: autoresearch pack inspect <name>");
|
|
1928
|
+
return 1;
|
|
1929
|
+
}
|
|
1930
|
+
const { readPack } = await import("./strategy-pack.js");
|
|
1931
|
+
const content = readPack(grouped.repo, name);
|
|
1932
|
+
if (!content) {
|
|
1933
|
+
console.error(`Pack not found: ${name}`);
|
|
1934
|
+
return 1;
|
|
1935
|
+
}
|
|
1936
|
+
console.log(content);
|
|
1937
|
+
break;
|
|
1938
|
+
}
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1941
|
+
case "leaderboard": {
|
|
1942
|
+
const { generateLeaderboard, formatLeaderboardMarkdown, formatLeaderboardText } = await import("./leaderboard.js");
|
|
1943
|
+
const { resolveRepo } = await import("./helpers.js");
|
|
1944
|
+
const repo = resolveRepo(grouped.repo);
|
|
1945
|
+
const leaderboard = generateLeaderboard(repo);
|
|
1946
|
+
if (useJson) {
|
|
1947
|
+
printJson(leaderboard);
|
|
1948
|
+
break;
|
|
1949
|
+
}
|
|
1950
|
+
if (leaderboard.entries.length === 0) {
|
|
1951
|
+
console.log("No runs found. Complete some runs to see the leaderboard.");
|
|
1952
|
+
break;
|
|
1953
|
+
}
|
|
1954
|
+
if (grouped.format === "markdown") {
|
|
1955
|
+
console.log(formatLeaderboardMarkdown(leaderboard));
|
|
1956
|
+
}
|
|
1957
|
+
else {
|
|
1958
|
+
console.log(formatLeaderboardText(leaderboard));
|
|
1959
|
+
}
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
1962
|
+
case "worker": {
|
|
1963
|
+
const { workerOnce } = await import("./worker.js");
|
|
1964
|
+
const once = grouped["once"] === "true";
|
|
1965
|
+
if (!once) {
|
|
1966
|
+
console.error("worker requires --once flag");
|
|
1967
|
+
console.error("Usage: autoresearch worker --once [--json] [--repo <path>]");
|
|
1968
|
+
return 1;
|
|
1969
|
+
}
|
|
1970
|
+
const result = workerOnce(grouped.repo, grouped["state-path"], grouped["results-path"]);
|
|
1971
|
+
if (useJson) {
|
|
1972
|
+
printJsonEnvelope("worker", result);
|
|
1973
|
+
}
|
|
1974
|
+
else {
|
|
1975
|
+
if (result.ready) {
|
|
1976
|
+
console.log(`✓ Ready for iteration ${result.iteration}`);
|
|
1977
|
+
console.log(` Run ID: ${result.run_id}`);
|
|
1978
|
+
console.log(` Status: ${result.status}`);
|
|
1979
|
+
console.log(` Goal: ${result.goal}`);
|
|
1980
|
+
if (result.metric)
|
|
1981
|
+
console.log(` Metric: ${result.metric}`);
|
|
1982
|
+
}
|
|
1983
|
+
else {
|
|
1984
|
+
console.log(`✗ Not ready: ${result.reason || "unknown"}`);
|
|
1985
|
+
console.log(` Run ID: ${result.run_id}`);
|
|
1986
|
+
console.log(` Iter: ${result.iteration}`);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
return result.ready ? 0 : 1;
|
|
1990
|
+
}
|
|
1499
1991
|
default: {
|
|
1500
1992
|
console.error(`Unknown command: ${cmd}`);
|
|
1501
1993
|
console.error("Run 'autoresearch --help' for usage.");
|
|
@@ -1504,7 +1996,14 @@ const main = async () => {
|
|
|
1504
1996
|
}
|
|
1505
1997
|
}
|
|
1506
1998
|
catch (exc) {
|
|
1507
|
-
|
|
1999
|
+
const { categorizeError, formatStructuredError } = await import("./error-categories.js");
|
|
2000
|
+
const structured = categorizeError(exc);
|
|
2001
|
+
if (useJson) {
|
|
2002
|
+
console.error(formatStructuredError(structured, true));
|
|
2003
|
+
}
|
|
2004
|
+
else {
|
|
2005
|
+
console.error(formatStructuredError(structured, false));
|
|
2006
|
+
}
|
|
1508
2007
|
return 2;
|
|
1509
2008
|
}
|
|
1510
2009
|
return 0;
|