deepcode-ai 1.1.23 → 1.1.25
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/dist/index.js +188 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10360,6 +10360,28 @@ async function initCommand(cwd) {
|
|
|
10360
10360
|
const filePath = await new ConfigLoader().init(cwd);
|
|
10361
10361
|
await writeStdoutLine(`DeepCode config created at ${filePath}`);
|
|
10362
10362
|
}
|
|
10363
|
+
function resolveSessionTarget(config, overrides = {}) {
|
|
10364
|
+
const requestedProvider = parseProviderId(overrides.provider);
|
|
10365
|
+
const fallback = resolveUsableProviderTarget(config, [
|
|
10366
|
+
requestedProvider,
|
|
10367
|
+
config.defaultProvider
|
|
10368
|
+
]);
|
|
10369
|
+
const parsedSelection = overrides.model ? parseModelSelection(overrides.model, requestedProvider ?? fallback.provider) : null;
|
|
10370
|
+
if (overrides.model && !parsedSelection) {
|
|
10371
|
+
throw new Error(
|
|
10372
|
+
`Invalid model selection: ${overrides.model}. Use "<model>" or "<provider>/<model>".`
|
|
10373
|
+
);
|
|
10374
|
+
}
|
|
10375
|
+
const provider = parsedSelection?.provider ?? requestedProvider ?? fallback.provider;
|
|
10376
|
+
const model = parsedSelection?.model ?? resolveConfiguredModelForProvider(config, provider) ?? (provider === fallback.provider ? fallback.model : void 0);
|
|
10377
|
+
return { provider, model };
|
|
10378
|
+
}
|
|
10379
|
+
function parseProviderId(value) {
|
|
10380
|
+
if (!value) return void 0;
|
|
10381
|
+
const parsed = ProviderIdSchema.safeParse(value);
|
|
10382
|
+
if (parsed.success) return parsed.data;
|
|
10383
|
+
throw new Error(`Invalid provider: ${value}. Expected one of: ${PROVIDER_IDS.join(", ")}`);
|
|
10384
|
+
}
|
|
10363
10385
|
async function githubLoginCommand(options) {
|
|
10364
10386
|
const loader = new ConfigLoader();
|
|
10365
10387
|
const loadOptions = { cwd: options.cwd, configPath: options.config };
|
|
@@ -10626,6 +10648,7 @@ Focus areas: ${options.focus.join(", ")}.` : "";
|
|
|
10626
10648
|
const prompt = [
|
|
10627
10649
|
`Review PR #${pr.number}: ${pr.title}`,
|
|
10628
10650
|
`Branch: ${pr.head ?? "?"} \u2192 ${pr.base ?? "?"}`,
|
|
10651
|
+
"Do not modify any files. Output the review only.",
|
|
10629
10652
|
"",
|
|
10630
10653
|
pr.body ? `Description:
|
|
10631
10654
|
${pr.body}` : "No description provided.",
|
|
@@ -10641,18 +10664,24 @@ ${diff}
|
|
|
10641
10664
|
"3. **Suggestions** \u2014 improvements and nitpicks",
|
|
10642
10665
|
"4. **Verdict** \u2014 Approve / Request Changes / Neutral with a one-line rationale"
|
|
10643
10666
|
].join("\n");
|
|
10644
|
-
const target =
|
|
10667
|
+
const target = resolveSessionTarget(runtime.config, {});
|
|
10645
10668
|
const session = runtime.sessions.create({
|
|
10646
10669
|
provider: target.provider,
|
|
10647
10670
|
model: target.model
|
|
10648
10671
|
});
|
|
10649
10672
|
const secretValues = collectSecretValues(runtime.config);
|
|
10650
10673
|
await writeStdoutLine(`Reviewing PR #${pr.number}: ${pr.title}`);
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10674
|
+
try {
|
|
10675
|
+
await runtime.agent.run({
|
|
10676
|
+
session,
|
|
10677
|
+
input: prompt,
|
|
10678
|
+
mode: "plan",
|
|
10679
|
+
onChunk: (text) => void writeStdout(redactText(text, secretValues))
|
|
10680
|
+
});
|
|
10681
|
+
} finally {
|
|
10682
|
+
await runtime.sessions.persist(session.id).catch(() => {
|
|
10683
|
+
});
|
|
10684
|
+
}
|
|
10656
10685
|
await writeStdout("\n");
|
|
10657
10686
|
}
|
|
10658
10687
|
async function runGit(cwd, args) {
|
|
@@ -10665,28 +10694,6 @@ async function runGit(cwd, args) {
|
|
|
10665
10694
|
function slugify(input) {
|
|
10666
10695
|
return input.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 48);
|
|
10667
10696
|
}
|
|
10668
|
-
function resolveSessionTarget(config, overrides = {}) {
|
|
10669
|
-
const requestedProvider = parseProviderId(overrides.provider);
|
|
10670
|
-
const fallback = resolveUsableProviderTarget(config, [
|
|
10671
|
-
requestedProvider,
|
|
10672
|
-
config.defaultProvider
|
|
10673
|
-
]);
|
|
10674
|
-
const parsedSelection = overrides.model ? parseModelSelection(overrides.model, requestedProvider ?? fallback.provider) : null;
|
|
10675
|
-
if (overrides.model && !parsedSelection) {
|
|
10676
|
-
throw new Error(
|
|
10677
|
-
`Invalid model selection: ${overrides.model}. Use "<model>" or "<provider>/<model>".`
|
|
10678
|
-
);
|
|
10679
|
-
}
|
|
10680
|
-
const provider = parsedSelection?.provider ?? requestedProvider ?? fallback.provider;
|
|
10681
|
-
const model = parsedSelection?.model ?? resolveConfiguredModelForProvider(config, provider) ?? (provider === fallback.provider ? fallback.model : void 0);
|
|
10682
|
-
return { provider, model };
|
|
10683
|
-
}
|
|
10684
|
-
function parseProviderId(value) {
|
|
10685
|
-
if (!value) return void 0;
|
|
10686
|
-
const parsed = ProviderIdSchema.safeParse(value);
|
|
10687
|
-
if (parsed.success) return parsed.data;
|
|
10688
|
-
throw new Error(`Invalid provider: ${value}. Expected one of: ${PROVIDER_IDS.join(", ")}`);
|
|
10689
|
-
}
|
|
10690
10697
|
async function runCommand(input, options) {
|
|
10691
10698
|
if (options.mode && options.mode !== "plan" && options.mode !== "build") {
|
|
10692
10699
|
throw new Error(`Invalid mode: ${options.mode}. Expected plan or build.`);
|
|
@@ -10983,6 +10990,130 @@ async function projectsCommand(options) {
|
|
|
10983
10990
|
);
|
|
10984
10991
|
await waitUntilExit();
|
|
10985
10992
|
}
|
|
10993
|
+
var DIFF_MAX_CHARS = 2e4;
|
|
10994
|
+
async function runGit2(cwd, args) {
|
|
10995
|
+
const result = await execFileAsync("git", args, { cwd, timeoutMs: 3e4 });
|
|
10996
|
+
if (result.exitCode !== 0) {
|
|
10997
|
+
throw new Error(result.stderr || `git ${args.join(" ")} failed`);
|
|
10998
|
+
}
|
|
10999
|
+
return result.stdout;
|
|
11000
|
+
}
|
|
11001
|
+
async function isGitRepo(cwd) {
|
|
11002
|
+
const result = await execFileAsync(
|
|
11003
|
+
"git",
|
|
11004
|
+
["rev-parse", "--is-inside-work-tree"],
|
|
11005
|
+
{ cwd, timeoutMs: 5e3 }
|
|
11006
|
+
);
|
|
11007
|
+
return result.exitCode === 0;
|
|
11008
|
+
}
|
|
11009
|
+
function buildDiffArgs(options) {
|
|
11010
|
+
if (options.staged) {
|
|
11011
|
+
const args2 = ["diff", "--cached"];
|
|
11012
|
+
if (options.file) args2.push("--", options.file);
|
|
11013
|
+
return { args: args2, label: "staged changes" };
|
|
11014
|
+
}
|
|
11015
|
+
if (options.ref) {
|
|
11016
|
+
const args2 = ["diff", options.ref];
|
|
11017
|
+
if (options.file) args2.push("--", options.file);
|
|
11018
|
+
return { args: args2, label: `diff vs ${options.ref}` };
|
|
11019
|
+
}
|
|
11020
|
+
const args = ["diff", "HEAD"];
|
|
11021
|
+
if (options.file) args.push("--", options.file);
|
|
11022
|
+
return { args, label: options.file ? `local changes in ${options.file}` : "local changes vs HEAD" };
|
|
11023
|
+
}
|
|
11024
|
+
function buildPrompt(diff, label, focus, truncated) {
|
|
11025
|
+
const focusLine = focus.length > 0 ? `
|
|
11026
|
+
Focus areas: ${focus.join(", ")}.` : "";
|
|
11027
|
+
const truncationNote = truncated ? `
|
|
11028
|
+
(Diff truncated at ${DIFF_MAX_CHARS} characters; some changes are not shown.)
|
|
11029
|
+
` : "";
|
|
11030
|
+
return [
|
|
11031
|
+
`Review the following local git diff (${label}).`,
|
|
11032
|
+
"Do not modify any files. Output the review only.",
|
|
11033
|
+
focusLine,
|
|
11034
|
+
"",
|
|
11035
|
+
`\`\`\`diff`,
|
|
11036
|
+
diff,
|
|
11037
|
+
`\`\`\``,
|
|
11038
|
+
truncationNote,
|
|
11039
|
+
"Produce a structured code review:",
|
|
11040
|
+
"1. **Summary** \u2014 what changed (inferred from the diff)",
|
|
11041
|
+
"2. **Issues** \u2014 bugs, security concerns, logic errors, missing error handling; quote the relevant lines",
|
|
11042
|
+
"3. **Suggestions** \u2014 improvements and nitpicks",
|
|
11043
|
+
"4. **Verdict** \u2014 Looks good / Has issues, with a one-line rationale"
|
|
11044
|
+
].filter((l) => l !== void 0).join("\n");
|
|
11045
|
+
}
|
|
11046
|
+
async function reviewCommand(options) {
|
|
11047
|
+
if (!await isGitRepo(options.cwd)) {
|
|
11048
|
+
await writeStderrLine("error: not inside a git repository");
|
|
11049
|
+
process.exit(1);
|
|
11050
|
+
}
|
|
11051
|
+
const { args, label } = buildDiffArgs(options);
|
|
11052
|
+
let rawDiff;
|
|
11053
|
+
try {
|
|
11054
|
+
rawDiff = await runGit2(options.cwd, args);
|
|
11055
|
+
} catch (err) {
|
|
11056
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11057
|
+
await writeStderrLine(`error: ${msg}`);
|
|
11058
|
+
process.exit(1);
|
|
11059
|
+
}
|
|
11060
|
+
const trimmed = rawDiff.trim();
|
|
11061
|
+
if (!trimmed) {
|
|
11062
|
+
await writeStdoutLine(`No changes to review (${label}).`);
|
|
11063
|
+
return;
|
|
11064
|
+
}
|
|
11065
|
+
let diff = trimmed;
|
|
11066
|
+
let truncated = false;
|
|
11067
|
+
if (diff.length > DIFF_MAX_CHARS) {
|
|
11068
|
+
diff = diff.slice(0, DIFF_MAX_CHARS);
|
|
11069
|
+
truncated = true;
|
|
11070
|
+
}
|
|
11071
|
+
const runtime = await createRuntime({
|
|
11072
|
+
cwd: options.cwd,
|
|
11073
|
+
configPath: options.config,
|
|
11074
|
+
interactive: Boolean(options.yes)
|
|
11075
|
+
});
|
|
11076
|
+
if (options.yes) {
|
|
11077
|
+
runtime.events.on("approval:request", (request) => {
|
|
11078
|
+
runtime.events.emit("approval:decision", {
|
|
11079
|
+
requestId: request.id,
|
|
11080
|
+
decision: { allowed: true }
|
|
11081
|
+
});
|
|
11082
|
+
});
|
|
11083
|
+
}
|
|
11084
|
+
const target = resolveSessionTarget(runtime.config, {
|
|
11085
|
+
provider: options.provider,
|
|
11086
|
+
model: options.model
|
|
11087
|
+
});
|
|
11088
|
+
const session = runtime.sessions.create({
|
|
11089
|
+
provider: target.provider,
|
|
11090
|
+
model: target.model
|
|
11091
|
+
});
|
|
11092
|
+
const prompt = buildPrompt(diff, label, options.focus ?? [], truncated);
|
|
11093
|
+
const secretValues = collectSecretValues(runtime.config);
|
|
11094
|
+
await writeStdoutLine(`Reviewing ${label}\u2026
|
|
11095
|
+
`);
|
|
11096
|
+
let streamed = false;
|
|
11097
|
+
try {
|
|
11098
|
+
const output = await runtime.agent.run({
|
|
11099
|
+
session,
|
|
11100
|
+
input: prompt,
|
|
11101
|
+
mode: "plan",
|
|
11102
|
+
provider: target.provider,
|
|
11103
|
+
onChunk: (text) => {
|
|
11104
|
+
streamed = true;
|
|
11105
|
+
process.stdout.write(redactText(text, secretValues));
|
|
11106
|
+
}
|
|
11107
|
+
});
|
|
11108
|
+
if (!streamed && output) {
|
|
11109
|
+
process.stdout.write(redactText(output, secretValues));
|
|
11110
|
+
}
|
|
11111
|
+
if (!streamed || !output) process.stdout.write("\n");
|
|
11112
|
+
} finally {
|
|
11113
|
+
await runtime.sessions.persist(session.id).catch(() => {
|
|
11114
|
+
});
|
|
11115
|
+
}
|
|
11116
|
+
}
|
|
10986
11117
|
function sessionLabel(session) {
|
|
10987
11118
|
const name = typeof session.metadata["name"] === "string" ? session.metadata["name"] : void 0;
|
|
10988
11119
|
const firstUser = session.messages.find((m) => m.role === "user");
|
|
@@ -11260,7 +11391,7 @@ var FileSearchFactory = class {
|
|
|
11260
11391
|
return new ProjectFileSearch(options);
|
|
11261
11392
|
}
|
|
11262
11393
|
};
|
|
11263
|
-
var
|
|
11394
|
+
var execFileAsync4 = promisify(execFile3);
|
|
11264
11395
|
var GIT_TIMEOUT_MS = 5e3;
|
|
11265
11396
|
var MAX_FILES2 = 50;
|
|
11266
11397
|
var MAX_FILES_FOR_DETAILS = 500;
|
|
@@ -11285,7 +11416,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11285
11416
|
if (!gitRoot) return null;
|
|
11286
11417
|
if (await isInTransientGitState(gitRoot)) return null;
|
|
11287
11418
|
const [shortstatOut, untrackedOut] = await Promise.all([
|
|
11288
|
-
|
|
11419
|
+
runGit3(
|
|
11289
11420
|
[
|
|
11290
11421
|
"--no-optional-locks",
|
|
11291
11422
|
"diff",
|
|
@@ -11296,7 +11427,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11296
11427
|
],
|
|
11297
11428
|
gitRoot
|
|
11298
11429
|
),
|
|
11299
|
-
|
|
11430
|
+
runGit3(
|
|
11300
11431
|
[
|
|
11301
11432
|
"--no-optional-locks",
|
|
11302
11433
|
"ls-files",
|
|
@@ -11319,7 +11450,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11319
11450
|
};
|
|
11320
11451
|
}
|
|
11321
11452
|
const [numstatOut, nameStatusOut] = await Promise.all([
|
|
11322
|
-
|
|
11453
|
+
runGit3(
|
|
11323
11454
|
[
|
|
11324
11455
|
"--no-optional-locks",
|
|
11325
11456
|
"diff",
|
|
@@ -11331,7 +11462,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11331
11462
|
],
|
|
11332
11463
|
gitRoot
|
|
11333
11464
|
),
|
|
11334
|
-
|
|
11465
|
+
runGit3(
|
|
11335
11466
|
[
|
|
11336
11467
|
"--no-optional-locks",
|
|
11337
11468
|
"diff",
|
|
@@ -11491,7 +11622,7 @@ function splitNulDelimited(stdout) {
|
|
|
11491
11622
|
return stdout.split("\0").filter(Boolean);
|
|
11492
11623
|
}
|
|
11493
11624
|
async function resolveGitRoot(cwd) {
|
|
11494
|
-
const output = await
|
|
11625
|
+
const output = await runGit3(["rev-parse", "--show-toplevel"], cwd);
|
|
11495
11626
|
const root = output?.trim();
|
|
11496
11627
|
return root ? root : null;
|
|
11497
11628
|
}
|
|
@@ -11601,10 +11732,10 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
11601
11732
|
}
|
|
11602
11733
|
return results;
|
|
11603
11734
|
}
|
|
11604
|
-
async function
|
|
11735
|
+
async function runGit3(args, cwd) {
|
|
11605
11736
|
const fullArgs = ["-c", "core.quotepath=false", ...args];
|
|
11606
11737
|
try {
|
|
11607
|
-
const { stdout } = await
|
|
11738
|
+
const { stdout } = await execFileAsync4("git", fullArgs, {
|
|
11608
11739
|
cwd,
|
|
11609
11740
|
timeout: GIT_TIMEOUT_MS,
|
|
11610
11741
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -31218,6 +31349,27 @@ function createProgram() {
|
|
|
31218
31349
|
model: options.model
|
|
31219
31350
|
});
|
|
31220
31351
|
});
|
|
31352
|
+
program.command("review").description("AI code review of local git changes").argument("[ref]", "git ref to diff against (e.g. HEAD~3, main); defaults to HEAD").option("--staged", "review only staged changes (git diff --cached)").option("--file <path>", "limit review to a specific file").option(
|
|
31353
|
+
"--focus <area>",
|
|
31354
|
+
"focus area: security, performance, correctness, style; repeat for multiple",
|
|
31355
|
+
(val, acc) => {
|
|
31356
|
+
acc.push(val);
|
|
31357
|
+
return acc;
|
|
31358
|
+
},
|
|
31359
|
+
[]
|
|
31360
|
+
).option("--provider <provider>", "provider override").option("--model <model>", "model override").option("-y, --yes", "approve permission requests").action(async (ref, options) => {
|
|
31361
|
+
await reviewCommand({
|
|
31362
|
+
cwd: program.opts().cwd,
|
|
31363
|
+
config: program.opts().config,
|
|
31364
|
+
ref,
|
|
31365
|
+
staged: options.staged,
|
|
31366
|
+
file: options.file,
|
|
31367
|
+
focus: options.focus,
|
|
31368
|
+
provider: options.provider,
|
|
31369
|
+
model: options.model,
|
|
31370
|
+
yes: options.yes
|
|
31371
|
+
});
|
|
31372
|
+
});
|
|
31221
31373
|
program.command("projects").description('interactive project browser \u2014 Enter/c prints selected path (add shell fn: dc() { cd "$(deepcode projects)"; })').option("--path <path>", "root path to scan for git repos (default: $HOME)").action(async (options) => {
|
|
31222
31374
|
await projectsCommand({
|
|
31223
31375
|
cwd: options.path ?? process.env["HOME"] ?? program.opts().cwd
|