deepcode-ai 1.1.23 → 1.1.24
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 +153 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10983,6 +10983,130 @@ async function projectsCommand(options) {
|
|
|
10983
10983
|
);
|
|
10984
10984
|
await waitUntilExit();
|
|
10985
10985
|
}
|
|
10986
|
+
var DIFF_MAX_CHARS = 2e4;
|
|
10987
|
+
async function runGit2(cwd, args) {
|
|
10988
|
+
const result = await execFileAsync("git", args, { cwd, timeoutMs: 3e4 });
|
|
10989
|
+
if (result.exitCode !== 0) {
|
|
10990
|
+
throw new Error(result.stderr || `git ${args.join(" ")} failed`);
|
|
10991
|
+
}
|
|
10992
|
+
return result.stdout;
|
|
10993
|
+
}
|
|
10994
|
+
async function isGitRepo(cwd) {
|
|
10995
|
+
const result = await execFileAsync(
|
|
10996
|
+
"git",
|
|
10997
|
+
["rev-parse", "--is-inside-work-tree"],
|
|
10998
|
+
{ cwd, timeoutMs: 5e3 }
|
|
10999
|
+
);
|
|
11000
|
+
return result.exitCode === 0;
|
|
11001
|
+
}
|
|
11002
|
+
function buildDiffArgs(options) {
|
|
11003
|
+
if (options.staged) {
|
|
11004
|
+
const args2 = ["diff", "--cached"];
|
|
11005
|
+
if (options.file) args2.push("--", options.file);
|
|
11006
|
+
return { args: args2, label: "staged changes" };
|
|
11007
|
+
}
|
|
11008
|
+
if (options.ref) {
|
|
11009
|
+
const args2 = ["diff", options.ref];
|
|
11010
|
+
if (options.file) args2.push("--", options.file);
|
|
11011
|
+
return { args: args2, label: `diff vs ${options.ref}` };
|
|
11012
|
+
}
|
|
11013
|
+
const args = ["diff", "HEAD"];
|
|
11014
|
+
if (options.file) args.push("--", options.file);
|
|
11015
|
+
return { args, label: options.file ? `local changes in ${options.file}` : "local changes vs HEAD" };
|
|
11016
|
+
}
|
|
11017
|
+
function buildPrompt(diff, label, focus, truncated) {
|
|
11018
|
+
const focusLine = focus.length > 0 ? `
|
|
11019
|
+
Focus areas: ${focus.join(", ")}.` : "";
|
|
11020
|
+
const truncationNote = truncated ? `
|
|
11021
|
+
(Diff truncated at ${DIFF_MAX_CHARS} characters; some changes are not shown.)
|
|
11022
|
+
` : "";
|
|
11023
|
+
return [
|
|
11024
|
+
`Review the following local git diff (${label}).`,
|
|
11025
|
+
"Do not modify any files. Output the review only.",
|
|
11026
|
+
focusLine,
|
|
11027
|
+
"",
|
|
11028
|
+
`\`\`\`diff`,
|
|
11029
|
+
diff,
|
|
11030
|
+
`\`\`\``,
|
|
11031
|
+
truncationNote,
|
|
11032
|
+
"Produce a structured code review:",
|
|
11033
|
+
"1. **Summary** \u2014 what changed (inferred from the diff)",
|
|
11034
|
+
"2. **Issues** \u2014 bugs, security concerns, logic errors, missing error handling; quote the relevant lines",
|
|
11035
|
+
"3. **Suggestions** \u2014 improvements and nitpicks",
|
|
11036
|
+
"4. **Verdict** \u2014 Looks good / Has issues, with a one-line rationale"
|
|
11037
|
+
].filter((l) => l !== void 0).join("\n");
|
|
11038
|
+
}
|
|
11039
|
+
async function reviewCommand(options) {
|
|
11040
|
+
if (!await isGitRepo(options.cwd)) {
|
|
11041
|
+
await writeStderrLine("error: not inside a git repository");
|
|
11042
|
+
process.exit(1);
|
|
11043
|
+
}
|
|
11044
|
+
const { args, label } = buildDiffArgs(options);
|
|
11045
|
+
let rawDiff;
|
|
11046
|
+
try {
|
|
11047
|
+
rawDiff = await runGit2(options.cwd, args);
|
|
11048
|
+
} catch (err) {
|
|
11049
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11050
|
+
await writeStderrLine(`error: ${msg}`);
|
|
11051
|
+
process.exit(1);
|
|
11052
|
+
}
|
|
11053
|
+
const trimmed = rawDiff.trim();
|
|
11054
|
+
if (!trimmed) {
|
|
11055
|
+
await writeStdoutLine(`No changes to review (${label}).`);
|
|
11056
|
+
return;
|
|
11057
|
+
}
|
|
11058
|
+
let diff = trimmed;
|
|
11059
|
+
let truncated = false;
|
|
11060
|
+
if (diff.length > DIFF_MAX_CHARS) {
|
|
11061
|
+
diff = diff.slice(0, DIFF_MAX_CHARS);
|
|
11062
|
+
truncated = true;
|
|
11063
|
+
}
|
|
11064
|
+
const runtime = await createRuntime({
|
|
11065
|
+
cwd: options.cwd,
|
|
11066
|
+
configPath: options.config,
|
|
11067
|
+
interactive: Boolean(options.yes)
|
|
11068
|
+
});
|
|
11069
|
+
if (options.yes) {
|
|
11070
|
+
runtime.events.on("approval:request", (request) => {
|
|
11071
|
+
runtime.events.emit("approval:decision", {
|
|
11072
|
+
requestId: request.id,
|
|
11073
|
+
decision: { allowed: true }
|
|
11074
|
+
});
|
|
11075
|
+
});
|
|
11076
|
+
}
|
|
11077
|
+
const target = resolveSessionTarget(runtime.config, {
|
|
11078
|
+
provider: options.provider,
|
|
11079
|
+
model: options.model
|
|
11080
|
+
});
|
|
11081
|
+
const session = runtime.sessions.create({
|
|
11082
|
+
provider: target.provider,
|
|
11083
|
+
model: target.model
|
|
11084
|
+
});
|
|
11085
|
+
const prompt = buildPrompt(diff, label, options.focus ?? [], truncated);
|
|
11086
|
+
const secretValues = collectSecretValues(runtime.config);
|
|
11087
|
+
await writeStdoutLine(`Reviewing ${label}\u2026
|
|
11088
|
+
`);
|
|
11089
|
+
let streamed = false;
|
|
11090
|
+
try {
|
|
11091
|
+
const output = await runtime.agent.run({
|
|
11092
|
+
session,
|
|
11093
|
+
input: prompt,
|
|
11094
|
+
mode: "build",
|
|
11095
|
+
provider: target.provider,
|
|
11096
|
+
onChunk: (text) => {
|
|
11097
|
+
streamed = true;
|
|
11098
|
+
process.stdout.write(redactText(text, secretValues));
|
|
11099
|
+
}
|
|
11100
|
+
});
|
|
11101
|
+
if (!streamed && output) {
|
|
11102
|
+
process.stdout.write(redactText(output, secretValues));
|
|
11103
|
+
}
|
|
11104
|
+
if (!streamed || !output) process.stdout.write("\n");
|
|
11105
|
+
} finally {
|
|
11106
|
+
await runtime.sessions.persist(session.id).catch(() => {
|
|
11107
|
+
});
|
|
11108
|
+
}
|
|
11109
|
+
}
|
|
10986
11110
|
function sessionLabel(session) {
|
|
10987
11111
|
const name = typeof session.metadata["name"] === "string" ? session.metadata["name"] : void 0;
|
|
10988
11112
|
const firstUser = session.messages.find((m) => m.role === "user");
|
|
@@ -11260,7 +11384,7 @@ var FileSearchFactory = class {
|
|
|
11260
11384
|
return new ProjectFileSearch(options);
|
|
11261
11385
|
}
|
|
11262
11386
|
};
|
|
11263
|
-
var
|
|
11387
|
+
var execFileAsync4 = promisify(execFile3);
|
|
11264
11388
|
var GIT_TIMEOUT_MS = 5e3;
|
|
11265
11389
|
var MAX_FILES2 = 50;
|
|
11266
11390
|
var MAX_FILES_FOR_DETAILS = 500;
|
|
@@ -11285,7 +11409,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11285
11409
|
if (!gitRoot) return null;
|
|
11286
11410
|
if (await isInTransientGitState(gitRoot)) return null;
|
|
11287
11411
|
const [shortstatOut, untrackedOut] = await Promise.all([
|
|
11288
|
-
|
|
11412
|
+
runGit3(
|
|
11289
11413
|
[
|
|
11290
11414
|
"--no-optional-locks",
|
|
11291
11415
|
"diff",
|
|
@@ -11296,7 +11420,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11296
11420
|
],
|
|
11297
11421
|
gitRoot
|
|
11298
11422
|
),
|
|
11299
|
-
|
|
11423
|
+
runGit3(
|
|
11300
11424
|
[
|
|
11301
11425
|
"--no-optional-locks",
|
|
11302
11426
|
"ls-files",
|
|
@@ -11319,7 +11443,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11319
11443
|
};
|
|
11320
11444
|
}
|
|
11321
11445
|
const [numstatOut, nameStatusOut] = await Promise.all([
|
|
11322
|
-
|
|
11446
|
+
runGit3(
|
|
11323
11447
|
[
|
|
11324
11448
|
"--no-optional-locks",
|
|
11325
11449
|
"diff",
|
|
@@ -11331,7 +11455,7 @@ async function fetchGitDiff(cwd) {
|
|
|
11331
11455
|
],
|
|
11332
11456
|
gitRoot
|
|
11333
11457
|
),
|
|
11334
|
-
|
|
11458
|
+
runGit3(
|
|
11335
11459
|
[
|
|
11336
11460
|
"--no-optional-locks",
|
|
11337
11461
|
"diff",
|
|
@@ -11491,7 +11615,7 @@ function splitNulDelimited(stdout) {
|
|
|
11491
11615
|
return stdout.split("\0").filter(Boolean);
|
|
11492
11616
|
}
|
|
11493
11617
|
async function resolveGitRoot(cwd) {
|
|
11494
|
-
const output = await
|
|
11618
|
+
const output = await runGit3(["rev-parse", "--show-toplevel"], cwd);
|
|
11495
11619
|
const root = output?.trim();
|
|
11496
11620
|
return root ? root : null;
|
|
11497
11621
|
}
|
|
@@ -11601,10 +11725,10 @@ async function mapWithConcurrency(items, limit, mapper) {
|
|
|
11601
11725
|
}
|
|
11602
11726
|
return results;
|
|
11603
11727
|
}
|
|
11604
|
-
async function
|
|
11728
|
+
async function runGit3(args, cwd) {
|
|
11605
11729
|
const fullArgs = ["-c", "core.quotepath=false", ...args];
|
|
11606
11730
|
try {
|
|
11607
|
-
const { stdout } = await
|
|
11731
|
+
const { stdout } = await execFileAsync4("git", fullArgs, {
|
|
11608
11732
|
cwd,
|
|
11609
11733
|
timeout: GIT_TIMEOUT_MS,
|
|
11610
11734
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -31218,6 +31342,27 @@ function createProgram() {
|
|
|
31218
31342
|
model: options.model
|
|
31219
31343
|
});
|
|
31220
31344
|
});
|
|
31345
|
+
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(
|
|
31346
|
+
"--focus <area>",
|
|
31347
|
+
"focus area: security, performance, correctness, style; repeat for multiple",
|
|
31348
|
+
(val, acc) => {
|
|
31349
|
+
acc.push(val);
|
|
31350
|
+
return acc;
|
|
31351
|
+
},
|
|
31352
|
+
[]
|
|
31353
|
+
).option("--provider <provider>", "provider override").option("--model <model>", "model override").option("-y, --yes", "approve permission requests").action(async (ref, options) => {
|
|
31354
|
+
await reviewCommand({
|
|
31355
|
+
cwd: program.opts().cwd,
|
|
31356
|
+
config: program.opts().config,
|
|
31357
|
+
ref,
|
|
31358
|
+
staged: options.staged,
|
|
31359
|
+
file: options.file,
|
|
31360
|
+
focus: options.focus,
|
|
31361
|
+
provider: options.provider,
|
|
31362
|
+
model: options.model,
|
|
31363
|
+
yes: options.yes
|
|
31364
|
+
});
|
|
31365
|
+
});
|
|
31221
31366
|
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
31367
|
await projectsCommand({
|
|
31223
31368
|
cwd: options.path ?? process.env["HOME"] ?? program.opts().cwd
|