@usepipr/runtime 0.5.0 → 0.6.0

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.
Files changed (32) hide show
  1. package/dist/{commands-CF59DZXx.d.mts → commands-BHXh7P28.d.mts} +2 -2
  2. package/dist/{commands-CF59DZXx.d.mts.map → commands-BHXh7P28.d.mts.map} +1 -1
  3. package/dist/{commands-DgZU7tf7.mjs → commands-GWHklzvq.mjs} +1111 -105
  4. package/dist/commands-GWHklzvq.mjs.map +1 -0
  5. package/dist/index.d.mts +3 -3
  6. package/dist/index.mjs +2 -2
  7. package/dist/internal/action-result.d.mts +1 -1
  8. package/dist/internal/docs.d.mts +1 -1
  9. package/dist/internal/docs.d.mts.map +1 -1
  10. package/dist/internal/docs.mjs +1 -1
  11. package/dist/internal/pipr-result.d.mts +1 -1
  12. package/dist/internal/testing.d.mts +3 -2
  13. package/dist/internal/testing.d.mts.map +1 -1
  14. package/dist/internal/testing.mjs +1 -1
  15. package/dist/{official-github-workflow-DNzV5Z70.mjs → official-github-workflow-B3yeaCgq.mjs} +240 -83
  16. package/dist/official-github-workflow-B3yeaCgq.mjs.map +1 -0
  17. package/dist/pi/runtime-tools-extension.d.mts.map +1 -1
  18. package/dist/pi/runtime-tools-extension.mjs +128 -2
  19. package/dist/pi/runtime-tools-extension.mjs.map +1 -1
  20. package/dist/{recipes-w72EpAOi.d.mts → recipes-C1kxJCgQ.d.mts} +2 -1
  21. package/dist/recipes-C1kxJCgQ.d.mts.map +1 -0
  22. package/dist/runtime-tools-core-BF8rSeNS.mjs +476 -0
  23. package/dist/runtime-tools-core-BF8rSeNS.mjs.map +1 -0
  24. package/dist/{types-Dn1U3MDG.d.mts → types-w22wbdo9.d.mts} +43 -2
  25. package/dist/types-w22wbdo9.d.mts.map +1 -0
  26. package/package.json +2 -2
  27. package/dist/commands-DgZU7tf7.mjs.map +0 -1
  28. package/dist/official-github-workflow-DNzV5Z70.mjs.map +0 -1
  29. package/dist/recipes-w72EpAOi.d.mts.map +0 -1
  30. package/dist/runtime-tools-core-D8_WsbL_.mjs +0 -179
  31. package/dist/runtime-tools-core-D8_WsbL_.mjs.map +0 -1
  32. package/dist/types-Dn1U3MDG.d.mts.map +0 -1
@@ -16,7 +16,7 @@ export default definePipr((pipr) => {
16
16
  provider: "deepseek",
17
17
  model: "deepseek-v4-pro",
18
18
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
19
- options: { thinking: "high" },
19
+ thinking: "high",
20
20
  });
21
21
 
