@iceinvein/agent-skills 0.1.36 → 0.1.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iceinvein/agent-skills",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Install agent skills into AI coding tools",
5
5
  "author": "iceinvein",
6
6
  "license": "MIT",
package/skills/index.json CHANGED
@@ -221,7 +221,7 @@
221
221
  "name": "magpie",
222
222
  "description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec (falling back to a Claude second opinion when codex is unavailable), and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request.",
223
223
  "type": "prompt",
224
- "version": "0.6.0"
224
+ "version": "0.7.0"
225
225
  },
226
226
  {
227
227
  "name": "module-secret-auditor",
@@ -58,7 +58,7 @@ magpie render "$RUN_DIR" progress
58
58
 
59
59
  Dispatch the five specialist subagents in a single message using five Agent tool calls in parallel. For each focus in (security, bugs, performance, code-smells, architecture), the prompt is:
60
60
 
61
- ```
61
+ ````
62
62
  <specialist block for focus from this SKILL.md>
63
63
 
64
64
  You are reviewing PR #<PR_NUMBER>.
@@ -95,6 +95,7 @@ Write findings to $RUN_DIR/findings/<focus>.json before returning. The file MUST
95
95
  - `severity`, `risk.impact`, `risk.confidence` use category names (e.g. `high`, `low`), not sentences.
96
96
  - `risk.likelihood` describes frequency, not impact. Valid values are exactly `likely`, `possible`, `edge-case`, `unknown`. NEVER use `high`/`medium`/`low` here (those are likelihood-as-impact and will be auto-corrected, but pick the right axis).
97
97
  - `risk.action` is the disposition tag, not the recommendation text. Valid values are exactly `must-fix`, `should-fix`, `consider`, `optional`. The recommendation prose belongs in `description` under `Suggested direction:`, never in `risk.action`.
98
+ - Keep `severity` coherent with `risk`. `severity` is the headline label: use `blocker`/`high` only with `risk.impact` of `critical`/`high` and `risk.action` of `must-fix`/`should-fix`. A `low` severity paired with `must-fix`, or a `blocker` paired with `optional`, is contradictory. The 0-10 score that gates the drop threshold is derived from `risk`, not from `severity`, so an inflated `severity` on a weak `risk` is still dropped. Set `risk` accurately rather than leaning on `severity`.
98
99
 
99
100
  Bad (will be silently coerced, do not rely on this):
100
101
  ```
@@ -111,7 +112,7 @@ Good:
111
112
  - `Observation: <one idea, what the diff actually does and where>`
112
113
  - `Why it matters: <impact at realistic scale or on a real user path>`
113
114
  - `Suggested direction: <one concrete next step, optional if the fix isn't obvious>`
114
- - `Needs verification: <what you couldn't confirm from the bundle, optional, low/medium severity only>`
115
+ - `Needs verification: <what you couldn't confirm from the bundle, optional, low/medium severity only>` This labelled paragraph is the only channel for uncertainty: never hedge inside another section, and never raise `severity` to compensate for what you couldn't verify (a blocker/high you cannot stand behind is not a blocker/high). Use the exact `Needs verification:` prefix, not inline phrasing.
115
116
 
116
117
  One idea per paragraph. Do not collapse them into a single wall of text. Do not invent extra labels. If a section doesn't apply, omit it. The interactive report and the GitHub comment both parse these labels and render them as section headers, so missing labels degrade the output.
117
118
 
@@ -123,7 +124,7 @@ One idea per paragraph. Do not collapse them into a single wall of text. Do not
123
124
  - Wrapping the code in a `` ``` `` fence inside `body` is tolerated (the poster hoists the inner code out), but bare code is preferred.
124
125
 
125
126
  If you have no findings, write []. Return as your final tool result a single line: `<focus>: <N> findings (<blocker>/<high>/<medium>/<low>)`. Do not include other prose.
126
- ```
127
+ ````
127
128
 
128
129
  After each subagent returns, append `{stage: specialist, focus: <focus>, status: done, findings: <count>}` to `$RUN_DIR/log.jsonl` and re-render progress. (Per-focus `specialist` entries are diagnostic; only the aggregate `specialists` entry advances `magpie status`.)
129
130
 
@@ -143,7 +144,7 @@ Re-render progress.
143
144
 
144
145
  ### 5. Critic
145
146
 
146
- Read `$RUN_DIR/findings.deduped.json`. Apply the critic rubric from this SKILL.md verbatim (one verdict per finding). Write the kept subset to `$RUN_DIR/findings.kept.json`. Append `{stage: critic, status: done}` and re-render progress.
147
+ Read `$RUN_DIR/findings.deduped.json`. Substitute both placeholders in the critic rubric (the compact candidate list including each finding's `onChangedLine`, and the `<<DIFF_EXCERPT>>` hunks for the referenced files), then apply the rubric verbatim (one verdict per finding). Write the kept subset to `$RUN_DIR/findings.kept.json`. Append `{stage: critic, status: done}` and re-render progress.
147
148
 
148
149
  ### 6. Peer review
149
150
 
@@ -273,7 +274,9 @@ For each potential finding:
273
274
  - medium: Defense-in-depth concern or validation gap with limited or uncertain exploitability
274
275
  - low: Minor hardening opportunity with low impact
275
276
 
276
- Report only credible concerns grounded in code shown. If a concern depends on context you can't see, note it as "needs verification" in the description. Do not invent vulnerabilities without evidence.
277
+ Report only credible concerns grounded in code shown. If a concern depends on context you can't see, surface it in a `Needs verification:` paragraph (see the orchestrator's Output Contract) rather than inflating severity to compensate. Do not invent vulnerabilities without evidence.
278
+
279
+ Boundary with Architecture: report missing input validation here when it enables an attack (injection, path traversal, SSRF, auth bypass). Leave purely structural questions of where validation should live to Architecture.
277
280
 
278
281
  Use the JSON schema defined in the orchestrator's `## Output Contract` block; do not invent fields.
279
282
  ```
@@ -334,7 +337,9 @@ For each potential bug:
334
337
  - medium: Edge-case bug or missing guard with limited blast radius
335
338
  - low: Very small correctness cleanup with low user impact
336
339
 
337
- Prioritize bugs that cause silent wrong behavior over those that crash (crashes are at least visible). Flag "needs verification" when you can't determine reachability from the diff alone.
340
+ Prioritize bugs that cause silent wrong behavior over those that crash (crashes are at least visible). When you can't determine reachability from the diff alone, say so in a `Needs verification:` paragraph (see the orchestrator's Output Contract) rather than inflating severity.
341
+
342
+ Boundary with Performance: report leaks, unbounded growth, and missing cleanup here only when the primary consequence is incorrect behavior, a crash, or resource exhaustion that breaks a workflow. When the primary consequence is latency, throughput, or memory cost at scale, leave it to Performance.
338
343
 
339
344
  Use the JSON schema defined in the orchestrator's `## Output Contract` block; do not invent fields.
340
345
  ```
@@ -393,6 +398,8 @@ For each potential issue:
393
398
 
394
399
  Only flag issues that would have noticeable impact at realistic scale. Don't suggest micro-optimizations on cold paths.
395
400
 
401
+ Boundary with Bugs: focus on cost at realistic scale. Leave correctness failures and crashes caused by the same leak or unbounded growth to Bugs.
402
+
396
403
  Use the JSON schema defined in the orchestrator's `## Output Contract` block; do not invent fields.
397
404
  ```
398
405
 
@@ -510,6 +517,8 @@ For each potential issue:
510
517
 
511
518
  Boundary with Code Smells: focus on module boundaries, public contracts, ownership, and system-level data flow. Leave local implementation smells such as duplicate branches, long functions, and primitive obsession to Code Smells.
512
519
 
520
+ Boundary with Security: flag validation gaps as design/contract issues (where validation belongs, which boundary should enforce it). Leave exploitability assessment to Security.
521
+
513
522
  Focus on design decisions introduced or materially worsened by this PR that affect the long-term health of the codebase. Don't flag things that are "technically impure" but work well in practice.
514
523
 
515
524
  Use the JSON schema defined in the orchestrator's `## Output Contract` block; do not invent fields.
@@ -521,24 +530,24 @@ The main agent runs this in-conversation against `findings.deduped.json` and wri
521
530
 
522
531
  ## Substitute before use
523
532
 
524
- The block below contains one placeholder. Replace it before running the rubric.
533
+ The block below contains two placeholders. Replace both before running the rubric.
525
534
 
526
- - `<<DEDUPED_FINDINGS_COMPACT>>` — pretty-printed JSON array of the deduped candidates with only the fields the critic needs. Build with:
535
+ - `<<DEDUPED_FINDINGS_COMPACT>>` — pretty-printed JSON array of the deduped candidates with only the fields the critic needs. Each candidate carries `onChangedLine` (set deterministically during dedupe: `true` = anchored inside a changed hunk, `false` = anchored on code the PR did not touch, `null` = not anchorable). Build with:
527
536
  ```
528
- jq '[.[] | {id, file, line, severity, risk, domain, title, description}]' "$RUN_DIR/findings.deduped.json"
537
+ jq '[.[] | {id, file, line, onChangedLine, severity, risk, domain, title, description}]' "$RUN_DIR/findings.deduped.json"
529
538
  ```
539
+ - `<<DIFF_EXCERPT>>` — the diff hunks for the files referenced by the candidates. For small PRs the full `diff.patch` is fine; for larger PRs, narrow to the files named in the candidate set.
530
540
 
531
541
  ````magpie-critic
532
- You are a senior code reviewer auditing a list of candidate review findings produced by other agents on a pull request. Your only job is to keep the findings that a busy reviewer would genuinely thank you for surfacing, and drop the rest. You do not see the diff itself; you only see what the candidate finding claims, its anchor, and its risk fields. Treat each candidate skeptically.
542
+ You are a senior code reviewer auditing a list of candidate review findings produced by other agents on a pull request. Your only job is to keep the findings that a busy reviewer would genuinely thank you for surfacing, and drop the rest. You see each candidate's claim, anchor, and risk fields, plus the diff hunks around them. Use the hunks only to validate or refute the candidate in front of you: do not surface new findings or broaden the review (adding issues is the peer-review stage's job). Treat each candidate skeptically.
533
543
 
