opencara 0.18.0 → 0.18.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 +1 -1
- package/dist/index.js +177 -38
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Review prompts are delivered via **stdin** to your command. The command reads st
|
|
|
84
84
|
|
|
85
85
|
| Field | Default | Description |
|
|
86
86
|
| ------------------------ | -------------------------- | ------------------------------------- |
|
|
87
|
-
| `platform_url` | `https://api.opencara.
|
|
87
|
+
| `platform_url` | `https://api.opencara.com` | OpenCara server URL |
|
|
88
88
|
| `github_token` | -- | GitHub token for private repo diffs |
|
|
89
89
|
| `codebase_dir` | -- | Default clone directory for repos |
|
|
90
90
|
| `max_diff_size_kb` | `100` | Skip PRs with diffs larger than this |
|
package/dist/index.js
CHANGED
|
@@ -378,7 +378,7 @@ import * as fs from "fs";
|
|
|
378
378
|
import * as path from "path";
|
|
379
379
|
import * as os from "os";
|
|
380
380
|
import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
|
|
381
|
-
var DEFAULT_PLATFORM_URL = "https://api.opencara.
|
|
381
|
+
var DEFAULT_PLATFORM_URL = "https://api.opencara.com";
|
|
382
382
|
var CONFIG_DIR = path.join(os.homedir(), ".opencara");
|
|
383
383
|
var CONFIG_FILE = process.env.OPENCARA_CONFIG && process.env.OPENCARA_CONFIG.trim() ? path.resolve(process.env.OPENCARA_CONFIG) : path.join(CONFIG_DIR, "config.toml");
|
|
384
384
|
function ensureConfigDir() {
|
|
@@ -565,7 +565,6 @@ function loadConfig() {
|
|
|
565
565
|
const envPlatformUrl = process.env.OPENCARA_PLATFORM_URL?.trim() || null;
|
|
566
566
|
const defaults = {
|
|
567
567
|
platformUrl: envPlatformUrl || DEFAULT_PLATFORM_URL,
|
|
568
|
-
apiKey: null,
|
|
569
568
|
maxDiffSizeKb: DEFAULT_MAX_DIFF_SIZE_KB,
|
|
570
569
|
maxConsecutiveErrors: DEFAULT_MAX_CONSECUTIVE_ERRORS,
|
|
571
570
|
codebaseDir: null,
|
|
@@ -609,7 +608,6 @@ function loadConfig() {
|
|
|
609
608
|
}
|
|
610
609
|
return {
|
|
611
610
|
platformUrl: envPlatformUrl || (typeof data.platform_url === "string" ? data.platform_url : DEFAULT_PLATFORM_URL),
|
|
612
|
-
apiKey: typeof data.api_key === "string" ? data.api_key.trim() || null : null,
|
|
613
611
|
maxDiffSizeKb: overrides.maxDiffSizeKb ?? (typeof data.max_diff_size_kb === "number" ? data.max_diff_size_kb : DEFAULT_MAX_DIFF_SIZE_KB),
|
|
614
612
|
maxConsecutiveErrors: overrides.maxConsecutiveErrors ?? (typeof data.max_consecutive_errors === "number" ? data.max_consecutive_errors : DEFAULT_MAX_CONSECUTIVE_ERRORS),
|
|
615
613
|
codebaseDir: typeof data.codebase_dir === "string" ? data.codebase_dir : null,
|
|
@@ -816,11 +814,16 @@ To authenticate, visit: ${initData.verification_uri}`);
|
|
|
816
814
|
if (Date.now() >= deadline) {
|
|
817
815
|
break;
|
|
818
816
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
817
|
+
let tokenRes;
|
|
818
|
+
try {
|
|
819
|
+
tokenRes = await fetchFn(`${platformUrl}/api/auth/device/token`, {
|
|
820
|
+
method: "POST",
|
|
821
|
+
headers: { "Content-Type": "application/json" },
|
|
822
|
+
body: JSON.stringify({ device_code: initData.device_code })
|
|
823
|
+
});
|
|
824
|
+
} catch {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
824
827
|
if (!tokenRes.ok) {
|
|
825
828
|
try {
|
|
826
829
|
await tokenRes.text();
|
|
@@ -2195,8 +2198,14 @@ You MUST output ONLY a valid JSON object matching this exact schema (no markdown
|
|
|
2195
2198
|
|
|
2196
2199
|
- "duplicates": array of matches found (empty array if no duplicates)
|
|
2197
2200
|
- "similarity": "exact" = identical intent/change, "high" = very similar with minor differences, "partial" = overlapping but distinct
|
|
2198
|
-
- "index_entry": a single line in the format: \`-
|
|
2201
|
+
- "index_entry": a single line in the format: \`- <number>(<label1>, <label2>, ...): <short description>\` where labels are inferred from GitHub labels, PR/issue title, body, and any available context`);
|
|
2202
|
+
if (task.customPrompt) {
|
|
2203
|
+
parts.push(`
|
|
2204
|
+
## Repo-Specific Instructions
|
|
2199
2205
|
|
|
2206
|
+
${task.customPrompt}`);
|
|
2207
|
+
}
|
|
2208
|
+
parts.push(`
|
|
2200
2209
|
## Index of Existing Items
|
|
2201
2210
|
|
|
2202
2211
|
<UNTRUSTED_CONTENT>`);
|
|
@@ -2346,7 +2355,7 @@ async function executeDedup(prompt, timeoutSeconds, deps, runTool = executeTool,
|
|
|
2346
2355
|
}
|
|
2347
2356
|
async function executeDedupTask(client, agentId, taskId, task, diffContent, timeoutSeconds, reviewDeps, consumptionDeps, logger, signal, role = "pr_dedup") {
|
|
2348
2357
|
logger.log(` ${icons.running} Executing dedup: ${reviewDeps.commandTemplate}`);
|
|
2349
|
-
const prompt = buildDedupPrompt({ ...task, diffContent });
|
|
2358
|
+
const prompt = buildDedupPrompt({ ...task, diffContent, customPrompt: task.prompt });
|
|
2350
2359
|
const result = await executeDedup(
|
|
2351
2360
|
prompt,
|
|
2352
2361
|
timeoutSeconds,
|
|
@@ -2607,6 +2616,11 @@ function buildTriagePrompt(task) {
|
|
|
2607
2616
|
const title = task.issue_title ?? `PR #${task.pr_number}`;
|
|
2608
2617
|
const rawBody = task.issue_body ?? "";
|
|
2609
2618
|
const safeBody = truncateToBytes(rawBody, MAX_ISSUE_BODY_BYTES);
|
|
2619
|
+
const repoPromptSection = task.prompt ? `
|
|
2620
|
+
|
|
2621
|
+
## Repo-Specific Instructions
|
|
2622
|
+
|
|
2623
|
+
${task.prompt}` : "";
|
|
2610
2624
|
const userMessage = [
|
|
2611
2625
|
`## Issue Title`,
|
|
2612
2626
|
title,
|
|
@@ -2616,7 +2630,7 @@ function buildTriagePrompt(task) {
|
|
|
2616
2630
|
safeBody,
|
|
2617
2631
|
"</UNTRUSTED_CONTENT>"
|
|
2618
2632
|
].join("\n");
|
|
2619
|
-
return `${TRIAGE_SYSTEM_PROMPT}
|
|
2633
|
+
return `${TRIAGE_SYSTEM_PROMPT}${repoPromptSection}
|
|
2620
2634
|
|
|
2621
2635
|
${userMessage}`;
|
|
2622
2636
|
}
|
|
@@ -3167,7 +3181,8 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
3167
3181
|
issue_title: task.issue_title,
|
|
3168
3182
|
issue_body: task.issue_body,
|
|
3169
3183
|
diff_url,
|
|
3170
|
-
index_issue_body: task.index_issue_body
|
|
3184
|
+
index_issue_body: task.index_issue_body,
|
|
3185
|
+
prompt
|
|
3171
3186
|
},
|
|
3172
3187
|
diffContent,
|
|
3173
3188
|
timeout_seconds,
|
|
@@ -3568,7 +3583,7 @@ function sleep2(ms, signal) {
|
|
|
3568
3583
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
3569
3584
|
const client = new ApiClient(platformUrl, {
|
|
3570
3585
|
authToken: options?.authToken,
|
|
3571
|
-
cliVersion: "0.18.
|
|
3586
|
+
cliVersion: "0.18.2",
|
|
3572
3587
|
versionOverride: options?.versionOverride,
|
|
3573
3588
|
onTokenRefresh: options?.onTokenRefresh
|
|
3574
3589
|
});
|
|
@@ -4075,11 +4090,77 @@ async function updateIssueComment(owner, repo, commentId, body, token, fetchFn =
|
|
|
4075
4090
|
}
|
|
4076
4091
|
function formatEntry(item, compact = false) {
|
|
4077
4092
|
if (compact) {
|
|
4078
|
-
return `-
|
|
4093
|
+
return `- ${item.number}(): ${item.title}`;
|
|
4079
4094
|
}
|
|
4080
|
-
const labels = item.labels.map((l) =>
|
|
4081
|
-
|
|
4082
|
-
|
|
4095
|
+
const labels = item.labels.map((l) => l.name).join(", ");
|
|
4096
|
+
return `- ${item.number}(${labels}): ${item.title}`;
|
|
4097
|
+
}
|
|
4098
|
+
var AI_ENTRY_TIMEOUT_MS = 6e4;
|
|
4099
|
+
function buildIndexEntryPrompt(item, kind) {
|
|
4100
|
+
const typeLabel = kind === "prs" ? "PR" : "Issue";
|
|
4101
|
+
const labels = item.labels.map((l) => l.name).join(", ");
|
|
4102
|
+
return `You are a dedup index entry generator. Given a GitHub ${typeLabel}, produce a concise one-line description suitable for duplicate detection.
|
|
4103
|
+
|
|
4104
|
+
## Input
|
|
4105
|
+
|
|
4106
|
+
${typeLabel} #${item.number}: ${item.title}
|
|
4107
|
+
Labels: ${labels || "(none)"}
|
|
4108
|
+
State: ${item.state}
|
|
4109
|
+
|
|
4110
|
+
## Output Format
|
|
4111
|
+
|
|
4112
|
+
Respond with ONLY a JSON object (no markdown fences, no preamble):
|
|
4113
|
+
|
|
4114
|
+
{
|
|
4115
|
+
"description": "<concise one-line description for duplicate detection>"
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
The description should capture the core intent/change of the ${typeLabel.toLowerCase()} in a way that helps identify duplicates. Keep it under 120 characters.`;
|
|
4119
|
+
}
|
|
4120
|
+
function parseIndexEntryResponse(stdout) {
|
|
4121
|
+
const jsonStr = extractJson(stdout);
|
|
4122
|
+
if (!jsonStr) return null;
|
|
4123
|
+
try {
|
|
4124
|
+
const parsed = JSON.parse(jsonStr);
|
|
4125
|
+
if (typeof parsed.description === "string" && parsed.description.length > 0) {
|
|
4126
|
+
return parsed.description;
|
|
4127
|
+
}
|
|
4128
|
+
} catch {
|
|
4129
|
+
}
|
|
4130
|
+
return null;
|
|
4131
|
+
}
|
|
4132
|
+
function resolveAgentCommand(toolName) {
|
|
4133
|
+
const config = loadConfig();
|
|
4134
|
+
if (config.agents) {
|
|
4135
|
+
const agent = config.agents.find((a) => a.tool === toolName);
|
|
4136
|
+
if (agent) {
|
|
4137
|
+
const cmd = agent.command ?? config.agentCommand;
|
|
4138
|
+
if (cmd) return cmd;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
const registryTool = DEFAULT_REGISTRY.tools.find((t) => t.name === toolName);
|
|
4142
|
+
if (registryTool) {
|
|
4143
|
+
const defaultModel = DEFAULT_REGISTRY.models.find((m) => m.tools.includes(toolName));
|
|
4144
|
+
const modelName = defaultModel?.name ?? "";
|
|
4145
|
+
return registryTool.commandTemplate.replaceAll("${MODEL}", modelName);
|
|
4146
|
+
}
|
|
4147
|
+
return null;
|
|
4148
|
+
}
|
|
4149
|
+
async function generateAIEntry(item, kind, commandTemplate, runTool = executeTool) {
|
|
4150
|
+
const prompt = buildIndexEntryPrompt(item, kind);
|
|
4151
|
+
try {
|
|
4152
|
+
const result = await runTool(commandTemplate, prompt, AI_ENTRY_TIMEOUT_MS);
|
|
4153
|
+
return parseIndexEntryResponse(result.stdout);
|
|
4154
|
+
} catch {
|
|
4155
|
+
return null;
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
function formatEntryWithDescription(item, description, compact = false) {
|
|
4159
|
+
if (compact) {
|
|
4160
|
+
return `- ${item.number}(): ${description}`;
|
|
4161
|
+
}
|
|
4162
|
+
const labels = item.labels.map((l) => l.name).join(", ");
|
|
4163
|
+
return `- ${item.number}(${labels}): ${description}`;
|
|
4083
4164
|
}
|
|
4084
4165
|
function categorizeItems(items, recentDays = DEFAULT_RECENT_DAYS, nowMs = Date.now()) {
|
|
4085
4166
|
const cutoff = nowMs - recentDays * 24 * 60 * 60 * 1e3;
|
|
@@ -4099,22 +4180,28 @@ function categorizeItems(items, recentDays = DEFAULT_RECENT_DAYS, nowMs = Date.n
|
|
|
4099
4180
|
}
|
|
4100
4181
|
function parseExistingNumbers(body) {
|
|
4101
4182
|
const numbers = /* @__PURE__ */ new Set();
|
|
4102
|
-
const regex = /^-
|
|
4183
|
+
const regex = /^- #?(\d+)/gm;
|
|
4103
4184
|
let match;
|
|
4104
4185
|
while ((match = regex.exec(body)) !== null) {
|
|
4105
4186
|
numbers.add(parseInt(match[1], 10));
|
|
4106
4187
|
}
|
|
4107
4188
|
return numbers;
|
|
4108
4189
|
}
|
|
4109
|
-
function buildCommentBody(marker, header, items, existingBody, compact = false) {
|
|
4190
|
+
function buildCommentBody(marker, header, items, existingBody, compact = false, descriptions) {
|
|
4110
4191
|
const existingNumbers = existingBody ? parseExistingNumbers(existingBody) : /* @__PURE__ */ new Set();
|
|
4111
4192
|
const newItems = items.filter((item) => !existingNumbers.has(item.number));
|
|
4112
4193
|
let body = existingBody ?? `${marker}
|
|
4113
4194
|
## ${header}
|
|
4114
4195
|
`;
|
|
4115
4196
|
for (const item of newItems) {
|
|
4116
|
-
|
|
4197
|
+
const aiDesc = descriptions?.get(item.number);
|
|
4198
|
+
if (aiDesc) {
|
|
4199
|
+
body += `
|
|
4200
|
+
${formatEntryWithDescription(item, aiDesc, compact)}`;
|
|
4201
|
+
} else {
|
|
4202
|
+
body += `
|
|
4117
4203
|
${formatEntry(item, compact)}`;
|
|
4204
|
+
}
|
|
4118
4205
|
}
|
|
4119
4206
|
return body;
|
|
4120
4207
|
}
|
|
@@ -4134,6 +4221,7 @@ async function initIndex(opts) {
|
|
|
4134
4221
|
const fetchFn = opts.fetchFn ?? fetch;
|
|
4135
4222
|
const log = opts.log ?? (() => {
|
|
4136
4223
|
});
|
|
4224
|
+
const runTool = opts.runTool ?? executeTool;
|
|
4137
4225
|
log(`Scanning ${kind}...`);
|
|
4138
4226
|
const items = kind === "prs" ? await fetchAllPRs(owner, repo, token, fetchFn, log) : await fetchAllIssues(owner, repo, token, fetchFn, log);
|
|
4139
4227
|
log(`${icons.info} Found ${items.length} ${kind}.`);
|
|
@@ -4143,34 +4231,64 @@ async function initIndex(opts) {
|
|
|
4143
4231
|
);
|
|
4144
4232
|
const comments = await fetchIssueComments2(owner, repo, indexIssue, token, fetchFn);
|
|
4145
4233
|
const found = findIndexComments(comments);
|
|
4146
|
-
const
|
|
4234
|
+
const existingOpen = found.open ? parseExistingNumbers(found.open.body) : /* @__PURE__ */ new Set();
|
|
4235
|
+
const existingRecent = found.recent ? parseExistingNumbers(found.recent.body) : /* @__PURE__ */ new Set();
|
|
4236
|
+
const existingArchived = found.archived ? parseExistingNumbers(found.archived.body) : /* @__PURE__ */ new Set();
|
|
4237
|
+
const newOpenItems = open.filter((i) => !existingOpen.has(i.number));
|
|
4238
|
+
const newRecentItems = recentlyClosed.filter((i) => !existingRecent.has(i.number));
|
|
4239
|
+
const newArchivedItems = archived.filter((i) => !existingArchived.has(i.number));
|
|
4240
|
+
const newEntries = newOpenItems.length + newRecentItems.length + newArchivedItems.length;
|
|
4241
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
4242
|
+
if (opts.agentCommandTemplate && newEntries > 0) {
|
|
4243
|
+
const allNewItems = [...newOpenItems, ...newRecentItems, ...newArchivedItems];
|
|
4244
|
+
log(`
|
|
4245
|
+
Generating AI-enriched descriptions for ${allNewItems.length} items...`);
|
|
4246
|
+
for (let i = 0; i < allNewItems.length; i++) {
|
|
4247
|
+
const item = allNewItems[i];
|
|
4248
|
+
log(` Processing item ${i + 1}/${allNewItems.length} (#${item.number})...`);
|
|
4249
|
+
const desc = await generateAIEntry(item, kind, opts.agentCommandTemplate, runTool);
|
|
4250
|
+
if (desc) {
|
|
4251
|
+
descriptions.set(item.number, desc);
|
|
4252
|
+
} else {
|
|
4253
|
+
log(` ${icons.warn} AI failed for #${item.number}, using raw title`);
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
const enriched = descriptions.size;
|
|
4257
|
+
log(
|
|
4258
|
+
`${icons.info} AI enrichment: ${enriched}/${allNewItems.length} items enriched successfully`
|
|
4259
|
+
);
|
|
4260
|
+
}
|
|
4261
|
+
const openBody = buildCommentBody(
|
|
4262
|
+
OPEN_MARKER,
|
|
4263
|
+
"Open Items",
|
|
4264
|
+
open,
|
|
4265
|
+
found.open?.body ?? null,
|
|
4266
|
+
false,
|
|
4267
|
+
descriptions
|
|
4268
|
+
);
|
|
4147
4269
|
const recentBody = buildCommentBody(
|
|
4148
4270
|
RECENT_MARKER,
|
|
4149
4271
|
"Recently Closed Items",
|
|
4150
4272
|
recentlyClosed,
|
|
4151
|
-
found.recent?.body ?? null
|
|
4273
|
+
found.recent?.body ?? null,
|
|
4274
|
+
false,
|
|
4275
|
+
descriptions
|
|
4152
4276
|
);
|
|
4153
4277
|
const archivedBody = buildCommentBody(
|
|
4154
4278
|
ARCHIVED_MARKER,
|
|
4155
4279
|
"Archived Items",
|
|
4156
4280
|
archived,
|
|
4157
4281
|
found.archived?.body ?? null,
|
|
4158
|
-
true
|
|
4282
|
+
true,
|
|
4159
4283
|
// compact format
|
|
4284
|
+
descriptions
|
|
4160
4285
|
);
|
|
4161
|
-
const existingOpen = found.open ? parseExistingNumbers(found.open.body) : /* @__PURE__ */ new Set();
|
|
4162
|
-
const existingRecent = found.recent ? parseExistingNumbers(found.recent.body) : /* @__PURE__ */ new Set();
|
|
4163
|
-
const existingArchived = found.archived ? parseExistingNumbers(found.archived.body) : /* @__PURE__ */ new Set();
|
|
4164
|
-
const newOpen = open.filter((i) => !existingOpen.has(i.number)).length;
|
|
4165
|
-
const newRecent = recentlyClosed.filter((i) => !existingRecent.has(i.number)).length;
|
|
4166
|
-
const newArchived = archived.filter((i) => !existingArchived.has(i.number)).length;
|
|
4167
|
-
const newEntries = newOpen + newRecent + newArchived;
|
|
4168
4286
|
if (dryRun) {
|
|
4169
4287
|
log(`
|
|
4170
4288
|
${icons.info} Dry run \u2014 would update index issue #${indexIssue}:`);
|
|
4171
|
-
log(` Open Items: ${open.length} entries (${
|
|
4172
|
-
log(` Recently Closed: ${recentlyClosed.length} entries (${
|
|
4173
|
-
log(` Archived: ${archived.length} entries (${
|
|
4289
|
+
log(` Open Items: ${open.length} entries (${newOpenItems.length} new)`);
|
|
4290
|
+
log(` Recently Closed: ${recentlyClosed.length} entries (${newRecentItems.length} new)`);
|
|
4291
|
+
log(` Archived: ${archived.length} entries (${newArchivedItems.length} new)`);
|
|
4174
4292
|
return {
|
|
4175
4293
|
openCount: open.length,
|
|
4176
4294
|
recentCount: recentlyClosed.length,
|
|
@@ -4209,6 +4327,7 @@ async function runDedupInit(options, deps = {}) {
|
|
|
4209
4327
|
const log = deps.log ?? console.log;
|
|
4210
4328
|
const logError = deps.logError ?? console.error;
|
|
4211
4329
|
const loadAuthFn = deps.loadAuthFn ?? loadAuth;
|
|
4330
|
+
const resolveCmd = deps.resolveAgentCommandFn ?? resolveAgentCommand;
|
|
4212
4331
|
const auth = loadAuthFn();
|
|
4213
4332
|
if (!auth || auth.expires_at <= Date.now()) {
|
|
4214
4333
|
logError(`${icons.error} Not authenticated. Run: ${pc3.cyan("opencara auth login")}`);
|
|
@@ -4273,6 +4392,19 @@ async function runDedupInit(options, deps = {}) {
|
|
|
4273
4392
|
process.exitCode = 1;
|
|
4274
4393
|
return;
|
|
4275
4394
|
}
|
|
4395
|
+
let agentCommandTemplate;
|
|
4396
|
+
if (options.agent) {
|
|
4397
|
+
const cmd = resolveCmd(options.agent);
|
|
4398
|
+
if (!cmd) {
|
|
4399
|
+
logError(
|
|
4400
|
+
`${icons.error} Unknown agent tool "${options.agent}". Available: ${DEFAULT_REGISTRY.tools.map((t) => t.name).join(", ")}`
|
|
4401
|
+
);
|
|
4402
|
+
process.exitCode = 1;
|
|
4403
|
+
return;
|
|
4404
|
+
}
|
|
4405
|
+
agentCommandTemplate = cmd;
|
|
4406
|
+
log(`Using AI agent "${options.agent}" for enriched descriptions`);
|
|
4407
|
+
}
|
|
4276
4408
|
for (const target of filteredTargets) {
|
|
4277
4409
|
log(`
|
|
4278
4410
|
${pc3.bold(`Initializing ${target.kind} dedup index (issue #${target.indexIssue})...`)}`);
|
|
@@ -4284,16 +4416,23 @@ ${pc3.bold(`Initializing ${target.kind} dedup index (issue #${target.indexIssue}
|
|
|
4284
4416
|
recentDays,
|
|
4285
4417
|
dryRun: options.dryRun ?? false,
|
|
4286
4418
|
token,
|
|
4419
|
+
agentCommandTemplate,
|
|
4287
4420
|
fetchFn,
|
|
4288
|
-
log
|
|
4421
|
+
log,
|
|
4422
|
+
runTool: deps.runTool
|
|
4289
4423
|
});
|
|
4290
4424
|
}
|
|
4291
4425
|
}
|
|
4292
4426
|
function dedupCommand() {
|
|
4293
4427
|
const dedup = new Command3("dedup").description("Dedup index management");
|
|
4294
|
-
dedup.command("init").description("Scan existing PRs/issues and populate dedup index").requiredOption("--repo <owner/repo>", "Target repository (e.g., OpenCara/OpenCara)").option("--all", "Initialize both PR and issue dedup indexes").option("--dry-run", "Show what would be done without making changes").option("--days <number>", "Recently closed window in days (default: 30)", "30").
|
|
4295
|
-
|
|
4296
|
-
|
|
4428
|
+
dedup.command("init").description("Scan existing PRs/issues and populate dedup index").requiredOption("--repo <owner/repo>", "Target repository (e.g., OpenCara/OpenCara)").option("--all", "Initialize both PR and issue dedup indexes").option("--dry-run", "Show what would be done without making changes").option("--days <number>", "Recently closed window in days (default: 30)", "30").option(
|
|
4429
|
+
"--agent <tool-name>",
|
|
4430
|
+
"Use AI agent to generate enriched descriptions (e.g., claude, codex, gemini, qwen)"
|
|
4431
|
+
).action(
|
|
4432
|
+
async (options) => {
|
|
4433
|
+
await runDedupInit(options);
|
|
4434
|
+
}
|
|
4435
|
+
);
|
|
4297
4436
|
return dedup;
|
|
4298
4437
|
}
|
|
4299
4438
|
|
|
@@ -4424,7 +4563,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
|
|
|
4424
4563
|
});
|
|
4425
4564
|
|
|
4426
4565
|
// src/index.ts
|
|
4427
|
-
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.18.
|
|
4566
|
+
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.18.2");
|
|
4428
4567
|
program.addCommand(agentCommand);
|
|
4429
4568
|
program.addCommand(authCommand());
|
|
4430
4569
|
program.addCommand(dedupCommand());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencara",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "Distributed AI code review agent — poll, review, and submit PR reviews using your own AI tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,10 +30,13 @@
|
|
|
30
30
|
"node": ">=20"
|
|
31
31
|
},
|
|
32
32
|
"bin": {
|
|
33
|
-
"opencara": "dist/index.js"
|
|
33
|
+
"opencara": "dist/index.js",
|
|
34
|
+
"opencara-codex-agent": "bin/opencara-codex-agent",
|
|
35
|
+
"opencara-gemini-agent": "bin/opencara-gemini-agent"
|
|
34
36
|
},
|
|
35
37
|
"files": [
|
|
36
38
|
"dist",
|
|
39
|
+
"bin",
|
|
37
40
|
"README.md"
|
|
38
41
|
],
|
|
39
42
|
"scripts": {
|