@diegopetrucci/pi-extensions 0.1.74 → 0.1.78

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 (108) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +17 -12
  3. package/extensions/agent-workflow-audit/README.md +1 -1
  4. package/extensions/agent-workflow-audit/index.ts +1631 -1009
  5. package/extensions/agent-workflow-audit/package.json +1 -1
  6. package/extensions/annotate-git-diff/README.md +3 -1
  7. package/extensions/annotate-git-diff/clipboard.ts +109 -100
  8. package/extensions/annotate-git-diff/git.ts +846 -768
  9. package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
  10. package/extensions/annotate-git-diff/index.ts +712 -671
  11. package/extensions/annotate-git-diff/package.json +1 -1
  12. package/extensions/annotate-git-diff/prompt.ts +45 -45
  13. package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
  14. package/extensions/annotate-git-diff/review-server.ts +337 -311
  15. package/extensions/annotate-git-diff/types.ts +132 -132
  16. package/extensions/annotate-git-diff/ui.ts +27 -27
  17. package/extensions/annotate-git-diff/watch.ts +74 -74
  18. package/extensions/annotate-git-diff/web/app.js +1947 -1884
  19. package/extensions/annotate-git-diff/web/index.html +5 -6
  20. package/extensions/annotate-last-message/README.md +1 -1
  21. package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
  22. package/extensions/annotate-last-message/index.ts +210 -206
  23. package/extensions/annotate-last-message/package.json +1 -1
  24. package/extensions/annotate-last-message/prompt.ts +87 -66
  25. package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
  26. package/extensions/annotate-last-message/session.ts +94 -89
  27. package/extensions/annotate-last-message/theme.ts +199 -193
  28. package/extensions/annotate-last-message/types.ts +23 -23
  29. package/extensions/annotate-last-message/ui.ts +27 -25
  30. package/extensions/annotate-last-message/web/app.js +4 -7
  31. package/extensions/annotate-last-message/web/index.html +3 -3
  32. package/extensions/annotate-last-message/web/md-renderer.js +1 -1
  33. package/extensions/brrr/README.md +1 -1
  34. package/extensions/brrr/index.ts +213 -191
  35. package/extensions/brrr/package.json +1 -1
  36. package/extensions/claude-fast/README.md +5 -79
  37. package/extensions/claude-fast/index.ts +31 -289
  38. package/extensions/claude-fast/package.json +2 -3
  39. package/extensions/code-reviewer/README.md +6 -6
  40. package/extensions/code-reviewer/index.ts +1825 -1373
  41. package/extensions/code-reviewer/package.json +1 -1
  42. package/extensions/confirm-destructive/README.md +1 -1
  43. package/extensions/confirm-destructive/index.ts +50 -52
  44. package/extensions/confirm-destructive/package.json +1 -1
  45. package/extensions/context-cap/README.md +9 -9
  46. package/extensions/context-cap/index.ts +313 -305
  47. package/extensions/context-cap/package.json +2 -2
  48. package/extensions/context-inspector/index.ts +1211 -1064
  49. package/extensions/context-inspector/package.json +1 -1
  50. package/extensions/contrarian/README.md +4 -3
  51. package/extensions/contrarian/index.ts +1863 -1737
  52. package/extensions/contrarian/package.json +1 -1
  53. package/extensions/dirty-repo-guard/index.ts +37 -41
  54. package/extensions/dirty-repo-guard/package.json +1 -1
  55. package/extensions/dynamic-context-pruning/README.md +11 -43
  56. package/extensions/dynamic-context-pruning/index.ts +2131 -2105
  57. package/extensions/dynamic-context-pruning/package.json +1 -1
  58. package/extensions/fast/README.md +52 -14
  59. package/extensions/fast/index.ts +513 -317
  60. package/extensions/fast/package.json +1 -1
  61. package/extensions/git-footer/README.md +1 -1
  62. package/extensions/git-footer/index.ts +458 -411
  63. package/extensions/git-footer/package.json +2 -3
  64. package/extensions/gnosis/index.ts +180 -130
  65. package/extensions/gnosis/package.json +1 -1
  66. package/extensions/inline-bash/index.ts +87 -103
  67. package/extensions/inline-bash/package.json +1 -1
  68. package/extensions/librarian/README.md +1 -1
  69. package/extensions/librarian/index.ts +1877 -1419
  70. package/extensions/librarian/package.json +1 -1
  71. package/extensions/minimal-footer/README.md +5 -5
  72. package/extensions/minimal-footer/index.ts +987 -1012
  73. package/extensions/minimal-footer/minimal-footer.example.json +1 -1
  74. package/extensions/minimal-footer/openai-usage.ts +195 -190
  75. package/extensions/minimal-footer/package.json +1 -1
  76. package/extensions/notify/index.ts +222 -230
  77. package/extensions/notify/package.json +1 -1
  78. package/extensions/openai-fast/README.md +5 -73
  79. package/extensions/openai-fast/index.ts +32 -272
  80. package/extensions/openai-fast/package.json +2 -3
  81. package/extensions/oracle/README.md +4 -3
  82. package/extensions/oracle/index.ts +1740 -1627
  83. package/extensions/oracle/package.json +1 -1
  84. package/extensions/permission-gate/README.md +3 -0
  85. package/extensions/permission-gate/index.ts +785 -612
  86. package/extensions/permission-gate/package.json +1 -1
  87. package/extensions/permission-gate/powershell-safety.ts +840 -793
  88. package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
  89. package/extensions/project-mcp-json/README.md +199 -0
  90. package/extensions/project-mcp-json/config.ts +564 -0
  91. package/extensions/project-mcp-json/index.ts +119 -0
  92. package/extensions/project-mcp-json/package.json +38 -0
  93. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  94. package/extensions/quiet-tools/README.md +30 -19
  95. package/extensions/quiet-tools/index.ts +376 -291
  96. package/extensions/quiet-tools/package.json +2 -2
  97. package/extensions/review/README.md +2 -2
  98. package/extensions/review/index.ts +1745 -1872
  99. package/extensions/review/package.json +1 -1
  100. package/extensions/todo/index.ts +280 -280
  101. package/extensions/todo/package.json +1 -1
  102. package/extensions/triage-comments/README.md +6 -4
  103. package/extensions/triage-comments/index.ts +2620 -1888
  104. package/extensions/triage-comments/package.json +1 -1
  105. package/package.json +9 -6
  106. package/assets/social-preview.png +0 -0
  107. package/extensions/claude-fast/claude-fast.example.json +0 -4
  108. package/extensions/openai-fast/openai-fast.example.json +0 -4
