@open330/oac 2026.4.3 → 2026.220.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +2 -2
  3. package/dist/{chunk-YWIB3TRI.js → chunk-6A37SKAJ.js} +15 -2
  4. package/dist/chunk-6A37SKAJ.js.map +1 -0
  5. package/dist/{chunk-HDMLNOND.js → chunk-6JEFW6B7.js} +92 -20
  6. package/dist/chunk-6JEFW6B7.js.map +1 -0
  7. package/dist/{chunk-ZRYAHZQJ.js → chunk-7C7SC4TZ.js} +1 -1
  8. package/dist/{chunk-XUW3XWTX.js → chunk-LRTQCVQK.js} +145 -121
  9. package/dist/chunk-LRTQCVQK.js.map +1 -0
  10. package/dist/{chunk-CJAJ4MBO.js → chunk-OS3XDHOJ.js} +57 -18
  11. package/dist/chunk-OS3XDHOJ.js.map +1 -0
  12. package/dist/{chunk-7FWC3Z4W.js → chunk-QPVNC7S4.js} +479 -196
  13. package/dist/chunk-QPVNC7S4.js.map +1 -0
  14. package/dist/cli/cli.js +6 -6
  15. package/dist/cli/index.js +6 -6
  16. package/dist/completion/index.d.ts +5 -0
  17. package/dist/completion/index.js +49 -8
  18. package/dist/completion/index.js.map +1 -1
  19. package/dist/core/index.d.ts +16 -1
  20. package/dist/core/index.js +8 -4
  21. package/dist/dashboard/index.js +33 -24
  22. package/dist/dashboard/index.js.map +1 -1
  23. package/dist/discovery/index.d.ts +18 -2
  24. package/dist/discovery/index.js +4 -2
  25. package/dist/execution/index.d.ts +45 -1
  26. package/dist/execution/index.js +7 -3
  27. package/dist/repo/index.d.ts +2 -2
  28. package/dist/repo/index.js +1 -1
  29. package/dist/{types-CYCwgojB.d.ts → types-3_IAAxh5.d.ts} +1 -0
  30. package/docs/config-reference.md +271 -0
  31. package/docs/multi-agent-support-technical-spec.md +312 -0
  32. package/package.json +23 -18
  33. package/dist/chunk-7FWC3Z4W.js.map +0 -1
  34. package/dist/chunk-CJAJ4MBO.js.map +0 -1
  35. package/dist/chunk-HDMLNOND.js.map +0 -1
  36. package/dist/chunk-XUW3XWTX.js.map +0 -1
  37. package/dist/chunk-YWIB3TRI.js.map +0 -1
  38. /package/dist/{chunk-ZRYAHZQJ.js.map → chunk-7C7SC4TZ.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  cloneRepo,
3
3
  resolveRepo
4
- } from "./chunk-CJAJ4MBO.js";
4
+ } from "./chunk-OS3XDHOJ.js";
5
5
  import {
6
6
  CompositeScanner,
7
7
  GitHubIssuesScanner,
@@ -9,6 +9,7 @@ import {
9
9
  TestGapScanner,
10
10
  TodoScanner,
11
11
  analyzeCodebase,
12
+ buildScanners,
12
13
  createBacklog,
13
14
  getPendingEpics,
14
15
  groupFindingsIntoEpics,
@@ -19,7 +20,7 @@ import {
19
20
  persistContext,
20
21
  rankTasks,
21
22
  updateBacklog
22
- } from "./chunk-HDMLNOND.js";
23
+ } from "./chunk-6JEFW6B7.js";
23
24
  import {
24
25
  buildEpicExecutionPlan,
25
26
  buildExecutionPlan,
@@ -27,17 +28,20 @@ import {
27
28
  estimateTokens
28
29
  } from "./chunk-5GAUWC3L.js";
29
30
  import {
30
- ClaudeCodeAdapter,
31
- CodexAdapter,
31
+ adapterRegistry,
32
32
  createSandbox,
33
33
  epicAsTask,
34
34
  executeTask
35
- } from "./chunk-7FWC3Z4W.js";
35
+ } from "./chunk-QPVNC7S4.js";
36
36
  import {
37
37
  UNLIMITED_BUDGET,
38
38
  createEventBus,
39
39
  loadConfig
40
- } from "./chunk-ZRYAHZQJ.js";
40
+ } from "./chunk-7C7SC4TZ.js";
41
+ import {
42
+ isRecord,
43
+ truncate
44
+ } from "./chunk-6A37SKAJ.js";
41
45
  import {
42
46
  contributionLogSchema,
43
47
  writeContributionLog
@@ -51,7 +55,7 @@ import Table from "cli-table3";
51
55
  import { Command as Command2 } from "commander";
52
56
 
53
57
  // src/cli/github-auth.ts
54
- import { execFileSync } from "child_process";
58
+ import { execFileSync, spawnSync } from "child_process";
55
59
  function ensureGitHubAuth() {
56
60
  const githubToken = process.env.GITHUB_TOKEN?.trim();
57
61
  if (githubToken) {
@@ -79,24 +83,12 @@ function ensureGitHubAuth() {
79
83
  }
80
84
  function checkGitHubScopes(required = ["repo"]) {
81
85
  try {
82
- const output = execFileSync("gh", ["auth", "status"], {
86
+ const result = spawnSync("gh", ["auth", "status"], {
83
87
  timeout: 5e3,
84
88
  encoding: "utf-8",
85
- // Merge stderr into stdout so we can read scope info
86
89
  stdio: ["ignore", "pipe", "pipe"]
87
90
  });
88
- let combined = output;
89
- if (!combined.includes("Token scopes")) {
90
- try {
91
- combined = execFileSync("sh", ["-c", "gh auth status 2>&1"], {
92
- timeout: 5e3,
93
- encoding: "utf-8",
94
- stdio: ["ignore", "pipe", "ignore"]
95
- });
96
- } catch {
97
- return [];
98
- }
99
- }
91
+ const combined = `${result.stdout ?? ""}${result.stderr ?? ""}`;
100
92
  const scopeLine = combined.match(/Token scopes:\s*(.+)/);
101
93
  if (!scopeLine) return [];
102
94
  const scopes = scopeLine[1].split(",").map((s) => s.trim().replace(/^'|'$/g, ""));
@@ -110,14 +102,15 @@ function checkGitHubScopes(required = ["repo"]) {
110
102
  import chalk, { Chalk } from "chalk";
111
103
  import "commander";
112
104
  import ora from "ora";
105
+ import PQueue from "p-queue";
113
106
 
114
107
  // src/cli/config-loader.ts
115
108
  import { constants as fsConstants } from "fs";
116
109
  import { access, readFile } from "fs/promises";
117
110
  import { resolve } from "path";
118
111
  import { pathToFileURL } from "url";
119
- var LEGACY_DEFINE_CONFIG_IMPORT = /@(?:open330\/oac-core|oac\/core)/;
120
- var LEGACY_DEFINE_CONFIG_IMPORT_LINE = /^\s*import\s*\{\s*defineConfig\s*\}\s*from\s*["']@(?:open330\/oac-core|oac\/core)["'];\s*$/m;
112
+ var DEFINE_CONFIG_IMPORT = /@(?:open330\/oac(?:-core)?|oac\/core)/;
113
+ var DEFINE_CONFIG_IMPORT_LINE = /^\s*import\s*\{\s*defineConfig\s*\}\s*from\s*["']@(?:open330\/oac(?:-core)?|oac\/core)["'];\s*$/m;
121
114
  var LEGACY_DEFINE_CONFIG_EXPORT = /export\s+default\s+defineConfig\s*\(/;
122
115
  async function loadOptionalConfigFile(configPath, options = {}) {
123
116
  const absolutePath = resolve(options.cwd ?? process.cwd(), configPath);
@@ -161,16 +154,19 @@ function shouldTryLegacyDefineConfigFallback(error) {
161
154
  if (!(error instanceof Error)) {
162
155
  return false;
163
156
  }
164
- return LEGACY_DEFINE_CONFIG_IMPORT.test(error.message);
157
+ if (error.code === "ERR_UNKNOWN_FILE_EXTENSION") {
158
+ return true;
159
+ }
160
+ return DEFINE_CONFIG_IMPORT.test(error.message);
165
161
  }
166
162
  function transformLegacyDefineConfigSource(source) {
167
- if (!LEGACY_DEFINE_CONFIG_IMPORT_LINE.test(source)) {
163
+ if (!DEFINE_CONFIG_IMPORT_LINE.test(source)) {
168
164
  return null;
169
165
  }
170
166
  if (!LEGACY_DEFINE_CONFIG_EXPORT.test(source)) {
171
167
  return null;
172
168
  }
173
- return source.replace(LEGACY_DEFINE_CONFIG_IMPORT_LINE, "").replace(LEGACY_DEFINE_CONFIG_EXPORT, "export default (");
169
+ return source.replace(DEFINE_CONFIG_IMPORT_LINE, "").replace(LEGACY_DEFINE_CONFIG_EXPORT, "export default (");
174
170
  }
175
171
  async function importCandidate(moduleSpecifier) {
176
172
  const imported = await import(moduleSpecifier);
@@ -217,12 +213,6 @@ function parseInteger(value) {
217
213
  function formatInteger(value) {
218
214
  return new Intl.NumberFormat("en-US").format(value);
219
215
  }
220
- function truncate(value, maxLength) {
221
- if (value.length <= maxLength) {
222
- return value;
223
- }
224
- return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
225
- }
226
216
  async function loadOptionalConfig(configPath, verbose, ui) {
227
217
  return loadOptionalConfigFile(configPath, {
228
218
  onWarning: verbose ? (message) => {
@@ -263,13 +253,16 @@ function resolveBudget(tokensOption, config) {
263
253
  async function estimateTaskMap(tasks, providerId, onProgress) {
264
254
  let completed = 0;
265
255
  const total = tasks.length;
256
+ const queue = new PQueue({ concurrency: 10 });
266
257
  const entries = await Promise.all(
267
- tasks.map(async (task) => {
268
- const estimate = await estimateTokens(task, providerId);
269
- completed += 1;
270
- onProgress?.(completed, total);
271
- return [task.id, estimate];
272
- })
258
+ tasks.map(
259
+ (task) => queue.add(async () => {
260
+ const estimate = await estimateTokens(task, providerId);
261
+ completed += 1;
262
+ onProgress?.(completed, total);
263
+ return [task.id, estimate];
264
+ })
265
+ )
273
266
  );
274
267
  return new Map(entries);
275
268
  }
@@ -382,14 +375,7 @@ function normalizeOutputFormat(value) {
382
375
  throw new Error(`Unsupported --format value "${value}". Use "table" or "json".`);
383
376
  }
384
377
  function buildScannerList(config, hasGitHubAuth) {
385
- const scanners = [];
386
- const lint = config?.discovery.scanners.lint ?? true;
387
- const todo = config?.discovery.scanners.todo ?? true;
388
- if (lint) scanners.push(new LintScanner());
389
- if (todo) scanners.push(new TodoScanner());
390
- scanners.push(new TestGapScanner());
391
- if (hasGitHubAuth) scanners.push(new GitHubIssuesScanner());
392
- return scanners;
378
+ return buildScanners(config, hasGitHubAuth).instances;
393
379
  }
394
380
 
395
381
  // src/cli/commands/completion.ts
@@ -598,6 +584,16 @@ async function runDoctorChecks() {
598
584
  status: codexResult.ok ? "pass" : "fail",
599
585
  message: codexResult.ok ? void 0 : explainCommandFailure("codex", codexResult)
600
586
  });
587
+ const opencodeResult = await runCommand("opencode", ["--version"]);
588
+ const opencodeVersion = extractVersion(opencodeResult.stdout) ?? "--";
589
+ checks.push({
590
+ id: "opencode",
591
+ name: "OpenCode CLI",
592
+ requirement: "installed",
593
+ value: opencodeVersion,
594
+ status: opencodeResult.ok ? "pass" : "fail",
595
+ message: opencodeResult.ok ? void 0 : explainCommandFailure("opencode", opencodeResult)
596
+ });
601
597
  return checks;
602
598
  }
603
599
  async function checkGithubAuth() {
@@ -709,7 +705,7 @@ async function runCommand(command, args) {
709
705
  });
710
706
  return {
711
707
  ok: result.exitCode === 0,
712
- exitCode: result.exitCode,
708
+ exitCode: result.exitCode ?? null,
713
709
  stdout: result.stdout,
714
710
  stderr: result.stderr
715
711
  };
@@ -780,15 +776,15 @@ function createExplainCommand() {
780
776
  console.log(` ${ui.blue("Scope:")} ${epic.scope}`);
781
777
  console.log(` ${ui.blue("Priority:")} ${epic.priority}`);
782
778
  console.log(` ${ui.blue("Status:")} ${epic.status}`);
783
- console.log(` ${ui.blue("Tasks:")} ${epic.taskIds.length}`);
779
+ console.log(` ${ui.blue("Tasks:")} ${epic.subtasks.length}`);
784
780
  console.log("");
785
781
  console.log(ui.dim("Description:"));
786
782
  console.log(` ${epic.description}`);
787
- if (epic.taskIds.length > 0) {
783
+ if (epic.subtasks.length > 0) {
788
784
  console.log("");
789
785
  console.log(ui.dim("Task IDs:"));
790
- for (const taskId of epic.taskIds) {
791
- console.log(` - ${taskId}`);
786
+ for (const subtask of epic.subtasks) {
787
+ console.log(` - ${subtask.id}`);
792
788
  }
793
789
  }
794
790
  }
@@ -854,7 +850,7 @@ function printAvailableIds(ui, context, backlog) {
854
850
 
855
851
  // src/cli/commands/init.ts
856
852
  import { constants as fsConstants2 } from "fs";
857
- import { access as access2, mkdir, writeFile } from "fs/promises";
853
+ import { access as access2, mkdir, readFile as readFile2, writeFile } from "fs/promises";
858
854
  import { resolve as resolve3 } from "path";
859
855
  import { checkbox, confirm, input } from "@inquirer/prompts";
860
856
  import { Command as Command6 } from "commander";
@@ -919,6 +915,7 @@ async function runMinimalInit(options, globalOptions, ui) {
919
915
  });
920
916
  await writeFile(configPath, configContent, "utf8");
921
917
  await mkdir(trackingDirectory, { recursive: true });
918
+ await ensureGitignoreEntry(process.cwd(), ".oac/");
922
919
  const summary = {
923
920
  configPath,
924
921
  trackingDirectory,
@@ -947,7 +944,7 @@ async function runInteractiveInit(globalOptions, ui) {
947
944
  choices: [
948
945
  { name: "Claude Code", value: "claude-code", checked: true },
949
946
  { name: "Codex CLI", value: "codex" },
950
- { name: "OpenCode (coming soon)", value: "opencode", disabled: true }
947
+ { name: "OpenCode", value: "opencode" }
951
948
  ],
952
949
  validate: (value) => value.length > 0 ? true : "Select at least one provider to continue."
953
950
  });
@@ -1007,6 +1004,7 @@ async function runInteractiveInit(globalOptions, ui) {
1007
1004
  });
1008
1005
  await writeFile(configPath, configContent, "utf8");
1009
1006
  await mkdir(trackingDirectory, { recursive: true });
1007
+ await ensureGitignoreEntry(process.cwd(), ".oac/");
1010
1008
  const summary = {
1011
1009
  configPath,
1012
1010
  trackingDirectory,
@@ -1082,9 +1080,23 @@ async function pathExists2(path) {
1082
1080
  return false;
1083
1081
  }
1084
1082
  }
1083
+ async function ensureGitignoreEntry(dir, entry) {
1084
+ const gitignorePath = resolve3(dir, ".gitignore");
1085
+ let content = "";
1086
+ try {
1087
+ content = await readFile2(gitignorePath, "utf8");
1088
+ } catch {
1089
+ }
1090
+ if (content.split("\n").some((line) => line.trim() === entry)) {
1091
+ return;
1092
+ }
1093
+ const separator = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
1094
+ await writeFile(gitignorePath, `${content}${separator}${entry}
1095
+ `, "utf8");
1096
+ }
1085
1097
 
1086
1098
  // src/cli/commands/leaderboard.ts
1087
- import { readFile as readFile2, readdir } from "fs/promises";
1099
+ import { readFile as readFile3, readdir } from "fs/promises";
1088
1100
  import { resolve as resolve4 } from "path";
1089
1101
  import Table2 from "cli-table3";
1090
1102
  import { Command as Command7 } from "commander";
@@ -1144,7 +1156,7 @@ Examples:
1144
1156
  async function loadLeaderboardEntries(repoPath) {
1145
1157
  const leaderboardPath = resolve4(repoPath, ".oac", "leaderboard.json");
1146
1158
  try {
1147
- const leaderboardRaw = await readFile2(leaderboardPath, "utf8");
1159
+ const leaderboardRaw = await readFile3(leaderboardPath, "utf8");
1148
1160
  const leaderboardPayload = JSON.parse(leaderboardRaw);
1149
1161
  return parseStoredLeaderboardEntries(leaderboardPayload);
1150
1162
  } catch (error) {
@@ -1204,7 +1216,7 @@ async function readContributionLogs(repoPath) {
1204
1216
  fileNames.map(async (fileName) => {
1205
1217
  const filePath = resolve4(contributionsPath, fileName);
1206
1218
  try {
1207
- const content = await readFile2(filePath, "utf8");
1219
+ const content = await readFile3(filePath, "utf8");
1208
1220
  const payload = JSON.parse(content);
1209
1221
  const parsed = contributionLogSchema.safeParse(payload);
1210
1222
  return parsed.success ? parsed.data : null;
@@ -1270,9 +1282,6 @@ function sortValue(entry, field) {
1270
1282
  }
1271
1283
  return entry.totalPRsCreated;
1272
1284
  }
1273
- function isRecord(value) {
1274
- return typeof value === "object" && value !== null;
1275
- }
1276
1285
  function isFileNotFoundError(error) {
1277
1286
  if (!isRecord(error)) {
1278
1287
  return false;
@@ -1281,7 +1290,7 @@ function isFileNotFoundError(error) {
1281
1290
  }
1282
1291
 
1283
1292
  // src/cli/commands/log.ts
1284
- import { readFile as readFile3, readdir as readdir2 } from "fs/promises";
1293
+ import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
1285
1294
  import { resolve as resolve5 } from "path";
1286
1295
  import Table3 from "cli-table3";
1287
1296
  import { Command as Command8 } from "commander";
@@ -1357,7 +1366,7 @@ async function readContributionLogs2(repoPath) {
1357
1366
  files.map(async (fileName) => {
1358
1367
  const filePath = resolve5(contributionsPath, fileName);
1359
1368
  try {
1360
- const content = await readFile3(filePath, "utf8");
1369
+ const content = await readFile4(filePath, "utf8");
1361
1370
  const payload = JSON.parse(content);
1362
1371
  const parsed = contributionLogSchema.safeParse(payload);
1363
1372
  return parsed.success ? parsed.data : null;
@@ -1547,7 +1556,7 @@ import { randomUUID } from "crypto";
1547
1556
 
1548
1557
  // src/cli/commands/run/epic.ts
1549
1558
  import Table6 from "cli-table3";
1550
- import PQueue2 from "p-queue";
1559
+ import PQueue3 from "p-queue";
1551
1560
 
1552
1561
  // src/cli/commands/run/pr.ts
1553
1562
  import { execa } from "execa";
@@ -1593,6 +1602,8 @@ function formatDuration(seconds) {
1593
1602
  }
1594
1603
 
1595
1604
  // src/cli/commands/run/pr.ts
1605
+ var GITHUB_API_BASE_URL = "https://api.github.com";
1606
+ var OAC_PR_TITLE_PREFIX = "[OAC]";
1596
1607
  async function createPullRequest(input2) {
1597
1608
  if (!input2.sandbox) {
1598
1609
  return void 0;
@@ -1604,6 +1615,19 @@ async function createPullRequest(input2) {
1604
1615
  ghEnv.GH_TOKEN = input2.ghToken;
1605
1616
  ghEnv.GITHUB_TOKEN = input2.ghToken;
1606
1617
  }
1618
+ if (input2.task.linkedIssue && input2.ghToken) {
1619
+ const duplicate = await findExistingOacPR(
1620
+ input2.repoFullName,
1621
+ input2.task.linkedIssue.number,
1622
+ input2.ghToken
1623
+ );
1624
+ if (duplicate) {
1625
+ console.warn(
1626
+ `[oac] Skipping PR: existing OAC PR #${duplicate} already targets issue #${input2.task.linkedIssue.number}`
1627
+ );
1628
+ return void 0;
1629
+ }
1630
+ }
1607
1631
  await execa("git", ["push", "--set-upstream", "origin", branchName], {
1608
1632
  cwd: sandboxPath,
1609
1633
  env: ghEnv,
@@ -1668,11 +1692,49 @@ async function createPullRequest(input2) {
1668
1692
  return void 0;
1669
1693
  }
1670
1694
  }
1695
+ async function findExistingOacPR(repoFullName, issueNumber, token) {
1696
+ const url = `${GITHUB_API_BASE_URL}/repos/${repoFullName}/pulls?state=open&per_page=100&sort=updated&direction=desc`;
1697
+ try {
1698
+ const response = await fetch(url, {
1699
+ method: "GET",
1700
+ headers: {
1701
+ Authorization: `Bearer ${token}`,
1702
+ Accept: "application/vnd.github.v3+json"
1703
+ },
1704
+ signal: AbortSignal.timeout(15e3)
1705
+ });
1706
+ if (!response.ok) {
1707
+ return void 0;
1708
+ }
1709
+ const pulls = await response.json();
1710
+ if (!Array.isArray(pulls)) {
1711
+ return void 0;
1712
+ }
1713
+ const issueRefPattern = /(?:Fixes|Closes|Resolves)\s+#(\d+)/gi;
1714
+ for (const pr of pulls) {
1715
+ if (!pr || typeof pr !== "object") continue;
1716
+ const record = pr;
1717
+ const title = typeof record.title === "string" ? record.title : "";
1718
+ if (!title.startsWith(OAC_PR_TITLE_PREFIX)) continue;
1719
+ const prNumber = typeof record.number === "number" ? record.number : void 0;
1720
+ const body = typeof record.body === "string" ? record.body : "";
1721
+ for (const match of body.matchAll(issueRefPattern)) {
1722
+ const num = Number.parseInt(match[1], 10);
1723
+ if (num === issueNumber) {
1724
+ return prNumber;
1725
+ }
1726
+ }
1727
+ }
1728
+ return void 0;
1729
+ } catch {
1730
+ return void 0;
1731
+ }
1732
+ }
1671
1733
 
1672
1734
  // src/cli/commands/run/task.ts
1673
1735
  import Table5 from "cli-table3";
1674
1736
  import { execa as execa2 } from "execa";
1675
- import PQueue from "p-queue";
1737
+ import PQueue2 from "p-queue";
1676
1738
  async function discoverTasks(ctx, options, config, ghToken, resolvedRepo) {
1677
1739
  const scannerSelection = selectScannersFromConfig2(config, Boolean(ghToken));
1678
1740
  const minPriority = config?.discovery.minPriority ?? 20;
@@ -1795,7 +1857,7 @@ async function executePlan(ctx, params) {
1795
1857
  `Executing ${plan.selectedTasks.length} planned task(s)...`
1796
1858
  );
1797
1859
  let completedCount = 0;
1798
- const taskQueue = new PQueue({ concurrency });
1860
+ const taskQueue = new PQueue2({ concurrency });
1799
1861
  const executedTasks = await Promise.all(
1800
1862
  plan.selectedTasks.map(
1801
1863
  (entry) => taskQueue.add(async () => {
@@ -1820,7 +1882,7 @@ async function executePlan(ctx, params) {
1820
1882
  );
1821
1883
  executionSpinner?.succeed("Execution stage finished");
1822
1884
  const completionSpinner = createSpinner(ctx.suppressOutput, "Completing task outputs...");
1823
- const completionQueue = new PQueue({ concurrency });
1885
+ const completionQueue = new PQueue2({ concurrency });
1824
1886
  const completedTasks = await Promise.all(
1825
1887
  executedTasks.map(
1826
1888
  (result) => completionQueue.add(async () => {
@@ -1891,33 +1953,8 @@ function printFinalSummary(ctx, params) {
1891
1953
  }
1892
1954
  }
1893
1955
  function selectScannersFromConfig2(config, hasGitHubAuth) {
1894
- const enabled = [];
1895
- if (config?.discovery.scanners.lint !== false) {
1896
- enabled.push("lint");
1897
- }
1898
- if (config?.discovery.scanners.todo !== false) {
1899
- enabled.push("todo");
1900
- }
1901
- if (config?.discovery.scanners.testGap !== false) {
1902
- enabled.push("test-gap");
1903
- }
1904
- if (hasGitHubAuth) {
1905
- enabled.push("github-issues");
1906
- }
1907
- if (enabled.length === 0) {
1908
- enabled.push("lint", "todo", "test-gap");
1909
- }
1910
- const uniqueEnabled = [...new Set(enabled)];
1911
- const scannerInstances = uniqueEnabled.map((scannerName) => {
1912
- if (scannerName === "lint") return new LintScanner();
1913
- if (scannerName === "github-issues") return new GitHubIssuesScanner();
1914
- if (scannerName === "test-gap") return new TestGapScanner();
1915
- return new TodoScanner();
1916
- });
1917
- return {
1918
- enabled: uniqueEnabled,
1919
- scanner: new CompositeScanner(scannerInstances)
1920
- };
1956
+ const { names, composite } = buildScanners(config, hasGitHubAuth);
1957
+ return { enabled: names, scanner: composite };
1921
1958
  }
1922
1959
  async function executeWithAgent(input2) {
1923
1960
  const startedAt = Date.now();
@@ -2000,15 +2037,12 @@ Automated contribution by OAC using Codex CLI.`],
2000
2037
  }
2001
2038
  }
2002
2039
  async function resolveAdapter(providerId) {
2003
- const normalizedId = providerId === "codex-cli" ? "codex" : providerId;
2004
- const adapters = {
2005
- codex: () => new CodexAdapter(),
2006
- "claude-code": () => new ClaudeCodeAdapter()
2007
- };
2008
- const factory = adapters[normalizedId];
2040
+ const normalizedId = adapterRegistry.resolveId(providerId);
2041
+ const factory = adapterRegistry.get(providerId);
2009
2042
  if (!factory) {
2043
+ const supported = adapterRegistry.registeredIds().join(", ");
2010
2044
  throw new Error(
2011
- `Unknown provider "${providerId}". Supported providers: codex, claude-code.
2045
+ `Unknown provider "${providerId}". Supported providers: ${supported}.
2012
2046
  Run \`oac doctor\` to check your environment setup.`
2013
2047
  );
2014
2048
  }
@@ -2016,7 +2050,7 @@ Run \`oac doctor\` to check your environment setup.`
2016
2050
  const availability = await adapter.checkAvailability();
2017
2051
  if (!availability.available) {
2018
2052
  throw new Error(
2019
- `Agent CLI "${normalizedId}" is not available: ${availability.reason ?? "unknown reason"}.
2053
+ `Agent CLI "${normalizedId}" is not available: ${availability.error ?? "unknown reason"}.
2020
2054
  Install the ${normalizedId} CLI or switch providers.
2021
2055
  Run \`oac doctor\` for setup instructions.`
2022
2056
  );
@@ -2254,14 +2288,7 @@ async function tryLoadOrAnalyzeEpics(ctx, params) {
2254
2288
  return getPendingEpics(backlog);
2255
2289
  }
2256
2290
  function buildScannerList2(config, hasGitHubAuth) {
2257
- const scanners = [];
2258
- if (config?.discovery.scanners.lint !== false) scanners.push(new LintScanner());
2259
- if (config?.discovery.scanners.todo !== false) scanners.push(new TodoScanner());
2260
- if (config?.discovery.scanners.testGap !== false) {
2261
- scanners.push(new TestGapScanner());
2262
- }
2263
- if (hasGitHubAuth) scanners.push(new GitHubIssuesScanner());
2264
- return scanners;
2291
+ return buildScanners(config, hasGitHubAuth).instances;
2265
2292
  }
2266
2293
  function makeStubEstimate(taskId, providerId, tokens) {
2267
2294
  return {
@@ -2348,7 +2375,7 @@ async function runEpicPipeline(ctx, params) {
2348
2375
  ctx.suppressOutput,
2349
2376
  `Executing ${epicTotal} epic(s)...`
2350
2377
  );
2351
- const epicQueue = new PQueue2({ concurrency });
2378
+ const epicQueue = new PQueue3({ concurrency });
2352
2379
  const allTaskResults = await Promise.all(
2353
2380
  epicPlan.selectedEpics.map(
2354
2381
  (entry) => epicQueue.add(async () => {
@@ -2496,7 +2523,7 @@ function renderEpicPlanTable(ui, plan, budget) {
2496
2523
  }
2497
2524
 
2498
2525
  // src/cli/commands/run/retry.ts
2499
- import { readFile as readFile4, readdir as readdir3 } from "fs/promises";
2526
+ import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
2500
2527
  import { resolve as resolve6 } from "path";
2501
2528
  async function readMostRecentContributionLog(repoPath) {
2502
2529
  const contributionsPath = resolve6(repoPath, ".oac", "contributions");
@@ -2509,7 +2536,7 @@ async function readMostRecentContributionLog(repoPath) {
2509
2536
  }
2510
2537
  for (const fileName of entries) {
2511
2538
  try {
2512
- const content = await readFile4(resolve6(contributionsPath, fileName), "utf8");
2539
+ const content = await readFile5(resolve6(contributionsPath, fileName), "utf8");
2513
2540
  const parsed = contributionLogSchema.safeParse(JSON.parse(content));
2514
2541
  if (parsed.success) return parsed.data;
2515
2542
  } catch {
@@ -2967,7 +2994,7 @@ function parseCsv(value) {
2967
2994
  }
2968
2995
 
2969
2996
  // src/cli/commands/status.ts
2970
- import { readFile as readFile5 } from "fs/promises";
2997
+ import { readFile as readFile6 } from "fs/promises";
2971
2998
  import { resolve as resolve7 } from "path";
2972
2999
  import { Command as Command12 } from "commander";
2973
3000
  var WATCH_INTERVAL_MS = 2e3;
@@ -3009,7 +3036,7 @@ Examples:
3009
3036
  async function readRunStatus(repoPath) {
3010
3037
  const statusPath = resolve7(repoPath, ".oac", "status.json");
3011
3038
  try {
3012
- const raw = await readFile5(statusPath, "utf8");
3039
+ const raw = await readFile6(statusPath, "utf8");
3013
3040
  const payload = JSON.parse(raw);
3014
3041
  return parseRunStatus(payload);
3015
3042
  } catch (error) {
@@ -3020,7 +3047,7 @@ async function readRunStatus(repoPath) {
3020
3047
  }
3021
3048
  }
3022
3049
  function parseRunStatus(payload) {
3023
- if (!isRecord2(payload)) {
3050
+ if (!isRecord(payload)) {
3024
3051
  throw new Error("Invalid .oac/status.json format.");
3025
3052
  }
3026
3053
  const runId = payload.runId;
@@ -3038,7 +3065,7 @@ function parseRunStatus(payload) {
3038
3065
  };
3039
3066
  }
3040
3067
  function parseRunStatusTask(task, index) {
3041
- if (!isRecord2(task)) {
3068
+ if (!isRecord(task)) {
3042
3069
  throw new Error(`Invalid task at index ${String(index)} in .oac/status.json.`);
3043
3070
  }
3044
3071
  const taskId = task.taskId;
@@ -3126,11 +3153,8 @@ function formatTaskList(tasks) {
3126
3153
  }
3127
3154
  return tasks.map((task) => `${task.taskId} (${task.title})`).join(", ");
3128
3155
  }
3129
- function isRecord2(value) {
3130
- return typeof value === "object" && value !== null;
3131
- }
3132
3156
  function isFileNotFoundError3(error) {
3133
- if (!isRecord2(error)) {
3157
+ if (!isRecord(error)) {
3134
3158
  return false;
3135
3159
  }
3136
3160
  return error.code === "ENOENT";
@@ -3151,7 +3175,7 @@ function registerCommands(program) {
3151
3175
  program.addCommand(createExplainCommand());
3152
3176
  }
3153
3177
  async function createCliProgram() {
3154
- const version = true ? "2026.4.3" : "0.0.0";
3178
+ const version = true ? "2026.220.2" : "0.0.0";
3155
3179
  const program = new Command13();
3156
3180
  program.name("oac").description("Open Agent Contribution CLI").version(version).option("--config <path>", "Config file path", "oac.config.ts").option("--verbose", "Enable verbose logging", false).option("--quiet", "Suppress non-error output", false).option("--json", "Output machine-readable JSON", false).option("--no-color", "Disable ANSI colors");
3157
3181
  registerCommands(program);
@@ -3181,4 +3205,4 @@ export {
3181
3205
  createCliProgram,
3182
3206
  runCli
3183
3207
  };
3184
- //# sourceMappingURL=chunk-XUW3XWTX.js.map
3208
+ //# sourceMappingURL=chunk-LRTQCVQK.js.map