@ridit/lens 0.3.5 → 0.3.6

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/dist/index.mjs CHANGED
@@ -258398,6 +258398,12 @@ var require_asciichart = __commonJS((exports) => {
258398
258398
  });
258399
258399
 
258400
258400
  // src/utils/tools/registry.ts
258401
+ var INTENT_ALLOWED = {
258402
+ readonly: ["read", "net"],
258403
+ mutating: ["read", "net", "write", "delete", "shell"],
258404
+ any: ["read", "net", "write", "delete", "shell"]
258405
+ };
258406
+
258401
258407
  class ToolRegistry {
258402
258408
  tools = new Map;
258403
258409
  register(tool) {
@@ -258418,13 +258424,34 @@ class ToolRegistry {
258418
258424
  names() {
258419
258425
  return Array.from(this.tools.keys());
258420
258426
  }
258421
- buildSystemPromptSection() {
258427
+ namesForIntent(intent) {
258428
+ const allowed = new Set(INTENT_ALLOWED[intent]);
258429
+ return Array.from(this.tools.values()).filter((t) => {
258430
+ const tag = t.tag;
258431
+ if (!tag)
258432
+ return true;
258433
+ return allowed.has(tag);
258434
+ }).map((t) => t.name);
258435
+ }
258436
+ buildSystemPromptSection(intent = "any") {
258437
+ const allowed = new Set(INTENT_ALLOWED[intent]);
258438
+ const visible = Array.from(this.tools.values()).filter((t) => {
258439
+ const tag = t.tag;
258440
+ if (!tag)
258441
+ return true;
258442
+ return allowed.has(tag);
258443
+ });
258422
258444
  const lines = [`## TOOLS
258423
258445
  `];
258424
- lines.push("You have exactly " + this.tools.size + ` tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
258446
+ if (intent === "readonly") {
258447
+ lines.push(`You have ${visible.length} tools available for this read-only request. ` + `Do NOT attempt to write, delete, or run shell commands — ` + `those tools are not available right now.
258425
258448
  `);
258449
+ } else {
258450
+ lines.push(`You have exactly ${visible.length} tools. To use a tool you MUST wrap it ` + `in the exact XML tags shown below — no other format will work.
258451
+ `);
258452
+ }
258426
258453
  let i = 1;
258427
- for (const tool of this.tools.values()) {
258454
+ for (const tool of visible) {
258428
258455
  lines.push(tool.systemPromptEntry(i++));
258429
258456
  }
258430
258457
  return lines.join(`
@@ -268627,25 +268654,25 @@ ${f.content.slice(0, 2000)}
268627
268654
  `);
268628
268655
  const tools = toolsSection ?? BUILTIN_TOOLS_SECTION;
268629
268656
  return `You are an expert software engineer assistant with access to the user's codebase and tools.
268630
-
268657
+
268631
268658
  ${tools}
268632
-
268659
+
268633
268660
  ## MEMORY OPERATIONS
268634
-
268661
+
268635
268662
  You can save and delete memories at any time by emitting these tags alongside your normal response.
268636
268663
  They are stripped before display — the user will not see the raw tags.
268637
-
268664
+
268638
268665
  ### memory-add — save something important to long-term memory for this repo
268639
268666
  <memory-add>User prefers TypeScript strict mode in all new files</memory-add>
268640
-
268667
+
268641
268668
  ### memory-delete — delete a memory by its ID (shown in brackets like [abc123])
268642
268669
  <memory-delete>abc123</memory-delete>
268643
-
268670
+
268644
268671
  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.
268645
268672
  Use memory-delete when the user asks you to forget something or a memory is outdated.
268646
-
268673
+
268647
268674
  ## RULES
268648
-
268675
+
268649
268676
  1. ONE tool per response — emit the XML tag, then stop. Never chain tools in one response except when scaffolding (see below).
268650
268677
  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.
268651
268678
  3. NEVER write the same file twice in one session. One write per file, period. If you already wrote it, it is done.
@@ -268663,15 +268690,15 @@ Use memory-delete when the user asks you to forget something or a memory is outd
268663
268690
  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.
268664
268691
  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.
268665
268692
  17. When scaffolding multiple files, emit ONE write-file tag per response and wait for the result before writing the next file.
268666
-
268693
+
268667
268694
  ## ADDON FORMAT
268668
-
268695
+
268669
268696
  All addons use defineTool from @ridit/lens-sdk. The ONLY correct format is:
268670
-
268697
+
268671
268698
  \`\`\`js
268672
268699
  const { defineTool } = require("@ridit/lens-sdk");
268673
268700
  const { execSync } = require("child_process");
268674
-
268701
+
268675
268702
  defineTool({
268676
268703
  name: "tool-name",
268677
268704
  description: "what it does",
@@ -268687,23 +268714,23 @@ defineTool({
268687
268714
  },
268688
268715
  });
268689
268716
  \`\`\`
268690
-
268717
+
268691
268718
  NEVER use module.exports, registerTool, ctx.tools.shell, or any other format. See addons/run-tests.js for a full working example.
268692
-
268719
+
268693
268720
  ## SCAFFOLDING
268694
-
268721
+
268695
268722
  When creating multiple files, emit ONE write-file per response and wait for each result:
268696
-
268723
+
268697
268724
  <write-file>
268698
268725
  {"path": "myapp/package.json", "content": "..."}
268699
268726
  </write-file>
268700
-
268727
+
268701
268728
  Wait for result, then emit the next file. Never chain write-file tags when content is complex.
268702
-
268729
+
268703
268730
  ## CODEBASE
268704
-
268731
+
268705
268732
  ${fileList.length > 0 ? fileList : "(no files indexed)"}
268706
-
268733
+
268707
268734
  ${memorySummary}`;
268708
268735
  }
268709
268736
  var BUILTIN_TOOLS_SECTION = `## TOOLS
@@ -272908,6 +272935,7 @@ function parseArgs(body) {
272908
272935
  var gitStatusTool = {
272909
272936
  name: "git-status",
272910
272937
  description: "show working tree status",
272938
+ tag: "git",
272911
272939
  safe: true,
272912
272940
  permissionLabel: "git status",
272913
272941
  systemPromptEntry: (i) => `### ${i}. git-status — show working tree status (staged, unstaged, untracked)
@@ -272925,6 +272953,7 @@ var gitStatusTool = {
272925
272953
  var gitLogTool = {
272926
272954
  name: "git-log",
272927
272955
  description: "show commit log",
272956
+ tag: "git",
272928
272957
  safe: true,
272929
272958
  permissionLabel: "git log",
272930
272959
  systemPromptEntry: (i) => `### ${i}. git-log — show commit log with optional flags
@@ -272941,6 +272970,7 @@ var gitLogTool = {
272941
272970
  var gitDiffTool = {
272942
272971
  name: "git-diff",
272943
272972
  description: "show changes between commits, working tree, or staged files",
272973
+ tag: "git",
272944
272974
  safe: true,
272945
272975
  permissionLabel: "git diff",
272946
272976
  systemPromptEntry: (i) => `### ${i}. git-diff — show changes
@@ -272958,6 +272988,7 @@ var gitDiffTool = {
272958
272988
  var gitShowTool = {
272959
272989
  name: "git-show",
272960
272990
  description: "show a commit's details and stat",
272991
+ tag: "git",
272961
272992
  safe: true,
272962
272993
  permissionLabel: "git show",
272963
272994
  systemPromptEntry: (i) => `### ${i}. git-show — show a commit's details
@@ -272973,6 +273004,7 @@ var gitShowTool = {
272973
273004
  var gitBranchTool = {
272974
273005
  name: "git-branch",
272975
273006
  description: "list branches",
273007
+ tag: "git",
272976
273008
  safe: true,
272977
273009
  permissionLabel: "git branch",
272978
273010
  systemPromptEntry: (i) => `### ${i}. git-branch — list branches
@@ -272989,6 +273021,7 @@ var gitBranchTool = {
272989
273021
  var gitRemoteTool = {
272990
273022
  name: "git-remote",
272991
273023
  description: "list or inspect remotes",
273024
+ tag: "git",
272992
273025
  safe: true,
272993
273026
  permissionLabel: "git remote",
272994
273027
  systemPromptEntry: (i) => `### ${i}. git-remote — list remotes
@@ -273003,6 +273036,7 @@ var gitRemoteTool = {
273003
273036
  var gitTagTool = {
273004
273037
  name: "git-tag",
273005
273038
  description: "list tags",
273039
+ tag: "git",
273006
273040
  safe: true,
273007
273041
  permissionLabel: "git tag",
273008
273042
  systemPromptEntry: (i) => `### ${i}. git-tag — list tags
@@ -273018,6 +273052,7 @@ var gitTagTool = {
273018
273052
  var gitBlameTool = {
273019
273053
  name: "git-blame",
273020
273054
  description: "show who last modified each line of a file",
273055
+ tag: "git",
273021
273056
  safe: true,
273022
273057
  permissionLabel: "git blame",
273023
273058
  systemPromptEntry: (i) => `### ${i}. git-blame — show per-line authorship
@@ -273053,6 +273088,7 @@ var gitBlameTool = {
273053
273088
  var gitStashListTool = {
273054
273089
  name: "git-stash-list",
273055
273090
  description: "list stashed changes",
273091
+ tag: "git",
273056
273092
  safe: true,
273057
273093
  permissionLabel: "git stash list",
273058
273094
  systemPromptEntry: (i) => `### ${i}. git-stash-list — list stashes
@@ -273067,6 +273103,7 @@ var gitStashListTool = {
273067
273103
  var gitAddTool = {
273068
273104
  name: "git-add",
273069
273105
  description: "stage files for commit",
273106
+ tag: "git",
273070
273107
  safe: false,
273071
273108
  permissionLabel: "git add",
273072
273109
  systemPromptEntry: (i) => `### ${i}. git-add — stage files
@@ -273087,6 +273124,7 @@ var gitAddTool = {
273087
273124
  var gitCommitTool = {
273088
273125
  name: "git-commit",
273089
273126
  description: "commit staged changes with a message",
273127
+ tag: "git",
273090
273128
  safe: false,
273091
273129
  permissionLabel: "git commit",
273092
273130
  systemPromptEntry: (i) => `### ${i}. git-commit — commit staged changes (stage with git-add first)
@@ -273107,6 +273145,7 @@ var gitCommitTool = {
273107
273145
  var gitCommitAmendTool = {
273108
273146
  name: "git-commit-amend",
273109
273147
  description: "amend the last commit message",
273148
+ tag: "git",
273110
273149
  safe: false,
273111
273150
  permissionLabel: "git commit --amend",
273112
273151
  systemPromptEntry: (i) => `### ${i}. git-commit-amend — amend the last commit message
@@ -273127,6 +273166,7 @@ var gitCommitAmendTool = {
273127
273166
  var gitRevertTool = {
273128
273167
  name: "git-revert",
273129
273168
  description: "revert a commit by hash (creates a new revert commit, history preserved)",
273169
+ tag: "git",
273130
273170
  safe: false,
273131
273171
  permissionLabel: "git revert",
273132
273172
  systemPromptEntry: (i) => `### ${i}. git-revert — revert a commit (safe, creates new commit)
@@ -273146,6 +273186,7 @@ var gitRevertTool = {
273146
273186
  var gitResetTool = {
273147
273187
  name: "git-reset",
273148
273188
  description: "reset HEAD or unstage files",
273189
+ tag: "git",
273149
273190
  safe: false,
273150
273191
  permissionLabel: "git reset",
273151
273192
  systemPromptEntry: (i) => `### ${i}. git-reset — reset HEAD or unstage files
@@ -273166,6 +273207,7 @@ var gitResetTool = {
273166
273207
  var gitCheckoutTool = {
273167
273208
  name: "git-checkout",
273168
273209
  description: "switch branches or restore files",
273210
+ tag: "git",
273169
273211
  safe: false,
273170
273212
  permissionLabel: "git checkout",
273171
273213
  systemPromptEntry: (i) => `### ${i}. git-checkout — switch branch or restore file
@@ -273186,6 +273228,7 @@ var gitCheckoutTool = {
273186
273228
  var gitSwitchTool = {
273187
273229
  name: "git-switch",
273188
273230
  description: "switch or create branches",
273231
+ tag: "git",
273189
273232
  safe: false,
273190
273233
  permissionLabel: "git switch",
273191
273234
  systemPromptEntry: (i) => `### ${i}. git-switch — switch or create branches
@@ -273206,6 +273249,7 @@ var gitSwitchTool = {
273206
273249
  var gitMergeTool = {
273207
273250
  name: "git-merge",
273208
273251
  description: "merge a branch into the current branch",
273252
+ tag: "git",
273209
273253
  safe: false,
273210
273254
  permissionLabel: "git merge",
273211
273255
  systemPromptEntry: (i) => `### ${i}. git-merge — merge a branch into HEAD
@@ -273225,6 +273269,7 @@ var gitMergeTool = {
273225
273269
  var gitPullTool = {
273226
273270
  name: "git-pull",
273227
273271
  description: "pull from remote",
273272
+ tag: "git",
273228
273273
  safe: false,
273229
273274
  permissionLabel: "git pull",
273230
273275
  systemPromptEntry: (i) => `### ${i}. git-pull — pull from remote
@@ -273243,6 +273288,7 @@ var gitPullTool = {
273243
273288
  var gitPushTool = {
273244
273289
  name: "git-push",
273245
273290
  description: "push commits to remote",
273291
+ tag: "git",
273246
273292
  safe: false,
273247
273293
  permissionLabel: "git push",
273248
273294
  systemPromptEntry: (i) => `### ${i}. git-push — push to remote
@@ -273262,6 +273308,7 @@ var gitPushTool = {
273262
273308
  var gitStashTool = {
273263
273309
  name: "git-stash",
273264
273310
  description: "stash or apply stashed changes",
273311
+ tag: "git",
273265
273312
  safe: false,
273266
273313
  permissionLabel: "git stash",
273267
273314
  systemPromptEntry: (i) => `### ${i}. git-stash — stash/apply/pop/drop changes
@@ -273283,6 +273330,7 @@ var gitStashTool = {
273283
273330
  var gitBranchCreateTool = {
273284
273331
  name: "git-branch-create",
273285
273332
  description: "create a new branch without switching to it",
273333
+ tag: "git",
273286
273334
  safe: false,
273287
273335
  permissionLabel: "git branch (create)",
273288
273336
  systemPromptEntry: (i) => `### ${i}. git-branch-create — create a branch
@@ -273302,6 +273350,7 @@ var gitBranchCreateTool = {
273302
273350
  var gitBranchDeleteTool = {
273303
273351
  name: "git-branch-delete",
273304
273352
  description: "delete a branch",
273353
+ tag: "git",
273305
273354
  safe: false,
273306
273355
  permissionLabel: "git branch -d",
273307
273356
  systemPromptEntry: (i) => `### ${i}. git-branch-delete — delete a branch
@@ -273325,6 +273374,7 @@ var gitBranchDeleteTool = {
273325
273374
  var gitCherryPickTool = {
273326
273375
  name: "git-cherry-pick",
273327
273376
  description: "apply a specific commit from another branch",
273377
+ tag: "git",
273328
273378
  safe: false,
273329
273379
  permissionLabel: "git cherry-pick",
273330
273380
  systemPromptEntry: (i) => `### ${i}. git-cherry-pick — apply a commit by hash
@@ -273344,6 +273394,7 @@ var gitCherryPickTool = {
273344
273394
  var gitTagCreateTool = {
273345
273395
  name: "git-tag-create",
273346
273396
  description: "create a lightweight or annotated tag",
273397
+ tag: "git",
273347
273398
  safe: false,
273348
273399
  permissionLabel: "git tag (create)",
273349
273400
  systemPromptEntry: (i) => `### ${i}. git-tag-create — create a tag
@@ -273364,6 +273415,7 @@ var gitTagCreateTool = {
273364
273415
  var gitRestoreTool = {
273365
273416
  name: "git-restore",
273366
273417
  description: "discard working directory changes for a file (cannot be undone)",
273418
+ tag: "git",
273367
273419
  safe: false,
273368
273420
  permissionLabel: "git restore",
273369
273421
  systemPromptEntry: (i) => `### ${i}. git-restore — discard changes in a file (irreversible)
@@ -273383,6 +273435,7 @@ var gitRestoreTool = {
273383
273435
  var gitCleanTool = {
273384
273436
  name: "git-clean",
273385
273437
  description: "remove untracked files (cannot be undone)",
273438
+ tag: "git",
273386
273439
  safe: false,
273387
273440
  permissionLabel: "git clean",
273388
273441
  systemPromptEntry: (i) => `### ${i}. git-clean — remove untracked files (irreversible)
@@ -274736,6 +274789,43 @@ function TimelineRunner({
274736
274789
  // src/components/chat/hooks/useChat.ts
274737
274790
  var import_react48 = __toESM(require_react(), 1);
274738
274791
  var import_react49 = __toESM(require_react(), 1);
274792
+
274793
+ // src/utils/intentClassifier.ts
274794
+ var READONLY_PATTERNS = [
274795
+ /\b(list|ls|dir|show|display|print|dump)\b/i,
274796
+ /\bwhat(('?s| is| are| does)\b| files| folder)/i,
274797
+ /\b(folder|directory|file) (structure|tree|layout|contents?)\b/i,
274798
+ /\bexplore\b/i,
274799
+ /\b(read|open|view|look at|check out|inspect|peek)\b/i,
274800
+ /\b(explain|describe|summarize|summarise|tell me about|walk me through)\b/i,
274801
+ /\bhow does\b/i,
274802
+ /\bwhat('?s| is) (in|inside|this|that|the)\b/i,
274803
+ /\b(find|search|grep|locate|where is|where are)\b/i,
274804
+ /\b(look for|scan|trace)\b/i,
274805
+ /\bunderstand\b/i,
274806
+ /\bshow me (how|what|where|why)\b/i
274807
+ ];
274808
+ var MUTATING_PATTERNS = [
274809
+ /\b(write|create|make|generate|add|build|scaffold|init|initialize|setup|set up)\b/i,
274810
+ /\b(new file|new folder|new component|new page|new route)\b/i,
274811
+ /\b(edit|modify|update|change|refactor|rename|move|migrate)\b/i,
274812
+ /\b(fix|patch|resolve|correct|debug|repair)\b/i,
274813
+ /\b(implement|add .+ to|insert|inject|append|prepend)\b/i,
274814
+ /\b(delete|remove|drop|clean ?up|purge|wipe)\b/i,
274815
+ /\b(run|execute|install|deploy|build|test|start|launch|compile|lint|format)\b/i
274816
+ ];
274817
+ function classifyIntent(userMessage) {
274818
+ const text = userMessage.trim();
274819
+ const mutatingScore = MUTATING_PATTERNS.filter((p) => p.test(text)).length;
274820
+ const readonlyScore = READONLY_PATTERNS.filter((p) => p.test(text)).length;
274821
+ if (mutatingScore === 0 && readonlyScore > 0)
274822
+ return "readonly";
274823
+ if (mutatingScore > 0)
274824
+ return "mutating";
274825
+ return "any";
274826
+ }
274827
+
274828
+ // src/components/chat/hooks/useChat.ts
274739
274829
  function useChat(repoPath) {
274740
274830
  const [stage, setStage] = import_react48.useState({ type: "picking-provider" });
274741
274831
  const [committed, setCommitted] = import_react48.useState([]);
@@ -275052,8 +275142,13 @@ function useChat(repoPath) {
275052
275142
  }
275053
275143
  const abort = new AbortController;
275054
275144
  abortControllerRef.current = abort;
275145
+ const intent = classifyIntent(text);
275146
+ const scopedToolsSection = registry.buildSystemPromptSection(intent);
275147
+ const scopedSystemPrompt = currentSystemPrompt.replace(/## TOOLS[\s\S]*?(?=\n## (?!TOOLS))/, scopedToolsSection + `
275148
+
275149
+ `);
275055
275150
  setStage({ type: "thinking" });
275056
- callChat(currentProvider, currentSystemPrompt, nextAll, abort.signal).then((raw) => processResponse(raw, nextAll, abort.signal)).catch(handleError(nextAll));
275151
+ callChat(currentProvider, scopedSystemPrompt, nextAll, abort.signal).then((raw) => processResponse(raw, nextAll, abort.signal)).catch(handleError(nextAll));
275057
275152
  };
275058
275153
  const handleProviderDone = (p) => {
275059
275154
  setProvider(p);
@@ -278000,6 +278095,17 @@ function CommitCommand({
278000
278095
  confirm
278001
278096
  }, undefined, false, undefined, this);
278002
278097
  }
278098
+
278099
+ // node_modules/@ridit/lens-sdk/dist/index.mjs
278100
+ var TOOL_TAGS = {
278101
+ read: "read",
278102
+ net: "net",
278103
+ write: "write",
278104
+ delete: "delete",
278105
+ shell: "shell",
278106
+ git: "git",
278107
+ find: "find"
278108
+ };
278003
278109
  // src/tools/view-image.ts
278004
278110
  import path26 from "path";
278005
278111
  import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
@@ -278571,6 +278677,7 @@ var fetchTool = {
278571
278677
  name: "fetch",
278572
278678
  description: "load a URL",
278573
278679
  safe: true,
278680
+ tag: TOOL_TAGS.net,
278574
278681
  permissionLabel: "fetch",
278575
278682
  systemPromptEntry: (i) => `### ${i}. fetch — load a URL
278576
278683
  <fetch>https://example.com</fetch>`,
@@ -278592,6 +278699,7 @@ var shellTool = {
278592
278699
  name: "shell",
278593
278700
  description: "run a terminal command",
278594
278701
  safe: false,
278702
+ tag: TOOL_TAGS.shell,
278595
278703
  permissionLabel: "run",
278596
278704
  systemPromptEntry: (i) => `### ${i}. shell — run a terminal command
278597
278705
  <shell>node -v</shell>`,
@@ -278606,6 +278714,7 @@ var readFileTool = {
278606
278714
  name: "read-file",
278607
278715
  description: "read a file from the repo",
278608
278716
  safe: true,
278717
+ tag: TOOL_TAGS.read,
278609
278718
  permissionLabel: "read",
278610
278719
  systemPromptEntry: (i) => `### ${i}. read-file — read a file from the repo
278611
278720
  <read-file>src/foo.ts</read-file>`,
@@ -278619,6 +278728,7 @@ var readFileTool = {
278619
278728
  var readFolderTool = {
278620
278729
  name: "read-folder",
278621
278730
  description: "list contents of a folder (files + subfolders, one level deep)",
278731
+ tag: TOOL_TAGS.read,
278622
278732
  safe: true,
278623
278733
  permissionLabel: "folder",
278624
278734
  systemPromptEntry: (i) => `### ${i}. read-folder — list contents of a folder (files + subfolders, one level deep)
@@ -278633,6 +278743,7 @@ var readFolderTool = {
278633
278743
  var grepTool = {
278634
278744
  name: "grep",
278635
278745
  description: "search for a pattern across files in the repo",
278746
+ tag: TOOL_TAGS.find,
278636
278747
  safe: true,
278637
278748
  permissionLabel: "grep",
278638
278749
  systemPromptEntry: (i) => `### ${i}. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
@@ -278656,6 +278767,7 @@ var grepTool = {
278656
278767
  var writeFileTool = {
278657
278768
  name: "write-file",
278658
278769
  description: "create or overwrite a file",
278770
+ tag: TOOL_TAGS.write,
278659
278771
  safe: false,
278660
278772
  permissionLabel: "write",
278661
278773
  systemPromptEntry: (i) => `### ${i}. write-file — create or overwrite a file
@@ -278681,6 +278793,7 @@ var writeFileTool = {
278681
278793
  var deleteFileTool = {
278682
278794
  name: "delete-file",
278683
278795
  description: "permanently delete a single file",
278796
+ tag: TOOL_TAGS.delete,
278684
278797
  safe: false,
278685
278798
  permissionLabel: "delete",
278686
278799
  systemPromptEntry: (i) => `### ${i}. delete-file — permanently delete a single file
@@ -278695,6 +278808,7 @@ var deleteFileTool = {
278695
278808
  var deleteFolderTool = {
278696
278809
  name: "delete-folder",
278697
278810
  description: "permanently delete a folder and all its contents",
278811
+ tag: TOOL_TAGS.delete,
278698
278812
  safe: false,
278699
278813
  permissionLabel: "delete folder",
278700
278814
  systemPromptEntry: (i) => `### ${i}. delete-folder — permanently delete a folder and all its contents
@@ -278709,6 +278823,7 @@ var deleteFolderTool = {
278709
278823
  var openUrlTool = {
278710
278824
  name: "open-url",
278711
278825
  description: "open a URL in the user's default browser",
278826
+ tag: TOOL_TAGS.net,
278712
278827
  safe: true,
278713
278828
  permissionLabel: "open",
278714
278829
  systemPromptEntry: (i) => `### ${i}. open-url — open a URL in the user's default browser
@@ -278720,6 +278835,7 @@ var openUrlTool = {
278720
278835
  var generatePdfTool = {
278721
278836
  name: "generate-pdf",
278722
278837
  description: "generate a PDF file from markdown-style content",
278838
+ tag: TOOL_TAGS.write,
278723
278839
  safe: false,
278724
278840
  permissionLabel: "pdf",
278725
278841
  systemPromptEntry: (i) => `### ${i}. generate-pdf — generate a PDF file from markdown-style content
@@ -278745,6 +278861,7 @@ var generatePdfTool = {
278745
278861
  };
278746
278862
  var searchTool = {
278747
278863
  name: "search",
278864
+ tag: TOOL_TAGS.net,
278748
278865
  description: "search the internet for anything you are unsure about",
278749
278866
  safe: true,
278750
278867
  permissionLabel: "search",
@@ -278767,6 +278884,7 @@ var searchTool = {
278767
278884
  var cloneTool = {
278768
278885
  name: "clone",
278769
278886
  description: "clone a GitHub repo so you can explore and discuss it",
278887
+ tag: TOOL_TAGS.write,
278770
278888
  safe: false,
278771
278889
  permissionLabel: "clone",
278772
278890
  systemPromptEntry: (i) => `### ${i}. clone — clone a GitHub repo so you can explore and discuss it
@@ -278781,6 +278899,7 @@ var cloneTool = {
278781
278899
  var changesTool = {
278782
278900
  name: "changes",
278783
278901
  description: "propose code edits (shown as a diff for user approval)",
278902
+ tag: TOOL_TAGS.write,
278784
278903
  safe: false,
278785
278904
  permissionLabel: "changes",
278786
278905
  systemPromptEntry: (i) => `### ${i}. changes — propose code edits (shown as a diff for user approval)
@@ -278803,6 +278922,7 @@ var changesTool = {
278803
278922
  var readFilesTool = {
278804
278923
  name: "read-files",
278805
278924
  description: "read multiple files from the repo at once",
278925
+ tag: TOOL_TAGS.read,
278806
278926
  safe: true,
278807
278927
  permissionLabel: "read",
278808
278928
  systemPromptEntry: (i) => `### ${i}. read-files — read multiple files from the repo at once
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ridit/lens",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Know Your Codebase.",
5
5
  "author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  "prepublishOnly": "npm run build && npm run tag"
20
20
  },
21
21
  "dependencies": {
22
- "@ridit/lens-sdk": "0.1.6",
22
+ "@ridit/lens-sdk": "^0.2.0",
23
23
  "asciichart": "^1.5.25",
24
24
  "bun": "^1.3.11",
25
25
  "commander": "^14.0.3",
@@ -1,6 +1,7 @@
1
1
  import { useState, useRef } from "react";
2
2
  import React from "react";
3
3
  import type { Provider } from "../../../types/config";
4
+ import { classifyIntent } from "../../../utils/intentClassifier";
4
5
  import type { Message, ChatStage } from "../../../types/chat";
5
6
  import {
6
7
  saveChat,
@@ -421,8 +422,16 @@ export function useChat(repoPath: string) {
421
422
  const abort = new AbortController();
422
423
  abortControllerRef.current = abort;
423
424
 
425
+ const intent = classifyIntent(text);
426
+ const scopedToolsSection = registry.buildSystemPromptSection(intent);
427
+
428
+ const scopedSystemPrompt = currentSystemPrompt.replace(
429
+ /## TOOLS[\s\S]*?(?=\n## (?!TOOLS))/,
430
+ scopedToolsSection + "\n\n",
431
+ );
432
+
424
433
  setStage({ type: "thinking" });
425
- callChat(currentProvider, currentSystemPrompt, nextAll, abort.signal)
434
+ callChat(currentProvider, scopedSystemPrompt, nextAll, abort.signal)
426
435
  .then((raw: string) => processResponse(raw, nextAll, abort.signal))
427
436
  .catch(handleError(nextAll));
428
437
  };
@@ -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,25 @@ 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
+
23
24
  ### memory-add — save something important to long-term memory for this repo
24
25
  <memory-add>User prefers TypeScript strict mode in all new files</memory-add>
25
-
26
+
26
27
  ### memory-delete — delete a memory by its ID (shown in brackets like [abc123])
27
28
  <memory-delete>abc123</memory-delete>
28
-
29
+
29
30
  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.
30
31
  Use memory-delete when the user asks you to forget something or a memory is outdated.
31
-
32
+
32
33
  ## RULES
33
-
34
+
34
35
  1. ONE tool per response — emit the XML tag, then stop. Never chain tools in one response except when scaffolding (see below).
35
36
  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
37
  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 +49,15 @@ Use memory-delete when the user asks you to forget something or a memory is outd
48
49
  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
50
  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
51
  17. When scaffolding multiple files, emit ONE write-file tag per response and wait for the result before writing the next file.
51
-
52
+
52
53
  ## ADDON FORMAT
53
-
54
+
54
55
  All addons use defineTool from @ridit/lens-sdk. The ONLY correct format is:
55
-
56
+
56
57
  \`\`\`js
57
58
  const { defineTool } = require("@ridit/lens-sdk");
58
59
  const { execSync } = require("child_process");
59
-
60
+
60
61
  defineTool({
61
62
  name: "tool-name",
62
63
  description: "what it does",
@@ -72,26 +73,146 @@ defineTool({
72
73
  },
73
74
  });
74
75
  \`\`\`
75
-
76
+
76
77
  NEVER use module.exports, registerTool, ctx.tools.shell, or any other format. See addons/run-tests.js for a full working example.
77
-
78
+
78
79
  ## SCAFFOLDING
79
-
80
+
80
81
  When creating multiple files, emit ONE write-file per response and wait for each result:
81
-
82
+
82
83
  <write-file>
83
84
  {"path": "myapp/package.json", "content": "..."}
84
85
  </write-file>
85
-
86
+
86
87
  Wait for result, then emit the next file. Never chain write-file tags when content is complex.
87
-
88
+
88
89
  ## CODEBASE
89
-
90
+
90
91
  ${fileList.length > 0 ? fileList : "(no files indexed)"}
91
-
92
+
92
93
  ${memorySummary}`;
93
94
  }
94
95
 
96
+ export function buildBuiltinToolsSection(intent: Intent = "any"): string {
97
+ const isReadonly = intent === "readonly";
98
+
99
+ const readTools = `### 1. fetch — load a URL
100
+ <fetch>https://example.com</fetch>
101
+
102
+ ### 2. read-file — read a single file from the repo
103
+ <read-file>src/foo.ts</read-file>
104
+
105
+ ### 3. read-files — read multiple files at once
106
+ <read-files>
107
+ ["src/foo.ts", "src/bar.ts"]
108
+ </read-files>
109
+
110
+ ### 4. read-folder — list contents of a folder (one level deep)
111
+ <read-folder>src/components</read-folder>
112
+
113
+ ### 5. grep — search for a pattern across files
114
+ <grep>
115
+ {"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
116
+ </grep>
117
+
118
+ ### 6. search — search the internet
119
+ <search>how to use React useEffect cleanup</search>`;
120
+
121
+ const writeTools = `### 7. shell — run a terminal command (NOT for filesystem inspection)
122
+ <shell>node -v</shell>
123
+
124
+ ### 8. write-file — create or overwrite a file (COMPLETE content only)
125
+ <write-file>
126
+ {"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
127
+ </write-file>
128
+
129
+ ### 9. delete-file — permanently delete a single file
130
+ <delete-file>src/old-component.tsx</delete-file>
131
+
132
+ ### 10. delete-folder — permanently delete a folder and all its contents
133
+ <delete-folder>src/legacy</delete-folder>
134
+
135
+ ### 11. open-url — open a URL in the user's default browser
136
+ <open-url>https://github.com/owner/repo</open-url>
137
+
138
+ ### 12. generate-pdf — generate a PDF from markdown-style content
139
+ <generate-pdf>
140
+ {"path": "output/report.pdf", "content": "# Title\\n\\nBody text."}
141
+ </generate-pdf>
142
+
143
+ ### 13. clone — clone a GitHub repo
144
+ <clone>https://github.com/owner/repo</clone>
145
+
146
+ ### 14. changes — propose code edits shown as a diff for user approval
147
+ <changes>
148
+ {"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
149
+ </changes>`;
150
+
151
+ if (isReadonly) {
152
+ return `## TOOLS
153
+
154
+ 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.
155
+
156
+ ${readTools}`;
157
+ }
158
+
159
+ return `## TOOLS
160
+
161
+ 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.
162
+
163
+ ### 1. fetch — load a URL
164
+ <fetch>https://example.com</fetch>
165
+
166
+ ### 2. shell — run a terminal command (NOT for filesystem inspection)
167
+ <shell>node -v</shell>
168
+
169
+ ### 3. read-file — read a single file from the repo
170
+ <read-file>src/foo.ts</read-file>
171
+
172
+ ### 4. read-files — read multiple files at once
173
+ <read-files>
174
+ ["src/foo.ts", "src/bar.ts"]
175
+ </read-files>
176
+
177
+ ### 5. read-folder — list contents of a folder (one level deep)
178
+ <read-folder>src/components</read-folder>
179
+
180
+ ### 6. grep — search for a pattern across files
181
+ <grep>
182
+ {"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
183
+ </grep>
184
+
185
+ ### 7. write-file — create or overwrite a file (COMPLETE content only)
186
+ <write-file>
187
+ {"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
188
+ </write-file>
189
+
190
+ ### 8. delete-file — permanently delete a single file
191
+ <delete-file>src/old-component.tsx</delete-file>
192
+
193
+ ### 9. delete-folder — permanently delete a folder and all its contents
194
+ <delete-folder>src/legacy</delete-folder>
195
+
196
+ ### 10. open-url — open a URL in the user's default browser
197
+ <open-url>https://github.com/owner/repo</open-url>
198
+
199
+ ### 11. generate-pdf — generate a PDF from markdown-style content
200
+ <generate-pdf>
201
+ {"path": "output/report.pdf", "content": "# Title\\n\\nBody text."}
202
+ </generate-pdf>
203
+
204
+ ### 12. search — search the internet
205
+ <search>how to use React useEffect cleanup</search>
206
+
207
+ ### 13. clone — clone a GitHub repo
208
+ <clone>https://github.com/owner/repo</clone>
209
+
210
+ ### 14. changes — propose code edits shown as a diff for user approval
211
+ <changes>
212
+ {"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
213
+ </changes>`;
214
+ }
215
+
95
216
  const BUILTIN_TOOLS_SECTION = `## TOOLS
96
217
 
97
218
  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) =>
@@ -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
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Tool, ToolContext } from "@ridit/lens-sdk";
2
+ import { TOOL_TAGS } from "@ridit/lens-sdk";
2
3
  import {
3
4
  fetchUrl,
4
5
  searchWeb,
@@ -23,6 +24,7 @@ export const fetchTool: Tool<string> = {
23
24
  name: "fetch",
24
25
  description: "load a URL",
25
26
  safe: true,
27
+ tag: TOOL_TAGS.net,
26
28
  permissionLabel: "fetch",
27
29
  systemPromptEntry: (i) =>
28
30
  `### ${i}. fetch — load a URL\n<fetch>https://example.com</fetch>`,
@@ -45,6 +47,7 @@ export const shellTool: Tool<string> = {
45
47
  name: "shell",
46
48
  description: "run a terminal command",
47
49
  safe: false,
50
+ tag: TOOL_TAGS.shell,
48
51
  permissionLabel: "run",
49
52
  systemPromptEntry: (i) =>
50
53
  `### ${i}. shell — run a terminal command\n<shell>node -v</shell>`,
@@ -60,6 +63,7 @@ export const readFileTool: Tool<string> = {
60
63
  name: "read-file",
61
64
  description: "read a file from the repo",
62
65
  safe: true,
66
+ tag: TOOL_TAGS.read,
63
67
  permissionLabel: "read",
64
68
  systemPromptEntry: (i) =>
65
69
  `### ${i}. read-file — read a file from the repo\n<read-file>src/foo.ts</read-file>`,
@@ -74,6 +78,7 @@ export const readFileTool: Tool<string> = {
74
78
  export const readFolderTool: Tool<string> = {
75
79
  name: "read-folder",
76
80
  description: "list contents of a folder (files + subfolders, one level deep)",
81
+ tag: TOOL_TAGS.read,
77
82
  safe: true,
78
83
  permissionLabel: "folder",
79
84
  systemPromptEntry: (i) =>
@@ -94,6 +99,7 @@ interface GrepInput {
94
99
  export const grepTool: Tool<GrepInput> = {
95
100
  name: "grep",
96
101
  description: "search for a pattern across files in the repo",
102
+ tag: TOOL_TAGS.find,
97
103
  safe: true,
98
104
  permissionLabel: "grep",
99
105
  systemPromptEntry: (i) =>
@@ -124,6 +130,7 @@ interface WriteFileInput {
124
130
  export const writeFileTool: Tool<WriteFileInput> = {
125
131
  name: "write-file",
126
132
  description: "create or overwrite a file",
133
+ tag: TOOL_TAGS.write,
127
134
  safe: false,
128
135
  permissionLabel: "write",
129
136
  systemPromptEntry: (i) =>
@@ -147,6 +154,7 @@ export const writeFileTool: Tool<WriteFileInput> = {
147
154
  export const deleteFileTool: Tool<string> = {
148
155
  name: "delete-file",
149
156
  description: "permanently delete a single file",
157
+ tag: TOOL_TAGS.delete,
150
158
  safe: false,
151
159
  permissionLabel: "delete",
152
160
  systemPromptEntry: (i) =>
@@ -162,6 +170,7 @@ export const deleteFileTool: Tool<string> = {
162
170
  export const deleteFolderTool: Tool<string> = {
163
171
  name: "delete-folder",
164
172
  description: "permanently delete a folder and all its contents",
173
+ tag: TOOL_TAGS.delete,
165
174
  safe: false,
166
175
  permissionLabel: "delete folder",
167
176
  systemPromptEntry: (i) =>
@@ -177,6 +186,7 @@ export const deleteFolderTool: Tool<string> = {
177
186
  export const openUrlTool: Tool<string> = {
178
187
  name: "open-url",
179
188
  description: "open a URL in the user's default browser",
189
+ tag: TOOL_TAGS.net,
180
190
  safe: true,
181
191
  permissionLabel: "open",
182
192
  systemPromptEntry: (i) =>
@@ -194,6 +204,7 @@ interface GeneratePdfInput {
194
204
  export const generatePdfTool: Tool<GeneratePdfInput> = {
195
205
  name: "generate-pdf",
196
206
  description: "generate a PDF file from markdown-style content",
207
+ tag: TOOL_TAGS.write,
197
208
  safe: false,
198
209
  permissionLabel: "pdf",
199
210
  systemPromptEntry: (i) =>
@@ -222,6 +233,7 @@ export const generatePdfTool: Tool<GeneratePdfInput> = {
222
233
 
223
234
  export const searchTool: Tool<string> = {
224
235
  name: "search",
236
+ tag: TOOL_TAGS.net,
225
237
  description: "search the internet for anything you are unsure about",
226
238
  safe: true,
227
239
  permissionLabel: "search",
@@ -245,6 +257,7 @@ export const searchTool: Tool<string> = {
245
257
  export const cloneTool: Tool<string> = {
246
258
  name: "clone",
247
259
  description: "clone a GitHub repo so you can explore and discuss it",
260
+ tag: TOOL_TAGS.write,
248
261
  safe: false,
249
262
  permissionLabel: "clone",
250
263
  systemPromptEntry: (i) =>
@@ -265,6 +278,7 @@ export interface ChangesInput {
265
278
  export const changesTool: Tool<ChangesInput> = {
266
279
  name: "changes",
267
280
  description: "propose code edits (shown as a diff for user approval)",
281
+ tag: TOOL_TAGS.write,
268
282
  safe: false,
269
283
  permissionLabel: "changes",
270
284
  systemPromptEntry: (i) =>
@@ -290,6 +304,7 @@ interface ReadFilesInput {
290
304
  export const readFilesTool: Tool<ReadFilesInput> = {
291
305
  name: "read-files",
292
306
  description: "read multiple files from the repo at once",
307
+ tag: TOOL_TAGS.read,
293
308
  safe: true,
294
309
  permissionLabel: "read",
295
310
  systemPromptEntry: (i) =>
@@ -1,4 +1,23 @@
1
- import type { Tool } from "@ridit/lens-sdk";
1
+ import type { Tool, ToolTag } from "@ridit/lens-sdk";
2
+ import type { Intent } from "../intentClassifier";
3
+
4
+ /**
5
+ * Broad capability category for a tool.
6
+ * Used to filter the system prompt based on classified user intent.
7
+ *
8
+ * "read" — safe, purely observational (read-file, read-folder, grep, etc.)
9
+ * "net" — outbound network (fetch, search, clone, open-url)
10
+ * "write" — creates or overwrites file content (write-file, changes, generate-pdf)
11
+ * "delete" — destructive removal (delete-file, delete-folder)
12
+ * "shell" — arbitrary shell execution
13
+ */
14
+
15
+ /** Tools allowed for each intent level */
16
+ const INTENT_ALLOWED: Record<Intent, ToolTag[]> = {
17
+ readonly: ["read", "net"],
18
+ mutating: ["read", "net", "write", "delete", "shell"],
19
+ any: ["read", "net", "write", "delete", "shell"],
20
+ };
2
21
 
3
22
  class ToolRegistry {
4
23
  private tools = new Map<string, Tool<unknown>>();
@@ -27,17 +46,54 @@ class ToolRegistry {
27
46
  }
28
47
 
29
48
  /**
30
- * Build the TOOLS section of the system prompt from all registered tools.
49
+ * Returns tool names that are allowed for the given intent.
50
+ * Falls back to all names when a tool has no tag (legacy / addons).
51
+ */
52
+ namesForIntent(intent: Intent): string[] {
53
+ const allowed = new Set(INTENT_ALLOWED[intent]);
54
+ return Array.from(this.tools.values())
55
+ .filter((t) => {
56
+ const tag = (t as any).tag as ToolTag | undefined;
57
+ // No tag = addon / unknown → always allow (conservative)
58
+ if (!tag) return true;
59
+ return allowed.has(tag);
60
+ })
61
+ .map((t) => t.name);
62
+ }
63
+
64
+ /**
65
+ * Build the TOOLS section of the system prompt from all registered tools,
66
+ * optionally scoped to a specific intent.
67
+ *
68
+ * When intent is "readonly", write/delete/shell tools are omitted entirely
69
+ * so the LLM never sees them and can't hallucinate calls to them.
31
70
  */
32
- buildSystemPromptSection(): string {
71
+ buildSystemPromptSection(intent: Intent = "any"): string {
72
+ const allowed = new Set(INTENT_ALLOWED[intent]);
73
+
74
+ const visible = Array.from(this.tools.values()).filter((t) => {
75
+ const tag = (t as any).tag as ToolTag | undefined;
76
+ if (!tag) return true; // addon without tag → always show
77
+ return allowed.has(tag);
78
+ });
79
+
33
80
  const lines: string[] = ["## TOOLS\n"];
34
- lines.push(
35
- "You have exactly " +
36
- this.tools.size +
37
- " tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.\n",
38
- );
81
+
82
+ if (intent === "readonly") {
83
+ lines.push(
84
+ `You have ${visible.length} tools available for this read-only request. ` +
85
+ `Do NOT attempt to write, delete, or run shell commands — ` +
86
+ `those tools are not available right now.\n`,
87
+ );
88
+ } else {
89
+ lines.push(
90
+ `You have exactly ${visible.length} tools. To use a tool you MUST wrap it ` +
91
+ `in the exact XML tags shown below — no other format will work.\n`,
92
+ );
93
+ }
94
+
39
95
  let i = 1;
40
- for (const tool of this.tools.values()) {
96
+ for (const tool of visible) {
41
97
  lines.push(tool.systemPromptEntry(i++));
42
98
  }
43
99
  return lines.join("\n");