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 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 execFileAsync3 = promisify(execFile3);
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
- runGit2(
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
- runGit2(
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
- runGit2(
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
- runGit2(
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 runGit2(["rev-parse", "--show-toplevel"], cwd);
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 runGit2(args, cwd) {
11728
+ async function runGit3(args, cwd) {
11605
11729
  const fullArgs = ["-c", "core.quotepath=false", ...args];
11606
11730
  try {
11607
- const { stdout } = await execFileAsync3("git", fullArgs, {
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