@@ -4,228 +4,232 @@ import { openQuietGlimpse, type QuietGlimpseWindow } from "./quiet-glimpse.js";
4
4
  import { composeAnnotateLastMessagePrompt, hasAnnotateLastMessageFeedback } from "./prompt.js";
5
5
  import { findLastAssistantMessage } from "./session.js";
6
6
  import type {
7
- AnnotateLastMessageCancelPayload,
8
- AnnotateLastMessageInlineComment,
9
- AnnotateLastMessageSectionComment,
10
- AnnotateLastMessageSubmitPayload,
11
- AnnotateLastMessageWindowMessage,
12
- LastAssistantMessageData,
7
+ AnnotateLastMessageCancelPayload,
8
+ AnnotateLastMessageInlineComment,
9
+ AnnotateLastMessageSectionComment,
10
+ AnnotateLastMessageSubmitPayload,
11
+ AnnotateLastMessageWindowMessage,
12
+ LastAssistantMessageData,
13
13
  } from "./types.js";
14
14
  import { buildAnnotateLastMessageHtml } from "./ui.js";
15
15
 
16
16
  function isInlineComment(value: unknown): value is AnnotateLastMessageInlineComment {
17
- return (
18
- typeof value === "object" &&
19
- value != null &&
20
- "line" in value &&
21
- typeof value.line === "number" &&
22
- Number.isInteger(value.line) &&
23
- value.line > 0 &&
24
- "body" in value &&
25
- typeof value.body === "string"
26
- );
17
+ return (
18
+ typeof value === "object" &&
19
+ value != null &&
20
+ "line" in value &&
21
+ typeof value.line === "number" &&
22
+ Number.isInteger(value.line) &&
23
+ value.line > 0 &&
24
+ "body" in value &&
25
+ typeof value.body === "string"
26
+ );
27
27
  }