22
22
  const fallback = pipr.model({
@@ -24,29 +24,29 @@ export default definePipr((pipr) => {
24
24
  provider: "deepseek",
25
25
  model: "deepseek-v4-pro",
26
26
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
27
- options: { thinking: "medium" },
27
+ thinking: "medium",
28
28
  });
29
29
 
30
30
  pipr.config({ publication: { maxInlineComments: 8 } });
31
31
 
32
- const reviewer = pipr.reviewer({
33
- name: "bug-hunter",
34
- model: primary,
35
- fallbacks: [fallback],
36
- instructions: \`
37
- Review only defects with a reproducible failure path or a violated
38
- repository contract: broken logic, edge cases, concurrency risks, data
39
- loss, performance regressions, and behavior changes missing meaningful
40
- tests. For API, async, state, and concurrency changes, inspect relevant
41
- callers and tests before reporting. Suppress generic maintainability,
42
- style-only, and broad refactor feedback.
43
- \`,
44
- timeout: "7m",
45
- });
46
-
47
32
  pipr.review({
48
33
  id: "bug-hunter",
49
- reviewer,
34
+ model: primary,
35
+ fallbacks: [fallback],
36
+ instructions: {
37
+ findings: \`
38
+ Review only defects with a reproducible failure path or a violated
39
+ repository contract: broken logic, edge cases, concurrency risks, data
40
+ loss, performance regressions, and behavior changes missing meaningful
41
+ tests. For API, async, state, and concurrency changes, inspect relevant
42
+ callers and tests before reporting. Suppress generic maintainability,
43
+ style-only, and broad refactor feedback.
44
+ \`,
45
+ summary: \`
46
+ Summarize the changed behavior and concrete defect risk. Use merged
47
+ findings as evidence and omit generic praise or speculative concerns.
48
+ \`,
49
+ },
50
50
  paths: {
51
51
  exclude: ["docs/**", "**/*.md"],
52
52
  },
@@ -77,7 +77,7 @@ export default definePipr((pipr) => {
77
77
  provider: "deepseek",
78
78
  model: "deepseek-v4-pro",
79
79
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
80
- options: { thinking: "medium" },
80
+ thinking: "medium",
81
81
  });
82
82
 
83
83
  const changelogOutput = pipr.schema({
@@ -140,7 +140,7 @@ export default definePipr((pipr) => {
140
140
  provider: "deepseek",
141
141
  model: "deepseek-v4-pro",
142
142
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
143
- options: { thinking: "high" },
143
+ thinking: "high",
144
144
  });
145
145
 
146
146
  const ciTriageOutput = pipr.schema({
@@ -241,7 +241,7 @@ export default definePipr((pipr) => {
241
241
  provider: "deepseek",
242
242
  model: "deepseek-v4-pro",
243
243
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
244
- options: { thinking: "high" },
244
+ thinking: "high",
245
245
  });
246
246
 
247
247
  pipr.config({ publication: { maxInlineComments: 5 } });
@@ -249,12 +249,18 @@ export default definePipr((pipr) => {
249
249
  pipr.review({
250
250
  id: "review",
251
251
  model,
252
- instructions: \`
253
- Review changed behavior for correctness, security, maintainability, and
254
- meaningful regression gaps. Focus on concrete impact and compatibility
255
- with repository contracts. Return only actionable findings that target
256
- valid diff ranges.
257
- \`,
252
+ instructions: {
253
+ findings: \`
254
+ Review changed behavior for correctness, security, maintainability, and
255
+ meaningful regression gaps. Focus on concrete impact and compatibility
256
+ with repository contracts. Return only actionable findings that target
257
+ valid diff ranges.
258
+ \`,
259
+ summary: \`
260
+ Summarize the changed behavior, overall risk, and useful reviewer focus.
261
+ Use merged findings as evidence without introducing new defects.
262
+ \`,
263
+ },
258
264
  timeout: "10m",
259
265
  comment: (result, context) => {
260
266
  const inlineFindingSummary =
@@ -304,7 +310,7 @@ export default definePipr((pipr) => {
304
310
  provider: "deepseek",
305
311
  model: "deepseek-v4-pro",
306
312
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
307
- options: { thinking: "high" },
313
+ thinking: "high",
308
314
  });
309
315
 
310
316
  const dependencyOutput = pipr.schema({
@@ -404,7 +410,7 @@ export default definePipr((pipr) => {
404
410
  provider: "deepseek",
405
411
  model: "deepseek-v4-pro",
406
412
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
407
- options: { thinking: "high" },
413
+ thinking: "high",
408
414
  });
409
415
 
410
416
  const diagnosticOutput = pipr.schema({
@@ -480,7 +486,7 @@ export default definePipr((pipr) => {
480
486
  provider: "deepseek",
481
487
  model: "deepseek-v4-pro",
482
488
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
483
- options: { thinking: "high" },
489
+ thinking: "high",
484
490
  });
485
491
 
486
492
  pipr.config({ publication: { maxInlineComments: 6 } });
@@ -1063,7 +1069,7 @@ export default definePipr((pipr) => {
1063
1069
  provider: "deepseek",
1064
1070
  model: "deepseek-v4-pro",
1065
1071
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
1066
- options: { thinking: "high" },
1072
+ thinking: "high",
1067
1073
  });
1068
1074
 
1069
1075
  const askAgent = pipr.agent({
@@ -1109,6 +1115,7 @@ export default definePipr((pipr) => {
1109
1115
  //#region src/config/recipes/multi-agent-review.ts
1110
1116
  const multiAgentReviewRecipe = {
1111
1117
  id: "multi-agent-review",
1118
+ requiresChecksPermission: true,
1112
1119
  title: "Multi-agent Review",
1113
1120
  description: "Security, test, and maintainability agents with an aggregator agent.",
1114
1121
  sourceTools: [
@@ -1124,7 +1131,7 @@ export default definePipr((pipr) => {
1124
1131
  provider: "deepseek",
1125
1132
  model: "deepseek-v4-pro",
1126
1133
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
1127
- options: { thinking: "high" },
1134
+ thinking: "high",
1128
1135
  });
1129
1136
 
1130
1137
  const fast = pipr.model({
@@ -1132,7 +1139,7 @@ export default definePipr((pipr) => {
1132
1139
  provider: "deepseek",
1133
1140
  model: "deepseek-v4-pro",
1134
1141
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
1135
- options: { thinking: "medium" },
1142
+ thinking: "medium",
1136
1143
  });
1137
1144
 
1138
1145
  const specialistPrompt = (input: { manifest: unknown; focus: string }) => pipr.prompt\`
@@ -1232,7 +1239,7 @@ export default definePipr((pipr) => {
1232
1239
  provider: "deepseek",
1233
1240
  model: "deepseek-v4-pro",
1234
1241
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
1235
- options: { thinking: "high" },
1242
+ thinking: "high",
1236
1243
  });
1237
1244
 
1238
1245
  const memory = pipr.use(
@@ -1656,7 +1663,7 @@ export default definePipr((pipr) => {
1656
1663
  provider: "deepseek",
1657
1664
  model: "deepseek-v4-pro",
1658
1665
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
1659
- options: { thinking: "medium" },
1666
+ thinking: "medium",
1660
1667
  });
1661
1668
 
1662
1669
  pipr.config({ publication: { maxInlineComments: 0 } });
@@ -1843,6 +1850,7 @@ function markdownFenceFor(value: string): string {
1843
1850
  //#region src/config/recipes/pr-hygiene.ts
1844
1851
  const prHygieneRecipe = {
1845
1852
  id: "pr-hygiene",
1853
+ requiresChecksPermission: true,
1846
1854
  title: "PR Hygiene",
1847
1855
  description: "Change request hygiene checks for tests, docs, lockfiles, and size.",
1848
1856
  sourceTools: ["Danger JS"],
@@ -1854,7 +1862,7 @@ export default definePipr((pipr) => {
1854
1862
  provider: "deepseek",
1855
1863
  model: "deepseek-v4-pro",
1856
1864
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
1857
- options: { thinking: "medium" },
1865
+ thinking: "medium",
1858
1866
  });
1859
1867
 
1860
1868
  pipr.config({ publication: { maxInlineComments: 5 } });
@@ -2002,6 +2010,7 @@ function policyTable(checks: PolicyCheck[]): string {
2002
2010
  //#region src/config/recipes/quality-gate.ts
2003
2011
  const qualityGateRecipe = {
2004
2012
  id: "quality-gate",
2013
+ requiresChecksPermission: true,
2005
2014
  title: "Quality Gate",
2006
2015
  description: "Required review check that fails on blocking correctness and test risks.",
2007
2016
  sourceTools: ["SonarQube", "Snyk"],
@@ -2013,7 +2022,7 @@ export default definePipr((pipr) => {
2013
2022
  provider: "deepseek",
2014
2023
  model: "deepseek-v4-pro",
2015
2024
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
2016
- options: { thinking: "high" },
2025
+ thinking: "high",
2017
2026
  });
2018
2027
 
2019
2028
  pipr.config({
@@ -2294,7 +2303,11 @@ const structuredReviewRecipe = {
2294
2303
  "Greptile"
2295
2304
  ],
2296
2305
  configTs: `import { definePipr, z } from "@usepipr/sdk";
2297
- import type { ReviewFinding } from "@usepipr/sdk";
2306
+ import type {
2307
+ DefaultReviewSummaryManifest,
2308
+ DiffManifest,
2309
+ ReviewFinding,
2310
+ } from "@usepipr/sdk";
2298
2311
 
2299
2312
  type ReviewSummary = {
2300
2313
  headline: string;
@@ -2305,7 +2318,7 @@ type ReviewSummary = {
2305
2318
  };
2306
2319
 
2307
2320
  const categorizedFindingSchema = z.strictObject({
2308
- title: z.string().regex(/^[^\\r\\n]+$/),
2321
+ title: z.string().min(1).max(160),
2309
2322
  severity: z.enum(["critical", "high", "medium", "low"]),
2310
2323
  category: z.enum([
2311
2324
  "correctness",
@@ -2316,24 +2329,121 @@ const categorizedFindingSchema = z.strictObject({
2316
2329
  "maintainability",
2317
2330
  "documentation",
2318
2331
  ]),
2319
- rationale: z.string(),
2320
- body: z.string(),
2321
- path: z.string(),
2322
- rangeId: z.string(),
2332
+ rationale: z.string().min(1).max(1200),
2333
+ body: z.string().min(1).max(700),
2334
+ path: z.string().min(1),
2335
+ rangeId: z.string().min(1),
2323
2336
  side: z.enum(["RIGHT", "LEFT"]),
2324
2337
  startLine: z.number().int().positive(),
2325
2338
  endLine: z.number().int().positive(),
2326
- suggestedFix: z.string().optional(),
2339
+ suggestedFix: z.string().min(1).optional(),
2327
2340
  });
2341
+ type CategorizedFinding = ReviewFinding & {
2342
+ title: string;
2343
+ severity: "critical" | "high" | "medium" | "low";
2344
+ category:
2345
+ | "correctness"
2346
+ | "security"
2347
+ | "reliability"
2348
+ | "performance"
2349
+ | "test-coverage"
2350
+ | "maintainability"
2351
+ | "documentation";
2352
+ rationale: string;
2353
+ };
2328
2354
 
2329
2355
  const reviewSummarySchema = z.strictObject({
2330
- headline: z.string(),
2331
- changeSummary: z.array(z.string()).min(1).max(4),
2356
+ headline: z.string().min(1).max(160),
2357
+ changeSummary: z.array(z.string().min(1).max(500)).min(1).max(4),
2332
2358
  riskLevel: z.enum(["low", "medium", "high"]),
2333
- riskSummary: z.string(),
2334
- reviewerFocus: z.array(z.string()).max(4),
2359
+ riskSummary: z.string().min(1).max(500),
2360
+ reviewerFocus: z.array(z.string().min(1).max(500)).max(4),
2335
2361
  });
2336
2362
 
2363
+ const severityRank = { critical: 0, high: 1, medium: 2, low: 3 } as const;
2364
+
2365
+ function hasCommentableAnchor(
2366
+ finding: ReviewFinding,
2367
+ manifest: DiffManifest,
2368
+ ): boolean {
2369
+ const range = manifest.files
2370
+ .find((file) => file.path === finding.path)
2371
+ ?.commentableRanges.find(
2372
+ (candidate) =>
2373
+ candidate.id === finding.rangeId &&
2374
+ candidate.path === finding.path &&
2375
+ candidate.side === finding.side,
2376
+ );
2377
+ return Boolean(
2378
+ range &&
2379
+ finding.startLine <= finding.endLine &&
2380
+ finding.startLine >= range.startLine &&
2381
+ finding.endLine <= range.endLine,
2382
+ );
2383
+ }
2384
+
2385
+ function deduplicateFindings(findings: CategorizedFinding[]): CategorizedFinding[] {
2386
+ const seen = new Set<string>();
2387
+ return findings.filter((finding) => {
2388
+ const key = JSON.stringify([
2389
+ finding.path,
2390
+ finding.rangeId,
2391
+ finding.side,
2392
+ finding.startLine,
2393
+ finding.endLine,
2394
+ finding.body,
2395
+ ]);
2396
+ if (seen.has(key)) {
2397
+ return false;
2398
+ }
2399
+ seen.add(key);
2400
+ return true;
2401
+ });
2402
+ }
2403
+
2404
+ function summaryManifest(manifest: DiffManifest): DefaultReviewSummaryManifest {
2405
+ const files: DefaultReviewSummaryManifest["files"][number][] = [];
2406
+ let serializedCharacters = 0;
2407
+
2408
+ for (const file of manifest.files) {
2409
+ const projected = {
2410
+ path: file.path.slice(0, 1_000),
2411
+ ...(file.previousPath
2412
+ ? { previousPath: file.previousPath.slice(0, 1_000) }
2413
+ : {}),
2414
+ status: file.status,
2415
+ ...(file.language ? { language: file.language.slice(0, 100) } : {}),
2416
+ additions: file.additions,
2417
+ deletions: file.deletions,
2418
+ ...(file.changedSymbols?.length
2419
+ ? {
2420
+ changedSymbols: file.changedSymbols
2421
+ .slice(0, 20)
2422
+ .map((symbol) => symbol.slice(0, 200)),
2423
+ }
2424
+ : {}),
2425
+ ...(file.excludedReason
2426
+ ? { excludedReason: file.excludedReason.slice(0, 500) }
2427
+ : {}),
2428
+ };
2429
+ const projectedCharacters = JSON.stringify(projected).length;
2430
+ if (serializedCharacters + projectedCharacters > 40_000) {
2431
+ continue;
2432
+ }
2433
+ files.push(projected);
2434
+ serializedCharacters += projectedCharacters;
2435
+ }
2436
+
2437
+ return {
2438
+ baseSha: manifest.baseSha,
2439
+ headSha: manifest.headSha,
2440
+ mergeBaseSha: manifest.mergeBaseSha,
2441
+ fileCount: manifest.files.length,
2442
+ omittedFileCount: manifest.files.length - files.length,
2443
+ files,
2444
+ };
2445
+ }
2446
+
2337
2447
  function escapeInlineCommentHtml(value: string): string {
2338
2448
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2339
2449
  }
@@ -2343,21 +2453,33 @@ export default definePipr((pipr) => {
2343
2453
  provider: "deepseek",
2344
2454
  model: "deepseek-v4-pro",
2345
2455
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
2346
- options: { thinking: "high" },
2456
+ thinking: "high",
2347
2457
  });
2348
2458
 
2349
- pipr.config({ publication: { maxInlineComments: 8 } });
2459
+ pipr.config({
2460
+ publication: {
2461
+ maxInlineComments: 8,
2462
+ autoResolve: {
2463
+ enabled: true,
2464
+ synchronize: true,
2465
+ userReplies: {
2466
+ enabled: true,
2467
+ respondWhenStillValid: true,
2468
+ allowedActors: "write",
2469
+ },
2470
+ },
2471
+ },
2472
+ });
2350
2473
 
2351
- const reviewOutput = pipr.schema({
2474
+ const findingsOutput = pipr.schema({
2352
2475
  id: "review/categorized-findings",
2353
2476
  schema: z.strictObject({
2354
- summary: reviewSummarySchema,
2355
- findings: z.array(categorizedFindingSchema),
2477
+ inlineFindings: z.array(categorizedFindingSchema),
2356
2478
  }),
2357
2479
  });
2358
2480
 
2359
- const reviewer = pipr.agent({
2360
- name: "reviewer",
2481
+ const findingsReviewer = pipr.agent({
2482
+ name: "findings-reviewer",
2361
2483
  model,
2362
2484
  instructions: \`
2363
2485
  Review the change request diff for correctness, security, reliability,
@@ -2367,30 +2489,67 @@ export default definePipr((pipr) => {
2367
2489
  concrete non-blocking defects; and low for small but actionable issues. Each
2368
2490
  rationale must connect repository evidence to the defect and its concrete
2369
2491
  impact. Keep each finding title to one line. Put supporting evidence and
2370
- reasoning in rationale instead of appending it to the body.
2371
-
2372
- Make summary maintainer-facing and scannable: one concrete headline, one
2373
- to four behavior-focused change bullets, a risk level with rationale, and
2374
- reviewer focus only for useful human follow-up.
2492
+ reasoning in rationale instead of appending it to the body. Return no more
2493
+ than 20 findings for each diff shard.
2494
+ Never copy secret-looking literals into title, body, rationale, or
2495
+ suggestedFix. Describe only the secret kind and location.
2375
2496
  \`,
2376
- output: reviewOutput,
2497
+ output: findingsOutput,
2377
2498
  tools: pipr.tools.readOnly,
2378
2499
  retry: { invalidOutput: 1, transientFailure: 1 },
2379
2500
  timeout: "10m",
2380
2501
  prompt: () => "Review this change with severity and category metadata.",
2381
2502
  });
2382
2503
 
2504
+ const summaryOutput = pipr.schema({
2505
+ id: "review/rich-summary",
2506
+ schema: reviewSummarySchema,
2507
+ });
2508
+
2509
+ const summaryReviewer = pipr.agent({
2510
+ name: "summary-reviewer",
2511
+ model,
2512
+ instructions: \`
2513
+ Make the summary maintainer-facing and scannable: one concrete headline,
2514
+ one to four behavior-focused change bullets, a risk level with rationale,
2515
+ and reviewer focus only for useful human follow-up. Use the selected
2516
+ findings as evidence, but do not invent additional defects or copy
2517
+ secret-looking literals into any summary field.
2518
+ \`,
2519
+ output: summaryOutput,
2520
+ tools: pipr.tools.readOnly,
2521
+ retry: { invalidOutput: 1, transientFailure: 1 },
2522
+ timeout: "10m",
2523
+ prompt: ({ inlineFindings, manifestSummary }) =>
2524
+ pipr.prompt\`
2525
+ Summarize this change using the selected findings.
2526
+
2527
+ \${pipr.section("Scoped compressed manifest", pipr.json(manifestSummary, { maxCharacters: 60_000 }))}
2528
+
2529
+ \${pipr.section("Selected findings", pipr.json(inlineFindings, { maxCharacters: 60_000 }))}
2530
+ \`,
2531
+ });
2532
+
2383
2533
  const task = pipr.task({
2384
2534
  name: "review",
2385
2535
  async run(ctx) {
2386
2536
  const manifest = await ctx.change.diffManifest({ compressed: true });
2387
- const result = await ctx.pi.run(reviewer, { manifest });
2388
- const inlineFindings: ReviewFinding[] = result.findings.map((finding) => {
2537
+ const result = await ctx.pi.run(findingsReviewer, { manifest });
2538
+ const selectedFindings = deduplicateFindings(
2539
+ result.inlineFindings.filter((finding) => hasCommentableAnchor(finding, manifest)),
2540
+ )
2541
+ .sort((left, right) => severityRank[left.severity] - severityRank[right.severity])
2542
+ .slice(0, 8);
2543
+ const summary = await ctx.pi.run(summaryReviewer, {
2544
+ manifestSummary: summaryManifest(manifest),
2545
+ inlineFindings: selectedFindings,
2546
+ });
2547
+ const inlineFindings: ReviewFinding[] = selectedFindings.map((finding) => {
2389
2548
  const severity = finding.severity.charAt(0).toUpperCase() + finding.severity.slice(1);
2390
2549
  const category = finding.category.replaceAll("-", " ");
2391
2550
  return {
2392
2551
  body: [
2393
- \`**\${severity} \${category}:** \${escapeInlineCommentHtml(finding.title)}\`,
2552
+ \`**\${severity} \${category}:** \${escapeInlineCommentHtml(lineText(finding.title))}\`,
2394
2553
  "",
2395
2554
  escapeInlineCommentHtml(finding.body),
2396
2555
  "",
@@ -2413,17 +2572,17 @@ export default definePipr((pipr) => {
2413
2572
  main: [
2414
2573
  "## Summary",
2415
2574
  "",
2416
- \`**\${result.summary.headline}**\`,
2575
+ \`**\${lineText(summary.headline)}**\`,
2417
2576
  "",
2418
- summaryTable(result.summary),
2577
+ summaryTable(summary),
2419
2578
  "",
2420
2579
  "## What Changed",
2421
2580
  "",
2422
- bulletList(result.summary.changeSummary, "No changed behavior summarized."),
2581
+ bulletList(summary.changeSummary, "No changed behavior summarized."),
2423
2582
  "",
2424
2583
  "## Reviewer Focus",
2425
2584
  "",
2426
- bulletList(result.summary.reviewerFocus, "No special reviewer focus."),
2585
+ bulletList(summary.reviewerFocus, "No special reviewer focus."),
2427
2586
  "",
2428
2587
  ].join("\\n"),
2429
2588
  inlineFindings,
@@ -2457,7 +2616,7 @@ function labelValue(value: string): string {
2457
2616
  }
2458
2617
 
2459
2618
  function lineText(value: string): string {
2460
- return value.replaceAll("\\n", " ").trim();
2619
+ return value.replace(/\\r\\n?|\\n/g, " ").trim();
2461
2620
  }
2462
2621
 
2463
2622
  function tableCell(value: string): string {
@@ -2469,6 +2628,7 @@ function tableCell(value: string): string {
2469
2628
  //#region src/config/recipes/security-sast.ts
2470
2629
  const securitySastRecipe = {
2471
2630
  id: "security-sast",
2631
+ requiresChecksPermission: true,
2472
2632
  title: "Security SAST",
2473
2633
  description: "Security review with custom severity and category output.",
2474
2634
  sourceTools: [
@@ -2504,7 +2664,7 @@ export default definePipr((pipr) => {
2504
2664
  provider: "deepseek",
2505
2665
  model: "deepseek-v4-pro",
2506
2666
  apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
2507
- options: { thinking: "high" },
2667
+ thinking: "high",
2508
2668
  });
2509
2669
 
2510
2670
  const securityOutput = pipr.jsonSchema<SecurityReview>({
@@ -2843,6 +3003,9 @@ function officialInitRecipeFiles(recipe) {
2843
3003
  function officialInitRecipeWorkflowEnvSecrets(recipe) {
2844
3004
  return resolveOfficialInitRecipe(recipe).workflowEnvSecrets ?? [];
2845
3005
  }
3006
+ function officialInitRecipeRequiresChecksPermission(recipe) {
3007
+ return resolveOfficialInitRecipe(recipe).requiresChecksPermission ?? false;
3008
+ }
2846
3009
  function resolveOfficialInitRecipe(recipe) {
2847
3010
  const id = recipe ?? "default-review";
2848
3011
  if (!isOfficialInitRecipeId(id)) throw new Error(`Unsupported pipr init recipe '${id}'. Supported recipes: ${supportedOfficialInitRecipes.join(", ")}.`);
@@ -2853,7 +3016,7 @@ function isOfficialInitRecipeId(recipe) {
2853
3016
  }
2854
3017
  //#endregion
2855
3018
  //#region src/config/official-github-workflow.ts
2856
- const defaultWorkflowActionRef = "somus/pipr@v0.5.0";
3019
+ const defaultWorkflowActionRef = "somus/pipr@v0.6.0";
2857
3020
  /** Internal shared renderer for `pipr init` and generated recipe docs. */
2858
3021
  function renderOfficialGithubWorkflow(options = {}) {
2859
3022
  const relativeConfigDir = options.relativeConfigDir ?? ".pipr";
@@ -2862,25 +3025,19 @@ function renderOfficialGithubWorkflow(options = {}) {
2862
3025
  "",
2863
3026
  "on:",
2864
3027
  " pull_request:",
3028
+ " types: [opened, synchronize, reopened, ready_for_review]",
2865
3029
  " issue_comment:",
2866
3030
  " types: [created]",
2867
3031
  " pull_request_review_comment:",
2868
3032
  " types: [created]",
2869
3033
  "",
2870
3034
  "permissions:",
2871
- " contents: write",
3035
+ " contents: read",
2872
3036
  " pull-requests: write",
2873
- " issues: write",
2874
- " checks: write",
2875
- "",
2876
- "jobs:",
2877
- " review:",
2878
- " runs-on: ubuntu-latest",
2879
- " steps:",
2880
- " - uses: actions/checkout@v6",
2881
- " with:",
2882
- " fetch-depth: 0"
3037
+ " issues: write"
2883
3038
  ];
3039
+ if (officialInitRecipeRequiresChecksPermission(options.recipe)) lines.push(" checks: write");
3040
+ lines.push("", "jobs:", " review:", " runs-on: ubuntu-latest", " steps:", " - uses: actions/checkout@v6", " with:", " fetch-depth: 0");
2884
3041
  if (!options.minimal) lines.push(" - uses: actions/cache@v4", " with:", " path: /home/runner/work/_temp/_github_home/.bun/install/cache", ` key: pipr-bun-${githubExpression(`hashFiles('${relativeConfigDir}/bun.lock')`)}`);
2885
3042
  lines.push(` - uses: ${defaultWorkflowActionRef}${options.includeReleasePleaseVersionMarker ? " # x-release-please-version" : ""}`, " env:", ` DEEPSEEK_API_KEY: ${githubExpression("secrets.DEEPSEEK_API_KEY")}`, ` GITHUB_TOKEN: ${githubExpression("github.token")}`);
2886
3043
  for (const secret of officialInitRecipeWorkflowEnvSecrets(options.recipe)) lines.push(` ${secret.env}: ${githubExpression(`secrets.${secret.secret}`)}`);
@@ -2898,4 +3055,4 @@ function githubExpression(expression) {
2898
3055
  //#endregion
2899
3056
  export { officialInitRecipeWorkflowEnvSecrets as a, officialInitRecipeFiles as i, listOfficialInitRecipes as n, supportedOfficialInitRecipes as o, officialInitRecipeConfigTs as r, renderOfficialGithubWorkflow as t };
2900
3057
 
2901
- //# sourceMappingURL=official-github-workflow-DNzV5Z70.mjs.map
3058
+ //# sourceMappingURL=official-github-workflow-B3yeaCgq.mjs.map