534
544
  Drop a finding if any of the following hold:
535
545
  - The description sounds speculative, hedged, or "needs verification" without strong evidence in the title or anchor.
536
546
  - The finding is a stylistic preference, micro-optimization, or "nice to have" cleanup with no concrete user or maintenance impact.
537
- - The finding is a pre-existing concern not introduced by the PR.
538
- - The finding is a theoretical risk that requires unlikely preconditions, or defense-in-depth on already-defended code.
547
+ - Its `onChangedLine` is `false` and the description does not explain why the PR newly triggers a pre-existing concern (i.e. it is anchored on code this PR did not change).
548
+ - The finding is a theoretical risk that requires unlikely preconditions, or defense-in-depth on code the supplied hunks show is already guarded.
539
549
  - The finding belongs to a category the repository's linter already enforces (naming, formatting, unused imports).
540
550
  - The finding is on a test file or a generated/vendored file unless it materially affects test correctness.
541
- - The finding duplicates another candidate at a similar anchor and is the weaker version.
542
551
 
543
552
  Keep a finding if it points to a concrete defect on a changed line, with enough specificity that a reviewer could decide to act on it without re-reading the entire PR.
544
553
 
@@ -566,6 +575,11 @@ Output every candidate exactly once. Do not invent ids. Do not output anything o
566
575
  ```json
567
576
  <<DEDUPED_FINDINGS_COMPACT>>
568
577
  ```
