deepcode-ai 1.1.24 → 1.1.26

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,163 @@ 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
+ }
10385
+ var DIFF_MAX_CHARS = 2e4;
10386
+ function truncateDiff(raw, maxChars = DIFF_MAX_CHARS) {
10387
+ const fileChunks = raw.split(/(?=^diff --git )/m).filter(Boolean);
10388
+ const totalFiles = fileChunks.length;
10389
+ let result = "";
10390
+ let included = 0;
10391
+ for (const chunk of fileChunks) {
10392
+ if (result.length + chunk.length > maxChars) break;
10393
+ result += chunk;
10394
+ included++;
10395
+ }
10396
+ if (!result && fileChunks.length > 0) {
10397
+ result = fileChunks[0].slice(0, maxChars);
10398
+ included = 1;
10399
+ }
10400
+ return { diff: result.trimEnd(), omittedFiles: totalFiles - included, totalFiles };
10401
+ }
10402
+ async function runGit(cwd, args) {
10403
+ const result = await execFileAsync("git", args, { cwd, timeoutMs: 3e4 });
10404
+ if (result.exitCode !== 0) {
10405
+ throw new Error(result.stderr || `git ${args.join(" ")} failed`);
10406
+ }
10407
+ return result.stdout;
10408
+ }
10409
+ async function isGitRepo(cwd) {
10410
+ const result = await execFileAsync(
10411
+ "git",
10412
+ ["rev-parse", "--is-inside-work-tree"],
10413
+ { cwd, timeoutMs: 5e3 }
10414
+ );
10415
+ return result.exitCode === 0;
10416
+ }
10417
+ function buildDiffArgs(options) {
10418
+ if (options.staged) {
10419
+ const args2 = ["diff", "--cached"];
10420
+ if (options.file) args2.push("--", options.file);
10421
+ return { args: args2, label: "staged changes" };
10422
+ }
10423
+ if (options.ref) {
10424
+ const args2 = ["diff", options.ref];
10425
+ if (options.file) args2.push("--", options.file);
10426
+ return { args: args2, label: `diff vs ${options.ref}` };
10427
+ }
10428
+ const args = ["diff", "HEAD"];
10429
+ if (options.file) args.push("--", options.file);
10430
+ return { args, label: options.file ? `local changes in ${options.file}` : "local changes vs HEAD" };
10431
+ }
10432
+ function buildPrompt(diff, label, focus, truncation) {
10433
+ const focusLine = focus.length > 0 ? `
10434
+ Focus areas: ${focus.join(", ")}.` : "";
10435
+ const truncationNote = truncation.omittedFiles > 0 ? `
10436
+ (Showing ${truncation.totalFiles - truncation.omittedFiles} of ${truncation.totalFiles} changed files; ${truncation.omittedFiles} file(s) omitted due to size.)
10437
+ ` : "";
10438
+ return [
10439
+ `Review the following local git diff (${label}).`,
10440
+ "Do not modify any files. Output the review only.",
10441
+ focusLine,
10442
+ "",
10443
+ `\`\`\`diff`,
10444
+ diff,
10445
+ `\`\`\``,
10446
+ truncationNote,
10447
+ "Produce a structured code review:",
10448
+ "1. **Summary** \u2014 what changed (inferred from the diff)",
10449
+ "2. **Issues** \u2014 bugs, security concerns, logic errors, missing error handling; quote the relevant lines",
10450
+ "3. **Suggestions** \u2014 improvements and nitpicks",
10451
+ "4. **Verdict** \u2014 Looks good / Has issues, with a one-line rationale"
10452
+ ].filter((l) => l !== void 0).join("\n");
10453
+ }
10454
+ async function reviewCommand(options) {
10455
+ if (!await isGitRepo(options.cwd)) {
10456
+ await writeStderrLine("error: not inside a git repository");
10457
+ process.exit(1);
10458
+ }
10459
+ const { args, label } = buildDiffArgs(options);
10460
+ let rawDiff;
10461
+ try {
10462
+ rawDiff = await runGit(options.cwd, args);
10463
+ } catch (err) {
10464
+ const msg = err instanceof Error ? err.message : String(err);
10465
+ await writeStderrLine(`error: ${msg}`);
10466
+ process.exit(1);
10467
+ }
10468
+ const trimmed = rawDiff.trim();
10469
+ if (!trimmed) {
10470
+ await writeStdoutLine(`No changes to review (${label}).`);
10471
+ return;
10472
+ }
10473
+ const truncation = truncateDiff(trimmed);
10474
+ const runtime = await createRuntime({
10475
+ cwd: options.cwd,
10476
+ configPath: options.config,
10477
+ interactive: Boolean(options.yes)
10478
+ });
10479
+ if (options.yes) {
10480
+ runtime.events.on("approval:request", (request) => {
10481
+ runtime.events.emit("approval:decision", {
10482
+ requestId: request.id,
10483
+ decision: { allowed: true }
10484
+ });
10485
+ });
10486
+ }
10487
+ const target = resolveSessionTarget(runtime.config, {
10488
+ provider: options.provider,
10489
+ model: options.model
10490
+ });
10491
+ const session = runtime.sessions.create({
10492
+ provider: target.provider,
10493
+ model: target.model
10494
+ });
10495
+ const prompt = buildPrompt(truncation.diff, label, options.focus ?? [], truncation);
10496
+ const secretValues = collectSecretValues(runtime.config);
10497
+ await writeStdoutLine(`Reviewing ${label}\u2026
10498
+ `);
10499
+ let streamed = false;
10500
+ try {
10501
+ const output = await runtime.agent.run({
10502
+ session,
10503
+ input: prompt,
10504
+ mode: "plan",
10505
+ provider: target.provider,
10506
+ onChunk: (text) => {
10507
+ streamed = true;
10508
+ process.stdout.write(redactText(text, secretValues));
10509
+ }
10510
+ });
10511
+ if (!streamed && output) {
10512
+ process.stdout.write(redactText(output, secretValues));
10513
+ }
10514
+ if (!streamed || !output) process.stdout.write("\n");
10515
+ } finally {
10516
+ await runtime.sessions.persist(session.id).catch(() => {
10517
+ });
10518
+ }
10519
+ }
10363
10520
  async function githubLoginCommand(options) {
10364
10521
  const loader = new ConfigLoader();
10365
10522
  const loadOptions = { cwd: options.cwd, configPath: options.config };
@@ -10539,8 +10696,8 @@ async function solveIssueCommand(issueNumber, options) {
10539
10696
  const issue = await client.getIssue({ ...repo, number: issueNumber });
10540
10697
  const base = options.base ?? "main";
10541
10698
  const branch = `deepcode/issue-${issueNumber}-${slugify(issue.title)}`.slice(0, 80);
10542
- await runGit(options.cwd, ["fetch", "origin", base]);
10543
- await runGit(options.cwd, ["checkout", "-B", branch, `origin/${base}`]);
10699
+ await runGit2(options.cwd, ["fetch", "origin", base]);
10700
+ await runGit2(options.cwd, ["checkout", "-B", branch, `origin/${base}`]);
10544
10701
  const target = resolveUsableProviderTarget(runtime.config, [runtime.config.defaultProvider]);
10545
10702
  const session = runtime.sessions.create({
10546
10703
  provider: target.provider,
@@ -10565,16 +10722,16 @@ async function solveIssueCommand(issueNumber, options) {
10565
10722
  onChunk: (text) => void writeStdout(redactText(text, secretValues))
10566
10723
  });
10567
10724
  await writeStdout("\n");
10568
- const status = await runGit(options.cwd, ["status", "--porcelain"]);
10569
- const aheadLog = await runGit(options.cwd, ["log", `origin/${base}..HEAD`, "--oneline"]);
10725
+ const status = await runGit2(options.cwd, ["status", "--porcelain"]);
10726
+ const aheadLog = await runGit2(options.cwd, ["log", `origin/${base}..HEAD`, "--oneline"]);
10570
10727
  const hasUncommitted = Boolean(status.stdout.trim());
10571
10728
  const hasCommits = Boolean(aheadLog.stdout.trim());
10572
10729
  if (!hasUncommitted && !hasCommits) {
10573
10730
  throw new Error("Agent completed without file changes; no PR was created.");
10574
10731
  }
10575
10732
  if (hasUncommitted) {
10576
- await runGit(options.cwd, ["add", "."]);
10577
- await runGit(options.cwd, [
10733
+ await runGit2(options.cwd, ["add", "."]);
10734
+ await runGit2(options.cwd, [
10578
10735
  "commit",
10579
10736
  "-m",
10580
10737
  `fix: resolve issue #${issue.number}`,
@@ -10584,7 +10741,7 @@ async function solveIssueCommand(issueNumber, options) {
10584
10741
  Closes #${issue.number}`
10585
10742
  ]);
10586
10743
  }
10587
- await runGit(options.cwd, ["push", "-u", "origin", branch]);
10744
+ await runGit2(options.cwd, ["push", "-u", "origin", branch]);
10588
10745
  const pr = await client.createPullRequest({
10589
10746
  ...repo,
10590
10747
  title: `Fix: ${issue.title}`,
@@ -10623,17 +10780,23 @@ async function reviewPrCommand(prNumber, options) {
10623
10780
  ]);
10624
10781
  const focusLine = options.focus && options.focus.length > 0 ? `
10625
10782
  Focus areas: ${options.focus.join(", ")}.` : "";
10783
+ const truncation = truncateDiff(diff.trim());
10784
+ const truncationNote = truncation.omittedFiles > 0 ? `
10785
+ (Showing ${truncation.totalFiles - truncation.omittedFiles} of ${truncation.totalFiles} changed files; ${truncation.omittedFiles} file(s) omitted due to size.)
10786
+ ` : "";
10626
10787
  const prompt = [
10627
10788
  `Review PR #${pr.number}: ${pr.title}`,
10628
10789
  `Branch: ${pr.head ?? "?"} \u2192 ${pr.base ?? "?"}`,
10790
+ "Do not modify any files. Output the review only.",
10629
10791
  "",
10630
10792
  pr.body ? `Description:
10631
10793
  ${pr.body}` : "No description provided.",
10632
10794
  "",
10633
10795
  `Diff:
10634
10796
  \`\`\`diff
10635
- ${diff}
10797
+ ${truncation.diff}
10636
10798
  \`\`\``,
10799
+ truncationNote,
10637
10800
  "",
10638
10801
  `Produce a structured code review with:${focusLine}`,
10639
10802
  "1. **Summary** \u2014 what the PR does",
@@ -10641,21 +10804,30 @@ ${diff}
10641
10804
  "3. **Suggestions** \u2014 improvements and nitpicks",
10642
10805
  "4. **Verdict** \u2014 Approve / Request Changes / Neutral with a one-line rationale"
10643
10806
  ].join("\n");
10644
- const target = resolveUsableProviderTarget(runtime.config, [runtime.config.defaultProvider]);
10807
+ const target = resolveSessionTarget(runtime.config, {
10808
+ provider: options.provider,
10809
+ model: options.model
10810
+ });
10645
10811
  const session = runtime.sessions.create({
10646
10812
  provider: target.provider,
10647
10813
  model: target.model
10648
10814
  });
10649
10815
  const secretValues = collectSecretValues(runtime.config);
10650
10816
  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
- });
10817
+ try {
10818
+ await runtime.agent.run({
10819
+ session,
10820
+ input: prompt,
10821
+ mode: "plan",
10822
+ onChunk: (text) => void writeStdout(redactText(text, secretValues))
10823
+ });
10824
+ } finally {
10825
+ await runtime.sessions.persist(session.id).catch(() => {
10826
+ });
10827
+ }
10656
10828
  await writeStdout("\n");
10657
10829
  }
10658
- async function runGit(cwd, args) {
10830
+ async function runGit2(cwd, args) {
10659
10831
  const result = await execFileAsync("git", args, { cwd, timeoutMs: 18e4 });
10660
10832
  if (result.exitCode !== 0) {
10661
10833
  throw new Error(result.stderr || result.stdout || `git ${args.join(" ")} failed`);
@@ -10665,28 +10837,6 @@ async function runGit(cwd, args) {
10665
10837
  function slugify(input) {
10666
10838
  return input.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 48);
10667
10839
  }
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
10840
  async function runCommand(input, options) {
10691
10841
  if (options.mode && options.mode !== "plan" && options.mode !== "build") {
10692
10842
  throw new Error(`Invalid mode: ${options.mode}. Expected plan or build.`);
@@ -10983,130 +11133,6 @@ async function projectsCommand(options) {
10983
11133
  );
10984
11134
  await waitUntilExit();
10985
11135
  }
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
- }
11110
11136
  function sessionLabel(session) {
11111
11137
  const name = typeof session.metadata["name"] === "string" ? session.metadata["name"] : void 0;
11112
11138
  const firstUser = session.messages.find((m) => m.role === "user");
@@ -31477,7 +31503,7 @@ function createProgram() {
31477
31503
  "focus area: security, performance, correctness, style; repeat for multiple",
31478
31504
  collectOption,
31479
31505
  []
31480
- ).action(async (number, options) => {
31506
+ ).option("--provider <provider>", "provider override").option("--model <model>", "model override").action(async (number, options) => {
31481
31507
  const prNumber = Number.parseInt(number, 10);
31482
31508
  if (!Number.isInteger(prNumber) || prNumber <= 0) {
31483
31509
  throw new Error(`Invalid PR number: ${number}`);
@@ -31485,7 +31511,9 @@ function createProgram() {
31485
31511
  await reviewPrCommand(prNumber, {
31486
31512
  cwd: program.opts().cwd,
31487
31513
  config: program.opts().config,
31488
- focus: options.focus
31514
+ focus: options.focus,
31515
+ provider: options.provider,
31516
+ model: options.model
31489
31517
  });
31490
31518
  });
31491
31519
  github.command("solve").description("solve a GitHub issue end-to-end with branch, commit, push, and PR").argument("<number>", "issue number").option("--base <base>", "base branch", "main").option("-y, --yes", "approve commit/push/PR workflow").action(async (number, options) => {