pi-readseek 0.9.9 → 0.9.11

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 (3) hide show
  1. package/README.md +12 -8
  2. package/dist/index.ts +1456 -1231
  3. package/package.json +5 -5
package/dist/index.ts CHANGED
@@ -661,6 +661,7 @@ function buildRenameArgs(input) {
661
661
  return appendGitSelectionArgs(args, input);
662
662
  }
663
663
  // ../readseek-api/src/platform.ts
664
+ import { accessSync, constants as fsConstants } from "node:fs";
664
665
  import { createRequire } from "node:module";
665
666
  import path from "node:path";
666
667
  var READSEEK_PLATFORM_PACKAGES = {
@@ -689,10 +690,17 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
689
690
  const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
690
691
  const metaPackageJson = resolve(`${metaPackage}/package.json`);
691
692
  const metaPackageDir = path.dirname(metaPackageJson);
692
- const packageJsonPath = resolve(`${platformPackage}/package.json`, {
693
- paths: [metaPackageDir]
694
- });
695
- return path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
693
+ let packageJsonPath;
694
+ try {
695
+ packageJsonPath = resolve(`${platformPackage}/package.json`);
696
+ } catch {
697
+ packageJsonPath = resolve(`${platformPackage}/package.json`, {
698
+ paths: [metaPackageDir]
699
+ });
700
+ }
701
+ const binaryPath = path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
702
+ accessSync(binaryPath, fsConstants.X_OK);
703
+ return binaryPath;
696
704
  }
697
705
  // ../readseek-api/src/json.ts
698
706
  var READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