28
28
 
29
29
  function isSectionComment(value: unknown): value is AnnotateLastMessageSectionComment {
30
- return (
31
- typeof value === "object" &&
32
- value != null &&
33
- "sectionId" in value &&
34
- typeof value.sectionId === "string" &&
35
- "body" in value &&
36
- typeof value.body === "string"
37
- );
30
+ return (
31
+ typeof value === "object" &&
32
+ value != null &&
33
+ "sectionId" in value &&
34
+ typeof value.sectionId === "string" &&
35
+ "body" in value &&
36
+ typeof value.body === "string"
37
+ );
38
38
  }
39
39
 
40
40
  function isSubmitPayload(value: unknown): value is AnnotateLastMessageSubmitPayload {
41
- return (
42
- typeof value === "object" &&
43
- value != null &&
44
- "type" in value &&
45
- value.type === "submit" &&
46
- "overallComment" in value &&
47
- typeof value.overallComment === "string" &&
48
- "inlineComments" in value &&
49
- Array.isArray(value.inlineComments) &&
50
- value.inlineComments.every(isInlineComment) &&
51
- "sectionComments" in value &&
52
- Array.isArray(value.sectionComments) &&
53
- value.sectionComments.every(isSectionComment)
54
- );
41
+ return (
42
+ typeof value === "object" &&
43
+ value != null &&
44
+ "type" in value &&
45
+ value.type === "submit" &&
46
+ "overallComment" in value &&
47
+ typeof value.overallComment === "string" &&
48
+ "inlineComments" in value &&
49
+ Array.isArray(value.inlineComments) &&
50
+ value.inlineComments.every(isInlineComment) &&
51
+ "sectionComments" in value &&
52
+ Array.isArray(value.sectionComments) &&
53
+ value.sectionComments.every(isSectionComment)
54
+ );
55
55
  }
56
56
 
57
57
  function isCancelPayload(value: unknown): value is AnnotateLastMessageCancelPayload {
58
- return typeof value === "object" && value != null && "type" in value && value.type === "cancel";
58
+ return typeof value === "object" && value != null && "type" in value && value.type === "cancel";
59
59
  }
60
60
 