578
+
579
+ ## Diff Hunks For Those Candidates
580
+ ```diff
581
+ <<DIFF_EXCERPT>>
582
+ ```
569
583
  ````
570
584
 
571
585
  ## Peer-review prompt
@@ -0,0 +1,66 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { annotateChangedLines } from '../changed-lines.ts'
3
+ import type { ReviewFinding } from '../types.ts'
4
+
5
+ const SAMPLE = `diff --git a/src/a.ts b/src/a.ts
6
+ index abc..def 100644
7
+ --- a/src/a.ts
8
+ +++ b/src/a.ts
9
+ @@ -1,3 +1,4 @@
10
+ line1
11
+ -old
12
+ +new
13
+ +added
14
+ line3
15
+ `
16
+
17
+ function finding(overrides: Partial<ReviewFinding>): ReviewFinding {
18
+ return {
19
+ id: 'x-1',
20
+ file: 'src/a.ts',
21
+ line: 2,
22
+ severity: 'high',
23
+ risk: { impact: 'high', likelihood: 'likely', confidence: 'high', action: 'must-fix' },
24
+ title: 't',
25
+ description: 'd',
26
+ domain: 'bugs',
27
+ ...overrides,
28
+ }
29
+ }
30
+
31
+ describe('annotateChangedLines', () => {
32
+ test('flags an anchor inside a changed hunk as true', () => {
33
+ const [f] = annotateChangedLines([finding({ line: 3 })], SAMPLE)
34
+ expect(f?.onChangedLine).toBe(true)
35
+ })
36
+
37
+ test('flags a context line inside a hunk as true', () => {
38
+ const [f] = annotateChangedLines([finding({ line: 4 })], SAMPLE)
39
+ expect(f?.onChangedLine).toBe(true)
40
+ })
41
+
42
+ test('flags an anchor outside any hunk as false', () => {
43
+ const [f] = annotateChangedLines([finding({ line: 99 })], SAMPLE)
44
+ expect(f?.onChangedLine).toBe(false)
45
+ })
46
+
47
+ test('flags a finding whose file is absent from the diff as false', () => {
48
+ const [f] = annotateChangedLines([finding({ file: 'src/other.ts', line: 1 })], SAMPLE)
49
+ expect(f?.onChangedLine).toBe(false)
50
+ })
51
+
52
+ test('leaves unanchorable findings null', () => {
53
+ const [f] = annotateChangedLines([finding({ line: null })], SAMPLE)
54
+ expect(f?.onChangedLine).toBeNull()
55
+ })
56
+
57
+ test('leaves everything null when the diff is unavailable', () => {
58
+ const [f] = annotateChangedLines([finding({ line: 3 })], '')
59
+ expect(f?.onChangedLine).toBeNull()
60
+ })
61
+
62
+ test('matches paths that differ only by a b/ prefix', () => {
63
+ const [f] = annotateChangedLines([finding({ file: 'b/src/a.ts', line: 2 })], SAMPLE)
64
+ expect(f?.onChangedLine).toBe(true)
65
+ })
66
+ })
@@ -1,5 +1,10 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { filePathMatches, parseUnifiedDiffToHunks, splitDiffByFile } from '../diff-utils.ts'
2
+ import {
3
+ buildNewSideLineIndex,
4
+ filePathMatches,
5
+ parseUnifiedDiffToHunks,
6
+ splitDiffByFile,
7
+ } from '../diff-utils.ts'
3
8
 
