@ridit/lens 0.3.5 → 0.3.7

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.
@@ -1,4 +1,5 @@
1
1
  import type { ImportantFile } from "../types/repo";
2
+ import type { Intent } from "../utils/intentClassifier";
2
3
 
3
4
  export function buildSystemPrompt(
4
5
  files: ImportantFile[],
@@ -12,25 +13,37 @@ export function buildSystemPrompt(
12
13
  const tools = toolsSection ?? BUILTIN_TOOLS_SECTION;
13
14
 
14
15
  return `You are an expert software engineer assistant with access to the user's codebase and tools.
15
-
16
+
16
17
  ${tools}
17
-
18
+
18
19
  ## MEMORY OPERATIONS
19
-
20
+
20
21
  You can save and delete memories at any time by emitting these tags alongside your normal response.
21
22
  They are stripped before display — the user will not see the raw tags.
22
-
23
- ### memory-add — save something important to long-term memory for this repo
23
+
24
+ ### memory-add — save something important to long-term memory
24
25
  <memory-add>User prefers TypeScript strict mode in all new files</memory-add>
25
-
26
+
27
+ Use [global] prefix for things that apply across ALL repos (user preferences, name, coding style):
28
+ <memory-add>[global] User prefers bun over npm for all projects</memory-add>
29
+
30
+ Omit [global] for repo-specific memories (architecture decisions, patterns, agreed conventions):
31
+ <memory-add>This repo uses path aliases defined in tsconfig.json</memory-add>
32
+
26
33
  ### memory-delete — delete a memory by its ID (shown in brackets like [abc123])
27
34
  <memory-delete>abc123</memory-delete>
28
-
29
- Use memory-add when the user asks you to remember something, or when you learn something project-specific that would be useful in future sessions.
35
+
36
+ Use memory-add ONLY for information that cannot be inferred by reading the codebase:
37
+ - User preferences and coding conventions
38
+ - Decisions made during the session (e.g. "user chose bun over npm")
39
+ - Things the user explicitly asked you to remember
40
+ - Cross-session context that would otherwise be lost
41
+
42
+ NEVER save memories that just describe what files exist or what the project does — that can be read directly from the codebase.
30
43
  Use memory-delete when the user asks you to forget something or a memory is outdated.
31
-
44
+
32
45
  ## RULES
33
-
46
+
34
47
  1. ONE tool per response — emit the XML tag, then stop. Never chain tools in one response except when scaffolding (see below).
35
48
  2. NEVER call a tool more than once for the same path in a session. If write-file or shell returned a result, it succeeded. Move on immediately.
36
49
  3. NEVER write the same file twice in one session. One write per file, period. If you already wrote it, it is done.
@@ -48,15 +61,17 @@ Use memory-delete when the user asks you to forget something or a memory is outd
48
61
  15. When explaining how to use a tool in text, use [tag] bracket notation — NEVER emit a real XML tool tag as part of an explanation.
49
62
  16. NEVER use markdown formatting in plain text responses — no bold, no headings, no bullet points. Only use fenced code blocks when showing actual code.
50
63
  17. When scaffolding multiple files, emit ONE write-file tag per response and wait for the result before writing the next file.
51
-
64
+ 18. When you identify a bug or error, ALWAYS write the fix immediately using write-file or changes. Never describe the fix without writing it.
65
+ 19. NEVER use shell for filesystem inspection or searching — always use grep, read-file, or read-folder instead.
66
+
52
67
  ## ADDON FORMAT
53
-
68
+
54
69
  All addons use defineTool from @ridit/lens-sdk. The ONLY correct format is:
55
-
70
+
56
71
  \`\`\`js
57
72
  const { defineTool } = require("@ridit/lens-sdk");
58
73
  const { execSync } = require("child_process");
59
-
74
+
60
75
  defineTool({
61
76
  name: "tool-name",
62
77
  description: "what it does",
@@ -72,26 +87,146 @@ defineTool({
72
87
  },
73
88
  });
74
89
  \`\`\`
75
-
90
+
76
91
  NEVER use module.exports, registerTool, ctx.tools.shell, or any other format. See addons/run-tests.js for a full working example.
77
-
92
+
78
93
  ## SCAFFOLDING
79
-
94
+
80
95
  When creating multiple files, emit ONE write-file per response and wait for each result:
81
-
96
+
82
97
  <write-file>
83
98
  {"path": "myapp/package.json", "content": "..."}
84
99
  </write-file>
85
-
100
+
86
101
  Wait for result, then emit the next file. Never chain write-file tags when content is complex.
87
-
102
+
88
103
  ## CODEBASE
89
-
104
+
90
105
  ${fileList.length > 0 ? fileList : "(no files indexed)"}
91
-
106
+
92
107
  ${memorySummary}`;
93
108
  }
94
109
 
110
+ export function buildBuiltinToolsSection(intent: Intent = "any"): string {
111
+ const isReadonly = intent === "readonly";
112
+
113
+ const readTools = `### 1. fetch — load a URL
114
+ <fetch>https://example.com</fetch>
115
+
116
+ ### 2. read-file — read a single file from the repo
117
+ <read-file>src/foo.ts</read-file>
118
+
119
+ ### 3. read-files — read multiple files at once
120
+ <read-files>
121
+ ["src/foo.ts", "src/bar.ts"]
122
+ </read-files>
123
+
124
+ ### 4. read-folder — list contents of a folder (one level deep)
125
+ <read-folder>src/components</read-folder>
126
+
127
+ ### 5. grep — search for a pattern across files
128
+ <grep>
129
+ {"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
130
+ </grep>
131
+
132
+ ### 6. search — search the internet
133
+ <search>how to use React useEffect cleanup</search>`;
134
+
135
+ const writeTools = `### 7. shell — run a terminal command (NOT for filesystem inspection)
136
+ <shell>node -v</shell>
137
+
138
+ ### 8. write-file — create or overwrite a file (COMPLETE content only)
139
+ <write-file>
140
+ {"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
141
+ </write-file>
142
+
143
+ ### 9. delete-file — permanently delete a single file
144
+ <delete-file>src/old-component.tsx</delete-file>
145
+
146
+ ### 10. delete-folder — permanently delete a folder and all its contents
147
+ <delete-folder>src/legacy</delete-folder>
148
+
149
+ ### 11. open-url — open a URL in the user's default browser
150
+ <open-url>https://github.com/owner/repo</open-url>
151
+
152
+ ### 12. generate-pdf — generate a PDF from markdown-style content
153
+ <generate-pdf>
154
+ {"path": "output/report.pdf", "content": "# Title\\n\\nBody text."}
155
+ </generate-pdf>
156
+
157
+ ### 13. clone — clone a GitHub repo
158
+ <clone>https://github.com/owner/repo</clone>
159
+
160
+ ### 14. changes — propose code edits shown as a diff for user approval
161
+ <changes>
162
+ {"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
163
+ </changes>`;
164
+
165
+ if (isReadonly) {
166
+ return `## TOOLS
167
+
168
+ You have 6 tools available for this read-only request. Do NOT attempt to write, delete, or run shell commands — those tools are not available right now.
169
+
170
+ ${readTools}`;
171
+ }
172
+
173
+ return `## TOOLS
174
+
175
+ You have exactly 14 tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
176
+
177
+ ### 1. fetch — load a URL
178
+ <fetch>https://example.com</fetch>
179
+
180
+ ### 2. shell — run a terminal command (NOT for filesystem inspection)
181
+ <shell>node -v</shell>
182
+
183
+ ### 3. read-file — read a single file from the repo
184
+ <read-file>src/foo.ts</read-file>
185
+
186
+ ### 4. read-files — read multiple files at once
187
+ <read-files>
188
+ ["src/foo.ts", "src/bar.ts"]
189
+ </read-files>
190
+
191
+ ### 5. read-folder — list contents of a folder (one level deep)
192
+ <read-folder>src/components</read-folder>
193
+
194
+ ### 6. grep — search for a pattern across files
195
+ <grep>
196
+ {"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
197
+ </grep>
198
+
199
+ ### 7. write-file — create or overwrite a file (COMPLETE content only)
200
+ <write-file>
201
+ {"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
202
+ </write-file>
203
+
204
+ ### 8. delete-file — permanently delete a single file
205
+ <delete-file>src/old-component.tsx</delete-file>
206
+
207
+ ### 9. delete-folder — permanently delete a folder and all its contents
208
+ <delete-folder>src/legacy</delete-folder>
209
+
210
+ ### 10. open-url — open a URL in the user's default browser
211
+ <open-url>https://github.com/owner/repo</open-url>
212
+
213
+ ### 11. generate-pdf — generate a PDF from markdown-style content
214
+ <generate-pdf>
215
+ {"path": "output/report.pdf", "content": "# Title\\n\\nBody text."}
216
+ </generate-pdf>
217
+
218
+ ### 12. search — search the internet
219
+ <search>how to use React useEffect cleanup</search>
220
+
221
+ ### 13. clone — clone a GitHub repo
222
+ <clone>https://github.com/owner/repo</clone>
223
+
224
+ ### 14. changes — propose code edits shown as a diff for user approval
225
+ <changes>
226
+ {"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
227
+ </changes>`;
228
+ }
229
+
95
230
  const BUILTIN_TOOLS_SECTION = `## TOOLS
96
231
 
97
232
  You have exactly fourteen tools. Use ONLY the XML tags shown below.
package/src/tools/git.ts CHANGED
@@ -27,6 +27,7 @@ function parseArgs(body: string): GitArgs | null {
27
27
  export const gitStatusTool: Tool<GitArgs> = {
28
28
  name: "git-status",
29
29
  description: "show working tree status",
30
+ tag: "git",
30
31
  safe: true,
31
32
  permissionLabel: "git status",
32
33
  systemPromptEntry: (i) =>
@@ -45,6 +46,7 @@ export const gitStatusTool: Tool<GitArgs> = {
45
46
  export const gitLogTool: Tool<GitArgs> = {
46
47
  name: "git-log",
47
48
  description: "show commit log",
49
+ tag: "git",
48
50
  safe: true,
49
51
  permissionLabel: "git log",
50
52
  systemPromptEntry: (i) =>
@@ -60,6 +62,7 @@ export const gitLogTool: Tool<GitArgs> = {
60
62
  export const gitDiffTool: Tool<GitArgs> = {
61
63
  name: "git-diff",
62
64
  description: "show changes between commits, working tree, or staged files",
65
+ tag: "git",
63
66
  safe: true,
64
67
  permissionLabel: "git diff",
65
68
  systemPromptEntry: (i) =>
@@ -75,6 +78,7 @@ export const gitDiffTool: Tool<GitArgs> = {
75
78
  export const gitShowTool: Tool<GitArgs> = {
76
79
  name: "git-show",
77
80
  description: "show a commit's details and stat",
81
+ tag: "git",
78
82
  safe: true,
79
83
  permissionLabel: "git show",
80
84
  systemPromptEntry: (i) =>
@@ -90,6 +94,7 @@ export const gitShowTool: Tool<GitArgs> = {
90
94
  export const gitBranchTool: Tool<GitArgs> = {
91
95
  name: "git-branch",
92
96
  description: "list branches",
97
+ tag: "git",
93
98
  safe: true,
94
99
  permissionLabel: "git branch",
95
100
  systemPromptEntry: (i) =>
@@ -105,6 +110,7 @@ export const gitBranchTool: Tool<GitArgs> = {
105
110
  export const gitRemoteTool: Tool<GitArgs> = {
106
111
  name: "git-remote",
107
112
  description: "list or inspect remotes",
113
+ tag: "git",
108
114
  safe: true,
109
115
  permissionLabel: "git remote",
110
116
  systemPromptEntry: (i) =>
@@ -120,6 +126,7 @@ export const gitRemoteTool: Tool<GitArgs> = {
120
126
  export const gitTagTool: Tool<GitArgs> = {
121
127
  name: "git-tag",
122
128
  description: "list tags",
129
+ tag: "git",
123
130
  safe: true,
124
131
  permissionLabel: "git tag",
125
132
  systemPromptEntry: (i) =>
@@ -135,6 +142,7 @@ export const gitTagTool: Tool<GitArgs> = {
135
142
  export const gitBlameTool: Tool<GitArgs> = {
136
143
  name: "git-blame",
137
144
  description: "show who last modified each line of a file",
145
+ tag: "git",
138
146
  safe: true,
139
147
  permissionLabel: "git blame",
140
148
  systemPromptEntry: (i) =>
@@ -168,6 +176,7 @@ export const gitBlameTool: Tool<GitArgs> = {
168
176
  export const gitStashListTool: Tool<GitArgs> = {
169
177
  name: "git-stash-list",
170
178
  description: "list stashed changes",
179
+ tag: "git",
171
180
  safe: true,
172
181
  permissionLabel: "git stash list",
173
182
  systemPromptEntry: (i) =>
@@ -183,6 +192,7 @@ export const gitStashListTool: Tool<GitArgs> = {
183
192
  export const gitAddTool: Tool<GitArgs> = {
184
193
  name: "git-add",
185
194
  description: "stage files for commit",
195
+ tag: "git",
186
196
  safe: false,
187
197
  permissionLabel: "git add",
188
198
  systemPromptEntry: (i) =>
@@ -202,6 +212,7 @@ export const gitAddTool: Tool<GitArgs> = {
202
212
  export const gitCommitTool: Tool<GitArgs> = {
203
213
  name: "git-commit",
204
214
  description: "commit staged changes with a message",
215
+ tag: "git",
205
216
  safe: false,
206
217
  permissionLabel: "git commit",
207
218
  systemPromptEntry: (i) =>
@@ -222,6 +233,7 @@ export const gitCommitTool: Tool<GitArgs> = {
222
233
  export const gitCommitAmendTool: Tool<GitArgs> = {
223
234
  name: "git-commit-amend",
224
235
  description: "amend the last commit message",
236
+ tag: "git",
225
237
  safe: false,
226
238
  permissionLabel: "git commit --amend",
227
239
  systemPromptEntry: (i) =>
@@ -246,6 +258,7 @@ export const gitRevertTool: Tool<GitArgs> = {
246
258
  name: "git-revert",
247
259
  description:
248
260
  "revert a commit by hash (creates a new revert commit, history preserved)",
261
+ tag: "git",
249
262
  safe: false,
250
263
  permissionLabel: "git revert",
251
264
  systemPromptEntry: (i) =>
@@ -266,6 +279,7 @@ export const gitRevertTool: Tool<GitArgs> = {
266
279
  export const gitResetTool: Tool<GitArgs> = {
267
280
  name: "git-reset",
268
281
  description: "reset HEAD or unstage files",
282
+ tag: "git",
269
283
  safe: false,
270
284
  permissionLabel: "git reset",
271
285
  systemPromptEntry: (i) =>
@@ -285,6 +299,7 @@ export const gitResetTool: Tool<GitArgs> = {
285
299
  export const gitCheckoutTool: Tool<GitArgs> = {
286
300
  name: "git-checkout",
287
301
  description: "switch branches or restore files",
302
+ tag: "git",
288
303
  safe: false,
289
304
  permissionLabel: "git checkout",
290
305
  systemPromptEntry: (i) =>
@@ -305,6 +320,7 @@ export const gitCheckoutTool: Tool<GitArgs> = {
305
320
  export const gitSwitchTool: Tool<GitArgs> = {
306
321
  name: "git-switch",
307
322
  description: "switch or create branches",
323
+ tag: "git",
308
324
  safe: false,
309
325
  permissionLabel: "git switch",
310
326
  systemPromptEntry: (i) =>
@@ -325,6 +341,7 @@ export const gitSwitchTool: Tool<GitArgs> = {
325
341
  export const gitMergeTool: Tool<GitArgs> = {
326
342
  name: "git-merge",
327
343
  description: "merge a branch into the current branch",
344
+ tag: "git",
328
345
  safe: false,
329
346
  permissionLabel: "git merge",
330
347
  systemPromptEntry: (i) =>
@@ -345,6 +362,7 @@ export const gitMergeTool: Tool<GitArgs> = {
345
362
  export const gitPullTool: Tool<GitArgs> = {
346
363
  name: "git-pull",
347
364
  description: "pull from remote",
365
+ tag: "git",
348
366
  safe: false,
349
367
  permissionLabel: "git pull",
350
368
  systemPromptEntry: (i) =>
@@ -363,6 +381,7 @@ export const gitPullTool: Tool<GitArgs> = {
363
381
  export const gitPushTool: Tool<GitArgs> = {
364
382
  name: "git-push",
365
383
  description: "push commits to remote",
384
+ tag: "git",
366
385
  safe: false,
367
386
  permissionLabel: "git push",
368
387
  systemPromptEntry: (i) =>
@@ -381,6 +400,7 @@ export const gitPushTool: Tool<GitArgs> = {
381
400
  export const gitStashTool: Tool<GitArgs> = {
382
401
  name: "git-stash",
383
402
  description: "stash or apply stashed changes",
403
+ tag: "git",
384
404
  safe: false,
385
405
  permissionLabel: "git stash",
386
406
  systemPromptEntry: (i) =>
@@ -401,6 +421,7 @@ export const gitStashTool: Tool<GitArgs> = {
401
421
  export const gitBranchCreateTool: Tool<GitArgs> = {
402
422
  name: "git-branch-create",
403
423
  description: "create a new branch without switching to it",
424
+ tag: "git",
404
425
  safe: false,
405
426
  permissionLabel: "git branch (create)",
406
427
  systemPromptEntry: (i) =>
@@ -421,6 +442,7 @@ export const gitBranchCreateTool: Tool<GitArgs> = {
421
442
  export const gitBranchDeleteTool: Tool<GitArgs> = {
422
443
  name: "git-branch-delete",
423
444
  description: "delete a branch",
445
+ tag: "git",
424
446
  safe: false,
425
447
  permissionLabel: "git branch -d",
426
448
  systemPromptEntry: (i) =>
@@ -444,6 +466,7 @@ export const gitBranchDeleteTool: Tool<GitArgs> = {
444
466
  export const gitCherryPickTool: Tool<GitArgs> = {
445
467
  name: "git-cherry-pick",
446
468
  description: "apply a specific commit from another branch",
469
+ tag: "git",
447
470
  safe: false,
448
471
  permissionLabel: "git cherry-pick",
449
472
  systemPromptEntry: (i) =>
@@ -466,6 +489,7 @@ export const gitCherryPickTool: Tool<GitArgs> = {
466
489
  export const gitTagCreateTool: Tool<GitArgs> = {
467
490
  name: "git-tag-create",
468
491
  description: "create a lightweight or annotated tag",
492
+ tag: "git",
469
493
  safe: false,
470
494
  permissionLabel: "git tag (create)",
471
495
  systemPromptEntry: (i) =>
@@ -486,6 +510,7 @@ export const gitRestoreTool: Tool<GitArgs> = {
486
510
  name: "git-restore",
487
511
  description:
488
512
  "discard working directory changes for a file (cannot be undone)",
513
+ tag: "git",
489
514
  safe: false,
490
515
  permissionLabel: "git restore",
491
516
  systemPromptEntry: (i) =>
@@ -505,6 +530,7 @@ export const gitRestoreTool: Tool<GitArgs> = {
505
530
  export const gitCleanTool: Tool<GitArgs> = {
506
531
  name: "git-clean",
507
532
  description: "remove untracked files (cannot be undone)",
533
+ tag: "git",
508
534
  safe: false,
509
535
  permissionLabel: "git clean",
510
536
  systemPromptEntry: (i) =>
package/src/utils/chat.ts CHANGED
@@ -19,6 +19,11 @@ import { FEW_SHOT_MESSAGES } from "../prompts";
19
19
  import { registry } from "../utils/tools/registry";
20
20
  import type { FilePatch } from "../components/repo/DiffViewer";
21
21
 
22
+ export type ChatResult = {
23
+ text: string;
24
+ truncated: boolean;
25
+ };
26
+
22
27
  export type ParsedResponse =
23
28
  | { kind: "text"; content: string; remainder?: string }
24
29
  | {
@@ -219,7 +224,7 @@ export async function callChat(
219
224
  messages: Message[],
220
225
  abortSignal?: AbortSignal,
221
226
  retries = 2,
222
- ): Promise<string> {
227
+ ): Promise<ChatResult> {
223
228
  const apiMessages = [
224
229
  ...buildFewShotMessages(),
225
230
  ...buildApiMessages(messages),
@@ -288,13 +293,20 @@ export async function callChat(
288
293
 
289
294
  if (provider.type === "anthropic") {
290
295
  const content = data.content as { type: string; text: string }[];
291
- return content
296
+ const text = content
292
297
  .filter((b) => b.type === "text")
293
298
  .map((b) => b.text)
294
299
  .join("");
300
+ const truncated = (data as any).stop_reason === "max_tokens";
301
+ return { text, truncated };
295
302
  } else {
296
- const choices = data.choices as { message: { content: string } }[];
297
- return choices[0]?.message.content ?? "";
303
+ const choices = data.choices as {
304
+ message: { content: string };
305
+ finish_reason?: string;
306
+ }[];
307
+ const text = choices[0]?.message.content ?? "";
308
+ const truncated = choices[0]?.finish_reason === "length";
309
+ return { text, truncated };
298
310
  }
299
311
  } catch (err) {
300
312
  clearTimeout(timer);
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Classifies user message intent to scope which tools the LLM is allowed to use.
3
+ *
4
+ * readonly → only read/search/fetch tools exposed (no write, delete, shell)
5
+ * mutating → all tools exposed
6
+ * any → all tools exposed (ambiguous / can't tell)
7
+ */
8
+ export type Intent = "readonly" | "mutating" | "any";
9
+
10
+ const READONLY_PATTERNS: RegExp[] = [
11
+ // listing / exploring
12
+ /\b(list|ls|dir|show|display|print|dump)\b/i,
13
+ /\bwhat(('?s| is| are| does)\b| files| folder)/i,
14
+ /\b(folder|directory|file) (structure|tree|layout|contents?)\b/i,
15
+ /\bexplore\b/i,
16
+
17
+ // reading / explaining
18
+ /\b(read|open|view|look at|check out|inspect|peek)\b/i,
19
+ /\b(explain|describe|summarize|summarise|tell me about|walk me through)\b/i,
20
+ /\bhow does\b/i,
21
+ /\bwhat('?s| is) (in|inside|this|that|the)\b/i,
22
+
23
+ // searching
24
+ /\b(find|search|grep|locate|where is|where are)\b/i,
25
+ /\b(look for|scan|trace)\b/i,
26
+
27
+ // understanding
28
+ /\bunderstand\b/i,
29
+ /\bshow me (how|what|where|why)\b/i,
30
+ ];
31
+
32
+ const MUTATING_PATTERNS: RegExp[] = [
33
+ // writing
34
+ /\b(write|create|make|generate|add|build|scaffold|init|initialize|setup|set up)\b/i,
35
+ /\b(new file|new folder|new component|new page|new route)\b/i,
36
+
37
+ // editing
38
+ /\b(edit|modify|update|change|refactor|rename|move|migrate)\b/i,
39
+ /\b(fix|patch|resolve|correct|debug|repair)\b/i,
40
+ /\b(implement|add .+ to|insert|inject|append|prepend)\b/i,
41
+
42
+ // deleting
43
+ /\b(delete|remove|drop|clean ?up|purge|wipe)\b/i,
44
+
45
+ // running
46
+ /\b(run|execute|install|deploy|build|test|start|launch|compile|lint|format)\b/i,
47
+ ];
48
+
49
+ export function classifyIntent(userMessage: string): Intent {
50
+ const text = userMessage.trim();
51
+
52
+ const mutatingScore = MUTATING_PATTERNS.filter((p) => p.test(text)).length;
53
+ const readonlyScore = READONLY_PATTERNS.filter((p) => p.test(text)).length;
54
+
55
+ if (mutatingScore === 0 && readonlyScore > 0) return "readonly";
56
+ if (mutatingScore > 0) return "mutating";
57
+ return "any";
58
+ }