@usepipr/runtime 0.4.3 → 0.5.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/LICENSE +21 -0
  2. package/README.md +8 -5
  3. package/dist/{commands-DA0Ij4Di.d.mts → commands-CF59DZXx.d.mts} +2 -2
  4. package/dist/{commands-DA0Ij4Di.d.mts.map → commands-CF59DZXx.d.mts.map} +1 -1
  5. package/dist/{commands-B9qNW4pk.mjs → commands-DgZU7tf7.mjs} +474 -334
  6. package/dist/commands-DgZU7tf7.mjs.map +1 -0
  7. package/dist/index.d.mts +16 -3
  8. package/dist/index.d.mts.map +1 -1
  9. package/dist/index.mjs +117 -15
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/internal/action-result.d.mts +4 -2
  12. package/dist/internal/action-result.d.mts.map +1 -1
  13. package/dist/internal/action-result.mjs +28 -12
  14. package/dist/internal/action-result.mjs.map +1 -1
  15. package/dist/internal/docs.mjs +1 -1
  16. package/dist/internal/pipr-result.d.mts +24 -0
  17. package/dist/internal/pipr-result.d.mts.map +1 -0
  18. package/dist/internal/pipr-result.mjs +126 -0
  19. package/dist/internal/pipr-result.mjs.map +1 -0
  20. package/dist/internal/testing.d.mts +11 -11
  21. package/dist/internal/testing.d.mts.map +1 -1
  22. package/dist/internal/testing.mjs +1 -1
  23. package/dist/main-comment-envelope-DFirHYhW.mjs +69 -0
  24. package/dist/main-comment-envelope-DFirHYhW.mjs.map +1 -0
  25. package/dist/{official-github-workflow-Bjlu-FL3.mjs → official-github-workflow-DNzV5Z70.mjs} +183 -33
  26. package/dist/official-github-workflow-DNzV5Z70.mjs.map +1 -0
  27. package/dist/{types-BAis_Lwm.d.mts → types-Dn1U3MDG.d.mts} +30 -18
  28. package/dist/types-Dn1U3MDG.d.mts.map +1 -0
  29. package/package.json +16 -8
  30. package/dist/commands-B9qNW4pk.mjs.map +0 -1
  31. package/dist/official-github-workflow-Bjlu-FL3.mjs.map +0 -1
  32. package/dist/types-BAis_Lwm.d.mts.map +0 -1
