@weareikko/code-review 0.8.2 → 0.8.4

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/README.md CHANGED
@@ -5,15 +5,19 @@
5
5
  [![Size](https://img.shields.io/bundlephobia/minzip/@weareikko/code-review?style=flat&colorB=3e63dd&colorA=414853&label=size)](https://bundlephobia.com/package/@weareikko/code-review)
6
6
  ![Codecov](https://img.shields.io/codecov/c/github/weareikko/code-review?style=flat&colorB=3e63dd&colorA=414853)
7
7
 
8
- Run an agent-driven code review in GitLab CI, parse inline comments, post deduplicated merge request discussions, and report per-run token usage and cost.
8
+ Run an agent-driven code review on **GitLab merge requests and GitHub pull requests** — the same review engine on both. It parses inline comments, posts deduplicated review discussions, upserts a summary, and reports per-run token usage and cost. The platform is auto-detected from the environment (GitLab CI vs. GitHub Actions) and can be forced with `--platform github|gitlab`.
9
9
 
10
- The reviewer reads the MR **title and description** as the author's declared intent: it checks the diff against the stated purpose and flags code/intent mismatches (the change does something the description never claimed, or omits something it promised) as a first-class finding. A missing or empty description degrades gracefully — the review still runs.
10
+ The reviewer reads the merge/pull request **title and description** as the author's declared intent: it reads the diff against the stated purpose and surfaces code/intent mismatches (the change does something the description never claimed, or omits something it promised) as a summary note. A missing or empty description degrades gracefully — the review still runs.
11
11
 
12
12
  ## Requirements
13
13
 
14
14
  - Node.js `>=24`
15
- - `git` available in the runtime
16
- - A pipeline running in a merge request context (`CI_PROJECT_ID`, `CI_MERGE_REQUEST_IID`)
15
+ - `git` available in the runtime (full history — the review diffs against the merge base)
16
+ - A run in one of the two supported contexts, which the tool auto-detects:
17
+ - **GitLab CI** in a merge-request pipeline (`GITLAB_CI`, `CI_PROJECT_ID`, `CI_MERGE_REQUEST_IID`), or
18
+ - **GitHub Actions** on a `pull_request` event (`GITHUB_ACTIONS`, `GITHUB_REPOSITORY`, the PR number)
19
+
20
+ Detection precedence: an explicit `--platform github|gitlab` (or `CODE_REVIEW_PLATFORM`) always wins; otherwise `GITHUB_ACTIONS=true` selects GitHub and `GITLAB_CI=true` (or a present `CI_PROJECT_ID` / `CI_SERVER_URL`) selects GitLab; failing that, the tool infers the platform from whichever side's identifiers are present.
17
21
 
18
22
  ## Install / Run
19
23
 
@@ -79,13 +83,17 @@ review:
79
83
  - review-usage.json
80
84
  ```
81
85
 
82
- ## GitHub Actions
86
+ ## GitHub Actions example
87
+
88
+ On GitHub the same engine reviews **pull requests** — auto-detected from the Actions environment (`GITHUB_ACTIONS`, `GITHUB_REPOSITORY`, the `pull_request` event); pass `--platform github` to force it. Findings post as one batched PR review with inline comments plus an upserted summary comment.
83
89
 
84
- The same reviewer also reviews **GitHub pull requests** — the engine is platform-agnostic and auto-detects GitHub from the Actions environment (`GITHUB_ACTIONS`, `GITHUB_REPOSITORY`, the `pull_request` event). Set `--platform github` to force it.
90
+ ### Composite action (recommended)
85
91
 
86
- ### Reusable workflow (simplest)
92
+ The bundled composite action is the primary path: it checks out the repository, sets up Node, installs the CLI, and runs the review in one step. It needs:
87
93
 
88
- The quickest way to enable reviews in a repo is the bundled reusable workflow. Add a tiny caller and let it check out the code, install the CLI, and run the review for you:
94
+ - **Permissions:** `pull-requests: write` (to post the review and summary) and `contents: read` (to check out the code). The default `GITHUB_TOKEN` is enough; the action reads it as `${{ github.token }}` by default.
95
+ - **Full git history:** the action checks out the repository by default with `fetch-depth: 0` so the merge-base diff and commit log resolve (tune with `fetch-depth`, or set `checkout: false` and check out yourself first).
96
+ - **A model + its key:** pass the model via the `model` input and the provider's key via the `api-key` input (or expose the provider's standard env var, e.g. `ANTHROPIC_API_KEY`, to the step).
89
97
 
90
98
  ```yml
91
99
  name: code-review
@@ -98,19 +106,32 @@ permissions:
98
106
 
99
107
  jobs:
100
108
  review:
101
- uses: weareikko/code-review/.github/workflows/code-review.yml@0.8.2 # pin to a release tag
102
- secrets: inherit
109
+ runs-on: ubuntu-latest
110
+ steps:
111
+ # Checkout (full history) is bundled — no separate checkout step needed.
112
+ # Opt out with `checkout: false` if your job already checked out the code.
113
+ - uses: weareikko/code-review@0.8 # moving minor tag — auto patch updates (see Pinning below)
114
+ with:
115
+ model: anthropic/claude-sonnet-4-5
116
+ api-key: ${{ secrets.ANTHROPIC_API_KEY }}
117
+ # github-token defaults to ${{ github.token }}
118
+ # args: --min-severity warn --dry-run
103
119
  ```
104
120
 
105
- It reads review settings from repo/org **variables** (`CODE_REVIEW_MODEL`, `CODE_REVIEW_DEPTH`, `CODE_REVIEW_THINKING_LEVEL`, `CODE_REVIEW_VERIFY_MODEL`) and provider credentials from **secrets** named with the `CODE_REVIEW_` prefix (e.g. `CODE_REVIEW_ANTHROPIC_API_KEY`), which the CLI's env shim de-prefixes for the provider. `secrets: inherit` is required so the reusable workflow can see them. Optional `with:` inputs: `model` (overrides the `CODE_REVIEW_MODEL` variable), `version`, `node-version`, `working-directory`, `args`, and `runs-on`.
121
+ Inputs: `model` (required), `api-key`, `github-token` (default `${{ github.token }}`), `version` (npm dist-tag/version, default `latest`), `node-version` (default `24`), `working-directory`, `args` (extra CLI flags forwarded verbatim), `checkout` (default `true` — bundled repository checkout), and `fetch-depth` (default `0` — full history, required for the merge-base diff).
106
122
 
107
- ### Composite action
123
+ Because the composite action references your secrets directly (`${{ secrets.ANTHROPIC_API_KEY }}`), it works from **any** repository, including consumers in a different organization from this one.
108
124
 
109
- For more control, use the bundled composite action directly. It needs:
125
+ > **Pinning the ref.** Two moving tags are maintained, each re-pointed to the latest release it covers:
126
+ >
127
+ > - `@0.8` — **minor series**: newest `0.8.x`, patches only. Because a `0.x` minor bump marks a breaking change, this is the non-breaking channel and the recommended pin while the project is pre-1.0.
128
+ > - `@0` — **major series**: newest stable release. **Caveat:** in 0.x a minor bump _is_ a breaking change, so `@0` may advance across breaking releases (`0.8 → 0.9`); it becomes a true semver compatibility boundary only once `1.0` ships.
129
+ >
130
+ > For a frozen build, pin an exact patch (`@0.8.3`) or a commit SHA (immutable; strongest supply-chain posture); to track the tip, use `@main`. GitHub `uses:` refs do not support wildcards, so `@0.8.x` is not valid — use a moving tag instead.
110
131
 
111
- - **Permissions:** `pull-requests: write` (to post the review and summary) and `contents: read` (to check out the code). The default `GITHUB_TOKEN` is enough; the action reads it as `${{ github.token }}` by default.
112
- - **Full git history:** the bundled checkout uses `fetch-depth: 0` so the merge-base diff and commit log resolve (tune with `fetch-depth`, or set `checkout: false` and check out yourself).
113
- - **A model + its key:** pass the model via the `model` input and the provider's key via the `api-key` input (or expose the provider's standard env var, e.g. `ANTHROPIC_API_KEY`, to the step).
132
+ ### Reusable workflow (same org/enterprise)
133
+
134
+ The bundled reusable workflow lets a caller enable reviews with no `steps:` of its own — it checks out the code, installs the CLI, and runs the review for you:
114
135
 
115
136
  ```yml
116
137
  name: code-review
@@ -123,24 +144,23 @@ permissions:
123
144
 
124
145
  jobs:
125
146
  review:
126
- runs-on: ubuntu-latest
127
- steps:
128
- # Checkout (full history) is bundled — opt out with `checkout: false` if
129
- # your job already checked out the code with its own options.
130
- - uses: weareikko/code-review@main # pin to a release tag in production
131
- with:
132
- model: anthropic/claude-sonnet-4-5
133
- api-key: ${{ secrets.ANTHROPIC_API_KEY }}
134
- # github-token defaults to ${{ github.token }}
135
- # args: --min-severity warn --dry-run
147
+ uses: weareikko/code-review/.github/workflows/code-review.yml@0.8 # moving minor tag — auto patch updates
148
+ secrets: inherit
136
149
  ```
137
150
 
138
- Inputs: `model` (required), `api-key`, `github-token` (default `${{ github.token }}`), `version` (npm dist-tag/version, default `latest`), `node-version` (default `24`), `working-directory`, `args` (extra CLI flags forwarded verbatim), `checkout` (default `true` — bundled repository checkout), and `fetch-depth` (default `0` — full history, required for the merge-base diff).
151
+ It reads review settings from repo/org **variables** (`CODE_REVIEW_MODEL`, `CODE_REVIEW_DEPTH`, `CODE_REVIEW_THINKING_LEVEL`, `CODE_REVIEW_VERIFY_MODEL`) and provider credentials from **secrets** named with the `CODE_REVIEW_` prefix (e.g. `CODE_REVIEW_ANTHROPIC_API_KEY`), which the CLI's env shim de-prefixes for the provider. Optional `with:` inputs: `model` (overrides the `CODE_REVIEW_MODEL` variable), `version`, `node-version`, `working-directory`, `args`, and `runs-on`.
139
152
 
140
- Prefer to run the CLI directly (no composite action)? `GITHUB_TOKEN`, `GITHUB_REPOSITORY`, and the PR number are read straight from the Actions environment:
153
+ > **Caveat — `secrets: inherit` is same-organization (or enterprise) only.** Organization secrets are **not** inherited across organizations, so this pattern only works when the caller repository lives in the same org (or enterprise) as `weareikko/code-review`. Cross-organization consumers must use the **composite action** above, which references their own secrets directly. (The reusable workflow relies on `secrets: inherit` and declares no `workflow_call.secrets`, so there is no explicit-secrets path for it.)
154
+
155
+ ### Running the CLI directly
156
+
157
+ Prefer no action at all? `GITHUB_TOKEN`, `GITHUB_REPOSITORY`, and the PR number are read straight from the Actions environment. Check out the code first (full history) so the merge-base diff resolves:
141
158
 
142
159
  ```yml
143
- - uses: actions/setup-node@v4
160
+ - uses: actions/checkout@v5
161
+ with:
162
+ fetch-depth: 0
163
+ - uses: actions/setup-node@v5
144
164
  with:
145
165
  node-version: 24
146
166
  - run: npx @weareikko/code-review
@@ -159,17 +179,17 @@ The README covers getting started. Reference material lives in [`docs/`](https:/
159
179
  - [Skills](https://github.com/weareikko/code-review/blob/main/docs/skills.md) — built-in, external (`npm:`/`file:`/`git:`), and project auto-discovered review skills.
160
180
  - [Multi-stage review](https://github.com/weareikko/code-review/blob/main/docs/multi-stage-review.md) — the staged Find / Verify / Synthesize pipeline behind `--review-depth`.
161
181
  - [Observability](https://github.com/weareikko/code-review/blob/main/docs/observability.md) — diagnostics-channel tracing and the opt-in OpenTelemetry bridge (spans, metrics, logs).
162
- - [Output format](https://github.com/weareikko/code-review/blob/main/docs/output-format.md) — inline-comment shape, MR-level summary note, footer, and duplicate prevention.
182
+ - [Output format](https://github.com/weareikko/code-review/blob/main/docs/output-format.md) — inline-comment shape, the upserted summary (a note on GitLab, an issue comment on GitHub), footer, and duplicate prevention.
163
183
 
164
184
  ## Configuration
165
185
 
166
- The CLI auto-resolves most values from GitLab CI variables and provider-standard env vars. The two things you must provide are a model and its provider's API key:
186
+ The CLI auto-resolves most values from the CI environment (GitLab CI variables or the GitHub Actions context) and provider-standard env vars. The two things you must provide are a model and its provider's API key:
167
187
 
168
188
  ```bash
169
189
  code-review --model anthropic/claude-sonnet-4-5 --api-key "$ANTHROPIC_API_KEY"
170
190
  ```
171
191
 
172
- Equivalently, set `CODE_REVIEW_MODEL` and the provider's key (e.g. `ANTHROPIC_API_KEY`) as CI/CD variables. Common knobs include `--min-severity`, `--thinking`, `--posting-mode draft`, `--no-summary`, and `--dry-run`. See the full [environment-variable and flag reference](https://github.com/weareikko/code-review/blob/main/docs/configuration.md).
192
+ Equivalently, set `CODE_REVIEW_MODEL` and the provider's key (e.g. `ANTHROPIC_API_KEY`) as CI/CD variables (GitLab) or repository/organization variables and secrets (GitHub). Common knobs include `--min-severity`, `--thinking`, `--posting-mode draft`, `--no-summary`, and `--dry-run`. See the full [environment-variable and flag reference](https://github.com/weareikko/code-review/blob/main/docs/configuration.md).
173
193
 
174
194
  ## Providers
175
195
 
@@ -182,7 +202,7 @@ Equivalently, set `CODE_REVIEW_MODEL` and the provider's key (e.g. `ANTHROPIC_AP
182
202
  - parsed comment payload
183
203
  - computed fingerprints
184
204
  - duplicate status
185
- - final GitLab discussion payload
205
+ - final platform-specific posting payload (a GitLab discussion payload, or a GitHub review-comment payload)
186
206
  - `review-usage.json`: token and cost breakdown for the run (`tokens.{input,output,cacheRead,cacheWrite,total}`, `cost.{input,output,cacheRead,cacheWrite,total}`, `model`)
187
207
 
188
208
  The CLI also prints a one-line summary at the end of the run:
@@ -198,15 +218,17 @@ Use these files for CI debugging and auditing.
198
218
  - **`Node.js >=24 is required`**
199
219
  - Use `node:24` (or newer) in CI.
200
220
  - **`Missing required configuration`**
201
- - Provide required flags or ensure CI vars are available (`CI_PROJECT_ID`, `CI_MERGE_REQUEST_IID`, token, API key).
221
+ - Provide required flags or ensure the platform's identifiers/token are available: on GitLab `CI_PROJECT_ID`, `CI_MERGE_REQUEST_IID`, and a GitLab token; on GitHub `GITHUB_REPOSITORY`, the PR number, and `GITHUB_TOKEN`. A model and its API key are required on both.
222
+ - **`Could not detect the review platform` / `Ambiguous review platform`**
223
+ - Set `--platform github|gitlab` (or `CODE_REVIEW_PLATFORM`) to force the platform.
202
224
  - **`--min-severity must be one of: info, warn, critical`**
203
225
  - Fix `--min-severity` or `CODE_REVIEW_MIN_SEVERITY`.
204
226
  - **Git history errors / merge-base failures**
205
- - Set `GIT_DEPTH: 0`.
227
+ - Fetch full history: `GIT_DEPTH: 0` on GitLab, `fetch-depth: 0` on `actions/checkout` (the composite action does this by default).
206
228
  - Ensure source and target branches are fetchable from `origin`.
207
- - **GitLab API 401/403 when posting**
208
- - Ensure token has rights to read MR metadata/discussions and create MR discussions.
209
- - If using `CI_JOB_TOKEN`, ensure your GitLab project settings allow required API access.
229
+ - **API 401/403 when posting**
230
+ - GitLab: ensure the token can read MR metadata/discussions and create MR discussions; with `CI_JOB_TOKEN`, check that project settings allow the required API access.
231
+ - GitHub: ensure the token has `pull-requests: write` (the default `GITHUB_TOKEN` with that permission is enough).
210
232
  - **No comments posted**
211
233
  - Check `review-comments.json` for `duplicate: true` or empty parsed comments.
212
234
  - Run with `--dry-run` and inspect `code-review.md` formatting (`== Inline Comments ==`).
@@ -155,6 +155,20 @@ function parseNextLink(header) {
155
155
  }
156
156
  return null;
157
157
  }
158
+ var REVIEW_THREADS_QUERY = `
159
+ query ($owner: String!, $repo: String!, $pull: Int!, $cursor: String) {
160
+ repository(owner: $owner, name: $repo) {
161
+ pullRequest(number: $pull) {
162
+ reviewThreads(first: 100, after: $cursor) {
163
+ pageInfo { hasNextPage endCursor }
164
+ nodes {
165
+ isResolved
166
+ comments(first: 100) { nodes { databaseId } }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }`;
158
172
  var GitHubClient = class {
159
173
  base;
160
174
  token;
@@ -290,6 +304,65 @@ var GitHubClient = class {
290
304
  getCurrentUser() {
291
305
  return this.request("/user");
292
306
  }
307
+ /**
308
+ * Derive the GraphQL endpoint from the REST base. github.com exposes GraphQL
309
+ * at `<origin>/graphql`, while GitHub Enterprise Server exposes it at
310
+ * `<origin>/api/graphql` (its REST base is `<origin>/api/v3`).
311
+ */
312
+ graphqlEndpoint() {
313
+ if (this.base.endsWith("/api/v3")) return `${this.base.slice(0, -7)}/api/graphql`;
314
+ return `${this.base}/graphql`;
315
+ }
316
+ async graphql(query, variables) {
317
+ const url = this.graphqlEndpoint();
318
+ const response = await this.fetchWithTimeout(url, {
319
+ method: "POST",
320
+ headers: this.headers({ "Content-Type": "application/json" }),
321
+ body: JSON.stringify({
322
+ query,
323
+ variables
324
+ })
325
+ }, "POST", "/graphql");
326
+ if (!response.ok) this.failure("POST", "/graphql", response, await response.text());
327
+ const text = await response.text();
328
+ const parsed = JSON.parse(text);
329
+ if (parsed.errors && parsed.errors.length > 0) throw new GitHubApiError(`GitHub API POST /graphql failed: ${parsed.errors.map((e) => e.message ?? "").join("; ")}`, {
330
+ method: "POST",
331
+ path: "/graphql",
332
+ responseBody: text,
333
+ hint: "Ensure the token can read pull-request review threads (pull-requests: read / repo scope)."
334
+ });
335
+ return parsed.data;
336
+ }
337
+ /**
338
+ * Return the database IDs of review comments that belong to a **resolved**
339
+ * review thread. GitHub's REST comment endpoints omit thread-resolution state;
340
+ * it is only exposed via GraphQL `reviewThreads.isResolved`. Callers use this
341
+ * set to mark normalized notes resolved so resolved threads are excluded from
342
+ * summary carry-over and prior-thread context. Paginates over threads.
343
+ */
344
+ async listResolvedReviewCommentIds(owner, repo, pull) {
345
+ const resolved = /* @__PURE__ */ new Set();
346
+ let cursor = null;
347
+ let hasNext = true;
348
+ while (hasNext) {
349
+ const threads = (await this.graphql(REVIEW_THREADS_QUERY, {
350
+ owner,
351
+ repo,
352
+ pull,
353
+ cursor
354
+ })).repository?.pullRequest?.reviewThreads;
355
+ if (!threads) break;
356
+ for (const thread of threads.nodes ?? []) {
357
+ if (!thread.isResolved) continue;
358
+ for (const comment of thread.comments?.nodes ?? []) if (typeof comment.databaseId === "number") resolved.add(comment.databaseId);
359
+ }
360
+ hasNext = threads.pageInfo?.hasNextPage ?? false;
361
+ cursor = threads.pageInfo?.endCursor ?? null;
362
+ if (!cursor) hasNext = false;
363
+ }
364
+ return resolved;
365
+ }
293
366
  };
294
367
  //#endregion
295
368
  //#region src/fingerprints.ts
@@ -439,7 +512,12 @@ function buildSizeNoticeBlock(notice) {
439
512
  if (sizeSkippedFiles.length > 0) {
440
513
  const fileList = sizeSkippedFiles.map((file) => `- \`${file.path}\` (${formatChars(file.chars)})`).join("\n");
441
514
  const cov = notice.coverage;
442
- const coverageLine = cov && cov.totalLines > 0 ? `> **Partial review — ~${Math.round(cov.reviewedLines / cov.totalLines * 100)}% of changed lines reviewed** (${cov.reviewedLines} of ${cov.totalLines}). The files below were NOT reviewed; their absence from the findings is not a clean bill of health.` : `> **${sizeSkippedFiles.length} file(s) were not reviewed** — the diff exceeded the size budget, so these files were dropped from the review:`;
515
+ const retrieved = notice.retrieved === true;
516
+ let coverageLine;
517
+ if (cov && cov.totalLines > 0) {
518
+ const pct = Math.round(cov.reviewedLines / cov.totalLines * 100);
519
+ coverageLine = retrieved ? `> **Large diff — ~${pct}% of changed lines fit the inline budget** (${cov.reviewedLines} of ${cov.totalLines}). The files below exceeded it and were staged for on-demand retrieval; see the review summary for which were read. Absence from the findings is not a clean bill of health.` : `> **Partial review — ~${pct}% of changed lines reviewed** (${cov.reviewedLines} of ${cov.totalLines}). The files below were NOT reviewed; their absence from the findings is not a clean bill of health.`;
520
+ } else coverageLine = retrieved ? `> **${sizeSkippedFiles.length} file(s) exceeded the size budget** — their diffs were staged for on-demand retrieval; see the review summary for which were read:` : `> **${sizeSkippedFiles.length} file(s) were not reviewed** — the diff exceeded the size budget, so these files were dropped from the review:`;
443
521
  blocks.push([
444
522
  `> [!WARNING]`,
445
523
  coverageLine,
@@ -470,7 +548,7 @@ function buildSummaryBody(summary, costFooter, options = {}) {
470
548
  return `${withFooter}\n\n${buildSummaryHistoryBlock(historyEntries)}`;
471
549
  }
472
550
  function buildReviewedCommitFooter(commitSha) {
473
- return `Reviewed by ${PRODUCT_LINK} v0.8.2 for commit ${commitSha}.`;
551
+ return `Reviewed by ${PRODUCT_LINK} v0.8.4 for commit ${commitSha}.`;
474
552
  }
475
553
  function extractReviewedCommitSha(body) {
476
554
  return REVIEWED_COMMIT_FOOTER_PATTERN.exec(body)?.[1] ?? null;
@@ -863,6 +941,7 @@ var BOOLEAN_FLAGS = new Set([
863
941
  "no-summary",
864
942
  "force-review",
865
943
  "retrieve-skipped",
944
+ "no-retrieve-skipped",
866
945
  "verbose",
867
946
  "help",
868
947
  "version"
@@ -921,6 +1000,22 @@ function resolvePostSummary(args, env) {
921
1000
  }
922
1001
  return true;
923
1002
  }
1003
+ function resolveRetrieveSkipped(args, env) {
1004
+ if (args.noRetrieveSkipped === true) return false;
1005
+ if (args.retrieveSkipped === true) return true;
1006
+ const raw = env.CODE_REVIEW_RETRIEVE_SKIPPED;
1007
+ if (typeof raw === "string") {
1008
+ const normalized = raw.trim().toLowerCase();
1009
+ if ([
1010
+ "0",
1011
+ "false",
1012
+ "no",
1013
+ "off"
1014
+ ].includes(normalized)) return false;
1015
+ if (normalized.length > 0) return true;
1016
+ }
1017
+ return true;
1018
+ }
924
1019
  function normalizeChoice(value) {
925
1020
  return String(value ?? "").trim().toLowerCase();
926
1021
  }
@@ -1124,7 +1219,7 @@ function resolveConfig(argv = process.argv.slice(2), env = process.env) {
1124
1219
  maxDiffChars,
1125
1220
  decomposeHintLines,
1126
1221
  diffContext,
1127
- retrieveSkipped: toBoolean(args.retrieveSkipped) || toBoolean(env.CODE_REVIEW_RETRIEVE_SKIPPED),
1222
+ retrieveSkipped: resolveRetrieveSkipped(args, env),
1128
1223
  reviewFile: String(args.reviewFile ?? "code-review.md"),
1129
1224
  output: String(args.output ?? "review-comments.json"),
1130
1225
  dryRun: toBoolean(args.dryRun),
@@ -3715,10 +3810,10 @@ function buildUserPrompt(diff, skippedFiles = [], commitLog, priorThreads, inten
3715
3810
  if (commitLog?.trim()) parts.push(`Commits in this MR (oldest first):\n<commits>\n${commitLog.trim()}\n</commits>`);
3716
3811
  parts.push(`Review this diff:\n<diff>\n${diff}\n</diff>`);
3717
3812
  if (retrievableSkipped && retrievableSkipped.length > 0) parts.push(renderRetrievableSkippedBlock(retrievableSkipped));
3718
- else if (skippedFiles.length > 0) parts.push(`<skipped_files>\n${skippedFiles.map((file) => `- ${file}`).join("\n")}\n</skipped_files>\nThe above files were not included because the diff exceeded the size limit. Mention them explicitly in your summary as not reviewed.`);
3813
+ else if (skippedFiles.length > 0) parts.push(`<skipped_files>\n${skippedFiles.map((file) => `- ${file}`).join("\n")}\n</skipped_files>\nThe above files were not included because the diff exceeded the size limit. They are already surfaced to the reader in the MR summary, so do not re-list them; just do not assume they are clean, since you did not see them.`);
3719
3814
  if (coverage && coverage.totalLines > 0 && coverage.reviewedLines < coverage.totalLines) {
3720
3815
  const pct = Math.round(coverage.reviewedLines / coverage.totalLines * 100);
3721
- parts.push(`<coverage>You reviewed ${coverage.reviewedLines} of ${coverage.totalLines} changed lines (~${pct}%). The rest were dropped for the size budget and you did NOT see them. State this partial coverage in your summary and do not imply the unreviewed files are clean — their absence from your findings is not a clearance.</coverage>`);
3816
+ parts.push(`<coverage>You reviewed ${coverage.reviewedLines} of ${coverage.totalLines} changed lines (~${pct}%). The rest were dropped for the size budget and you did NOT see them. The MR summary already reports this partial coverage to the reader, so do not restate it; just do not imply the unreviewed files are clean — their absence from your findings is not a clearance.</coverage>`);
3722
3817
  }
3723
3818
  if (priorThreads && priorThreads.length > 0) {
3724
3819
  const block = renderPriorThreadsBlock(priorThreads);
@@ -3950,13 +4045,14 @@ async function runReview(config, options) {
3950
4045
  reviewedLines: reviewedChangedLines,
3951
4046
  totalLines: reviewedChangedLines + skippedChangedLines
3952
4047
  } : void 0;
4048
+ const retrievableSkipped = config.retrieveSkipped && sizeSkippedSections.length > 0 ? await writeSkippedDiffs(cwd, sizeSkippedSections) : [];
4049
+ if (retrievableSkipped.length > 0) logger.info(`Staged ${retrievableSkipped.length} dropped-file diff(s) on disk for retrieval.`);
3953
4050
  const sizeNotice = {
3954
4051
  sizeSkippedFiles,
3955
4052
  decomposeHint,
3956
- coverage
4053
+ coverage,
4054
+ retrieved: retrievableSkipped.length > 0
3957
4055
  };
3958
- const retrievableSkipped = config.retrieveSkipped && sizeSkippedSections.length > 0 ? await writeSkippedDiffs(cwd, sizeSkippedSections) : [];
3959
- if (retrievableSkipped.length > 0) logger.info(`Staged ${retrievableSkipped.length} dropped-file diff(s) on disk for retrieval.`);
3960
4056
  const context = await loadReviewContext(cwd, config.skills, (msg) => logger.warn(msg), { refreshGitSkills: config.refreshGitSkills });
3961
4057
  const systemPrompt = buildJSONSystemPrompt(context, minSeverity);
3962
4058
  const userPrompt = buildUserPrompt(diff, skippedFiles, options.commitLog, options.priorThreads, options.intent, coverage, retrievableSkipped);
@@ -4836,7 +4932,7 @@ async function loadDefaultRuntime() {
4836
4932
  const [sdkNode, resources, semconv] = modules;
4837
4933
  const serviceResource = resources.resourceFromAttributes({
4838
4934
  [semconv.ATTR_SERVICE_NAME ?? "service.name"]: SERVICE_NAME,
4839
- [semconv.ATTR_SERVICE_VERSION ?? "service.version"]: "0.8.2"
4935
+ [semconv.ATTR_SERVICE_VERSION ?? "service.version"]: "0.8.4"
4840
4936
  });
4841
4937
  process.env.OTEL_METRICS_EXPORTER = process.env.OTEL_METRICS_EXPORTER ?? "otlp";
4842
4938
  process.env.OTEL_LOGS_EXPORTER = process.env.OTEL_LOGS_EXPORTER ?? "otlp";
@@ -5268,7 +5364,7 @@ function boldCommentTitle(body) {
5268
5364
  */
5269
5365
  function buildCommentBody(body, commitSha, confidence) {
5270
5366
  const confidenceLine = `_Confidence: ${confidence}._`;
5271
- const footer = `<sub>Reviewed by ${PRODUCT_LINK} v0.8.2 for commit ${commitSha}.</sub>`;
5367
+ const footer = `<sub>Reviewed by ${PRODUCT_LINK} v0.8.4 for commit ${commitSha}.</sub>`;
5272
5368
  return `${boldCommentTitle(body.trim())}\n\n${confidenceLine}\n\n---\n\n${footer}`;
5273
5369
  }
5274
5370
  function buildPayload(comment, body, refs, resolved) {
@@ -5431,8 +5527,12 @@ function reviewCommentPosition(comment) {
5431
5527
  * The fingerprint markers, summary marker, and reviewed-commit footer are HTML
5432
5528
  * comments that render identically on GitHub, so `extractExistingFingerprints`,
5433
5529
  * `findExistingSummaryNote`, and the reviewed-commit scan all work as-is.
5530
+ *
5531
+ * `resolvedCommentIds` carries the database ids of comments in resolved review
5532
+ * threads (from the GraphQL `reviewThreads` query, since REST omits resolution),
5533
+ * so each note gets a `resolved` flag mirroring GitLab's per-note field.
5434
5534
  */
5435
- function normalizeGitHubDiscussions(reviewComments, issueComments) {
5535
+ function normalizeGitHubDiscussions(reviewComments, issueComments, resolvedCommentIds = /* @__PURE__ */ new Set()) {
5436
5536
  const threads = /* @__PURE__ */ new Map();
5437
5537
  const order = [];
5438
5538
  for (const comment of reviewComments) {
@@ -5446,6 +5546,7 @@ function normalizeGitHubDiscussions(reviewComments, issueComments) {
5446
5546
  notes.push({
5447
5547
  id: comment.id,
5448
5548
  body: comment.body ?? "",
5549
+ resolved: resolvedCommentIds.has(comment.id),
5449
5550
  position: reviewCommentPosition(comment)
5450
5551
  });
5451
5552
  }
@@ -5512,8 +5613,12 @@ var GitHubPlatform = class {
5512
5613
  return refs;
5513
5614
  }
5514
5615
  async getDiscussions() {
5515
- const [reviewComments, issueComments] = await Promise.all([this.client.listReviewComments(this.owner, this.repo, this.pull), this.client.listIssueComments(this.owner, this.repo, this.pull)]);
5516
- return normalizeGitHubDiscussions(reviewComments, issueComments);
5616
+ const [reviewComments, issueComments, resolvedCommentIds] = await Promise.all([
5617
+ this.client.listReviewComments(this.owner, this.repo, this.pull),
5618
+ this.client.listIssueComments(this.owner, this.repo, this.pull),
5619
+ this.client.listResolvedReviewCommentIds(this.owner, this.repo, this.pull)
5620
+ ]);
5621
+ return normalizeGitHubDiscussions(reviewComments, issueComments, resolvedCommentIds);
5517
5622
  }
5518
5623
  buildComments(comments, diff, refs, existingFingerprints) {
5519
5624
  this.commitId = refs.head_sha;
@@ -5984,9 +6089,10 @@ Options:
5984
6089
  context aids reasoning but inflates tokens and fits fewer files in
5985
6090
  the budget; less fits more. 0 = built-in default (20).
5986
6091
  (env: CODE_REVIEW_DIFF_CONTEXT)
5987
- --retrieve-skipped Stage diffs for files dropped by the size budget on disk so the
5988
- reviewer can read them on demand instead of losing them.
5989
- (env: CODE_REVIEW_RETRIEVE_SKIPPED=true)
6092
+ --no-retrieve-skipped Disable staging diffs for files dropped by the size budget on disk.
6093
+ Retrieval is on by default: dropped diffs are staged so the reviewer
6094
+ can read them on demand instead of losing them.
6095
+ (env: CODE_REVIEW_RETRIEVE_SKIPPED=0)
5990
6096
  --min-severity <level> info, warn, or critical (default: info)
5991
6097
  --thinking <level> off, minimal, low, medium, high, or xhigh (default: off).
5992
6098
  Higher levels add billable thinking tokens at the model output rate.
@@ -6346,10 +6452,10 @@ async function main(argv = process.argv.slice(2)) {
6346
6452
  return;
6347
6453
  }
6348
6454
  if (argv.includes("--version") || argv.includes("-v")) {
6349
- console.log("0.8.2");
6455
+ console.log("0.8.4");
6350
6456
  return;
6351
6457
  }
6352
- process.stderr.write(`[code-review] @weareikko/code-review v0.8.2\n`);
6458
+ process.stderr.write(`[code-review] @weareikko/code-review v0.8.4\n`);
6353
6459
  assertNodeVersion();
6354
6460
  applyCodeReviewEnvPrefix();
6355
6461
  applyDefaultCacheRetention();
@@ -6372,4 +6478,4 @@ if (isDirectRun()) main().catch((error) => {
6372
6478
  //#endregion
6373
6479
  export { normalizeBody as $, SUMMARY_HISTORY_END as A, buildSummaryHistoryEntries as B, createDiagnosticContext as C, traceDiagnosticPhase as D, traceDiagnostic as E, SUMMARY_MARKER as F, findExistingSummaryNoteId as G, extractSummaryHistoryEntries as H, buildArchivedSummaryEntry as I, upsertSummaryNote as J, stripSummaryHistory as K, buildReviewedCommitFooter as L, SUMMARY_HISTORY_ENTRY_START as M, SUMMARY_HISTORY_LIMIT as N, normalizeSeverity as O, SUMMARY_HISTORY_START as P, fingerprints as Q, buildSizeNoticeBlock as R, DIAGNOSTIC_CHANNEL_PREFIX as S, diagnosticChannels as T, findExistingReviewedCommitSha as U, extractReviewedCommitSha as V, findExistingSummaryNote as W, extractDiffHunkContext as X, appendFingerprintMarkers as Y, extractExistingFingerprints as Z, resolveNpmSkillDir as _, main as a, parseReviewMarkdownWithWarnings as b, buildGeneratedComments as c, startOtelBridge as d, sha256 as et, filterDiff as f, parseSkillSpec as g, loadNamedSkill as h, formatUsageLine as i, SUMMARY_HISTORY_ENTRY_END as j, toGitLabReviewSeverity as k, buildPayload as l, gitSkillCacheKey as m, formatPerModelUsage as n, run as o, runReview as p, stripSummaryMarker as q, formatSkillsFooter as r, withHttpStamping as s, countPostedBySeverity as t, isOtelEnabled as u, resolveSkillCacheDir as v, createDiagnosticRunId as w, DIAGNOSTIC_CHANNEL_NAMES as x, parseReviewMarkdown as y, buildSummaryBody as z };
6374
6480
 
6375
- //# sourceMappingURL=cli-DBkaU14C.js.map
6481
+ //# sourceMappingURL=cli-FDpPZaNn.js.map