pi-readseek 0.9.6 → 0.9.8

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 (2) hide show
  1. package/dist/index.ts +150 -99
  2. package/package.json +2 -2
package/dist/index.ts CHANGED
@@ -41,6 +41,8 @@ var TOOL_NAMES = [
41
41
  ];
42
42
  var PARAM_DESCRIPTIONS = {
43
43
  path: "Absolute or relative file path",
44
+ projectPath: "Path relative to the project directory",
45
+ projectSearchPath: "File or directory to search; defaults to the project directory",
44
46
  searchPath: "File or directory to search; defaults to the current directory",
45
47
  language: "Language override when auto-detection is ambiguous",
46
48
  languageOverride: "Language override",
@@ -94,6 +96,7 @@ var PARAM_DESCRIPTIONS = {
94
96
  symbol: "Mapped symbol name",
95
97
  newBody: "Complete replacement body for the symbol",
96
98
  apply: "When supported by the host, apply the verified plan instead of previewing only",
99
+ applyDefaultTrue: "Apply the verified edits; defaults to true",
97
100
  planHash: "Require the apply plan to match this dry-run plan hash",
98
101
  workspace: "Rename across the project",
99
102
  to: "New symbol name",
@@ -398,6 +401,90 @@ var TOOL_CONTRACTS = [
398
401
  }
399
402
  }
400
403
  ];