@@ -0,0 +1,126 @@
1
+ import { t as parseGeneratedMainCommentEnvelope } from "../main-comment-envelope-DFirHYhW.mjs";
2
+ import { parsePiprResult } from "@usepipr/sdk";
3
+ //#region src/internal/pipr-result.ts
4
+ const genericFailureMessage = "Pipr failed; see the Action log for details.";
5
+ const publicationFailureMessage = "Pipr could not complete publication; see the Action log for details.";
6
+ function toPiprResult(input) {
7
+ return parsePiprResult(input.source === "local" ? localResult(input.result) : hostResult(input.result));
8
+ }
9
+ function toPiprErrorResult(_error) {
10
+ const result = parsePiprResult({
11
+ formatVersion: 2,
12
+ kind: "error",
13
+ message: genericFailureMessage
14
+ });
15
+ if (result.kind !== "error") throw new Error("Pipr error result schema returned an unexpected result kind");
16
+ return result;
17
+ }
18
+ function localResult(result) {
19
+ if (result.kind === "skipped") return {
20
+ formatVersion: 2,
21
+ kind: "skipped",
22
+ reason: result.skipReason
23
+ };
24
+ return reviewResult(result, { state: "disabled" });
25
+ }
26
+ function hostResult(result) {
27
+ switch (result.kind) {
28
+ case "ignored": return {
29
+ formatVersion: 2,
30
+ kind: "ignored",
31
+ reason: result.reason
32
+ };
33
+ case "dry-run": return {
34
+ formatVersion: 2,
35
+ kind: "dry-run"
36
+ };
37
+ case "command-help": return {
38
+ formatVersion: 2,
39
+ kind: "command-help",
40
+ reason: result.reason,
41
+ mainComment: result.body
42
+ };
43
+ case "command-response": return {
44
+ formatVersion: 2,
45
+ kind: "command-response",
46
+ run: publicRunSummary(result.run),
47
+ mainComment: result.response.body,
48
+ publication: {
49
+ state: "completed",
50
+ action: result.publication.action
51
+ }
52
+ };
53
+ case "verifier": return {
54
+ formatVersion: 2,
55
+ kind: "verifier",
56
+ run: publicRunSummary(result.run),
57
+ publication: {
58
+ state: "completed",
59
+ inlineResolutionErrorCount: result.errors.length
60
+ }
61
+ };
62
+ case "review": return reviewResult(result.review, {
63
+ state: "completed",
64
+ mainComment: { action: result.publication.mainComment.action },
65
+ inlineComments: result.publication.inlineComments,
66
+ inlinePublicationErrorCount: result.publication.metadata.inlinePublicationErrors.length,
67
+ inlineResolutionErrorCount: result.publication.metadata.inlineResolutionErrors.length
68
+ });
69
+ case "publication-error": return publicationErrorResult(result.publication);
70
+ }
71
+ }
72
+ function reviewResult(review, publication) {
73
+ return {
74
+ formatVersion: 2,
75
+ kind: "review",
76
+ run: publicRunSummary(review.run),
77
+ mainComment: stripPiprMainCommentMarkers(review.mainComment),
78
+ inlineFindings: review.inlineCommentDrafts.map((draft) => draft.finding),
79
+ droppedFindings: review.validated.droppedFindings,
80
+ taskChecks: review.taskChecks,
81
+ repairAttempted: review.repairAttempted,
82
+ publication
83
+ };
84
+ }
85
+ function publicRunSummary(run) {
86
+ const bounded = (value) => value.slice(0, 200);
87
+ return {
88
+ ...run,
89
+ id: bounded(run.id),
90
+ baseSha: bounded(run.baseSha),
91
+ headSha: bounded(run.headSha),
92
+ tasks: run.tasks.slice(0, 200).map(bounded),
93
+ models: run.models.slice(0, 20).map(bounded)
94
+ };
95
+ }
96
+ function publicationErrorResult(publication) {
97
+ return {
98
+ formatVersion: 2,
99
+ kind: "publication-error",
100
+ message: publicationFailureMessage,
101
+ ...publication ? { publication: {
102
+ inlineComments: publication.inlineComments,
103
+ inlinePublicationErrorCount: publication.metadata.inlinePublicationErrors.length,
104
+ inlineResolutionErrorCount: publication.metadata.inlineResolutionErrors.length
105
+ } } : {}
106
+ };
107
+ }
108
+ function stripPiprMainCommentMarkers(mainComment) {
109
+ const lines = mainComment.split("\n");
110
+ const envelope = parseGeneratedMainCommentEnvelope(lines);
111
+ const generatedMarkerIndexes = /* @__PURE__ */ new Set([
112
+ envelope.mainMarkerIndex,
113
+ envelope.headerMarkerIndex,
114
+ envelope.statsMarkerIndex,
115
+ envelope.statsRange?.start ?? -1,
116
+ envelope.statsRange?.end ?? -1,
117
+ lines[envelope.footerIndex] === "<!-- pipr:footer:hidden -->" ? envelope.footerIndex : -1
118
+ ]);
119
+ const visibleLines = lines.filter((_line, index) => !generatedMarkerIndexes.has(index));
120
+ const firstContentLine = visibleLines.findIndex((line) => line !== "");
121
+ return firstContentLine === -1 ? "" : visibleLines.slice(firstContentLine).join("\n");
122
+ }
123
+ //#endregion
124
+ export { stripPiprMainCommentMarkers, toPiprErrorResult, toPiprResult };
125
+
126
+ //# sourceMappingURL=pipr-result.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipr-result.mjs","names":[],"sources":["../../src/internal/pipr-result.ts"],"sourcesContent":["import { type PiprResult, type PiprRunSummary, parsePiprResult } from \"@usepipr/sdk\";\nimport type { HostRunCommandResult, LocalReviewCommandResult } from \"../host-run/types.js\";\nimport { mainCommentFooterHiddenMarker } from \"../review/comment-branding.js\";\nimport { parseGeneratedMainCommentEnvelope } from \"../review/main-comment-envelope.js\";\nimport type { PublicationError } from \"../review/publication-result.js\";\n\nconst genericFailureMessage = \"Pipr failed; see the Action log for details.\";\nconst publicationFailureMessage =\n \"Pipr could not complete publication; see the Action log for details.\";\n\ntype ResultInput =\n | { source: \"host\"; result: HostRunCommandResult }\n | { source: \"local\"; result: LocalReviewCommandResult }\n | {\n source: \"host\";\n result: { kind: \"publication-error\"; publication: PublicationError[\"result\"] };\n };\n\nexport function toPiprResult(input: ResultInput): PiprResult {\n return parsePiprResult(\n input.source === \"local\" ? localResult(input.result) : hostResult(input.result),\n );\n}\n\nexport function toPiprErrorResult(_error: unknown): Extract<PiprResult, { kind: \"error\" }> {\n const result = parsePiprResult({\n formatVersion: 2,\n kind: \"error\",\n message: genericFailureMessage,\n });\n if (result.kind !== \"error\") {\n throw new Error(\"Pipr error result schema returned an unexpected result kind\");\n }\n return result;\n}\n\nfunction localResult(result: LocalReviewCommandResult): PiprResult {\n if (result.kind === \"skipped\") {\n return { formatVersion: 2, kind: \"skipped\", reason: result.skipReason };\n }\n return reviewResult(result, { state: \"disabled\" });\n}\n\nfunction hostResult(\n result:\n | HostRunCommandResult\n | { kind: \"publication-error\"; publication: PublicationError[\"result\"] },\n): PiprResult {\n switch (result.kind) {\n case \"ignored\":\n return { formatVersion: 2, kind: \"ignored\", reason: result.reason };\n case \"dry-run\":\n return { formatVersion: 2, kind: \"dry-run\" };\n case \"command-help\":\n return {\n formatVersion: 2,\n kind: \"command-help\",\n reason: result.reason,\n mainComment: result.body,\n };\n case \"command-response\":\n return {\n formatVersion: 2,\n kind: \"command-response\",\n run: publicRunSummary(result.run),\n mainComment: result.response.body,\n publication: { state: \"completed\", action: result.publication.action },\n };\n case \"verifier\":\n return {\n formatVersion: 2,\n kind: \"verifier\",\n run: publicRunSummary(result.run),\n publication: { state: \"completed\", inlineResolutionErrorCount: result.errors.length },\n };\n case \"review\":\n return reviewResult(result.review, {\n state: \"completed\",\n mainComment: { action: result.publication.mainComment.action },\n inlineComments: result.publication.inlineComments,\n inlinePublicationErrorCount: result.publication.metadata.inlinePublicationErrors.length,\n inlineResolutionErrorCount: result.publication.metadata.inlineResolutionErrors.length,\n });\n case \"publication-error\":\n return publicationErrorResult(result.publication);\n }\n}\n\nfunction reviewResult(\n review: Extract<LocalReviewCommandResult, { kind: \"review\" }>,\n publication: Extract<PiprResult, { kind: \"review\" }>[\"publication\"],\n): PiprResult {\n return {\n formatVersion: 2,\n kind: \"review\",\n run: publicRunSummary(review.run),\n mainComment: stripPiprMainCommentMarkers(review.mainComment),\n inlineFindings: review.inlineCommentDrafts.map((draft) => draft.finding),\n droppedFindings: review.validated.droppedFindings,\n taskChecks: review.taskChecks,\n repairAttempted: review.repairAttempted,\n publication,\n };\n}\n\nfunction publicRunSummary(run: PiprRunSummary): PiprRunSummary {\n const bounded = (value: string) => value.slice(0, 200);\n return {\n ...run,\n id: bounded(run.id),\n baseSha: bounded(run.baseSha),\n headSha: bounded(run.headSha),\n tasks: run.tasks.slice(0, 200).map(bounded),\n models: run.models.slice(0, 20).map(bounded),\n };\n}\n\nfunction publicationErrorResult(\n publication: PublicationError[\"result\"],\n): Extract<PiprResult, { kind: \"publication-error\" }> {\n return {\n formatVersion: 2,\n kind: \"publication-error\",\n message: publicationFailureMessage,\n ...(publication\n ? {\n publication: {\n inlineComments: publication.inlineComments,\n inlinePublicationErrorCount: publication.metadata.inlinePublicationErrors.length,\n inlineResolutionErrorCount: publication.metadata.inlineResolutionErrors.length,\n },\n }\n : {}),\n };\n}\n\nexport function stripPiprMainCommentMarkers(mainComment: string): string {\n const lines = mainComment.split(\"\\n\");\n const envelope = parseGeneratedMainCommentEnvelope(lines);\n const generatedMarkerIndexes = new Set([\n envelope.mainMarkerIndex,\n envelope.headerMarkerIndex,\n envelope.statsMarkerIndex,\n envelope.statsRange?.start ?? -1,\n envelope.statsRange?.end ?? -1,\n lines[envelope.footerIndex] === mainCommentFooterHiddenMarker ? envelope.footerIndex : -1,\n ]);\n const visibleLines = lines.filter((_line, index) => !generatedMarkerIndexes.has(index));\n const firstContentLine = visibleLines.findIndex((line) => line !== \"\");\n return firstContentLine === -1 ? \"\" : visibleLines.slice(firstContentLine).join(\"\\n\");\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { r as runHostRunCommandWithDependencies } from "../commands-DA0Ij4Di.mjs";
2
- import { a as HostRunCommandResult, b as RepositoryPermission, f as piBuiltinToolNames, g as SecretRedactor, h as piThinkingLevels, k as RepositoryRef, m as piRequiredCliFlags, p as piReadOnlyToolNames, w as ChangeRequestRef, y as CodeHostAdapter } from "../types-BAis_Lwm.mjs";
1
+ import { r as runHostRunCommandWithDependencies } from "../commands-CF59DZXx.mjs";
2
+ import { a as HostRunCommandResult, b as RepositoryPermission, f as piBuiltinToolNames, g as SecretRedactor, h as piThinkingLevels, k as RepositoryRef, m as piRequiredCliFlags, p as piReadOnlyToolNames, w as ChangeRequestRef, y as CodeHostAdapter } from "../types-Dn1U3MDG.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/hosts/github/command.d.ts
5
5
  type GitHubPullRequestDetails = {
@@ -38,23 +38,23 @@ declare const githubIssueCommentSchema: z.ZodPipe<z.ZodObject<{
38
38
  } | undefined;
39
39
  }>>;
