opencode-magi 0.0.0-dev-20260522220322 → 0.0.0-dev-20260522221258

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.
@@ -392,19 +392,43 @@ export async function removeIssueLabels(exec, repository, issue, labels, account
392
392
  return removed;
393
393
  }
394
394
  export async function fetchPullRequestReviews(exec, repository, pr) {
395
- const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviews(first: 100) { nodes { author { login } submittedAt state body commit { oid } comments(first: 100) { nodes { body path line startLine } } } } } } }`;
396
- const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}`);
397
- const data = JSON.parse(raw);
398
- return data.data.repository.pullRequest.reviews.nodes.map((review) => ({
395
+ const query = `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviews(first: 100, after: $cursor) { nodes { author { login } submittedAt state body commit { oid } comments(first: 100) { nodes { body path line startLine } } } pageInfo { hasNextPage endCursor } } } } }`;
396
+ const reviews = [];
397
+ let cursor;
398
+ for (;;) {
399
+ const cursorFlag = cursor ? ` -F cursor=${shellQuote(cursor)}` : "";
400
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}${cursorFlag}`);
401
+ const data = JSON.parse(raw);
402
+ const connection = data.data.repository.pullRequest.reviews;
403
+ reviews.push(...connection.nodes);
404
+ if (!connection.pageInfo?.hasNextPage)
405
+ break;
406
+ cursor = connection.pageInfo.endCursor;
407
+ if (!cursor)
408
+ throw new Error("GitHub reviews page was truncated");
409
+ }
410
+ return reviews.map((review) => ({
399
411
  ...review,
400
412
  comments: review.comments?.nodes ?? [],
401
413
  }));
402
414
  }
403
415
  export async function fetchPullRequestCommits(exec, repository, pr) {
404
- const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { commits(first: 100) { nodes { commit { oid committedDate parents { totalCount } } } } } } }`;
405
- const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}`);
406
- const data = JSON.parse(raw);
407
- return data.data.repository.pullRequest.commits.nodes.map(({ commit }) => ({
416
+ const query = `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { commits(first: 100, after: $cursor) { nodes { commit { oid committedDate parents { totalCount } } } pageInfo { hasNextPage endCursor } } } } }`;
417
+ const commits = [];
418
+ let cursor;
419
+ for (;;) {
420
+ const cursorFlag = cursor ? ` -F cursor=${shellQuote(cursor)}` : "";
421
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}${cursorFlag}`);
422
+ const data = JSON.parse(raw);
423
+ const connection = data.data.repository.pullRequest.commits;
424
+ commits.push(...connection.nodes);
425
+ if (!connection.pageInfo?.hasNextPage)
426
+ break;
427
+ cursor = connection.pageInfo.endCursor;
428
+ if (!cursor)
429
+ throw new Error("GitHub commits page was truncated");
430
+ }
431
+ return commits.map(({ commit }) => ({
408
432
  committedDate: commit.committedDate,
409
433
  oid: commit.oid,
410
434
  parentCount: commit.parents.totalCount,
@@ -664,11 +688,49 @@ export async function configureGitIdentity(exec, worktreePath, identity) {
664
688
  }
665
689
  }
666
690
  export async function fetchUnresolvedThreads(exec, repository, pr, author) {
667
- const query = `query($owner: String!, $repo: String!, $pr: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviewThreads(first: 100) { nodes { id isResolved comments(first: 50) { nodes { databaseId author { login } path line body createdAt } } } } } } }`;
668
- const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(query)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}`);
669
- const data = JSON.parse(raw);
670
- const threads = data.data.repository.pullRequest.reviewThreads
671
- .nodes;
691
+ const threadQuery = `query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { pullRequest(number: $pr) { reviewThreads(first: 100, after: $cursor) { nodes { id isResolved comments(first: 100) { nodes { databaseId author { login } path line body createdAt } pageInfo { hasNextPage endCursor } } } pageInfo { hasNextPage endCursor } } } } }`;
692
+ const commentQuery = `query($threadId: ID!, $cursor: String) { node(id: $threadId) { ... on PullRequestReviewThread { comments(first: 100, after: $cursor) { nodes { databaseId author { login } path line body createdAt } pageInfo { hasNextPage endCursor } } } } }`;
693
+ const threads = [];
694
+ let cursor;
695
+ async function fetchRemainingComments(threadId, initialCursor) {
696
+ const comments = [];
697
+ let commentsCursor = initialCursor;
698
+ for (;;) {
699
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(commentQuery)} -F threadId=${shellQuote(threadId)} -F cursor=${shellQuote(commentsCursor)}`);
700
+ const data = JSON.parse(raw);
701
+ const connection = data.data.node?.comments;
702
+ if (!connection)
703
+ throw new Error("GitHub review thread comments were missing");
704
+ comments.push(...connection.nodes);
705
+ if (!connection.pageInfo?.hasNextPage)
706
+ break;
707
+ const nextCursor = connection.pageInfo.endCursor;
708
+ if (!nextCursor)
709
+ throw new Error("GitHub review thread comments page was truncated");
710
+ commentsCursor = nextCursor;
711
+ }
712
+ return comments;
713
+ }
714
+ for (;;) {
715
+ const cursorFlag = cursor ? ` -F cursor=${shellQuote(cursor)}` : "";
716
+ const raw = await exec(`gh api${ghHostOption(repository)} graphql -f query=${shellQuote(threadQuery)} -F owner=${shellQuote(repository.github.owner)} -F repo=${shellQuote(repository.github.repo)} -F pr=${pr}${cursorFlag}`);
717
+ const data = JSON.parse(raw);
718
+ const connection = data.data.repository.pullRequest.reviewThreads;
719
+ for (const thread of connection.nodes) {
720
+ const commentsCursor = thread.comments.pageInfo?.endCursor;
721
+ if (thread.comments.pageInfo?.hasNextPage) {
722
+ if (!commentsCursor)
723
+ throw new Error("GitHub review thread comments page was truncated");
724
+ thread.comments.nodes.push(...(await fetchRemainingComments(thread.id, commentsCursor)));
725
+ }
726
+ }
727
+ threads.push(...connection.nodes);
728
+ if (!connection.pageInfo?.hasNextPage)
729
+ break;
730
+ cursor = connection.pageInfo.endCursor;
731
+ if (!cursor)
732
+ throw new Error("GitHub review threads page was truncated");
733
+ }
672
734
  return threads.flatMap((thread) => {
673
735
  if (thread.isResolved || !thread.comments.nodes.length)
674
736
  return [];
@@ -367,18 +367,20 @@ async function classifyChecks(input) {
367
367
  const rawPath = input.outputDir
368
368
  ? join(input.outputDir, `${reviewer.key}.ci-classification.raw.txt`)
369
369
  : undefined;
370
- const check = result.value.checks[0];
370
+ const checks = result.value.checks.map((check) => ({
371
+ classification: check.classification,
372
+ name: check.name,
373
+ reason: check.reason,
374
+ }));
371
375
  if (rawPath)
372
376
  await writeFile(rawPath, result.raw);
373
- run.classification = check?.classification;
377
+ run.checks = checks;
374
378
  run.rawPath = rawPath;
375
- run.reason = check?.reason;
376
379
  run.sessionId = result.sessionId;
377
380
  run.status = "completed";
378
381
  await input.onClassifierProgress?.({
379
- classification: check?.classification ?? "SCOPE_IN",
382
+ checks,
380
383
  rawPath,
381
- reason: check?.reason ?? "No classification reason was provided.",
382
384
  reviewer: reviewer.key,
383
385
  sessionId: result.sessionId,
384
386
  type: "classifier_completed",
@@ -151,6 +151,12 @@ function ciReportText(input) {
151
151
  const scopeInside = input.report.scopeInside.length;
152
152
  return `CI report for ${input.pr}: ${failed} failed, ${scopeInside} scope-in, ${rerun} rerun, ${recovered} recovered, ${unresolved} unresolved.`;
153
153
  }
154
+ function ciClassifierCompletedText(input) {
155
+ const summary = input.checks
156
+ .map((check) => `${check.name}: ${check.classification} - ${check.reason}`)
157
+ .join("; ");
158
+ return `**CI classifier ${input.reviewer}** completed for ${input.pr}: ${summary}`;
159
+ }
154
160
  function closeReconsiderationText(input) {
155
161
  if (input.to === "MERGE") {
156
162
  return `**Reviewer ${input.reviewer}** changed their close request to approval for ${input.pr}.`;
@@ -1704,9 +1710,8 @@ export class MagiRunManager {
1704
1710
  if (progress.type === "ci_classifier_completed") {
1705
1711
  const classifier = state.ciClassifiers?.[progress.reviewer];
1706
1712
  if (classifier) {
1707
- classifier.classification = progress.classification;
1713
+ classifier.checks = progress.checks;
1708
1714
  classifier.rawPath = progress.rawPath;
1709
- classifier.reason = progress.reason;
1710
1715
  classifier.sessionId = progress.sessionId;
1711
1716
  classifier.status = "completed";
1712
1717
  classifier.lastUpdate = now();
@@ -1814,7 +1819,11 @@ export class MagiRunManager {
1814
1819
  await this.notify(state, `**CI classifier ${progress.reviewer}** started JSON regeneration for ${prMarkdownLink(state)}.`);
1815
1820
  }
1816
1821
  if (progress.type === "ci_classifier_completed") {
1817
- await this.notify(state, `**CI classifier ${progress.reviewer}** completed for ${prMarkdownLink(state)}: ${progress.classification} - ${progress.reason}`);
1822
+ await this.notify(state, ciClassifierCompletedText({
1823
+ checks: progress.checks,
1824
+ pr: prMarkdownLink(state),
1825
+ reviewer: progress.reviewer,
1826
+ }));
1818
1827
  }
1819
1828
  if (progress.type === "ci_classifier_failed") {
1820
1829
  await this.notify(state, `**CI classifier ${progress.reviewer}** failed for ${prMarkdownLink(state)}: ${redactSecrets(progress.error)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-magi",
3
- "version": "0.0.0-dev-20260522220322",
3
+ "version": "0.0.0-dev-20260522221258",
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>",