@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
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.
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 =
|
|
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 =
|
|
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
|
])
|
package/skills/magpie/skill.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magpie",
|
|
3
|
-
"version": "0.4.
|
|
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",
|