pi-diff-review 0.1.9 → 0.1.10

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/README.md CHANGED
@@ -36,6 +36,7 @@ pi install https://github.com/cmpadden/pi-diff-review
36
36
  - `C` to add or edit an overall diff comment
37
37
  - `x` to delete a comment for the current line or selected range
38
38
  - `Enter` to submit comments back to pi
39
+ - Comments are cached per session and restored when reopening the same diff
39
40
  - `q` to exit
40
41
 
41
42
  ## Release
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-diff-review",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Local diff review TUI extension for pi",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import type {
2
3
  ExtensionAPI,
3
4
  ExtensionCommandContext,
@@ -9,6 +10,67 @@ import { buildReviewPrompt } from "./prompt.ts";
9
10
  import { ReviewComponent } from "./review-component.ts";
10
11
  import type { DiffSource, ReviewComment, ReviewResult } from "./types.ts";
11
12
 
13
+ const DIFF_REVIEW_CACHE_ENTRY = "pi-diff-review-cache";
14
+
15
+ type DiffReviewCacheEntry = {
16
+ cacheKey: string;
17
+ comments: ReviewComment[];
18
+ updatedAt: number;
19
+ };
20
+
21
+ function getDiffCacheKey(
22
+ cwd: string,
23
+ source: DiffSource,
24
+ diffText: string,
25
+ ): string {
26
+ const hash = createHash("sha256").update(diffText).digest("hex");
27
+ return `${cwd}\0${source.label}\0${hash}`;
28
+ }
29
+
30
+ function getCachedComments(
31
+ ctx: ExtensionCommandContext,
32
+ cacheKey: string,
33
+ ): Map<string, ReviewComment> {
34
+ let latest: DiffReviewCacheEntry | undefined;
35
+ for (const entry of ctx.sessionManager.getEntries()) {
36
+ if (
37
+ entry.type !== "custom" ||
38
+ entry.customType !== DIFF_REVIEW_CACHE_ENTRY
39
+ ) {
40
+ continue;
41
+ }
42
+
43
+ const data = entry.data as Partial<DiffReviewCacheEntry> | undefined;
44
+ if (data?.cacheKey !== cacheKey || !Array.isArray(data.comments)) {
45
+ continue;
46
+ }
47
+
48
+ if (!latest || (data.updatedAt ?? 0) >= latest.updatedAt) {
49
+ latest = {
50
+ cacheKey: data.cacheKey,
51
+ comments: data.comments,
52
+ updatedAt: data.updatedAt ?? 0,
53
+ };
54
+ }
55
+ }
56
+
57
+ return new Map(
58
+ (latest?.comments ?? []).map((comment) => [comment.id, comment]),
59
+ );
60
+ }
61
+
62
+ function persistCachedComments(
63
+ pi: ExtensionAPI,
64
+ cacheKey: string,
65
+ comments: Iterable<ReviewComment>,
66
+ ): void {
67
+ pi.appendEntry(DIFF_REVIEW_CACHE_ENTRY, {
68
+ cacheKey,
69
+ comments: [...comments],
70
+ updatedAt: Date.now(),
71
+ } satisfies DiffReviewCacheEntry);
72
+ }
73
+
12
74
  export function registerDiffReviewCommand(pi: ExtensionAPI): void {
13
75
  pi.registerCommand("diff", {
14
76
  description: "Review a git diff in a custom TUI (/diff [git diff args])",
@@ -30,9 +92,17 @@ export function registerDiffReviewCommand(pi: ExtensionAPI): void {
30
92
  }
31
93
 
32
94
  const reviewLines = parseDiff(diffText);
95
+ const cacheKey = getDiffCacheKey(ctx.cwd, source, diffText);
96
+ const comments = getCachedComments(ctx, cacheKey);
97
+ if (comments.size > 0) {
98
+ ctx.ui.notify(
99
+ `Restored ${comments.size} cached diff comment${comments.size === 1 ? "" : "s"}.`,
100
+ "info",
101
+ );
102
+ }
103
+
33
104
  const result = await ctx.ui.custom<ReviewResult>(
34
105
  (tui, theme, _keybindings, done) => {
35
- const comments = new Map<string, ReviewComment>();
36
106
  return new ReviewComponent(
37
107
  tui,
38
108
  theme,
@@ -41,6 +111,9 @@ export function registerDiffReviewCommand(pi: ExtensionAPI): void {
41
111
  comments,
42
112
  done,
43
113
  new PiModelDiffExplainer(ctx),
114
+ (updatedComments) => {
115
+ persistCachedComments(pi, cacheKey, updatedComments.values());
116
+ },
44
117
  );
45
118
  },
46
119
  );
@@ -48,9 +121,11 @@ export function registerDiffReviewCommand(pi: ExtensionAPI): void {
48
121
  if (!result || result.action !== "submit") return;
49
122
  if (result.comments.length === 0) {
50
123
  ctx.ui.notify("No review comments to send.", "info");
124
+ persistCachedComments(pi, cacheKey, []);
51
125
  return;
52
126
  }
53
127
 
128
+ persistCachedComments(pi, cacheKey, []);
54
129
  pi.sendUserMessage(
55
130
  buildReviewPrompt(result.comments, source.promptLabel),
56
131
  );
@@ -67,6 +67,7 @@ export class ReviewComponent {
67
67
  private comments: Map<string, ReviewComment>,
68
68
  private done: (result: ReviewResult) => void,
69
69
  private explainer?: DiffExplainer,
70
+ private onCommentsChanged?: (comments: Map<string, ReviewComment>) => void,
70
71
  ) {
71
72
  const firstCommentable = this.lines.findIndex((line) => line.commentable);
72
73
  this.selected = firstCommentable >= 0 ? firstCommentable : 0;
@@ -508,6 +509,7 @@ export class ReviewComponent {
508
509
 
509
510
  private markCommentsChanged(): void {
510
511
  this.commentsRevision++;
512
+ this.onCommentsChanged?.(this.comments);
511
513
  }
512
514
 
513
515
  private ensureCommentLineKeys(): void {