@open330/oac 2026.221.2 → 2026.222.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/README.md +170 -1
  2. package/dist/budget/index.js +1 -1
  3. package/dist/{chunk-EYUQMPVO.js → chunk-27FEE5KS.js} +86 -34
  4. package/dist/chunk-27FEE5KS.js.map +1 -0
  5. package/dist/{chunk-5GAUWC3L.js → chunk-ALBVUNUY.js} +1 -1
  6. package/dist/chunk-ALBVUNUY.js.map +1 -0
  7. package/dist/{chunk-VK33A5L4.js → chunk-ATVWSG75.js} +480 -232
  8. package/dist/chunk-ATVWSG75.js.map +1 -0
  9. package/dist/{chunk-7C7SC4TZ.js → chunk-I3TKNT4M.js} +9 -2
  10. package/dist/chunk-I3TKNT4M.js.map +1 -0
  11. package/dist/{chunk-6A37SKAJ.js → chunk-JDFAJP45.js} +1 -1
  12. package/dist/{chunk-6A37SKAJ.js.map → chunk-JDFAJP45.js.map} +1 -1
  13. package/dist/{chunk-OS3XDHOJ.js → chunk-UCYK4Z6O.js} +1 -1
  14. package/dist/chunk-UCYK4Z6O.js.map +1 -0
  15. package/dist/{chunk-OCCMKAJI.js → chunk-ZJBLRKCV.js} +3 -3
  16. package/dist/chunk-ZJBLRKCV.js.map +1 -0
  17. package/dist/cli/cli.js +7 -7
  18. package/dist/cli/index.js +7 -7
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/completion/index.d.ts +1 -1
  21. package/dist/completion/index.js +2 -2
  22. package/dist/completion/index.js.map +1 -1
  23. package/dist/{config-DequKoFA.d.ts → config-DnzZ7w92.d.ts} +60 -1
  24. package/dist/core/index.d.ts +1 -1
  25. package/dist/core/index.js +4 -2
  26. package/dist/dashboard/index.js +72 -23
  27. package/dist/dashboard/index.js.map +1 -1
  28. package/dist/discovery/index.d.ts +1 -1
  29. package/dist/discovery/index.js +2 -2
  30. package/dist/execution/index.js +3 -3
  31. package/dist/repo/index.js +1 -1
  32. package/package.json +13 -15
  33. package/dist/chunk-5GAUWC3L.js.map +0 -1
  34. package/dist/chunk-7C7SC4TZ.js.map +0 -1
  35. package/dist/chunk-EYUQMPVO.js.map +0 -1
  36. package/dist/chunk-OCCMKAJI.js.map +0 -1
  37. package/dist/chunk-OS3XDHOJ.js.map +0 -1
  38. package/dist/chunk-VK33A5L4.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  cloneRepo,
3
3
  resolveRepo
4
- } from "./chunk-OS3XDHOJ.js";
4
+ } from "./chunk-UCYK4Z6O.js";
5
5
  import {
6
6
  CompositeScanner,
7
7
  GitHubIssuesScanner,
@@ -20,39 +20,39 @@ import {
20
20
  persistContext,
21
21
  rankTasks,
22
22
  updateBacklog
23
- } from "./chunk-OCCMKAJI.js";
23
+ } from "./chunk-ZJBLRKCV.js";
24
24
  import {
25
25
  buildEpicExecutionPlan,
26
26
  buildExecutionPlan,
27
27
  estimateEpicTokens,
28
28
  estimateTokens
29
- } from "./chunk-5GAUWC3L.js";
29
+ } from "./chunk-ALBVUNUY.js";
30
30
  import {
31
31
  adapterRegistry,
32
32
  createSandbox,
33
33
  epicAsTask,
34
34
  executeTask
35
- } from "./chunk-EYUQMPVO.js";
35
+ } from "./chunk-27FEE5KS.js";
36
36
  import {
37
37
  UNLIMITED_BUDGET,
38
38
  createEventBus,
39
39
  loadConfig
40
- } from "./chunk-7C7SC4TZ.js";
40
+ } from "./chunk-I3TKNT4M.js";
41
41
  import {
42
42
  isRecord,
43
43
  truncate
44
- } from "./chunk-6A37SKAJ.js";
44
+ } from "./chunk-JDFAJP45.js";
45
45
  import {
46
46
  contributionLogSchema,
47
47
  writeContributionLog
48
48
  } from "./chunk-LQC5DLT7.js";
49
49
 
50
50
  // src/cli/cli.ts
51
- import { Command as Command13 } from "commander";
51
+ import { Command as Command12 } from "commander";
52
52
 
53
53
  // src/cli/commands/analyze.ts
54
54
  import Table from "cli-table3";
55
- import { Command as Command2 } from "commander";
55
+ import { Command } from "commander";
56
56
 
57
57
  // src/cli/github-auth.ts
58
58
  import { execFileSync, spawnSync } from "child_process";
