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 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 = resolveUsableProviderTarget(runtime.config, [runtime.config.defaultProvider]);
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
- await runtime.agent.run({
10652
- session,
10653
- input: prompt,
10654
- onChunk: (text) => void writeStdout(redactText(text, secretValues))
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 execFileAsync3 = promisify(execFile3);
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
- runGit2(
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
- runGit2(
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
- runGit2(
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
- runGit2(
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 runGit2(["rev-parse", "--show-toplevel"], cwd);
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 runGit2(args, cwd) {
11735
+ async function runGit3(args, cwd) {
11605
11736
  const fullArgs = ["-c", "core.quotepath=false", ...args];
11606
11737
  try {
11607
- const { stdout } = await execFileAsync3("git", fullArgs, {
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