@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.
- package/README.md +170 -1
- package/dist/{chunk-JROCVTM7.js → chunk-27FEE5KS.js} +39 -5
- package/dist/chunk-27FEE5KS.js.map +1 -0
- package/dist/{chunk-GPHH2IYN.js → chunk-ATVWSG75.js} +411 -163
- package/dist/chunk-ATVWSG75.js.map +1 -0
- package/dist/{chunk-7C7SC4TZ.js → chunk-I3TKNT4M.js} +9 -2
- package/dist/chunk-I3TKNT4M.js.map +1 -0
- package/dist/cli/cli.js +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/completion/index.d.ts +1 -1
- package/dist/completion/index.js +1 -1
- package/dist/{config-DequKoFA.d.ts → config-DnzZ7w92.d.ts} +60 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +3 -1
- package/dist/dashboard/index.js +57 -11
- package/dist/dashboard/index.js.map +1 -1
- package/dist/discovery/index.d.ts +1 -1
- package/dist/execution/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-7C7SC4TZ.js.map +0 -1
- package/dist/chunk-GPHH2IYN.js.map +0 -1
- package/dist/chunk-JROCVTM7.js.map +0 -1
|
@@ -32,12 +32,12 @@ import {
|
|
|
32
32
|
createSandbox,
|
|
33
33
|
epicAsTask,
|
|
34
34
|
executeTask
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-27FEE5KS.js";
|
|
36
36
|
import {
|
|
37
37
|
UNLIMITED_BUDGET,
|
|
38
38
|
createEventBus,
|
|
39
39
|
loadConfig
|
|
40
|
-
} from "./chunk-
|
|
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:
|
|
733
|
-
const result = await
|
|
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/
|
|
1593
|
-
import
|
|
1594
|
-
import
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
2422
|
+
const statusResult = await execa3("git", ["status", "--porcelain"], { cwd: sandboxPath });
|
|
2057
2423
|
if (statusResult.stdout.trim()) {
|
|
2058
|
-
await
|
|
2059
|
-
await
|
|
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
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
|
2563
|
-
import { resolve as
|
|
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 =
|
|
2812
|
+
const contributionsPath = resolve8(repoPath, ".oac", "contributions");
|
|
2566
2813
|
let entries;
|
|
2567
2814
|
try {
|
|
2568
|
-
const dirEntries = await
|
|
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
|
|
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
|
|
3034
|
-
import { resolve as
|
|
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 =
|
|
3321
|
+
const statusPath = resolve9(repoPath, ".oac", "status.json");
|
|
3074
3322
|
try {
|
|
3075
|
-
const raw = await
|
|
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.
|
|
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-
|
|
3484
|
+
//# sourceMappingURL=chunk-ATVWSG75.js.map
|