4
9
  const SAMPLE = `diff --git a/src/a.ts b/src/a.ts
5
10
  index abc..def 100644
@@ -93,6 +98,20 @@ describe('splitDiffByFile', () => {
93
98
  })
94
99
  })
95
100
 
101
+ describe('buildNewSideLineIndex', () => {
102
+ test('collects added and context new-side line numbers per file', () => {
103
+ const index = buildNewSideLineIndex(SAMPLE)
104
+ // src/a.ts: context(1), added(2), added(3), context(4). Removed line has no new-side number.
105
+ expect([...(index.get('src/a.ts') ?? [])].sort((a, b) => a - b)).toEqual([1, 2, 3, 4])
106
+ // src/b.ts: two added lines.
107
+ expect([...(index.get('src/b.ts') ?? [])].sort((a, b) => a - b)).toEqual([1, 2])
108
+ })
109
+
110
+ test('returns empty map for empty input', () => {
111
+ expect(buildNewSideLineIndex('').size).toBe(0)
112
+ })
113
+ })
114
+
96
115
  describe('filePathMatches', () => {
97
116
  test('exact match', () => {
98
117
  expect(filePathMatches('src/a.ts', 'src/a.ts')).toBe(true)
@@ -0,0 +1,36 @@
1
+ import { buildNewSideLineIndex, filePathMatches } from './diff-utils.ts'
2
+ import type { ReviewFinding } from './types.ts'
3
+
4
+ function lookup(index: Map<string, Set<number>>, file: string): Set<number> | undefined {
5
+ const direct = index.get(file)
6
+ if (direct) return direct
7
+ for (const [key, set] of index) {
8
+ if (filePathMatches(key, file)) return set
9
+ }
10
+ return undefined
11
+ }
12
+
13
+ /**
14
+ * Annotate each finding with `onChangedLine`: whether its anchor falls on a line
15
+ * this PR added or kept as context within a changed hunk.
16
+ *
17
+ * - `true` — anchor is inside a changed hunk (plausibly introduced/triggered here).
18
+ * - `false` — anchor is on code this PR did not touch (pre-existing / unrelated).
19
+ * - `null` — undecidable: the finding has no line anchor, or the diff is
20
+ * unavailable (e.g. archived-run replay).
21
+ *
22
+ * This is a deterministic input for the critic so it never has to eyeball
23
+ * "was this introduced by the PR?" from prose alone.
24
+ */
25
+ export function annotateChangedLines(findings: ReviewFinding[], diff: string): ReviewFinding[] {
26
+ if (!diff.trim()) {
27
+ return findings.map((f) => ({ ...f, onChangedLine: null }))
28
+ }
29
+ const index = buildNewSideLineIndex(diff)
30
+ return findings.map((f) => {
31
+ if (f.line == null || !f.file) return { ...f, onChangedLine: null }
32
+ const set = lookup(index, f.file)
33
+ if (!set) return { ...f, onChangedLine: false }
34
+ return { ...f, onChangedLine: set.has(f.line) }
35
+ })
36
+ }
@@ -1,5 +1,6 @@
1
1
  import { appendFile, readdir, readFile, writeFile } from 'node:fs/promises'
2
2
  import { join } from 'node:path'
3
+ import { annotateChangedLines } from './changed-lines.ts'
3
4
  import { deduplicateFindings } from './dedupe.ts'
4
5
  import { verifyEvidence } from './evidence-filter.ts'
5
6
  import { DEFAULT_THRESHOLD, scoreRisk } from './score.ts'
@@ -77,8 +78,15 @@ export async function runDedupe(runDir: string, options: RunDedupeOptions = {}):
77
78
  const deduped = deduplicateFindings(collected)
78
79
  const scored = deduped.map((f) => ({ ...f, score: scoreRisk(f.risk) }))
79
80
  const evidence = await verifyEvidence(scored, join(runDir, 'worktree'))
80
- const aboveThreshold = evidence.kept.filter((f) => (f.score ?? 0) >= threshold)
81
- const belowThreshold = evidence.kept.filter((f) => (f.score ?? 0) < threshold)
81
+ let diff = ''
82
+ try {
83
+ diff = await readFile(join(runDir, 'diff.patch'), 'utf8')
84
+ } catch {
85
+ diff = ''
86
+ }
87
+ const annotated = annotateChangedLines(evidence.kept, diff)
88
+ const aboveThreshold = annotated.filter((f) => (f.score ?? 0) >= threshold)
89
+ const belowThreshold = annotated.filter((f) => (f.score ?? 0) < threshold)
82
90
  await writeFile(
83
91
  join(runDir, 'findings.deduped.json'),
84
92
  `${JSON.stringify(aboveThreshold, null, 2)}\n`,
@@ -76,6 +76,31 @@ export function parseUnifiedDiffToHunks(diff: string): DiffHunk[] {
76
76
  return hunks
77
77
  }
78
78
 
79
+ /**
80
+ * Per-file set of new-side (RIGHT) line numbers that fall within a changed hunk
81
+ * (added or context lines). These are the lines GitHub accepts as inline-comment
82
+ * anchors, and also the lines we treat as "within the PR's diff" when deciding
83
+ * whether a finding is anchored on changed code vs pre-existing/unrelated code.
84
+ * Keyed by the post-image (`b/`) file path.
85
+ */
86
+ export function buildNewSideLineIndex(diff: string): Map<string, Set<number>> {
87
+ const result = new Map<string, Set<number>>()
88
+ if (!diff) return result
89
+ for (const [file, chunk] of splitDiffByFile(diff)) {
90
+ const hunks = parseUnifiedDiffToHunks(chunk)
91
+ const set = new Set<number>()
92
+ for (const h of hunks) {
93
+ for (const l of h.lines) {
94
+ if (l.newLineNo != null && (l.type === 'added' || l.type === 'context')) {
95
+ set.add(l.newLineNo)
96
+ }
97
+ }
98
+ }
99
+ result.set(file, set)
100
+ }
101
+ return result
102
+ }
103
+
79
104
  export function splitDiffByFile(diff: string): Map<string, string> {
80
105
  const m = new Map<string, string>()
81
106
  if (!diff) return m
@@ -83,7 +108,7 @@ export function splitDiffByFile(diff: string): Map<string, string> {
83
108
  for (const chunk of chunks) {
84
109
  if (!chunk.startsWith('diff --git ')) continue
85
110
  const headerMatch = chunk.match(FILE_HEADER)
86
- if (!headerMatch || !headerMatch[2]) continue
111
+ if (!headerMatch?.[2]) continue
87
112
  m.set(headerMatch[2], chunk)
88
113
  }
89
114
  return m
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import { appendFile, readFile, writeFile } from 'node:fs/promises'
3
3
  import { join } from 'node:path'
4
- import { parseUnifiedDiffToHunks, splitDiffByFile } from './diff-utils.ts'
4
+ import { buildNewSideLineIndex } from './diff-utils.ts'
5
5
  import { formatFindingDescriptionMarkdown } from './finding-description.ts'
6
6
  import {
7
7
  type FocusId,
@@ -11,29 +11,6 @@ import {
11
11
  type Severity,
12
12
  } from './types.ts'
13
13
 
14
- /**
15
- * Build a per-file set of RIGHT-side line numbers that GitHub's PR Reviews API
16
- * will accept as inline-comment anchors (added or context lines within hunks).
17
- * Returns an empty map when the diff is empty/unavailable.
18
- */
19
- function buildValidRightLines(diff: string): Map<string, Set<number>> {
20
- const result = new Map<string, Set<number>>()
21
- if (!diff) return result
22
- for (const [file, chunk] of splitDiffByFile(diff)) {
23
- const hunks = parseUnifiedDiffToHunks(chunk)
24
- const set = new Set<number>()
25
- for (const h of hunks) {
26
- for (const l of h.lines) {
27
- if (l.newLineNo != null && (l.type === 'added' || l.type === 'context')) {
28
- set.add(l.newLineNo)
29
- }
30
- }
31
- }
32
- result.set(file, set)
33
- }
34
- return result
35
- }
36
-
37
14
  export type PostInput = {
38
15
  runDir: string
39
16
  findingIds: string[]
@@ -555,7 +532,7 @@ export async function postFindingsAsReview(input: PostReviewInput): Promise<Post
555
532
  let validRightLines: Map<string, Set<number>> | null = null
556
533
  try {
557
534
  const diff = await readFile(join(input.runDir, 'diff.patch'), 'utf8')
558
- validRightLines = buildValidRightLines(diff)
535
+ validRightLines = buildNewSideLineIndex(diff)
559
536
  } catch {
560
537
  // diff.patch absent (archived/legacy runs); skip validation and trust caller.
561
538
  validRightLines = null
@@ -54,6 +54,11 @@ export type ReviewFinding = {
54
54
  mergedFrom?: MergedFromEntry[]
55
55
  /** Derived 0-10 importance score from risk fields. Populated during dedupe. */
56
56
  score?: number
57
+ /**
58
+ * Whether the anchor sits on a line this PR changed. Populated during dedupe
59
+ * from the diff; `null` when not anchorable or the diff is unavailable.
60
+ */
61
+ onChangedLine?: boolean | null
57
62
  }
58
63
 
59
64
  const SEVERITY_SYNONYMS: Record<string, Severity> = {
@@ -309,6 +314,9 @@ export function parseFinding(raw: unknown): ReviewFinding {
309
314
  domain: (r.domain as ReviewFinding['domain']) ?? null,
310
315
  mergedFrom: Array.isArray(r.mergedFrom) ? (r.mergedFrom as MergedFromEntry[]) : undefined,
311
316
  ...(typeof r.score === 'number' && Number.isFinite(r.score) ? { score: r.score } : {}),
317
+ ...(typeof r.onChangedLine === 'boolean' || r.onChangedLine === null
318
+ ? { onChangedLine: r.onChangedLine as boolean | null }
319
+ : {}),
312
320
  }
313
321
  }
314
322
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magpie",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Interactive PR review pipeline. Runs five parallel specialist subagents (security, bugs, performance, code-smells, architecture), dedupes findings, applies a critic rubric, peer-reviews via codex exec (falling back to a Claude second opinion when codex is unavailable), and serves an interactive HTML report for selecting findings to post via gh. Bundles a Bun CLI installed onto PATH via the skill's postinstall step. Use when the user asks to review a GitHub pull request.",
5
5
  "author": "iceinvein",
6
6
  "type": "prompt",