@@ -854,139 +862,805 @@ async function runReadSeekNodeText(binaryPath, args, options = {}) {
854
862
  async function runReadSeekNodeJson(binaryPath, args, options = {}) {
855
863
  return parseSuccessfulJson(await runReadSeekNode(binaryPath, args, options));
856
864
  }
857
- // src/tool-prompt-metadata.ts
858
- var REPLACEABLE_BY_TOOL = new Map(REPLACEABLE_TOOLS.map((entry) => [entry.tool, entry]));
859
- function toolNameFromPrompt(fileName) {
860
- const name = fileName.replace(/\.md$/, "");
861
- if (!TOOL_NAMES.includes(name))
862
- throw new Error(`unknown ReadSeek tool prompt: ${fileName}`);
863
- return name;
865
+ // ../readseek-api/src/client.ts
866
+ function classifyReadSeekFailure(err) {
867
+ const failure = err;
868
+ const message = String(failure?.message ?? err);
869
+ const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
870
+ if (missing) {
871
+ return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
872
+ }
873
+ return { code: "readseek-execution-error", message };
864
874
  }
865
- function promptFileName(promptUrl) {
866
- return promptUrl.pathname.split("/").pop() ?? "";
875
+ function requireNumber(value, field) {
876
+ if (typeof value !== "number" || !Number.isSafeInteger(value))
877
+ throw new Error(`invalid readseek ${field}: expected safe integer`);
878
+ return value;
867
879
  }
868
- function rewriteRegisteredToolNames(value, toolAliases) {
869
- if (!toolAliases)
870
- return value;
871
- return Object.entries(toolAliases).reduce((rewritten, [canonicalName, registeredName]) => rewritten.replaceAll(canonicalName, registeredName), value);
880
+ function requireString(value, field) {
881
+ if (typeof value !== "string")
882
+ throw new Error(`invalid readseek ${field}`);
883
+ return value;
872
884
  }
873
- function descriptionAliases(toolName, registeredName, toolAliases) {
874
- return Object.fromEntries(TOOL_NAMES.map((name) => {
875
- const readSeekName = `readSeek_${name}`;
876
- const alias = name === toolName && registeredName ? registeredName : toolAliases?.[readSeekName] ?? readSeekName;
877
- return [name, alias];
878
- }));
885
+ function requireBoolean(value, field) {
886
+ if (typeof value !== "boolean")
887
+ throw new Error(`invalid readseek ${field}: expected boolean`);
888
+ return value;
879
889
  }
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);
890
+ function optionalString(value, field) {
891
+ if (value === undefined || value === null)
892
+ return;
893
+ return requireString(value, field);
894
+ }
895
+ function parseHashline(value, field) {
896
+ if (!value || typeof value !== "object")
897
+ throw new Error(`invalid readseek ${field}`);
898
+ const item = value;
888
899
  return {
889
- description: renderToolDescription(toolName, aliases),
890
- promptSnippet: rewriteRegisteredToolNames(options.promptSnippet, options.toolAliases),
891
- promptGuidelines: [
892
- ...preferenceGuideline ? [preferenceGuideline] : [],
893
- ...renderCompactGuidelines(toolName, aliases)
894
- ]
900
+ line: requireNumber(item.line, `${field}.line`),
901
+ hash: requireString(item.hash, `${field}.hash`),
902
+ text: requireString(item.text, `${field}.text`)
895
903
  };
896
904
  }
897
-
898
- // src/edit-diff.ts
899
- import * as Diff from "diff";
900
-
901
- // src/hashline.ts
902
- import xxhashWasm from "xxhash-wasm";
903
- var HASH_LEN = 3;
904
- var RADIX = 16;
905
- var HASH_MOD = RADIX ** HASH_LEN;
906
- var DICT = Array.from({ length: HASH_MOD }, (_, i) => i.toString(RADIX).padStart(HASH_LEN, "0"));
907
- var HASHLINE_STATE_KEY = Symbol.for("pi-readseek.hashlineState.v1");
908
- function getHashlineState() {
909
- const globalObject = globalThis;
910
- const state = globalObject[HASHLINE_STATE_KEY] ?? {
911
- h32Fn: null,
912
- initPromise: null
913
- };
914
- globalObject[HASHLINE_STATE_KEY] = state;
915
- return state;
905
+ function parseSearchHashlines(value, field) {
906
+ if (!Array.isArray(value))
907
+ throw new Error(`invalid readseek ${field}`);
908
+ return value.map((line) => parseHashline(line, field));
916
909
  }
917
- async function ensureHashInit() {
918
- const state = getHashlineState();
919
- if (state.h32Fn)
920
- return;
921
- if (!state.initPromise) {
922
- state.initPromise = xxhashWasm().then((hasher) => {
923
- state.h32Fn = hasher.h32;
924
- });
925
- }
926
- await state.initPromise;
910
+ function parseReadOutput(value) {
911
+ if (!value || typeof value !== "object")
912
+ throw new Error("invalid readseek read output");
913
+ const output = value;
914
+ const hashlines = output.hashlines;
915
+ if (!Array.isArray(hashlines))
916
+ throw new Error("invalid readseek hashlines");
917
+ return {
918
+ file: requireString(output.file, "file"),
919
+ language: requireString(output.language, "language"),
920
+ line_count: requireNumber(output.line_count, "line_count"),
921
+ file_hash: requireString(output.file_hash, "file_hash"),
922
+ start_line: requireNumber(output.start_line, "start_line"),
923
+ end_line: requireNumber(output.end_line, "end_line"),
924
+ hashlines: hashlines.map((line) => parseHashline(line, "hashline"))
925
+ };
927
926
  }
928
- function xxh32(input) {
929
- const state = getHashlineState();
930
- if (!state.h32Fn)
931
- throw new Error("Hash not initialized — call ensureHashInit() first");
932
- return state.h32Fn(input, 0) >>> 0;
927
+ function parseMapOutput(value) {
928
+ if (!value || typeof value !== "object")
929
+ throw new Error("invalid readseek map output");
930
+ const output = value;
931
+ const symbols = output.symbols;
932
+ if (!Array.isArray(symbols))
933
+ throw new Error("invalid readseek symbols");
934
+ return {
935
+ file: requireString(output.file, "file"),
936
+ language: requireString(output.language, "language"),
937
+ line_count: requireNumber(output.line_count, "line_count"),
938
+ file_hash: requireString(output.file_hash, "file_hash"),
939
+ symbols: symbols.map((symbol) => {
940
+ if (!symbol || typeof symbol !== "object")
941
+ throw new Error("invalid readseek symbol");
942
+ const item = symbol;
943
+ return {
944
+ kind: requireString(item.kind, "symbol.kind"),
945
+ name: requireString(item.name, "symbol.name"),
946
+ qualified_name: requireString(item.qualified_name, "symbol.qualified_name"),
947
+ start_line: requireNumber(item.start_line, "symbol.start_line"),
948
+ end_line: requireNumber(item.end_line, "symbol.end_line"),
949
+ start_hash: requireString(item.start_hash, "symbol.start_hash"),
950
+ end_hash: requireString(item.end_hash, "symbol.end_hash")
951
+ };
952
+ })
953
+ };
933
954
  }
934
- function computeLineHash(line) {
935
- if (line.endsWith("\r"))
936
- line = line.slice(0, -1);
937
- line = line.replace(/\s+/g, "");
938
- return DICT[xxh32(line) % HASH_MOD];
955
+ function parseSearchOutput(value) {
956
+ if (!value || typeof value !== "object")
957
+ throw new Error("invalid readseek search output");
958
+ const output = value;
959
+ if (!Array.isArray(output.results))
960
+ throw new Error("invalid readseek search results");
961
+ return output.results.map((result) => {
962
+ if (!result || typeof result !== "object")
963
+ throw new Error("invalid readseek search result");
964
+ const file = result;
965
+ if (!Array.isArray(file.matches))
966
+ throw new Error("invalid readseek search matches");
967
+ return {
968
+ file: requireString(file.file, "search.file"),
969
+ language: requireString(file.language, "search.language"),
970
+ file_hash: requireString(file.file_hash, "search.file_hash"),
971
+ matches: file.matches.map((match) => {
972
+ if (!match || typeof match !== "object")
973
+ throw new Error("invalid readseek search match");
974
+ const item = match;
975
+ if (!Array.isArray(item.captures))
976
+ throw new Error("invalid readseek search captures");
977
+ return {
978
+ pattern_index: item.pattern_index === undefined ? 0 : requireNumber(item.pattern_index, "search.match.pattern_index"),
979
+ start_line: requireNumber(item.start_line, "search.match.start_line"),
980
+ end_line: requireNumber(item.end_line, "search.match.end_line"),
981
+ start_hash: requireString(item.start_hash, "search.match.start_hash"),
982
+ end_hash: requireString(item.end_hash, "search.match.end_hash"),
983
+ hashlines: parseSearchHashlines(item.hashlines, "search.match.hashlines"),
984
+ captures: item.captures.map((capture) => {
985
+ if (!capture || typeof capture !== "object")
986
+ throw new Error("invalid readseek search capture");
987
+ const captureItem = capture;
988
+ return {
989
+ name: requireString(captureItem.name, "search.capture.name"),
990
+ start_line: requireNumber(captureItem.start_line, "search.capture.start_line"),
991
+ end_line: requireNumber(captureItem.end_line, "search.capture.end_line"),
992
+ start_hash: requireString(captureItem.start_hash, "search.capture.start_hash"),
993
+ end_hash: requireString(captureItem.end_hash, "search.capture.end_hash"),
994
+ hashlines: parseSearchHashlines(captureItem.hashlines, "search.capture.hashlines")
995
+ };
996
+ })
997
+ };
998
+ })
999
+ };
1000
+ });
939
1001
  }
940
- var DISPLAY_CONTROL_CHAR_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
941
- function escapeControlCharsForDisplay(text) {
942
- return text.replace(DISPLAY_CONTROL_CHAR_RE, (ch) => {
943
- return `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`;
1002
+ function parseRefsOutput(value) {
1003
+ if (!value || typeof value !== "object")
1004
+ throw new Error("invalid readseek refs output");
1005
+ const output = value;
1006
+ if (!Array.isArray(output.references))
1007
+ throw new Error("invalid readseek references");
1008
+ return output.references.map((reference) => {
1009
+ if (!reference || typeof reference !== "object")
1010
+ throw new Error("invalid readseek reference");
1011
+ const item = reference;
1012
+ const symbol = item.symbol;
1013
+ const enclosing = symbol && typeof symbol === "object" ? optionalString(symbol.qualified_name, "reference.symbol.qualified_name") : undefined;
1014
+ return {
1015
+ file: requireString(item.file, "reference.file"),
1016
+ line: requireNumber(item.line, "reference.line"),
1017
+ column: requireNumber(item.column, "reference.column"),
1018
+ line_hash: requireString(item.line_hash, "reference.line_hash"),
1019
+ text: requireString(item.text, "reference.text"),
1020
+ enclosingSymbol: enclosing
1021
+ };
944
1022
  });
945
1023
  }
946
- function formatHashlineDisplay(lineNumber, content) {
947
- return `${lineNumber}:${computeLineHash(content)}|${escapeControlCharsForDisplay(content)}`;
1024
+ function parseDiagnosticKind(value) {
1025
+ if (value === "error" || value === "missing")
1026
+ return value;
1027
+ throw new Error("invalid readseek diagnostic.kind");
948
1028
  }
949
-
950
- // src/edit-diff.ts
951
- function normalizeToLF(text) {
952
- return text.replace(/\r\n/g, `
953
- `).replace(/\r/g, `
954
- `);
1029
+ function parseCheckOutput(value) {
1030
+ if (!value || typeof value !== "object")
1031
+ throw new Error("invalid readseek check output");
1032
+ const output = value;
1033
+ if (!Array.isArray(output.diagnostics))
1034
+ throw new Error("invalid readseek diagnostics");
1035
+ return {
1036
+ errorCount: requireNumber(output.error_count, "error_count"),
1037
+ missingCount: requireNumber(output.missing_count, "missing_count"),
1038
+ diagnostics: output.diagnostics.map((diagnostic) => {
1039
+ if (!diagnostic || typeof diagnostic !== "object")
1040
+ throw new Error("invalid readseek diagnostic");
1041
+ const item = diagnostic;
1042
+ return {
1043
+ kind: parseDiagnosticKind(item.kind),
1044
+ start_line: requireNumber(item.start_line, "diagnostic.start_line"),
1045
+ end_line: requireNumber(item.end_line, "diagnostic.end_line")
1046
+ };
1047
+ })
1048
+ };
955
1049
  }
956
- function stripBom(content) {
957
- return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
1050
+ function parseDetectedObjects(value) {
1051
+ if (value === undefined || value === null)
1052
+ return;
1053
+ if (!Array.isArray(value))
1054
+ throw new Error("invalid readseek detect objects");
1055
+ return value.map((object) => {
1056
+ if (!object || typeof object !== "object")
1057
+ throw new Error("invalid readseek detect object");
1058
+ const item = object;
1059
+ const bbox = item.bbox;
1060
+ if (!Array.isArray(bbox) || bbox.length !== 4)
1061
+ throw new Error("invalid readseek detect object.bbox");
1062
+ return {
1063
+ label: requireString(item.label, "object.label"),
1064
+ bbox: bbox.map((n, i) => requireNumber(n, `object.bbox[${i}]`))
1065
+ };
1066
+ });
958
1067
  }
959
- function hasBareCarriageReturn(content) {
960
- return content.replace(/\r\n/g, "").includes("\r");
1068
+ function parseImageEncoding(value) {
1069
+ if (value === "base64")
1070
+ return value;
1071
+ throw new Error("invalid image encoding");
961
1072
  }
962
- function generateDiffString(oldContent, newContent, contextLines = 4) {
963
- const parts = Diff.diffLines(oldContent, newContent);
964
- const output = [];
965
- const maxLineNum = Math.max(oldContent.split(`
966
- `).length, newContent.split(`
967
- `).length);
968
- const lineNumWidth = String(maxLineNum).length;
969
- let oldLineNum = 1;
970
- let newLineNum = 1;
971
- let lastWasChange = false;
972
- let firstChangedLine;
973
- for (let i = 0;i < parts.length; i++) {
974
- const part = parts[i];
975
- const raw = part.value.split(`
976
- `);
977
- if (raw[raw.length - 1] === "")
978
- raw.pop();
979
- if (part.added || part.removed) {
980
- if (firstChangedLine === undefined)
981
- firstChangedLine = newLineNum;
982
- for (const line of raw) {
983
- if (part.added) {
984
- output.push(`+${String(newLineNum).padStart(lineNumWidth, " ")} ${line}`);
985
- newLineNum++;
986
- } else {
987
- output.push(`-${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`);
988
- oldLineNum++;
989
- }
1073
+ function parseDetectOutput(value) {
1074
+ if (!value || typeof value !== "object")
1075
+ throw new Error("invalid readseek detect output");
1076
+ const output = value;
1077
+ const type = requireString(output.type, "type");
1078
+ const file = requireString(output.file, "file");
1079
+ const mime = optionalString(output.mime, "mime");
1080
+ if (type === "application/pdf") {
1081
+ if (output.format !== "pdf")
1082
+ throw new Error("invalid readseek PDF format");
1083
+ return {
1084
+ kind: "pdf",
1085
+ type,
1086
+ file,
1087
+ mime,
1088
+ format: output.format,
1089
+ pages: requireNumber(output.pages, "pages")
1090
+ };
1091
+ }
1092
+ if (output.width !== undefined || output.height !== undefined) {
1093
+ return {
1094
+ kind: "image",
1095
+ type,
1096
+ file,
1097
+ mime,
1098
+ format: requireString(output.format, "format"),
1099
+ width: requireNumber(output.width, "width"),
1100
+ height: requireNumber(output.height, "height"),
1101
+ animated: requireBoolean(output.animated, "animated"),
1102
+ encoding: output.encoding === undefined ? undefined : parseImageEncoding(output.encoding),
1103
+ data: output.data === undefined ? undefined : requireString(output.data, "data"),
1104
+ ocr: optionalString(output.ocr, "ocr"),
1105
+ caption: optionalString(output.caption, "caption"),
1106
+ objects: parseDetectedObjects(output.objects)
1107
+ };
1108
+ }
1109
+ if (output.language !== undefined) {
1110
+ return {
1111
+ kind: "source",
1112
+ type,
1113
+ file,
1114
+ language: requireString(output.language, "language"),
1115
+ engine: optionalString(output.engine, "engine"),
1116
+ supported: requireBoolean(output.supported, "supported"),
1117
+ mime,
1118
+ syntax: optionalString(output.syntax, "syntax")
1119
+ };
1120
+ }
1121
+ return { kind: "text", type, file, mime };
1122
+ }
1123
+ function parseDocumentCache(value) {
1124
+ if (!value || typeof value !== "object")
1125
+ throw new Error("invalid readseek document cache");
1126
+ const cache = value;
1127
+ const state = requireString(cache.state, "cache.state");
1128
+ if (state !== "built" && state !== "reused")
1129
+ throw new Error("invalid readseek cache.state");
1130
+ return {
1131
+ content_hash: requireString(cache.content_hash, "cache.content_hash"),
1132
+ state
1133
+ };
1134
+ }
1135
+ function parseIdentifyOutput(value) {
1136
+ if (!value || typeof value !== "object")
1137
+ throw new Error("invalid readseek identify output");
1138
+ const output = value;
1139
+ const identifier = output.identifier;
1140
+ const symbol = output.symbol;
1141
+ return {
1142
+ file: requireString(output.file, "file"),
1143
+ language: requireString(output.language, "language"),
1144
+ engine: optionalString(output.engine, "engine"),
1145
+ line_count: requireNumber(output.line_count, "line_count"),
1146
+ file_hash: requireString(output.file_hash, "file_hash"),
1147
+ line: requireNumber(output.line, "line"),
1148
+ column: requireNumber(output.column, "column"),
1149
+ line_hash: requireString(output.line_hash, "line_hash"),
1150
+ identifier: identifier && typeof identifier === "object" ? {
1151
+ text: requireString(identifier.text, "identifier.text"),
1152
+ start_column: requireNumber(identifier.start_column, "identifier.start_column"),
1153
+ end_column: requireNumber(identifier.end_column, "identifier.end_column"),
1154
+ start_byte: requireNumber(identifier.start_byte, "identifier.start_byte"),
1155
+ end_byte: requireNumber(identifier.end_byte, "identifier.end_byte")
1156
+ } : undefined,
1157
+ symbol: symbol && typeof symbol === "object" ? {
1158
+ name: requireString(symbol.name, "symbol.name"),
1159
+ kind: requireString(symbol.kind, "symbol.kind"),
1160
+ qualified_name: requireString(symbol.qualified_name, "symbol.qualified_name"),
1161
+ start_line: requireNumber(symbol.start_line, "symbol.start_line"),
1162
+ end_line: requireNumber(symbol.end_line, "symbol.end_line")
1163
+ } : undefined
1164
+ };
1165
+ }
1166
+ function parseImageContent(value) {
1167
+ const detection = parseDetectOutput(value);
1168
+ if (detection.kind !== "image")
1169
+ throw new Error("invalid readseek image content");
1170
+ return detection;
1171
+ }
1172
+ function parseTextContent(value) {
1173
+ if (!value || typeof value !== "object")
1174
+ throw new Error("invalid readseek content");
1175
+ const output = value;
1176
+ if (output.hashlines !== undefined && output.start_line === undefined) {
1177
+ const hashlines = parseSearchHashlines(output.hashlines, "content.hashlines");
1178
+ const first = hashlines[0];
1179
+ const last = hashlines[hashlines.length - 1];
1180
+ return {
1181
+ file: requireString(output.file, "file"),
1182
+ language: requireString(output.language, "language"),
1183
+ line_count: requireNumber(output.line_count, "line_count"),
1184
+ file_hash: requireString(output.file_hash, "file_hash"),
1185
+ start_line: first?.line ?? 1,
1186
+ end_line: last?.line ?? 0,
1187
+ hashlines
1188
+ };
1189
+ }
1190
+ return parseReadOutput(value);
1191
+ }
1192
+ function parseDigestEnvelope(value) {
1193
+ if (!value || typeof value !== "object")
1194
+ throw new Error("invalid readseek digest output");
1195
+ const output = value;
1196
+ if (output.metadata === undefined)
1197
+ throw new Error("invalid readseek digest metadata");
1198
+ const result = {
1199
+ metadata: parseDetectOutput(output.metadata)
1200
+ };
1201
+ if (output.cache !== undefined)
1202
+ result.cache = parseDocumentCache(output.cache);
1203
+ if (output.content !== undefined)
1204
+ result.content = output.content;
1205
+ if (output.map !== undefined)
1206
+ result.map = parseMapOutput(output.map);
1207
+ if (output.diagnostics !== undefined)
1208
+ result.diagnostics = parseCheckOutput(output.diagnostics);
1209
+ if (output.identity !== undefined)
1210
+ result.identity = parseIdentifyOutput(output.identity);
1211
+ return result;
1212
+ }
1213
+ function parseEditOutput(value) {
1214
+ if (!value || typeof value !== "object")
1215
+ throw new Error("invalid readseek edit output");
1216
+ const output = value;
1217
+ return {
1218
+ file: requireString(output.file, "file"),
1219
+ before_hash: requireString(output.before_hash, "before_hash"),
1220
+ file_hash: requireString(output.file_hash, "file_hash"),
1221
+ plan_hash: requireString(output.plan_hash, "plan_hash"),
1222
+ applied: requireBoolean(output.applied, "applied"),
1223
+ changed: requireBoolean(output.changed, "changed"),
1224
+ edit_count: requireNumber(output.edit_count, "edit_count"),
1225
+ before_content: optionalString(output.before_content, "before_content"),
1226
+ content: optionalString(output.content, "content")
1227
+ };
1228
+ }
1229
+ function parseRenameConflicts(value, field) {
1230
+ if (value === undefined)
1231
+ return [];
1232
+ if (!Array.isArray(value))
1233
+ throw new Error(`invalid readseek ${field}`);
1234
+ return value.map((item) => {
1235
+ if (!item || typeof item !== "object")
1236
+ throw new Error(`invalid readseek ${field} entry`);
1237
+ const c = item;
1238
+ return {
1239
+ line: requireNumber(c.line, `${field}.line`),
1240
+ column: requireNumber(c.column, `${field}.column`),
1241
+ reason: requireString(c.reason, `${field}.reason`)
1242
+ };
1243
+ });
1244
+ }
1245
+ function parseRenameEdits(value, field) {
1246
+ if (value === undefined)
1247
+ return [];
1248
+ if (!Array.isArray(value))
1249
+ throw new Error(`invalid readseek ${field} entry`);
1250
+ return value.map((item) => {
1251
+ if (!item || typeof item !== "object")
1252
+ throw new Error(`invalid readseek ${field} entry`);
1253
+ const e = item;
1254
+ return {
1255
+ line: requireNumber(e.line, `${field}.line`),
1256
+ start_column: requireNumber(e.start_column, `${field}.start_column`),
1257
+ end_column: requireNumber(e.end_column, `${field}.end_column`),
1258
+ start_byte: requireNumber(e.start_byte, `${field}.start_byte`),
1259
+ end_byte: requireNumber(e.end_byte, `${field}.end_byte`),
1260
+ occurrence: requireString(e.occurrence, `${field}.occurrence`),
1261
+ line_hash: requireString(e.line_hash, `${field}.line_hash`),
1262
+ text: requireString(e.text, `${field}.text`)
1263
+ };
1264
+ });
1265
+ }
1266
+ function parseRenameOutput(value) {
1267
+ if (!value || typeof value !== "object")
1268
+ throw new Error("invalid readseek rename output");
1269
+ const output = value;
1270
+ const others = output.others;
1271
+ if (others !== undefined && !Array.isArray(others))
1272
+ throw new Error("invalid readseek others");
1273
+ return {
1274
+ file: requireString(output.file, "file"),
1275
+ language: requireString(output.language, "language"),
1276
+ engine: optionalString(output.engine, "engine"),
1277
+ file_hash: requireString(output.file_hash, "file_hash"),
1278
+ old_name: requireString(output.old_name, "old_name"),
1279
+ new_name: requireString(output.new_name, "new_name"),
1280
+ applied: requireBoolean(output.applied, "applied"),
1281
+ conflicts: parseRenameConflicts(output.conflicts, "conflicts"),
1282
+ edits: parseRenameEdits(output.edits, "edits"),
1283
+ others: others?.map((entry) => {
1284
+ if (!entry || typeof entry !== "object")
1285
+ throw new Error("invalid readseek other");
1286
+ const o = entry;
1287
+ return {
1288
+ file: requireString(o.file, "other.file"),
1289
+ language: requireString(o.language, "other.language"),
1290
+ engine: optionalString(o.engine, "other.engine"),
1291
+ file_hash: requireString(o.file_hash, "other.file_hash"),
1292
+ conflicts: parseRenameConflicts(o.conflicts, "other.conflicts"),
1293
+ edits: parseRenameEdits(o.edits, "other.edits")
1294
+ };
1295
+ }) ?? []
1296
+ };
1297
+ }
1298
+ function parseDefCompact(value) {
1299
+ if (!value || typeof value !== "object")
1300
+ throw new Error("invalid readseek def output");
1301
+ const output = value;
1302
+ const locations = output.locations;
1303
+ if (!Array.isArray(locations))
1304
+ throw new Error("invalid readseek def locations");
1305
+ return locations.map((loc) => {
1306
+ if (!loc || typeof loc !== "object")
1307
+ throw new Error("invalid readseek def location");
1308
+ const item = loc;
1309
+ return {
1310
+ file: requireString(item.file, "location.file"),
1311
+ line: requireNumber(item.line, "location.line"),
1312
+ column: requireNumber(item.column, "location.column"),
1313
+ line_hash: requireString(item.line_hash, "location.line_hash"),
1314
+ text: requireString(item.text, "location.text"),
1315
+ kind: optionalString(item.kind, "location.kind"),
1316
+ name: optionalString(item.name, "location.name"),
1317
+ qualified_name: optionalString(item.qualified_name, "location.qualified_name")
1318
+ };
1319
+ });
1320
+ }
1321
+ function createReadSeekClient(runner) {
1322
+ function digestArgs(target, options) {
1323
+ return buildDigestArgs({
1324
+ path: target,
1325
+ select: options.select ?? "content",
1326
+ at: options.at,
1327
+ end: options.end,
1328
+ limit: options.limit,
1329
+ language: options.language,
1330
+ visionMode: options.visionMode,
1331
+ visionLevel: options.visionLevel,
1332
+ stdinName: options.stdin !== undefined && target === "-" ? options.stdinName ?? target : undefined
1333
+ });
1334
+ }
1335
+ async function runDigestRaw(target, options = {}) {
1336
+ const args = digestArgs(target, options);
1337
+ const runOptions = {
1338
+ signal: options.signal,
1339
+ stdin: options.stdin,
1340
+ timeoutMs: options.timeoutMs,
1341
+ vision: options.vision
1342
+ };
1343
+ return runner.json(args, runOptions);
1344
+ }
1345
+ async function digest(target, options = {}) {
1346
+ return parseDigestEnvelope(await runDigestRaw(target, options));
1347
+ }
1348
+ function requireDigestContent(envelope, field) {
1349
+ if (envelope.content === undefined)
1350
+ throw new Error(`invalid readseek ${field}: missing content`);
1351
+ return envelope.content;
1352
+ }
1353
+ async function digestContent(filePath, options = {}) {
1354
+ const envelope = await digest(filePath, {
1355
+ select: "content",
1356
+ at: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1357
+ end: options.endLine,
1358
+ visionMode: options.visionMode,
1359
+ signal: options.signal,
1360
+ vision: options.vision
1361
+ });
1362
+ if (envelope.metadata.kind === "pdf") {
1363
+ return { metadata: envelope.metadata, cache: envelope.cache };
1364
+ }
1365
+ const content = requireDigestContent(envelope, "content");
1366
+ if (envelope.metadata.kind === "image") {
1367
+ return { metadata: envelope.metadata, image: parseImageContent(content) };
1368
+ }
1369
+ return { metadata: envelope.metadata, text: parseTextContent(content) };
1370
+ }
1371
+ return {
1372
+ async view(filePath, options = {}) {
1373
+ const args = buildViewArgs({
1374
+ path: filePath,
1375
+ node: options.node,
1376
+ page: options.page,
1377
+ kind: options.kind,
1378
+ depth: options.depth,
1379
+ outline: options.outline,
1380
+ visionMode: options.visionMode,
1381
+ visionLevel: options.visionLevel
1382
+ });
1383
+ return runner.text(args, {
1384
+ signal: options.signal,
1385
+ vision: options.visionMode !== undefined
1386
+ });
1387
+ },
1388
+ digestRaw(target, options = {}) {
1389
+ return runDigestRaw(target, options);
1390
+ },
1391
+ digest,
1392
+ digestContent,
1393
+ async image(filePath, modes, options = {}) {
1394
+ const requestedModes = modes.length > 1 ? ["all"] : modes;
1395
+ const results = await Promise.allSettled(requestedModes.map(async (mode) => {
1396
+ const digested = await digestContent(filePath, {
1397
+ visionMode: mode,
1398
+ signal: options.signal,
1399
+ vision: true
1400
+ });
1401
+ if (!digested.image)
1402
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1403
+ return digested.image;
1404
+ }));
1405
+ let merged;
1406
+ for (const result of results) {
1407
+ if (result.status !== "fulfilled")
1408
+ continue;
1409
+ const detection = result.value;
1410
+ merged = merged === undefined ? detection : {
1411
+ ...merged,
1412
+ ocr: detection.ocr ?? merged.ocr,
1413
+ caption: detection.caption ?? merged.caption,
1414
+ objects: detection.objects ?? merged.objects
1415
+ };
1416
+ }
1417
+ if (merged !== undefined)
1418
+ return merged;
1419
+ const failure = results.find((result) => result.status === "rejected");
1420
+ if (failure?.status === "rejected")
1421
+ throw failure.reason;
1422
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1423
+ },
1424
+ async preparedImage(filePath, options = {}) {
1425
+ const digested = await digestContent(filePath, {
1426
+ visionMode: "none",
1427
+ signal: options.signal
1428
+ });
1429
+ const output = digested.image;
1430
+ if (!output || output.encoding !== "base64" || output.data === undefined || output.mime === undefined) {
1431
+ throw new Error(`readseek returned no prepared image for ${filePath}`);
1432
+ }
1433
+ return { mime: output.mime, encoding: output.encoding, data: output.data };
1434
+ },
1435
+ async search(target, pattern, options = {}) {
1436
+ const args = buildSearchArgs({
1437
+ path: target,
1438
+ pattern,
1439
+ language: options.language,
1440
+ cached: options.cached,
1441
+ others: options.others,
1442
+ ignored: options.ignored
1443
+ });
1444
+ return parseSearchOutput(await runner.json(args, { signal: options.signal }));
1445
+ },
1446
+ async refs(target, options = {}) {
1447
+ const args = buildRefsArgs({
1448
+ path: target,
1449
+ name: options.name,
1450
+ scope: options.scope,
1451
+ line: options.line,
1452
+ column: options.column,
1453
+ language: options.language,
1454
+ cached: options.cached,
1455
+ others: options.others,
1456
+ ignored: options.ignored
1457
+ });
1458
+ return parseRefsOutput(await runner.json(args, { signal: options.signal }));
1459
+ },
1460
+ async def(target, options) {
1461
+ const args = buildDefArgs({
1462
+ path: target,
1463
+ name: options.name,
1464
+ format: "plain",
1465
+ language: options.language,
1466
+ cached: options.cached,
1467
+ others: options.others,
1468
+ ignored: options.ignored
1469
+ });
1470
+ return parseDefCompact(await runner.json(args, { signal: options.signal }));
1471
+ },
1472
+ async check(filePath, content, options = {}) {
1473
+ const envelope = await digest("-", {
1474
+ select: "diagnostics",
1475
+ stdin: content,
1476
+ stdinName: filePath,
1477
+ signal: options.signal
1478
+ });
1479
+ if (!envelope.diagnostics)
1480
+ throw new Error("invalid readseek diagnostics: missing diagnostics facet");
1481
+ return envelope.diagnostics;
1482
+ },
1483
+ async identify(filePath, content, options = {}) {
1484
+ const envelope = await digest("-", {
1485
+ select: "identity",
1486
+ stdin: content,
1487
+ stdinName: filePath,
1488
+ at: options.line !== undefined ? buildLineLocation(options.line, options.column) : undefined,
1489
+ language: options.language,
1490
+ signal: options.signal
1491
+ });
1492
+ if (!envelope.identity)
1493
+ throw new Error("invalid readseek identity: missing identity facet");
1494
+ return envelope.identity;
1495
+ },
1496
+ async detect(filePath, options = {}) {
1497
+ const envelope = await digest(filePath, { select: "metadata", signal: options.signal });
1498
+ return envelope.metadata;
1499
+ },
1500
+ async edit(filePath, edits, options = {}) {
1501
+ const args = buildEditArgs({
1502
+ path: filePath,
1503
+ apply: options.apply,
1504
+ planHash: options.planHash,
1505
+ language: options.language
1506
+ });
1507
+ return parseEditOutput(await runner.json(args, {
1508
+ signal: options.signal,
1509
+ stdin: JSON.stringify({ edits }),
1510
+ timeoutMs: options.timeoutMs,
1511
+ cancelable: options.cancelable
1512
+ }));
1513
+ },
1514
+ async rename(filePath, options) {
1515
+ const args = buildRenameArgs({
1516
+ path: filePath,
1517
+ to: options.to,
1518
+ line: options.line,
1519
+ column: options.column,
1520
+ workspace: options.workspace,
1521
+ apply: options.apply,
1522
+ language: options.language,
1523
+ cached: options.cached,
1524
+ others: options.others,
1525
+ ignored: options.ignored
1526
+ });
1527
+ return parseRenameOutput(await runner.json(args, { signal: options.signal }));
1528
+ }
1529
+ };
1530
+ }
1531
+ // src/tool-prompt-metadata.ts
1532
+ var REPLACEABLE_BY_TOOL = new Map(REPLACEABLE_TOOLS.map((entry) => [entry.tool, entry]));
1533
+ function toolNameFromPrompt(fileName) {
1534
+ const name = fileName.replace(/\.md$/, "");
1535
+ if (!TOOL_NAMES.includes(name))
1536
+ throw new Error(`unknown ReadSeek tool prompt: ${fileName}`);
1537
+ return name;
1538
+ }
1539
+ function promptFileName(promptUrl) {
1540
+ return promptUrl.pathname.split("/").pop() ?? "";
1541
+ }
1542
+ function rewriteRegisteredToolNames(value, toolAliases) {
1543
+ if (!toolAliases)
1544
+ return value;
1545
+ return Object.entries(toolAliases).reduce((rewritten, [canonicalName, registeredName]) => rewritten.replaceAll(canonicalName, registeredName), value);
1546
+ }
1547
+ function descriptionAliases(toolName, registeredName, toolAliases) {
1548
+ return Object.fromEntries(TOOL_NAMES.map((name) => {
1549
+ const readSeekName = `readSeek_${name}`;
1550
+ const alias = name === toolName && registeredName ? registeredName : toolAliases?.[readSeekName] ?? readSeekName;
1551
+ return [name, alias];
1552
+ }));
1553
+ }
1554
+ function defineToolPromptMetadata(options) {
1555
+ const fileName = promptFileName(options.promptUrl);
1556
+ const toolName = toolNameFromPrompt(fileName);
1557
+ const replaceable = REPLACEABLE_BY_TOOL.get(toolName);
1558
+ const defaultReadSeekName = `readSeek_${toolName}`;
1559
+ const registeredName = options.registeredName ?? (replaceable ? defaultReadSeekName : undefined);
1560
+ const preferenceGuideline = replaceable && registeredName ? registeredName === defaultReadSeekName ? `Prefer ${registeredName} over ${replaceable.builtIn} when both are available; ${replaceable.benefit}` : `Use ${registeredName}; ${replaceable.benefit}` : undefined;
1561
+ const aliases = descriptionAliases(toolName, registeredName, options.toolAliases);
1562
+ return {
1563
+ description: renderToolDescription(toolName, aliases),
1564
+ promptSnippet: rewriteRegisteredToolNames(options.promptSnippet, options.toolAliases),
1565
+ promptGuidelines: [
1566
+ ...preferenceGuideline ? [preferenceGuideline] : [],
1567
+ ...renderCompactGuidelines(toolName, aliases)
1568
+ ]
1569
+ };
1570
+ }
1571
+
1572
+ // src/edit-diff.ts
1573
+ import * as Diff from "diff";
1574
+
1575
+ // src/hashline.ts
1576
+ import xxhashWasm from "xxhash-wasm";
1577
+ var HASH_LEN = 3;
1578
+ var RADIX = 16;
1579
+ var HASH_MOD = RADIX ** HASH_LEN;
1580
+ var DICT = Array.from({ length: HASH_MOD }, (_, i) => i.toString(RADIX).padStart(HASH_LEN, "0"));
1581
+ var HASHLINE_STATE_KEY = Symbol.for("pi-readseek.hashlineState.v1");
1582
+ function getHashlineState() {
1583
+ const globalObject = globalThis;
1584
+ const state = globalObject[HASHLINE_STATE_KEY] ?? {
1585
+ h32Fn: null,
1586
+ initPromise: null
1587
+ };
1588
+ globalObject[HASHLINE_STATE_KEY] = state;
1589
+ return state;
1590
+ }
1591
+ async function ensureHashInit() {
1592
+ const state = getHashlineState();
1593
+ if (state.h32Fn)
1594
+ return;
1595
+ if (!state.initPromise) {
1596
+ state.initPromise = xxhashWasm().then((hasher) => {
1597
+ state.h32Fn = hasher.h32;
1598
+ });
1599
+ }
1600
+ await state.initPromise;
1601
+ }
1602
+ function xxh32(input) {
1603
+ const state = getHashlineState();
1604
+ if (!state.h32Fn)
1605
+ throw new Error("Hash not initialized — call ensureHashInit() first");
1606
+ return state.h32Fn(input, 0) >>> 0;
1607
+ }
1608
+ function computeLineHash(line) {
1609
+ if (line.endsWith("\r"))
1610
+ line = line.slice(0, -1);
1611
+ line = line.replace(/\s+/g, "");
1612
+ return DICT[xxh32(line) % HASH_MOD];
1613
+ }
1614
+ var DISPLAY_CONTROL_CHAR_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
1615
+ function escapeControlCharsForDisplay(text) {
1616
+ return text.replace(DISPLAY_CONTROL_CHAR_RE, (ch) => {
1617
+ return `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`;
1618
+ });
1619
+ }
1620
+ function formatHashlineDisplay(lineNumber, content) {
1621
+ return `${lineNumber}:${computeLineHash(content)}|${escapeControlCharsForDisplay(content)}`;
1622
+ }
1623
+
1624
+ // src/edit-diff.ts
1625
+ function normalizeToLF(text) {
1626
+ return text.replace(/\r\n/g, `
1627
+ `).replace(/\r/g, `
1628
+ `);
1629
+ }
1630
+ function stripBom(content) {
1631
+ return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
1632
+ }
1633
+ function hasBareCarriageReturn(content) {
1634
+ return content.replace(/\r\n/g, "").includes("\r");
1635
+ }
1636
+ function generateDiffString(oldContent, newContent, contextLines = 4) {
1637
+ const parts = Diff.diffLines(oldContent, newContent);
1638
+ const output = [];
1639
+ const maxLineNum = Math.max(oldContent.split(`
1640
+ `).length, newContent.split(`
1641
+ `).length);
1642
+ const lineNumWidth = String(maxLineNum).length;
1643
+ let oldLineNum = 1;
1644
+ let newLineNum = 1;
1645
+ let lastWasChange = false;
1646
+ let firstChangedLine;
1647
+ for (let i = 0;i < parts.length; i++) {
1648
+ const part = parts[i];
1649
+ const raw = part.value.split(`
1650
+ `);
1651
+ if (raw[raw.length - 1] === "")
1652
+ raw.pop();
1653
+ if (part.added || part.removed) {
1654
+ if (firstChangedLine === undefined)
1655
+ firstChangedLine = newLineNum;
1656
+ for (const line of raw) {
1657
+ if (part.added) {
1658
+ output.push(`+${String(newLineNum).padStart(lineNumWidth, " ")} ${line}`);
1659
+ newLineNum++;
1660
+ } else {
1661
+ output.push(`-${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`);
1662
+ oldLineNum++;
1663
+ }
990
1664
  }
991
1665
  lastWasChange = true;
992
1666
  continue;
@@ -1257,964 +1931,630 @@ function buildReadSeekWarning(code, message, metadata = {}) {
1257
1931
  function buildReadSeekError(code, message, hint, details) {
1258
1932
  return {
1259
1933
  code,
1260
- message,
1261
- ...hint !== undefined ? { hint } : {},
1262
- ...details !== undefined ? { details } : {}
1263
- };
1264
- }
1265
- function buildToolErrorResult(tool, code, message, opts = {}) {
1266
- return {
1267
- content: [{ type: "text", text: message }],
1268
- isError: true,
1269
- details: {
1270
- readSeekValue: {
1271
- tool,
1272
- ...opts.extra ?? {},
1273
- ok: false,
1274
- ...opts.path !== undefined ? { path: opts.path } : {},
1275
- error: buildReadSeekError(code, message, opts.hint, opts.details)
1276
- }
1277
- }
1278
- };
1279
- }
1280
- function buildReadSeekEditResult(input) {
1281
- return {
1282
- tool: "edit",
1283
- ok: input.ok ?? true,
1284
- path: input.path,
1285
- summary: input.summary,
1286
- diff: input.diff,
1287
- ...input.diffData ? { diffData: input.diffData } : {},
1288
- firstChangedLine: input.firstChangedLine,
1289
- warnings: [...input.warnings],
1290
- noopEdits: [...input.noopEdits],
1291
- ...input.semanticSummary ? { semanticSummary: input.semanticSummary } : {}
1292
- };
1293
- }
1294
-
1295
- // src/edit-output.ts
1296
- var EDIT_OPERATION_NAMES = ["set_line", "replace_lines", "insert_after", "replace"];
1297
- function hasNewText(value) {
1298
- return typeof value === "object" && value !== null && typeof value.new_text === "string";
1299
- }
1300
- function getVisibleDiffStats(diff) {
1301
- const stats = parseDiffStats(diff);
1302
- if (stats.added > 0 || stats.removed > 0)
1303
- return stats;
1304
- if (!diff.includes("→"))
1305
- return stats;
1306
- if (diff.includes("→ [deleted]"))
1307
- return { added: 0, removed: 1 };
1308
- return { added: 1, removed: 1 };
1309
- }
1310
- function buildVisibleSummary(displayPath, diff, edits) {
1311
- let stats = getVisibleDiffStats(diff);
1312
- const counts = countEditTypes(edits);
1313
- const editCount = counts.total || 1;
1314
- if (counts.total > 0 && counts.insert_after === counts.total && stats.removed > 0 && stats.added === stats.removed + counts.insert_after) {
1315
- stats = { added: counts.insert_after, removed: 0 };
1316
- }
1317
- const changeWord = editCount === 1 ? "change" : "changes";
1318
- const changedLineCount = Math.max(stats.added, stats.removed);
1319
- const lineWord = changedLineCount === 1 ? "line" : "lines";
1320
- return `Edited ${displayPath} (${editCount} ${changeWord}, +${stats.added} -${stats.removed} ${lineWord})`;
1321
- }
1322
- function extractNewTextValues(edits) {
1323
- const values = [];
1324
- for (const edit of edits ?? []) {
1325
- if (!edit || typeof edit !== "object")
1326
- continue;
1327
- const operations = edit;
1328
- for (const operationName of EDIT_OPERATION_NAMES) {
1329
- const operation = operations[operationName];
1330
- if (hasNewText(operation))
1331
- values.push(operation.new_text);
1332
- }
1333
- }
1334
- return values;
1335
- }
1336
- function formatWhitespaceOnlyWarning(semanticSummary, edits) {
1337
- if (semanticSummary?.classification !== "whitespace-only")
1338
- return;
1339
- if (!extractNewTextValues(edits).some((text) => /\S/.test(text)))
1340
- return;
1341
- return "⚠ Edit classified as whitespace-only — if you intended a behavior change, re-read to verify.";
1342
- }
1343
- function formatReplaceHint(edits, noopEdits) {
1344
- if ((noopEdits ?? []).length > 0)
1345
- return;
1346
- const counts = countEditTypes(edits);
1347
- if (counts.replace === 0)
1348
- return;
1349
- if (counts.replace !== counts.total)
1350
- return;
1351
- return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_digest/readSeek_grep/readSeek_search.]";
1352
- }
1353
- function buildEditOutput(input) {
1354
- const summary = `Updated ${input.displayPath}`;
1355
- const visibleSummary = buildVisibleSummary(input.displayPath, input.diff, input.edits);
1356
- const semanticWarning = formatWhitespaceOnlyWarning(input.semanticSummary, input.edits);
1357
- const warningText = input.warnings.length ? `
1358
-
1359
- Warnings:
1360
- ${input.warnings.join(`
1361
- `)}` : "";
1362
- const replaceHint = formatReplaceHint(input.edits, input.noopEdits);
1363
- let text = visibleSummary;
1364
- if (semanticWarning)
1365
- text += `
1366
- ${semanticWarning}`;
1367
- text += warningText;
1368
- if (replaceHint)
1369
- text += `
1370
- ${replaceHint}`;
1371
- return {
1372
- text,
1373
- patch: input.patch ?? "",
1374
- readSeekValue: buildReadSeekEditResult({
1375
- path: input.path,
1376
- summary,
1377
- diff: input.diff,
1378
- ...input.diffData ? { diffData: input.diffData } : {},
1379
- firstChangedLine: input.firstChangedLine,
1380
- warnings: input.warnings,
1381
- noopEdits: input.noopEdits,
1382
- ...input.semanticSummary ? { semanticSummary: input.semanticSummary } : {}
1383
- })
1384
- };
1385
- }
1386
-
1387
- // src/edit-classify.ts
1388
- import * as Diff2 from "diff";
1389
- function classifyEdit(oldContent, newContent) {
1390
- if (oldContent === newContent) {
1391
- return { classification: "no-op" };
1392
- }
1393
- const oldLines = oldContent.split(`
1394
- `);
1395
- const newLines = newContent.split(`
1396
- `);
1397
- if (oldLines.length === newLines.length) {
1398
- let hasWhitespaceChange2 = false;
1399
- let hasSemanticChange2 = false;
1400
- for (let i = 0;i < oldLines.length; i++) {
1401
- if (oldLines[i] === newLines[i])
1402
- continue;
1403
- if (oldLines[i].trim() === newLines[i].trim()) {
1404
- hasWhitespaceChange2 = true;
1405
- } else {
1406
- hasSemanticChange2 = true;
1407
- }
1408
- }
1409
- if (hasSemanticChange2 && hasWhitespaceChange2)
1410
- return { classification: "mixed" };
1411
- if (hasWhitespaceChange2)
1412
- return { classification: "whitespace-only" };
1413
- return { classification: "semantic" };
1414
- }
1415
- const parts = Diff2.diffLines(oldContent, newContent);
1416
- let hasWhitespaceChange = false;
1417
- let hasSemanticChange = false;
1418
- for (const part of parts) {
1419
- if (!part.added && !part.removed)
1420
- continue;
1421
- const lines = part.value.split(`
1422
- `);
1423
- if (lines[lines.length - 1] === "")
1424
- lines.pop();
1425
- for (const line of lines) {
1426
- if (line.trim() === "") {
1427
- hasWhitespaceChange = true;
1428
- } else {
1429
- hasSemanticChange = true;
1430
- }
1431
- }
1432
- }
1433
- if (hasSemanticChange && hasWhitespaceChange)
1434
- return { classification: "mixed" };
1435
- if (hasWhitespaceChange)
1436
- return { classification: "whitespace-only" };
1437
- return { classification: "semantic" };
1438
- }
1439
-
1440
- // src/readseek-client.ts
1441
- import { existsSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
1442
- import { createRequire as createRequire2 } from "node:module";
1443
- import { homedir as homedir3 } from "node:os";
1444
- import path2 from "node:path";
1445
-
1446
- // src/readseek/enums.ts
1447
- var SymbolKind;
1448
- ((SymbolKind2) => {
1449
- SymbolKind2["Class"] = "class";
1450
- SymbolKind2["Function"] = "function";
1451
- SymbolKind2["Method"] = "method";
1452
- SymbolKind2["Variable"] = "variable";
1453
- SymbolKind2["Constant"] = "constant";
1454
- SymbolKind2["Interface"] = "interface";
1455
- SymbolKind2["Type"] = "type";
1456
- SymbolKind2["Enum"] = "enum";
1457
- SymbolKind2["Struct"] = "struct";
1458
- SymbolKind2["Import"] = "import";
1459
- SymbolKind2["Module"] = "module";
1460
- SymbolKind2["Namespace"] = "namespace";
1461
- SymbolKind2["Property"] = "property";
1462
- SymbolKind2["Heading"] = "heading";
1463
- SymbolKind2["Table"] = "table";
1464
- SymbolKind2["View"] = "view";
1465
- SymbolKind2["Procedure"] = "procedure";
1466
- SymbolKind2["Trigger"] = "trigger";
1467
- SymbolKind2["Index"] = "index";
1468
- SymbolKind2["Schema"] = "schema";
1469
- SymbolKind2["Signal"] = "signal";
1470
- SymbolKind2["Unknown"] = "unknown";
1471
- })(SymbolKind ||= {});
1472
-
1473
- // src/readseek-settings.ts
1474
- import { readFileSync, statSync } from "node:fs";
1475
- import { homedir as homedir2 } from "node:os";
1476
- import { join } from "node:path";
1477
- var READSEEK_TOOL_REPLACEMENTS = {
1478
- read: "readSeek_digest",
1479
- edit: "readSeek_edit",
1480
- grep: "readSeek_grep",
1481
- write: "readSeek_write"
1482
- };
1483
- var READSEEK_KEYS = ["overrideTools", "imageMode", "syntaxValidation", "timeoutMs", "grep", "display"];
1484
- var READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
1485
- var READSEEK_DISPLAY_KEYS = ["read", "grep", "edit", "write"];
1486
- var DEFAULT_DISPLAY_MODES = {
1487
- read: "compact",
1488
- grep: "compact",
1489
- edit: "expanded",
1490
- write: "expanded"
1491
- };
1492
- function globalSettingsPath() {
1493
- return join(homedir2(), ".pi/agent/settings.json");
1934
+ message,
1935
+ ...hint !== undefined ? { hint } : {},
1936
+ ...details !== undefined ? { details } : {}
1937
+ };
1494
1938
  }
1495
- function projectSettingsPath() {
1496
- return join(process.cwd(), ".pi/settings.json");
1939
+ function buildToolErrorResult(tool, code, message, opts = {}) {
1940
+ return {
1941
+ content: [{ type: "text", text: message }],
1942
+ isError: true,
1943
+ details: {
1944
+ readSeekValue: {
1945
+ tool,
1946
+ ...opts.extra ?? {},
1947
+ ok: false,
1948
+ ...opts.path !== undefined ? { path: opts.path } : {},
1949
+ error: buildReadSeekError(code, message, opts.hint, opts.details)
1950
+ }
1951
+ }
1952
+ };
1497
1953
  }
1498
- function isRecord(value) {
1499
- return typeof value === "object" && value !== null && !Array.isArray(value);
1954
+ function buildReadSeekEditResult(input) {
1955
+ return {
1956
+ tool: "edit",
1957
+ ok: input.ok ?? true,
1958
+ path: input.path,
1959
+ summary: input.summary,
1960
+ diff: input.diff,
1961
+ ...input.diffData ? { diffData: input.diffData } : {},
1962
+ firstChangedLine: input.firstChangedLine,
1963
+ warnings: [...input.warnings],
1964
+ noopEdits: [...input.noopEdits],
1965
+ ...input.semanticSummary ? { semanticSummary: input.semanticSummary } : {}
1966
+ };
1500
1967
  }
1501
- function invalid(source, path2) {
1502
- return { source, path: path2, message: `Invalid readseek setting at ${path2}` };
1968
+
1969
+ // src/edit-output.ts
1970
+ var EDIT_OPERATION_NAMES = ["set_line", "replace_lines", "insert_after", "replace"];
1971
+ function hasNewText(value) {
1972
+ return typeof value === "object" && value !== null && typeof value.new_text === "string";
1503
1973
  }
1504
- function warnUnknownKeys(raw, known, prefix, source, warnings) {
1505
- for (const key of Object.keys(raw)) {
1506
- if (known.includes(key))
1507
- continue;
1508
- const path2 = `${prefix}.${key}`;
1509
- warnings.push({ source, path: path2, message: `Unknown readseek setting at ${path2}` });
1974
+ function getVisibleDiffStats(diff) {
1975
+ const stats = parseDiffStats(diff);
1976
+ if (stats.added > 0 || stats.removed > 0)
1977
+ return stats;
1978
+ if (!diff.includes("→"))
1979
+ return stats;
1980
+ if (diff.includes("→ [deleted]"))
1981
+ return { added: 0, removed: 1 };
1982
+ return { added: 1, removed: 1 };
1983
+ }
1984
+ function buildVisibleSummary(displayPath, diff, edits) {
1985
+ let stats = getVisibleDiffStats(diff);
1986
+ const counts = countEditTypes(edits);
1987
+ const editCount = counts.total || 1;
1988
+ if (counts.total > 0 && counts.insert_after === counts.total && stats.removed > 0 && stats.added === stats.removed + counts.insert_after) {
1989
+ stats = { added: counts.insert_after, removed: 0 };
1510
1990
  }
1991
+ const changeWord = editCount === 1 ? "change" : "changes";
1992
+ const changedLineCount = Math.max(stats.added, stats.removed);
1993
+ const lineWord = changedLineCount === 1 ? "line" : "lines";
1994
+ return `Edited ${displayPath} (${editCount} ${changeWord}, +${stats.added} -${stats.removed} ${lineWord})`;
1511
1995
  }
1512
- function readPositive(raw, key, path2, source, warnings) {
1513
- if (!(key in raw))
1514
- return;
1515
- const val = raw[key];
1516
- if (typeof val === "number" && Number.isSafeInteger(val) && val > 0)
1517
- return val;
1518
- warnings.push(invalid(source, path2));
1519
- return;
1996
+ function extractNewTextValues(edits) {
1997
+ const values = [];
1998
+ for (const edit of edits ?? []) {
1999
+ if (!edit || typeof edit !== "object")
2000
+ continue;
2001
+ const operations = edit;
2002
+ for (const operationName of EDIT_OPERATION_NAMES) {
2003
+ const operation = operations[operationName];
2004
+ if (hasNewText(operation))
2005
+ values.push(operation.new_text);
2006
+ }
2007
+ }
2008
+ return values;
1520
2009
  }
1521
- function readEnum(raw, key, values, path2, source, warnings) {
1522
- if (!(key in raw))
2010
+ function formatWhitespaceOnlyWarning(semanticSummary, edits) {
2011
+ if (semanticSummary?.classification !== "whitespace-only")
1523
2012
  return;
1524
- const val = raw[key];
1525
- if (typeof val === "string" && values.includes(val))
1526
- return val;
1527
- warnings.push(invalid(source, path2));
1528
- return;
1529
- }
1530
- function readImageMode(raw, source, warnings) {
1531
- if (!("imageMode" in raw))
2013
+ if (!extractNewTextValues(edits).some((text) => /\S/.test(text)))
1532
2014
  return;
1533
- const val = raw.imageMode;
1534
- if (val === "on" || val === "off" || val === "auto")
1535
- return val;
1536
- warnings.push(invalid(source, "readseek.imageMode"));
1537
- return;
2015
+ return "⚠ Edit classified as whitespace-only — if you intended a behavior change, re-read to verify.";
1538
2016
  }
1539
- function readOverrideTools(raw, source, warnings) {
1540
- if (!("overrideTools" in raw))
1541
- return;
1542
- const value = raw.overrideTools;
1543
- if (!Array.isArray(value)) {
1544
- warnings.push(invalid(source, "readseek.overrideTools"));
2017
+ function formatReplaceHint(edits, noopEdits) {
2018
+ if ((noopEdits ?? []).length > 0)
1545
2019
  return;
1546
- }
1547
- const tools = [];
1548
- value.forEach((tool, index) => {
1549
- if (typeof tool === "string" && Object.hasOwn(READSEEK_TOOL_REPLACEMENTS, tool)) {
1550
- tools.push(tool);
1551
- } else {
1552
- warnings.push(invalid(source, `readseek.overrideTools[${index}]`));
1553
- }
1554
- });
1555
- return tools;
1556
- }
1557
- function readDisplaySettings(raw, source, warnings) {
1558
- if (!("display" in raw))
2020
+ const counts = countEditTypes(edits);
2021
+ if (counts.replace === 0)
1559
2022
  return;
1560
- if (!isRecord(raw.display)) {
1561
- warnings.push(invalid(source, "readseek.display"));
2023
+ if (counts.replace !== counts.total)
1562
2024
  return;
1563
- }
1564
- warnUnknownKeys(raw.display, READSEEK_DISPLAY_KEYS, "readseek.display", source, warnings);
1565
- const display = {};
1566
- for (const tool of READSEEK_DISPLAY_KEYS) {
1567
- const mode = readEnum(raw.display, tool, ["compact", "expanded"], `readseek.display.${tool}`, source, warnings);
1568
- if (mode !== undefined)
1569
- display[tool] = mode;
1570
- }
1571
- return Object.keys(display).length > 0 ? display : undefined;
1572
- }
1573
- function validateSettings(raw, source) {
1574
- const settings = {};
1575
- const warnings = [];
1576
- if (!isRecord(raw)) {
1577
- warnings.push({ source, message: "Invalid readseek settings: expected a JSON object" });
1578
- return { settings, warnings };
1579
- }
1580
- if (!("readseek" in raw)) {
1581
- const misplaced = READSEEK_KEYS.filter((key) => (key in raw));
1582
- if (misplaced.length > 0) {
1583
- warnings.push({
1584
- source,
1585
- path: misplaced[0],
1586
- message: `Readseek setting at top level: move ${misplaced.join(", ")} under "readseek"`
1587
- });
1588
- }
1589
- return { settings, warnings };
1590
- }
1591
- if (!isRecord(raw.readseek)) {
1592
- warnings.push(invalid(source, "readseek"));
1593
- return { settings, warnings };
1594
- }
1595
- const section = raw.readseek;
1596
- warnUnknownKeys(section, READSEEK_KEYS, "readseek", source, warnings);
1597
- const overrideTools = readOverrideTools(section, source, warnings);
1598
- if (overrideTools !== undefined)
1599
- settings.overrideTools = overrideTools;
1600
- const imageMode = readImageMode(section, source, warnings);
1601
- if (imageMode !== undefined)
1602
- settings.imageMode = imageMode;
1603
- const syntaxValidation = readEnum(section, "syntaxValidation", ["warn", "block", "off"], "readseek.syntaxValidation", source, warnings);
1604
- if (syntaxValidation !== undefined)
1605
- settings.syntaxValidation = syntaxValidation;
1606
- const timeoutMs = readPositive(section, "timeoutMs", "readseek.timeoutMs", source, warnings);
1607
- if (timeoutMs !== undefined)
1608
- settings.timeoutMs = timeoutMs;
1609
- if ("grep" in section) {
1610
- if (isRecord(section.grep)) {
1611
- warnUnknownKeys(section.grep, READSEEK_GREP_KEYS, "readseek.grep", source, warnings);
1612
- const grep = {};
1613
- const maxLines = readPositive(section.grep, "maxLines", "readseek.grep.maxLines", source, warnings);
1614
- if (maxLines !== undefined)
1615
- grep.maxLines = maxLines;
1616
- const maxBytes = readPositive(section.grep, "maxBytes", "readseek.grep.maxBytes", source, warnings);
1617
- if (maxBytes !== undefined)
1618
- grep.maxBytes = maxBytes;
1619
- if (Object.keys(grep).length > 0)
1620
- settings.grep = grep;
1621
- } else {
1622
- warnings.push(invalid(source, "readseek.grep"));
1623
- }
1624
- }
1625
- const display = readDisplaySettings(section, source, warnings);
1626
- if (display !== undefined)
1627
- settings.display = display;
1628
- return { settings, warnings };
1629
- }
1630
- var settingsFileCache = new Map;
1631
- function statFileOrNull(path2) {
1632
- try {
1633
- return statSync(path2);
1634
- } catch {
1635
- return null;
1636
- }
1637
- }
1638
- function readSettingsFile(path2) {
1639
- const stats = statFileOrNull(path2);
1640
- if (!stats) {
1641
- settingsFileCache.delete(path2);
1642
- return { settings: {}, warnings: [] };
1643
- }
1644
- const cached = settingsFileCache.get(path2);
1645
- if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size)
1646
- return cached.result;
1647
- let result;
1648
- try {
1649
- const text = readFileSync(path2, "utf8");
1650
- result = validateSettings(JSON.parse(text), path2);
1651
- } catch (error) {
1652
- const message = error instanceof Error ? error.message : String(error);
1653
- result = { settings: {}, warnings: [{ source: path2, message: `Invalid JSON: ${message}` }] };
1654
- }
1655
- settingsFileCache.set(path2, { mtimeMs: stats.mtimeMs, size: stats.size, result });
1656
- return result;
1657
- }
1658
- function mergeSettings(base, override) {
1659
- const settings = { ...base, ...override };
1660
- const grep = { ...base.grep ?? {}, ...override.grep ?? {} };
1661
- if (Object.keys(grep).length > 0)
1662
- settings.grep = grep;
1663
- const display = { ...base.display ?? {}, ...override.display ?? {} };
1664
- if (Object.keys(display).length > 0)
1665
- settings.display = display;
1666
- return settings;
2025
+ return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_digest/readSeek_grep/readSeek_search.]";
1667
2026
  }
1668
- function resolveReadSeekJsonSettings() {
1669
- const globalResult = readSettingsFile(globalSettingsPath());
1670
- const projectResult = readSettingsFile(projectSettingsPath());
2027
+ function buildEditOutput(input) {
2028
+ const summary = `Updated ${input.displayPath}`;
2029
+ const visibleSummary = buildVisibleSummary(input.displayPath, input.diff, input.edits);
2030
+ const semanticWarning = formatWhitespaceOnlyWarning(input.semanticSummary, input.edits);
2031
+ const warningText = input.warnings.length ? `
2032
+
2033
+ Warnings:
2034
+ ${input.warnings.join(`
2035
+ `)}` : "";
2036
+ const replaceHint = formatReplaceHint(input.edits, input.noopEdits);
2037
+ let text = visibleSummary;
2038
+ if (semanticWarning)
2039
+ text += `
2040
+ ${semanticWarning}`;
2041
+ text += warningText;
2042
+ if (replaceHint)
2043
+ text += `
2044
+ ${replaceHint}`;
1671
2045
  return {
1672
- settings: mergeSettings(globalResult.settings, projectResult.settings),
1673
- warnings: [...globalResult.warnings, ...projectResult.warnings]
2046
+ text,
2047
+ patch: input.patch ?? "",
2048
+ readSeekValue: buildReadSeekEditResult({
2049
+ path: input.path,
2050
+ summary,
2051
+ diff: input.diff,
2052
+ ...input.diffData ? { diffData: input.diffData } : {},
2053
+ firstChangedLine: input.firstChangedLine,
2054
+ warnings: input.warnings,
2055
+ noopEdits: input.noopEdits,
2056
+ ...input.semanticSummary ? { semanticSummary: input.semanticSummary } : {}
2057
+ })
1674
2058
  };
1675
2059
  }
1676
- function resolveReadSeekSyntaxValidation() {
1677
- return resolveReadSeekJsonSettings().settings.syntaxValidation;
1678
- }
1679
- function resolveReadSeekTimeoutMs() {
1680
- return resolveReadSeekJsonSettings().settings.timeoutMs;
1681
- }
1682
- function resolveReadSeekToolDisplayMode(tool) {
1683
- return resolveReadSeekJsonSettings().settings.display?.[tool] ?? DEFAULT_DISPLAY_MODES[tool];
1684
- }
1685
2060
 
1686
- // src/readseek-client.ts
1687
- var resolveFromHost = createRequire2(import.meta.url).resolve;
1688
- function normalizeLanguage(language) {
1689
- return language === "java" ? "Java" : language;
1690
- }
1691
- function normalizeKind(kind) {
1692
- if (kind === "constructor")
1693
- return "method" /* Method */;
1694
- if (Object.values(SymbolKind).includes(kind))
1695
- return kind;
1696
- return "unknown" /* Unknown */;
1697
- }
1698
- function parentQualifiedNameFor(qualifiedName) {
1699
- const lastDot = qualifiedName.lastIndexOf(".");
1700
- return lastDot === -1 ? "" : qualifiedName.slice(0, lastDot);
1701
- }
1702
- function symbolsFromReadSeek(symbols) {
1703
- const symbolsByQualifiedName = new Map;
1704
- const entries = [];
1705
- for (const symbol of symbols) {
1706
- const parentQualifiedName = parentQualifiedNameFor(symbol.qualified_name);
1707
- const fileSymbol = {
1708
- name: symbol.name,
1709
- kind: normalizeKind(symbol.kind),
1710
- startLine: symbol.start_line,
1711
- endLine: symbol.end_line
1712
- };
1713
- const bucket = symbolsByQualifiedName.get(symbol.qualified_name);
1714
- if (bucket)
1715
- bucket.push(fileSymbol);
1716
- else
1717
- symbolsByQualifiedName.set(symbol.qualified_name, [fileSymbol]);
1718
- entries.push({ parentQualifiedName, symbol: fileSymbol });
2061
+ // src/edit-classify.ts
2062
+ import * as Diff2 from "diff";
2063
+ function classifyEdit(oldContent, newContent) {
2064
+ if (oldContent === newContent) {
2065
+ return { classification: "no-op" };
1719
2066
  }
1720
- const roots = [];
1721
- for (const entry of entries) {
1722
- const parent = entry.parentQualifiedName ? symbolsByQualifiedName.get(entry.parentQualifiedName)?.[0] : undefined;
1723
- if (!parent) {
1724
- roots.push(entry.symbol);
2067
+ const oldLines = oldContent.split(`
2068
+ `);
2069
+ const newLines = newContent.split(`
2070
+ `);
2071
+ if (oldLines.length === newLines.length) {
2072
+ let hasWhitespaceChange2 = false;
2073
+ let hasSemanticChange2 = false;
2074
+ for (let i = 0;i < oldLines.length; i++) {
2075
+ if (oldLines[i] === newLines[i])
2076
+ continue;
2077
+ if (oldLines[i].trim() === newLines[i].trim()) {
2078
+ hasWhitespaceChange2 = true;
2079
+ } else {
2080
+ hasSemanticChange2 = true;
2081
+ }
2082
+ }
2083
+ if (hasSemanticChange2 && hasWhitespaceChange2)
2084
+ return { classification: "mixed" };
2085
+ if (hasWhitespaceChange2)
2086
+ return { classification: "whitespace-only" };
2087
+ return { classification: "semantic" };
2088
+ }
2089
+ const parts = Diff2.diffLines(oldContent, newContent);
2090
+ let hasWhitespaceChange = false;
2091
+ let hasSemanticChange = false;
2092
+ for (const part of parts) {
2093
+ if (!part.added && !part.removed)
1725
2094
  continue;
2095
+ const lines = part.value.split(`
2096
+ `);
2097
+ if (lines[lines.length - 1] === "")
2098
+ lines.pop();
2099
+ for (const line of lines) {
2100
+ if (line.trim() === "") {
2101
+ hasWhitespaceChange = true;
2102
+ } else {
2103
+ hasSemanticChange = true;
2104
+ }
1726
2105
  }
1727
- parent.children ??= [];
1728
- parent.children.push(entry.symbol);
1729
2106
  }
1730
- return roots;
2107
+ if (hasSemanticChange && hasWhitespaceChange)
2108
+ return { classification: "mixed" };
2109
+ if (hasWhitespaceChange)
2110
+ return { classification: "whitespace-only" };
2111
+ return { classification: "semantic" };
1731
2112
  }
1732
- var READSEEK_REPO_PACKAGE_NAMES = new Set(["@jarkkojs/readseek", "readseek"]);
1733
- var defaultReadSeekDirInit;
1734
- function readSeekBinaryPath() {
1735
- if (process.env.READSEEK_BIN)
1736
- return process.env.READSEEK_BIN;
1737
- return resolveReadSeekBinaryFromPackages(resolveFromHost);
2113
+
2114
+ // src/readseek-client.ts
2115
+ import { existsSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
2116
+ import { createRequire as createRequire2 } from "node:module";
2117
+ import { homedir as homedir3 } from "node:os";
2118
+ import path2 from "node:path";
2119
+
2120
+ // src/readseek/enums.ts
2121
+ var SymbolKind;
2122
+ ((SymbolKind2) => {
2123
+ SymbolKind2["Class"] = "class";
2124
+ SymbolKind2["Function"] = "function";
2125
+ SymbolKind2["Method"] = "method";
2126
+ SymbolKind2["Variable"] = "variable";
2127
+ SymbolKind2["Constant"] = "constant";
2128
+ SymbolKind2["Interface"] = "interface";
2129
+ SymbolKind2["Type"] = "type";
2130
+ SymbolKind2["Enum"] = "enum";
2131
+ SymbolKind2["Struct"] = "struct";
2132
+ SymbolKind2["Import"] = "import";
2133
+ SymbolKind2["Module"] = "module";
2134
+ SymbolKind2["Namespace"] = "namespace";
2135
+ SymbolKind2["Property"] = "property";
2136
+ SymbolKind2["Heading"] = "heading";
2137
+ SymbolKind2["Table"] = "table";
2138
+ SymbolKind2["View"] = "view";
2139
+ SymbolKind2["Procedure"] = "procedure";
2140
+ SymbolKind2["Trigger"] = "trigger";
2141
+ SymbolKind2["Index"] = "index";
2142
+ SymbolKind2["Schema"] = "schema";
2143
+ SymbolKind2["Signal"] = "signal";
2144
+ SymbolKind2["Unknown"] = "unknown";
2145
+ })(SymbolKind ||= {});
2146
+
2147
+ // src/readseek-settings.ts
2148
+ import { readFileSync, statSync } from "node:fs";
2149
+ import { homedir as homedir2 } from "node:os";
2150
+ import { join } from "node:path";
2151
+ var READSEEK_TOOL_REPLACEMENTS = {
2152
+ read: "readSeek_digest",
2153
+ edit: "readSeek_edit",
2154
+ grep: "readSeek_grep",
2155
+ write: "readSeek_write"
2156
+ };
2157
+ var READSEEK_KEYS = ["overrideTools", "imageMode", "syntaxValidation", "timeoutMs", "grep", "display"];
2158
+ var READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
2159
+ var READSEEK_DISPLAY_KEYS = ["grep", "edit", "write"];
2160
+ var DEFAULT_DISPLAY_MODES = {
2161
+ grep: "compact",
2162
+ edit: "expanded",
2163
+ write: "expanded"
2164
+ };
2165
+ function globalSettingsPath() {
2166
+ return join(homedir2(), ".pi/agent/settings.json");
1738
2167
  }
1739
- function readSeekBinaryAvailability() {
1740
- try {
1741
- readSeekBinaryPath();
1742
- return { available: true };
1743
- } catch (err) {
1744
- return { available: false, reason: classifyReadSeekFailure(err).message };
1745
- }
2168
+ function projectSettingsPath() {
2169
+ return join(process.cwd(), ".pi/settings.json");
1746
2170
  }
1747
- function classifyReadSeekFailure(err) {
1748
- const failure = err;
1749
- const message = String(failure?.message ?? err);
1750
- const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1751
- if (missing) {
1752
- return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1753
- }
1754
- return { code: "readseek-execution-error", message };
2171
+ function isRecord(value) {
2172
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1755
2173
  }
1756
- function directoryExists(dirPath) {
1757
- try {
1758
- return statSync2(dirPath).isDirectory();
1759
- } catch {
1760
- return false;
1761
- }
2174
+ function invalid(source, path2) {
2175
+ return { source, path: path2, message: `Invalid readseek setting at ${path2}` };
1762
2176
  }
1763
- var ownRepositoryByDir = new Map;
1764
- function isOwnReadSeekRepository(cwd = process.cwd()) {
1765
- const start = path2.resolve(cwd);
1766
- const cached = ownRepositoryByDir.get(start);
1767
- if (cached !== undefined)
1768
- return cached;
1769
- let dir = start;
1770
- let result = false;
1771
- while (true) {
1772
- const packageJsonPath = path2.join(dir, "package.json");
1773
- if (existsSync(packageJsonPath)) {
1774
- try {
1775
- const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
1776
- if (typeof packageJson.name === "string" && READSEEK_REPO_PACKAGE_NAMES.has(packageJson.name)) {
1777
- result = true;
1778
- break;
1779
- }
1780
- } catch {}
1781
- }
1782
- const parent = path2.dirname(dir);
1783
- if (parent === dir)
1784
- break;
1785
- dir = parent;
2177
+ function warnUnknownKeys(raw, known, prefix, source, warnings) {
2178
+ for (const key of Object.keys(raw)) {
2179
+ if (known.includes(key))
2180
+ continue;
2181
+ const path2 = `${prefix}.${key}`;
2182
+ warnings.push({ source, path: path2, message: `Unknown readseek setting at ${path2}` });
1786
2183
  }
1787
- ownRepositoryByDir.set(start, result);
1788
- return result;
1789
- }
1790
- function defaultReadSeekDir() {
1791
- const home = homedir3();
1792
- return home ? path2.join(home, ".pi", "readseek") : null;
1793
- }
1794
- var DEFAULT_READSEEK_VISION_TIMEOUT_MS = 30 * 60000;
1795
- function readSeekTimeoutMs() {
1796
- return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
1797
- }
1798
- async function spawnReadSeekRaw(args, options = {}) {
1799
- return runReadSeekNodeText(readSeekBinaryPath(), args, {
1800
- signal: options.signal,
1801
- stdin: options.stdin,
1802
- timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
1803
- cancelable: options.cancelable
1804
- });
1805
2184
  }
1806
- async function ensureDefaultReadSeekDir() {
1807
- const dir = defaultReadSeekDir();
1808
- if (!dir)
1809
- return null;
1810
- if (directoryExists(dir))
1811
- return dir;
1812
- defaultReadSeekDirInit ??= spawnReadSeekRaw(["--readseek-dir", dir, "init"]).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
1813
- defaultReadSeekDirInit = undefined;
1814
- });
1815
- return defaultReadSeekDirInit;
2185
+ function readPositive(raw, key, path2, source, warnings) {
2186
+ if (!(key in raw))
2187
+ return;
2188
+ const val = raw[key];
2189
+ if (typeof val === "number" && Number.isSafeInteger(val) && val > 0)
2190
+ return val;
2191
+ warnings.push(invalid(source, path2));
2192
+ return;
1816
2193
  }
1817
- async function readSeekInvocationArgs(args) {
1818
- if (isOwnReadSeekRepository())
1819
- return args;
1820
- const readSeekDir = await ensureDefaultReadSeekDir();
1821
- return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : args;
2194
+ function readEnum(raw, key, values, path2, source, warnings) {
2195
+ if (!(key in raw))
2196
+ return;
2197
+ const val = raw[key];
2198
+ if (typeof val === "string" && values.includes(val))
2199
+ return val;
2200
+ warnings.push(invalid(source, path2));
2201
+ return;
1822
2202
  }
1823
- async function runReadSeekRaw(args, options = {}) {
1824
- return spawnReadSeekRaw(await readSeekInvocationArgs(args), options);
2203
+ function readImageMode(raw, source, warnings) {
2204
+ if (!("imageMode" in raw))
2205
+ return;
2206
+ const val = raw.imageMode;
2207
+ if (val === "on" || val === "off" || val === "auto")
2208
+ return val;
2209
+ warnings.push(invalid(source, "readseek.imageMode"));
2210
+ return;
1825
2211
  }
1826
- async function runReadSeek(args, options = {}) {
1827
- return runReadSeekNodeJson(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
1828
- signal: options.signal,
1829
- stdin: options.stdin,
1830
- timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
1831
- cancelable: options.cancelable
2212
+ function readOverrideTools(raw, source, warnings) {
2213
+ if (!("overrideTools" in raw))
2214
+ return;
2215
+ const value = raw.overrideTools;
2216
+ if (!Array.isArray(value)) {
2217
+ warnings.push(invalid(source, "readseek.overrideTools"));
2218
+ return;
2219
+ }
2220
+ const tools = [];
2221
+ value.forEach((tool, index) => {
2222
+ if (typeof tool === "string" && Object.hasOwn(READSEEK_TOOL_REPLACEMENTS, tool)) {
2223
+ tools.push(tool);
2224
+ } else {
2225
+ warnings.push(invalid(source, `readseek.overrideTools[${index}]`));
2226
+ }
1832
2227
  });
2228
+ return tools;
1833
2229
  }
1834
- async function readSeekView(filePath, options = {}) {
1835
- const args = buildViewArgs({
1836
- path: filePath,
1837
- node: options.node,
1838
- page: options.page,
1839
- kind: options.kind,
1840
- depth: options.depth,
1841
- outline: options.outline,
1842
- visionMode: options.visionMode,
1843
- visionLevel: options.visionLevel
1844
- });
1845
- const runOptions = { signal: options.signal };
1846
- return options.visionMode !== undefined ? runReadSeekVisionRaw(args, runOptions) : runReadSeekRaw(args, runOptions);
2230
+ function readDisplaySettings(raw, source, warnings) {
2231
+ if (!("display" in raw))
2232
+ return;
2233
+ if (!isRecord(raw.display)) {
2234
+ warnings.push(invalid(source, "readseek.display"));
2235
+ return;
2236
+ }
2237
+ warnUnknownKeys(raw.display, READSEEK_DISPLAY_KEYS, "readseek.display", source, warnings);
2238
+ const display = {};
2239
+ for (const tool of READSEEK_DISPLAY_KEYS) {
2240
+ const mode = readEnum(raw.display, tool, ["compact", "expanded"], `readseek.display.${tool}`, source, warnings);
2241
+ if (mode !== undefined)
2242
+ display[tool] = mode;
2243
+ }
2244
+ return Object.keys(display).length > 0 ? display : undefined;
1847
2245
  }
1848
- var visionInvocationTail = Promise.resolve();
1849
- async function runReadSeekVisionRaw(args, options = {}) {
1850
- let release;
1851
- const gate = new Promise((resolve) => {
1852
- release = resolve;
1853
- });
1854
- const predecessor = visionInvocationTail;
1855
- visionInvocationTail = predecessor.then(() => gate);
1856
- const signal = options.signal;
1857
- try {
1858
- if (signal) {
1859
- signal.throwIfAborted();
1860
- await new Promise((resolve, reject) => {
1861
- const onAbort = () => {
1862
- signal.removeEventListener("abort", onAbort);
1863
- reject(signal.reason);
1864
- };
1865
- signal.addEventListener("abort", onAbort, { once: true });
1866
- predecessor.then(() => {
1867
- signal.removeEventListener("abort", onAbort);
1868
- resolve();
1869
- }, (error) => {
1870
- signal.removeEventListener("abort", onAbort);
1871
- reject(error);
1872
- });
2246
+ function validateSettings(raw, source) {
2247
+ const settings = {};
2248
+ const warnings = [];
2249
+ if (!isRecord(raw)) {
2250
+ warnings.push({ source, message: "Invalid readseek settings: expected a JSON object" });
2251
+ return { settings, warnings };
2252
+ }
2253
+ if (!("readseek" in raw)) {
2254
+ const misplaced = READSEEK_KEYS.filter((key) => (key in raw));
2255
+ if (misplaced.length > 0) {
2256
+ warnings.push({
2257
+ source,
2258
+ path: misplaced[0],
2259
+ message: `Readseek setting at top level: move ${misplaced.join(", ")} under "readseek"`
1873
2260
  });
2261
+ }
2262
+ return { settings, warnings };
2263
+ }
2264
+ if (!isRecord(raw.readseek)) {
2265
+ warnings.push(invalid(source, "readseek"));
2266
+ return { settings, warnings };
2267
+ }
2268
+ const section = raw.readseek;
2269
+ warnUnknownKeys(section, READSEEK_KEYS, "readseek", source, warnings);
2270
+ const overrideTools = readOverrideTools(section, source, warnings);
2271
+ if (overrideTools !== undefined)
2272
+ settings.overrideTools = overrideTools;
2273
+ const imageMode = readImageMode(section, source, warnings);
2274
+ if (imageMode !== undefined)
2275
+ settings.imageMode = imageMode;
2276
+ const syntaxValidation = readEnum(section, "syntaxValidation", ["warn", "block", "off"], "readseek.syntaxValidation", source, warnings);
2277
+ if (syntaxValidation !== undefined)
2278
+ settings.syntaxValidation = syntaxValidation;
2279
+ const timeoutMs = readPositive(section, "timeoutMs", "readseek.timeoutMs", source, warnings);
2280
+ if (timeoutMs !== undefined)
2281
+ settings.timeoutMs = timeoutMs;
2282
+ if ("grep" in section) {
2283
+ if (isRecord(section.grep)) {
2284
+ warnUnknownKeys(section.grep, READSEEK_GREP_KEYS, "readseek.grep", source, warnings);
2285
+ const grep = {};
2286
+ const maxLines = readPositive(section.grep, "maxLines", "readseek.grep.maxLines", source, warnings);
2287
+ if (maxLines !== undefined)
2288
+ grep.maxLines = maxLines;
2289
+ const maxBytes = readPositive(section.grep, "maxBytes", "readseek.grep.maxBytes", source, warnings);
2290
+ if (maxBytes !== undefined)
2291
+ grep.maxBytes = maxBytes;
2292
+ if (Object.keys(grep).length > 0)
2293
+ settings.grep = grep;
1874
2294
  } else {
1875
- await predecessor;
2295
+ warnings.push(invalid(source, "readseek.grep"));
1876
2296
  }
1877
- signal?.throwIfAborted();
1878
- return await runReadSeekRaw(args, {
1879
- ...options,
1880
- timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS
1881
- });
1882
- } finally {
1883
- release();
1884
2297
  }
2298
+ const display = readDisplaySettings(section, source, warnings);
2299
+ if (display !== undefined)
2300
+ settings.display = display;
2301
+ return { settings, warnings };
1885
2302
  }
1886
- async function runReadSeekVision(args, options = {}) {
1887
- return JSON.parse(await runReadSeekVisionRaw(args, options));
1888
- }
1889
- function requireNumber(value, field) {
1890
- if (typeof value !== "number" || !Number.isSafeInteger(value))
1891
- throw new Error(`invalid readseek ${field}: expected safe integer`);
1892
- return value;
1893
- }
1894
- function requireString(value, field) {
1895
- if (typeof value !== "string")
1896
- throw new Error(`invalid readseek ${field}`);
1897
- return value;
1898
- }
1899
- function requireBoolean(value, field) {
1900
- if (typeof value !== "boolean")
1901
- throw new Error(`invalid readseek ${field}: expected boolean`);
1902
- return value;
1903
- }
1904
- function parseMapOutput(value) {
1905
- if (!value || typeof value !== "object")
1906
- throw new Error("invalid readseek map output");
1907
- const output = value;
1908
- const symbols = output.symbols;
1909
- if (!Array.isArray(symbols))
1910
- throw new Error("invalid readseek symbols");
1911
- return {
1912
- file: requireString(output.file, "file"),
1913
- language: requireString(output.language, "language"),
1914
- line_count: requireNumber(output.line_count, "line_count"),
1915
- file_hash: requireString(output.file_hash, "file_hash"),
1916
- symbols: symbols.map((symbol) => {
1917
- if (!symbol || typeof symbol !== "object")
1918
- throw new Error("invalid readseek symbol");
1919
- const item = symbol;
1920
- return {
1921
- kind: requireString(item.kind, "symbol.kind"),
1922
- name: requireString(item.name, "symbol.name"),
1923
- qualified_name: requireString(item.qualified_name, "symbol.qualified_name"),
1924
- start_line: requireNumber(item.start_line, "symbol.start_line"),
1925
- end_line: requireNumber(item.end_line, "symbol.end_line"),
1926
- start_hash: requireString(item.start_hash, "symbol.start_hash"),
1927
- end_hash: requireString(item.end_hash, "symbol.end_hash")
1928
- };
1929
- })
1930
- };
1931
- }
1932
- function parseHashline(value, field) {
1933
- if (!value || typeof value !== "object")
1934
- throw new Error(`invalid readseek ${field}`);
1935
- const item = value;
1936
- return {
1937
- line: requireNumber(item.line, `${field}.line`),
1938
- hash: requireString(item.hash, `${field}.hash`),
1939
- text: requireString(item.text, `${field}.text`)
1940
- };
1941
- }
1942
- function parseSearchHashlines(value, field) {
1943
- if (!Array.isArray(value))
1944
- throw new Error(`invalid readseek ${field}`);
1945
- return value.map((line) => parseHashline(line, field));
1946
- }
1947
- function parseSearchOutput(value) {
1948
- if (!value || typeof value !== "object")
1949
- throw new Error("invalid readseek search output");
1950
- const output = value;
1951
- if (!Array.isArray(output.results))
1952
- throw new Error("invalid readseek search results");
1953
- return {
1954
- results: output.results.map((result) => {
1955
- if (!result || typeof result !== "object")
1956
- throw new Error("invalid readseek search result");
1957
- const file = result;
1958
- if (!Array.isArray(file.matches))
1959
- throw new Error("invalid readseek search matches");
1960
- return {
1961
- file: requireString(file.file, "search.file"),
1962
- language: requireString(file.language, "search.language"),
1963
- file_hash: requireString(file.file_hash, "search.file_hash"),
1964
- matches: file.matches.map((match) => {
1965
- if (!match || typeof match !== "object")
1966
- throw new Error("invalid readseek search match");
1967
- const item = match;
1968
- if (!Array.isArray(item.captures))
1969
- throw new Error("invalid readseek search captures");
1970
- return {
1971
- pattern_index: item.pattern_index === undefined ? 0 : requireNumber(item.pattern_index, "search.match.pattern_index"),
1972
- start_line: requireNumber(item.start_line, "search.match.start_line"),
1973
- end_line: requireNumber(item.end_line, "search.match.end_line"),
1974
- start_hash: requireString(item.start_hash, "search.match.start_hash"),
1975
- end_hash: requireString(item.end_hash, "search.match.end_hash"),
1976
- hashlines: parseSearchHashlines(item.hashlines, "search.match.hashlines"),
1977
- captures: item.captures.map((capture) => {
1978
- if (!capture || typeof capture !== "object")
1979
- throw new Error("invalid readseek search capture");
1980
- const captureItem = capture;
1981
- return {
1982
- name: requireString(captureItem.name, "search.capture.name"),
1983
- start_line: requireNumber(captureItem.start_line, "search.capture.start_line"),
1984
- end_line: requireNumber(captureItem.end_line, "search.capture.end_line"),
1985
- start_hash: requireString(captureItem.start_hash, "search.capture.start_hash"),
1986
- end_hash: requireString(captureItem.end_hash, "search.capture.end_hash"),
1987
- hashlines: parseSearchHashlines(captureItem.hashlines, "search.capture.hashlines")
1988
- };
1989
- })
1990
- };
1991
- })
1992
- };
1993
- })
1994
- };
2303
+ var settingsFileCache = new Map;
2304
+ function statFileOrNull(path2) {
2305
+ try {
2306
+ return statSync(path2);
2307
+ } catch {
2308
+ return null;
2309
+ }
1995
2310
  }
1996
- function optionalString(value, field) {
1997
- if (value === undefined || value === null)
1998
- return;
1999
- return requireString(value, field);
2311
+ function readSettingsFile(path2) {
2312
+ const stats = statFileOrNull(path2);
2313
+ if (!stats) {
2314
+ settingsFileCache.delete(path2);
2315
+ return { settings: {}, warnings: [] };
2316
+ }
2317
+ const cached = settingsFileCache.get(path2);
2318
+ if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size)
2319
+ return cached.result;
2320
+ let result;
2321
+ try {
2322
+ const text = readFileSync(path2, "utf8");
2323
+ result = validateSettings(JSON.parse(text), path2);
2324
+ } catch (error) {
2325
+ const message = error instanceof Error ? error.message : String(error);
2326
+ result = { settings: {}, warnings: [{ source: path2, message: `Invalid JSON: ${message}` }] };
2327
+ }
2328
+ settingsFileCache.set(path2, { mtimeMs: stats.mtimeMs, size: stats.size, result });
2329
+ return result;
2000
2330
  }
2001
- function parseRefsOutput(value) {
2002
- if (!value || typeof value !== "object")
2003
- throw new Error("invalid readseek refs output");
2004
- const output = value;
2005
- if (!Array.isArray(output.references))
2006
- throw new Error("invalid readseek references");
2331
+ function mergeSettings(base, override) {
2332
+ const settings = { ...base, ...override };
2333
+ const grep = { ...base.grep ?? {}, ...override.grep ?? {} };
2334
+ if (Object.keys(grep).length > 0)
2335
+ settings.grep = grep;
2336
+ const display = { ...base.display ?? {}, ...override.display ?? {} };
2337
+ if (Object.keys(display).length > 0)
2338
+ settings.display = display;
2339
+ return settings;
2340
+ }
2341
+ function resolveReadSeekJsonSettings() {
2342
+ const globalResult = readSettingsFile(globalSettingsPath());
2343
+ const projectResult = readSettingsFile(projectSettingsPath());
2007
2344
  return {
2008
- references: output.references.map((reference) => {
2009
- if (!reference || typeof reference !== "object")
2010
- throw new Error("invalid readseek reference");
2011
- const item = reference;
2012
- const symbol = item.symbol;
2013
- const enclosing = symbol && typeof symbol === "object" ? optionalString(symbol.qualified_name, "reference.symbol.qualified_name") : undefined;
2014
- return {
2015
- file: requireString(item.file, "reference.file"),
2016
- line: requireNumber(item.line, "reference.line"),
2017
- column: requireNumber(item.column, "reference.column"),
2018
- line_hash: requireString(item.line_hash, "reference.line_hash"),
2019
- text: requireString(item.text, "reference.text"),
2020
- enclosingSymbol: enclosing
2021
- };
2022
- })
2345
+ settings: mergeSettings(globalResult.settings, projectResult.settings),
2346
+ warnings: [...globalResult.warnings, ...projectResult.warnings]
2023
2347
  };
2024
2348
  }
2025
- function parseDiagnosticKind(value) {
2026
- if (value === "error" || value === "missing")
2027
- return value;
2028
- throw new Error("invalid readseek diagnostic.kind");
2349
+ function resolveReadSeekImageMode() {
2350
+ return resolveReadSeekJsonSettings().settings.imageMode ?? "auto";
2029
2351
  }
2030
- function parseCheckOutput(value) {
2031
- if (!value || typeof value !== "object")
2032
- throw new Error("invalid readseek check output");
2033
- const output = value;
2034
- if (!Array.isArray(output.diagnostics))
2035
- throw new Error("invalid readseek diagnostics");
2036
- return {
2037
- errorCount: requireNumber(output.error_count, "error_count"),
2038
- missingCount: requireNumber(output.missing_count, "missing_count"),
2039
- diagnostics: output.diagnostics.map((diagnostic) => {
2040
- if (!diagnostic || typeof diagnostic !== "object")
2041
- throw new Error("invalid readseek diagnostic");
2042
- const item = diagnostic;
2043
- return {
2044
- kind: parseDiagnosticKind(item.kind),
2045
- start_line: requireNumber(item.start_line, "diagnostic.start_line"),
2046
- end_line: requireNumber(item.end_line, "diagnostic.end_line")
2047
- };
2048
- })
2049
- };
2352
+ function resolveReadSeekSyntaxValidation() {
2353
+ return resolveReadSeekJsonSettings().settings.syntaxValidation;
2050
2354
  }
2051
- function parseOcrText(value) {
2052
- return optionalString(value, "ocr");
2355
+ function resolveReadSeekTimeoutMs() {
2356
+ return resolveReadSeekJsonSettings().settings.timeoutMs;
2053
2357
  }
2054
- function parseDetectedObjects(value) {
2055
- if (value === undefined || value === null)
2056
- return;
2057
- if (!Array.isArray(value))
2058
- throw new Error("invalid readseek detect objects");
2059
- return value.map((object) => {
2060
- if (!object || typeof object !== "object")
2061
- throw new Error("invalid readseek detect object");
2062
- const item = object;
2063
- const bbox = item.bbox;
2064
- if (!Array.isArray(bbox) || bbox.length !== 4)
2065
- throw new Error("invalid readseek detect object.bbox");
2066
- return {
2067
- label: requireString(item.label, "object.label"),
2068
- bbox: bbox.map((n, i) => requireNumber(n, `object.bbox[${i}]`))
2069
- };
2070
- });
2358
+ function resolveReadSeekToolDisplayMode(tool) {
2359
+ return resolveReadSeekJsonSettings().settings.display?.[tool] ?? DEFAULT_DISPLAY_MODES[tool];
2071
2360
  }
2072
- function parseImageEncoding(value) {
2073
- if (value === "base64")
2074
- return value;
2075
- throw new Error("invalid image encoding");
2361
+
2362
+ // src/readseek-client.ts
2363
+ var resolveFromHost = createRequire2(import.meta.url).resolve;
2364
+ function readSeekBinaryPath() {
2365
+ return resolveReadSeekBinaryFromPackages(resolveFromHost);
2076
2366
  }
2077
- function parseDetectOutput(value) {
2078
- if (!value || typeof value !== "object")
2079
- throw new Error("invalid readseek detect output");
2080
- const output = value;
2081
- const type = requireString(output.type, "type");
2082
- const file = requireString(output.file, "file");
2083
- const mime = optionalString(output.mime, "mime");
2084
- if (type === "application/pdf") {
2085
- if (output.format !== "pdf")
2086
- throw new Error("invalid readseek PDF format");
2087
- return {
2088
- kind: "pdf",
2089
- type,
2090
- file,
2091
- mime,
2092
- format: output.format,
2093
- pages: requireNumber(output.pages, "pages")
2094
- };
2367
+ function readSeekBinaryAvailability() {
2368
+ try {
2369
+ readSeekBinaryPath();
2370
+ return { available: true };
2371
+ } catch (err) {
2372
+ return { available: false, reason: classifyReadSeekFailure(err).message };
2095
2373
  }
2096
- if (output.width !== undefined || output.height !== undefined) {
2097
- return {
2098
- kind: "image",
2099
- type,
2100
- file,
2101
- mime,
2102
- format: requireString(output.format, "format"),
2103
- width: requireNumber(output.width, "width"),
2104
- height: requireNumber(output.height, "height"),
2105
- animated: requireBoolean(output.animated, "animated"),
2106
- encoding: output.encoding === undefined ? undefined : parseImageEncoding(output.encoding),
2107
- data: output.data === undefined ? undefined : requireString(output.data, "data"),
2108
- ocr: parseOcrText(output.ocr),
2109
- caption: optionalString(output.caption, "caption"),
2110
- objects: parseDetectedObjects(output.objects)
2111
- };
2374
+ }
2375
+ var READSEEK_REPO_PACKAGE_NAMES = new Set(["@jarkkojs/readseek", "readseek"]);
2376
+ var defaultReadSeekDirInit;
2377
+ function directoryExists(dirPath) {
2378
+ try {
2379
+ return statSync2(dirPath).isDirectory();
2380
+ } catch {
2381
+ return false;
2112
2382
  }
2113
- if (output.language !== undefined) {
2114
- return {
2115
- kind: "source",
2116
- type,
2117
- file,
2118
- language: requireString(output.language, "language"),
2119
- engine: optionalString(output.engine, "engine"),
2120
- supported: requireBoolean(output.supported, "supported"),
2121
- mime,
2122
- syntax: optionalString(output.syntax, "syntax")
2123
- };
2383
+ }
2384
+ var ownRepositoryByDir = new Map;
2385
+ function isOwnReadSeekRepository(cwd = process.cwd()) {
2386
+ const start = path2.resolve(cwd);
2387
+ const cached = ownRepositoryByDir.get(start);
2388
+ if (cached !== undefined)
2389
+ return cached;
2390
+ let dir = start;
2391
+ let result = false;
2392
+ while (true) {
2393
+ const packageJsonPath = path2.join(dir, "package.json");
2394
+ if (existsSync(packageJsonPath)) {
2395
+ try {
2396
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
2397
+ if (typeof packageJson.name === "string" && READSEEK_REPO_PACKAGE_NAMES.has(packageJson.name)) {
2398
+ result = true;
2399
+ break;
2400
+ }
2401
+ } catch {}
2402
+ }
2403
+ const parent = path2.dirname(dir);
2404
+ if (parent === dir)
2405
+ break;
2406
+ dir = parent;
2124
2407
  }
2125
- return { kind: "text", type, file, mime };
2408
+ ownRepositoryByDir.set(start, result);
2409
+ return result;
2126
2410
  }
2127
- function parseDocumentCache(value) {
2128
- if (!value || typeof value !== "object")
2129
- throw new Error("invalid readseek document cache");
2130
- const cache = value;
2131
- const state = requireString(cache.state, "cache.state");
2132
- if (state !== "built" && state !== "reused")
2133
- throw new Error("invalid readseek cache.state");
2134
- return {
2135
- content_hash: requireString(cache.content_hash, "cache.content_hash"),
2136
- state
2137
- };
2411
+ function defaultReadSeekDir() {
2412
+ const home = homedir3();
2413
+ return home ? path2.join(home, ".pi", "readseek") : null;
2138
2414
  }
2139
- function parseIdentifyOutput(value) {
2140
- if (!value || typeof value !== "object")
2141
- throw new Error("invalid readseek identify output");
2142
- const output = value;
2143
- const identifier = output.identifier;
2144
- const symbol = output.symbol;
2145
- return {
2146
- file: requireString(output.file, "file"),
2147
- language: requireString(output.language, "language"),
2148
- engine: optionalString(output.engine, "engine"),
2149
- line_count: requireNumber(output.line_count, "line_count"),
2150
- file_hash: requireString(output.file_hash, "file_hash"),
2151
- line: requireNumber(output.line, "line"),
2152
- column: requireNumber(output.column, "column"),
2153
- line_hash: requireString(output.line_hash, "line_hash"),
2154
- identifier: identifier && typeof identifier === "object" ? {
2155
- text: requireString(identifier.text, "identifier.text"),
2156
- start_column: requireNumber(identifier.start_column, "identifier.start_column"),
2157
- end_column: requireNumber(identifier.end_column, "identifier.end_column"),
2158
- start_byte: requireNumber(identifier.start_byte, "identifier.start_byte"),
2159
- end_byte: requireNumber(identifier.end_byte, "identifier.end_byte")
2160
- } : undefined,
2161
- symbol: symbol && typeof symbol === "object" ? {
2162
- name: requireString(symbol.name, "symbol.name"),
2163
- kind: requireString(symbol.kind, "symbol.kind"),
2164
- qualified_name: requireString(symbol.qualified_name, "symbol.qualified_name"),
2165
- start_line: requireNumber(symbol.start_line, "symbol.start_line"),
2166
- end_line: requireNumber(symbol.end_line, "symbol.end_line")
2167
- } : undefined
2168
- };
2415
+ var DEFAULT_READSEEK_VISION_TIMEOUT_MS = 30 * 60000;
2416
+ function readSeekTimeoutMs() {
2417
+ return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
2169
2418
  }
2170
- function parseDigestEnvelope(value) {
2171
- if (!value || typeof value !== "object")
2172
- throw new Error("invalid readseek digest output");
2173
- const output = value;
2174
- if (output.metadata === undefined)
2175
- throw new Error("invalid readseek digest metadata");
2176
- const result = {
2177
- metadata: parseDetectOutput(output.metadata)
2178
- };
2179
- if (output.cache !== undefined)
2180
- result.cache = parseDocumentCache(output.cache);
2181
- if (output.content !== undefined)
2182
- result.content = output.content;
2183
- if (output.map !== undefined)
2184
- result.map = parseMapOutput(output.map);
2185
- if (output.diagnostics !== undefined)
2186
- result.diagnostics = parseCheckOutput(output.diagnostics);
2187
- if (output.identity !== undefined)
2188
- result.identity = parseIdentifyOutput(output.identity);
2189
- return result;
2419
+ async function spawnReadSeekRaw(args, options = {}) {
2420
+ return runReadSeekNodeText(readSeekBinaryPath(), args, {
2421
+ signal: options.signal,
2422
+ stdin: options.stdin,
2423
+ timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2424
+ cancelable: options.cancelable
2425
+ });
2426
+ }
2427
+ async function ensureDefaultReadSeekDir() {
2428
+ const dir = defaultReadSeekDir();
2429
+ if (!dir)
2430
+ return null;
2431
+ if (directoryExists(dir))
2432
+ return dir;
2433
+ defaultReadSeekDirInit ??= spawnReadSeekRaw(["--readseek-dir", dir, "init"]).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2434
+ defaultReadSeekDirInit = undefined;
2435
+ });
2436
+ return defaultReadSeekDirInit;
2437
+ }
2438
+ async function readSeekInvocationArgs(args) {
2439
+ if (isOwnReadSeekRepository())
2440
+ return [...args];
2441
+ const readSeekDir = await ensureDefaultReadSeekDir();
2442
+ return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : [...args];
2190
2443
  }
2191
- function digestArgs(target, options) {
2192
- return buildDigestArgs({
2193
- path: target,
2194
- select: options.select ?? "content",
2195
- at: options.at,
2196
- end: options.end,
2197
- limit: options.limit,
2198
- language: options.language,
2199
- visionMode: options.visionMode,
2200
- visionLevel: options.visionLevel,
2201
- stdinName: options.stdin !== undefined && target === "-" ? options.stdinName ?? target : undefined
2202
- });
2444
+ async function runReadSeekRaw(args, options = {}) {
2445
+ return spawnReadSeekRaw(await readSeekInvocationArgs(args), options);
2203
2446
  }
2204
- async function runDigestRaw(target, options = {}) {
2205
- const args = digestArgs(target, options);
2206
- const runOptions = {
2447
+ async function runReadSeek(args, options = {}) {
2448
+ return runReadSeekNodeJson(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2207
2449
  signal: options.signal,
2208
2450
  stdin: options.stdin,
2209
- timeoutMs: options.timeoutMs
2210
- };
2211
- return options.vision ? await runReadSeekVision(args, runOptions) : await runReadSeek(args, runOptions);
2451
+ timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2452
+ cancelable: options.cancelable
2453
+ });
2454
+ }
2455
+ var visionInvocationTail = Promise.resolve();
2456
+ async function runReadSeekVisionRaw(args, options = {}) {
2457
+ let release;
2458
+ const gate = new Promise((resolve) => {
2459
+ release = resolve;
2460
+ });
2461
+ const predecessor = visionInvocationTail;
2462
+ visionInvocationTail = predecessor.then(() => gate);
2463
+ const signal = options.signal;
2464
+ try {
2465
+ if (signal) {
2466
+ signal.throwIfAborted();
2467
+ await new Promise((resolve, reject) => {
2468
+ const onAbort = () => {
2469
+ signal.removeEventListener("abort", onAbort);
2470
+ reject(signal.reason);
2471
+ };
2472
+ signal.addEventListener("abort", onAbort, { once: true });
2473
+ predecessor.then(() => {
2474
+ signal.removeEventListener("abort", onAbort);
2475
+ resolve();
2476
+ }, (error) => {
2477
+ signal.removeEventListener("abort", onAbort);
2478
+ reject(error);
2479
+ });
2480
+ });
2481
+ } else {
2482
+ await predecessor;
2483
+ }
2484
+ signal?.throwIfAborted();
2485
+ return await runReadSeekRaw(args, {
2486
+ ...options,
2487
+ timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS
2488
+ });
2489
+ } finally {
2490
+ release();
2491
+ }
2492
+ }
2493
+ async function runReadSeekVision(args, options = {}) {
2494
+ return JSON.parse(await runReadSeekVisionRaw(args, options));
2495
+ }
2496
+ var runner = {
2497
+ text: (args, options = {}) => options.vision ? runReadSeekVisionRaw(args, options) : runReadSeekRaw(args, options),
2498
+ json: (args, options = {}) => options.vision ? runReadSeekVision(args, options) : runReadSeek(args, options)
2499
+ };
2500
+ var client = createReadSeekClient(runner);
2501
+ var readSeekView = client.view;
2502
+ var readSeekDigestNative = client.digestRaw;
2503
+ var readSeekDigest = client.digest;
2504
+ var readSeekDigestContent = client.digestContent;
2505
+ var readSeekImage = client.image;
2506
+ var readSeekPreparedImage = client.preparedImage;
2507
+ var readSeekSearch = client.search;
2508
+ var readSeekRefs = client.refs;
2509
+ var readSeekDef = client.def;
2510
+ var readSeekCheck = client.check;
2511
+ var readSeekIdentify = client.identify;
2512
+ var readSeekDetect = client.detect;
2513
+ var readSeekEdit = client.edit;
2514
+ var readSeekRename = client.rename;
2515
+ function normalizeLanguage(language) {
2516
+ return language === "java" ? "Java" : language;
2517
+ }
2518
+ function normalizeKind(kind) {
2519
+ if (kind === "constructor")
2520
+ return "method" /* Method */;
2521
+ if (Object.values(SymbolKind).includes(kind))
2522
+ return kind;
2523
+ return "unknown" /* Unknown */;
2212
2524
  }
2213
- async function readSeekDigestNative(target, options = {}) {
2214
- return runDigestRaw(target, options);
2525
+ function parentQualifiedNameFor(qualifiedName) {
2526
+ const lastDot = qualifiedName.lastIndexOf(".");
2527
+ return lastDot === -1 ? "" : qualifiedName.slice(0, lastDot);
2215
2528
  }
2216
- async function readSeekDigest(target, options = {}) {
2217
- return parseDigestEnvelope(await runDigestRaw(target, options));
2529
+ function symbolsFromReadSeek(symbols) {
2530
+ const symbolsByQualifiedName = new Map;
2531
+ const entries = [];
2532
+ for (const symbol of symbols) {
2533
+ const parentQualifiedName = parentQualifiedNameFor(symbol.qualified_name);
2534
+ const fileSymbol = {
2535
+ name: symbol.name,
2536
+ kind: normalizeKind(symbol.kind),
2537
+ startLine: symbol.start_line,
2538
+ endLine: symbol.end_line
2539
+ };
2540
+ const bucket = symbolsByQualifiedName.get(symbol.qualified_name);
2541
+ if (bucket)
2542
+ bucket.push(fileSymbol);
2543
+ else
2544
+ symbolsByQualifiedName.set(symbol.qualified_name, [fileSymbol]);
2545
+ entries.push({ parentQualifiedName, symbol: fileSymbol });
2546
+ }
2547
+ const roots = [];
2548
+ for (const entry of entries) {
2549
+ const parent = entry.parentQualifiedName ? symbolsByQualifiedName.get(entry.parentQualifiedName)?.[0] : undefined;
2550
+ if (!parent) {
2551
+ roots.push(entry.symbol);
2552
+ continue;
2553
+ }
2554
+ parent.children ??= [];
2555
+ parent.children.push(entry.symbol);
2556
+ }
2557
+ return roots;
2218
2558
  }
2219
2559
  function fileMapFromReadSeekOutput(output, filePath, totalBytes) {
2220
2560
  if (output.language === "unknown" && output.symbols.length === 0)
@@ -2229,196 +2569,11 @@ function fileMapFromReadSeekOutput(output, filePath, totalBytes) {
2229
2569
  };
2230
2570
  }
2231
2571
  async function readSeekMap(filePath, totalBytes, options = {}) {
2232
- const envelope = await readSeekDigest(filePath, { select: "map", signal: options.signal });
2572
+ const envelope = await client.digest(filePath, { select: "map", signal: options.signal });
2233
2573
  if (!envelope.map)
2234
2574
  return null;
2235
2575
  return fileMapFromReadSeekOutput(envelope.map, filePath, totalBytes);
2236
2576
  }
2237
- async function readSeekSearch(target, pattern, options = {}) {
2238
- const args = buildSearchArgs({
2239
- path: target,
2240
- pattern,
2241
- language: options.language,
2242
- cached: options.cached,
2243
- others: options.others,
2244
- ignored: options.ignored
2245
- });
2246
- return parseSearchOutput(await runReadSeek(args, { signal: options.signal })).results;
2247
- }
2248
- async function readSeekRefs(target, options = {}) {
2249
- const args = buildRefsArgs({
2250
- path: target,
2251
- name: options.name,
2252
- scope: options.scope,
2253
- line: options.line,
2254
- column: options.column,
2255
- language: options.language,
2256
- cached: options.cached,
2257
- others: options.others,
2258
- ignored: options.ignored
2259
- });
2260
- return parseRefsOutput(await runReadSeek(args, { signal: options.signal })).references;
2261
- }
2262
- async function readSeekCheck(filePath, content, options = {}) {
2263
- const envelope = await readSeekDigest("-", {
2264
- select: "diagnostics",
2265
- stdin: content,
2266
- stdinName: filePath,
2267
- signal: options.signal
2268
- });
2269
- if (!envelope.diagnostics)
2270
- throw new Error("invalid readseek diagnostics: missing diagnostics facet");
2271
- return envelope.diagnostics;
2272
- }
2273
- function parseEditOutput(value) {
2274
- if (!value || typeof value !== "object")
2275
- throw new Error("invalid readseek edit output");
2276
- const output = value;
2277
- return {
2278
- file: requireString(output.file, "file"),
2279
- before_hash: requireString(output.before_hash, "before_hash"),
2280
- file_hash: requireString(output.file_hash, "file_hash"),
2281
- plan_hash: requireString(output.plan_hash, "plan_hash"),
2282
- applied: requireBoolean(output.applied, "applied"),
2283
- changed: requireBoolean(output.changed, "changed"),
2284
- edit_count: requireNumber(output.edit_count, "edit_count"),
2285
- before_content: optionalString(output.before_content, "before_content"),
2286
- content: optionalString(output.content, "content")
2287
- };
2288
- }
2289
- async function readSeekEdit(filePath, edits, options = {}) {
2290
- const args = buildEditArgs({
2291
- path: filePath,
2292
- apply: options.apply,
2293
- planHash: options.planHash,
2294
- language: options.language
2295
- });
2296
- return parseEditOutput(await runReadSeek(args, {
2297
- signal: options.signal,
2298
- stdin: JSON.stringify({ edits }),
2299
- timeoutMs: options.timeoutMs,
2300
- cancelable: options.cancelable
2301
- }));
2302
- }
2303
- function parseRenameConflicts(value, field) {
2304
- if (value === undefined)
2305
- return [];
2306
- if (!Array.isArray(value))
2307
- throw new Error(`invalid readseek ${field}`);
2308
- return value.map((item) => {
2309
- if (!item || typeof item !== "object")
2310
- throw new Error(`invalid readseek ${field} entry`);
2311
- const c = item;
2312
- return {
2313
- line: requireNumber(c.line, `${field}.line`),
2314
- column: requireNumber(c.column, `${field}.column`),
2315
- reason: requireString(c.reason, `${field}.reason`)
2316
- };
2317
- });
2318
- }
2319
- function parseRenameEdits(value, field) {
2320
- if (value === undefined)
2321
- return [];
2322
- if (!Array.isArray(value))
2323
- throw new Error(`invalid readseek ${field}`);
2324
- return value.map((item) => {
2325
- if (!item || typeof item !== "object")
2326
- throw new Error(`invalid readseek ${field} entry`);
2327
- const e = item;
2328
- return {
2329
- line: requireNumber(e.line, `${field}.line`),
2330
- start_column: requireNumber(e.start_column, `${field}.start_column`),
2331
- end_column: requireNumber(e.end_column, `${field}.end_column`),
2332
- start_byte: requireNumber(e.start_byte, `${field}.start_byte`),
2333
- end_byte: requireNumber(e.end_byte, `${field}.end_byte`),
2334
- occurrence: requireString(e.occurrence, `${field}.occurrence`),
2335
- line_hash: requireString(e.line_hash, `${field}.line_hash`),
2336
- text: requireString(e.text, `${field}.text`)
2337
- };
2338
- });
2339
- }
2340
- function parseRenameOutput(value) {
2341
- if (!value || typeof value !== "object")
2342
- throw new Error("invalid readseek rename output");
2343
- const output = value;
2344
- const others = output.others;
2345
- if (others !== undefined && !Array.isArray(others))
2346
- throw new Error("invalid readseek others");
2347
- return {
2348
- file: requireString(output.file, "file"),
2349
- language: requireString(output.language, "language"),
2350
- engine: optionalString(output.engine, "engine"),
2351
- file_hash: requireString(output.file_hash, "file_hash"),
2352
- old_name: requireString(output.old_name, "old_name"),
2353
- new_name: requireString(output.new_name, "new_name"),
2354
- applied: requireBoolean(output.applied, "applied"),
2355
- conflicts: parseRenameConflicts(output.conflicts, "conflicts"),
2356
- edits: parseRenameEdits(output.edits, "edits"),
2357
- others: others?.map((entry) => {
2358
- if (!entry || typeof entry !== "object")
2359
- throw new Error("invalid readseek other");
2360
- const o = entry;
2361
- return {
2362
- file: requireString(o.file, "other.file"),
2363
- language: requireString(o.language, "other.language"),
2364
- engine: optionalString(o.engine, "other.engine"),
2365
- file_hash: requireString(o.file_hash, "other.file_hash"),
2366
- conflicts: parseRenameConflicts(o.conflicts, "other.conflicts"),
2367
- edits: parseRenameEdits(o.edits, "other.edits")
2368
- };
2369
- }) ?? []
2370
- };
2371
- }
2372
- async function readSeekRename(filePath, options) {
2373
- const args = buildRenameArgs({
2374
- path: filePath,
2375
- to: options.to,
2376
- line: options.line,
2377
- column: options.column,
2378
- workspace: options.workspace,
2379
- apply: options.apply,
2380
- language: options.language,
2381
- cached: options.cached,
2382
- others: options.others,
2383
- ignored: options.ignored
2384
- });
2385
- return parseRenameOutput(await runReadSeek(args, { signal: options.signal }));
2386
- }
2387
- function parseDefCompact(value) {
2388
- if (!value || typeof value !== "object")
2389
- throw new Error("invalid readseek def output");
2390
- const output = value;
2391
- const locations = output.locations;
2392
- if (!Array.isArray(locations))
2393
- throw new Error("invalid readseek def locations");
2394
- return locations.map((loc) => {
2395
- if (!loc || typeof loc !== "object")
2396
- throw new Error("invalid readseek def location");
2397
- const item = loc;
2398
- return {
2399
- file: requireString(item.file, "location.file"),
2400
- line: requireNumber(item.line, "location.line"),
2401
- column: requireNumber(item.column, "location.column"),
2402
- line_hash: requireString(item.line_hash, "location.line_hash"),
2403
- text: requireString(item.text, "location.text"),
2404
- kind: optionalString(item.kind, "location.kind"),
2405
- name: optionalString(item.name, "location.name"),
2406
- qualified_name: optionalString(item.qualified_name, "location.qualified_name")
2407
- };
2408
- });
2409
- }
2410
- async function readSeekDef(target, options) {
2411
- const args = buildDefArgs({
2412
- path: target,
2413
- name: options.name,
2414
- format: "plain",
2415
- language: options.language,
2416
- cached: options.cached,
2417
- others: options.others,
2418
- ignored: options.ignored
2419
- });
2420
- return parseDefCompact(await runReadSeek(args, { signal: options.signal }));
2421
- }
2422
2577
 
2423
2578
  // src/edit-syntax-validate.ts
2424
2579
  function dedupeSortLines(diagnostics) {
@@ -5308,6 +5463,36 @@ function summarizeEnvelope(envelope, facets) {
5308
5463
  parts.unshift("metadata");
5309
5464
  return `digested ${parts.join(",") || "metadata"}`;
5310
5465
  }
5466
+ function parseImageEnvelope(envelope) {
5467
+ if (!envelope || typeof envelope !== "object")
5468
+ return;
5469
+ const record = envelope;
5470
+ if (!record.metadata || typeof record.metadata !== "object")
5471
+ return;
5472
+ const metadata = record.metadata;
5473
+ if (typeof metadata.type !== "string" || !metadata.type.startsWith("image/"))
5474
+ return;
5475
+ if (!record.content || typeof record.content !== "object")
5476
+ return { metadata };
5477
+ const content = record.content;
5478
+ const mime = content.mime ?? content.type ?? metadata.mime ?? metadata.type;
5479
+ const prepared = content.encoding === "base64" && typeof content.data === "string" && typeof mime === "string" ? { data: content.data, mimeType: mime } : undefined;
5480
+ return { metadata, prepared };
5481
+ }
5482
+ function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5483
+ return {
5484
+ content,
5485
+ details: {
5486
+ readSeekValue: {
5487
+ tool: "digest",
5488
+ ok: true,
5489
+ path: filePath,
5490
+ select: facets,
5491
+ envelope
5492
+ }
5493
+ }
5494
+ };
5495
+ }
5311
5496
  async function executeDigest(opts) {
5312
5497
  const { params, signal, cwd, onFileAnchored } = opts;
5313
5498
  const p = params;
@@ -5319,6 +5504,8 @@ async function executeDigest(opts) {
5319
5504
  path: p.path
5320
5505
  });
5321
5506
  }
5507
+ const facets = selectedFacets(select);
5508
+ const appliesImagePolicy = opts.imageMode !== undefined && facets.includes("content");
5322
5509
  const end = coerceObviousBase10Int(p.end, "end");
5323
5510
  if (!end.ok || end.value !== undefined && end.value < 1) {
5324
5511
  const message = end.ok ? `Invalid end: expected a positive integer, received ${end.value}.` : end.message;
@@ -5335,41 +5522,67 @@ async function executeDigest(opts) {
5335
5522
  path: p.path
5336
5523
  });
5337
5524
  }
5338
- if (p.visionLevel !== undefined && p.visionMode === undefined) {
5525
+ if (p.visionLevel !== undefined && p.visionMode === undefined && !appliesImagePolicy) {
5339
5526
  return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5340
5527
  path: p.path
5341
5528
  });
5342
5529
  }
5343
5530
  const filePath = resolveToCwd(p.path, cwd);
5344
- const facets = selectedFacets(select);
5345
- const vision = p.visionMode !== undefined && p.visionMode !== "none";
5531
+ const commonOptions = { select, signal };
5532
+ const textOptions = {
5533
+ ...commonOptions,
5534
+ at,
5535
+ end: end.value,
5536
+ limit: limit.value,
5537
+ language: p.language
5538
+ };
5539
+ const hasTextOptions = at !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5540
+ const hasVisionOptions = p.visionMode !== undefined || p.visionLevel !== undefined;
5541
+ const initialVisionMode = appliesImagePolicy ? undefined : p.visionMode;
5542
+ const initialVisionLevel = appliesImagePolicy ? undefined : p.visionLevel;
5346
5543
  try {
5347
5544
  const envelope = await readSeekDigestNative(filePath, {
5348
- select,
5349
- at,
5350
- end: end.value,
5351
- limit: limit.value,
5352
- language: p.language,
5545
+ ...appliesImagePolicy ? commonOptions : textOptions,
5546
+ visionMode: initialVisionMode,
5547
+ visionLevel: initialVisionLevel,
5548
+ vision: initialVisionMode !== undefined && initialVisionMode !== "none"
5549
+ });
5550
+ const image = appliesImagePolicy ? parseImageEnvelope(envelope) : undefined;
5551
+ if (image) {
5552
+ const imageDetails = { metadata: image.metadata };
5553
+ if (opts.imageMode === "off") {
5554
+ return buildDigestSuccess(filePath, facets, imageDetails, [
5555
+ { type: "text", text: `[Skipped standalone image: ${p.path}; readseek.imageMode is off]` }
5556
+ ]);
5557
+ }
5558
+ if (opts.imageMode === "auto" && opts.modelSupportsImages && image.prepared) {
5559
+ return buildDigestSuccess(filePath, facets, imageDetails, [
5560
+ { type: "image", data: image.prepared.data, mimeType: image.prepared.mimeType }
5561
+ ]);
5562
+ }
5563
+ const visionMode = p.visionMode && p.visionMode !== "none" ? p.visionMode : "all";
5564
+ const analyzedEnvelope = await readSeekDigestNative(filePath, {
5565
+ ...commonOptions,
5566
+ visionMode,
5567
+ visionLevel: p.visionLevel,
5568
+ vision: true
5569
+ });
5570
+ return buildDigestSuccess(filePath, facets, analyzedEnvelope);
5571
+ }
5572
+ if (p.visionLevel !== undefined && p.visionMode === undefined) {
5573
+ return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5574
+ path: p.path
5575
+ });
5576
+ }
5577
+ const textEnvelope = appliesImagePolicy && (hasTextOptions || hasVisionOptions) ? await readSeekDigestNative(filePath, {
5578
+ ...textOptions,
5353
5579
  visionMode: p.visionMode,
5354
5580
  visionLevel: p.visionLevel,
5355
- signal,
5356
- vision
5357
- });
5358
- if (hasDigestAnchors(envelope))
5581
+ vision: p.visionMode !== undefined && p.visionMode !== "none"
5582
+ }) : envelope;
5583
+ if (hasDigestAnchors(textEnvelope))
5359
5584
  onFileAnchored?.(filePath);
5360
- const text = JSON.stringify(envelope);
5361
- return {
5362
- content: [{ type: "text", text }],
5363
- details: {
5364
- readSeekValue: {
5365
- tool: "digest",
5366
- ok: true,
5367
- path: filePath,
5368
- select: facets,
5369
- envelope
5370
- }
5371
- }
5372
- };
5585
+ return buildDigestSuccess(filePath, facets, textEnvelope);
5373
5586
  } catch (err) {
5374
5587
  const failure = classifyReadSeekFailure(err);
5375
5588
  return buildToolErrorResult("digest", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
@@ -5391,7 +5604,14 @@ function registerDigestTool(pi, options = {}) {
5391
5604
  promptGuidelines: promptMetadata.promptGuidelines,
5392
5605
  parameters: digestSchema,
5393
5606
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5394
- return executeDigest({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
5607
+ return executeDigest({
5608
+ params,
5609
+ signal,
5610
+ cwd: ctx.cwd,
5611
+ onFileAnchored: options.onFileAnchored,
5612
+ imageMode: options.imageMode,
5613
+ modelSupportsImages: ctx.model?.input.includes("image") ?? false
5614
+ });
5395
5615
  },
5396
5616
  renderCall(args, theme, ...rest) {
5397
5617
  const context = rest[0] ?? {};
@@ -5604,7 +5824,7 @@ function piReadSeekExtension(pi) {
5604
5824
  const { settings } = resolveReadSeekJsonSettings();
5605
5825
  const replacedBuiltIns = new Set(settings.overrideTools ?? []);
5606
5826
  const binaryAvailable = readSeekBinaryAvailability().available;
5607
- const swap = (builtIn, readSeekName) => binaryAvailable && replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5827
+ const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5608
5828
  const readName = swap("read", "readSeek_digest");
5609
5829
  const editName = swap("edit", "readSeek_edit");
5610
5830
  const writeName = swap("write", "readSeek_write");
@@ -5625,7 +5845,12 @@ function piReadSeekExtension(pi) {
5625
5845
  registerRefsTool(pi, { onFileAnchored: markAnchored });
5626
5846
  registerRenameTool(pi, { onFileMutated: forgetAnchors });
5627
5847
  registerDefTool(pi, { onFileAnchored: markAnchored });
5628
- registerDigestTool(pi, { onFileAnchored: markAnchored, name: readName, toolAliases });
5848
+ registerDigestTool(pi, {
5849
+ onFileAnchored: markAnchored,
5850
+ name: readName,
5851
+ toolAliases,
5852
+ imageMode: replacedBuiltIns.has("read") ? resolveReadSeekImageMode() : undefined
5853
+ });
5629
5854
  registerViewTool(pi);
5630
5855
  registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
5631
5856
  pi.on("before_agent_start", (event) => {
@@ -5641,7 +5866,7 @@ ${toolRoutingPolicy(policyAliases)}` };
5641
5866
  const problems = warnings.map(formatSettingsWarning);
5642
5867
  const availability = readSeekBinaryAvailability();
5643
5868
  if (!availability.available) {
5644
- problems.push(`readseek tools are inactive: ${availability.reason}`);
5869
+ problems.push(`readseek installation is broken: ${availability.reason}`);
5645
5870
  }
5646
5871
  for (const problem of problems) {
5647
5872
  if (ctx.hasUI)