40
40
  declare const githubReviewCommentSchema: z.ZodPipe<z.ZodObject<{
41
+ id: z.ZodNumber;
42
+ body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
43
  path: z.ZodOptional<z.ZodString>;
42
44
  commit_id: z.ZodOptional<z.ZodString>;
43
45
  line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
44
46
  start_line: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
45
47
  side: z.ZodOptional<z.ZodEnum<{
46
- RIGHT: "RIGHT";
47
48
  LEFT: "LEFT";
49
+ RIGHT: "RIGHT";
48
50
  }>>;
49
51
  start_side: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
50
- RIGHT: "RIGHT";
51
52
  LEFT: "LEFT";
53
+ RIGHT: "RIGHT";
52
54
  }>>>;
53
55
  user: z.ZodOptional<z.ZodObject<{
54
56
  login: z.ZodString;
55
57
  }, z.core.$loose>>;
56
- id: z.ZodNumber;
57
- body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
58
58
  }, z.core.$loose>, z.ZodTransform<{
59
59
  id: number;
60
60
  body: string | null | undefined;
@@ -63,22 +63,22 @@ declare const githubReviewCommentSchema: z.ZodPipe<z.ZodObject<{
63
63
  commitId: string | undefined;
64
64
  line: number | undefined;
65
65
  startLine: number | undefined;
66
- side: "RIGHT" | "LEFT" | undefined;
67
- startSide: "RIGHT" | "LEFT" | undefined;
66
+ side: "LEFT" | "RIGHT" | undefined;
67
+ startSide: "LEFT" | "RIGHT" | undefined;
68
68
  }, {
69
69
  [x: string]: unknown;
70
70
  id: number;
71
+ body?: string | null | undefined;
71
72
  path?: string | undefined;
72
73
  commit_id?: string | undefined;
73
74
  line?: number | null | undefined;
74
75
  start_line?: number | null | undefined;
75
- side?: "RIGHT" | "LEFT" | undefined;
76
- start_side?: "RIGHT" | "LEFT" | null | undefined;
76
+ side?: "LEFT" | "RIGHT" | undefined;
77
+ start_side?: "LEFT" | "RIGHT" | null | undefined;
77
78
  user?: {
78
79
  [x: string]: unknown;
79
80
  login: string;
80
81
  } | undefined;
81
- body?: string | null | undefined;
82
82
  }>>;
83
83
  declare const githubReviewThreadSchema: z.ZodObject<{
84
84
  id: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.mts","names":[],"sources":["../../src/hosts/github/command.ts","../../src/hosts/github/publication-client.ts","../../src/hosts/github/adapter.ts","../../src/shared/secret-redactor.ts"],"mappings":";;;;KAiDY;EACV,YAAY;EACZ,QAAQ;;KAGE;EACV,eAAe;IACb,YAAY;IACZ;MACE,QAAQ;EACZ,wBAAwB;IACtB,YAAY;IACZ;MACE,QAAQ;;;;cChDR,0BAAwB,EAAA,QAAA,EAAA;;;;;;;;;;;;;;;;;;;cAMxB,2BAAyB,EAAA,QAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BzB,0BAAwB,EAAA;;;;;cAwExB,sBAAoB,EAAA;;;;KAKd,qBAAqB,EAAE,aAAa;KACpC,sBAAsB,EAAE,aAAa;KACrC,qBAAqB,EAAE,aAAa;KACpC,iBAAiB,EAAE,aAAa;KAEhC;EACV,6BAA6B;EAC7B,sBAAsB;IAAW;IAAc;MAA8B;EAC7E,kBAAkB;IAAW;IAAc;MAAwB,QAAQ;EAC3E,mBAAmB;IACjB;IACA;IACA;MACE;IAAU;;EACd,mBAAmB;IACjB;IACA;IACA;MACE;IAAU;;EACd,mBAAmB;IACjB;IACA;MACE,QAAQ;EACZ,kBAAkB;IAChB;IACA;MACE,QAAQ;EACZ,oBAAoB;IAClB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;MACE;IAAU;;EACd,yBAAyB;IACvB;IACA;IACA;IACA;MACE;IAAU;;EACd,oBAAoB;IAAW;MAAqB;EACpD,eAAe;IACb;IACA;IACA;IACA;MACE,QAAQ;EACZ,eAAe;IACb;IACA;IACA;IACA;IACA;MACE;;;;KCjKM;EACV,MAAM,OAAO;EACb,gBAAgB;EAChB,oBAAoB;;iBAGN,wBAAwB,UAAS,2BAAgC;;;iBCvBjE,0BAA0B;EAAY,MAAM,OAAO;IAAA"}
1
+ {"version":3,"file":"testing.d.mts","names":[],"sources":["../../src/hosts/github/command.ts","../../src/hosts/github/publication-client.ts","../../src/hosts/github/adapter.ts","../../src/shared/secret-redactor.ts"],"mappings":";;;;KAiDY;EACV,YAAY;EACZ,QAAQ;;KAGE;EACV,eAAe;IACb,YAAY;IACZ;MACE,QAAQ;EACZ,wBAAwB;IACtB,YAAY;IACZ;MACE,QAAQ;;;;cChDR,0BAAwB,EAAA,QAAA,EAAA;;;;;;;;;;;;;;;;;;;cAMxB,2BAAyB,EAAA,QAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BzB,0BAAwB,EAAA;;;;GAI5B,EAAA,KAAA;cAoEI,sBAAoB,EAAA;;;GAGxB,EAAA,KAAA;KAEU,qBAAqB,EAAE,aAAa;KACpC,sBAAsB,EAAE,aAAa;KACrC,qBAAqB,EAAE,aAAa;KACpC,iBAAiB,EAAE,aAAa;KAEhC;EACV,6BAA6B;EAC7B,sBAAsB;IAAW;IAAc;MAA8B;EAC7E,kBAAkB;IAAW;IAAc;MAAwB,QAAQ;EAC3E,mBAAmB;IACjB;IACA;IACA;MACE;IAAU;;EACd,mBAAmB;IACjB;IACA;IACA;MACE;IAAU;;EACd,mBAAmB;IACjB;IACA;MACE,QAAQ;EACZ,kBAAkB;IAChB;IACA;MACE,QAAQ;EACZ,oBAAoB;IAClB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;MACE;IAAU;;EACd,yBAAyB;IACvB;IACA;IACA;IACA;MACE;IAAU;;EACd,oBAAoB;IAAW;MAAqB;EACpD,eAAe;IACb;IACA;IACA;IACA;MACE,QAAQ;EACZ,eAAe;IACb;IACA;IACA;IACA;IACA;MACE;;;;KChKM;EACV,MAAM,OAAO;EACb,gBAAgB;EAChB,oBAAoB;;iBAGN,wBAAwB,UAAS,2BAAgC;;;iBCxBjE,0BAA0B;EAAY,MAAM,OAAO;IAAA"}
@@ -1,2 +1,2 @@
1
- import { _ as piReadOnlyToolNames, g as piBuiltinToolNames, l as createGitHubHostAdapter, m as createKnownSecretRedactor, r as runHostRunCommandWithDependencies, v as piRequiredCliFlags, y as piThinkingLevels } from "../commands-B9qNW4pk.mjs";
1
+ import { _ as piReadOnlyToolNames, g as piBuiltinToolNames, l as createGitHubHostAdapter, m as createKnownSecretRedactor, r as runHostRunCommandWithDependencies, v as piRequiredCliFlags, y as piThinkingLevels } from "../commands-DgZU7tf7.mjs";
2
2
  export { createGitHubHostAdapter, createKnownSecretRedactor, piBuiltinToolNames, piReadOnlyToolNames, piRequiredCliFlags, piThinkingLevels, runHostRunCommandWithDependencies };
@@ -0,0 +1,69 @@
1
+ //#region src/review/comment-branding.ts
2
+ const piprLogoUrl = "https://pipr.run/images/pipr/pipr-mark.svg";
3
+ const piprRepositoryUrl = "https://github.com/somus/pipr";
4
+ const mainCommentTitle = `# <img src="${piprLogoUrl}" width="22" height="22" alt=""> Pipr Review`;
5
+ const mainCommentTitles = /* @__PURE__ */ new Set([
6
+ "# pipr Review",
7
+ "# Pipr Review",
8
+ mainCommentTitle
9
+ ]);
10
+ const mainCommentHeaderHiddenMarker = "<!-- pipr:header:hidden -->";
11
+ const mainCommentFooterHiddenMarker = "<!-- pipr:footer:hidden -->";
12
+ const reviewStatsStartMarker = "<!-- pipr:stats:start -->";
13
+ const reviewStatsEndMarker = "<!-- pipr:stats:end -->";
14
+ const reviewStatsHiddenMarker = "<!-- pipr:stats:hidden -->";
15
+ const escapedRepositoryUrl = piprRepositoryUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16
+ const mainCommentAttributionSource = `Review generated by \\[Pipr\\]\\(${escapedRepositoryUrl}\\) for commit \`[^\`]+\`\\.(?: Config SDK \\d+\\.\\d+\\.\\d+ is behind (?:Pipr \\d+\\.\\d+\\.\\d+|\\[Pipr \\d+\\.\\d+\\.\\d+\\]\\(${escapedRepositoryUrl}/releases/tag/v\\d+\\.\\d+\\.\\d+\\))\\.)?`;
17
+ const mainCommentAttributionTextPattern = new RegExp(`^${mainCommentAttributionSource}$`);
18
+ const mainCommentAttributionPattern = new RegExp(`^<sub>${mainCommentAttributionSource}</sub>$`);
19
+ //#endregion
20
+ //#region src/review/main-comment-envelope.ts
21
+ const generatedReviewStatsShape = [
22
+ /^$/,
23
+ /^\| Metric \| Total \|$/,
24
+ /^\| --- \| ---: \|$/,
25
+ /^\| Models \| .+ \|$/,
26
+ /^\| Agent runs \| \d+ \|$/,
27
+ /^\| Elapsed \| .+ \|$/,
28
+ /^\| Input tokens \| (?:Unavailable|[\d,]+(?: \(reported\))?) \|$/,
29
+ /^\| Output tokens \| (?:Unavailable|[\d,]+(?: \(reported\))?) \|$/,
30
+ /^\| Cost \(USD\) \| (?:Unavailable|\$\d+(?:\.\d+)?(?:e[+-]?\d+)?(?: \(reported\))?) \|$/,
31
+ /^$/,
32
+ /^<\/details>$/,
33
+ /^<!-- pipr:stats:end -->$/
34
+ ];
35
+ function parseGeneratedMainCommentEnvelope(lines) {
36
+ const mainMarkerIndex = lines.findIndex((line) => line.startsWith("<!-- pipr:main-comment "));
37
+ const headerCandidateOffset = lines.slice(mainMarkerIndex + 1).findIndex((line) => line !== "");
38
+ const headerCandidateIndex = mainMarkerIndex + 1 + headerCandidateOffset;
39
+ const headerMarkerIndex = mainMarkerIndex >= 0 && headerCandidateOffset >= 0 && lines[headerCandidateIndex] === "<!-- pipr:header:hidden -->" ? headerCandidateIndex : -1;
40
+ const lastLineIndex = lines.findLastIndex((line) => line !== "");
41
+ const lastLine = lines[lastLineIndex] ?? "";
42
+ const footerIndex = lastLine === "<!-- pipr:footer:hidden -->" || mainCommentAttributionPattern.test(lastLine) ? lastLineIndex : -1;
43
+ const lastContentIndex = lines.slice(0, footerIndex < 0 ? lines.length : footerIndex).findLastIndex((line) => line !== "");
44
+ return {
45
+ mainMarkerIndex,
46
+ headerMarkerIndex,
47
+ statsMarkerIndex: lines[lastContentIndex] === "<!-- pipr:stats:hidden -->" ? lastContentIndex : -1,
48
+ statsRange: generatedReviewStatsRange(lines, footerIndex),
49
+ footerIndex
50
+ };
51
+ }
52
+ function generatedReviewStatsRange(lines, generatedFooterIndex) {
53
+ const end = lines.slice(0, generatedFooterIndex < 0 ? lines.length : generatedFooterIndex).findLastIndex((line) => line !== "");
54
+ if (end < 0 || lines[end] !== "<!-- pipr:stats:end -->" || lines[end - 1] !== "</details>") return;
55
+ const start = lines.lastIndexOf(reviewStatsStartMarker, end - 2);
56
+ if (start < 0 || lines[start + 1] !== "<details>" || lines[start + 2] !== "<summary>Review stats</summary>" || !matchesGeneratedReviewStatsShape(lines, start, end)) return;
57
+ return {
58
+ start,
59
+ end
60
+ };
61
+ }
62
+ function matchesGeneratedReviewStatsShape(lines, start, end) {
63
+ const generatedShape = lines.slice(start + 3, end + 1);
64
+ return generatedShape.length === generatedReviewStatsShape.length && generatedReviewStatsShape.every((pattern, index) => pattern.test(generatedShape[index] ?? ""));
65
+ }
66
+ //#endregion
67
+ export { mainCommentTitle as a, reviewStatsEndMarker as c, mainCommentHeaderHiddenMarker as i, reviewStatsHiddenMarker as l, mainCommentAttributionTextPattern as n, mainCommentTitles as o, mainCommentFooterHiddenMarker as r, piprRepositoryUrl as s, parseGeneratedMainCommentEnvelope as t, reviewStatsStartMarker as u };
68
+
69
+ //# sourceMappingURL=main-comment-envelope-DFirHYhW.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-comment-envelope-DFirHYhW.mjs","names":[],"sources":["../src/review/comment-branding.ts","../src/review/main-comment-envelope.ts"],"sourcesContent":["const piprLogoUrl = \"https://pipr.run/images/pipr/pipr-mark.svg\";\nexport const piprRepositoryUrl = \"https://github.com/somus/pipr\";\n\nexport const mainCommentTitle = `# <img src=\"${piprLogoUrl}\" width=\"22\" height=\"22\" alt=\"\"> Pipr Review`;\n\nexport const mainCommentTitles = new Set([\"# pipr Review\", \"# Pipr Review\", mainCommentTitle]);\nexport const mainCommentHeaderHiddenMarker = \"<!-- pipr:header:hidden -->\";\nexport const mainCommentFooterHiddenMarker = \"<!-- pipr:footer:hidden -->\";\nexport const reviewStatsStartMarker = \"<!-- pipr:stats:start -->\";\nexport const reviewStatsEndMarker = \"<!-- pipr:stats:end -->\";\nexport const reviewStatsHiddenMarker = \"<!-- pipr:stats:hidden -->\";\n\nconst escapedRepositoryUrl = piprRepositoryUrl.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\nconst mainCommentAttributionSource = `Review generated by \\\\[Pipr\\\\]\\\\(${escapedRepositoryUrl}\\\\) for commit \\`[^\\`]+\\`\\\\.(?: Config SDK \\\\d+\\\\.\\\\d+\\\\.\\\\d+ is behind (?:Pipr \\\\d+\\\\.\\\\d+\\\\.\\\\d+|\\\\[Pipr \\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\]\\\\(${escapedRepositoryUrl}/releases/tag/v\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\))\\\\.)?`;\n\nexport const mainCommentAttributionTextPattern = new RegExp(`^${mainCommentAttributionSource}$`);\n\nexport const mainCommentAttributionPattern = new RegExp(\n `^<sub>${mainCommentAttributionSource}</sub>$`,\n);\n","import {\n mainCommentAttributionPattern,\n mainCommentFooterHiddenMarker,\n mainCommentHeaderHiddenMarker,\n reviewStatsEndMarker,\n reviewStatsHiddenMarker,\n reviewStatsStartMarker,\n} from \"./comment-branding.js\";\n\nconst generatedReviewStatsShape = [\n /^$/,\n /^\\| Metric \\| Total \\|$/,\n /^\\| --- \\| ---: \\|$/,\n /^\\| Models \\| .+ \\|$/,\n /^\\| Agent runs \\| \\d+ \\|$/,\n /^\\| Elapsed \\| .+ \\|$/,\n /^\\| Input tokens \\| (?:Unavailable|[\\d,]+(?: \\(reported\\))?) \\|$/,\n /^\\| Output tokens \\| (?:Unavailable|[\\d,]+(?: \\(reported\\))?) \\|$/,\n /^\\| Cost \\(USD\\) \\| (?:Unavailable|\\$\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?(?: \\(reported\\))?) \\|$/,\n /^$/,\n /^<\\/details>$/,\n /^<!-- pipr:stats:end -->$/,\n];\n\nexport type GeneratedMainCommentEnvelope = {\n mainMarkerIndex: number;\n headerMarkerIndex: number;\n statsMarkerIndex: number;\n statsRange: { start: number; end: number } | undefined;\n footerIndex: number;\n};\n\nexport function parseGeneratedMainCommentEnvelope(lines: string[]): GeneratedMainCommentEnvelope {\n const mainMarkerIndex = lines.findIndex((line) => line.startsWith(\"<!-- pipr:main-comment \"));\n const headerCandidateOffset = lines.slice(mainMarkerIndex + 1).findIndex((line) => line !== \"\");\n const headerCandidateIndex = mainMarkerIndex + 1 + headerCandidateOffset;\n const headerMarkerIndex =\n mainMarkerIndex >= 0 &&\n headerCandidateOffset >= 0 &&\n lines[headerCandidateIndex] === mainCommentHeaderHiddenMarker\n ? headerCandidateIndex\n : -1;\n const lastLineIndex = lines.findLastIndex((line) => line !== \"\");\n const lastLine = lines[lastLineIndex] ?? \"\";\n const footerIndex =\n lastLine === mainCommentFooterHiddenMarker || mainCommentAttributionPattern.test(lastLine)\n ? lastLineIndex\n : -1;\n const lastContentIndex = lines\n .slice(0, footerIndex < 0 ? lines.length : footerIndex)\n .findLastIndex((line) => line !== \"\");\n const statsMarkerIndex =\n lines[lastContentIndex] === reviewStatsHiddenMarker ? lastContentIndex : -1;\n\n return {\n mainMarkerIndex,\n headerMarkerIndex,\n statsMarkerIndex,\n statsRange: generatedReviewStatsRange(lines, footerIndex),\n footerIndex,\n };\n}\n\nfunction generatedReviewStatsRange(\n lines: string[],\n generatedFooterIndex: number,\n): { start: number; end: number } | undefined {\n const end = lines\n .slice(0, generatedFooterIndex < 0 ? lines.length : generatedFooterIndex)\n .findLastIndex((line) => line !== \"\");\n if (end < 0 || lines[end] !== reviewStatsEndMarker || lines[end - 1] !== \"</details>\") {\n return undefined;\n }\n const start = lines.lastIndexOf(reviewStatsStartMarker, end - 2);\n if (\n start < 0 ||\n lines[start + 1] !== \"<details>\" ||\n lines[start + 2] !== \"<summary>Review stats</summary>\" ||\n !matchesGeneratedReviewStatsShape(lines, start, end)\n ) {\n return undefined;\n }\n return { start, end };\n}\n\nfunction matchesGeneratedReviewStatsShape(lines: string[], start: number, end: number): boolean {\n const generatedShape = lines.slice(start + 3, end + 1);\n return (\n generatedShape.length === generatedReviewStatsShape.length &&\n generatedReviewStatsShape.every((pattern, index) => pattern.test(generatedShape[index] ?? \"\"))\n );\n}\n"],"mappings":";AAAA,MAAM,cAAc;AACpB,MAAa,oBAAoB;AAEjC,MAAa,mBAAmB,eAAe,YAAY;AAE3D,MAAa,oCAAoB,IAAI,IAAI;CAAC;CAAiB;CAAiB;AAAgB,CAAC;AAC7F,MAAa,gCAAgC;AAC7C,MAAa,gCAAgC;AAC7C,MAAa,yBAAyB;AACtC,MAAa,uBAAuB;AACpC,MAAa,0BAA0B;AAEvC,MAAM,uBAAuB,kBAAkB,QAAQ,uBAAuB,MAAM;AACpF,MAAM,+BAA+B,oCAAoC,qBAAqB,qIAAqI,qBAAqB;AAExP,MAAa,oCAAoC,IAAI,OAAO,IAAI,6BAA6B,EAAE;AAE/F,MAAa,gCAAgC,IAAI,OAC/C,SAAS,6BAA6B,QACxC;;;ACVA,MAAM,4BAA4B;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAUA,SAAgB,kCAAkC,OAA+C;CAC/F,MAAM,kBAAkB,MAAM,WAAW,SAAS,KAAK,WAAW,yBAAyB,CAAC;CAC5F,MAAM,wBAAwB,MAAM,MAAM,kBAAkB,CAAC,CAAC,CAAC,WAAW,SAAS,SAAS,EAAE;CAC9F,MAAM,uBAAuB,kBAAkB,IAAI;CACnD,MAAM,oBACJ,mBAAmB,KACnB,yBAAyB,KACzB,MAAM,0BAAA,gCACF,uBACA;CACN,MAAM,gBAAgB,MAAM,eAAe,SAAS,SAAS,EAAE;CAC/D,MAAM,WAAW,MAAM,kBAAkB;CACzC,MAAM,cACJ,aAAA,iCAA8C,8BAA8B,KAAK,QAAQ,IACrF,gBACA;CACN,MAAM,mBAAmB,MACtB,MAAM,GAAG,cAAc,IAAI,MAAM,SAAS,WAAW,CAAC,CACtD,eAAe,SAAS,SAAS,EAAE;CAItC,OAAO;EACL;EACA;EACA,kBALA,MAAM,sBAAA,+BAAgD,mBAAmB;EAMzE,YAAY,0BAA0B,OAAO,WAAW;EACxD;CACF;AACF;AAEA,SAAS,0BACP,OACA,sBAC4C;CAC5C,MAAM,MAAM,MACT,MAAM,GAAG,uBAAuB,IAAI,MAAM,SAAS,oBAAoB,CAAC,CACxE,eAAe,SAAS,SAAS,EAAE;CACtC,IAAI,MAAM,KAAK,MAAM,SAAA,6BAAiC,MAAM,MAAM,OAAO,cACvE;CAEF,MAAM,QAAQ,MAAM,YAAY,wBAAwB,MAAM,CAAC;CAC/D,IACE,QAAQ,KACR,MAAM,QAAQ,OAAO,eACrB,MAAM,QAAQ,OAAO,qCACrB,CAAC,iCAAiC,OAAO,OAAO,GAAG,GAEnD;CAEF,OAAO;EAAE;EAAO;CAAI;AACtB;AAEA,SAAS,iCAAiC,OAAiB,OAAe,KAAsB;CAC9F,MAAM,iBAAiB,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;CACrD,OACE,eAAe,WAAW,0BAA0B,UACpD,0BAA0B,OAAO,SAAS,UAAU,QAAQ,KAAK,eAAe,UAAU,EAAE,CAAC;AAEjG"}
@@ -281,7 +281,7 @@ export default definePipr((pipr) => {
281
281
  "| --- | ---: |",
282
282
  \`| Inline findings | \${result.inlineFindings.length} |\`,
283
283
  "",
284
- context.platform.id === "local" ? localInlineFindingSummary : inlineFindingSummary,
284
+ context.run.trigger === "local" ? localInlineFindingSummary : inlineFindingSummary,
285
285
  ].join("\\n"),
286
286
  inlineFindings: result.inlineFindings,
287
287
  };
@@ -1222,10 +1222,10 @@ export default definePipr((pipr) => {
1222
1222
  const pluginToolReviewRecipe = {
1223
1223
  id: "plugin-tool-review",
1224
1224
  title: "Plugin Tool Review",
1225
- description: "Typed R2-backed memory plugin for reviewer agents that need durable project context.",
1225
+ description: "Typed R2-backed memory plugin with search-only review and explicit maintainer curation.",
1226
1226
  sourceTools: ["Cloudflare R2", "Reviewer memory"],
1227
1227
  configTs: `import { definePipr } from "@usepipr/sdk";
1228
- import { r2MemoryPlugin } from "./r2-memory";
1228
+ import { memoryLimits, r2MemoryPlugin } from "./r2-memory";
1229
1229
 
1230
1230
  export default definePipr((pipr) => {
1231
1231
  const model = pipr.model({
@@ -1277,8 +1277,44 @@ export default definePipr((pipr) => {
1277
1277
  },
1278
1278
  });
1279
1279
 
1280
+ const rememberTask = pipr.task<{ lesson: string }>({
1281
+ name: "remember-review-memory",
1282
+ async run(ctx, input) {
1283
+ if (!ctx.command) {
1284
+ throw new Error("remember-review-memory is a command-only task");
1285
+ }
1286
+ const lesson = input.lesson.trim();
1287
+ if (lesson.length === 0) {
1288
+ await ctx.command.reply("Usage: @pipr remember <lesson...>");
1289
+ return;
1290
+ }
1291
+ if (lesson.length > memoryLimits.bodyCharacters) {
1292
+ await ctx.command.reply(
1293
+ "Reviewer memory must be " + memoryLimits.bodyCharacters + " characters or fewer.",
1294
+ );
1295
+ return;
1296
+ }
1297
+ const stored = await memory.curate(
1298
+ {
1299
+ subject: lesson.slice(0, memoryLimits.subjectCharacters),
1300
+ body: lesson,
1301
+ tags: ["maintainer-curated"],
1302
+ },
1303
+ ctx,
1304
+ );
1305
+ await ctx.command.reply("Stored reviewer memory \`" + stored.id + "\`.");
1306
+ },
1307
+ });
1308
+
1280
1309
  pipr.on.changeRequest({ actions: ["opened", "updated"], task });
1281
1310
  pipr.command({ pattern: "@pipr memory-review", permission: "write", task });
1311
+ pipr.command({
1312
+ pattern: "@pipr remember <lesson...>",
1313
+ permission: "write",
1314
+ description: "Store an explicit maintainer-curated reviewer lesson.",
1315
+ parse: (args) => ({ lesson: args.lesson ?? "" }),
1316
+ task: rememberTask,
1317
+ });
1282
1318
  });
1283
1319
  `,
1284
1320
  files: [{
@@ -1286,22 +1322,55 @@ export default definePipr((pipr) => {
1286
1322
  contents: `import { S3Client } from "bun";
1287
1323
  import { definePlugin, type SecretRef, type TaskContext, z } from "@usepipr/sdk";
1288
1324
 
1325
+ export const memoryLimits = {
1326
+ subjectCharacters: 120,
1327
+ bodyCharacters: 4000,
1328
+ tagCount: 12,
1329
+ tagCharacters: 50,
1330
+ queryCharacters: 500,
1331
+ resultDefault: 5,
1332
+ resultMinimum: 1,
1333
+ resultMaximum: 20,
1334
+ searchObjectMaximum: 2000,
1335
+ } as const;
1336
+
1337
+ const memorySource = z.strictObject({
1338
+ kind: z.enum(["maintainer-command", "agent-tool"]),
1339
+ runId: z.string().min(1).max(200),
1340
+ platform: z.string().min(1).max(50),
1341
+ changeRequestNumber: z.number().int().nonnegative().optional(),
1342
+ headSha: z.string().min(1).max(200),
1343
+ });
1344
+
1289
1345
  const memoryItem = z.strictObject({
1290
- subject: z.string(),
1291
- body: z.string(),
1292
- tags: z.array(z.string()).optional(),
1293
- updatedAt: z.string().optional(),
1346
+ id: z.string().uuid().optional(),
1347
+ subject: z.string().trim().min(1).max(memoryLimits.subjectCharacters),
1348
+ body: z.string().trim().min(1).max(memoryLimits.bodyCharacters),
1349
+ tags: z
1350
+ .array(z.string().trim().min(1).max(memoryLimits.tagCharacters))
1351
+ .max(memoryLimits.tagCount)
1352
+ .optional(),
1353
+ source: memorySource.optional(),
1354
+ updatedAt: z.string().max(50).optional(),
1294
1355
  });
1295
1356
 
1296
1357
  const memorySearchInput = z.strictObject({
1297
- query: z.string(),
1298
- limit: z.number().optional(),
1358
+ query: z.string().trim().min(1).max(memoryLimits.queryCharacters),
1359
+ limit: z
1360
+ .number()
1361
+ .int()
1362
+ .min(memoryLimits.resultMinimum)
1363
+ .max(memoryLimits.resultMaximum)
1364
+ .optional(),
1299
1365
  });
1300
1366
 
1301
1367
  const memoryStoreInput = z.strictObject({
1302
- subject: z.string(),
1303
- body: z.string(),
1304
- tags: z.array(z.string()).optional(),
1368
+ subject: z.string().trim().min(1).max(memoryLimits.subjectCharacters),
1369
+ body: z.string().trim().min(1).max(memoryLimits.bodyCharacters),
1370
+ tags: z
1371
+ .array(z.string().trim().min(1).max(memoryLimits.tagCharacters))
1372
+ .max(memoryLimits.tagCount)
1373
+ .optional(),
1305
1374
  });
1306
1375
 
1307
1376
  type MemoryItem = ReturnType<typeof memoryItem.parse>;
@@ -1328,6 +1397,7 @@ export function r2MemoryPlugin(options: R2MemoryOptions) {
1328
1397
  id: "memory/search-output",
1329
1398
  schema: z.strictObject({
1330
1399
  memories: z.array(memoryItem),
1400
+ skippedObjects: z.number().int().nonnegative(),
1331
1401
  }),
1332
1402
  });
1333
1403
  const storeInput = pipr.schema({
@@ -1339,6 +1409,7 @@ export function r2MemoryPlugin(options: R2MemoryOptions) {
1339
1409
  schema: z.strictObject({
1340
1410
  stored: z.boolean(),
1341
1411
  key: z.string(),
1412
+ id: z.string().uuid(),
1342
1413
  }),
1343
1414
  });
1344
1415
 
@@ -1361,12 +1432,15 @@ export function r2MemoryPlugin(options: R2MemoryOptions) {
1361
1432
  input: storeInput,
1362
1433
  output: storeOutput,
1363
1434
  async run({ input, ctx, signal }) {
1364
- return await storeMemory(input, ctx, options, signal);
1435
+ return await storeMemory(input, ctx, options, "agent-tool", signal);
1365
1436
  },
1366
1437
  toModelOutput(output) {
1367
1438
  return output;
1368
1439
  },
1369
1440
  }),
1441
+ curate(input: MemoryStoreInput, ctx: TaskContext, signal?: AbortSignal) {
1442
+ return storeMemory(input, ctx, options, "maintainer-command", signal);
1443
+ },
1370
1444
  };
1371
1445
  });
1372
1446
  }
