opencara 0.18.0 → 0.18.1
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 +167 -33
- package/package.json +1 -1
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,
|
|
@@ -2195,8 +2193,14 @@ You MUST output ONLY a valid JSON object matching this exact schema (no markdown
|
|
|
2195
2193
|
|
|
2196
2194
|
- "duplicates": array of matches found (empty array if no duplicates)
|
|
2197
2195
|
- "similarity": "exact" = identical intent/change, "high" = very similar with minor differences, "partial" = overlapping but distinct
|
|
2198
|
-
- "index_entry": a single line in the format: \`-
|
|
2196
|
+
- "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`);
|
|
2197
|
+
if (task.customPrompt) {
|
|
2198
|
+
parts.push(`
|
|
2199
|
+
## Repo-Specific Instructions
|
|
2199
2200
|
|
|
2201
|
+
${task.customPrompt}`);
|
|
2202
|
+
}
|
|
2203
|
+
parts.push(`
|
|
2200
2204
|
## Index of Existing Items
|
|
2201
2205
|
|
|
2202
2206
|
<UNTRUSTED_CONTENT>`);
|
|
@@ -2346,7 +2350,7 @@ async function executeDedup(prompt, timeoutSeconds, deps, runTool = executeTool,
|
|
|
2346
2350
|
}
|
|
2347
2351
|
async function executeDedupTask(client, agentId, taskId, task, diffContent, timeoutSeconds, reviewDeps, consumptionDeps, logger, signal, role = "pr_dedup") {
|
|
2348
2352
|
logger.log(` ${icons.running} Executing dedup: ${reviewDeps.commandTemplate}`);
|
|
2349
|
-
const prompt = buildDedupPrompt({ ...task, diffContent });
|
|
2353
|
+
const prompt = buildDedupPrompt({ ...task, diffContent, customPrompt: task.prompt });
|
|
2350
2354
|
const result = await executeDedup(
|
|
2351
2355
|
prompt,
|
|
2352
2356
|
timeoutSeconds,
|
|
@@ -2607,6 +2611,11 @@ function buildTriagePrompt(task) {
|
|
|
2607
2611
|
const title = task.issue_title ?? `PR #${task.pr_number}`;
|
|
2608
2612
|
const rawBody = task.issue_body ?? "";
|
|
2609
2613
|
const safeBody = truncateToBytes(rawBody, MAX_ISSUE_BODY_BYTES);
|
|
2614
|
+
const repoPromptSection = task.prompt ? `
|
|
2615
|
+
|
|
2616
|
+
## Repo-Specific Instructions
|
|
2617
|
+
|
|
2618
|
+
${task.prompt}` : "";
|
|
2610
2619
|
const userMessage = [
|
|
2611
2620
|
`## Issue Title`,
|
|
2612
2621
|
title,
|
|
@@ -2616,7 +2625,7 @@ function buildTriagePrompt(task) {
|
|
|
2616
2625
|
safeBody,
|
|
2617
2626
|
"</UNTRUSTED_CONTENT>"
|
|
2618
2627
|
].join("\n");
|
|
2619
|
-
return `${TRIAGE_SYSTEM_PROMPT}
|
|
2628
|
+
return `${TRIAGE_SYSTEM_PROMPT}${repoPromptSection}
|
|
2620
2629
|
|
|
2621
2630
|
${userMessage}`;
|
|
2622
2631
|
}
|
|
@@ -3167,7 +3176,8 @@ async function handleTask(client, agentId, task, reviewDeps, consumptionDeps, ag
|
|
|
3167
3176
|
issue_title: task.issue_title,
|
|
3168
3177
|
issue_body: task.issue_body,
|
|
3169
3178
|
diff_url,
|
|
3170
|
-
index_issue_body: task.index_issue_body
|
|
3179
|
+
index_issue_body: task.index_issue_body,
|
|
3180
|
+
prompt
|
|
3171
3181
|
},
|
|
3172
3182
|
diffContent,
|
|
3173
3183
|
timeout_seconds,
|
|
@@ -3568,7 +3578,7 @@ function sleep2(ms, signal) {
|
|
|
3568
3578
|
async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
|
|
3569
3579
|
const client = new ApiClient(platformUrl, {
|
|
3570
3580
|
authToken: options?.authToken,
|
|
3571
|
-
cliVersion: "0.18.
|
|
3581
|
+
cliVersion: "0.18.1",
|
|
3572
3582
|
versionOverride: options?.versionOverride,
|
|
3573
3583
|
onTokenRefresh: options?.onTokenRefresh
|
|
3574
3584
|
});
|
|
@@ -4075,11 +4085,77 @@ async function updateIssueComment(owner, repo, commentId, body, token, fetchFn =
|
|
|
4075
4085
|
}
|
|
4076
4086
|
function formatEntry(item, compact = false) {
|
|
4077
4087
|
if (compact) {
|
|
4078
|
-
return `-
|
|
4088
|
+
return `- ${item.number}(): ${item.title}`;
|
|
4089
|
+
}
|
|
4090
|
+
const labels = item.labels.map((l) => l.name).join(", ");
|
|
4091
|
+
return `- ${item.number}(${labels}): ${item.title}`;
|
|
4092
|
+
}
|
|
4093
|
+
var AI_ENTRY_TIMEOUT_MS = 6e4;
|
|
4094
|
+
function buildIndexEntryPrompt(item, kind) {
|
|
4095
|
+
const typeLabel = kind === "prs" ? "PR" : "Issue";
|
|
4096
|
+
const labels = item.labels.map((l) => l.name).join(", ");
|
|
4097
|
+
return `You are a dedup index entry generator. Given a GitHub ${typeLabel}, produce a concise one-line description suitable for duplicate detection.
|
|
4098
|
+
|
|
4099
|
+
## Input
|
|
4100
|
+
|
|
4101
|
+
${typeLabel} #${item.number}: ${item.title}
|
|
4102
|
+
Labels: ${labels || "(none)"}
|
|
4103
|
+
State: ${item.state}
|
|
4104
|
+
|
|
4105
|
+
## Output Format
|
|
4106
|
+
|
|
4107
|
+
Respond with ONLY a JSON object (no markdown fences, no preamble):
|
|
4108
|
+
|
|
4109
|
+
{
|
|
4110
|
+
"description": "<concise one-line description for duplicate detection>"
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
The description should capture the core intent/change of the ${typeLabel.toLowerCase()} in a way that helps identify duplicates. Keep it under 120 characters.`;
|
|
4114
|
+
}
|
|
4115
|
+
function parseIndexEntryResponse(stdout) {
|
|
4116
|
+
const jsonStr = extractJson(stdout);
|
|
4117
|
+
if (!jsonStr) return null;
|
|
4118
|
+
try {
|
|
4119
|
+
const parsed = JSON.parse(jsonStr);
|
|
4120
|
+
if (typeof parsed.description === "string" && parsed.description.length > 0) {
|
|
4121
|
+
return parsed.description;
|
|
4122
|
+
}
|
|
4123
|
+
} catch {
|
|
4124
|
+
}
|
|
4125
|
+
return null;
|
|
4126
|
+
}
|
|
4127
|
+
function resolveAgentCommand(toolName) {
|
|
4128
|
+
const config = loadConfig();
|
|
4129
|
+
if (config.agents) {
|
|
4130
|
+
const agent = config.agents.find((a) => a.tool === toolName);
|
|
4131
|
+
if (agent) {
|
|
4132
|
+
const cmd = agent.command ?? config.agentCommand;
|
|
4133
|
+
if (cmd) return cmd;
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
const registryTool = DEFAULT_REGISTRY.tools.find((t) => t.name === toolName);
|
|
4137
|
+
if (registryTool) {
|
|
4138
|
+
const defaultModel = DEFAULT_REGISTRY.models.find((m) => m.tools.includes(toolName));
|
|
4139
|
+
const modelName = defaultModel?.name ?? "";
|
|
4140
|
+
return registryTool.commandTemplate.replaceAll("${MODEL}", modelName);
|
|
4141
|
+
}
|
|
4142
|
+
return null;
|
|
4143
|
+
}
|
|
4144
|
+
async function generateAIEntry(item, kind, commandTemplate, runTool = executeTool) {
|
|
4145
|
+
const prompt = buildIndexEntryPrompt(item, kind);
|
|
4146
|
+
try {
|
|
4147
|
+
const result = await runTool(commandTemplate, prompt, AI_ENTRY_TIMEOUT_MS);
|
|
4148
|
+
return parseIndexEntryResponse(result.stdout);
|
|
4149
|
+
} catch {
|
|
4150
|
+
return null;
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
function formatEntryWithDescription(item, description, compact = false) {
|
|
4154
|
+
if (compact) {
|
|
4155
|
+
return `- ${item.number}(): ${description}`;
|
|
4079
4156
|
}
|
|
4080
|
-
const labels = item.labels.map((l) =>
|
|
4081
|
-
|
|
4082
|
-
return `- #${item.number}${labelPart} \u2014 ${item.title}`;
|
|
4157
|
+
const labels = item.labels.map((l) => l.name).join(", ");
|
|
4158
|
+
return `- ${item.number}(${labels}): ${description}`;
|
|
4083
4159
|
}
|
|
4084
4160
|
function categorizeItems(items, recentDays = DEFAULT_RECENT_DAYS, nowMs = Date.now()) {
|
|
4085
4161
|
const cutoff = nowMs - recentDays * 24 * 60 * 60 * 1e3;
|
|
@@ -4099,22 +4175,28 @@ function categorizeItems(items, recentDays = DEFAULT_RECENT_DAYS, nowMs = Date.n
|
|
|
4099
4175
|
}
|
|
4100
4176
|
function parseExistingNumbers(body) {
|
|
4101
4177
|
const numbers = /* @__PURE__ */ new Set();
|
|
4102
|
-
const regex = /^-
|
|
4178
|
+
const regex = /^- #?(\d+)/gm;
|
|
4103
4179
|
let match;
|
|
4104
4180
|
while ((match = regex.exec(body)) !== null) {
|
|
4105
4181
|
numbers.add(parseInt(match[1], 10));
|
|
4106
4182
|
}
|
|
4107
4183
|
return numbers;
|
|
4108
4184
|
}
|
|
4109
|
-
function buildCommentBody(marker, header, items, existingBody, compact = false) {
|
|
4185
|
+
function buildCommentBody(marker, header, items, existingBody, compact = false, descriptions) {
|
|
4110
4186
|
const existingNumbers = existingBody ? parseExistingNumbers(existingBody) : /* @__PURE__ */ new Set();
|
|
4111
4187
|
const newItems = items.filter((item) => !existingNumbers.has(item.number));
|
|
4112
4188
|
let body = existingBody ?? `${marker}
|
|
4113
4189
|
## ${header}
|
|
4114
4190
|
`;
|
|
4115
4191
|
for (const item of newItems) {
|
|
4116
|
-
|
|
4192
|
+
const aiDesc = descriptions?.get(item.number);
|
|
4193
|
+
if (aiDesc) {
|
|
4194
|
+
body += `
|
|
4195
|
+
${formatEntryWithDescription(item, aiDesc, compact)}`;
|
|
4196
|
+
} else {
|
|
4197
|
+
body += `
|
|
4117
4198
|
${formatEntry(item, compact)}`;
|
|
4199
|
+
}
|
|
4118
4200
|
}
|
|
4119
4201
|
return body;
|
|
4120
4202
|
}
|
|
@@ -4134,6 +4216,7 @@ async function initIndex(opts) {
|
|
|
4134
4216
|
const fetchFn = opts.fetchFn ?? fetch;
|
|
4135
4217
|
const log = opts.log ?? (() => {
|
|
4136
4218
|
});
|
|
4219
|
+
const runTool = opts.runTool ?? executeTool;
|
|
4137
4220
|
log(`Scanning ${kind}...`);
|
|
4138
4221
|
const items = kind === "prs" ? await fetchAllPRs(owner, repo, token, fetchFn, log) : await fetchAllIssues(owner, repo, token, fetchFn, log);
|
|
4139
4222
|
log(`${icons.info} Found ${items.length} ${kind}.`);
|
|
@@ -4143,34 +4226,64 @@ async function initIndex(opts) {
|
|
|
4143
4226
|
);
|
|
4144
4227
|
const comments = await fetchIssueComments2(owner, repo, indexIssue, token, fetchFn);
|
|
4145
4228
|
const found = findIndexComments(comments);
|
|
4146
|
-
const
|
|
4229
|
+
const existingOpen = found.open ? parseExistingNumbers(found.open.body) : /* @__PURE__ */ new Set();
|
|
4230
|
+
const existingRecent = found.recent ? parseExistingNumbers(found.recent.body) : /* @__PURE__ */ new Set();
|
|
4231
|
+
const existingArchived = found.archived ? parseExistingNumbers(found.archived.body) : /* @__PURE__ */ new Set();
|
|
4232
|
+
const newOpenItems = open.filter((i) => !existingOpen.has(i.number));
|
|
4233
|
+
const newRecentItems = recentlyClosed.filter((i) => !existingRecent.has(i.number));
|
|
4234
|
+
const newArchivedItems = archived.filter((i) => !existingArchived.has(i.number));
|
|
4235
|
+
const newEntries = newOpenItems.length + newRecentItems.length + newArchivedItems.length;
|
|
4236
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
4237
|
+
if (opts.agentCommandTemplate && newEntries > 0) {
|
|
4238
|
+
const allNewItems = [...newOpenItems, ...newRecentItems, ...newArchivedItems];
|
|
4239
|
+
log(`
|
|
4240
|
+
Generating AI-enriched descriptions for ${allNewItems.length} items...`);
|
|
4241
|
+
for (let i = 0; i < allNewItems.length; i++) {
|
|
4242
|
+
const item = allNewItems[i];
|
|
4243
|
+
log(` Processing item ${i + 1}/${allNewItems.length} (#${item.number})...`);
|
|
4244
|
+
const desc = await generateAIEntry(item, kind, opts.agentCommandTemplate, runTool);
|
|
4245
|
+
if (desc) {
|
|
4246
|
+
descriptions.set(item.number, desc);
|
|
4247
|
+
} else {
|
|
4248
|
+
log(` ${icons.warn} AI failed for #${item.number}, using raw title`);
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
const enriched = descriptions.size;
|
|
4252
|
+
log(
|
|
4253
|
+
`${icons.info} AI enrichment: ${enriched}/${allNewItems.length} items enriched successfully`
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
const openBody = buildCommentBody(
|
|
4257
|
+
OPEN_MARKER,
|
|
4258
|
+
"Open Items",
|
|
4259
|
+
open,
|
|
4260
|
+
found.open?.body ?? null,
|
|
4261
|
+
false,
|
|
4262
|
+
descriptions
|
|
4263
|
+
);
|
|
4147
4264
|
const recentBody = buildCommentBody(
|
|
4148
4265
|
RECENT_MARKER,
|
|
4149
4266
|
"Recently Closed Items",
|
|
4150
4267
|
recentlyClosed,
|
|
4151
|
-
found.recent?.body ?? null
|
|
4268
|
+
found.recent?.body ?? null,
|
|
4269
|
+
false,
|
|
4270
|
+
descriptions
|
|
4152
4271
|
);
|
|
4153
4272
|
const archivedBody = buildCommentBody(
|
|
4154
4273
|
ARCHIVED_MARKER,
|
|
4155
4274
|
"Archived Items",
|
|
4156
4275
|
archived,
|
|
4157
4276
|
found.archived?.body ?? null,
|
|
4158
|
-
true
|
|
4277
|
+
true,
|
|
4159
4278
|
// compact format
|
|
4279
|
+
descriptions
|
|
4160
4280
|
);
|
|
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
4281
|
if (dryRun) {
|
|
4169
4282
|
log(`
|
|
4170
4283
|
${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 (${
|
|
4284
|
+
log(` Open Items: ${open.length} entries (${newOpenItems.length} new)`);
|
|
4285
|
+
log(` Recently Closed: ${recentlyClosed.length} entries (${newRecentItems.length} new)`);
|
|
4286
|
+
log(` Archived: ${archived.length} entries (${newArchivedItems.length} new)`);
|
|
4174
4287
|
return {
|
|
4175
4288
|
openCount: open.length,
|
|
4176
4289
|
recentCount: recentlyClosed.length,
|
|
@@ -4209,6 +4322,7 @@ async function runDedupInit(options, deps = {}) {
|
|
|
4209
4322
|
const log = deps.log ?? console.log;
|
|
4210
4323
|
const logError = deps.logError ?? console.error;
|
|
4211
4324
|
const loadAuthFn = deps.loadAuthFn ?? loadAuth;
|
|
4325
|
+
const resolveCmd = deps.resolveAgentCommandFn ?? resolveAgentCommand;
|
|
4212
4326
|
const auth = loadAuthFn();
|
|
4213
4327
|
if (!auth || auth.expires_at <= Date.now()) {
|
|
4214
4328
|
logError(`${icons.error} Not authenticated. Run: ${pc3.cyan("opencara auth login")}`);
|
|
@@ -4273,6 +4387,19 @@ async function runDedupInit(options, deps = {}) {
|
|
|
4273
4387
|
process.exitCode = 1;
|
|
4274
4388
|
return;
|
|
4275
4389
|
}
|
|
4390
|
+
let agentCommandTemplate;
|
|
4391
|
+
if (options.agent) {
|
|
4392
|
+
const cmd = resolveCmd(options.agent);
|
|
4393
|
+
if (!cmd) {
|
|
4394
|
+
logError(
|
|
4395
|
+
`${icons.error} Unknown agent tool "${options.agent}". Available: ${DEFAULT_REGISTRY.tools.map((t) => t.name).join(", ")}`
|
|
4396
|
+
);
|
|
4397
|
+
process.exitCode = 1;
|
|
4398
|
+
return;
|
|
4399
|
+
}
|
|
4400
|
+
agentCommandTemplate = cmd;
|
|
4401
|
+
log(`Using AI agent "${options.agent}" for enriched descriptions`);
|
|
4402
|
+
}
|
|
4276
4403
|
for (const target of filteredTargets) {
|
|
4277
4404
|
log(`
|
|
4278
4405
|
${pc3.bold(`Initializing ${target.kind} dedup index (issue #${target.indexIssue})...`)}`);
|
|
@@ -4284,16 +4411,23 @@ ${pc3.bold(`Initializing ${target.kind} dedup index (issue #${target.indexIssue}
|
|
|
4284
4411
|
recentDays,
|
|
4285
4412
|
dryRun: options.dryRun ?? false,
|
|
4286
4413
|
token,
|
|
4414
|
+
agentCommandTemplate,
|
|
4287
4415
|
fetchFn,
|
|
4288
|
-
log
|
|
4416
|
+
log,
|
|
4417
|
+
runTool: deps.runTool
|
|
4289
4418
|
});
|
|
4290
4419
|
}
|
|
4291
4420
|
}
|
|
4292
4421
|
function dedupCommand() {
|
|
4293
4422
|
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
|
-
|
|
4423
|
+
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(
|
|
4424
|
+
"--agent <tool-name>",
|
|
4425
|
+
"Use AI agent to generate enriched descriptions (e.g., claude, codex, gemini, qwen)"
|
|
4426
|
+
).action(
|
|
4427
|
+
async (options) => {
|
|
4428
|
+
await runDedupInit(options);
|
|
4429
|
+
}
|
|
4430
|
+
);
|
|
4297
4431
|
return dedup;
|
|
4298
4432
|
}
|
|
4299
4433
|
|
|
@@ -4424,7 +4558,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
|
|
|
4424
4558
|
});
|
|
4425
4559
|
|
|
4426
4560
|
// src/index.ts
|
|
4427
|
-
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.18.
|
|
4561
|
+
var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version("0.18.1");
|
|
4428
4562
|
program.addCommand(agentCommand);
|
|
4429
4563
|
program.addCommand(authCommand());
|
|
4430
4564
|
program.addCommand(dedupCommand());
|