@usepipr/runtime 0.4.3 → 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.
- package/LICENSE +21 -0
- package/README.md +8 -5
- package/dist/{commands-DA0Ij4Di.d.mts → commands-BHXh7P28.d.mts} +2 -2
- package/dist/{commands-DA0Ij4Di.d.mts.map → commands-BHXh7P28.d.mts.map} +1 -1
- package/dist/{commands-B9qNW4pk.mjs → commands-GWHklzvq.mjs} +1612 -466
- package/dist/commands-GWHklzvq.mjs.map +1 -0
- package/dist/index.d.mts +17 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +117 -15
- package/dist/index.mjs.map +1 -1
- package/dist/internal/action-result.d.mts +4 -2
- package/dist/internal/action-result.d.mts.map +1 -1
- package/dist/internal/action-result.mjs +28 -12
- package/dist/internal/action-result.mjs.map +1 -1
- package/dist/internal/docs.d.mts +1 -1
- package/dist/internal/docs.d.mts.map +1 -1
- package/dist/internal/docs.mjs +1 -1
- package/dist/internal/pipr-result.d.mts +24 -0
- package/dist/internal/pipr-result.d.mts.map +1 -0
- package/dist/internal/pipr-result.mjs +126 -0
- package/dist/internal/pipr-result.mjs.map +1 -0
- package/dist/internal/testing.d.mts +12 -11
- package/dist/internal/testing.d.mts.map +1 -1
- package/dist/internal/testing.mjs +1 -1
- package/dist/main-comment-envelope-DFirHYhW.mjs +69 -0
- package/dist/main-comment-envelope-DFirHYhW.mjs.map +1 -0
- package/dist/{official-github-workflow-Bjlu-FL3.mjs → official-github-workflow-B3yeaCgq.mjs} +421 -114
- package/dist/official-github-workflow-B3yeaCgq.mjs.map +1 -0
- package/dist/pi/runtime-tools-extension.d.mts.map +1 -1
- package/dist/pi/runtime-tools-extension.mjs +128 -2
- package/dist/pi/runtime-tools-extension.mjs.map +1 -1
- package/dist/{recipes-w72EpAOi.d.mts → recipes-C1kxJCgQ.d.mts} +2 -1
- package/dist/recipes-C1kxJCgQ.d.mts.map +1 -0
- package/dist/runtime-tools-core-BF8rSeNS.mjs +476 -0
- package/dist/runtime-tools-core-BF8rSeNS.mjs.map +1 -0
- package/dist/{types-BAis_Lwm.d.mts → types-w22wbdo9.d.mts} +72 -19
- package/dist/types-w22wbdo9.d.mts.map +1 -0
- package/package.json +16 -8
- package/dist/commands-B9qNW4pk.mjs.map +0 -1
- package/dist/official-github-workflow-Bjlu-FL3.mjs.map +0 -1
- package/dist/recipes-w72EpAOi.d.mts.map +0 -1
- package/dist/runtime-tools-core-D8_WsbL_.mjs +0 -179
- package/dist/runtime-tools-core-D8_WsbL_.mjs.map +0 -1
- package/dist/types-BAis_Lwm.d.mts.map +0 -1
package/dist/{official-github-workflow-Bjlu-FL3.mjs → official-github-workflow-B3yeaCgq.mjs}
RENAMED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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 =
|
|
@@ -281,7 +287,7 @@ export default definePipr((pipr) => {
|
|
|
281
287
|
"| --- | ---: |",
|
|
282
288
|
\`| Inline findings | \${result.inlineFindings.length} |\`,
|
|
283
289
|
"",
|
|
284
|
-
context.
|
|
290
|
+
context.run.trigger === "local" ? localInlineFindingSummary : inlineFindingSummary,
|
|
285
291
|
].join("\\n"),
|
|
286
292
|
inlineFindings: result.inlineFindings,
|
|
287
293
|
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1142
|
+
thinking: "medium",
|
|
1136
1143
|
});
|
|
1137
1144
|
|
|
1138
1145
|
const specialistPrompt = (input: { manifest: unknown; focus: string }) => pipr.prompt\`
|
|
@@ -1222,17 +1229,17 @@ export default definePipr((pipr) => {
|
|
|
1222
1229
|
const pluginToolReviewRecipe = {
|
|
1223
1230
|
id: "plugin-tool-review",
|
|
1224
1231
|
title: "Plugin Tool Review",
|
|
1225
|
-
description: "Typed R2-backed memory plugin
|
|
1232
|
+
description: "Typed R2-backed memory plugin with search-only review and explicit maintainer curation.",
|
|
1226
1233
|
sourceTools: ["Cloudflare R2", "Reviewer memory"],
|
|
1227
1234
|
configTs: `import { definePipr } from "@usepipr/sdk";
|
|
1228
|
-
import { r2MemoryPlugin } from "./r2-memory";
|
|
1235
|
+
import { memoryLimits, r2MemoryPlugin } from "./r2-memory";
|
|
1229
1236
|
|
|
1230
1237
|
export default definePipr((pipr) => {
|
|
1231
1238
|
const model = pipr.model({
|
|
1232
1239
|
provider: "deepseek",
|
|
1233
1240
|
model: "deepseek-v4-pro",
|
|
1234
1241
|
apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
|
|
1235
|
-
|
|
1242
|
+
thinking: "high",
|
|
1236
1243
|
});
|
|
1237
1244
|
|
|
1238
1245
|
const memory = pipr.use(
|
|
@@ -1277,8 +1284,44 @@ export default definePipr((pipr) => {
|
|
|
1277
1284
|
},
|
|
1278
1285
|
});
|
|
1279
1286
|
|
|
1287
|
+
const rememberTask = pipr.task<{ lesson: string }>({
|
|
1288
|
+
name: "remember-review-memory",
|
|
1289
|
+
async run(ctx, input) {
|
|
1290
|
+
if (!ctx.command) {
|
|
1291
|
+
throw new Error("remember-review-memory is a command-only task");
|
|
1292
|
+
}
|
|
1293
|
+
const lesson = input.lesson.trim();
|
|
1294
|
+
if (lesson.length === 0) {
|
|
1295
|
+
await ctx.command.reply("Usage: @pipr remember <lesson...>");
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (lesson.length > memoryLimits.bodyCharacters) {
|
|
1299
|
+
await ctx.command.reply(
|
|
1300
|
+
"Reviewer memory must be " + memoryLimits.bodyCharacters + " characters or fewer.",
|
|
1301
|
+
);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const stored = await memory.curate(
|
|
1305
|
+
{
|
|
1306
|
+
subject: lesson.slice(0, memoryLimits.subjectCharacters),
|
|
1307
|
+
body: lesson,
|
|
1308
|
+
tags: ["maintainer-curated"],
|
|
1309
|
+
},
|
|
1310
|
+
ctx,
|
|
1311
|
+
);
|
|
1312
|
+
await ctx.command.reply("Stored reviewer memory \`" + stored.id + "\`.");
|
|
1313
|
+
},
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1280
1316
|
pipr.on.changeRequest({ actions: ["opened", "updated"], task });
|
|
1281
1317
|
pipr.command({ pattern: "@pipr memory-review", permission: "write", task });
|
|
1318
|
+
pipr.command({
|
|
1319
|
+
pattern: "@pipr remember <lesson...>",
|
|
1320
|
+
permission: "write",
|
|
1321
|
+
description: "Store an explicit maintainer-curated reviewer lesson.",
|
|
1322
|
+
parse: (args) => ({ lesson: args.lesson ?? "" }),
|
|
1323
|
+
task: rememberTask,
|
|
1324
|
+
});
|
|
1282
1325
|
});
|
|
1283
1326
|
`,
|
|
1284
1327
|
files: [{
|
|
@@ -1286,22 +1329,55 @@ export default definePipr((pipr) => {
|
|
|
1286
1329
|
contents: `import { S3Client } from "bun";
|
|
1287
1330
|
import { definePlugin, type SecretRef, type TaskContext, z } from "@usepipr/sdk";
|
|
1288
1331
|
|
|
1332
|
+
export const memoryLimits = {
|
|
1333
|
+
subjectCharacters: 120,
|
|
1334
|
+
bodyCharacters: 4000,
|
|
1335
|
+
tagCount: 12,
|
|
1336
|
+
tagCharacters: 50,
|
|
1337
|
+
queryCharacters: 500,
|
|
1338
|
+
resultDefault: 5,
|
|
1339
|
+
resultMinimum: 1,
|
|
1340
|
+
resultMaximum: 20,
|
|
1341
|
+
searchObjectMaximum: 2000,
|
|
1342
|
+
} as const;
|
|
1343
|
+
|
|
1344
|
+
const memorySource = z.strictObject({
|
|
1345
|
+
kind: z.enum(["maintainer-command", "agent-tool"]),
|
|
1346
|
+
runId: z.string().min(1).max(200),
|
|
1347
|
+
platform: z.string().min(1).max(50),
|
|
1348
|
+
changeRequestNumber: z.number().int().nonnegative().optional(),
|
|
1349
|
+
headSha: z.string().min(1).max(200),
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1289
1352
|
const memoryItem = z.strictObject({
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1353
|
+
id: z.string().uuid().optional(),
|
|
1354
|
+
subject: z.string().trim().min(1).max(memoryLimits.subjectCharacters),
|
|
1355
|
+
body: z.string().trim().min(1).max(memoryLimits.bodyCharacters),
|
|
1356
|
+
tags: z
|
|
1357
|
+
.array(z.string().trim().min(1).max(memoryLimits.tagCharacters))
|
|
1358
|
+
.max(memoryLimits.tagCount)
|
|
1359
|
+
.optional(),
|
|
1360
|
+
source: memorySource.optional(),
|
|
1361
|
+
updatedAt: z.string().max(50).optional(),
|
|
1294
1362
|
});
|
|
1295
1363
|
|
|
1296
1364
|
const memorySearchInput = z.strictObject({
|
|
1297
|
-
query: z.string(),
|
|
1298
|
-
limit: z
|
|
1365
|
+
query: z.string().trim().min(1).max(memoryLimits.queryCharacters),
|
|
1366
|
+
limit: z
|
|
1367
|
+
.number()
|
|
1368
|
+
.int()
|
|
1369
|
+
.min(memoryLimits.resultMinimum)
|
|
1370
|
+
.max(memoryLimits.resultMaximum)
|
|
1371
|
+
.optional(),
|
|
1299
1372
|
});
|
|
1300
1373
|
|
|
1301
1374
|
const memoryStoreInput = z.strictObject({
|
|
1302
|
-
subject: z.string(),
|
|
1303
|
-
body: z.string(),
|
|
1304
|
-
tags: z
|
|
1375
|
+
subject: z.string().trim().min(1).max(memoryLimits.subjectCharacters),
|
|
1376
|
+
body: z.string().trim().min(1).max(memoryLimits.bodyCharacters),
|
|
1377
|
+
tags: z
|
|
1378
|
+
.array(z.string().trim().min(1).max(memoryLimits.tagCharacters))
|
|
1379
|
+
.max(memoryLimits.tagCount)
|
|
1380
|
+
.optional(),
|
|
1305
1381
|
});
|
|
1306
1382
|
|
|
1307
1383
|
type MemoryItem = ReturnType<typeof memoryItem.parse>;
|
|
@@ -1328,6 +1404,7 @@ export function r2MemoryPlugin(options: R2MemoryOptions) {
|
|
|
1328
1404
|
id: "memory/search-output",
|
|
1329
1405
|
schema: z.strictObject({
|
|
1330
1406
|
memories: z.array(memoryItem),
|
|
1407
|
+
skippedObjects: z.number().int().nonnegative(),
|
|
1331
1408
|
}),
|
|
1332
1409
|
});
|
|
1333
1410
|
const storeInput = pipr.schema({
|
|
@@ -1339,6 +1416,7 @@ export function r2MemoryPlugin(options: R2MemoryOptions) {
|
|
|
1339
1416
|
schema: z.strictObject({
|
|
1340
1417
|
stored: z.boolean(),
|
|
1341
1418
|
key: z.string(),
|
|
1419
|
+
id: z.string().uuid(),
|
|
1342
1420
|
}),
|
|
1343
1421
|
});
|
|
1344
1422
|
|
|
@@ -1361,12 +1439,15 @@ export function r2MemoryPlugin(options: R2MemoryOptions) {
|
|
|
1361
1439
|
input: storeInput,
|
|
1362
1440
|
output: storeOutput,
|
|
1363
1441
|
async run({ input, ctx, signal }) {
|
|
1364
|
-
return await storeMemory(input, ctx, options, signal);
|
|
1442
|
+
return await storeMemory(input, ctx, options, "agent-tool", signal);
|
|
1365
1443
|
},
|
|
1366
1444
|
toModelOutput(output) {
|
|
1367
1445
|
return output;
|
|
1368
1446
|
},
|
|
1369
1447
|
}),
|
|
1448
|
+
curate(input: MemoryStoreInput, ctx: TaskContext, signal?: AbortSignal) {
|
|
1449
|
+
return storeMemory(input, ctx, options, "maintainer-command", signal);
|
|
1450
|
+
},
|
|
1370
1451
|
};
|
|
1371
1452
|
});
|
|
1372
1453
|
}
|
|
@@ -1376,29 +1457,52 @@ async function searchMemory(
|
|
|
1376
1457
|
ctx: TaskContext,
|
|
1377
1458
|
options: R2MemoryOptions,
|
|
1378
1459
|
signal?: AbortSignal,
|
|
1379
|
-
): Promise<{ memories: MemoryItem[] }> {
|
|
1460
|
+
): Promise<{ memories: MemoryItem[]; skippedObjects: number }> {
|
|
1380
1461
|
signal?.throwIfAborted();
|
|
1381
1462
|
const bucket = r2Bucket(ctx, options);
|
|
1382
|
-
const listed = await bucket.list({ prefix: memoryPrefix(ctx, options) + "/", maxKeys: 200 });
|
|
1383
1463
|
const memories: MemoryItem[] = [];
|
|
1464
|
+
let continuationToken: string | undefined;
|
|
1465
|
+
let scannedObjects = 0;
|
|
1466
|
+
let skippedObjects = 0;
|
|
1384
1467
|
|
|
1385
|
-
|
|
1468
|
+
do {
|
|
1386
1469
|
signal?.throwIfAborted();
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1470
|
+
const listed = await bucket.list({
|
|
1471
|
+
prefix: memoryPrefix(ctx, options) + "/",
|
|
1472
|
+
maxKeys: 200,
|
|
1473
|
+
continuationToken,
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
const objects = (listed.contents ?? []).slice(
|
|
1477
|
+
0,
|
|
1478
|
+
memoryLimits.searchObjectMaximum - scannedObjects,
|
|
1479
|
+
);
|
|
1480
|
+
scannedObjects += objects.length;
|
|
1481
|
+
for (const object of objects) {
|
|
1482
|
+
signal?.throwIfAborted();
|
|
1483
|
+
try {
|
|
1484
|
+
const value = memoryItem.parse(await bucket.file(object.key).json());
|
|
1485
|
+
if (matchesMemory(value, input.query)) {
|
|
1486
|
+
memories.push(value);
|
|
1487
|
+
}
|
|
1488
|
+
} catch {
|
|
1489
|
+
// Exclude malformed or concurrently deleted objects and report the count.
|
|
1490
|
+
skippedObjects += 1;
|
|
1391
1491
|
}
|
|
1392
|
-
} catch {
|
|
1393
|
-
// Ignore malformed or concurrently deleted memory objects.
|
|
1394
1492
|
}
|
|
1395
|
-
}
|
|
1396
1493
|
|
|
1397
|
-
|
|
1494
|
+
continuationToken = listed.isTruncated ? listed.nextContinuationToken : undefined;
|
|
1495
|
+
} while (continuationToken && scannedObjects < memoryLimits.searchObjectMaximum);
|
|
1496
|
+
|
|
1497
|
+
const limit = Math.min(
|
|
1498
|
+
Math.max(Math.trunc(input.limit ?? memoryLimits.resultDefault), memoryLimits.resultMinimum),
|
|
1499
|
+
memoryLimits.resultMaximum,
|
|
1500
|
+
);
|
|
1398
1501
|
return {
|
|
1399
1502
|
memories: memories
|
|
1400
1503
|
.sort((left, right) => (right.updatedAt ?? "").localeCompare(left.updatedAt ?? ""))
|
|
1401
1504
|
.slice(0, limit),
|
|
1505
|
+
skippedObjects,
|
|
1402
1506
|
};
|
|
1403
1507
|
}
|
|
1404
1508
|
|
|
@@ -1406,14 +1510,53 @@ async function storeMemory(
|
|
|
1406
1510
|
input: MemoryStoreInput,
|
|
1407
1511
|
ctx: TaskContext,
|
|
1408
1512
|
options: R2MemoryOptions,
|
|
1513
|
+
sourceKind: "maintainer-command" | "agent-tool",
|
|
1409
1514
|
signal?: AbortSignal,
|
|
1410
|
-
): Promise<{ stored: boolean; key: string }> {
|
|
1515
|
+
): Promise<{ stored: boolean; key: string; id: string }> {
|
|
1411
1516
|
signal?.throwIfAborted();
|
|
1412
1517
|
const bucket = r2Bucket(ctx, options);
|
|
1413
|
-
const
|
|
1414
|
-
const
|
|
1518
|
+
const parsedInput = memoryStoreInput.parse(input);
|
|
1519
|
+
const curatedKey =
|
|
1520
|
+
sourceKind === "maintainer-command"
|
|
1521
|
+
? memoryPrefix(ctx, options) +
|
|
1522
|
+
"/maintainer-command/" +
|
|
1523
|
+
encodeURIComponent(ctx.run.id) +
|
|
1524
|
+
".json"
|
|
1525
|
+
: undefined;
|
|
1526
|
+
|
|
1527
|
+
const id = curatedKey ? await stableCommandMemoryId(ctx.run.id) : crypto.randomUUID();
|
|
1528
|
+
const entry = memoryItem.parse({
|
|
1529
|
+
...parsedInput,
|
|
1530
|
+
id,
|
|
1531
|
+
source: {
|
|
1532
|
+
kind: sourceKind,
|
|
1533
|
+
runId: ctx.run.id,
|
|
1534
|
+
platform: ctx.platform.id,
|
|
1535
|
+
changeRequestNumber: ctx.change.number,
|
|
1536
|
+
headSha: ctx.change.head.sha,
|
|
1537
|
+
},
|
|
1538
|
+
updatedAt: new Date().toISOString(),
|
|
1539
|
+
});
|
|
1540
|
+
const key = curatedKey ?? memoryKey(id, parsedInput.subject, ctx, options);
|
|
1415
1541
|
await bucket.write(key, JSON.stringify(entry, null, 2), { type: "application/json" });
|
|
1416
|
-
return { stored: true, key };
|
|
1542
|
+
return { stored: true, key, id };
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
async function stableCommandMemoryId(runId: string): Promise<string> {
|
|
1546
|
+
const digest = new Uint8Array(
|
|
1547
|
+
await crypto.subtle.digest(
|
|
1548
|
+
"SHA-256",
|
|
1549
|
+
new TextEncoder().encode("pipr-memory/maintainer-command/" + runId),
|
|
1550
|
+
),
|
|
1551
|
+
);
|
|
1552
|
+
digest[6] = (digest[6]! & 0x0f) | 0x50;
|
|
1553
|
+
digest[8] = (digest[8]! & 0x3f) | 0x80;
|
|
1554
|
+
const hex = Array.from(digest.slice(0, 16), (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
1555
|
+
"",
|
|
1556
|
+
);
|
|
1557
|
+
return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20)].join(
|
|
1558
|
+
"-",
|
|
1559
|
+
);
|
|
1417
1560
|
}
|
|
1418
1561
|
|
|
1419
1562
|
function r2Bucket(ctx: TaskContext, options: R2MemoryOptions): S3Client {
|
|
@@ -1444,14 +1587,26 @@ function cleanPathSegment(value: string): string {
|
|
|
1444
1587
|
);
|
|
1445
1588
|
}
|
|
1446
1589
|
|
|
1447
|
-
function memoryKey(
|
|
1590
|
+
function memoryKey(
|
|
1591
|
+
id: string,
|
|
1592
|
+
subject: string,
|
|
1593
|
+
ctx: TaskContext,
|
|
1594
|
+
options: R2MemoryOptions,
|
|
1595
|
+
): string {
|
|
1448
1596
|
const slug = subject
|
|
1449
1597
|
.toLowerCase()
|
|
1450
1598
|
.replace(/[^a-z0-9]+/g, "-")
|
|
1451
1599
|
.replace(/^-|-$/g, "")
|
|
1452
1600
|
.slice(0, 60);
|
|
1453
1601
|
return (
|
|
1454
|
-
memoryPrefix(ctx, options) +
|
|
1602
|
+
memoryPrefix(ctx, options) +
|
|
1603
|
+
"/" +
|
|
1604
|
+
new Date().toISOString() +
|
|
1605
|
+
"-" +
|
|
1606
|
+
id +
|
|
1607
|
+
"-" +
|
|
1608
|
+
(slug || "memory") +
|
|
1609
|
+
".json"
|
|
1455
1610
|
);
|
|
1456
1611
|
}
|
|
1457
1612
|
|
|
@@ -1488,7 +1643,9 @@ function matchesMemory(item: MemoryItem, query: string): boolean {
|
|
|
1488
1643
|
|
|
1489
1644
|
This recipe uses Bun's S3-compatible client against Cloudflare R2. R2 credentials are declared with \`pipr.secret(...)\`, then resolved inside tool execution with \`ctx.secret(...)\`. The generated GitHub workflow maps \`PIPR_R2_MEMORY_BUCKET\`, \`PIPR_R2_MEMORY_ENDPOINT\`, \`PIPR_R2_MEMORY_ACCESS_KEY_ID\`, and \`PIPR_R2_MEMORY_SECRET_ACCESS_KEY\` repository secrets into matching runtime environment variables.
|
|
1490
1645
|
|
|
1491
|
-
R2 is object storage, not a search index. The sample
|
|
1646
|
+
R2 is object storage, not a search index. The sample paginates up to 2,000 JSON memory objects under \`prefix/repository-owner/repository-name\` and filters them locally, which keeps each search bounded and is enough for small reviewer-memory sets. Change \`prefix\` in \`.pipr/config.ts\` when multiple repositories share one bucket; Pipr still adds the repository scope below it. The generated defaults cap subjects at 120 characters, bodies at 4,000 characters, tags at 12 entries of 50 characters, queries at 500 characters, and results at 20. Existing entries without ids or provenance remain readable when they satisfy those bounds. Search results include \`skippedObjects\`, the number of malformed, unavailable, or over-limit objects excluded during that search, so upgrades do not hide incompatible entries without a diagnostic.
|
|
1647
|
+
|
|
1648
|
+
The generated reviewer treats memory as untrusted historical context and requires current repository evidence for findings. It only searches memory by default. A repository user with write permission can store one bounded, provenance-bearing lesson with \`@pipr remember <lesson...>\`; re-delivery of the same command run deterministically reuses its stored object and UUID. Full review summaries and human feedback are not persisted automatically; feedback collection, eval generation, scheduling, and proposal pull requests remain user-owned extensions.
|
|
1492
1649
|
|
|
1493
1650
|
`
|
|
1494
1651
|
};
|
|
@@ -1506,7 +1663,7 @@ export default definePipr((pipr) => {
|
|
|
1506
1663
|
provider: "deepseek",
|
|
1507
1664
|
model: "deepseek-v4-pro",
|
|
1508
1665
|
apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
|
|
1509
|
-
|
|
1666
|
+
thinking: "medium",
|
|
1510
1667
|
});
|
|
1511
1668
|
|
|
1512
1669
|
pipr.config({ publication: { maxInlineComments: 0 } });
|
|
@@ -1693,6 +1850,7 @@ function markdownFenceFor(value: string): string {
|
|
|
1693
1850
|
//#region src/config/recipes/pr-hygiene.ts
|
|
1694
1851
|
const prHygieneRecipe = {
|
|
1695
1852
|
id: "pr-hygiene",
|
|
1853
|
+
requiresChecksPermission: true,
|
|
1696
1854
|
title: "PR Hygiene",
|
|
1697
1855
|
description: "Change request hygiene checks for tests, docs, lockfiles, and size.",
|
|
1698
1856
|
sourceTools: ["Danger JS"],
|
|
@@ -1704,7 +1862,7 @@ export default definePipr((pipr) => {
|
|
|
1704
1862
|
provider: "deepseek",
|
|
1705
1863
|
model: "deepseek-v4-pro",
|
|
1706
1864
|
apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
|
|
1707
|
-
|
|
1865
|
+
thinking: "medium",
|
|
1708
1866
|
});
|
|
1709
1867
|
|
|
1710
1868
|
pipr.config({ publication: { maxInlineComments: 5 } });
|
|
@@ -1852,6 +2010,7 @@ function policyTable(checks: PolicyCheck[]): string {
|
|
|
1852
2010
|
//#region src/config/recipes/quality-gate.ts
|
|
1853
2011
|
const qualityGateRecipe = {
|
|
1854
2012
|
id: "quality-gate",
|
|
2013
|
+
requiresChecksPermission: true,
|
|
1855
2014
|
title: "Quality Gate",
|
|
1856
2015
|
description: "Required review check that fails on blocking correctness and test risks.",
|
|
1857
2016
|
sourceTools: ["SonarQube", "Snyk"],
|
|
@@ -1863,7 +2022,7 @@ export default definePipr((pipr) => {
|
|
|
1863
2022
|
provider: "deepseek",
|
|
1864
2023
|
model: "deepseek-v4-pro",
|
|
1865
2024
|
apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
|
|
1866
|
-
|
|
2025
|
+
thinking: "high",
|
|
1867
2026
|
});
|
|
1868
2027
|
|
|
1869
2028
|
pipr.config({
|
|
@@ -2144,7 +2303,11 @@ const structuredReviewRecipe = {
|
|
|
2144
2303
|
"Greptile"
|
|
2145
2304
|
],
|
|
2146
2305
|
configTs: `import { definePipr, z } from "@usepipr/sdk";
|
|
2147
|
-
import type {
|
|
2306
|
+
import type {
|
|
2307
|
+
DefaultReviewSummaryManifest,
|
|
2308
|
+
DiffManifest,
|
|
2309
|
+
ReviewFinding,
|
|
2310
|
+
} from "@usepipr/sdk";
|
|
2148
2311
|
|
|
2149
2312
|
type ReviewSummary = {
|
|
2150
2313
|
headline: string;
|
|
@@ -2155,7 +2318,7 @@ type ReviewSummary = {
|
|
|
2155
2318
|
};
|
|
2156
2319
|
|
|
2157
2320
|
const categorizedFindingSchema = z.strictObject({
|
|
2158
|
-
title: z.string().
|
|
2321
|
+
title: z.string().min(1).max(160),
|
|
2159
2322
|
severity: z.enum(["critical", "high", "medium", "low"]),
|
|
2160
2323
|
category: z.enum([
|
|
2161
2324
|
"correctness",
|
|
@@ -2166,24 +2329,121 @@ const categorizedFindingSchema = z.strictObject({
|
|
|
2166
2329
|
"maintainability",
|
|
2167
2330
|
"documentation",
|
|
2168
2331
|
]),
|
|
2169
|
-
rationale: z.string(),
|
|
2170
|
-
body: z.string(),
|
|
2171
|
-
path: z.string(),
|
|
2172
|
-
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),
|
|
2173
2336
|
side: z.enum(["RIGHT", "LEFT"]),
|
|
2174
2337
|
startLine: z.number().int().positive(),
|
|
2175
2338
|
endLine: z.number().int().positive(),
|
|
2176
|
-
suggestedFix: z.string().optional(),
|
|
2339
|
+
suggestedFix: z.string().min(1).optional(),
|
|
2177
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
|
+
};
|
|
2178
2354
|
|
|
2179
2355
|
const reviewSummarySchema = z.strictObject({
|
|
2180
|
-
headline: z.string(),
|
|
2181
|
-
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),
|
|
2182
2358
|
riskLevel: z.enum(["low", "medium", "high"]),
|
|
2183
|
-
riskSummary: z.string(),
|
|
2184
|
-
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),
|
|
2185
2361
|
});
|
|
2186
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
|
+
|
|
2187
2447
|
function escapeInlineCommentHtml(value: string): string {
|
|
2188
2448
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2189
2449
|
}
|
|
@@ -2193,21 +2453,33 @@ export default definePipr((pipr) => {
|
|
|
2193
2453
|
provider: "deepseek",
|
|
2194
2454
|
model: "deepseek-v4-pro",
|
|
2195
2455
|
apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
|
|
2196
|
-
|
|
2456
|
+
thinking: "high",
|
|
2197
2457
|
});
|
|
2198
2458
|
|
|
2199
|
-
pipr.config({
|
|
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
|
+
});
|
|
2200
2473
|
|
|
2201
|
-
const
|
|
2474
|
+
const findingsOutput = pipr.schema({
|
|
2202
2475
|
id: "review/categorized-findings",
|
|
2203
2476
|
schema: z.strictObject({
|
|
2204
|
-
|
|
2205
|
-
findings: z.array(categorizedFindingSchema),
|
|
2477
|
+
inlineFindings: z.array(categorizedFindingSchema),
|
|
2206
2478
|
}),
|
|
2207
2479
|
});
|
|
2208
2480
|
|
|
2209
|
-
const
|
|
2210
|
-
name: "reviewer",
|
|
2481
|
+
const findingsReviewer = pipr.agent({
|
|
2482
|
+
name: "findings-reviewer",
|
|
2211
2483
|
model,
|
|
2212
2484
|
instructions: \`
|
|
2213
2485
|
Review the change request diff for correctness, security, reliability,
|
|
@@ -2217,30 +2489,67 @@ export default definePipr((pipr) => {
|
|
|
2217
2489
|
concrete non-blocking defects; and low for small but actionable issues. Each
|
|
2218
2490
|
rationale must connect repository evidence to the defect and its concrete
|
|
2219
2491
|
impact. Keep each finding title to one line. Put supporting evidence and
|
|
2220
|
-
reasoning in rationale instead of appending it to the body.
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
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.
|
|
2225
2496
|
\`,
|
|
2226
|
-
output:
|
|
2497
|
+
output: findingsOutput,
|
|
2227
2498
|
tools: pipr.tools.readOnly,
|
|
2228
2499
|
retry: { invalidOutput: 1, transientFailure: 1 },
|
|
2229
2500
|
timeout: "10m",
|
|
2230
2501
|
prompt: () => "Review this change with severity and category metadata.",
|
|
2231
2502
|
});
|
|
2232
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
|
+
|
|
2233
2533
|
const task = pipr.task({
|
|
2234
2534
|
name: "review",
|
|
2235
2535
|
async run(ctx) {
|
|
2236
2536
|
const manifest = await ctx.change.diffManifest({ compressed: true });
|
|
2237
|
-
const result = await ctx.pi.run(
|
|
2238
|
-
const
|
|
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) => {
|
|
2239
2548
|
const severity = finding.severity.charAt(0).toUpperCase() + finding.severity.slice(1);
|
|
2240
2549
|
const category = finding.category.replaceAll("-", " ");
|
|
2241
2550
|
return {
|
|
2242
2551
|
body: [
|
|
2243
|
-
\`**\${severity} \${category}:** \${escapeInlineCommentHtml(finding.title)}\`,
|
|
2552
|
+
\`**\${severity} \${category}:** \${escapeInlineCommentHtml(lineText(finding.title))}\`,
|
|
2244
2553
|
"",
|
|
2245
2554
|
escapeInlineCommentHtml(finding.body),
|
|
2246
2555
|
"",
|
|
@@ -2263,17 +2572,17 @@ export default definePipr((pipr) => {
|
|
|
2263
2572
|
main: [
|
|
2264
2573
|
"## Summary",
|
|
2265
2574
|
"",
|
|
2266
|
-
\`**\${
|
|
2575
|
+
\`**\${lineText(summary.headline)}**\`,
|
|
2267
2576
|
"",
|
|
2268
|
-
summaryTable(
|
|
2577
|
+
summaryTable(summary),
|
|
2269
2578
|
"",
|
|
2270
2579
|
"## What Changed",
|
|
2271
2580
|
"",
|
|
2272
|
-
bulletList(
|
|
2581
|
+
bulletList(summary.changeSummary, "No changed behavior summarized."),
|
|
2273
2582
|
"",
|
|
2274
2583
|
"## Reviewer Focus",
|
|
2275
2584
|
"",
|
|
2276
|
-
bulletList(
|
|
2585
|
+
bulletList(summary.reviewerFocus, "No special reviewer focus."),
|
|
2277
2586
|
"",
|
|
2278
2587
|
].join("\\n"),
|
|
2279
2588
|
inlineFindings,
|
|
@@ -2307,7 +2616,7 @@ function labelValue(value: string): string {
|
|
|
2307
2616
|
}
|
|
2308
2617
|
|
|
2309
2618
|
function lineText(value: string): string {
|
|
2310
|
-
return value.
|
|
2619
|
+
return value.replace(/\\r\\n?|\\n/g, " ").trim();
|
|
2311
2620
|
}
|
|
2312
2621
|
|
|
2313
2622
|
function tableCell(value: string): string {
|
|
@@ -2319,6 +2628,7 @@ function tableCell(value: string): string {
|
|
|
2319
2628
|
//#region src/config/recipes/security-sast.ts
|
|
2320
2629
|
const securitySastRecipe = {
|
|
2321
2630
|
id: "security-sast",
|
|
2631
|
+
requiresChecksPermission: true,
|
|
2322
2632
|
title: "Security SAST",
|
|
2323
2633
|
description: "Security review with custom severity and category output.",
|
|
2324
2634
|
sourceTools: [
|
|
@@ -2354,7 +2664,7 @@ export default definePipr((pipr) => {
|
|
|
2354
2664
|
provider: "deepseek",
|
|
2355
2665
|
model: "deepseek-v4-pro",
|
|
2356
2666
|
apiKey: pipr.secret({ name: "DEEPSEEK_API_KEY" }),
|
|
2357
|
-
|
|
2667
|
+
thinking: "high",
|
|
2358
2668
|
});
|
|
2359
2669
|
|
|
2360
2670
|
const securityOutput = pipr.jsonSchema<SecurityReview>({
|
|
@@ -2693,6 +3003,9 @@ function officialInitRecipeFiles(recipe) {
|
|
|
2693
3003
|
function officialInitRecipeWorkflowEnvSecrets(recipe) {
|
|
2694
3004
|
return resolveOfficialInitRecipe(recipe).workflowEnvSecrets ?? [];
|
|
2695
3005
|
}
|
|
3006
|
+
function officialInitRecipeRequiresChecksPermission(recipe) {
|
|
3007
|
+
return resolveOfficialInitRecipe(recipe).requiresChecksPermission ?? false;
|
|
3008
|
+
}
|
|
2696
3009
|
function resolveOfficialInitRecipe(recipe) {
|
|
2697
3010
|
const id = recipe ?? "default-review";
|
|
2698
3011
|
if (!isOfficialInitRecipeId(id)) throw new Error(`Unsupported pipr init recipe '${id}'. Supported recipes: ${supportedOfficialInitRecipes.join(", ")}.`);
|
|
@@ -2703,7 +3016,7 @@ function isOfficialInitRecipeId(recipe) {
|
|
|
2703
3016
|
}
|
|
2704
3017
|
//#endregion
|
|
2705
3018
|
//#region src/config/official-github-workflow.ts
|
|
2706
|
-
const defaultWorkflowActionRef = "somus/pipr@v0.
|
|
3019
|
+
const defaultWorkflowActionRef = "somus/pipr@v0.6.0";
|
|
2707
3020
|
/** Internal shared renderer for `pipr init` and generated recipe docs. */
|
|
2708
3021
|
function renderOfficialGithubWorkflow(options = {}) {
|
|
2709
3022
|
const relativeConfigDir = options.relativeConfigDir ?? ".pipr";
|
|
@@ -2712,25 +3025,19 @@ function renderOfficialGithubWorkflow(options = {}) {
|
|
|
2712
3025
|
"",
|
|
2713
3026
|
"on:",
|
|
2714
3027
|
" pull_request:",
|
|
3028
|
+
" types: [opened, synchronize, reopened, ready_for_review]",
|
|
2715
3029
|
" issue_comment:",
|
|
2716
3030
|
" types: [created]",
|
|
2717
3031
|
" pull_request_review_comment:",
|
|
2718
3032
|
" types: [created]",
|
|
2719
3033
|
"",
|
|
2720
3034
|
"permissions:",
|
|
2721
|
-
" contents:
|
|
3035
|
+
" contents: read",
|
|
2722
3036
|
" pull-requests: write",
|
|
2723
|
-
" issues: write"
|
|
2724
|
-
" checks: write",
|
|
2725
|
-
"",
|
|
2726
|
-
"jobs:",
|
|
2727
|
-
" review:",
|
|
2728
|
-
" runs-on: ubuntu-latest",
|
|
2729
|
-
" steps:",
|
|
2730
|
-
" - uses: actions/checkout@v6",
|
|
2731
|
-
" with:",
|
|
2732
|
-
" fetch-depth: 0"
|
|
3037
|
+
" issues: write"
|
|
2733
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");
|
|
2734
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')`)}`);
|
|
2735
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")}`);
|
|
2736
3043
|
for (const secret of officialInitRecipeWorkflowEnvSecrets(options.recipe)) lines.push(` ${secret.env}: ${githubExpression(`secrets.${secret.secret}`)}`);
|
|
@@ -2748,4 +3055,4 @@ function githubExpression(expression) {
|
|
|
2748
3055
|
//#endregion
|
|
2749
3056
|
export { officialInitRecipeWorkflowEnvSecrets as a, officialInitRecipeFiles as i, listOfficialInitRecipes as n, supportedOfficialInitRecipes as o, officialInitRecipeConfigTs as r, renderOfficialGithubWorkflow as t };
|
|
2750
3057
|
|
|
2751
|
-
//# sourceMappingURL=official-github-workflow-
|
|
3058
|
+
//# sourceMappingURL=official-github-workflow-B3yeaCgq.mjs.map
|