404
+ // ../readseek-api/src/policy.ts
405
+ var REPLACEABLE_TOOLS = [
406
+ {
407
+ tool: "digest",
408
+ builtIn: "read",
409
+ benefit: "it returns LINE:HASH anchors for safe edits."
410
+ },
411
+ {
412
+ tool: "edit",
413
+ builtIn: "edit",
414
+ benefit: "it verifies fresh LINE:HASH anchors."
415
+ },
416
+ {
417
+ tool: "grep",
418
+ builtIn: "grep",
419
+ benefit: "it returns LINE:HASH anchors."
420
+ },
421
+ {
422
+ tool: "write",
423
+ builtIn: "write",
424
+ benefit: "it returns LINE:HASH anchors."
425
+ }
426
+ ];
427
+ var TOOL_COMPACT_GUIDELINES = {
428
+ digest: [
429
+ "Use `digest` for map, diagnostics, and identity facets instead of separate tools."
430
+ ],
431
+ edit: [
432
+ "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
433
+ ],
434
+ grep: [
435
+ "Use `grep` with summary first for broad searches, then narrow by path, glob, or pattern."
436
+ ],
437
+ write: [
438
+ "Use anchored edits rather than `write` for small changes or appends."
439
+ ],
440
+ search: [
441
+ "Use `search` for AST patterns; use `grep` for plain text."
442
+ ],
443
+ refs: [
444
+ "Use `refs` before changing a symbol; add scope plus line/column to follow one binding."
445
+ ]
446
+ };
447
+ function resolveToolName(name, aliases = {}) {
448
+ return aliases[name] ?? name;
449
+ }
450
+ function rewriteBacktickedToolNames(text, aliases = {}) {
451
+ return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${resolveToolName(toolName, aliases)}\``), text);
452
+ }
453
+ function renderCompactGuidelines(name, aliases = {}) {
454
+ return (TOOL_COMPACT_GUIDELINES[name] ?? []).map((guideline) => rewriteBacktickedToolNames(guideline, aliases));
455
+ }
456
+ function renderToolRoutingPolicy(options = {}) {
457
+ const aliases = options.aliases ?? {};
458
+ const digest = resolveToolName("digest", aliases);
459
+ const edit = resolveToolName("edit", aliases);
460
+ const write = resolveToolName("write", aliases);
461
+ const grep = resolveToolName("grep", aliases);
462
+ const search = resolveToolName("search", aliases);
463
+ const def = resolveToolName("def", aliases);
464
+ const refs = resolveToolName("refs", aliases);
465
+ const rename = resolveToolName("rename", aliases);
466
+ const view = resolveToolName("view", aliases);
467
+ if (options.style === "server") {
468
+ return [
469
+ "Structural code navigation, document viewing, and hash-verified edits with readseek.",
470
+ "For structural questions, prefer these tools to text Grep/Bash: definitions, references, AST search, multi-facet digest (content/map/diagnostics/identity), and document views.",
471
+ `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
472
+ "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
473
+ "call MCP tools for active navigation, document questions, or anchored edits."
474
+ ].join(" ");
475
+ }
476
+ const title = options.title ?? "ReadSeek tool policy:";
477
+ const bullets = [];
478
+ if (options.preferOverBuiltIns) {
479
+ bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
480
+ }
481
+ bullets.push(`- Read with ${digest} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, and ${digest}/${def}/${refs} for symbol navigation.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${view} for document structure; run ${digest} with select diagnostics after source edits for a quick syntax check.`);
482
+ if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
483
+ bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
484
+ }
485
+ return [title, ...bullets].join(`
486
+ `);
487
+ }
401
488
  // ../readseek-api/src/validation.ts
402
489
  function validateGitSelection(input) {
403
490
  if (input.ignored && !input.others) {
@@ -768,48 +855,7 @@ async function runReadSeekNodeJson(binaryPath, args, options = {}) {
768
855
  return parseSuccessfulJson(await runReadSeekNode(binaryPath, args, options));
769
856
  }
770
857
  // src/tool-prompt-metadata.ts
771
- var REPLACEABLE_TOOL_GUIDELINES = {
772
- "digest.md": {
773
- readSeekName: "readSeek_digest",
774
- builtInName: "read",
775
- benefit: "it returns LINE:HASH anchors for safe edits."
776
- },
777
- "edit.md": {
778
- readSeekName: "readSeek_edit",
779
- builtInName: "edit",
780
- benefit: "it verifies fresh LINE:HASH anchors."
781
- },
782
- "grep.md": {
783
- readSeekName: "readSeek_grep",
784
- builtInName: "grep",
785
- benefit: "it returns LINE:HASH anchors."
786
- },
787
- "write.md": {
788
- readSeekName: "readSeek_write",
789
- builtInName: "write",
790
- benefit: "it returns LINE:HASH anchors."
791
- }
792
- };
793
- var COMPACT_GUIDELINES = {
794
- "digest.md": [
795
- "Use readSeek_digest for map, diagnostics, and identity facets instead of separate tools."
796
- ],
797
- "edit.md": [
798
- "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
799
- ],
800
- "grep.md": [
801
- "Use readSeek_grep with summary first for broad searches, then narrow by path, glob, or pattern."
802
- ],
803
- "write.md": [
804
- "Use anchored edits rather than readSeek_write for small changes or appends."
805
- ],
806
- "search.md": [
807
- "Use readSeek_search for AST patterns; use readSeek_grep for plain text."
808
- ],
809
- "refs.md": [
810
- "Use readSeek_refs before changing a symbol; add scope plus line/column to follow one binding."
811
- ]
812
- };
858
+ var REPLACEABLE_BY_TOOL = new Map(REPLACEABLE_TOOLS.map((entry) => [entry.tool, entry]));
813
859
  function toolNameFromPrompt(fileName) {
814
860
  const name = fileName.replace(/\.md$/, "");
815
861
  if (!TOOL_NAMES.includes(name))
@@ -819,28 +865,32 @@ function toolNameFromPrompt(fileName) {
819
865
  function promptFileName(promptUrl) {
820
866
  return promptUrl.pathname.split("/").pop() ?? "";
821
867
  }
822
- function rewriteToolAliases(value, toolAliases) {
868
+ function rewriteRegisteredToolNames(value, toolAliases) {
823
869
  if (!toolAliases)
824
870
  return value;
825
871
  return Object.entries(toolAliases).reduce((rewritten, [canonicalName, registeredName]) => rewritten.replaceAll(canonicalName, registeredName), value);
826
872
  }
827
- function defineToolPromptMetadata(options) {
828
- const fileName = promptFileName(options.promptUrl);
829
- const toolName = toolNameFromPrompt(fileName);
830
- const replaceable = REPLACEABLE_TOOL_GUIDELINES[fileName];
831
- const registeredName = options.registeredName ?? replaceable?.readSeekName;
832
- const preferenceGuideline = replaceable && registeredName ? registeredName === replaceable.readSeekName ? `Prefer ${registeredName} over ${replaceable.builtInName} when both are available; ${replaceable.benefit}` : `Use ${registeredName}; ${replaceable.benefit}` : undefined;
833
- const aliases = Object.fromEntries(TOOL_NAMES.map((name) => {
873
+ function descriptionAliases(toolName, registeredName, toolAliases) {
874
+ return Object.fromEntries(TOOL_NAMES.map((name) => {
834
875
  const readSeekName = `readSeek_${name}`;
835
- const alias = name === toolName && registeredName ? registeredName : options.toolAliases?.[readSeekName] ?? readSeekName;
876
+ const alias = name === toolName && registeredName ? registeredName : toolAliases?.[readSeekName] ?? readSeekName;
836
877
  return [name, alias];
837
878
  }));
879
+ }
880
+ function defineToolPromptMetadata(options) {
881
+ const fileName = promptFileName(options.promptUrl);
882
+ const toolName = toolNameFromPrompt(fileName);
883
+ const replaceable = REPLACEABLE_BY_TOOL.get(toolName);
884
+ const defaultReadSeekName = `readSeek_${toolName}`;
885
+ const registeredName = options.registeredName ?? (replaceable ? defaultReadSeekName : undefined);
886
+ const preferenceGuideline = replaceable && registeredName ? registeredName === defaultReadSeekName ? `Prefer ${registeredName} over ${replaceable.builtIn} when both are available; ${replaceable.benefit}` : `Use ${registeredName}; ${replaceable.benefit}` : undefined;
887
+ const aliases = descriptionAliases(toolName, registeredName, options.toolAliases);
838
888
  return {
839
889
  description: renderToolDescription(toolName, aliases),
840
- promptSnippet: rewriteToolAliases(options.promptSnippet, options.toolAliases),
890
+ promptSnippet: rewriteRegisteredToolNames(options.promptSnippet, options.toolAliases),
841
891
  promptGuidelines: [
842
892
  ...preferenceGuideline ? [preferenceGuideline] : [],
843
- ...(COMPACT_GUIDELINES[fileName] ?? []).map((guideline) => rewriteToolAliases(registeredName && replaceable ? guideline.replaceAll(replaceable.readSeekName, registeredName) : guideline, options.toolAliases))
893
+ ...renderCompactGuidelines(toolName, aliases)
844
894
  ]
845
895
  };
846
896
  }
@@ -2972,7 +3022,7 @@ function optionalIntOrString(description) {
2972
3022
  return Type.Optional(Type.Union([Type.Number({ description }), Type.String({ description })]));
2973
3023
  }
2974
3024
  function filePathParam() {
2975
- return Type.String({ description: "Absolute or relative file path" });
3025
+ return Type.String({ description: PARAM_DESCRIPTIONS.path });
2976
3026
  }
2977
3027
  function registerReadSeekTool(pi, tool) {
2978
3028
  pi.registerTool(tool);
@@ -2982,42 +3032,42 @@ function registerReadSeekTool(pi, tool) {
2982
3032
  // src/edit.ts
2983
3033
  var hashlineEditItemSchema = Type2.Object({
2984
3034
  set_line: Type2.Optional(Type2.Object({
2985
- anchor: Type2.String({ description: "Fresh LINE:HASH anchor for the line to replace" }),
2986
- new_text: Type2.String({ description: "Replacement text; use an empty string to delete the line" })
2987
- }, { additionalProperties: false, description: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}' })),
3035
+ anchor: Type2.String({ description: PARAM_DESCRIPTIONS.anchor }),
3036
+ new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextLine })
3037
+ }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.setLine })),
2988
3038
  replace_lines: Type2.Optional(Type2.Object({
2989
- start_anchor: Type2.String({ description: "Fresh LINE:HASH anchor for the first line in the range" }),
2990
- end_anchor: Type2.String({ description: "Fresh LINE:HASH anchor for the last line in the range" }),
2991
- new_text: Type2.String({ description: "Replacement text; use an empty string to delete the range" })
2992
- }, { additionalProperties: false, description: 'Replace a range: {"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}' })),
3039
+ start_anchor: Type2.String({ description: PARAM_DESCRIPTIONS.startAnchor }),
3040
+ end_anchor: Type2.String({ description: PARAM_DESCRIPTIONS.endAnchor }),
3041
+ new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextRange })
3042
+ }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replaceLines })),
2993
3043
  insert_after: Type2.Optional(Type2.Object({
2994
- anchor: Type2.String({ description: "Fresh LINE:HASH anchor for the line after which to insert" }),
2995
- new_text: Type2.String({ description: "Text to insert after the anchored line" })
2996
- }, { additionalProperties: false, description: 'Insert text: {"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}' })),
3044
+ anchor: Type2.String({ description: PARAM_DESCRIPTIONS.insertAnchor }),
3045
+ new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextInsert })
3046
+ }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.insertAfter })),
2997
3047
  replace: Type2.Optional(Type2.Object({
2998
- old_text: Type2.String({ description: "Exact text to find" }),
2999
- new_text: Type2.String({ description: "Replacement text" }),
3000
- all: Type2.Optional(Type2.Boolean({ description: "Replace every exact match" }))
3001
- }, { additionalProperties: false, description: 'Replace text: {"replace":{"old_text":"...","new_text":"..."}}' })),
3048
+ old_text: Type2.String({ description: PARAM_DESCRIPTIONS.oldText }),
3049
+ new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newText }),
3050
+ all: Type2.Optional(Type2.Boolean({ description: PARAM_DESCRIPTIONS.all }))
3051
+ }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replace })),
3002
3052
  replace_symbol: Type2.Optional(Type2.Object({
3003
- symbol: Type2.String({ description: "Mapped symbol name" }),
3004
- new_body: Type2.String({ description: "Complete replacement body for the symbol" })
3005
- }, { additionalProperties: false, description: 'Replace a symbol: {"replace_symbol":{"symbol":"name","new_body":"..."}}' }))
3053
+ symbol: Type2.String({ description: PARAM_DESCRIPTIONS.symbol }),
3054
+ new_body: Type2.String({ description: PARAM_DESCRIPTIONS.newBody })
3055
+ }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replaceSymbol }))
3006
3056
  }, {
3007
3057
  additionalProperties: false,
3008
3058
  minProperties: 1,
3009
3059
  maxProperties: 1,
3010
- description: "Exactly one nested edit variant"
3060
+ description: PARAM_DESCRIPTIONS.editVariant
3011
3061
  });
3012
3062
  var hashlineEditSchema = Type2.Object({
3013
3063
  path: filePathParam(),
3014
- language: Type2.Optional(Type2.String({ description: "Language override for symbol replacement" })),
3064
+ language: Type2.Optional(Type2.String({ description: PARAM_DESCRIPTIONS.languageSymbol })),
3015
3065
  edits: Type2.Array(hashlineEditItemSchema, {
3016
3066
  minItems: 1,
3017
- description: "Use set_line, replace_lines, insert_after, replace_symbol, or replace"
3067
+ description: PARAM_DESCRIPTIONS.edits
3018
3068
  }),
3019
3069
  postEditVerify: Type2.Optional(Type2.Boolean({
3020
- description: "Read back and verify persisted content"
3070
+ description: PARAM_DESCRIPTIONS.postEditVerify
3021
3071
  }))
3022
3072
  }, { additionalProperties: false });
3023
3073
  function buildEditError(path3, code, message, hint, errorDetails) {
@@ -3724,18 +3774,18 @@ function validateIgnoredRequiresOthers(tool, params) {
3724
3774
 
3725
3775
  // src/grep.ts
3726
3776
  var grepSchema = Type4.Object({
3727
- pattern: Type4.String({ description: "Regex pattern; set literal for exact text" }),
3777
+ pattern: Type4.String({ description: PARAM_DESCRIPTIONS.grepPattern }),
3728
3778
  path: searchPathParam(),
3729
- glob: Type4.Optional(Type4.String({ description: "File-name glob, such as *.ts" })),
3730
- ignoreCase: Type4.Optional(Type4.Boolean({ description: "Ignore case" })),
3731
- literal: Type4.Optional(Type4.Boolean({ description: "Treat pattern literally" })),
3732
- context: optionalIntOrString("Surrounding lines for each match"),
3733
- limit: optionalIntOrString("Maximum matches to return"),
3734
- summary: Type4.Optional(Type4.Boolean({ description: "Return per-file counts" })),
3779
+ glob: Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.glob })),
3780
+ ignoreCase: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.ignoreCase })),
3781
+ literal: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.literal })),
3782
+ context: optionalIntOrString(PARAM_DESCRIPTIONS.context),
3783
+ limit: optionalIntOrString(PARAM_DESCRIPTIONS.matchLimit),
3784
+ summary: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.summary })),
3735
3785
  scope: Type4.Optional(Type4.Literal("symbol", {
3736
- description: "Group matches by enclosing symbol"
3786
+ description: PARAM_DESCRIPTIONS.grepScope
3737
3787
  })),
3738
- scopeContext: optionalIntOrString("Context lines within each symbol")
3788
+ scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext)
3739
3789
  });
3740
3790
  var MATCH_LINE_RE = /^(.*?):(\d+): (.*)$/;
3741
3791
  var CONTEXT_LINE_RE = /^(.*?)-(\d+)- (.*)$/;
@@ -4539,11 +4589,11 @@ var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
4539
4589
  });
4540
4590
  var renameSchema = Type7.Object({
4541
4591
  path: filePathParam(),
4542
- line: Type7.Integer({ minimum: 1, description: "One-based cursor line of the symbol to rename" }),
4543
- column: Type7.Optional(Type7.Integer({ minimum: 1, description: "One-based cursor byte column for disambiguation" })),
4544
- to: Type7.String({ description: "New symbol name" }),
4545
- workspace: Type7.Optional(Type7.Boolean({ description: "Rename across the project" })),
4546
- apply: Type7.Optional(Type7.Boolean({ description: "Apply the verified edits; defaults to true" }))
4592
+ line: Type7.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.renameLine }),
4593
+ column: Type7.Optional(Type7.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.column })),
4594
+ to: Type7.String({ description: PARAM_DESCRIPTIONS.to }),
4595
+ workspace: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.workspace })),
4596
+ apply: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.applyDefaultTrue }))
4547
4597
  });
4548
4598
  async function executeRename(opts) {
4549
4599
  const { params, signal, cwd, onFileMutated } = opts;
@@ -4975,7 +5025,7 @@ function registerWriteTool(pi, options = {}) {
4975
5025
  promptGuidelines: promptMetadata.promptGuidelines,
4976
5026
  parameters: Type8.Object({
4977
5027
  path: filePathParam(),
4978
- content: Type8.String({ description: "Complete text file content" })
5028
+ content: Type8.String({ description: PARAM_DESCRIPTIONS.content })
4979
5029
  }),
4980
5030
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
4981
5031
  const cwd = ctx?.cwd ?? process.cwd();
@@ -5540,14 +5590,11 @@ var READSEEK_TOOL_ENTRIES = [
5540
5590
  function formatSettingsWarning(warning) {
5541
5591
  return `${warning.message} (${warning.source})`;
5542
5592
  }
5543
- function editingPolicy(readName, editName, writeName) {
5544
- return [
5545
- "ReadSeek editing policy:",
5546
- `- Read first with ${readName} when preparing to edit; ${editName} needs fresh LINE:HASH anchors.`,
5547
- `- Use ${editName} for existing files, ${writeName} for whole-file creation or replacement, and readSeek_rename for symbol renames.`,
5548
- `- Run ${readName} with select diagnostics after source edits for a quick syntax check.`
5549
- ].join(`
5550
- `);
5593
+ function toolRoutingPolicy(aliases) {
5594
+ return renderToolRoutingPolicy({
5595
+ aliases,
5596
+ title: "ReadSeek tool policy:"
5597
+ });
5551
5598
  }
5552
5599
  function piReadSeekExtension(pi) {
5553
5600
  const sessionAnchors = new SessionAnchors;
@@ -5568,6 +5615,10 @@ function piReadSeekExtension(pi) {
5568
5615
  readSeek_grep: grepName,
5569
5616
  readSeek_write: writeName
5570
5617
  };
5618
+ const policyAliases = Object.fromEntries(TOOL_NAMES.map((name) => {
5619
+ const readSeekName = `readSeek_${name}`;
5620
+ return [name, toolAliases[readSeekName] ?? readSeekName];
5621
+ }));
5571
5622
  registerEditTool(pi, { wasReadInSession: hasFreshAnchors, onFileMutated: forgetAnchors, name: editName, toolAliases });
5572
5623
  registerGrepTool(pi, { onFileAnchored: markAnchored, name: grepName });
5573
5624
  registerSearchTool(pi, { onFileAnchored: markAnchored });
@@ -5582,7 +5633,7 @@ function piReadSeekExtension(pi) {
5582
5633
  return;
5583
5634
  return { systemPrompt: `${event.systemPrompt}
5584
5635
 
5585
- ${editingPolicy(readName, editName, writeName)}` };
5636
+ ${toolRoutingPolicy(policyAliases)}` };
5586
5637
  });
5587
5638
  pi.on("session_start", (_event, ctx) => {
5588
5639
  sessionAnchors.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "Pi extension for LINE:HASH-anchored file operations and structural code navigation",
5
5
  "type": "module",
6
6
  "exports": {
@@ -40,7 +40,7 @@
40
40
  "node": ">=20.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@jarkkojs/readseek": "^0.9.6",
43
+ "@jarkkojs/readseek": "^0.9.8",
44
44
  "diff": "^9.0.0",
45
45
  "xxhash-wasm": "^1.1.0"
46
46
  },