@open330/oac 2026.222.1 → 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.
@@ -32,12 +32,12 @@ import {
32
32
  createSandbox,
33
33
  epicAsTask,
34
34
  executeTask
35
- } from "./chunk-JROCVTM7.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
@@ -729,8 +729,8 @@ function isVersionAtLeast(version, minimum) {
729
729
  }
730
730
  async function runCommand(command, args) {
731
731
  try {
732
- const { execa: execa3 } = await import("execa");
733
- const result = await execa3(command, args, {
732
+ const { execa: execa4 } = await import("execa");
733
+ const result = await execa4(command, args, {
734
734
  reject: false,
735
735
  timeout: 1e4,
736
736
  stdin: "ignore"
@@ -1589,12 +1589,10 @@ import { Command as Command9 } from "commander";
1589
1589
  // src/cli/commands/run/pipeline.ts
1590
1590
  import { randomUUID } from "crypto";
1591
1591
 
1592
- // src/cli/commands/run/epic.ts
1593
- import Table6 from "cli-table3";
1594
- import PQueue3 from "p-queue";
1595
-
1596
- // src/cli/commands/run/pr.ts
1597
- 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";
1598
1596
 
1599
1597
  // src/cli/commands/run/types.ts
1600
1598
  var DEFAULT_TIMEOUT_SECONDS = 300;
@@ -1636,7 +1634,140 @@ function formatDuration(seconds) {
1636
1634
  return `${minutes}m ${remainingSeconds}s`;
1637
1635
  }
1638
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
+
1639
1769
  // src/cli/commands/run/pr.ts
1770
+ import { execa } from "execa";
1640
1771
  var GITHUB_API_BASE_URL = "https://api.github.com";
1641
1772
  var OAC_PR_TITLE_PREFIX = "[OAC]";
1642
1773
  async function createPullRequest(input2) {
@@ -1689,6 +1820,23 @@ async function createPullRequest(input2) {
1689
1820
  if (input2.task.linkedIssue) {
1690
1821
  prBodyLines.push(`- **Resolves:** #${input2.task.linkedIssue.number}`);
1691
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
+ }
1692
1840
  prBodyLines.push(
1693
1841
  "",
1694
1842
  "---",
@@ -1727,6 +1875,23 @@ async function createPullRequest(input2) {
1727
1875
  return void 0;
1728
1876
  }
1729
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
+ }
1730
1895
  async function findExistingOacPR(repoFullName, issueNumber, token) {
1731
1896
  const url = `${GITHUB_API_BASE_URL}/repos/${repoFullName}/pulls?state=open&per_page=100&sort=updated&direction=desc`;
1732
1897
  try {
@@ -1768,8 +1933,186 @@ async function findExistingOacPR(repoFullName, issueNumber, token) {
1768
1933
 
1769
1934
  // src/cli/commands/run/task.ts
1770
1935
  import Table5 from "cli-table3";
1771
- import { execa as execa2 } from "execa";
1936
+ import { execa as execa3 } from "execa";
1772
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
1773
2116
  async function discoverTasks(ctx, options, config, ghToken, resolvedRepo) {
1774
2117
  const scannerSelection = selectScannersFromConfig2(config, Boolean(ghToken));
1775
2118
  const minPriority = config?.discovery.minPriority ?? 20;
@@ -1899,8 +2242,9 @@ async function executePlan(ctx, params) {
1899
2242
  const executedTasks = await Promise.all(
1900
2243
  plan.selectedTasks.map(
1901
2244
  (entry) => taskQueue.add(async () => {
2245
+ const taskForExecution = withContextAck(entry.task, ctx.contextAck);
1902
2246
  const result = await executeWithAgent({
1903
- task: entry.task,
2247
+ task: taskForExecution,
1904
2248
  estimate: entry.estimate,
1905
2249
  adapter,
1906
2250
  repoPath: resolvedRepo.localPath,
@@ -1914,7 +2258,7 @@ async function executePlan(ctx, params) {
1914
2258
  const pct = Math.round(completedCount / total * 100);
1915
2259
  executionSpinner.text = `Executing tasks... (${completedCount}/${total} \u2014 ${pct}%)`;
1916
2260
  }
1917
- return { task: entry.task, estimate: entry.estimate, execution, sandbox };
2261
+ return { task: taskForExecution, estimate: entry.estimate, execution, sandbox };
1918
2262
  })
1919
2263
  )
1920
2264
  );
@@ -1925,6 +2269,16 @@ async function executePlan(ctx, params) {
1925
2269
  executedTasks.map(
1926
2270
  (result) => completionQueue.add(async () => {
1927
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
+ }
1928
2282
  const pr = await createPullRequest({
1929
2283
  task: result.task,
1930
2284
  execution: result.execution,
@@ -1994,6 +2348,18 @@ function selectScannersFromConfig2(config, hasGitHubAuth) {
1994
2348
  const { names, composite } = buildScanners(config, hasGitHubAuth);
1995
2349
  return { enabled: names, scanner: composite };
1996
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
+ }
1997
2363
  async function executeWithAgent(input2) {
1998
2364
  const startedAt = Date.now();
1999
2365
  const taskSlug = input2.task.id.replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 30);
@@ -2053,10 +2419,10 @@ async function executeWithAgent(input2) {
2053
2419
  }
2054
2420
  async function commitSandboxChanges(sandboxPath, task, baseBranch) {
2055
2421
  try {
2056
- const statusResult = await execa2("git", ["status", "--porcelain"], { cwd: sandboxPath });
2422
+ const statusResult = await execa3("git", ["status", "--porcelain"], { cwd: sandboxPath });
2057
2423
  if (statusResult.stdout.trim()) {
2058
- await execa2("git", ["add", "-A"], { cwd: sandboxPath });
2059
- await execa2(
2424
+ await execa3("git", ["add", "-A"], { cwd: sandboxPath });
2425
+ await execa3(
2060
2426
  "git",
2061
2427
  ["commit", "-m", `[OAC] ${task.title}
2062
2428
 
@@ -2064,11 +2430,9 @@ Automated contribution by OAC.`],
2064
2430
  { cwd: sandboxPath }
2065
2431
  );
2066
2432
  }
2067
- const diffResult = await execa2(
2068
- "git",
2069
- ["diff", "--name-only", `origin/${baseBranch}`, "HEAD"],
2070
- { cwd: sandboxPath }
2071
- );
2433
+ const diffResult = await execa3("git", ["diff", "--name-only", `origin/${baseBranch}`, "HEAD"], {
2434
+ cwd: sandboxPath
2435
+ });
2072
2436
  const changedFiles = diffResult.stdout.trim().split("\n").filter(Boolean);
2073
2437
  return { hasChanges: changedFiles.length > 0, filesChanged: changedFiles };
2074
2438
  } catch {
@@ -2155,134 +2519,6 @@ function renderTaskResults(ui, taskResults) {
2155
2519
  }
2156
2520
  }
2157
2521
 
2158
- // src/cli/commands/run/tracking.ts
2159
- async function writeTracking(ctx, params) {
2160
- const { resolvedRepo, providerId, totalBudget, candidateTasks, completedTasks } = params;
2161
- const runDurationSeconds = (Date.now() - ctx.runStartedAt) / 1e3;
2162
- const contributionLog = buildContributionLog({
2163
- runId: ctx.runId,
2164
- repoFullName: resolvedRepo.fullName,
2165
- repoHeadSha: resolvedRepo.git.headSha,
2166
- defaultBranch: resolvedRepo.meta.defaultBranch,
2167
- repoOwner: resolvedRepo.owner,
2168
- providerId,
2169
- totalBudget,
2170
- runDurationSeconds,
2171
- discoveredTasks: candidateTasks.length,
2172
- taskResults: completedTasks
2173
- });
2174
- const trackingSpinner = createSpinner(ctx.suppressOutput, "Writing contribution log...");
2175
- try {
2176
- const logPath = await writeContributionLog(contributionLog, resolvedRepo.localPath);
2177
- trackingSpinner?.succeed(`Contribution log written: ${logPath}`);
2178
- return logPath;
2179
- } catch (error) {
2180
- trackingSpinner?.fail("Failed to write contribution log");
2181
- if (ctx.globalOptions.verbose && !ctx.suppressOutput) {
2182
- const message = error instanceof Error ? error.message : String(error);
2183
- console.warn(ctx.ui.yellow(`[oac] Tracking failed: ${message}`));
2184
- }
2185
- return void 0;
2186
- }
2187
- }
2188
- function buildContributionLog(input2) {
2189
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2190
- const contributor = resolveGithubUsername(input2.repoOwner);
2191
- const contributionTasks = input2.taskResults.map((result) => ({
2192
- taskId: result.task.id,
2193
- title: result.task.title,
2194
- source: result.task.source,
2195
- complexity: result.task.complexity,
2196
- status: deriveTaskStatus(result.execution),
2197
- tokensUsed: Math.max(0, Math.floor(result.execution.totalTokensUsed)),
2198
- duration: Math.max(0, result.execution.duration),
2199
- filesChanged: result.execution.filesChanged,
2200
- pr: result.pr,
2201
- linkedIssue: result.task.linkedIssue ? {
2202
- number: result.task.linkedIssue.number,
2203
- url: result.task.linkedIssue.url
2204
- } : void 0,
2205
- error: result.execution.error
2206
- }));
2207
- const tasksSucceeded = contributionTasks.filter((task) => task.status !== "failed").length;
2208
- const tasksFailed = contributionTasks.length - tasksSucceeded;
2209
- const totalTokensUsed = contributionTasks.reduce((sum, task) => sum + task.tokensUsed, 0);
2210
- const totalFilesChanged = contributionTasks.reduce(
2211
- (sum, task) => sum + task.filesChanged.length,
2212
- 0
2213
- );
2214
- return {
2215
- version: "1.0",
2216
- runId: input2.runId,
2217
- timestamp,
2218
- contributor: {
2219
- githubUsername: contributor,
2220
- email: process.env.GIT_AUTHOR_EMAIL ?? void 0
2221
- },
2222
- repo: {
2223
- fullName: input2.repoFullName,
2224
- headSha: input2.repoHeadSha,
2225
- defaultBranch: input2.defaultBranch
2226
- },
2227
- budget: {
2228
- provider: input2.providerId,
2229
- totalTokensBudgeted: input2.totalBudget,
2230
- totalTokensUsed
2231
- },
2232
- tasks: contributionTasks,
2233
- metrics: {
2234
- tasksDiscovered: input2.discoveredTasks,
2235
- tasksAttempted: contributionTasks.length,
2236
- tasksSucceeded,
2237
- tasksFailed,
2238
- totalDuration: Math.max(0, input2.runDurationSeconds),
2239
- totalFilesChanged,
2240
- totalLinesAdded: 0,
2241
- totalLinesRemoved: 0
2242
- }
2243
- };
2244
- }
2245
- function deriveTaskStatus(execution) {
2246
- if (execution.success) {
2247
- return "success";
2248
- }
2249
- if (execution.filesChanged.length > 0) {
2250
- return "partial";
2251
- }
2252
- return "failed";
2253
- }
2254
- function resolveGithubUsername(fallback) {
2255
- const candidates = [
2256
- process.env.GITHUB_USER,
2257
- process.env.GITHUB_USERNAME,
2258
- process.env.USER,
2259
- process.env.LOGNAME,
2260
- fallback,
2261
- "oac-user"
2262
- ];
2263
- for (const candidate of candidates) {
2264
- const normalized = sanitizeGithubUsername(candidate ?? "");
2265
- if (normalized) {
2266
- return normalized;
2267
- }
2268
- }
2269
- return "oac-user";
2270
- }
2271
- function sanitizeGithubUsername(value) {
2272
- const trimmed = value.trim();
2273
- if (!trimmed) {
2274
- return null;
2275
- }
2276
- const cleaned = trimmed.replace(/[^A-Za-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2277
- if (cleaned.length === 0 || cleaned.length > 39) {
2278
- return null;
2279
- }
2280
- if (!/^(?!-)[A-Za-z0-9-]+(?<!-)$/.test(cleaned)) {
2281
- return null;
2282
- }
2283
- return cleaned;
2284
- }
2285
-
2286
2522
  // src/cli/commands/run/epic.ts
2287
2523
  async function tryLoadOrAnalyzeEpics(ctx, params) {
2288
2524
  const { resolvedRepo, config, ghToken, contextDir, staleAfterMs } = params;
@@ -2342,8 +2578,8 @@ function makeStubEstimate(taskId, providerId, tokens) {
2342
2578
  };
2343
2579
  }
2344
2580
  async function executeEpicEntry(entry, params) {
2345
- const { adapter, resolvedRepo, providerId, timeoutSeconds, mode, ghToken } = params;
2346
- const task = epicAsTask(entry.epic);
2581
+ const { adapter, resolvedRepo, providerId, timeoutSeconds, mode, ghToken, contextAck } = params;
2582
+ const task = withContextAck2(epicAsTask(entry.epic), contextAck);
2347
2583
  const estimate = makeStubEstimate(task.id, providerId, entry.estimatedTokens);
2348
2584
  const result = await executeWithAgent({
2349
2585
  task,
@@ -2429,7 +2665,8 @@ async function runEpicPipeline(ctx, params) {
2429
2665
  providerId,
2430
2666
  timeoutSeconds,
2431
2667
  mode,
2432
- ghToken
2668
+ ghToken,
2669
+ contextAck: ctx.contextAck
2433
2670
  });
2434
2671
  epicCompletedCount += 1;
2435
2672
  if (executionSpinner) {
@@ -2557,22 +2794,32 @@ function renderEpicPlanTable(ui, plan, budget) {
2557
2794
  }
2558
2795
  }
2559
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
+ }
2560
2807
 
2561
2808
  // src/cli/commands/run/retry.ts
2562
- import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
2563
- import { resolve as resolve6 } from "path";
2809
+ import { readFile as readFile6, readdir as readdir4 } from "fs/promises";
2810
+ import { resolve as resolve8 } from "path";
2564
2811
  async function readMostRecentContributionLog(repoPath) {
2565
- const contributionsPath = resolve6(repoPath, ".oac", "contributions");
2812
+ const contributionsPath = resolve8(repoPath, ".oac", "contributions");
2566
2813
  let entries;
2567
2814
  try {
2568
- const dirEntries = await readdir3(contributionsPath, { withFileTypes: true, encoding: "utf8" });
2815
+ const dirEntries = await readdir4(contributionsPath, { withFileTypes: true, encoding: "utf8" });
2569
2816
  entries = dirEntries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name).sort((a, b) => b.localeCompare(a));
2570
2817
  } catch {
2571
2818
  return void 0;
2572
2819
  }
2573
2820
  for (const fileName of entries) {
2574
2821
  try {
2575
- const content = await readFile5(resolve6(contributionsPath, fileName), "utf8");
2822
+ const content = await readFile6(resolve8(contributionsPath, fileName), "utf8");
2576
2823
  const parsed = contributionLogSchema.safeParse(JSON.parse(content));
2577
2824
  if (parsed.success) return parsed.data;
2578
2825
  } catch {
@@ -2686,6 +2933,7 @@ async function runPipeline(options, globalOptions, ui) {
2686
2933
  const cloneSpinner = createSpinner(ctx.suppressOutput, "Preparing local clone...");
2687
2934
  await cloneRepo(resolvedRepo);
2688
2935
  cloneSpinner?.succeed(`Repository ready at ${resolvedRepo.localPath}`);
2936
+ ctx.contextAck = await resolveContextAck(resolvedRepo.localPath, config, ui, ctx.suppressOutput);
2689
2937
  if (options.retryFailed) {
2690
2938
  const retryResults = await runRetryPipeline(ctx, {
2691
2939
  resolvedRepo,
@@ -3030,8 +3278,8 @@ function parseCsv(value) {
3030
3278
  }
3031
3279
 
3032
3280
  // src/cli/commands/status.ts
3033
- import { readFile as readFile6 } from "fs/promises";
3034
- import { resolve as resolve7 } from "path";
3281
+ import { readFile as readFile7 } from "fs/promises";
3282
+ import { resolve as resolve9 } from "path";
3035
3283
  import { Command as Command11 } from "commander";
3036
3284
  var WATCH_INTERVAL_MS = 2e3;
3037
3285
  function createStatusCommand() {
@@ -3070,9 +3318,9 @@ Examples:
3070
3318
  return command;
3071
3319
  }
3072
3320
  async function readRunStatus(repoPath) {
3073
- const statusPath = resolve7(repoPath, ".oac", "status.json");
3321
+ const statusPath = resolve9(repoPath, ".oac", "status.json");
3074
3322
  try {
3075
- const raw = await readFile6(statusPath, "utf8");
3323
+ const raw = await readFile7(statusPath, "utf8");
3076
3324
  const payload = JSON.parse(raw);
3077
3325
  return parseRunStatus(payload);
3078
3326
  } catch (error) {
@@ -3211,7 +3459,7 @@ function registerCommands(program) {
3211
3459
  program.addCommand(createExplainCommand());
3212
3460
  }
3213
3461
  async function createCliProgram() {
3214
- const version = true ? "2026.222.1" : "0.0.0";
3462
+ const version = true ? "2026.222.2" : "0.0.0";
3215
3463
  const program = new Command12();
3216
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");
3217
3465
  registerCommands(program);
@@ -3233,4 +3481,4 @@ export {
3233
3481
  createCliProgram,
3234
3482
  runCli
3235
3483
  };
3236
- //# sourceMappingURL=chunk-GPHH2IYN.js.map
3484
+ //# sourceMappingURL=chunk-ATVWSG75.js.map