@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.
- package/README.md +170 -1
- package/dist/budget/index.js +1 -1
- package/dist/{chunk-EYUQMPVO.js → chunk-27FEE5KS.js} +86 -34
- package/dist/chunk-27FEE5KS.js.map +1 -0
- package/dist/{chunk-5GAUWC3L.js → chunk-ALBVUNUY.js} +1 -1
- package/dist/chunk-ALBVUNUY.js.map +1 -0
- package/dist/{chunk-VK33A5L4.js → chunk-ATVWSG75.js} +480 -232
- 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/{chunk-6A37SKAJ.js → chunk-JDFAJP45.js} +1 -1
- package/dist/{chunk-6A37SKAJ.js.map → chunk-JDFAJP45.js.map} +1 -1
- package/dist/{chunk-OS3XDHOJ.js → chunk-UCYK4Z6O.js} +1 -1
- package/dist/chunk-UCYK4Z6O.js.map +1 -0
- package/dist/{chunk-OCCMKAJI.js → chunk-ZJBLRKCV.js} +3 -3
- package/dist/chunk-ZJBLRKCV.js.map +1 -0
- package/dist/cli/cli.js +7 -7
- package/dist/cli/index.js +7 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/completion/index.d.ts +1 -1
- package/dist/completion/index.js +2 -2
- package/dist/completion/index.js.map +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 +4 -2
- package/dist/dashboard/index.js +72 -23
- package/dist/dashboard/index.js.map +1 -1
- package/dist/discovery/index.d.ts +1 -1
- package/dist/discovery/index.js +2 -2
- package/dist/execution/index.js +3 -3
- package/dist/repo/index.js +1 -1
- package/package.json +13 -15
- package/dist/chunk-5GAUWC3L.js.map +0 -1
- package/dist/chunk-7C7SC4TZ.js.map +0 -1
- package/dist/chunk-EYUQMPVO.js.map +0 -1
- package/dist/chunk-OCCMKAJI.js.map +0 -1
- package/dist/chunk-OS3XDHOJ.js.map +0 -1
- package/dist/chunk-VK33A5L4.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cloneRepo,
|
|
3
3
|
resolveRepo
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
23
|
+
} from "./chunk-ZJBLRKCV.js";
|
|
24
24
|
import {
|
|
25
25
|
buildEpicExecutionPlan,
|
|
26
26
|
buildExecutionPlan,
|
|
27
27
|
estimateEpicTokens,
|
|
28
28
|
estimateTokens
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-ALBVUNUY.js";
|
|
30
30
|
import {
|
|
31
31
|
adapterRegistry,
|
|
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
|
|
44
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = [
|
|
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
|
|
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 = {
|
|
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
|
|
516
|
+
import { Command as Command3 } from "commander";
|
|
510
517
|
var MINIMUM_NODE_VERSION = "24.0.0";
|
|
511
518
|
function createDoctorCommand() {
|
|
512
|
-
const command = new
|
|
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:
|
|
726
|
-
const result = await
|
|
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
|
|
759
|
+
import { Command as Command4 } from "commander";
|
|
753
760
|
function createExplainCommand() {
|
|
754
|
-
const command = new
|
|
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(
|
|
837
|
-
console.log(
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
console.log(
|
|
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
|
|
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
|
|
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
|
|
1136
|
+
import { Command as Command6 } from "commander";
|
|
1128
1137
|
function createLeaderboardCommand() {
|
|
1129
|
-
const command = new
|
|
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
|
|
1330
|
+
import { Command as Command7 } from "commander";
|
|
1322
1331
|
function createLogCommand() {
|
|
1323
|
-
const command = new
|
|
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
|
|
1450
|
+
import { Command as Command8 } from "commander";
|
|
1442
1451
|
function createPlanCommand() {
|
|
1443
|
-
const command = new
|
|
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
|
|
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/
|
|
1584
|
-
import
|
|
1585
|
-
import
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
2046
|
-
if (
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
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
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
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:
|
|
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
|
|
2554
|
-
import { resolve as
|
|
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 =
|
|
2812
|
+
const contributionsPath = resolve8(repoPath, ".oac", "contributions");
|
|
2557
2813
|
let entries;
|
|
2558
2814
|
try {
|
|
2559
|
-
const dirEntries = await
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3026
|
-
import { resolve as
|
|
3027
|
-
import { Command as
|
|
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
|
|
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 =
|
|
3321
|
+
const statusPath = resolve9(repoPath, ".oac", "status.json");
|
|
3066
3322
|
try {
|
|
3067
|
-
const raw = await
|
|
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.
|
|
3207
|
-
const program = new
|
|
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-
|
|
3484
|
+
//# sourceMappingURL=chunk-ATVWSG75.js.map
|