@@ -1376,29 +1450,52 @@ async function searchMemory(
1376
1450
  ctx: TaskContext,
1377
1451
  options: R2MemoryOptions,
1378
1452
  signal?: AbortSignal,
1379
- ): Promise<{ memories: MemoryItem[] }> {
1453
+ ): Promise<{ memories: MemoryItem[]; skippedObjects: number }> {
1380
1454
  signal?.throwIfAborted();
1381
1455
  const bucket = r2Bucket(ctx, options);
1382
- const listed = await bucket.list({ prefix: memoryPrefix(ctx, options) + "/", maxKeys: 200 });
1383
1456
  const memories: MemoryItem[] = [];
1457
+ let continuationToken: string | undefined;
1458
+ let scannedObjects = 0;
1459
+ let skippedObjects = 0;
1384
1460
 
1385
- for (const object of listed.contents ?? []) {
1461
+ do {
1386
1462
  signal?.throwIfAborted();
1387
- try {
1388
- const value = memoryItem.parse(await bucket.file(object.key).json());
1389
- if (matchesMemory(value, input.query)) {
1390
- memories.push(value);
1463
+ const listed = await bucket.list({
1464
+ prefix: memoryPrefix(ctx, options) + "/",
1465
+ maxKeys: 200,
1466
+ continuationToken,
1467
+ });
1468
+
1469
+ const objects = (listed.contents ?? []).slice(
1470
+ 0,
1471
+ memoryLimits.searchObjectMaximum - scannedObjects,
1472
+ );
1473
+ scannedObjects += objects.length;
1474
+ for (const object of objects) {
1475
+ signal?.throwIfAborted();
1476
+ try {
1477
+ const value = memoryItem.parse(await bucket.file(object.key).json());
1478
+ if (matchesMemory(value, input.query)) {
1479
+ memories.push(value);
1480
+ }
1481
+ } catch {
1482
+ // Exclude malformed or concurrently deleted objects and report the count.
1483
+ skippedObjects += 1;
1391
1484
  }
1392
- } catch {
1393
- // Ignore malformed or concurrently deleted memory objects.
1394
1485
  }
1395
- }
1396
1486
 
1397
- const limit = Math.min(Math.max(Math.trunc(input.limit ?? 5), 1), 20);
1487
+ continuationToken = listed.isTruncated ? listed.nextContinuationToken : undefined;
1488
+ } while (continuationToken && scannedObjects < memoryLimits.searchObjectMaximum);
1489
+
1490
+ const limit = Math.min(
1491
+ Math.max(Math.trunc(input.limit ?? memoryLimits.resultDefault), memoryLimits.resultMinimum),
1492
+ memoryLimits.resultMaximum,
1493
+ );
1398
1494
  return {
1399
1495
  memories: memories
1400
1496
  .sort((left, right) => (right.updatedAt ?? "").localeCompare(left.updatedAt ?? ""))
1401
1497
  .slice(0, limit),
1498
+ skippedObjects,
1402
1499
  };
1403
1500
  }
1404
1501
 
@@ -1406,14 +1503,53 @@ async function storeMemory(
1406
1503
  input: MemoryStoreInput,
1407
1504
  ctx: TaskContext,
1408
1505
  options: R2MemoryOptions,
1506
+ sourceKind: "maintainer-command" | "agent-tool",
1409
1507
  signal?: AbortSignal,
1410
- ): Promise<{ stored: boolean; key: string }> {
1508
+ ): Promise<{ stored: boolean; key: string; id: string }> {
1411
1509
  signal?.throwIfAborted();
1412
1510
  const bucket = r2Bucket(ctx, options);
1413
- const entry: MemoryItem = { ...input, updatedAt: new Date().toISOString() };
1414
- const key = memoryKey(input.subject, ctx, options);
1511
+ const parsedInput = memoryStoreInput.parse(input);
1512
+ const curatedKey =
1513
+ sourceKind === "maintainer-command"
1514
+ ? memoryPrefix(ctx, options) +
1515
+ "/maintainer-command/" +
1516
+ encodeURIComponent(ctx.run.id) +
1517
+ ".json"
1518
+ : undefined;
1519
+
1520
+ const id = curatedKey ? await stableCommandMemoryId(ctx.run.id) : crypto.randomUUID();
1521
+ const entry = memoryItem.parse({
1522
+ ...parsedInput,
1523
+ id,
1524
+ source: {
1525
+ kind: sourceKind,
1526
+ runId: ctx.run.id,
1527
+ platform: ctx.platform.id,
1528
+ changeRequestNumber: ctx.change.number,
1529
+ headSha: ctx.change.head.sha,
1530
+ },
1531
+ updatedAt: new Date().toISOString(),
1532
+ });
1533
+ const key = curatedKey ?? memoryKey(id, parsedInput.subject, ctx, options);
1415
1534
  await bucket.write(key, JSON.stringify(entry, null, 2), { type: "application/json" });
1416
- return { stored: true, key };
1535
+ return { stored: true, key, id };
1536
+ }
1537
+
1538
+ async function stableCommandMemoryId(runId: string): Promise<string> {
1539
+ const digest = new Uint8Array(
1540
+ await crypto.subtle.digest(
1541
+ "SHA-256",
1542
+ new TextEncoder().encode("pipr-memory/maintainer-command/" + runId),
1543
+ ),
1544
+ );
1545
+ digest[6] = (digest[6]! & 0x0f) | 0x50;
1546
+ digest[8] = (digest[8]! & 0x3f) | 0x80;
1547
+ const hex = Array.from(digest.slice(0, 16), (byte) => byte.toString(16).padStart(2, "0")).join(
1548
+ "",
1549
+ );
1550
+ return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20)].join(
1551
+ "-",
1552
+ );
1417
1553
  }