@@ -100,7 +100,6 @@ function checkGitHubScopes(required = ["repo"]) {
100
100
 
101
101
  // src/cli/helpers.ts
102
102
  import chalk, { Chalk } from "chalk";
103
- import "commander";
104
103
  import ora from "ora";
105
104
  import PQueue from "p-queue";
106
105
 
@@ -269,10 +268,8 @@ async function estimateTaskMap(tasks, providerId, onProgress) {
269
268
 
270
269
  // src/cli/commands/analyze.ts
271
270
  function createAnalyzeCommand() {
272
- const command = new Command2("analyze");
273
- command.description(
274
- "Deep codebase analysis \u2014 build module graph and group findings into epics"
275
- ).option("--repo <owner/repo>", "Target repository (owner/repo or GitHub URL)").option("--force", "Force re-analysis even if context is fresh", false).option("--format <format>", "Output format: table|json", "table").action(async (options, cmd) => {
271
+ const command = new Command("analyze");
272
+ command.description("Deep codebase analysis \u2014 build module graph and group findings into epics").option("--repo <owner/repo>", "Target repository (owner/repo or GitHub URL)").option("--force", "Force re-analysis even if context is fresh", false).option("--format <format>", "Output format: table|json", "table").action(async (options, cmd) => {
276
273
  const globalOptions = getGlobalOptions(cmd);
277
274
  const ui = createUi(globalOptions);
278
275
  const outputJson = globalOptions.json || normalizeOutputFormat(options.format) === "json";
@@ -379,7 +376,7 @@ function buildScannerList(config, hasGitHubAuth) {
379
376
  }
380
377
 
381
378
  // src/cli/commands/completion.ts
382
- import { Command as Command3 } from "commander";
379
+ import { Command as Command2 } from "commander";
383
380
  var SUBCOMMANDS = [
384
381
  "init",
385
382
  "analyze",
@@ -392,7 +389,15 @@ var SUBCOMMANDS = [
392
389
  "status",
393
390
  "completion"
394
391
  ];
395
- var GLOBAL_OPTIONS = ["--config", "--verbose", "--quiet", "--json", "--no-color", "--help", "--version"];
392
+ var GLOBAL_OPTIONS = [
393
+ "--config",
394
+ "--verbose",
395
+ "--quiet",
396
+ "--json",
397
+ "--no-color",
398
+ "--help",
399
+ "--version"
400
+ ];
396
401
  var COMMAND_OPTIONS = {
397
402
  run: [
398
403
  "--repo",
@@ -477,17 +482,19 @@ function generateFish() {
477
482
  for (const [cmd, opts] of Object.entries(COMMAND_OPTIONS)) {
478
483
  for (const opt of opts) {
479
484
  const long = opt.replace(/^--/, "");
480
- lines.push(
481
- `complete -c oac -n '__fish_seen_subcommand_from ${cmd}' -l '${long}'`
482
- );
485
+ lines.push(`complete -c oac -n '__fish_seen_subcommand_from ${cmd}' -l '${long}'`);
483
486
  }
484
487
  }
485
488
  return lines.join("\n");
486
489
  }
487
490
  function createCompletionCommand() {
488
- const command = new Command3("completion");
491
+ const command = new Command2("completion");
489
492
  command.description("Generate shell completion scripts").argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
490
- const generators = { bash: generateBash, zsh: generateZsh, fish: generateFish };
493
+ const generators = {
494
+ bash: generateBash,
495
+ zsh: generateZsh,
496
+ fish: generateFish
497
+ };
491
498
  const gen = generators[shell];
492
499
  if (!gen) {
493
500
  throw new Error(`Unsupported shell "${shell}". Supported: bash, zsh, fish`);
@@ -506,10 +513,10 @@ Examples:
506
513
  }
507
514
 
508
515
  // src/cli/commands/doctor.ts
509
- import { Command as Command4 } from "commander";
516
+ import { Command as Command3 } from "commander";
510
517
  var MINIMUM_NODE_VERSION = "24.0.0";
511
518
  function createDoctorCommand() {
512
- const command = new Command4("doctor");
519
+ const command = new Command3("doctor");
513
520
  command.description("Check local environment readiness").action(async (_options, cmd) => {
514
521
  const globalOptions = getGlobalOptions(cmd);
515
522
  const ui = createUi(globalOptions);
@@ -722,8 +729,8 @@ function isVersionAtLeast(version, minimum) {
722
729
  }
723
730
  async function runCommand(command, args) {
724
731
  try {
725
- const { execa: execa3 } = await import("execa");
726
- const result = await execa3(command, args, {
732
+ const { execa: execa4 } = await import("execa");
733
+ const result = await execa4(command, args, {
727
734
  reject: false,
728
735
  timeout: 1e4,
729
736
  stdin: "ignore"
@@ -749,9 +756,9 @@ async function runCommand(command, args) {
749
756
 
750
757
  // src/cli/commands/explain.ts
751
758
  import { resolve as resolve2 } from "path";
752
- import { Command as Command5 } from "commander";
759
+ import { Command as Command4 } from "commander";
753
760
  function createExplainCommand() {
754
- const command = new Command5("explain");
761
+ const command = new Command4("explain");
755
762
  command.description("Explain why a task or epic was selected and what the agent would do").argument("<id>", "Task or epic ID (from scan / analyze / run --dry-run output)").action(async (id, _options, cmd) => {
756
763
  const globalOptions = getGlobalOptions(cmd);
757
764
  const ui = createUi(globalOptions);
@@ -833,11 +840,13 @@ function createExplainCommand() {
833
840
  console.log(` ${finding.description}`);
834
841
  console.log("");
835
842
  console.log(ui.dim("What the agent would do:"));
836
- console.log(` 1. Check out a clean branch for this task`);
837
- console.log(` 2. Open ${finding.filePath}${finding.line ? ` at line ${finding.line}` : ""}`);
838
- console.log(` 3. Apply the fix described above`);
839
- console.log(` 4. Run tests and linters to verify`);
840
- console.log(` 5. Create a PR with the changes`);
843
+ console.log(" 1. Check out a clean branch for this task");
844
+ console.log(
845
+ ` 2. Open ${finding.filePath}${finding.line ? ` at line ${finding.line}` : ""}`
846
+ );
847
+ console.log(" 3. Apply the fix described above");
848
+ console.log(" 4. Run tests and linters to verify");
849
+ console.log(" 5. Create a PR with the changes");
841
850
  }
842
851
  });
843
852
  command.addHelpText(
@@ -878,7 +887,7 @@ import { constants as fsConstants2 } from "fs";
878
887
  import { access as access2, mkdir, readFile as readFile2, writeFile } from "fs/promises";
879
888
  import { resolve as resolve3 } from "path";
880
889
  import { checkbox, confirm, input } from "@inquirer/prompts";
881
- import { Command as Command6 } from "commander";
890
+ import { Command as Command5 } from "commander";
882
891
  var OAC_LOGO = [
883
892
  " ___ _ ___",
884
893
  " / _ \\ /_\\ / __|",
@@ -887,7 +896,7 @@ var OAC_LOGO = [
887
896
  ].join("\n");
888
897
  var OWNER_REPO_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?$/;
889
898
  function createInitCommand() {
890
- const command = new Command6("init");
899
+ const command = new Command5("init");
891
900
  command.description("Initialize OAC in the current directory").option("--minimal", "Generate a bare-bones config without the interactive wizard").option("--repo <owner/repo>", "Repository in owner/repo format (required with --minimal)").action(async (options, cmd) => {
892
901
  const globalOptions = getGlobalOptions(cmd);
893
902
  const ui = createUi(globalOptions);
@@ -1124,9 +1133,9 @@ async function ensureGitignoreEntry(dir, entry) {
1124
1133
  import { readFile as readFile3, readdir } from "fs/promises";
1125
1134
  import { resolve as resolve4 } from "path";
1126
1135
  import Table2 from "cli-table3";
1127
- import { Command as Command7 } from "commander";
1136
+ import { Command as Command6 } from "commander";
1128
1137
  function createLeaderboardCommand() {
1129
- const command = new Command7("leaderboard");
1138
+ const command = new Command6("leaderboard");
1130
1139
  command.description("Show contribution rankings").option("--limit <number>", "Max entries to show", parseInteger, 10).option("--sort <field>", "Sort by: runs, tasks, tokens, prs", "tasks").action(async (options, cmd) => {
1131
1140
  if (options.limit <= 0) {
1132
1141
  throw new Error("--limit must be a positive integer.");
@@ -1318,9 +1327,9 @@ function isFileNotFoundError(error) {
1318
1327
  import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
1319
1328
  import { resolve as resolve5 } from "path";
1320
1329
  import Table3 from "cli-table3";
1321
- import { Command as Command8 } from "commander";
1330
+ import { Command as Command7 } from "commander";
1322
1331
  function createLogCommand() {
1323
- const command = new Command8("log");
1332
+ const command = new Command7("log");
1324
1333
  command.description("View contribution history").option("--limit <number>", "Max entries to show", parseInteger, 20).option("--repo <name>", "Filter by repo name").option("--source <type>", "Filter by task source").option("--since <date>", "Filter contributions after date (ISO string)").action(async (options, cmd) => {
1325
1334
  if (options.limit <= 0) {
1326
1335
  throw new Error("--limit must be a positive integer.");
@@ -1438,9 +1447,9 @@ function isFileNotFoundError2(error) {
1438
1447
 
1439
1448
  // src/cli/commands/plan.ts
1440
1449
  import Table4 from "cli-table3";
1441
- import { Command as Command9 } from "commander";
1450
+ import { Command as Command8 } from "commander";
1442
1451
  function createPlanCommand() {
1443
- const command = new Command9("plan");
1452
+ const command = new Command8("plan");
1444
1453
  command.description("Build an execution plan from discovered tasks").option("--repo <owner/repo>", "Target repository (owner/repo or GitHub URL)").option("--tokens <number>", "Token budget for planning", parseInteger).option("--provider <id>", "Agent provider id").action(async (options, cmd) => {
1445
1454
  const globalOptions = getGlobalOptions(cmd);
1446
1455
  const ui = createUi(globalOptions);
@@ -1575,17 +1584,15 @@ function renderPlan(ui, data) {
1575
1584
  }
1576
1585
 
1577
1586
  // src/cli/commands/run/index.ts
1578
- import { Command as Command10 } from "commander";
1587
+ import { Command as Command9 } from "commander";
1579
1588
 
1580
1589
  // src/cli/commands/run/pipeline.ts
1581
1590
  import { randomUUID } from "crypto";
1582
1591
 
1583
- // src/cli/commands/run/epic.ts
1584
- import Table6 from "cli-table3";
1585
- import PQueue3 from "p-queue";
1586
-
1587
- // src/cli/commands/run/pr.ts
1588
- import { execa } from "execa";
1592
+ // src/cli/commands/run/context-policy.ts
1593
+ import { createHash } from "crypto";
1594
+ import { readFile as readFile5, readdir as readdir3, stat } from "fs/promises";
1595
+ import { join, relative, resolve as resolve6 } from "path";
1589
1596
 
1590
1597
  // src/cli/commands/run/types.ts
1591
1598
  var DEFAULT_TIMEOUT_SECONDS = 300;
@@ -1627,7 +1634,140 @@ function formatDuration(seconds) {
1627
1634
  return `${minutes}m ${remainingSeconds}s`;
1628
1635
  }
1629
1636
 
1637
+ // src/cli/commands/run/context-policy.ts
1638
+ async function resolveContextAck(repoPath, config, ui, suppressOutput) {
1639
+ const mode = config?.context.mode ?? "off";
1640
+ if (mode === "off") {
1641
+ return void 0;
1642
+ }
1643
+ const requiredGlobs = (config?.context.requiredGlobs ?? []).filter(
1644
+ (item) => item.trim().length > 0
1645
+ );
1646
+ if (requiredGlobs.length === 0) {
1647
+ return void 0;
1648
+ }
1649
+ const files = await collectFilesForGlobs(repoPath, requiredGlobs);
1650
+ if (files.length === 0) {
1651
+ const message = `Missing required context files for run policy (${requiredGlobs.join(
1652
+ ", "
1653
+ )}). Create repository-owned markdown plans under .context/plans and retry.`;
1654
+ if (mode === "enforce") {
1655
+ throw new ConfigError(message);
1656
+ }
1657
+ if (!suppressOutput) {
1658
+ console.warn(ui.yellow(`[oac] Context policy warning: ${message}`));
1659
+ }
1660
+ return void 0;
1661
+ }
1662
+ const maxItems = config?.context.maxAckItems ?? 3;
1663
+ const summary = await summarizeContextFiles(repoPath, files.slice(0, maxItems));
1664
+ const digest = await hashContextFiles(repoPath, files);
1665
+ if (!suppressOutput) {
1666
+ console.log(
1667
+ ui.blue(
1668
+ `[oac] Context policy loaded ${files.length} file(s): ${files.slice(0, maxItems).join(", ")}`
1669
+ )
1670
+ );
1671
+ }
1672
+ return {
1673
+ mode,
1674
+ requiredGlobs,
1675
+ files,
1676
+ summary,
1677
+ digest
1678
+ };
1679
+ }
1680
+ async function collectFilesForGlobs(repoPath, globs) {
1681
+ const files = /* @__PURE__ */ new Set();
1682
+ for (const pattern of globs) {
1683
+ const root = resolveSearchRoot(repoPath, pattern);
1684
+ const rootStat = await safeStat(root);
1685
+ if (!rootStat?.isDirectory()) continue;
1686
+ const candidates = await listFilesRecursively(root);
1687
+ const matcher = createSimpleGlobMatcher(pattern);
1688
+ for (const candidate of candidates) {
1689
+ const relPath = toPosixPath(relative(repoPath, candidate));
1690
+ if (matcher(relPath)) {
1691
+ files.add(relPath);
1692
+ }
1693
+ }
1694
+ }
1695
+ return [...files].sort((a, b) => a.localeCompare(b));
1696
+ }
1697
+ function resolveSearchRoot(repoPath, pattern) {
1698
+ const normalized = toPosixPath(pattern);
1699
+ const wildcardIndex = normalized.search(/[\[*?]/);
1700
+ const prefix = wildcardIndex === -1 ? normalized : normalized.slice(0, wildcardIndex);
1701
+ const cleanedPrefix = prefix.replace(/\/+$|\/+$/g, "");
1702
+ const fallback = ".";
1703
+ return resolve6(repoPath, cleanedPrefix.length > 0 ? cleanedPrefix : fallback);
1704
+ }
1705
+ function createSimpleGlobMatcher(pattern) {
1706
+ const tokenized = toPosixPath(pattern).replaceAll("**/", "::DOUBLE_STAR_DIR::").replaceAll("**", "::DOUBLE_STAR::").replaceAll("*", "::STAR::").replaceAll("?", "::QUESTION::");
1707
+ const escaped = tokenized.replace(/[\\.^$+{}()|[\]]/g, "\\$&").replaceAll("::DOUBLE_STAR_DIR::", "(?:.*/)?").replaceAll("::DOUBLE_STAR::", ".*").replaceAll("::STAR::", "[^/]*").replaceAll("::QUESTION::", "[^/]");
1708
+ const regex = new RegExp(`^${escaped}$`);
1709
+ return (path) => regex.test(toPosixPath(path));
1710
+ }
1711
+ async function listFilesRecursively(rootDir) {
1712
+ const output = [];
1713
+ const queue = [rootDir];
1714
+ while (queue.length > 0) {
1715
+ const current = queue.pop();
1716
+ if (!current) continue;
1717
+ const entries = await readdir3(current, { withFileTypes: true });
1718
+ for (const entry of entries) {
1719
+ const fullPath = join(current, entry.name);
1720
+ if (entry.isDirectory()) {
1721
+ queue.push(fullPath);
1722
+ } else if (entry.isFile()) {
1723
+ output.push(fullPath);
1724
+ }
1725
+ }
1726
+ }
1727
+ return output;
1728
+ }
1729
+ async function summarizeContextFiles(repoPath, relPaths) {
1730
+ const lines = [];
1731
+ for (const relPath of relPaths) {
1732
+ const fullPath = resolve6(repoPath, relPath);
1733
+ const content = await readFile5(fullPath, "utf8");
1734
+ const condensed = content.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).filter((line) => line.startsWith("#") || line.startsWith("-") || line.startsWith("*"));
1735
+ const selected = condensed.slice(0, 3);
1736
+ if (selected.length > 0) {
1737
+ lines.push(`${relPath}: ${selected.join(" | ")}`);
1738
+ }
1739
+ }
1740
+ return lines;
1741
+ }
1742
+ async function hashContextFiles(repoPath, relPaths) {
1743
+ const hash = createHash("sha256");
1744
+ for (const relPath of relPaths) {
1745
+ const fullPath = resolve6(repoPath, relPath);
1746
+ const content = await readFile5(fullPath, "utf8");
1747
+ hash.update(relPath);
1748
+ hash.update("\n");
1749
+ hash.update(content);
1750
+ hash.update("\n");
1751
+ }
1752
+ return hash.digest("hex");
1753
+ }
1754
+ async function safeStat(path) {
1755
+ try {
1756
+ return await stat(path);
1757
+ } catch {
1758
+ return void 0;
1759
+ }
1760
+ }
1761
+ function toPosixPath(value) {
1762
+ return value.replaceAll("\\", "/");
1763
+ }
1764
+
1765
+ // src/cli/commands/run/epic.ts
1766
+ import Table6 from "cli-table3";
1767
+ import PQueue3 from "p-queue";
1768
+
1630
1769
  // src/cli/commands/run/pr.ts
1770
+ import { execa } from "execa";
1631
1771
  var GITHUB_API_BASE_URL = "https://api.github.com";
1632
1772
  var OAC_PR_TITLE_PREFIX = "[OAC]";
1633
1773
  async function createPullRequest(input2) {
@@ -1680,6 +1820,23 @@ async function createPullRequest(input2) {
1680
1820
  if (input2.task.linkedIssue) {
1681
1821
  prBodyLines.push(`- **Resolves:** #${input2.task.linkedIssue.number}`);
1682
1822
  }
1823
+ const contextAck = readContextAck(input2.task);
1824
+ if (contextAck) {
1825
+ prBodyLines.push("", "## Repository Policy Acknowledgement", "");
1826
+ prBodyLines.push("- **Context files read:**");
1827
+ for (const file of contextAck.files.slice(0, 5)) {
1828
+ prBodyLines.push(` - \`${file}\``);
1829
+ }
1830
+ if (contextAck.summary.length > 0) {
1831
+ prBodyLines.push("", "- **Policy summary used:**");
1832
+ for (const line of contextAck.summary.slice(0, 5)) {
1833
+ prBodyLines.push(` - ${line}`);
1834
+ }
1835
+ }
1836
+ if (contextAck.digest) {
1837
+ prBodyLines.push("", `- **Context digest:** \`${contextAck.digest}\``);
1838
+ }
1839
+ }
1683
1840
  prBodyLines.push(
1684
1841
  "",
1685
1842
  "---",
@@ -1718,6 +1875,23 @@ async function createPullRequest(input2) {
1718
1875
  return void 0;
1719
1876
  }
1720
1877
  }
1878
+ function readContextAck(task) {
1879
+ if (!task.metadata || typeof task.metadata !== "object" || task.metadata === null) {
1880
+ return void 0;
1881
+ }
1882
+ const raw = task.metadata.contextAck;
1883
+ if (!raw || typeof raw !== "object") return void 0;
1884
+ const record = raw;
1885
+ const files = Array.isArray(record.files) ? record.files.filter(
1886
+ (item) => typeof item === "string" && item.trim().length > 0
1887
+ ) : [];
1888
+ if (files.length === 0) return void 0;
1889
+ const summary = Array.isArray(record.summary) ? record.summary.filter(
1890
+ (item) => typeof item === "string" && item.trim().length > 0
1891
+ ) : [];
1892
+ const digest = typeof record.digest === "string" && record.digest.trim().length > 0 ? record.digest : void 0;
1893
+ return { files, summary, digest };
1894
+ }
1721
1895
  async function findExistingOacPR(repoFullName, issueNumber, token) {
1722
1896
  const url = `${GITHUB_API_BASE_URL}/repos/${repoFullName}/pulls?state=open&per_page=100&sort=updated&direction=desc`;
1723
1897
  try {
@@ -1759,8 +1933,186 @@ async function findExistingOacPR(repoFullName, issueNumber, token) {
1759
1933
 
1760
1934
  // src/cli/commands/run/task.ts
1761
1935
  import Table5 from "cli-table3";
1762
- import { execa as execa2 } from "execa";
1936
+ import { execa as execa3 } from "execa";
1763
1937
  import PQueue2 from "p-queue";
1938
+
1939
+ // src/cli/commands/run/tracking.ts
1940
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1941
+ import { join as join2, resolve as resolve7 } from "path";
1942
+ import { execa as execa2 } from "execa";
1943
+ async function writeTracking(ctx, params) {
1944
+ const { resolvedRepo, providerId, totalBudget, candidateTasks, completedTasks } = params;
1945
+ const runDurationSeconds = (Date.now() - ctx.runStartedAt) / 1e3;
1946
+ const contributionLog = buildContributionLog({
1947
+ runId: ctx.runId,
1948
+ repoFullName: resolvedRepo.fullName,
1949
+ repoHeadSha: resolvedRepo.git.headSha,
1950
+ defaultBranch: resolvedRepo.meta.defaultBranch,
1951
+ repoOwner: resolvedRepo.owner,
1952
+ providerId,
1953
+ totalBudget,
1954
+ runDurationSeconds,
1955
+ discoveredTasks: candidateTasks.length,
1956
+ taskResults: completedTasks
1957
+ });
1958
+ const trackingSpinner = createSpinner(ctx.suppressOutput, "Writing contribution log...");
1959
+ try {
1960
+ const logPath = await writeContributionLog(contributionLog, resolvedRepo.localPath);
1961
+ trackingSpinner?.succeed(`Contribution log written: ${logPath}`);
1962
+ return logPath;
1963
+ } catch (error) {
1964
+ trackingSpinner?.fail("Failed to write contribution log");
1965
+ if (ctx.globalOptions.verbose && !ctx.suppressOutput) {
1966
+ const message = error instanceof Error ? error.message : String(error);
1967
+ console.warn(ctx.ui.yellow(`[oac] Tracking failed: ${message}`));
1968
+ }
1969
+ return void 0;
1970
+ }
1971
+ }
1972
+ function buildContributionLog(input2) {
1973
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1974
+ const contributor = resolveGithubUsername(input2.repoOwner);
1975
+ const contributionTasks = input2.taskResults.map((result) => ({
1976
+ taskId: result.task.id,
1977
+ title: result.task.title,
1978
+ source: result.task.source,
1979
+ complexity: result.task.complexity,
1980
+ status: deriveTaskStatus(result.execution),
1981
+ tokensUsed: Math.max(0, Math.floor(result.execution.totalTokensUsed)),
1982
+ duration: Math.max(0, result.execution.duration),
1983
+ filesChanged: result.execution.filesChanged,
1984
+ pr: result.pr,
1985
+ linkedIssue: result.task.linkedIssue ? {
1986
+ number: result.task.linkedIssue.number,
1987
+ url: result.task.linkedIssue.url
1988
+ } : void 0,
1989
+ error: result.execution.error
1990
+ }));
1991
+ const tasksSucceeded = contributionTasks.filter((task) => task.status !== "failed").length;
1992
+ const tasksFailed = contributionTasks.length - tasksSucceeded;
1993
+ const totalTokensUsed = contributionTasks.reduce((sum, task) => sum + task.tokensUsed, 0);
1994
+ const totalFilesChanged = contributionTasks.reduce(
1995
+ (sum, task) => sum + task.filesChanged.length,
1996
+ 0
1997
+ );
1998
+ return {
1999
+ version: "1.0",
2000
+ runId: input2.runId,
2001
+ timestamp,
2002
+ contributor: {
2003
+ githubUsername: contributor,
2004
+ email: process.env.GIT_AUTHOR_EMAIL ?? void 0
2005
+ },
2006
+ repo: {
2007
+ fullName: input2.repoFullName,
2008
+ headSha: input2.repoHeadSha,
2009
+ defaultBranch: input2.defaultBranch
2010
+ },
2011
+ budget: {
2012
+ provider: input2.providerId,
2013
+ totalTokensBudgeted: input2.totalBudget,
2014
+ totalTokensUsed
2015
+ },
2016
+ tasks: contributionTasks,
2017
+ metrics: {
2018
+ tasksDiscovered: input2.discoveredTasks,
2019
+ tasksAttempted: contributionTasks.length,
2020
+ tasksSucceeded,
2021
+ tasksFailed,
2022
+ totalDuration: Math.max(0, input2.runDurationSeconds),
2023
+ totalFilesChanged,
2024
+ totalLinesAdded: 0,
2025
+ totalLinesRemoved: 0
2026
+ }
2027
+ };
2028
+ }
2029
+ function deriveTaskStatus(execution) {
2030
+ if (execution.success) {
2031
+ return "success";
2032
+ }
2033
+ if (execution.filesChanged.length > 0) {
2034
+ return "partial";
2035
+ }
2036
+ return "failed";
2037
+ }
2038
+ function resolveGithubUsername(fallback) {
2039
+ const candidates = [
2040
+ process.env.GITHUB_USER,
2041
+ process.env.GITHUB_USERNAME,
2042
+ process.env.USER,
2043
+ process.env.LOGNAME,
2044
+ fallback,
2045
+ "oac-user"
2046
+ ];
2047
+ for (const candidate of candidates) {
2048
+ const normalized = sanitizeGithubUsername(candidate ?? "");
2049
+ if (normalized) {
2050
+ return normalized;
2051
+ }
2052
+ }
2053
+ return "oac-user";
2054
+ }
2055
+ function sanitizeGithubUsername(value) {
2056
+ const trimmed = value.trim();
2057
+ if (!trimmed) {
2058
+ return null;
2059
+ }
2060
+ const cleaned = trimmed.replace(/[^A-Za-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2061
+ if (cleaned.length === 0 || cleaned.length > 39) {
2062
+ return null;
2063
+ }
2064
+ if (!/^(?!-)[A-Za-z0-9-]+(?<!-)$/.test(cleaned)) {
2065
+ return null;
2066
+ }
2067
+ return cleaned;
2068
+ }
2069
+ async function writeContributionToSandbox(input2) {
2070
+ const { sandboxPath, task, execution, runId, repoOwner } = input2;
2071
+ const contributionsDir = resolve7(sandboxPath, ".oac", "contributions");
2072
+ await mkdir2(contributionsDir, { recursive: true });
2073
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2074
+ const contributor = resolveGithubUsername(repoOwner);
2075
+ const datePrefix = timestamp.replace(/[:.]/g, "").replace("T", "-").slice(0, 15);
2076
+ const safeTaskId = task.id.replace(/[^A-Za-z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 40);
2077
+ const filename = `${datePrefix}-${safeTaskId}.json`;
2078
+ const metadata = {
2079
+ version: "1.0",
2080
+ runId,
2081
+ timestamp,
2082
+ contributor,
2083
+ task: {
2084
+ id: task.id,
2085
+ title: task.title,
2086
+ source: task.source,
2087
+ complexity: task.complexity,
2088
+ contextAck: isRecord2(task.metadata.contextAck) && Array.isArray(task.metadata.contextAck.files) ? {
2089
+ files: task.metadata.contextAck.files,
2090
+ summary: Array.isArray(task.metadata.contextAck.summary) ? task.metadata.contextAck.summary : [],
2091
+ digest: typeof task.metadata.contextAck.digest === "string" ? task.metadata.contextAck.digest : void 0
2092
+ } : void 0,
2093
+ linkedIssue: task.linkedIssue ? { number: task.linkedIssue.number, url: task.linkedIssue.url } : void 0
2094
+ },
2095
+ execution: {
2096
+ success: execution.success,
2097
+ tokensUsed: execution.totalTokensUsed,
2098
+ duration: execution.duration,
2099
+ filesChanged: execution.filesChanged
2100
+ }
2101
+ };
2102
+ const filePath = join2(contributionsDir, filename);
2103
+ await writeFile2(filePath, `${JSON.stringify(metadata, null, 2)}
2104
+ `, "utf8");
2105
+ try {
2106
+ await execa2("git", ["add", filePath], { cwd: sandboxPath });
2107
+ await execa2("git", ["commit", "-m", "[OAC] Add contribution metadata"], { cwd: sandboxPath });
2108
+ } catch {
2109
+ }
2110
+ }
2111
+ function isRecord2(value) {
2112
+ return typeof value === "object" && value !== null;
2113
+ }
2114
+
2115
+ // src/cli/commands/run/task.ts
1764
2116
  async function discoverTasks(ctx, options, config, ghToken, resolvedRepo) {
1765
2117
  const scannerSelection = selectScannersFromConfig2(config, Boolean(ghToken));
1766
2118
  const minPriority = config?.discovery.minPriority ?? 20;
@@ -1854,7 +2206,9 @@ function renderDryRunDiff(ui, plan) {
1854
2206
  const sourceLabel = task.source.replace(/-/g, " ");
1855
2207
  const complexityColor = task.complexity === "trivial" || task.complexity === "simple" ? ui.green : task.complexity === "moderate" ? ui.yellow : ui.red;
1856
2208
  console.log(`${ui.green("+")} ${ui.bold(task.title)}`);
1857
- console.log(` ${ui.dim(`source: ${sourceLabel} complexity: `)}${complexityColor(task.complexity)}`);
2209
+ console.log(
2210
+ ` ${ui.dim(`source: ${sourceLabel} complexity: `)}${complexityColor(task.complexity)}`
2211
+ );
1858
2212
  if (task.targetFiles.length > 0) {
1859
2213
  for (const file of task.targetFiles.slice(0, 5)) {
1860
2214
  console.log(` ${ui.yellow("~")} ${file}`);
@@ -1888,8 +2242,9 @@ async function executePlan(ctx, params) {
1888
2242
  const executedTasks = await Promise.all(
1889
2243
  plan.selectedTasks.map(
1890
2244
  (entry) => taskQueue.add(async () => {
2245
+ const taskForExecution = withContextAck(entry.task, ctx.contextAck);
1891
2246
  const result = await executeWithAgent({
1892
- task: entry.task,
2247
+ task: taskForExecution,
1893
2248
  estimate: entry.estimate,
1894
2249
  adapter,
1895
2250
  repoPath: resolvedRepo.localPath,
@@ -1903,7 +2258,7 @@ async function executePlan(ctx, params) {
1903
2258
  const pct = Math.round(completedCount / total * 100);
1904
2259
  executionSpinner.text = `Executing tasks... (${completedCount}/${total} \u2014 ${pct}%)`;
1905
2260
  }
1906
- return { task: entry.task, estimate: entry.estimate, execution, sandbox };
2261
+ return { task: taskForExecution, estimate: entry.estimate, execution, sandbox };
1907
2262
  })
1908
2263
  )
1909
2264
  );
@@ -1914,6 +2269,16 @@ async function executePlan(ctx, params) {
1914
2269
  executedTasks.map(
1915
2270
  (result) => completionQueue.add(async () => {
1916
2271
  if (mode === "direct-commit" || !result.execution.success) return result;
2272
+ if (result.sandbox) {
2273
+ await writeContributionToSandbox({
2274
+ sandboxPath: result.sandbox.sandboxPath,
2275
+ task: result.task,
2276
+ execution: result.execution,
2277
+ runId: ctx.runId,
2278
+ repoFullName: resolvedRepo.fullName,
2279
+ repoOwner: resolvedRepo.owner
2280
+ });
2281
+ }
1917
2282
  const pr = await createPullRequest({
1918
2283
  task: result.task,
1919
2284
  execution: result.execution,
@@ -1983,6 +2348,18 @@ function selectScannersFromConfig2(config, hasGitHubAuth) {
1983
2348
  const { names, composite } = buildScanners(config, hasGitHubAuth);
1984
2349
  return { enabled: names, scanner: composite };
1985
2350
  }
2351
+ function withContextAck(task, contextAck) {
2352
+ if (!contextAck) {
2353
+ return task;
2354
+ }
2355
+ return {
2356
+ ...task,
2357
+ metadata: {
2358
+ ...task.metadata,
2359
+ contextAck
2360
+ }
2361
+ };
2362
+ }
1986
2363
  async function executeWithAgent(input2) {
1987
2364
  const startedAt = Date.now();
1988
2365
  const taskSlug = input2.task.id.replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 30);
@@ -1999,7 +2376,7 @@ async function executeWithAgent(input2) {
1999
2376
  tokenBudget: input2.estimate.totalEstimatedTokens,
2000
2377
  timeoutMs: input2.timeoutSeconds * 1e3
2001
2378
  });
2002
- const commitResult = await commitSandboxChanges(sandbox.path, input2.task);
2379
+ const commitResult = await commitSandboxChanges(sandbox.path, input2.task, input2.baseBranch);
2003
2380
  const filesChanged = commitResult.filesChanged.length > 0 ? commitResult.filesChanged : result.filesChanged.length > 0 ? result.filesChanged : [];
2004
2381
  return {
2005
2382
  execution: {
@@ -2013,7 +2390,7 @@ async function executeWithAgent(input2) {
2013
2390
  sandbox: sandboxInfo
2014
2391
  };
2015
2392
  } catch (error) {
2016
- const commitResult = await commitSandboxChanges(sandbox.path, input2.task);
2393
+ const commitResult = await commitSandboxChanges(sandbox.path, input2.task, input2.baseBranch);
2017
2394
  if (commitResult.hasChanges) {
2018
2395
  return {
2019
2396
  execution: {
@@ -2040,25 +2417,24 @@ async function executeWithAgent(input2) {
2040
2417
  };
2041
2418
  }
2042
2419
  }
2043
- async function commitSandboxChanges(sandboxPath, task) {
2420
+ async function commitSandboxChanges(sandboxPath, task, baseBranch) {
2044
2421
  try {
2045
- const statusResult = await execa2("git", ["status", "--porcelain"], { cwd: sandboxPath });
2046
- if (!statusResult.stdout.trim()) {
2047
- return { hasChanges: false, filesChanged: [] };
2048
- }
2049
- await execa2("git", ["add", "-A"], { cwd: sandboxPath });
2050
- await execa2(
2051
- "git",
2052
- ["commit", "-m", `[OAC] ${task.title}
2422
+ const statusResult = await execa3("git", ["status", "--porcelain"], { cwd: sandboxPath });
2423
+ if (statusResult.stdout.trim()) {
2424
+ await execa3("git", ["add", "-A"], { cwd: sandboxPath });
2425
+ await execa3(
2426
+ "git",
2427
+ ["commit", "-m", `[OAC] ${task.title}
2053
2428
 
2054
- Automated contribution by OAC using Codex CLI.`],
2055
- { cwd: sandboxPath }
2056
- );
2057
- const diffResult = await execa2("git", ["diff", "--name-only", "HEAD~1", "HEAD"], {
2429
+ Automated contribution by OAC.`],
2430
+ { cwd: sandboxPath }
2431
+ );
2432
+ }
2433
+ const diffResult = await execa3("git", ["diff", "--name-only", `origin/${baseBranch}`, "HEAD"], {
2058
2434
  cwd: sandboxPath
2059
2435
  });
2060
2436
  const changedFiles = diffResult.stdout.trim().split("\n").filter(Boolean);
2061
- return { hasChanges: true, filesChanged: changedFiles };
2437
+ return { hasChanges: changedFiles.length > 0, filesChanged: changedFiles };
2062
2438
  } catch {
2063
2439
  return { hasChanges: false, filesChanged: [] };
2064
2440
  }
@@ -2143,134 +2519,6 @@ function renderTaskResults(ui, taskResults) {
2143
2519
  }
2144
2520
  }
2145
2521
 
2146
- // src/cli/commands/run/tracking.ts
2147
- async function writeTracking(ctx, params) {
2148
- const { resolvedRepo, providerId, totalBudget, candidateTasks, completedTasks } = params;
2149
- const runDurationSeconds = (Date.now() - ctx.runStartedAt) / 1e3;
2150
- const contributionLog = buildContributionLog({
2151
- runId: ctx.runId,
2152
- repoFullName: resolvedRepo.fullName,
2153
- repoHeadSha: resolvedRepo.git.headSha,
2154
- defaultBranch: resolvedRepo.meta.defaultBranch,
2155
- repoOwner: resolvedRepo.owner,
2156
- providerId,
2157
- totalBudget,
2158
- runDurationSeconds,
2159
- discoveredTasks: candidateTasks.length,
2160
- taskResults: completedTasks
2161
- });
2162
- const trackingSpinner = createSpinner(ctx.suppressOutput, "Writing contribution log...");
2163
- try {
2164
- const logPath = await writeContributionLog(contributionLog, resolvedRepo.localPath);
2165
- trackingSpinner?.succeed(`Contribution log written: ${logPath}`);
2166
- return logPath;
2167
- } catch (error) {
2168
- trackingSpinner?.fail("Failed to write contribution log");
2169
- if (ctx.globalOptions.verbose && !ctx.suppressOutput) {
2170
- const message = error instanceof Error ? error.message : String(error);
2171
- console.warn(ctx.ui.yellow(`[oac] Tracking failed: ${message}`));
2172
- }
2173
- return void 0;
2174
- }
2175
- }
2176
- function buildContributionLog(input2) {
2177
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2178
- const contributor = resolveGithubUsername(input2.repoOwner);
2179
- const contributionTasks = input2.taskResults.map((result) => ({
2180
- taskId: result.task.id,
2181
- title: result.task.title,
2182
- source: result.task.source,
2183
- complexity: result.task.complexity,
2184
- status: deriveTaskStatus(result.execution),
2185
- tokensUsed: Math.max(0, Math.floor(result.execution.totalTokensUsed)),
2186
- duration: Math.max(0, result.execution.duration),
2187
- filesChanged: result.execution.filesChanged,
2188
- pr: result.pr,
2189
- linkedIssue: result.task.linkedIssue ? {
2190
- number: result.task.linkedIssue.number,
2191
- url: result.task.linkedIssue.url
2192
- } : void 0,
2193
- error: result.execution.error
2194
- }));
2195
- const tasksSucceeded = contributionTasks.filter((task) => task.status !== "failed").length;
2196
- const tasksFailed = contributionTasks.length - tasksSucceeded;
2197
- const totalTokensUsed = contributionTasks.reduce((sum, task) => sum + task.tokensUsed, 0);
2198
- const totalFilesChanged = contributionTasks.reduce(
2199
- (sum, task) => sum + task.filesChanged.length,
2200
- 0
2201
- );
2202
- return {
2203
- version: "1.0",
2204
- runId: input2.runId,
2205
- timestamp,
2206
- contributor: {
2207
- githubUsername: contributor,
2208
- email: process.env.GIT_AUTHOR_EMAIL ?? void 0
2209
- },
2210
- repo: {
2211
- fullName: input2.repoFullName,
2212
- headSha: input2.repoHeadSha,
2213
- defaultBranch: input2.defaultBranch
2214
- },
2215
- budget: {
2216
- provider: input2.providerId,
2217
- totalTokensBudgeted: input2.totalBudget,
2218
- totalTokensUsed
2219
- },
2220
- tasks: contributionTasks,
2221
- metrics: {
2222
- tasksDiscovered: input2.discoveredTasks,
2223
- tasksAttempted: contributionTasks.length,
2224
- tasksSucceeded,
2225
- tasksFailed,
2226
- totalDuration: Math.max(0, input2.runDurationSeconds),
2227
- totalFilesChanged,
2228
- totalLinesAdded: 0,
2229
- totalLinesRemoved: 0
2230
- }
2231
- };
2232
- }
2233
- function deriveTaskStatus(execution) {
2234
- if (execution.success) {
2235
- return "success";
2236
- }
2237
- if (execution.filesChanged.length > 0) {
2238
- return "partial";
2239
- }
2240
- return "failed";
2241
- }
2242
- function resolveGithubUsername(fallback) {
2243
- const candidates = [
2244
- process.env.GITHUB_USER,
2245
- process.env.GITHUB_USERNAME,
2246
- process.env.USER,
2247
- process.env.LOGNAME,
2248
- fallback,
2249
- "oac-user"
2250
- ];
2251
- for (const candidate of candidates) {
2252
- const normalized = sanitizeGithubUsername(candidate ?? "");
2253
- if (normalized) {
2254
- return normalized;
2255
- }
2256
- }
2257
- return "oac-user";
2258
- }
2259
- function sanitizeGithubUsername(value) {
2260
- const trimmed = value.trim();
2261
- if (!trimmed) {
2262
- return null;
2263
- }
2264
- const cleaned = trimmed.replace(/[^A-Za-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2265
- if (cleaned.length === 0 || cleaned.length > 39) {
2266
- return null;
2267
- }
2268
- if (!/^(?!-)[A-Za-z0-9-]+(?<!-)$/.test(cleaned)) {
2269
- return null;
2270
- }
2271
- return cleaned;
2272
- }
2273
-
2274
2522
  // src/cli/commands/run/epic.ts
2275
2523
  async function tryLoadOrAnalyzeEpics(ctx, params) {
2276
2524
  const { resolvedRepo, config, ghToken, contextDir, staleAfterMs } = params;
@@ -2330,8 +2578,8 @@ function makeStubEstimate(taskId, providerId, tokens) {
2330
2578
  };
2331
2579
  }
2332
2580
  async function executeEpicEntry(entry, params) {
2333
- const { adapter, resolvedRepo, providerId, timeoutSeconds, mode, ghToken } = params;
2334
- const task = epicAsTask(entry.epic);
2581
+ const { adapter, resolvedRepo, providerId, timeoutSeconds, mode, ghToken, contextAck } = params;
2582
+ const task = withContextAck2(epicAsTask(entry.epic), contextAck);
2335
2583
  const estimate = makeStubEstimate(task.id, providerId, entry.estimatedTokens);
2336
2584
  const result = await executeWithAgent({
2337
2585
  task,
@@ -2398,10 +2646,7 @@ async function runEpicPipeline(ctx, params) {
2398
2646
  const { adapter } = await resolveAdapter(providerId);
2399
2647
  let epicCompletedCount = 0;
2400
2648
  const epicTotal = epicPlan.selectedEpics.length;
2401
- const executionSpinner = createSpinner(
2402
- ctx.suppressOutput,
2403
- `Executing ${epicTotal} epic(s)...`
2404
- );
2649
+ const executionSpinner = createSpinner(ctx.suppressOutput, `Executing ${epicTotal} epic(s)...`);
2405
2650
  const epicQueue = new PQueue3({ concurrency });
2406
2651
  const allTaskResults = await Promise.all(
2407
2652
  epicPlan.selectedEpics.map(
@@ -2420,7 +2665,8 @@ async function runEpicPipeline(ctx, params) {
2420
2665
  providerId,
2421
2666
  timeoutSeconds,
2422
2667
  mode,
2423
- ghToken
2668
+ ghToken,
2669
+ contextAck: ctx.contextAck
2424
2670
  });
2425
2671
  epicCompletedCount += 1;
2426
2672
  if (executionSpinner) {
@@ -2548,26 +2794,35 @@ function renderEpicPlanTable(ui, plan, budget) {
2548
2794
  }
2549
2795
  }
2550
2796
  }
2797
+ function withContextAck2(task, contextAck) {
2798
+ if (!contextAck) return task;
2799
+ return {
2800
+ ...task,
2801
+ metadata: {
2802
+ ...task.metadata,
2803
+ contextAck
2804
+ }
2805
+ };
2806
+ }
2551
2807
 
2552
2808
  // src/cli/commands/run/retry.ts
2553
- import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
2554
- import { resolve as resolve6 } from "path";
2809
+ import { readFile as readFile6, readdir as readdir4 } from "fs/promises";
2810
+ import { resolve as resolve8 } from "path";
2555
2811
  async function readMostRecentContributionLog(repoPath) {
2556
- const contributionsPath = resolve6(repoPath, ".oac", "contributions");
2812
+ const contributionsPath = resolve8(repoPath, ".oac", "contributions");
2557
2813
  let entries;
2558
2814
  try {
2559
- const dirEntries = await readdir3(contributionsPath, { withFileTypes: true, encoding: "utf8" });
2815
+ const dirEntries = await readdir4(contributionsPath, { withFileTypes: true, encoding: "utf8" });
2560
2816
  entries = dirEntries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name).sort((a, b) => b.localeCompare(a));
2561
2817
  } catch {
2562
2818
  return void 0;
2563
2819
  }
2564
2820
  for (const fileName of entries) {
2565
2821
  try {
2566
- const content = await readFile5(resolve6(contributionsPath, fileName), "utf8");
2822
+ const content = await readFile6(resolve8(contributionsPath, fileName), "utf8");
2567
2823
  const parsed = contributionLogSchema.safeParse(JSON.parse(content));
2568
2824
  if (parsed.success) return parsed.data;
2569
2825
  } catch {
2570
- continue;
2571
2826
  }
2572
2827
  }
2573
2828
  return void 0;
@@ -2594,7 +2849,9 @@ async function runRetryPipeline(ctx, params) {
2594
2849
  if (!log) {
2595
2850
  retrySpinner?.fail("No contribution logs found in .oac/contributions/");
2596
2851
  if (!ctx.suppressOutput) {
2597
- console.log(ctx.ui.yellow("[oac] Run the pipeline at least once before using --retry-failed."));
2852
+ console.log(
2853
+ ctx.ui.yellow("[oac] Run the pipeline at least once before using --retry-failed.")
2854
+ );
2598
2855
  }
2599
2856
  return [];
2600
2857
  }
@@ -2676,6 +2933,7 @@ async function runPipeline(options, globalOptions, ui) {
2676
2933
  const cloneSpinner = createSpinner(ctx.suppressOutput, "Preparing local clone...");
2677
2934
  await cloneRepo(resolvedRepo);
2678
2935
  cloneSpinner?.succeed(`Repository ready at ${resolvedRepo.localPath}`);
2936
+ ctx.contextAck = await resolveContextAck(resolvedRepo.localPath, config, ui, ctx.suppressOutput);
2679
2937
  if (options.retryFailed) {
2680
2938
  const retryResults = await runRetryPipeline(ctx, {
2681
2939
  resolvedRepo,
@@ -2820,10 +3078,8 @@ function parseTokens(value) {
2820
3078
  return parseInteger(value);
2821
3079
  }
2822
3080
  function createRunCommand() {
2823
- const command = new Command10("run");
2824
- command.alias("r").description(
2825
- "Run the full OAC pipeline \u2014 analyze, plan, and execute in one command"
2826
- ).option("--repo <owner/repo>", "Target repository (owner/repo or GitHub URL)").option("--tokens <value>", 'Token budget (number or "unlimited")', parseTokens).option("--provider <id>", "Agent provider id").option("--concurrency <number>", "Maximum parallel task executions", parseInteger).option("--dry-run", "Show plan without executing tasks", false).option("--mode <mode>", "Execution mode: new-pr|update-pr|direct-commit").option("--max-tasks <number>", "Maximum number of discovered tasks to consider", parseInteger).option("--timeout <seconds>", "Per-task timeout in seconds", parseInteger).option("--source <source>", "Filter tasks by source: lint, todo, github-issue, test-gap").option("--retry-failed", "Re-run only failed tasks from the most recent run", false).action(async (options, cmd) => {
3081
+ const command = new Command9("run");
3082
+ command.alias("r").description("Run the full OAC pipeline \u2014 analyze, plan, and execute in one command").option("--repo <owner/repo>", "Target repository (owner/repo or GitHub URL)").option("--tokens <value>", 'Token budget (number or "unlimited")', parseTokens).option("--provider <id>", "Agent provider id").option("--concurrency <number>", "Maximum parallel task executions", parseInteger).option("--dry-run", "Show plan without executing tasks", false).option("--mode <mode>", "Execution mode: new-pr|update-pr|direct-commit").option("--max-tasks <number>", "Maximum number of discovered tasks to consider", parseInteger).option("--timeout <seconds>", "Per-task timeout in seconds", parseInteger).option("--source <source>", "Filter tasks by source: lint, todo, github-issue, test-gap").option("--retry-failed", "Re-run only failed tasks from the most recent run", false).action(async (options, cmd) => {
2827
3083
  const globalOptions = getGlobalOptions(cmd);
2828
3084
  const ui = createUi(globalOptions);
2829
3085
  validateRunOptions(options);
@@ -2857,10 +3113,10 @@ Exit Codes:
2857
3113
 
2858
3114
  // src/cli/commands/scan.ts
2859
3115
  import Table7 from "cli-table3";
2860
- import { Command as Command11 } from "commander";
3116
+ import { Command as Command10 } from "commander";
2861
3117
  var SUPPORTED_SCANNERS = ["lint", "todo", "github-issues", "test-gap"];
2862
3118
  function createScanCommand() {
2863
- const command = new Command11("scan");
3119
+ const command = new Command10("scan");
2864
3120
  command.description("Quick task discovery \u2014 list individual issues ranked by priority").option("--repo <owner/repo>", "Target repository (owner/repo or GitHub URL)").option("--scanners <names>", "Comma-separated scanner filter (lint,todo)").option("--min-priority <number>", "Minimum priority threshold (0-100)", parseInteger, 20).option("--format <format>", "Output format: table|json", "table").action(async (options, cmd) => {
2865
3121
  const globalOptions = getGlobalOptions(cmd);
2866
3122
  const ui = createUi(globalOptions);
@@ -3022,12 +3278,12 @@ function parseCsv(value) {
3022
3278
  }
3023
3279
 
3024
3280
  // src/cli/commands/status.ts
3025
- import { readFile as readFile6 } from "fs/promises";
3026
- import { resolve as resolve7 } from "path";
3027
- import { Command as Command12 } from "commander";
3281
+ import { readFile as readFile7 } from "fs/promises";
3282
+ import { resolve as resolve9 } from "path";
3283
+ import { Command as Command11 } from "commander";
3028
3284
  var WATCH_INTERVAL_MS = 2e3;
3029
3285
  function createStatusCommand() {
3030
- const command = new Command12("status");
3286
+ const command = new Command11("status");
3031
3287
  command.description("Show current job status").option("--watch", "Poll every 2 seconds", false).action(async (options, cmd) => {
3032
3288
  const globalOptions = getGlobalOptions(cmd);
3033
3289
  const render = async () => {
@@ -3062,9 +3318,9 @@ Examples:
3062
3318
  return command;
3063
3319
  }
3064
3320
  async function readRunStatus(repoPath) {
3065
- const statusPath = resolve7(repoPath, ".oac", "status.json");
3321
+ const statusPath = resolve9(repoPath, ".oac", "status.json");
3066
3322
  try {
3067
- const raw = await readFile6(statusPath, "utf8");
3323
+ const raw = await readFile7(statusPath, "utf8");
3068
3324
  const payload = JSON.parse(raw);
3069
3325
  return parseRunStatus(payload);
3070
3326
  } catch (error) {
@@ -3203,21 +3459,13 @@ function registerCommands(program) {
3203
3459
  program.addCommand(createExplainCommand());
3204
3460
  }
3205
3461
  async function createCliProgram() {
3206
- const version = true ? "2026.221.2" : "0.0.0";
3207
- const program = new Command13();
3462
+ const version = true ? "2026.222.2" : "0.0.0";
3463
+ const program = new Command12();
3208
3464
  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");
3209
3465
  registerCommands(program);
3210
3466
  program.addHelpText(
3211
3467
  "after",
3212
- `
3213
- Getting Started:
3214
- $ oac init Set up your project configuration
3215
- $ oac doctor Verify your environment is ready
3216
- $ oac analyze Analyze codebase for contribution opportunities
3217
- $ oac run Run the full contribution pipeline
3218
-
3219
- Documentation: https://github.com/Open330/open-agent-contribution
3220
- `
3468
+ "\nGetting Started:\n $ oac init Set up your project configuration\n $ oac doctor Verify your environment is ready\n $ oac analyze Analyze codebase for contribution opportunities\n $ oac run Run the full contribution pipeline\n\nDocumentation: https://github.com/Open330/open-agent-contribution\n"
3221
3469
  );
3222
3470
  return program;
3223
3471
  }
@@ -3233,4 +3481,4 @@ export {
3233
3481
  createCliProgram,
3234
3482
  runCli
3235
3483
  };
3236
- //# sourceMappingURL=chunk-VK33A5L4.js.map
3484
+ //# sourceMappingURL=chunk-ATVWSG75.js.map