@iceinvein/agent-skills 0.1.30 → 0.1.31

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.30",
3
+ "version": "0.1.31",
4
4
  "description": "Install agent skills into AI coding tools",
5
5
  "author": "iceinvein",
6
6
  "license": "MIT",
package/skills/index.json CHANGED
@@ -197,7 +197,7 @@
197
197
  "name": "magpie",
198
198
  "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, 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.",
199
199
  "type": "prompt",
200
- "version": "0.4.0"
200
+ "version": "0.4.1"
201
201
  },
202
202
  {
203
203
  "name": "module-secret-auditor",
@@ -109,6 +109,61 @@ test('formatPostBody omits the suggestion block when no suggestion is set', () =
109
109
  expect(body).not.toContain('```suggestion')
110
110
  })
111
111
 
112
+ test('formatInlineBody extracts an embedded fenced code block out of the suggestion body', () => {
113
+ const body = formatInlineBody({
114
+ ...findingA,
115
+ line: 12,
116
+ suggestion: {
117
+ body: 'Switch to the inference profile and widen IAM:\n\n```hcl\nstatement {\n effect = "Allow"\n}\n```\n\nAt minimum, log the failure.',
118
+ startLine: 12,
119
+ endLine: 12,
120
+ },
121
+ } as never)
122
+ // Prose appears as ordinary markdown, not inside the suggestion fence.
123
+ expect(body).toContain('Switch to the inference profile and widen IAM:')
124
+ expect(body).toContain('At minimum, log the failure.')
125
+ // The suggestion body contains only the extracted code, not the prose.
126
+ expect(body).toContain('```suggestion\nstatement {\n effect = "Allow"\n}\n```')
127
+ // The hidden finding marker stays inside an HTML comment, not a code block.
128
+ const markerIdx = body.indexOf('<!-- magpie:finding')
129
+ expect(markerIdx).toBeGreaterThan(-1)
130
+ // After the marker the body must be balanced; no dangling open fence above it.
131
+ const beforeMarker = body.slice(0, markerIdx)
132
+ const fenceCount = (beforeMarker.match(/```/g) ?? []).length
133
+ expect(fenceCount % 2).toBe(0)
134
+ })
135
+
136
+ test('formatInlineBody handles a fenced suggestion that contains no surrounding prose', () => {
137
+ const body = formatInlineBody({
138
+ ...findingA,
139
+ line: 12,
140
+ suggestion: {
141
+ body: '```ts\nconst safe = true;\n```',
142
+ startLine: 12,
143
+ endLine: 12,
144
+ },
145
+ } as never)
146
+ expect(body).toContain('```suggestion\nconst safe = true;\n```')
147
+ // No stray, unterminated fence above the magpie marker.
148
+ const markerIdx = body.indexOf('<!-- magpie:finding')
149
+ const beforeMarker = body.slice(0, markerIdx)
150
+ expect((beforeMarker.match(/```/g) ?? []).length % 2).toBe(0)
151
+ })
152
+
153
+ test('buildSuggestion uses a longer outer fence when the code body contains triple backticks', () => {
154
+ const body = formatInlineBody({
155
+ ...findingA,
156
+ line: 12,
157
+ suggestion: {
158
+ body: 'const md = "```inline```"',
159
+ startLine: 12,
160
+ endLine: 12,
161
+ },
162
+ } as never)
163
+ // 3 backticks in body → outer fence must be at least 4 to avoid premature close.
164
+ expect(body).toContain('````suggestion\nconst md = "```inline```"\n````')
165
+ })
166
+
112
167
  test('runPost rejects when pr.json is missing', async () => {
113
168
  const outcome = await runPost({ runDir, findingIds: ['anything'] })
114
169
  expect(outcome.ok).toBe(false)
@@ -116,10 +116,57 @@ function buildRiskParts(f: ReviewFinding): string[] {
116
116
  ]
117
117
  }
118
118
 
119
- function buildSuggestionBlock(f: ReviewFinding): string | null {
120
- const body = f.suggestion?.body.trim()
121
- if (!body) return null
122
- return ['```suggestion', body, '```'].join('\n')
119
+ function maxBacktickRun(s: string): number {
120
+ let max = 0
121
+ for (const m of s.matchAll(/`+/g)) {
122
+ if (m[0].length > max) max = m[0].length
123
+ }
124
+ return max
125
+ }
126
+
127
+ /**
128
+ * Pull a single fenced code block out of a suggestion body, returning its code
129
+ * plus any surrounding prose. LLMs sometimes wrap the real replacement in
130
+ * ```lang ... ``` with explanatory text around it; if that nested fence leaks
131
+ * into the outer ```suggestion``` wrapper it breaks GitHub's renderer and
132
+ * causes downstream markdown (e.g. the magpie:finding HTML marker) to spill
133
+ * into a stray code block. We hoist the inner code out so the suggestion body
134
+ * contains only what should be committed.
135
+ */
136
+ function splitEmbeddedFence(
137
+ body: string,
138
+ ): { code: string; preamble: string; postamble: string } | null {
139
+ const re = /(?:^|\n)(`{3,})[^\n`]*\n([\s\S]*?)\n\1(?=\n|$)/
140
+ const match = body.match(re)
141
+ if (match?.index === undefined) return null
142
+ const code = match[2] ?? ''
143
+ const matchStart = match.index + (body[match.index] === '\n' ? 1 : 0)
144
+ const matchEnd = matchStart + match[0].length - (body[match.index] === '\n' ? 1 : 0)
145
+ return {
146
+ code,
147
+ preamble: body.slice(0, matchStart).trim(),
148
+ postamble: body.slice(matchEnd).trim(),
149
+ }
150
+ }
151
+
152
+ type SuggestionParts = {
153
+ block: string | null
154
+ preamble: string | null
155
+ postamble: string | null
156
+ }
157
+
158
+ function buildSuggestion(f: ReviewFinding): SuggestionParts {
159
+ const raw = f.suggestion?.body.trim()
160
+ if (!raw) return { block: null, preamble: null, postamble: null }
161
+ const split = splitEmbeddedFence(raw)
162
+ const code = split?.code ?? raw
163
+ const fenceLen = Math.max(3, maxBacktickRun(code) + 1)
164
+ const fence = '`'.repeat(fenceLen)
165
+ return {
166
+ block: `${fence}suggestion\n${code}\n${fence}`,
167
+ preamble: split?.preamble || null,
168
+ postamble: split?.postamble || null,
169
+ }
123
170
  }
124
171
 
125
172
  function buildFindingMarker(f: ReviewFinding): string {
@@ -150,7 +197,7 @@ export function formatInlineBody(f: ReviewFinding): string {
150
197
  const label = SEVERITY_LABEL[f.severity]
151
198
  const focus = formatFocus(f)
152
199
  const metaLine = buildMetaLine([...buildRiskParts(f), focus ? `Focus · ${focus}` : null])
153
- const suggestion = buildSuggestionBlock(f)
200
+ const { block: suggestion, preamble: sugPre, postamble: sugPost } = buildSuggestion(f)
154
201
  const sections = formatFindingDescriptionMarkdown(f.description)
155
202
 
156
203
  return joinBlock([
@@ -159,8 +206,12 @@ export function formatInlineBody(f: ReviewFinding): string {
159
206
  metaLine,
160
207
  '',
161
208
  sections,
209
+ sugPre ? '' : null,
210
+ sugPre,
162
211
  suggestion ? '' : null,
163
212
  suggestion,
213
+ sugPost ? '' : null,
214
+ sugPost,
164
215
  '',
165
216
  buildFindingMarker(f),
166
217
  ])
@@ -181,7 +232,7 @@ export function formatConversationBody(f: ReviewFinding): string {
181
232
  ...buildRiskParts(f),
182
233
  ])
183
234
  const sections = formatFindingDescriptionMarkdown(f.description)
184
- const suggestion = buildSuggestionBlock(f)
235
+ const { block: suggestion, preamble: sugPre, postamble: sugPost } = buildSuggestion(f)
185
236
 
186
237
  return joinBlock([
187
238
  `### ${icon} ${label}: ${f.title}`,
@@ -189,8 +240,12 @@ export function formatConversationBody(f: ReviewFinding): string {
189
240
  metaLine,
190
241
  '',
191
242
  sections,
243
+ sugPre ? '' : null,
244
+ sugPre,
192
245
  suggestion ? '' : null,
193
246
  suggestion,
247
+ sugPost ? '' : null,
248
+ sugPost,
194
249
  '',
195
250
  buildFindingMarker(f),
196
251
  ])
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magpie",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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, 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",