61
- export function registerAnnotateLastMessageCommand(pi: ExtensionAPI): void {
62
- let activeWindow: QuietGlimpseWindow | null = null;
63
- let annotationOpening = false;
64
- let annotationAttempt = 0;
65
- const suppressedWindows = new WeakSet<QuietGlimpseWindow>();
66
-
67
- function closeActiveWindow(options: { suppressResults?: boolean } = {}): void {
68
- annotationAttempt += 1;
69
- const windowToClose = activeWindow;
70
- activeWindow = null;
71
- if (windowToClose == null) return;
72
- if (options.suppressResults) {
73
- suppressedWindows.add(windowToClose);
74
- }
75
- try {
76
- windowToClose.close();
77
- } catch {
78
- // Ignore races when the native window is already closing.
79
- }
80
- }
81
-
82
- async function openAnnotationWindow(ctx: ExtensionCommandContext): Promise<void> {
83
- if (ctx.mode !== "tui") {
84
- ctx.ui.notify("annotate-last-message requires interactive mode.", "error");
85
- return;
86
- }
87
- if (annotationOpening || activeWindow != null) {
88
- ctx.ui.notify("A last-message annotation window is already open.", "warning");
89
- return;
90
- }
91
-
92
- const messageResult = findLastAssistantMessage(ctx.sessionManager.getBranch());
93
- if (!messageResult.ok) {
94
- ctx.ui.notify(messageResult.message, "error");
95
- return;
96
- }
97
-
98
- const messageData = messageResult.data;
99
- annotationOpening = true;
100
- const attempt = ++annotationAttempt;
101
-
102
- try {
103
- const html = buildAnnotateLastMessageHtml(messageData, ctx.ui.theme);
104
- const window = await openQuietGlimpse(html, {
105
- width: 1440,
106
- height: 980,
107
- title: "annotate last message",
108
- });
109
- if (attempt !== annotationAttempt) {
110
- suppressedWindows.add(window);
111
- window.close();
112
- return;
113
- }
114
- activeWindow = window;
115
- if (window.failure != null) throw window.failure;
116
- if (window.closed) throw new Error("Glimpse closed while the annotation window was starting.");
117
-
118
- const terminalMessagePromise = new Promise<AnnotateLastMessageSubmitPayload | AnnotateLastMessageCancelPayload | null>(
119
- (resolve, reject) => {
120
- let settled = false;
121
- let closeTimer: ReturnType<typeof setTimeout> | null = null;
122
-
123
- const requestWindowClose = (): void => {
124
- try {
125
- window.close();
126
- } catch {
127
- // Ignore races when the native process has already exited.
128
- }
129
- };
130
-
131
- const cleanup = (): void => {
132
- if (closeTimer != null) {
133
- clearTimeout(closeTimer);
134
- closeTimer = null;
135
- }
136
- window.removeListener("message", onMessage);
137
- window.removeListener("closed", onClosed);
138
- window.removeListener("error", onError);
139
- if (activeWindow === window) {
140
- activeWindow = null;
141
- }
142
- };
143
-
144
- const settle = (value: AnnotateLastMessageWindowMessage | null): void => {
145
- if (settled) return;
146
- settled = true;
147
- cleanup();
148
- resolve(value);
149
- };
150
-
151
- const onMessage = (data: unknown): void => {
152
- if (isSubmitPayload(data) || isCancelPayload(data)) {
153
- settle(data);
154
- requestWindowClose();
155
- }
156
- };
157
-
158
- const onClosed = (): void => {
159
- if (settled || closeTimer != null) return;
160
- closeTimer = setTimeout(() => {
161
- closeTimer = null;
162
- settle(null);
163
- }, 250);
164
- };
165
-
166
- const onError = (error: Error): void => {
167
- if (settled) return;
168
- settled = true;
169
- requestWindowClose();
170
- cleanup();
171
- reject(error);
172
- };
173
-
174
- window.on("message", onMessage);
175
- window.on("closed", onClosed);
176
- window.on("error", onError);
177
- if (window.failure != null) onError(window.failure);
178
- else if (window.closed) onClosed();
179
- },
180
- );
181
-
182
- void (async (windowMessageSource: QuietGlimpseWindow, sourceData: LastAssistantMessageData) => {
183
- try {
184
- const result = await terminalMessagePromise;
185
- if (suppressedWindows.has(windowMessageSource) || attempt !== annotationAttempt) return;
186
- if (result == null) return;
187
- if (result.type === "cancel") {
188
- ctx.ui.notify("Annotation cancelled.", "info");
189
- return;
190
- }
191
- if (!hasAnnotateLastMessageFeedback(result)) {
192
- ctx.ui.notify("No annotation feedback submitted.", "info");
193
- return;
194
- }
195
-
196
- const prompt = composeAnnotateLastMessagePrompt(sourceData, result);
197
- pi.sendUserMessage(prompt, { deliverAs: "followUp" });
198
- ctx.ui.notify("Annotation feedback sent to the agent.", "info");
199
- } catch (error) {
200
- if (suppressedWindows.has(windowMessageSource) || attempt !== annotationAttempt) return;
201
- const message = error instanceof Error ? error.message : String(error);
202
- ctx.ui.notify(`Annotation failed: ${message}`, "error");
203
- }
204
- })(window, messageData);
205
-
206
- ctx.ui.notify("Opened native annotation window.", "info");
207
- } catch (error) {
208
- if (attempt !== annotationAttempt) return;
209
- closeActiveWindow({ suppressResults: true });
210
- const message = error instanceof Error ? error.message : String(error);
211
- ctx.ui.notify(`Annotation failed: ${message}`, "error");
212
- } finally {
213
- annotationOpening = false;
214
- }
215
- }
216
-
217
- pi.registerCommand("annotate-last-message", {
218
- description: "Open a native annotation window for the latest assistant message",
219
- handler: async (_args, ctx) => {
220
- await openAnnotationWindow(ctx);
221
- },
222
- });
223
-
224
- pi.on("session_shutdown", async () => {
225
- closeActiveWindow({ suppressResults: true });
226
- });
61
+ function registerAnnotateLastMessageCommand(pi: ExtensionAPI): void {
62
+ let activeWindow: QuietGlimpseWindow | null = null;
63
+ let annotationOpening = false;
64
+ let annotationAttempt = 0;
65
+ const suppressedWindows = new WeakSet<QuietGlimpseWindow>();
66
+
67
+ function closeActiveWindow(options: { suppressResults?: boolean } = {}): void {
68
+ annotationAttempt += 1;
69
+ const windowToClose = activeWindow;
70
+ activeWindow = null;
71
+ if (windowToClose == null) return;
72
+ if (options.suppressResults) {
73
+ suppressedWindows.add(windowToClose);
74
+ }
75
+ try {
76
+ windowToClose.close();
77
+ } catch {
78
+ // Ignore races when the native window is already closing.
79
+ }
80
+ }
81
+
82
+ async function openAnnotationWindow(ctx: ExtensionCommandContext): Promise<void> {
83
+ if (ctx.mode !== "tui") {
84
+ ctx.ui.notify("annotate-last-message requires interactive mode.", "error");
85
+ return;
86
+ }
87
+ if (annotationOpening || activeWindow != null) {
88
+ ctx.ui.notify("A last-message annotation window is already open.", "warning");
89
+ return;
90
+ }
91
+
92
+ const messageResult = findLastAssistantMessage(ctx.sessionManager.getBranch());
93
+ if (!messageResult.ok) {
94
+ ctx.ui.notify(messageResult.message, "error");
95
+ return;
96
+ }
97
+
98
+ const messageData = messageResult.data;
99
+ annotationOpening = true;
100
+ const attempt = ++annotationAttempt;
101
+
102
+ try {
103
+ const html = buildAnnotateLastMessageHtml(messageData, ctx.ui.theme);
104
+ const window = await openQuietGlimpse(html, {
105
+ width: 1440,
106
+ height: 980,
107
+ title: "annotate last message",
108
+ });
109
+ if (attempt !== annotationAttempt) {
110
+ suppressedWindows.add(window);
111
+ window.close();
112
+ return;
113
+ }
114
+ activeWindow = window;
115
+ if (window.failure != null) throw window.failure;
116
+ if (window.closed)
117
+ throw new Error("Glimpse closed while the annotation window was starting.");
118
+
119
+ const terminalMessagePromise = new Promise<
120
+ AnnotateLastMessageSubmitPayload | AnnotateLastMessageCancelPayload | null
121
+ >((resolve, reject) => {
122
+ let settled = false;
123
+ let closeTimer: ReturnType<typeof setTimeout> | null = null;
124
+
125
+ const requestWindowClose = (): void => {
126
+ try {
127
+ window.close();
128
+ } catch {
129
+ // Ignore races when the native process has already exited.
130
+ }
131
+ };
132
+
133
+ const cleanup = (): void => {
134
+ if (closeTimer != null) {
135
+ clearTimeout(closeTimer);
136
+ closeTimer = null;
137
+ }
138
+ window.removeListener("message", onMessage);
139
+ window.removeListener("closed", onClosed);
140
+ window.removeListener("error", onError);
141
+ if (activeWindow === window) {
142
+ activeWindow = null;
143
+ }
144
+ };
145
+
146
+ const settle = (value: AnnotateLastMessageWindowMessage | null): void => {
147
+ if (settled) return;
148
+ settled = true;
149
+ cleanup();
150
+ resolve(value);
151
+ };
152
+
153
+ const onMessage = (data: unknown): void => {
154
+ if (isSubmitPayload(data) || isCancelPayload(data)) {
155
+ settle(data);
156
+ requestWindowClose();
157
+ }
158
+ };
159
+
160
+ const onClosed = (): void => {
161
+ if (settled || closeTimer != null) return;
162
+ closeTimer = setTimeout(() => {
163
+ closeTimer = null;
164
+ settle(null);
165
+ }, 250);
166
+ };
167
+
168
+ const onError = (error: Error): void => {
169
+ if (settled) return;
170
+ settled = true;
171
+ requestWindowClose();
172
+ cleanup();
173
+ reject(error);
174
+ };
175
+
176
+ window.on("message", onMessage);
177
+ window.on("closed", onClosed);
178
+ window.on("error", onError);
179
+ if (window.failure != null) onError(window.failure);
180
+ else if (window.closed) onClosed();
181
+ });
182
+
183
+ void (async (
184
+ windowMessageSource: QuietGlimpseWindow,
185
+ sourceData: LastAssistantMessageData,
186
+ ) => {
187
+ try {
188
+ const result = await terminalMessagePromise;
189
+ if (suppressedWindows.has(windowMessageSource) || attempt !== annotationAttempt) return;
190
+ if (result == null) return;
191
+ if (result.type === "cancel") {
192
+ ctx.ui.notify("Annotation cancelled.", "info");
193
+ return;
194
+ }
195
+ if (!hasAnnotateLastMessageFeedback(result)) {
196
+ ctx.ui.notify("No annotation feedback submitted.", "info");
197
+ return;
198
+ }
199
+
200
+ const prompt = composeAnnotateLastMessagePrompt(sourceData, result);
201
+ pi.sendUserMessage(prompt, { deliverAs: "followUp" });
202
+ ctx.ui.notify("Annotation feedback sent to the agent.", "info");
203
+ } catch (error) {
204
+ if (suppressedWindows.has(windowMessageSource) || attempt !== annotationAttempt) return;
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ ctx.ui.notify(`Annotation failed: ${message}`, "error");
207
+ }
208
+ })(window, messageData);
209
+
210
+ ctx.ui.notify("Opened native annotation window.", "info");
211
+ } catch (error) {
212
+ if (attempt !== annotationAttempt) return;
213
+ closeActiveWindow({ suppressResults: true });
214
+ const message = error instanceof Error ? error.message : String(error);
215
+ ctx.ui.notify(`Annotation failed: ${message}`, "error");
216
+ } finally {
217
+ annotationOpening = false;
218
+ }
219
+ }
220
+
221
+ pi.registerCommand("annotate-last-message", {
222
+ description: "Open a native annotation window for the latest assistant message",
223
+ handler: async (_args, ctx) => {
224
+ await openAnnotationWindow(ctx);
225
+ },
226
+ });
227
+
228
+ pi.on("session_shutdown", async () => {
229
+ closeActiveWindow({ suppressResults: true });
230
+ });
227
231
  }
228
232
 
229
233
  export default function (pi: ExtensionAPI): void {
230
- registerAnnotateLastMessageCommand(pi);
234
+ registerAnnotateLastMessageCommand(pi);
231
235
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegopetrucci/pi-annotate-last-message",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A standalone pi extension that adds /annotate-last-message, a native Glimpse UI for annotating the latest assistant reply.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,93 +1,114 @@
1
- import type { AnnotateLastMessageInlineComment, AnnotateLastMessageSectionComment, AnnotateLastMessageSubmitPayload, LastAssistantMessageData, LastAssistantMessageLine, LastAssistantMessageSection } from "./types.js";
1
+ import type {
2
+ AnnotateLastMessageInlineComment,
3
+ AnnotateLastMessageSectionComment,
4
+ AnnotateLastMessageSubmitPayload,
5
+ LastAssistantMessageData,
6
+ LastAssistantMessageLine,
7
+ LastAssistantMessageSection,
8
+ } from "./types.js";
2
9
 
3
10
  const EXCERPT_LIMIT = 120;
4
11
 
5
12
  function truncateExcerpt(value: string, limit = EXCERPT_LIMIT): string {
6
- const normalized = value.replace(/\s+/g, " ").trim();
7
- if (normalized.length === 0) return "(blank line)";
8
- if (normalized.length <= limit) return normalized;
9
- return `${normalized.slice(0, Math.max(0, limit - 1)).trimEnd()}…`;
13
+ const normalized = value.replace(/\s+/g, " ").trim();
14
+ if (normalized.length === 0) return "(blank line)";
15
+ if (normalized.length <= limit) return normalized;
16
+ return `${normalized.slice(0, Math.max(0, limit - 1)).trimEnd()}…`;
10
17
  }
11
18
 
12
19
  function formatLineLabel(line: number): string {
13
- return `line ${line}`;
20
+ return `line ${line}`;
14
21
  }
15
22
 
16
- function formatLineComment(comment: AnnotateLastMessageInlineComment, line: LastAssistantMessageLine | undefined): string {
17
- const excerpt = truncateExcerpt(line?.text ?? "");
18
- return `${formatLineLabel(comment.line)} — “${excerpt}”`;
23
+ function formatLineComment(
24
+ comment: AnnotateLastMessageInlineComment,
25
+ line: LastAssistantMessageLine | undefined,
26
+ ): string {
27
+ const excerpt = truncateExcerpt(line?.text ?? "");
28
+ return `${formatLineLabel(comment.line)} — “${excerpt}”`;
19
29
  }
20
30
 
21
31
  function formatSectionLineRange(section: LastAssistantMessageSection): string {
22
- return section.startLine === section.endLine
23
- ? `line ${section.startLine}`
24
- : `lines ${section.startLine}-${section.endLine}`;
32
+ return section.startLine === section.endLine
33
+ ? `line ${section.startLine}`
34
+ : `lines ${section.startLine}-${section.endLine}`;
25
35
  }
26
36
 
27
- function formatSectionComment(comment: AnnotateLastMessageSectionComment, section: LastAssistantMessageSection | undefined): string {
28
- if (section == null) {
29
- return "Unknown section";
30
- }
31
- return `Section ${section.index} (${formatSectionLineRange(section)}) — “${truncateExcerpt(section.preview)}”`;
37
+ function formatSectionComment(
38
+ comment: AnnotateLastMessageSectionComment,
39
+ section: LastAssistantMessageSection | undefined,
40
+ ): string {
41
+ if (section == null) {
42
+ return "Unknown section";
43
+ }
44
+ return `Section ${section.index} (${formatSectionLineRange(section)}) — “${truncateExcerpt(section.preview)}”`;
32
45
  }
33
46
 
34
47
  export function hasAnnotateLastMessageFeedback(payload: AnnotateLastMessageSubmitPayload): boolean {
35
- if (payload.overallComment.trim().length > 0) {
36
- return true;
37
- }
38
- if (payload.inlineComments.some((comment) => comment.body.trim().length > 0)) {
39
- return true;
40
- }
41
- return payload.sectionComments.some((comment) => comment.body.trim().length > 0);
48
+ if (payload.overallComment.trim().length > 0) {
49
+ return true;
50
+ }
51
+ if (payload.inlineComments.some((comment) => comment.body.trim().length > 0)) {
52
+ return true;
53
+ }
54
+ return payload.sectionComments.some((comment) => comment.body.trim().length > 0);
42
55
  }
43
56
 
44
57
  export function composeAnnotateLastMessagePrompt(
45
- message: LastAssistantMessageData,
46
- payload: AnnotateLastMessageSubmitPayload,
58
+ message: LastAssistantMessageData,
59
+ payload: AnnotateLastMessageSubmitPayload,
47
60
  ): string {
48
- const lineMap = new Map(message.lines.map((line) => [line.number, line]));
49
- const sectionMap = new Map(message.sections.map((section) => [section.id, section]));
50
- const inlineComments = payload.inlineComments
51
- .filter((comment) => comment.body.trim().length > 0)
52
- .sort((left, right) => left.line - right.line);
53
- const sectionComments = payload.sectionComments
54
- .filter((comment) => comment.body.trim().length > 0)
55
- .sort((left, right) => (sectionMap.get(left.sectionId)?.index ?? Number.MAX_SAFE_INTEGER) - (sectionMap.get(right.sectionId)?.index ?? Number.MAX_SAFE_INTEGER));
56
- const lines: string[] = [];
61
+ const lineMap = new Map(message.lines.map((line) => [line.number, line]));
62
+ const sectionMap = new Map(message.sections.map((section) => [section.id, section]));
63
+ const inlineComments = payload.inlineComments
64
+ .filter((comment) => comment.body.trim().length > 0)
65
+ .sort((left, right) => left.line - right.line);
66
+ const sectionComments = payload.sectionComments
67
+ .filter((comment) => comment.body.trim().length > 0)
68
+ .sort(
69
+ (left, right) =>
70
+ (sectionMap.get(left.sectionId)?.index ?? Number.MAX_SAFE_INTEGER) -
71
+ (sectionMap.get(right.sectionId)?.index ?? Number.MAX_SAFE_INTEGER),
72
+ );
73
+ const lines: string[] = [];
57
74
 
58
- lines.push("Please revisit your last assistant message using the annotation feedback below.");
59
- lines.push("");
60
- lines.push("Treat this as planning-oriented feedback:");
61
- lines.push("- update your explanation, plan, or proposed approach in chat;");
62
- lines.push("- do not assume any code or file changes have already been applied;");
63
- lines.push("- do not auto-apply anything outside the normal response flow.");
64
- lines.push("");
75
+ lines.push("Please revisit your last assistant message using the annotation feedback below.");
76
+ lines.push("");
77
+ lines.push("Treat this as planning-oriented feedback:");
78
+ lines.push("- update your explanation, plan, or proposed approach in chat;");
79
+ lines.push("- do not assume any code or file changes have already been applied;");
80
+ lines.push("- do not auto-apply anything outside the normal response flow.");
81
+ lines.push("");
65
82
 
66
- const overallComment = payload.overallComment.trim();
67
- if (overallComment.length > 0) {
68
- lines.push("## Overall guidance");
69
- lines.push(overallComment);
70
- lines.push("");
71
- }
83
+ const overallComment = payload.overallComment.trim();
84
+ if (overallComment.length > 0) {
85
+ lines.push("## Overall guidance");
86
+ lines.push(overallComment);
87
+ lines.push("");
88
+ }
72
89
 
73
- if (sectionComments.length > 0) {
74
- lines.push("## Section comments");
75
- sectionComments.forEach((comment, index) => {
76
- lines.push(`${index + 1}. ${formatSectionComment(comment, sectionMap.get(comment.sectionId))}`);
77
- lines.push(` ${comment.body.trim()}`);
78
- lines.push("");
79
- });
80
- }
90
+ if (sectionComments.length > 0) {
91
+ lines.push("## Section comments");
92
+ sectionComments.forEach((comment, index) => {
93
+ lines.push(
94
+ `${index + 1}. ${formatSectionComment(comment, sectionMap.get(comment.sectionId))}`,
95
+ );
96
+ lines.push(` ${comment.body.trim()}`);
97
+ lines.push("");
98
+ });
99
+ }
81
100
 
82
- if (inlineComments.length > 0) {
83
- lines.push("## Inline comments");
84
- inlineComments.forEach((comment, index) => {
85
- lines.push(`${index + 1}. ${formatLineComment(comment, lineMap.get(comment.line))}`);
86
- lines.push(` ${comment.body.trim()}`);
87
- lines.push("");
88
- });
89
- }
101
+ if (inlineComments.length > 0) {
102
+ lines.push("## Inline comments");
103
+ inlineComments.forEach((comment, index) => {
104
+ lines.push(`${index + 1}. ${formatLineComment(comment, lineMap.get(comment.line))}`);
105
+ lines.push(` ${comment.body.trim()}`);
106
+ lines.push("");
107
+ });
108
+ }
90
109
 
91
- lines.push("Please respond by revising your last message or its plan in chat, incorporating the feedback above.");
92
- return lines.join("\n").trim();
110
+ lines.push(
111
+ "Please respond by revising your last message or its plan in chat, incorporating the feedback above.",
112
+ );
113
+ return lines.join("\n").trim();
93
114
  }