opencode-magi 0.0.0-dev-20260522144259 → 0.0.0-dev-20260522144424

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.
@@ -20,6 +20,68 @@ function errorText(error) {
20
20
  .filter((item) => typeof item === "string")
21
21
  .join("\n");
22
22
  }
23
+ async function localCommitExists(exec, worktreePath, sha) {
24
+ try {
25
+ await exec(`git cat-file -e ${shellQuote(`${sha}^{commit}`)}`, {
26
+ cwd: worktreePath,
27
+ });
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ function pullRequestCommitSource(input) {
35
+ if (input.source === "base") {
36
+ return {
37
+ owner: input.repository.github.owner,
38
+ refName: input.meta.baseRefName,
39
+ repo: input.repository.github.repo,
40
+ };
41
+ }
42
+ return {
43
+ owner: input.meta.headRepositoryOwner?.login ?? input.repository.github.owner,
44
+ refName: input.meta.headRefName,
45
+ repo: input.meta.headRepository?.name ?? input.repository.github.repo,
46
+ };
47
+ }
48
+ async function fetchPullRequestCommitSource(input) {
49
+ const commitSource = pullRequestCommitSource(input);
50
+ try {
51
+ await input.exec(`git fetch --no-tags ${shellQuote(repositoryGitUrl(input.repository, commitSource.owner, commitSource.repo))} ${shellQuote(`refs/heads/${commitSource.refName}`)}`, { cwd: input.worktreePath });
52
+ }
53
+ catch (error) {
54
+ throw new Error(`Could not fetch ${input.source} ref ${commitSource.refName} for #${input.meta.number}: ${errorText(error)}`);
55
+ }
56
+ }
57
+ export async function ensurePullRequestCommits(input) {
58
+ const missing = [];
59
+ for (const commit of input.commits) {
60
+ if (!(await localCommitExists(input.exec, input.worktreePath, commit.sha))) {
61
+ missing.push(commit);
62
+ }
63
+ }
64
+ for (const source of new Set(missing.map((commit) => commit.source))) {
65
+ await fetchPullRequestCommitSource({
66
+ exec: input.exec,
67
+ meta: input.meta,
68
+ repository: input.repository,
69
+ source,
70
+ worktreePath: input.worktreePath,
71
+ });
72
+ }
73
+ for (const commit of missing) {
74
+ if (await localCommitExists(input.exec, input.worktreePath, commit.sha)) {
75
+ continue;
76
+ }
77
+ const source = pullRequestCommitSource({
78
+ meta: input.meta,
79
+ repository: input.repository,
80
+ source: commit.source,
81
+ });
82
+ throw new Error(`${commit.label} commit ${commit.sha} is unavailable after fetching ${commit.source} ref ${source.refName}`);
83
+ }
84
+ }
23
85
  function isCheckoutConfigLockError(error) {
24
86
  const text = errorText(error);
25
87
  return (/could not lock config file/i.test(text) ||
@@ -1,17 +1,17 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { prRunOutputDir } from "../config/output";
4
- import { closePullRequest, configureGitIdentity, fetchMergeQueueRequirement, fetchPullRequest, fetchUnresolvedThreads, mergePullRequest, postApproval, postChangesRequested, postCloseComment, postReply, pushHead, removeWorktree, resolveThread, shellQuote, waitForMergeQueue, } from "../github/commands";
4
+ import { closePullRequest, configureGitIdentity, fetchMergeQueueRequirement, fetchPullRequest, fetchUnresolvedThreads, mergePullRequest, postApproval, postChangesRequested, postCloseComment, postReply, pushHead, removeWorktree, resolveThread, waitForMergeQueue, } from "../github/commands";
5
5
  import { composeEditPrompt, composeRereviewCloseReconsiderationPrompt, composeRereviewPrompt, } from "../prompts/compose";
6
6
  import { parseEditOutput, parseRereviewCloseReconsiderationOutput, parseRereviewOutput, } from "../prompts/output";
7
7
  import { throwIfAborted, withAbortSignal } from "./abort";
8
8
  import { waitForChecksWithClassification } from "./ci";
9
- import { parseRightSideDiffTargets, validateInlineCommentTargets, } from "./inline-comments";
9
+ import { validateInlineCommentTargets, } from "./inline-comments";
10
10
  import { closeMinorityReviewers, mergeVerdictForPolicy } from "./majority";
11
11
  import { runModelWithRepair } from "./model";
12
12
  import { mapPool } from "./pool";
13
13
  import { formatMergeReport } from "./report";
14
- import { runReview } from "./review";
14
+ import { inlineCommentTargetsForDiff, runReview, } from "./review";
15
15
  import { checkSafetyGate, hasSafetyGate } from "./safety";
16
16
  function outputDir(input) {
17
17
  return prRunOutputDir({
@@ -196,7 +196,21 @@ async function runRereview(input, worktreePath, previousHeadSha, cycle, sessionI
196
196
  throwIfAborted(input.signal);
197
197
  const meta = await fetchPullRequest(input.exec, input.repository, input.pr);
198
198
  const headSha = options.dryRunHeadSha ?? meta.headRefOid;
199
- const inlineCommentTargets = parseRightSideDiffTargets(await input.exec(`git diff --no-ext-diff --unified=3 ${shellQuote(meta.baseRefOid)} ${shellQuote(headSha)}`, { cwd: worktreePath }));
199
+ const inlineCommentTargets = await inlineCommentTargetsForDiff({
200
+ ensure: options.dryRunHeadSha
201
+ ? undefined
202
+ : {
203
+ fromSource: "base",
204
+ meta,
205
+ repository: input.repository,
206
+ toSource: "head",
207
+ },
208
+ exec: input.exec,
209
+ fromSha: meta.baseRefOid,
210
+ range: "direct",
211
+ toSha: headSha,
212
+ worktreePath,
213
+ });
200
214
  const artifactDir = outputDir(input);
201
215
  let entries = await mapPool(input.repository.agents.reviewers, input.repository.concurrency.reviewers, async (reviewer) => {
202
216
  throwIfAborted(input.signal);
@@ -1,6 +1,6 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { createWorktree, fetchPullRequest, fetchPullRequestCommits, fetchPullRequestReviews, fetchUnresolvedThreads, closePullRequest, mergePullRequest, postApproval, postChangesRequested, postCloseComment, postReply, removeWorktree, resolveThread, shellQuote, } from "../github/commands";
3
+ import { createWorktree, fetchPullRequest, fetchPullRequestCommits, fetchPullRequestReviews, fetchUnresolvedThreads, closePullRequest, mergePullRequest, ensurePullRequestCommits, postApproval, postChangesRequested, postCloseComment, postReply, removeWorktree, resolveThread, shellQuote, } from "../github/commands";
4
4
  import { composeFindingValidationPrompt, composeCloseReconsiderationPrompt, composeRereviewPrompt, composeReviewPrompt, } from "../prompts/compose";
5
5
  import { prRunOutputDir } from "../config/output";
6
6
  import { prRunWorktreeDir } from "../config/worktree";
@@ -136,7 +136,32 @@ function parseRereviewOutputWithInlineTargets(text, targets) {
136
136
  return output;
137
137
  }
138
138
  export async function inlineCommentTargetsForDiff(input) {
139
- return parseRightSideDiffTargets(await input.exec(`git diff --no-ext-diff --unified=3 ${shellQuote(input.fromSha)}...${shellQuote(input.toSha)}`, { cwd: input.worktreePath }));
139
+ if (input.ensure) {
140
+ await ensurePullRequestCommits({
141
+ commits: [
142
+ {
143
+ label: "base",
144
+ sha: input.fromSha,
145
+ source: input.ensure.fromSource,
146
+ },
147
+ {
148
+ label: "head",
149
+ sha: input.toSha,
150
+ source: input.ensure.toSource,
151
+ },
152
+ ],
153
+ exec: input.exec,
154
+ meta: input.ensure.meta,
155
+ repository: input.ensure.repository,
156
+ worktreePath: input.worktreePath,
157
+ });
158
+ }
159
+ const diffRange = input.range === "direct"
160
+ ? `${shellQuote(input.fromSha)} ${shellQuote(input.toSha)}`
161
+ : `${shellQuote(input.fromSha)}...${shellQuote(input.toSha)}`;
162
+ return parseRightSideDiffTargets(await input.exec(`git diff --no-ext-diff --unified=3 ${diffRange}`, {
163
+ cwd: input.worktreePath,
164
+ }));
140
165
  }
141
166
  function parsePostedFindingLocation(location) {
142
167
  const range = /^(.*):(\d+)-(\d+)$/.exec(location);
@@ -645,6 +670,12 @@ export async function runReview(input) {
645
670
  return [{ assignment, reviewer }];
646
671
  });
647
672
  const initialInlineCommentTargets = await inlineCommentTargetsForDiff({
673
+ ensure: {
674
+ fromSource: "base",
675
+ meta,
676
+ repository: input.repository,
677
+ toSource: "head",
678
+ },
648
679
  exec,
649
680
  fromSha: meta.baseRefOid,
650
681
  toSha: meta.headRefOid,
@@ -670,6 +701,12 @@ export async function runReview(input) {
670
701
  if (!previous.commit?.oid)
671
702
  throw new Error(`Missing previous review commit for ${reviewer.account}`);
672
703
  const inlineCommentTargets = await inlineCommentTargetsForDiff({
704
+ ensure: {
705
+ fromSource: "head",
706
+ meta,
707
+ repository: input.repository,
708
+ toSource: "head",
709
+ },
673
710
  exec,
674
711
  fromSha: previous.commit.oid,
675
712
  toSha: meta.headRefOid,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260522144259",
3
+ "version": "0.0.0-dev-20260522144424",
4
4
  "description": "Multi-agent PR review and merge orchestration plugin for OpenCode.",
5
5
  "license": "MIT",
6
6
  "author": "Hirotomo Yamada <hirotomo.yamada@avap.co.jp>",