1418
1554
 
1419
1555
  function r2Bucket(ctx: TaskContext, options: R2MemoryOptions): S3Client {
@@ -1444,14 +1580,26 @@ function cleanPathSegment(value: string): string {
1444
1580
  );
1445
1581
  }
1446
1582
 
1447
- function memoryKey(subject: string, ctx: TaskContext, options: R2MemoryOptions): string {
1583
+ function memoryKey(
1584
+ id: string,
1585
+ subject: string,
1586
+ ctx: TaskContext,
1587
+ options: R2MemoryOptions,
1588
+ ): string {
1448
1589
  const slug = subject
1449
1590
  .toLowerCase()
1450
1591
  .replace(/[^a-z0-9]+/g, "-")
1451
1592
  .replace(/^-|-$/g, "")
1452
1593
  .slice(0, 60);
1453
1594
  return (
1454
- memoryPrefix(ctx, options) + "/" + new Date().toISOString() + "-" + (slug || "memory") + ".json"
1595
+ memoryPrefix(ctx, options) +
1596
+ "/" +
1597
+ new Date().toISOString() +
1598
+ "-" +
1599
+ id +
1600
+ "-" +
1601
+ (slug || "memory") +
1602
+ ".json"
1455
1603
  );
1456
1604
  }
1457
1605
 
@@ -1488,7 +1636,9 @@ function matchesMemory(item: MemoryItem, query: string): boolean {
1488
1636
 
1489
1637
  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
1638
 
1491
- R2 is object storage, not a search index. The sample lists recent JSON memory objects under \`prefix/repository-owner/repository-name\` and filters them locally, which 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 reviewer treats memory as untrusted historical context and requires current repository evidence for findings. It only searches memory by default. The store tool is available for explicit customization, but full review summaries are not persisted automatically.
1639
+ 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.
1640
+
1641
+ 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
1642
 
1493
1643
  `
1494
1644
  };
@@ -2703,7 +2853,7 @@ function isOfficialInitRecipeId(recipe) {
2703
2853
  }
2704
2854
  //#endregion
2705
2855
  //#region src/config/official-github-workflow.ts
2706
- const defaultWorkflowActionRef = "somus/pipr@v0.4.3";
2856
+ const defaultWorkflowActionRef = "somus/pipr@v0.5.0";
2707
2857
  /** Internal shared renderer for `pipr init` and generated recipe docs. */
2708
2858
  function renderOfficialGithubWorkflow(options = {}) {
2709
2859
  const relativeConfigDir = options.relativeConfigDir ?? ".pipr";
@@ -2748,4 +2898,4 @@ function githubExpression(expression) {
2748
2898
  //#endregion
2749
2899
  export { officialInitRecipeWorkflowEnvSecrets as a, officialInitRecipeFiles as i, listOfficialInitRecipes as n, supportedOfficialInitRecipes as o, officialInitRecipeConfigTs as r, renderOfficialGithubWorkflow as t };
2750
2900
 
2751
- //# sourceMappingURL=official-github-workflow-Bjlu-FL3.mjs.map
2901
+ //# sourceMappingURL=official-github-workflow-DNzV5Z70.mjs.map