token-goat 2.9.14 → 2.9.15

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 (41) hide show
  1. package/README.md +15 -1
  2. package/dist/token-goat-chunk-2X2EBBC6.mjs +277 -0
  3. package/dist/{token-goat-chunk-XQF5J25J.mjs → token-goat-chunk-3NSDDTGL.mjs} +5 -4
  4. package/dist/token-goat-chunk-4EXFN2AW.mjs +29 -0
  5. package/dist/{token-goat-chunk-4SDX3QP3.mjs → token-goat-chunk-5V7DAC7V.mjs} +5 -4
  6. package/dist/token-goat-chunk-6DLVZDB6.mjs +34 -0
  7. package/dist/{token-goat-chunk-XEH6KBWW.mjs → token-goat-chunk-7ZYK25AO.mjs} +4 -3
  8. package/dist/{token-goat-chunk-7OGKZ7AP.mjs → token-goat-chunk-A4JYKD5H.mjs} +5 -3
  9. package/dist/{token-goat-chunk-2ESBO4IN.mjs → token-goat-chunk-ASYEPR3S.mjs} +10 -7
  10. package/dist/{token-goat-chunk-PGGDW7DZ.mjs → token-goat-chunk-ATIFTMRC.mjs} +9 -6
  11. package/dist/{token-goat-chunk-IT6O3PNN.mjs → token-goat-chunk-BL5LNGBG.mjs} +813 -4613
  12. package/dist/{token-goat-chunk-PRJVGIC5.mjs → token-goat-chunk-C5JIO6HK.mjs} +7 -5
  13. package/dist/{token-goat-chunk-YTQHZJXW.mjs → token-goat-chunk-DQ4J5AFF.mjs} +40 -17
  14. package/dist/token-goat-chunk-ERTXEKB6.mjs +228 -0
  15. package/dist/{token-goat-chunk-ASVVF4JV.mjs → token-goat-chunk-GIIHUSZX.mjs} +137 -19
  16. package/dist/{token-goat-chunk-DAMXYVIW.mjs → token-goat-chunk-GMQQA7E4.mjs} +296 -276
  17. package/dist/{token-goat-chunk-T2IWWTHB.mjs → token-goat-chunk-IVUQLQWN.mjs} +33 -1142
  18. package/dist/{token-goat-chunk-POBYR64E.mjs → token-goat-chunk-K7F2BFIK.mjs} +801 -3
  19. package/dist/{token-goat-chunk-RUDOKYPJ.mjs → token-goat-chunk-LCZBPOIN.mjs} +1733 -1360
  20. package/dist/token-goat-chunk-LMFO66YD.mjs +331 -0
  21. package/dist/{token-goat-chunk-EZNVAIR3.mjs → token-goat-chunk-LT7JRU6K.mjs} +4 -3
  22. package/dist/{token-goat-chunk-YHGTGG6K.mjs → token-goat-chunk-MZDIJJ3R.mjs} +29 -5
  23. package/dist/{token-goat-chunk-O5WAMISC.mjs → token-goat-chunk-NDPO7GAH.mjs} +26 -16
  24. package/dist/token-goat-chunk-NDRP4KJQ.mjs +4371 -0
  25. package/dist/{token-goat-chunk-ZFM4PWXL.mjs → token-goat-chunk-NU7TLMQK.mjs} +3 -3
  26. package/dist/token-goat-chunk-OSUFN2FV.mjs +326 -0
  27. package/dist/token-goat-chunk-OUGNPMDA.mjs +959 -0
  28. package/dist/{token-goat-chunk-AMYCQJX4.mjs → token-goat-chunk-PM76YS22.mjs} +49 -256
  29. package/dist/{token-goat-chunk-SAQ5PG4L.mjs → token-goat-chunk-S4XRY446.mjs} +5 -2
  30. package/dist/{token-goat-chunk-DBNY4RLN.mjs → token-goat-chunk-SFAS46RE.mjs} +5 -3
  31. package/dist/{token-goat-chunk-XVZ4MNQC.mjs → token-goat-chunk-SZWYESBS.mjs} +221 -78
  32. package/dist/{token-goat-chunk-HF6H7RNK.mjs → token-goat-chunk-XEPXYDPI.mjs} +3 -2
  33. package/dist/{token-goat-chunk-EG3663UT.mjs → token-goat-chunk-YKG35VHC.mjs} +4 -2
  34. package/dist/{token-goat-chunk-VZYD4OZB.mjs → token-goat-chunk-YQ7WI2CO.mjs} +78 -266
  35. package/dist/{token-goat-chunk-3XQPEJMV.mjs → token-goat-chunk-YZX7EFG4.mjs} +1 -1
  36. package/dist/token-goat-hook.mjs +13 -9
  37. package/dist/token-goat.core.mjs +21 -16
  38. package/docs/cli.md +8 -6
  39. package/package.json +1 -1
  40. package/dist/token-goat-chunk-ZD4EM4LR.mjs +0 -30
  41. package/dist/token-goat-chunk-ZYNNQ36L.mjs +0 -479
@@ -1,56 +1,44 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ formatSqliteQueryTable,
5
+ formatSqliteSchema,
6
+ formatSqliteTables,
7
+ getSqliteSchema,
8
+ getSqliteTables,
9
+ runReadOnlySqliteQuery
10
+ } from "./token-goat-chunk-LMFO66YD.mjs";
3
11
  import {
4
12
  ImageDecodeError,
5
- MAX_OVER_FETCH,
6
13
  NOTEBOOK_CELL_LINES_SUFFIX,
7
- OVER_FETCH_FACTOR,
8
14
  SKIP_DIRS,
15
+ canShrinkFormat,
9
16
  emptyIndexMessage,
10
17
  enqueueDirtyPathSafe,
11
18
  fenceUntrusted,
12
- findContainingSection,
13
19
  formatSymbolLocation,
14
20
  getEmbeddingCoverage,
15
21
  getTrackedFiles,
16
- indexFileSync,
17
22
  indexedSourceText,
18
- isAvailable,
19
23
  isImagePath,
20
24
  isIndexEmptyForProject,
21
25
  isPrivateIpv4Octets,
22
26
  isPrivateIpv6Groups,
23
- isTreeSitterAvailable,
24
27
  isVirtualIndexedPath,
25
- listSections,
26
- mergeNearbyHits,
27
28
  parseIpv6Groups,
28
29
  probeImageMeta,
29
- readSection,
30
30
  scanAndRecord,
31
31
  searchEvidenceSemantically,
32
- searchSemantic,
33
32
  shrinkImage,
34
33
  urlPolicyDenialReason,
35
34
  virtualIndexedScopeNote,
36
35
  walkProject
37
- } from "./token-goat-chunk-IT6O3PNN.mjs";
36
+ } from "./token-goat-chunk-BL5LNGBG.mjs";
38
37
  import {
39
- DEFAULT_QUERY_LIMIT,
40
38
  capJsonRows,
41
- countRefs,
42
- countSymbols,
43
- distinctSymbolKinds,
44
39
  estimateTokens,
45
- getFileEntry,
46
- pathEqClause,
47
- queryRefCounts,
48
- queryRefs,
49
- queryRefsByContext,
50
- querySymbols,
51
- searchSymbolsFts,
52
40
  trimToBudget
53
- } from "./token-goat-chunk-VZYD4OZB.mjs";
41
+ } from "./token-goat-chunk-YQ7WI2CO.mjs";
54
42
  import {
55
43
  extractPdfMeta,
56
44
  extractPdfOutline,
@@ -64,7 +52,7 @@ import {
64
52
  parseWhereSpecs,
65
53
  profileCsv,
66
54
  queryCsv
67
- } from "./token-goat-chunk-EG3663UT.mjs";
55
+ } from "./token-goat-chunk-YKG35VHC.mjs";
68
56
  import {
69
57
  MAX_ZIP_INPUT_BYTES,
70
58
  MAX_ZIP_OUTPUT_BYTES,
@@ -75,46 +63,6 @@ import {
75
63
  import {
76
64
  createLazyModuleLoader
77
65
  } from "./token-goat-chunk-AH6QILZM.mjs";
78
- import {
79
- isOcrEngineAvailable,
80
- isTextHeavy,
81
- ocrImage,
82
- ocrIntegrityFailed
83
- } from "./token-goat-chunk-DBNY4RLN.mjs";
84
- import {
85
- Database,
86
- FILENAME_LANGUAGE,
87
- TREE_SITTER_LANGUAGES,
88
- basenameImportsExtension,
89
- colorStdout,
90
- detectLanguage,
91
- detectLanguageOfFile,
92
- getDb,
93
- isBlobStale,
94
- languageLabel,
95
- loadBlob,
96
- partialRefsReason,
97
- recordStat,
98
- redactIfDotenv,
99
- redactSecrets,
100
- savedTokensFromBytes,
101
- storeBlob,
102
- stripAnsi,
103
- unsupportedLanguageName
104
- } from "./token-goat-chunk-T2IWWTHB.mjs";
105
- import {
106
- fileIsAbsent,
107
- findProject,
108
- fingerprintContent,
109
- fingerprintFile,
110
- getDisplayRoot,
111
- loadConfig,
112
- resolveProjectRoot,
113
- shortFingerprint
114
- } from "./token-goat-chunk-SAQ5PG4L.mjs";
115
- import {
116
- registerReset
117
- } from "./token-goat-chunk-EEIDFMEM.mjs";
118
66
  import {
119
67
  IMPORT_RE,
120
68
  extractAbap,
@@ -139,34 +87,92 @@ import {
139
87
  extractSolidity,
140
88
  extractThrift,
141
89
  stripLeadingAttributes
142
- } from "./token-goat-chunk-HF6H7RNK.mjs";
90
+ } from "./token-goat-chunk-XEPXYDPI.mjs";
91
+ import {
92
+ MAX_OVER_FETCH,
93
+ OVER_FETCH_FACTOR,
94
+ checkEmbeddingPreflight,
95
+ findContainingSection,
96
+ indexFileSync,
97
+ isAvailable,
98
+ isTreeSitterAvailable,
99
+ listSections,
100
+ mergeNearbyHits,
101
+ readSection,
102
+ searchSemantic
103
+ } from "./token-goat-chunk-NDRP4KJQ.mjs";
104
+ import {
105
+ DEFAULT_QUERY_LIMIT,
106
+ countRefs,
107
+ countSymbols,
108
+ distinctSymbolKinds,
109
+ getFileEntry,
110
+ pathEqClause,
111
+ queryRefCounts,
112
+ queryRefs,
113
+ queryRefsByContext,
114
+ querySymbols,
115
+ searchSymbolsFts
116
+ } from "./token-goat-chunk-2X2EBBC6.mjs";
117
+ import {
118
+ isOcrEngineAvailable,
119
+ isTextHeavy,
120
+ ocrImage,
121
+ ocrIntegrityFailed
122
+ } from "./token-goat-chunk-SFAS46RE.mjs";
123
+ import {
124
+ colorStdout,
125
+ isBlobStale,
126
+ loadBlob,
127
+ recordStat,
128
+ redactSecrets,
129
+ savedTokensFromBytes,
130
+ storeBlob,
131
+ stripAnsi
132
+ } from "./token-goat-chunk-IVUQLQWN.mjs";
143
133
  import {
134
+ FILENAME_LANGUAGE,
135
+ TREE_SITTER_LANGUAGES,
136
+ basenameImportsExtension,
137
+ detectLanguage,
138
+ detectLanguageOfFile,
139
+ getDb,
144
140
  isAblSource,
145
141
  isMatlabSource,
146
142
  isObjcHeader,
147
143
  isObjcSource,
148
144
  isPascalSource,
149
145
  isPerlSource,
150
- isPrologSource
151
- } from "./token-goat-chunk-POBYR64E.mjs";
146
+ isPrologSource,
147
+ languageLabel,
148
+ partialRefsReason,
149
+ redactIfDotenv,
150
+ unsupportedLanguageName
151
+ } from "./token-goat-chunk-K7F2BFIK.mjs";
152
+ import {
153
+ fileIsAbsent,
154
+ findProject,
155
+ fingerprintContent,
156
+ fingerprintFile,
157
+ getDisplayRoot,
158
+ loadConfig,
159
+ resolveProjectRoot,
160
+ shortFingerprint
161
+ } from "./token-goat-chunk-S4XRY446.mjs";
162
+ import {
163
+ registerReset
164
+ } from "./token-goat-chunk-EEIDFMEM.mjs";
152
165
  import {
153
166
  PER_FILE_COUNTERFACTUAL_CEILING,
154
167
  SKELETON_SYMBOL_CAP,
155
- UNTRUSTED_FILE_TAG,
156
- UNTRUSTED_GITHUB_TAG,
157
- UNTRUSTED_HTML_TAG,
158
168
  atomicWriteBytes,
159
169
  compileGrepMatcher,
160
170
  countNoun,
161
171
  decodeSource,
162
- displaySafeJson,
163
- displaySafeText,
164
172
  ensureNewline,
165
173
  escapeRegExp,
166
174
  excludeTestsHiddenNote,
167
175
  extractErrorMessage,
168
- fenceUntrustedContent,
169
- fenceUntrustedFileContent,
170
176
  filtersFilteredToEmptyNotice,
171
177
  foldCaseForContainment,
172
178
  foldPath,
@@ -174,20 +180,29 @@ import {
174
180
  grepFilteredToEmptyNotice,
175
181
  isInsideRoot,
176
182
  isTestFile,
177
- normalizePath,
178
183
  pushAll,
179
184
  redactUrlQuery,
180
185
  requireNonNegativeStrictInt,
181
186
  requirePositiveStrictInt,
182
- resolveIndexPath,
183
187
  resolveOnPath,
184
188
  runGit,
185
189
  stripBom,
186
190
  supportRequestLine,
187
- toDisplayPath,
188
191
  windowsCmdQuoteArg,
189
192
  withExtension
190
- } from "./token-goat-chunk-AMYCQJX4.mjs";
193
+ } from "./token-goat-chunk-PM76YS22.mjs";
194
+ import {
195
+ UNTRUSTED_FILE_TAG,
196
+ UNTRUSTED_GITHUB_TAG,
197
+ UNTRUSTED_HTML_TAG,
198
+ displaySafeJson,
199
+ displaySafeText,
200
+ fenceUntrustedContent,
201
+ fenceUntrustedFileContent,
202
+ normalizePath,
203
+ resolveIndexPath,
204
+ toDisplayPath
205
+ } from "./token-goat-chunk-ERTXEKB6.mjs";
191
206
  import {
192
207
  compileGuardedRegex
193
208
  } from "./token-goat-chunk-GMOUBOX4.mjs";
@@ -195,185 +210,439 @@ import {
195
210
  init_define_import_meta_env
196
211
  } from "./token-goat-chunk-A37V4PBF.mjs";
197
212
 
198
- // src/read_spec.ts
199
- init_define_import_meta_env();
200
- import * as fs14 from "node:fs";
201
-
202
- // src/read_commands.ts
203
- init_define_import_meta_env();
204
- import * as fs13 from "node:fs";
205
- import * as path12 from "node:path";
206
-
207
- // src/import_export_extract.ts
213
+ // src/json_query.ts
208
214
  init_define_import_meta_env();
209
- import * as path from "node:path";
210
- var STATEMENT_ADAPTER_IMPORTS = new Map([
211
- [".abap", extractAbap],
212
- [".sas", extractSas],
213
- [".pli", extractPli],
214
- [".pl1", extractPli],
215
- [".rpgle", extractRpg],
216
- [".sqlrpgle", extractRpg],
217
- [".jcl", extractJcl],
218
- ...[".f", ".for", ".f77", ".f90", ".f95", ".f03", ".f08"].map((e) => [e, extractFortran]),
219
- ...[".pas", ".dpr", ".dpk", ".lpr"].map((e) => [e, extractPascal]),
220
- [".cmake", extractCmake],
221
- ...[".s", ".asm", ".nasm"].map((e) => [e, extractAsm]),
222
- ...[".bat", ".cmd"].map((e) => [e, extractBatch]),
223
- ...[".erl", ".hrl"].map((e) => [e, extractErlang])
224
- ]);
225
- var BRACE_ADAPTER_IMPORTS = new Map([
226
- [".mm", extractObjc],
227
- [".groovy", extractGroovy],
228
- [".gvy", extractGroovy],
229
- [".gradle", extractGroovy],
230
- [".pm", extractPerl],
231
- [".sol", extractSolidity],
232
- [".thrift", extractThrift],
233
- ...[".glsl", ".vert", ".frag", ".comp", ".geom", ".tesc", ".tese", ".hlsl", ".hlsli", ".metal"].map((e) => [e, extractCShader])
234
- ]);
235
- function braceAdapterImportsFor(e, text) {
236
- if (e === ".m") return isObjcSource(text) ? extractObjc : isMatlabSource(text) ? extractMatlab : void 0;
237
- if (e === ".pp") return isPascalSource(text) ? extractPascal : void 0;
238
- if (e === ".h") return isObjcHeader(text) ? extractObjc : void 0;
239
- if (e === ".pl") return isPrologSource(text) ? void 0 : extractPerl;
240
- if (e === ".t") return isPerlSource(text) ? extractPerl : void 0;
241
- return BRACE_ADAPTER_IMPORTS.get(e);
215
+ function jsonType(value) {
216
+ if (value === null) return "null";
217
+ if (Array.isArray(value)) return "array";
218
+ return typeof value;
242
219
  }
243
- function extractExportNames(text, ext) {
244
- const names = [];
245
- const push = (s) => {
246
- let v = (s ?? "").trim();
247
- if (v.includes(" as ")) v = v.split(/\s+as\s+/).pop()?.trim() ?? v;
248
- if (v !== "" && v !== "default" && !names.includes(v)) names.push(v);
249
- };
250
- const e = ext.toLowerCase();
251
- const lines = text.split(/\r?\n/);
252
- if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"].includes(e)) {
253
- const declRe = /\bexport\s+(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:function\*?|class|const|let|var|interface|type|enum|namespace)\s+([A-Za-z_$][\w$]*)/g;
254
- const defaultRe = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*(?:;|$)/g;
255
- const namedRe = /\bexport\s+(?:type\s+)?\{([^}]*)\}/g;
256
- let m;
257
- while ((m = declRe.exec(text)) !== null) push(m[1]);
258
- while ((m = defaultRe.exec(text)) !== null) push(m[1]);
259
- while ((m = namedRe.exec(text)) !== null) {
260
- for (const part of (m[1] ?? "").split(",")) push(part);
261
- }
262
- } else if (e === ".py") {
263
- for (const line of lines) {
264
- const m = /^(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)/.exec(line);
265
- if (m && !(m[1] ?? "").startsWith("_")) push(m[1]);
266
- }
267
- } else if (e === ".rs") {
268
- for (const line of lines) {
269
- const m = /^\s*pub(?:\s*\([^)]*\))?\s+(?:async\s+)?(?:fn|struct|enum|trait|type|const|mod|static)\s+([A-Za-z_]\w*)/.exec(line);
270
- if (m) push(m[1]);
220
+ function fieldSummary(name, value) {
221
+ const type = jsonType(value);
222
+ if (type === "array") return { name, type, size: value.length };
223
+ if (type === "object") return { name, type, size: Object.keys(value).length };
224
+ return { name, type };
225
+ }
226
+ function outlineJson(data, opts = {}) {
227
+ const sampleSize = opts.sampleSize ?? 5;
228
+ const type = jsonType(data);
229
+ if (type === "array") {
230
+ const arr = data;
231
+ const sample = arr.slice(0, sampleSize);
232
+ const elementTypes = new Set(sample.map(jsonType));
233
+ const elementType = elementTypes.size === 0 ? "unknown" : elementTypes.size === 1 ? [...elementTypes][0] : "mixed";
234
+ const result = { kind: "array", length: arr.length, elementType };
235
+ if (elementType === "object") {
236
+ const objects = sample;
237
+ const keySets = objects.map((el) => Object.keys(el));
238
+ const allKeys = [...new Set(keySets.flat())];
239
+ result.sampleKeys = allKeys.map((k) => {
240
+ const owner = objects.find((el) => Object.prototype.hasOwnProperty.call(el, k));
241
+ return fieldSummary(k, owner?.[k]);
242
+ });
243
+ const firstKeySet = keySets[0] ?? [];
244
+ result.heterogeneous = keySets.some((ks) => ks.length !== firstKeySet.length || !ks.every((k) => firstKeySet.includes(k)));
271
245
  }
272
- } else if (e === ".java") {
273
- for (const line of lines) {
274
- const m = /\bpublic\s+(?:static\s+|final\s+|abstract\s+)*(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/.exec(line);
275
- if (m) push(m[1]);
246
+ return result;
247
+ }
248
+ if (type === "object") {
249
+ const obj = data;
250
+ return { kind: "object", fields: Object.keys(obj).map((k) => fieldSummary(k, obj[k])) };
251
+ }
252
+ return { kind: "primitive", type };
253
+ }
254
+ function formatJsonOutline(outline) {
255
+ if (outline.kind === "primitive") return `(scalar ${outline.type})`;
256
+ if (outline.kind === "object") {
257
+ if (outline.fields.length === 0) return "(empty object)";
258
+ return outline.fields.map((f) => `${displaySafeText(f.name)}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`).join("\n");
259
+ }
260
+ const lines = [`array of ${outline.length} element${outline.length === 1 ? "" : "s"} (${outline.elementType})`];
261
+ if (outline.sampleKeys !== void 0) {
262
+ lines.push(outline.heterogeneous === true ? "keys (from first elements, shape varies across sample):" : "keys (from first elements):");
263
+ for (const f of outline.sampleKeys) {
264
+ lines.push(` ${displaySafeText(f.name)}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`);
276
265
  }
277
266
  }
278
- return names;
267
+ return lines.join("\n");
279
268
  }
280
- function splitTopLevelCommas(s) {
281
- const parts = [];
282
- let depth = 0;
283
- let cur = "";
284
- for (const ch of s) {
285
- if (ch === "{") depth++;
286
- if (ch === "}") depth--;
287
- if (ch === "," && depth === 0) {
288
- parts.push(cur);
289
- cur = "";
269
+ var MAX_RECURSIVE_NODES = 5e4;
270
+ var MAX_RECURSIVE_DEPTH = 100;
271
+ function collectRecursiveKey(root, keyName, out, budget) {
272
+ const seen = /* @__PURE__ */ new Set();
273
+ function walk(val, depth) {
274
+ if (depth > MAX_RECURSIVE_DEPTH) {
275
+ budget.exhausted = true;
276
+ return;
277
+ }
278
+ if (val === null || typeof val !== "object") return;
279
+ if (seen.has(val)) return;
280
+ seen.add(val);
281
+ if (budget.nodesLeft <= 0) {
282
+ budget.exhausted = true;
283
+ return;
284
+ }
285
+ budget.nodesLeft--;
286
+ if (Array.isArray(val)) {
287
+ for (let i = 0; i < val.length; i++) {
288
+ walk(val[i], depth + 1);
289
+ if (budget.nodesLeft <= 0) {
290
+ if (i < val.length - 1) budget.exhausted = true;
291
+ return;
292
+ }
293
+ }
290
294
  } else {
291
- cur += ch;
295
+ const obj = val;
296
+ if (Object.prototype.hasOwnProperty.call(obj, keyName)) {
297
+ out.push(obj[keyName]);
298
+ }
299
+ const keys = Object.keys(obj);
300
+ for (let i = 0; i < keys.length; i++) {
301
+ walk(obj[keys[i]], depth + 1);
302
+ if (budget.nodesLeft <= 0) {
303
+ if (i < keys.length - 1) budget.exhausted = true;
304
+ return;
305
+ }
306
+ }
292
307
  }
293
308
  }
294
- if (cur.trim() !== "") parts.push(cur);
295
- return parts;
309
+ walk(root, 0);
296
310
  }
297
- function expandRustUseGroup(base, inner) {
298
- const results = [];
299
- for (const part of splitTopLevelCommas(inner)) {
300
- const trimmed = part.trim();
301
- if (trimmed === "" || trimmed === "self") {
302
- if (trimmed === "self") results.push(base);
311
+ function getNestedField(obj, fieldPath) {
312
+ if (obj === null || typeof obj !== "object") return void 0;
313
+ if (Object.prototype.hasOwnProperty.call(obj, fieldPath)) {
314
+ return obj[fieldPath];
315
+ }
316
+ const parts = fieldPath.split(".");
317
+ let cur = obj;
318
+ for (const part of parts) {
319
+ if (cur === null || typeof cur !== "object") return void 0;
320
+ if (!Object.prototype.hasOwnProperty.call(cur, part)) return void 0;
321
+ cur = cur[part];
322
+ }
323
+ return cur;
324
+ }
325
+ function parseJsonPath(spec) {
326
+ const ops = [];
327
+ const n = spec.length;
328
+ let i = 0;
329
+ while (i < n) {
330
+ if (spec[i] === "." && i + 1 < n && spec[i + 1] === ".") {
331
+ i += 2;
332
+ let j2 = i;
333
+ while (j2 < n && spec[j2] !== "." && spec[j2] !== "[") j2++;
334
+ if (j2 === i) throw new Error(`invalid path spec: expected property name after '..' in '${spec}'`);
335
+ ops.push({ kind: "recursive_key", name: spec.slice(i, j2) });
336
+ i = j2;
303
337
  continue;
304
338
  }
305
- const nested = /^([\w:]+)::\{([\s\S]*)\}$/.exec(trimmed);
306
- if (nested) {
307
- results.push(...expandRustUseGroup(`${base}::${nested[1] ?? ""}`, nested[2] ?? ""));
339
+ const ch = spec[i];
340
+ if (ch === ".") {
341
+ i++;
308
342
  continue;
309
343
  }
310
- const name = (trimmed.split(/\s+as\s+/)[0] ?? "").trim();
311
- if (name === "" || name === "self") {
312
- results.push(base);
344
+ if (ch === "[") {
345
+ let inQuote = null;
346
+ let close = -1;
347
+ for (let k = i + 1; k < n; k++) {
348
+ const c = spec[k];
349
+ if (inQuote !== null) {
350
+ if (c === "\\") {
351
+ k++;
352
+ continue;
353
+ }
354
+ if (c === inQuote) {
355
+ inQuote = null;
356
+ }
357
+ } else if (c === '"' || c === "'") {
358
+ inQuote = c;
359
+ } else if (c === "]") {
360
+ close = k;
361
+ break;
362
+ }
363
+ }
364
+ if (close === -1) throw new Error(`invalid path spec: unterminated '[' in '${spec}'`);
365
+ const inner = spec.slice(i + 1, close);
366
+ if (inner === "*") {
367
+ ops.push({ kind: "wildcard" });
368
+ } else if (/^-?\d+$/.test(inner)) {
369
+ ops.push({ kind: "index", index: Number(inner) });
370
+ } else {
371
+ let expr = inner.trim();
372
+ if (expr.startsWith("?(") && expr.endsWith(")")) {
373
+ expr = expr.slice(2, -1).trim();
374
+ }
375
+ if (expr.startsWith("@.")) {
376
+ expr = expr.slice(2).trim();
377
+ }
378
+ const m = /^([^=]+)==?(.*)$/.exec(expr);
379
+ if (!m) {
380
+ const bareOperator = /[<>]/.exec(expr);
381
+ if (bareOperator !== null) {
382
+ throw new Error(`unsupported comparison operator '${bareOperator[0]}' in '[${inner}]' of path spec '${spec}': this grammar filters by equality only ([field=value])`);
383
+ }
384
+ throw new Error(`invalid bracket expression '[${inner}]' in path spec: '${spec}' (expected [n], [*], or [field=value])`);
385
+ }
386
+ let field = m[1].trim();
387
+ if (field.startsWith("@.")) field = field.slice(2).trim();
388
+ const swallowedOperator = /[!<>]$/.exec(field);
389
+ if (swallowedOperator !== null) {
390
+ throw new Error(`unsupported comparison operator '${swallowedOperator[0]}=' in '[${inner}]' of path spec '${spec}': this grammar filters by equality only ([field=value])`);
391
+ }
392
+ let rawVal = m[2].trim();
393
+ if (rawVal.startsWith('"') && rawVal.endsWith('"') || rawVal.startsWith("'") && rawVal.endsWith("'")) {
394
+ if (rawVal.length >= 2) {
395
+ rawVal = rawVal.slice(1, -1).replace(/\\(["'\\])/g, "$1");
396
+ }
397
+ }
398
+ ops.push({ kind: "filter", field, value: rawVal });
399
+ }
400
+ i = close + 1;
313
401
  continue;
314
402
  }
315
- results.push(`${base}::${name}`);
403
+ let j = i;
404
+ while (j < n && spec[j] !== "." && spec[j] !== "[") j++;
405
+ if (j === i) throw new Error(`invalid path spec: '${spec}'`);
406
+ ops.push({ kind: "key", name: spec.slice(i, j) });
407
+ i = j;
316
408
  }
317
- return results;
409
+ return ops;
318
410
  }
319
- function extractImports(text, ext) {
320
- const found = [];
321
- const push = (s) => {
322
- const v = (s ?? "").trim();
323
- if (v !== "" && !found.includes(v)) found.push(v);
324
- };
325
- const e = ext.toLowerCase();
326
- const lines = text.split(/\r?\n/);
327
- if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue", ".svelte", ".astro"].includes(e)) {
328
- const fromRe = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]/g;
329
- const bareRe = /^\s*import\s*['"]([^'"]+)['"]/gm;
330
- const reqRe = /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
331
- const dynRe = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
332
- const matches = [];
333
- let m;
334
- while ((m = fromRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
335
- while ((m = bareRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
336
- while ((m = reqRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
337
- while ((m = dynRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
338
- matches.sort((a, b) => a.index - b.index);
339
- for (const match of matches) push(match.value);
340
- } else if (e === ".py") {
341
- for (const line of lines) {
342
- const from = /^\s*from\s+([.\w]+)\s+import\b/.exec(line);
343
- if (from) {
344
- push(from[1]);
345
- continue;
346
- }
347
- const imp = /^\s*import\s+(.+)$/.exec(line);
348
- if (imp) {
349
- const spec = (imp[1] ?? "").split("#")[0] ?? "";
350
- for (const part of spec.split(",")) push(part.trim().split(/\s+as\s+/)[0]);
351
- }
352
- }
353
- } else if (e === ".go") {
354
- let inBlock = false;
355
- for (const line of lines) {
356
- if (/^\s*import\s*\(/.test(line)) {
357
- inBlock = true;
358
- continue;
359
- }
360
- if (inBlock) {
361
- if (/^\s*\)/.test(line)) {
362
- inBlock = false;
363
- continue;
411
+ function evalJsonPath(data, ops) {
412
+ let current = [data];
413
+ let fanned = false;
414
+ const budget = { nodesLeft: MAX_RECURSIVE_NODES, exhausted: false };
415
+ for (const op of ops) {
416
+ const next = [];
417
+ for (const item of current) {
418
+ if (op.kind === "key") {
419
+ if (typeof item === "object" && item !== null && !Array.isArray(item) && Object.prototype.hasOwnProperty.call(item, op.name)) {
420
+ next.push(item[op.name]);
421
+ } else if (!fanned) {
422
+ throw new Error(`path not found: key '${op.name}' does not exist on ${jsonType(item)} value`);
423
+ }
424
+ } else if (op.kind === "recursive_key") {
425
+ fanned = true;
426
+ collectRecursiveKey(item, op.name, next, budget);
427
+ } else if (op.kind === "index") {
428
+ if (Array.isArray(item)) {
429
+ const idx = op.index < 0 ? item.length + op.index : op.index;
430
+ if (idx >= 0 && idx < item.length) {
431
+ next.push(item[idx]);
432
+ } else if (!fanned) {
433
+ throw new Error(`path not found: index [${op.index}] out of range (length ${item.length})`);
434
+ }
435
+ } else if (!fanned) {
436
+ throw new Error(`path not found: index [${op.index}] on non-array ${jsonType(item)} value`);
437
+ }
438
+ } else if (op.kind === "wildcard") {
439
+ fanned = true;
440
+ if (Array.isArray(item)) {
441
+ pushAll(next, item);
442
+ } else if (typeof item === "object" && item !== null) {
443
+ pushAll(next, Object.values(item));
444
+ }
445
+ } else {
446
+ fanned = true;
447
+ if (Array.isArray(item)) {
448
+ for (const el of item) {
449
+ if (typeof el === "object" && el !== null && !Array.isArray(el)) {
450
+ const val = getNestedField(el, op.field);
451
+ if (val !== void 0 && String(val) === op.value) {
452
+ next.push(el);
453
+ }
454
+ }
455
+ }
364
456
  }
365
- const m = /['"]([^'"]+)['"]/.exec(line);
366
- if (m) push(m[1]);
367
- continue;
368
457
  }
369
- const single = /^\s*import\s+(?:[\w.]+\s+)?['"]([^'"]+)['"]/.exec(line);
370
- if (single) push(single[1]);
371
458
  }
372
- } else if (e === ".rs") {
373
- for (const line of lines) {
374
- const groupM = /^\s*(?:pub(?:\([^)]*\))?\s+)?use\s+([\w:]+)::\{([\s\S]*)\}/.exec(line);
375
- if (groupM) {
376
- for (const t of expandRustUseGroup(groupM[1] ?? "", groupM[2] ?? "")) push(t);
459
+ current = next;
460
+ }
461
+ return { fanned, items: current, truncated: budget.exhausted };
462
+ }
463
+ function queryJson(data, spec) {
464
+ return evalJsonPath(data, parseJsonPath(spec));
465
+ }
466
+
467
+ // src/read_spec.ts
468
+ init_define_import_meta_env();
469
+ import * as fs13 from "node:fs";
470
+
471
+ // src/read_commands.ts
472
+ init_define_import_meta_env();
473
+ import * as fs12 from "node:fs";
474
+ import * as path12 from "node:path";
475
+
476
+ // src/import_export_extract.ts
477
+ init_define_import_meta_env();
478
+ import * as path from "node:path";
479
+ var STATEMENT_ADAPTER_IMPORTS = new Map([
480
+ [".abap", extractAbap],
481
+ [".sas", extractSas],
482
+ [".pli", extractPli],
483
+ [".pl1", extractPli],
484
+ [".rpgle", extractRpg],
485
+ [".sqlrpgle", extractRpg],
486
+ [".jcl", extractJcl],
487
+ ...[".f", ".for", ".f77", ".f90", ".f95", ".f03", ".f08"].map((e) => [e, extractFortran]),
488
+ ...[".pas", ".dpr", ".dpk", ".lpr"].map((e) => [e, extractPascal]),
489
+ [".cmake", extractCmake],
490
+ ...[".s", ".asm", ".nasm"].map((e) => [e, extractAsm]),
491
+ ...[".bat", ".cmd"].map((e) => [e, extractBatch]),
492
+ ...[".erl", ".hrl"].map((e) => [e, extractErlang])
493
+ ]);
494
+ var BRACE_ADAPTER_IMPORTS = new Map([
495
+ [".mm", extractObjc],
496
+ [".groovy", extractGroovy],
497
+ [".gvy", extractGroovy],
498
+ [".gradle", extractGroovy],
499
+ [".pm", extractPerl],
500
+ [".sol", extractSolidity],
501
+ [".thrift", extractThrift],
502
+ ...[".glsl", ".vert", ".frag", ".comp", ".geom", ".tesc", ".tese", ".hlsl", ".hlsli", ".metal"].map((e) => [e, extractCShader])
503
+ ]);
504
+ function braceAdapterImportsFor(e, text) {
505
+ if (e === ".m") return isObjcSource(text) ? extractObjc : isMatlabSource(text) ? extractMatlab : void 0;
506
+ if (e === ".pp") return isPascalSource(text) ? extractPascal : void 0;
507
+ if (e === ".h") return isObjcHeader(text) ? extractObjc : void 0;
508
+ if (e === ".pl") return isPrologSource(text) ? void 0 : extractPerl;
509
+ if (e === ".t") return isPerlSource(text) ? extractPerl : void 0;
510
+ return BRACE_ADAPTER_IMPORTS.get(e);
511
+ }
512
+ function extractExportNames(text, ext) {
513
+ const names = [];
514
+ const push = (s) => {
515
+ let v = (s ?? "").trim();
516
+ if (v.includes(" as ")) v = v.split(/\s+as\s+/).pop()?.trim() ?? v;
517
+ if (v !== "" && v !== "default" && !names.includes(v)) names.push(v);
518
+ };
519
+ const e = ext.toLowerCase();
520
+ const lines = text.split(/\r?\n/);
521
+ if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"].includes(e)) {
522
+ const declRe = /\bexport\s+(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:function\*?|class|const|let|var|interface|type|enum|namespace)\s+([A-Za-z_$][\w$]*)/g;
523
+ const defaultRe = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*(?:;|$)/g;
524
+ const namedRe = /\bexport\s+(?:type\s+)?\{([^}]*)\}/g;
525
+ let m;
526
+ while ((m = declRe.exec(text)) !== null) push(m[1]);
527
+ while ((m = defaultRe.exec(text)) !== null) push(m[1]);
528
+ while ((m = namedRe.exec(text)) !== null) {
529
+ for (const part of (m[1] ?? "").split(",")) push(part);
530
+ }
531
+ } else if (e === ".py") {
532
+ for (const line of lines) {
533
+ const m = /^(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)/.exec(line);
534
+ if (m && !(m[1] ?? "").startsWith("_")) push(m[1]);
535
+ }
536
+ } else if (e === ".rs") {
537
+ for (const line of lines) {
538
+ const m = /^\s*pub(?:\s*\([^)]*\))?\s+(?:async\s+)?(?:fn|struct|enum|trait|type|const|mod|static)\s+([A-Za-z_]\w*)/.exec(line);
539
+ if (m) push(m[1]);
540
+ }
541
+ } else if (e === ".java") {
542
+ for (const line of lines) {
543
+ const m = /\bpublic\s+(?:static\s+|final\s+|abstract\s+)*(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/.exec(line);
544
+ if (m) push(m[1]);
545
+ }
546
+ }
547
+ return names;
548
+ }
549
+ function splitTopLevelCommas(s) {
550
+ const parts = [];
551
+ let depth = 0;
552
+ let cur = "";
553
+ for (const ch of s) {
554
+ if (ch === "{") depth++;
555
+ if (ch === "}") depth--;
556
+ if (ch === "," && depth === 0) {
557
+ parts.push(cur);
558
+ cur = "";
559
+ } else {
560
+ cur += ch;
561
+ }
562
+ }
563
+ if (cur.trim() !== "") parts.push(cur);
564
+ return parts;
565
+ }
566
+ function expandRustUseGroup(base, inner) {
567
+ const results = [];
568
+ for (const part of splitTopLevelCommas(inner)) {
569
+ const trimmed = part.trim();
570
+ if (trimmed === "" || trimmed === "self") {
571
+ if (trimmed === "self") results.push(base);
572
+ continue;
573
+ }
574
+ const nested = /^([\w:]+)::\{([\s\S]*)\}$/.exec(trimmed);
575
+ if (nested) {
576
+ results.push(...expandRustUseGroup(`${base}::${nested[1] ?? ""}`, nested[2] ?? ""));
577
+ continue;
578
+ }
579
+ const name = (trimmed.split(/\s+as\s+/)[0] ?? "").trim();
580
+ if (name === "" || name === "self") {
581
+ results.push(base);
582
+ continue;
583
+ }
584
+ results.push(`${base}::${name}`);
585
+ }
586
+ return results;
587
+ }
588
+ function extractImports(text, ext) {
589
+ const found = [];
590
+ const push = (s) => {
591
+ const v = (s ?? "").trim();
592
+ if (v !== "" && !found.includes(v)) found.push(v);
593
+ };
594
+ const e = ext.toLowerCase();
595
+ const lines = text.split(/\r?\n/);
596
+ if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue", ".svelte", ".astro"].includes(e)) {
597
+ const fromRe = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]/g;
598
+ const bareRe = /^\s*import\s*['"]([^'"]+)['"]/gm;
599
+ const reqRe = /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
600
+ const dynRe = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
601
+ const matches = [];
602
+ let m;
603
+ while ((m = fromRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
604
+ while ((m = bareRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
605
+ while ((m = reqRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
606
+ while ((m = dynRe.exec(text)) !== null) matches.push({ index: m.index, value: m[1] ?? "" });
607
+ matches.sort((a, b) => a.index - b.index);
608
+ for (const match of matches) push(match.value);
609
+ } else if (e === ".py") {
610
+ for (const line of lines) {
611
+ const from = /^\s*from\s+([.\w]+)\s+import\b/.exec(line);
612
+ if (from) {
613
+ push(from[1]);
614
+ continue;
615
+ }
616
+ const imp = /^\s*import\s+(.+)$/.exec(line);
617
+ if (imp) {
618
+ const spec = (imp[1] ?? "").split("#")[0] ?? "";
619
+ for (const part of spec.split(",")) push(part.trim().split(/\s+as\s+/)[0]);
620
+ }
621
+ }
622
+ } else if (e === ".go") {
623
+ let inBlock = false;
624
+ for (const line of lines) {
625
+ if (/^\s*import\s*\(/.test(line)) {
626
+ inBlock = true;
627
+ continue;
628
+ }
629
+ if (inBlock) {
630
+ if (/^\s*\)/.test(line)) {
631
+ inBlock = false;
632
+ continue;
633
+ }
634
+ const m = /['"]([^'"]+)['"]/.exec(line);
635
+ if (m) push(m[1]);
636
+ continue;
637
+ }
638
+ const single = /^\s*import\s+(?:[\w.]+\s+)?['"]([^'"]+)['"]/.exec(line);
639
+ if (single) push(single[1]);
640
+ }
641
+ } else if (e === ".rs") {
642
+ for (const line of lines) {
643
+ const groupM = /^\s*(?:pub(?:\([^)]*\))?\s+)?use\s+([\w:]+)::\{([\s\S]*)\}/.exec(line);
644
+ if (groupM) {
645
+ for (const t of expandRustUseGroup(groupM[1] ?? "", groupM[2] ?? "")) push(t);
377
646
  continue;
378
647
  }
379
648
  const m = /^\s*(?:pub(?:\([^)]*\))?\s+)?use\s+([^;{]+)/.exec(line);
@@ -5644,737 +5913,483 @@ function parseHtml(html) {
5644
5913
  const prevLine = seenIds.get(attrVal);
5645
5914
  if (prevLine !== void 0) {
5646
5915
  issues.push({
5647
- line: tagStartLine,
5648
- type: "error",
5649
- message: `Duplicate ID '#${attrVal}' previously defined on line ${prevLine}`,
5650
- rule: "duplicate-id"
5651
- });
5652
- } else {
5653
- seenIds.set(attrVal, tagStartLine);
5654
- }
5655
- }
5656
- }
5657
- const newNode = {
5658
- tag: tagName,
5659
- attributes,
5660
- children: [],
5661
- parent: currentParent,
5662
- text: "",
5663
- rawHtml: tagSlice,
5664
- startOffset: pos,
5665
- endOffset: closeBracket + 1,
5666
- line: tagStartLine,
5667
- endLine: tagStartLine + sliceLines,
5668
- isVoid
5669
- };
5670
- currentParent.children.push(newNode);
5671
- if (!isVoid) {
5672
- currentParent = newNode;
5673
- }
5674
- currentLine += sliceLines;
5675
- pos = closeBracket + 1;
5676
- continue;
5677
- }
5678
- issues.push({
5679
- line: tagStartLine,
5680
- type: "warning",
5681
- message: "Unescaped '<' character in text: consider '&lt;'",
5682
- rule: "unescaped-angle-bracket"
5683
- });
5684
- if (currentParent) {
5685
- currentParent.text += "<";
5686
- }
5687
- pos++;
5688
- continue;
5689
- }
5690
- const nextBracket = html.indexOf("<", pos);
5691
- const textChunkEnd = nextBracket !== -1 ? nextBracket : len;
5692
- const textChunk = html.slice(pos, textChunkEnd);
5693
- if (textChunk) {
5694
- if (currentParent) {
5695
- currentParent.text += textChunk;
5696
- }
5697
- currentLine += (textChunk.match(/\n/g) || []).length;
5698
- }
5699
- pos = textChunkEnd;
5700
- }
5701
- let dangling = currentParent;
5702
- while (dangling && dangling.tag !== "#root") {
5703
- if (!HTML_VOID_TAGS.has(dangling.tag.toLowerCase())) {
5704
- issues.push({
5705
- line: dangling.line,
5706
- type: "error",
5707
- message: `Unclosed tag <${dangling.tag}> at end of document`,
5708
- rule: "unclosed-tag"
5709
- });
5710
- }
5711
- dangling.endOffset = len;
5712
- dangling = dangling.parent;
5713
- }
5714
- for (const tag of Object.keys(tagCounts)) {
5715
- const entry = tagCounts[tag];
5716
- entry.diff = entry.open - entry.close;
5717
- }
5718
- root.endLine = currentLine;
5719
- return { root, issues, tagCounts };
5720
- }
5721
- function lintHtml(htmlText) {
5722
- const { issues, tagCounts } = parseHtml(htmlText);
5723
- const errors = issues.filter((i) => i.type === "error");
5724
- const warnings = issues.filter((i) => i.type === "warning");
5725
- return {
5726
- valid: errors.length === 0,
5727
- errors,
5728
- warnings,
5729
- tagCounts
5730
- };
5731
- }
5732
- function outlineHtml(htmlText) {
5733
- const { root } = parseHtml(htmlText);
5734
- let title = null;
5735
- let doctype = null;
5736
- const docMatch = /<!doctype\s+(\S[^>]*)>/i.exec(htmlText);
5737
- if (docMatch) doctype = docMatch[1].trim();
5738
- let totalElements = 0;
5739
- const uniqueTags = /* @__PURE__ */ new Set();
5740
- let maxDepth = 0;
5741
- const headings = [];
5742
- const landmarks = [];
5743
- const tables = [];
5744
- const forms = [];
5745
- let scripts = 0;
5746
- let styles = 0;
5747
- const landmarkTags = /* @__PURE__ */ new Set(["header", "nav", "main", "article", "section", "footer", "aside"]);
5748
- function walk(node, depth) {
5749
- if (node.tag !== "#root") {
5750
- totalElements++;
5751
- uniqueTags.add(node.tag);
5752
- if (depth > maxDepth) maxDepth = depth;
5753
- const tag = node.tag.toLowerCase();
5754
- if (tag === "title" && !title) {
5755
- title = node.text.trim();
5756
- } else if (/^h[1-6]$/.test(tag)) {
5757
- const level = parseInt(tag[1], 10);
5758
- headings.push({ level, text: node.text.replace(/\s+/g, " ").trim().slice(0, 80), line: node.line });
5759
- } else if (landmarkTags.has(tag)) {
5760
- landmarks.push({
5761
- tag,
5762
- id: node.attributes["id"],
5763
- class: node.attributes["class"],
5764
- line: node.line
5765
- });
5766
- } else if (tag === "table") {
5767
- const rows = node.children.flatMap((c) => c.tag === "tbody" || c.tag === "thead" ? c.children : [c]).filter((c) => c.tag === "tr").length;
5768
- tables.push({ id: node.attributes["id"], class: node.attributes["class"], rows, line: node.line });
5769
- } else if (tag === "form") {
5770
- forms.push({ id: node.attributes["id"], action: node.attributes["action"], method: node.attributes["method"], line: node.line });
5771
- } else if (tag === "script") {
5772
- scripts++;
5773
- } else if (tag === "style") {
5774
- styles++;
5775
- }
5776
- }
5777
- for (const child of node.children) {
5778
- walk(child, depth + 1);
5779
- }
5780
- }
5781
- walk(root, 0);
5782
- return {
5783
- title,
5784
- doctype,
5785
- totalElements,
5786
- uniqueTags: Array.from(uniqueTags).sort(),
5787
- maxDepth,
5788
- headings,
5789
- landmarks,
5790
- tables,
5791
- forms,
5792
- scripts,
5793
- styles
5794
- };
5795
- }
5796
- function formatHtmlOutline(summary) {
5797
- const lines = [];
5798
- lines.push(`HTML Document (${summary.totalElements} elements, ${summary.uniqueTags.length} unique tags, max depth ${summary.maxDepth})`);
5799
- if (summary.title) lines.push(`Title: "${displaySafeText(summary.title)}"`);
5800
- if (summary.doctype) lines.push(`DOCTYPE: ${displaySafeText(summary.doctype)}`);
5801
- lines.push(`Assets: ${summary.scripts} scripts, ${summary.styles} stylesheets`);
5802
- if (summary.headings.length > 0) {
5803
- lines.push("", "Headings:");
5804
- for (const h of summary.headings.slice(0, 30)) {
5805
- lines.push(` ${" ".repeat(h.level - 1)}[h${h.level}] ${displaySafeText(h.text)} (line ${h.line})`);
5806
- }
5807
- if (summary.headings.length > 30) {
5808
- lines.push(` ... (${summary.headings.length - 30} more headings elided)`);
5809
- }
5810
- }
5811
- if (summary.landmarks.length > 0) {
5812
- lines.push("", "Landmarks:");
5813
- for (const l of summary.landmarks.slice(0, 25)) {
5814
- const idStr = l.id ? `#${displaySafeText(l.id)}` : "";
5815
- const classStr = l.class ? `.${displaySafeText(l.class.trim().split(/\s+/).join("."))}` : "";
5816
- lines.push(` <${displaySafeText(l.tag)}${idStr}${classStr}> (line ${l.line})`);
5817
- }
5818
- }
5819
- if (summary.tables.length > 0) {
5820
- lines.push("", "Tables:");
5821
- for (const t of summary.tables) {
5822
- const idStr = t.id ? `#${displaySafeText(t.id)}` : "";
5823
- lines.push(` <table${idStr}> with ~${t.rows} rows (line ${t.line})`);
5824
- }
5825
- }
5826
- if (summary.forms.length > 0) {
5827
- lines.push("", "Forms:");
5828
- for (const f of summary.forms) {
5829
- const idStr = f.id ? `#${displaySafeText(f.id)}` : "";
5830
- lines.push(` <form${idStr} method="${displaySafeText(f.method || "GET")}" action="${displaySafeText(f.action || "")}"> (line ${f.line})`);
5831
- }
5832
- }
5833
- return lines.join("\n");
5834
- }
5835
- function splitTopLevel(str, delimiter) {
5836
- const results = [];
5837
- let start = 0;
5838
- let inQuote = null;
5839
- let bracketDepth = 0;
5840
- for (let i = 0; i < str.length; i++) {
5841
- const ch = str[i];
5842
- if (inQuote) {
5843
- if (ch === inQuote) inQuote = null;
5844
- } else {
5845
- if (ch === '"' || ch === "'") {
5846
- inQuote = ch;
5847
- } else if (ch === "[") {
5848
- bracketDepth++;
5849
- } else if (ch === "]") {
5850
- if (bracketDepth > 0) bracketDepth--;
5851
- } else if (bracketDepth === 0 && ch === delimiter) {
5852
- results.push(str.slice(start, i));
5853
- start = i + 1;
5854
- }
5855
- }
5856
- }
5857
- results.push(str.slice(start));
5858
- return results;
5859
- }
5860
- function tokenizeSelector(sel) {
5861
- const tokens = [];
5862
- let i = 0;
5863
- const n = sel.length;
5864
- while (i < n) {
5865
- while (i < n && /\s/.test(sel[i])) i++;
5866
- if (i >= n) break;
5867
- if (sel[i] === ">") {
5868
- tokens.push(">");
5869
- i++;
5870
- continue;
5871
- }
5872
- const start = i;
5873
- let inQuote = null;
5874
- let bracketDepth = 0;
5875
- while (i < n) {
5876
- const ch = sel[i];
5877
- if (inQuote) {
5878
- if (ch === inQuote) inQuote = null;
5879
- } else {
5880
- if (ch === '"' || ch === "'") {
5881
- inQuote = ch;
5882
- } else if (ch === "[") {
5883
- bracketDepth++;
5884
- } else if (ch === "]") {
5885
- if (bracketDepth > 0) bracketDepth--;
5886
- } else if (bracketDepth === 0) {
5887
- if (/\s/.test(ch) || ch === ">") {
5888
- break;
5889
- }
5890
- }
5891
- }
5892
- i++;
5893
- }
5894
- if (i > start) {
5895
- tokens.push(sel.slice(start, i));
5896
- }
5897
- }
5898
- return tokens;
5899
- }
5900
- function parseCompoundSelector(sel) {
5901
- const result = {
5902
- ids: [],
5903
- classes: [],
5904
- attributes: []
5905
- };
5906
- let i = 0;
5907
- const n = sel.length;
5908
- if (sel[0] !== "#" && sel[0] !== "." && sel[0] !== "[") {
5909
- const tagMatch = /^([a-zA-Z*][a-zA-Z0-9_-]*)/.exec(sel);
5910
- if (tagMatch) {
5911
- result.tag = tagMatch[1];
5912
- i = tagMatch[0].length;
5913
- }
5914
- }
5915
- while (i < n) {
5916
- const ch = sel[i];
5917
- if (ch === "#") {
5918
- i++;
5919
- const idMatch = /^([a-zA-Z0-9_-]+)/.exec(sel.slice(i));
5920
- if (idMatch) {
5921
- result.ids.push(idMatch[1]);
5922
- i += idMatch[0].length;
5923
- }
5924
- } else if (ch === ".") {
5925
- i++;
5926
- const classMatch = /^([a-zA-Z0-9_-]+)/.exec(sel.slice(i));
5927
- if (classMatch) {
5928
- result.classes.push(classMatch[1]);
5929
- i += classMatch[0].length;
5930
- }
5931
- } else if (ch === "[") {
5932
- let endBracket = -1;
5933
- let inQuote = null;
5934
- for (let j = i + 1; j < n; j++) {
5935
- const c = sel[j];
5936
- if (inQuote) {
5937
- if (c === inQuote) inQuote = null;
5938
- } else {
5939
- if (c === '"' || c === "'") inQuote = c;
5940
- else if (c === "]") {
5941
- endBracket = j;
5942
- break;
5943
- }
5944
- }
5945
- }
5946
- if (endBracket === -1) {
5947
- break;
5948
- }
5949
- const inner = sel.slice(i + 1, endBracket).trim();
5950
- i = endBracket + 1;
5951
- const attrMatch = /^([a-zA-Z0-9_:-]+)(?:\s*([*^$!~]?=)\s*(?:"([^"]*)"|'([^']*)'|([^\s"'\]]+)))?$/.exec(inner);
5952
- if (attrMatch) {
5953
- const attrName = attrMatch[1].toLowerCase();
5954
- const op = attrMatch[2];
5955
- const val = attrMatch[3] ?? attrMatch[4] ?? attrMatch[5];
5956
- result.attributes.push({ name: attrName, op, val });
5957
- }
5958
- } else {
5959
- i++;
5960
- }
5961
- }
5962
- return result;
5963
- }
5964
- function matchesCompoundSelector(node, sel) {
5965
- if (!sel || node.tag === "#root") return false;
5966
- if (sel === "*") return true;
5967
- const parsed = parseCompoundSelector(sel);
5968
- if (parsed.tag && parsed.tag !== "*") {
5969
- if (node.tag.toLowerCase() !== parsed.tag.toLowerCase()) return false;
5970
- }
5971
- for (const id of parsed.ids) {
5972
- if (node.attributes["id"] !== id) return false;
5973
- }
5974
- if (parsed.classes.length > 0) {
5975
- const nodeClasses = (node.attributes["class"] || "").split(/\s+/).filter(Boolean);
5976
- for (const c of parsed.classes) {
5977
- if (!nodeClasses.includes(c)) return false;
5978
- }
5979
- }
5980
- for (const attr of parsed.attributes) {
5981
- const hasAttr = Object.prototype.hasOwnProperty.call(node.attributes, attr.name);
5982
- if (!hasAttr) {
5983
- if (attr.op === "!=") continue;
5984
- return false;
5985
- }
5986
- if (!attr.op) continue;
5987
- const actual = node.attributes[attr.name] || "";
5988
- const val = attr.val ?? "";
5989
- if (attr.op === "=") {
5990
- if (actual !== val) return false;
5991
- } else if (attr.op === "^=") {
5992
- if (!actual.startsWith(val)) return false;
5993
- } else if (attr.op === "$=") {
5994
- if (!actual.endsWith(val)) return false;
5995
- } else if (attr.op === "*=") {
5996
- if (!actual.includes(val)) return false;
5997
- } else if (attr.op === "~=") {
5998
- if (!actual.split(/\s+/).includes(val)) return false;
5999
- } else if (attr.op === "!=") {
6000
- if (actual === val) return false;
6001
- }
6002
- }
6003
- return true;
6004
- }
6005
- function queryHtml(htmlText, selectorStr) {
6006
- const { root } = parseHtml(htmlText);
6007
- let selector = selectorStr.trim();
6008
- let targetAttr;
6009
- const attrAtMatch = /@([a-zA-Z0-9_:-]+)$/.exec(selector);
6010
- if (attrAtMatch) {
6011
- targetAttr = attrAtMatch[1].toLowerCase();
6012
- selector = selector.slice(0, selector.length - attrAtMatch[0].length).trim();
6013
- if (!selector) selector = "*";
6014
- }
6015
- const rawSubSelectors = splitTopLevel(selector, ",").map((s) => s.trim()).filter(Boolean);
6016
- const matchedSet = /* @__PURE__ */ new Set();
6017
- for (const subSel of rawSubSelectors) {
6018
- let collectAll2 = function(node) {
6019
- if (node.tag !== "#root") currentCandidates.push(node);
6020
- for (const c of node.children) collectAll2(c);
6021
- };
6022
- var collectAll = collectAll2;
6023
- const tokens = tokenizeSelector(subSel);
6024
- let currentCandidates = [];
6025
- collectAll2(root);
6026
- let i = 0;
6027
- while (i < tokens.length) {
6028
- const token = tokens[i];
6029
- if (token === ">") {
6030
- const nextTarget = tokens[i + 1];
6031
- if (!nextTarget) break;
6032
- const nextCandidates = [];
6033
- for (const parent of currentCandidates) {
6034
- for (const child of parent.children) {
6035
- if (nextTarget === "*" || matchesCompoundSelector(child, nextTarget)) {
6036
- nextCandidates.push(child);
6037
- }
6038
- }
6039
- }
6040
- currentCandidates = nextCandidates;
6041
- i += 2;
6042
- } else {
6043
- if (i === 0) {
6044
- currentCandidates = currentCandidates.filter((node) => token === "*" || matchesCompoundSelector(node, token));
6045
- } else {
6046
- const nextSet = /* @__PURE__ */ new Set();
6047
- for (const ancestor of currentCandidates) {
6048
- let findDescendants2 = function(node) {
6049
- for (const child of node.children) {
6050
- if (token === "*" || matchesCompoundSelector(child, token)) {
6051
- nextSet.add(child);
6052
- }
6053
- findDescendants2(child);
6054
- }
6055
- };
6056
- var findDescendants = findDescendants2;
6057
- findDescendants2(ancestor);
5916
+ line: tagStartLine,
5917
+ type: "error",
5918
+ message: `Duplicate ID '#${attrVal}' previously defined on line ${prevLine}`,
5919
+ rule: "duplicate-id"
5920
+ });
5921
+ } else {
5922
+ seenIds.set(attrVal, tagStartLine);
5923
+ }
6058
5924
  }
6059
- currentCandidates = Array.from(nextSet);
6060
5925
  }
6061
- i++;
5926
+ const newNode = {
5927
+ tag: tagName,
5928
+ attributes,
5929
+ children: [],
5930
+ parent: currentParent,
5931
+ text: "",
5932
+ rawHtml: tagSlice,
5933
+ startOffset: pos,
5934
+ endOffset: closeBracket + 1,
5935
+ line: tagStartLine,
5936
+ endLine: tagStartLine + sliceLines,
5937
+ isVoid
5938
+ };
5939
+ currentParent.children.push(newNode);
5940
+ if (!isVoid) {
5941
+ currentParent = newNode;
5942
+ }
5943
+ currentLine += sliceLines;
5944
+ pos = closeBracket + 1;
5945
+ continue;
6062
5946
  }
5947
+ issues.push({
5948
+ line: tagStartLine,
5949
+ type: "warning",
5950
+ message: "Unescaped '<' character in text: consider '&lt;'",
5951
+ rule: "unescaped-angle-bracket"
5952
+ });
5953
+ if (currentParent) {
5954
+ currentParent.text += "<";
5955
+ }
5956
+ pos++;
5957
+ continue;
6063
5958
  }
6064
- for (const match of currentCandidates) {
6065
- matchedSet.add(match);
6066
- }
6067
- }
6068
- const elements = Array.from(matchedSet);
6069
- if (targetAttr !== void 0) {
6070
- const attributeValues = [];
6071
- for (const el of elements) {
6072
- if (Object.prototype.hasOwnProperty.call(el.attributes, targetAttr)) {
6073
- attributeValues.push(el.attributes[targetAttr]);
5959
+ const nextBracket = html.indexOf("<", pos);
5960
+ const textChunkEnd = nextBracket !== -1 ? nextBracket : len;
5961
+ const textChunk = html.slice(pos, textChunkEnd);
5962
+ if (textChunk) {
5963
+ if (currentParent) {
5964
+ currentParent.text += textChunk;
6074
5965
  }
5966
+ currentLine += (textChunk.match(/\n/g) || []).length;
6075
5967
  }
6076
- return { elements, attributeValues, fanned: attributeValues.length > 1, sourceHtml: htmlText };
5968
+ pos = textChunkEnd;
6077
5969
  }
6078
- return { elements, fanned: elements.length > 1, sourceHtml: htmlText };
6079
- }
6080
- function extractNodeText(node, sourceHtml) {
6081
- if (RAW_TEXT_TAGS.has(node.tag.toLowerCase())) return "";
6082
- if (sourceHtml && node.startOffset !== void 0 && node.endOffset !== void 0) {
6083
- const raw = sourceHtml.slice(node.startOffset, node.endOffset);
6084
- const clean = raw.replace(/<!--[\s\S]*?-->/g, "").replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
6085
- return clean.replace(/\s+/g, " ").trim();
5970
+ let dangling = currentParent;
5971
+ while (dangling && dangling.tag !== "#root") {
5972
+ if (!HTML_VOID_TAGS.has(dangling.tag.toLowerCase())) {
5973
+ issues.push({
5974
+ line: dangling.line,
5975
+ type: "error",
5976
+ message: `Unclosed tag <${dangling.tag}> at end of document`,
5977
+ rule: "unclosed-tag"
5978
+ });
5979
+ }
5980
+ dangling.endOffset = len;
5981
+ dangling = dangling.parent;
6086
5982
  }
6087
- const parts = [];
6088
- if (node.text.trim()) parts.push(node.text.trim());
6089
- for (const child of node.children) {
6090
- const t = extractNodeText(child);
6091
- if (t) parts.push(t);
5983
+ for (const tag of Object.keys(tagCounts)) {
5984
+ const entry = tagCounts[tag];
5985
+ entry.diff = entry.open - entry.close;
6092
5986
  }
6093
- return parts.join(" ");
5987
+ root.endLine = currentLine;
5988
+ return { root, issues, tagCounts };
6094
5989
  }
6095
- function serializeHtmlNode(node, indent = 0, sourceHtml) {
6096
- if (node.tag === "#root") {
6097
- return node.children.map((c) => serializeHtmlNode(c, indent, sourceHtml)).join("\n");
6098
- }
6099
- if (sourceHtml && node.startOffset !== void 0 && node.endOffset !== void 0) {
6100
- return sourceHtml.slice(node.startOffset, node.endOffset);
6101
- }
6102
- const pad = " ".repeat(indent);
6103
- const attrs = Object.entries(node.attributes).map(([k, v]) => v !== "" ? `${k}="${v.replace(/"/g, "&quot;")}"` : k).join(" ");
6104
- const attrStr = attrs ? ` ${attrs}` : "";
6105
- if (node.isVoid) {
6106
- return `${pad}<${node.tag}${attrStr}>`;
6107
- }
6108
- if (node.children.length === 0) {
6109
- const textTrimmed = node.text.trim();
6110
- if (!textTrimmed) {
6111
- return `${pad}<${node.tag}${attrStr}></${node.tag}>`;
5990
+ function lintHtml(htmlText) {
5991
+ const { issues, tagCounts } = parseHtml(htmlText);
5992
+ const errors = issues.filter((i) => i.type === "error");
5993
+ const warnings = issues.filter((i) => i.type === "warning");
5994
+ return {
5995
+ valid: errors.length === 0,
5996
+ errors,
5997
+ warnings,
5998
+ tagCounts
5999
+ };
6000
+ }
6001
+ function outlineHtml(htmlText) {
6002
+ const { root } = parseHtml(htmlText);
6003
+ let title = null;
6004
+ let doctype = null;
6005
+ const docMatch = /<!doctype\s+(\S[^>]*)>/i.exec(htmlText);
6006
+ if (docMatch) doctype = docMatch[1].trim();
6007
+ let totalElements = 0;
6008
+ const uniqueTags = /* @__PURE__ */ new Set();
6009
+ let maxDepth = 0;
6010
+ const headings = [];
6011
+ const landmarks = [];
6012
+ const tables = [];
6013
+ const forms = [];
6014
+ let scripts = 0;
6015
+ let styles = 0;
6016
+ const landmarkTags = /* @__PURE__ */ new Set(["header", "nav", "main", "article", "section", "footer", "aside"]);
6017
+ function walk(node, depth) {
6018
+ if (node.tag !== "#root") {
6019
+ totalElements++;
6020
+ uniqueTags.add(node.tag);
6021
+ if (depth > maxDepth) maxDepth = depth;
6022
+ const tag = node.tag.toLowerCase();
6023
+ if (tag === "title" && !title) {
6024
+ title = node.text.trim();
6025
+ } else if (/^h[1-6]$/.test(tag)) {
6026
+ const level = parseInt(tag[1], 10);
6027
+ headings.push({ level, text: node.text.replace(/\s+/g, " ").trim().slice(0, 80), line: node.line });
6028
+ } else if (landmarkTags.has(tag)) {
6029
+ landmarks.push({
6030
+ tag,
6031
+ id: node.attributes["id"],
6032
+ class: node.attributes["class"],
6033
+ line: node.line
6034
+ });
6035
+ } else if (tag === "table") {
6036
+ const rows = node.children.flatMap((c) => c.tag === "tbody" || c.tag === "thead" ? c.children : [c]).filter((c) => c.tag === "tr").length;
6037
+ tables.push({ id: node.attributes["id"], class: node.attributes["class"], rows, line: node.line });
6038
+ } else if (tag === "form") {
6039
+ forms.push({ id: node.attributes["id"], action: node.attributes["action"], method: node.attributes["method"], line: node.line });
6040
+ } else if (tag === "script") {
6041
+ scripts++;
6042
+ } else if (tag === "style") {
6043
+ styles++;
6044
+ }
6112
6045
  }
6113
- if (!textTrimmed.includes("\n") && textTrimmed.length < 80) {
6114
- return `${pad}<${node.tag}${attrStr}>${textTrimmed}</${node.tag}>`;
6046
+ for (const child of node.children) {
6047
+ walk(child, depth + 1);
6115
6048
  }
6116
- return `${pad}<${node.tag}${attrStr}>
6117
- ${pad} ${textTrimmed}
6118
- ${pad}</${node.tag}>`;
6119
6049
  }
6120
- const inner = node.children.map((c) => serializeHtmlNode(c, indent + 1, sourceHtml)).join("\n");
6121
- return `${pad}<${node.tag}${attrStr}>
6122
- ${inner}
6123
- ${pad}</${node.tag}>`;
6124
- }
6125
-
6126
- // src/json_query.ts
6127
- init_define_import_meta_env();
6128
- function jsonType(value) {
6129
- if (value === null) return "null";
6130
- if (Array.isArray(value)) return "array";
6131
- return typeof value;
6132
- }
6133
- function fieldSummary(name, value) {
6134
- const type = jsonType(value);
6135
- if (type === "array") return { name, type, size: value.length };
6136
- if (type === "object") return { name, type, size: Object.keys(value).length };
6137
- return { name, type };
6050
+ walk(root, 0);
6051
+ return {
6052
+ title,
6053
+ doctype,
6054
+ totalElements,
6055
+ uniqueTags: Array.from(uniqueTags).sort(),
6056
+ maxDepth,
6057
+ headings,
6058
+ landmarks,
6059
+ tables,
6060
+ forms,
6061
+ scripts,
6062
+ styles
6063
+ };
6138
6064
  }
6139
- function outlineJson(data, opts = {}) {
6140
- const sampleSize = opts.sampleSize ?? 5;
6141
- const type = jsonType(data);
6142
- if (type === "array") {
6143
- const arr = data;
6144
- const sample = arr.slice(0, sampleSize);
6145
- const elementTypes = new Set(sample.map(jsonType));
6146
- const elementType = elementTypes.size === 0 ? "unknown" : elementTypes.size === 1 ? [...elementTypes][0] : "mixed";
6147
- const result = { kind: "array", length: arr.length, elementType };
6148
- if (elementType === "object") {
6149
- const objects = sample;
6150
- const keySets = objects.map((el) => Object.keys(el));
6151
- const allKeys = [...new Set(keySets.flat())];
6152
- result.sampleKeys = allKeys.map((k) => {
6153
- const owner = objects.find((el) => Object.prototype.hasOwnProperty.call(el, k));
6154
- return fieldSummary(k, owner?.[k]);
6155
- });
6156
- const firstKeySet = keySets[0] ?? [];
6157
- result.heterogeneous = keySets.some((ks) => ks.length !== firstKeySet.length || !ks.every((k) => firstKeySet.includes(k)));
6065
+ function formatHtmlOutline(summary) {
6066
+ const lines = [];
6067
+ lines.push(`HTML Document (${summary.totalElements} elements, ${summary.uniqueTags.length} unique tags, max depth ${summary.maxDepth})`);
6068
+ if (summary.title) lines.push(`Title: "${displaySafeText(summary.title)}"`);
6069
+ if (summary.doctype) lines.push(`DOCTYPE: ${displaySafeText(summary.doctype)}`);
6070
+ lines.push(`Assets: ${summary.scripts} scripts, ${summary.styles} stylesheets`);
6071
+ if (summary.headings.length > 0) {
6072
+ lines.push("", "Headings:");
6073
+ for (const h of summary.headings.slice(0, 30)) {
6074
+ lines.push(` ${" ".repeat(h.level - 1)}[h${h.level}] ${displaySafeText(h.text)} (line ${h.line})`);
6075
+ }
6076
+ if (summary.headings.length > 30) {
6077
+ lines.push(` ... (${summary.headings.length - 30} more headings elided)`);
6158
6078
  }
6159
- return result;
6160
6079
  }
6161
- if (type === "object") {
6162
- const obj = data;
6163
- return { kind: "object", fields: Object.keys(obj).map((k) => fieldSummary(k, obj[k])) };
6080
+ if (summary.landmarks.length > 0) {
6081
+ lines.push("", "Landmarks:");
6082
+ for (const l of summary.landmarks.slice(0, 25)) {
6083
+ const idStr = l.id ? `#${displaySafeText(l.id)}` : "";
6084
+ const classStr = l.class ? `.${displaySafeText(l.class.trim().split(/\s+/).join("."))}` : "";
6085
+ lines.push(` <${displaySafeText(l.tag)}${idStr}${classStr}> (line ${l.line})`);
6086
+ }
6164
6087
  }
6165
- return { kind: "primitive", type };
6166
- }
6167
- function formatJsonOutline(outline) {
6168
- if (outline.kind === "primitive") return `(scalar ${outline.type})`;
6169
- if (outline.kind === "object") {
6170
- if (outline.fields.length === 0) return "(empty object)";
6171
- return outline.fields.map((f) => `${displaySafeText(f.name)}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`).join("\n");
6088
+ if (summary.tables.length > 0) {
6089
+ lines.push("", "Tables:");
6090
+ for (const t of summary.tables) {
6091
+ const idStr = t.id ? `#${displaySafeText(t.id)}` : "";
6092
+ lines.push(` <table${idStr}> with ~${t.rows} rows (line ${t.line})`);
6093
+ }
6172
6094
  }
6173
- const lines = [`array of ${outline.length} element${outline.length === 1 ? "" : "s"} (${outline.elementType})`];
6174
- if (outline.sampleKeys !== void 0) {
6175
- lines.push(outline.heterogeneous === true ? "keys (from first elements, shape varies across sample):" : "keys (from first elements):");
6176
- for (const f of outline.sampleKeys) {
6177
- lines.push(` ${displaySafeText(f.name)}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`);
6095
+ if (summary.forms.length > 0) {
6096
+ lines.push("", "Forms:");
6097
+ for (const f of summary.forms) {
6098
+ const idStr = f.id ? `#${displaySafeText(f.id)}` : "";
6099
+ lines.push(` <form${idStr} method="${displaySafeText(f.method || "GET")}" action="${displaySafeText(f.action || "")}"> (line ${f.line})`);
6178
6100
  }
6179
6101
  }
6180
6102
  return lines.join("\n");
6181
6103
  }
6182
- var MAX_RECURSIVE_NODES = 5e4;
6183
- var MAX_RECURSIVE_DEPTH = 100;
6184
- function collectRecursiveKey(root, keyName, out, budget) {
6185
- const seen = /* @__PURE__ */ new Set();
6186
- function walk(val, depth) {
6187
- if (depth > MAX_RECURSIVE_DEPTH) {
6188
- budget.exhausted = true;
6189
- return;
6190
- }
6191
- if (val === null || typeof val !== "object") return;
6192
- if (seen.has(val)) return;
6193
- seen.add(val);
6194
- if (budget.nodesLeft <= 0) {
6195
- budget.exhausted = true;
6196
- return;
6197
- }
6198
- budget.nodesLeft--;
6199
- if (Array.isArray(val)) {
6200
- for (let i = 0; i < val.length; i++) {
6201
- walk(val[i], depth + 1);
6202
- if (budget.nodesLeft <= 0) {
6203
- if (i < val.length - 1) budget.exhausted = true;
6204
- return;
6205
- }
6206
- }
6104
+ function splitTopLevel(str, delimiter) {
6105
+ const results = [];
6106
+ let start = 0;
6107
+ let inQuote = null;
6108
+ let bracketDepth = 0;
6109
+ for (let i = 0; i < str.length; i++) {
6110
+ const ch = str[i];
6111
+ if (inQuote) {
6112
+ if (ch === inQuote) inQuote = null;
6207
6113
  } else {
6208
- const obj = val;
6209
- if (Object.prototype.hasOwnProperty.call(obj, keyName)) {
6210
- out.push(obj[keyName]);
6211
- }
6212
- const keys = Object.keys(obj);
6213
- for (let i = 0; i < keys.length; i++) {
6214
- walk(obj[keys[i]], depth + 1);
6215
- if (budget.nodesLeft <= 0) {
6216
- if (i < keys.length - 1) budget.exhausted = true;
6217
- return;
6218
- }
6114
+ if (ch === '"' || ch === "'") {
6115
+ inQuote = ch;
6116
+ } else if (ch === "[") {
6117
+ bracketDepth++;
6118
+ } else if (ch === "]") {
6119
+ if (bracketDepth > 0) bracketDepth--;
6120
+ } else if (bracketDepth === 0 && ch === delimiter) {
6121
+ results.push(str.slice(start, i));
6122
+ start = i + 1;
6219
6123
  }
6220
6124
  }
6221
6125
  }
6222
- walk(root, 0);
6223
- }
6224
- function getNestedField(obj, fieldPath) {
6225
- if (obj === null || typeof obj !== "object") return void 0;
6226
- if (Object.prototype.hasOwnProperty.call(obj, fieldPath)) {
6227
- return obj[fieldPath];
6228
- }
6229
- const parts = fieldPath.split(".");
6230
- let cur = obj;
6231
- for (const part of parts) {
6232
- if (cur === null || typeof cur !== "object") return void 0;
6233
- if (!Object.prototype.hasOwnProperty.call(cur, part)) return void 0;
6234
- cur = cur[part];
6235
- }
6236
- return cur;
6126
+ results.push(str.slice(start));
6127
+ return results;
6237
6128
  }
6238
- function parseJsonPath(spec) {
6239
- const ops = [];
6240
- const n = spec.length;
6129
+ function tokenizeSelector(sel) {
6130
+ const tokens = [];
6241
6131
  let i = 0;
6132
+ const n = sel.length;
6242
6133
  while (i < n) {
6243
- if (spec[i] === "." && i + 1 < n && spec[i + 1] === ".") {
6244
- i += 2;
6245
- let j2 = i;
6246
- while (j2 < n && spec[j2] !== "." && spec[j2] !== "[") j2++;
6247
- if (j2 === i) throw new Error(`invalid path spec: expected property name after '..' in '${spec}'`);
6248
- ops.push({ kind: "recursive_key", name: spec.slice(i, j2) });
6249
- i = j2;
6250
- continue;
6251
- }
6252
- const ch = spec[i];
6253
- if (ch === ".") {
6134
+ while (i < n && /\s/.test(sel[i])) i++;
6135
+ if (i >= n) break;
6136
+ if (sel[i] === ">") {
6137
+ tokens.push(">");
6254
6138
  i++;
6255
6139
  continue;
6256
6140
  }
6257
- if (ch === "[") {
6258
- let inQuote = null;
6259
- let close = -1;
6260
- for (let k = i + 1; k < n; k++) {
6261
- const c = spec[k];
6262
- if (inQuote !== null) {
6263
- if (c === "\\") {
6264
- k++;
6265
- continue;
6266
- }
6267
- if (c === inQuote) {
6268
- inQuote = null;
6269
- }
6270
- } else if (c === '"' || c === "'") {
6271
- inQuote = c;
6272
- } else if (c === "]") {
6273
- close = k;
6274
- break;
6275
- }
6276
- }
6277
- if (close === -1) throw new Error(`invalid path spec: unterminated '[' in '${spec}'`);
6278
- const inner = spec.slice(i + 1, close);
6279
- if (inner === "*") {
6280
- ops.push({ kind: "wildcard" });
6281
- } else if (/^-?\d+$/.test(inner)) {
6282
- ops.push({ kind: "index", index: Number(inner) });
6141
+ const start = i;
6142
+ let inQuote = null;
6143
+ let bracketDepth = 0;
6144
+ while (i < n) {
6145
+ const ch = sel[i];
6146
+ if (inQuote) {
6147
+ if (ch === inQuote) inQuote = null;
6283
6148
  } else {
6284
- let expr = inner.trim();
6285
- if (expr.startsWith("?(") && expr.endsWith(")")) {
6286
- expr = expr.slice(2, -1).trim();
6287
- }
6288
- if (expr.startsWith("@.")) {
6289
- expr = expr.slice(2).trim();
6290
- }
6291
- const m = /^([^=]+)==?(.*)$/.exec(expr);
6292
- if (!m) {
6293
- const bareOperator = /[<>]/.exec(expr);
6294
- if (bareOperator !== null) {
6295
- throw new Error(`unsupported comparison operator '${bareOperator[0]}' in '[${inner}]' of path spec '${spec}': this grammar filters by equality only ([field=value])`);
6149
+ if (ch === '"' || ch === "'") {
6150
+ inQuote = ch;
6151
+ } else if (ch === "[") {
6152
+ bracketDepth++;
6153
+ } else if (ch === "]") {
6154
+ if (bracketDepth > 0) bracketDepth--;
6155
+ } else if (bracketDepth === 0) {
6156
+ if (/\s/.test(ch) || ch === ">") {
6157
+ break;
6296
6158
  }
6297
- throw new Error(`invalid bracket expression '[${inner}]' in path spec: '${spec}' (expected [n], [*], or [field=value])`);
6298
- }
6299
- let field = m[1].trim();
6300
- if (field.startsWith("@.")) field = field.slice(2).trim();
6301
- const swallowedOperator = /[!<>]$/.exec(field);
6302
- if (swallowedOperator !== null) {
6303
- throw new Error(`unsupported comparison operator '${swallowedOperator[0]}=' in '[${inner}]' of path spec '${spec}': this grammar filters by equality only ([field=value])`);
6304
6159
  }
6305
- let rawVal = m[2].trim();
6306
- if (rawVal.startsWith('"') && rawVal.endsWith('"') || rawVal.startsWith("'") && rawVal.endsWith("'")) {
6307
- if (rawVal.length >= 2) {
6308
- rawVal = rawVal.slice(1, -1).replace(/\\(["'\\])/g, "$1");
6160
+ }
6161
+ i++;
6162
+ }
6163
+ if (i > start) {
6164
+ tokens.push(sel.slice(start, i));
6165
+ }
6166
+ }
6167
+ return tokens;
6168
+ }
6169
+ function parseCompoundSelector(sel) {
6170
+ const result = {
6171
+ ids: [],
6172
+ classes: [],
6173
+ attributes: []
6174
+ };
6175
+ let i = 0;
6176
+ const n = sel.length;
6177
+ if (sel[0] !== "#" && sel[0] !== "." && sel[0] !== "[") {
6178
+ const tagMatch = /^([a-zA-Z*][a-zA-Z0-9_-]*)/.exec(sel);
6179
+ if (tagMatch) {
6180
+ result.tag = tagMatch[1];
6181
+ i = tagMatch[0].length;
6182
+ }
6183
+ }
6184
+ while (i < n) {
6185
+ const ch = sel[i];
6186
+ if (ch === "#") {
6187
+ i++;
6188
+ const idMatch = /^([a-zA-Z0-9_-]+)/.exec(sel.slice(i));
6189
+ if (idMatch) {
6190
+ result.ids.push(idMatch[1]);
6191
+ i += idMatch[0].length;
6192
+ }
6193
+ } else if (ch === ".") {
6194
+ i++;
6195
+ const classMatch = /^([a-zA-Z0-9_-]+)/.exec(sel.slice(i));
6196
+ if (classMatch) {
6197
+ result.classes.push(classMatch[1]);
6198
+ i += classMatch[0].length;
6199
+ }
6200
+ } else if (ch === "[") {
6201
+ let endBracket = -1;
6202
+ let inQuote = null;
6203
+ for (let j = i + 1; j < n; j++) {
6204
+ const c = sel[j];
6205
+ if (inQuote) {
6206
+ if (c === inQuote) inQuote = null;
6207
+ } else {
6208
+ if (c === '"' || c === "'") inQuote = c;
6209
+ else if (c === "]") {
6210
+ endBracket = j;
6211
+ break;
6309
6212
  }
6310
6213
  }
6311
- ops.push({ kind: "filter", field, value: rawVal });
6312
6214
  }
6313
- i = close + 1;
6314
- continue;
6215
+ if (endBracket === -1) {
6216
+ break;
6217
+ }
6218
+ const inner = sel.slice(i + 1, endBracket).trim();
6219
+ i = endBracket + 1;
6220
+ const attrMatch = /^([a-zA-Z0-9_:-]+)(?:\s*([*^$!~]?=)\s*(?:"([^"]*)"|'([^']*)'|([^\s"'\]]+)))?$/.exec(inner);
6221
+ if (attrMatch) {
6222
+ const attrName = attrMatch[1].toLowerCase();
6223
+ const op = attrMatch[2];
6224
+ const val = attrMatch[3] ?? attrMatch[4] ?? attrMatch[5];
6225
+ result.attributes.push({ name: attrName, op, val });
6226
+ }
6227
+ } else {
6228
+ i++;
6229
+ }
6230
+ }
6231
+ return result;
6232
+ }
6233
+ function matchesCompoundSelector(node, sel) {
6234
+ if (!sel || node.tag === "#root") return false;
6235
+ if (sel === "*") return true;
6236
+ const parsed = parseCompoundSelector(sel);
6237
+ if (parsed.tag && parsed.tag !== "*") {
6238
+ if (node.tag.toLowerCase() !== parsed.tag.toLowerCase()) return false;
6239
+ }
6240
+ for (const id of parsed.ids) {
6241
+ if (node.attributes["id"] !== id) return false;
6242
+ }
6243
+ if (parsed.classes.length > 0) {
6244
+ const nodeClasses = (node.attributes["class"] || "").split(/\s+/).filter(Boolean);
6245
+ for (const c of parsed.classes) {
6246
+ if (!nodeClasses.includes(c)) return false;
6315
6247
  }
6316
- let j = i;
6317
- while (j < n && spec[j] !== "." && spec[j] !== "[") j++;
6318
- if (j === i) throw new Error(`invalid path spec: '${spec}'`);
6319
- ops.push({ kind: "key", name: spec.slice(i, j) });
6320
- i = j;
6321
6248
  }
6322
- return ops;
6249
+ for (const attr of parsed.attributes) {
6250
+ const hasAttr = Object.prototype.hasOwnProperty.call(node.attributes, attr.name);
6251
+ if (!hasAttr) {
6252
+ if (attr.op === "!=") continue;
6253
+ return false;
6254
+ }
6255
+ if (!attr.op) continue;
6256
+ const actual = node.attributes[attr.name] || "";
6257
+ const val = attr.val ?? "";
6258
+ if (attr.op === "=") {
6259
+ if (actual !== val) return false;
6260
+ } else if (attr.op === "^=") {
6261
+ if (!actual.startsWith(val)) return false;
6262
+ } else if (attr.op === "$=") {
6263
+ if (!actual.endsWith(val)) return false;
6264
+ } else if (attr.op === "*=") {
6265
+ if (!actual.includes(val)) return false;
6266
+ } else if (attr.op === "~=") {
6267
+ if (!actual.split(/\s+/).includes(val)) return false;
6268
+ } else if (attr.op === "!=") {
6269
+ if (actual === val) return false;
6270
+ }
6271
+ }
6272
+ return true;
6323
6273
  }
6324
- function evalJsonPath(data, ops) {
6325
- let current = [data];
6326
- let fanned = false;
6327
- const budget = { nodesLeft: MAX_RECURSIVE_NODES, exhausted: false };
6328
- for (const op of ops) {
6329
- const next = [];
6330
- for (const item of current) {
6331
- if (op.kind === "key") {
6332
- if (typeof item === "object" && item !== null && !Array.isArray(item) && Object.prototype.hasOwnProperty.call(item, op.name)) {
6333
- next.push(item[op.name]);
6334
- } else if (!fanned) {
6335
- throw new Error(`path not found: key '${op.name}' does not exist on ${jsonType(item)} value`);
6336
- }
6337
- } else if (op.kind === "recursive_key") {
6338
- fanned = true;
6339
- collectRecursiveKey(item, op.name, next, budget);
6340
- } else if (op.kind === "index") {
6341
- if (Array.isArray(item)) {
6342
- const idx = op.index < 0 ? item.length + op.index : op.index;
6343
- if (idx >= 0 && idx < item.length) {
6344
- next.push(item[idx]);
6345
- } else if (!fanned) {
6346
- throw new Error(`path not found: index [${op.index}] out of range (length ${item.length})`);
6274
+ function queryHtml(htmlText, selectorStr) {
6275
+ const { root } = parseHtml(htmlText);
6276
+ let selector = selectorStr.trim();
6277
+ let targetAttr;
6278
+ const attrAtMatch = /@([a-zA-Z0-9_:-]+)$/.exec(selector);
6279
+ if (attrAtMatch) {
6280
+ targetAttr = attrAtMatch[1].toLowerCase();
6281
+ selector = selector.slice(0, selector.length - attrAtMatch[0].length).trim();
6282
+ if (!selector) selector = "*";
6283
+ }
6284
+ const rawSubSelectors = splitTopLevel(selector, ",").map((s) => s.trim()).filter(Boolean);
6285
+ const matchedSet = /* @__PURE__ */ new Set();
6286
+ for (const subSel of rawSubSelectors) {
6287
+ let collectAll2 = function(node) {
6288
+ if (node.tag !== "#root") currentCandidates.push(node);
6289
+ for (const c of node.children) collectAll2(c);
6290
+ };
6291
+ var collectAll = collectAll2;
6292
+ const tokens = tokenizeSelector(subSel);
6293
+ let currentCandidates = [];
6294
+ collectAll2(root);
6295
+ let i = 0;
6296
+ while (i < tokens.length) {
6297
+ const token = tokens[i];
6298
+ if (token === ">") {
6299
+ const nextTarget = tokens[i + 1];
6300
+ if (!nextTarget) break;
6301
+ const nextCandidates = [];
6302
+ for (const parent of currentCandidates) {
6303
+ for (const child of parent.children) {
6304
+ if (nextTarget === "*" || matchesCompoundSelector(child, nextTarget)) {
6305
+ nextCandidates.push(child);
6306
+ }
6347
6307
  }
6348
- } else if (!fanned) {
6349
- throw new Error(`path not found: index [${op.index}] on non-array ${jsonType(item)} value`);
6350
- }
6351
- } else if (op.kind === "wildcard") {
6352
- fanned = true;
6353
- if (Array.isArray(item)) {
6354
- pushAll(next, item);
6355
- } else if (typeof item === "object" && item !== null) {
6356
- pushAll(next, Object.values(item));
6357
6308
  }
6309
+ currentCandidates = nextCandidates;
6310
+ i += 2;
6358
6311
  } else {
6359
- fanned = true;
6360
- if (Array.isArray(item)) {
6361
- for (const el of item) {
6362
- if (typeof el === "object" && el !== null && !Array.isArray(el)) {
6363
- const val = getNestedField(el, op.field);
6364
- if (val !== void 0 && String(val) === op.value) {
6365
- next.push(el);
6312
+ if (i === 0) {
6313
+ currentCandidates = currentCandidates.filter((node) => token === "*" || matchesCompoundSelector(node, token));
6314
+ } else {
6315
+ const nextSet = /* @__PURE__ */ new Set();
6316
+ for (const ancestor of currentCandidates) {
6317
+ let findDescendants2 = function(node) {
6318
+ for (const child of node.children) {
6319
+ if (token === "*" || matchesCompoundSelector(child, token)) {
6320
+ nextSet.add(child);
6321
+ }
6322
+ findDescendants2(child);
6366
6323
  }
6367
- }
6324
+ };
6325
+ var findDescendants = findDescendants2;
6326
+ findDescendants2(ancestor);
6368
6327
  }
6328
+ currentCandidates = Array.from(nextSet);
6369
6329
  }
6330
+ i++;
6370
6331
  }
6371
6332
  }
6372
- current = next;
6333
+ for (const match of currentCandidates) {
6334
+ matchedSet.add(match);
6335
+ }
6373
6336
  }
6374
- return { fanned, items: current, truncated: budget.exhausted };
6337
+ const elements = Array.from(matchedSet);
6338
+ if (targetAttr !== void 0) {
6339
+ const attributeValues = [];
6340
+ for (const el of elements) {
6341
+ if (Object.prototype.hasOwnProperty.call(el.attributes, targetAttr)) {
6342
+ attributeValues.push(el.attributes[targetAttr]);
6343
+ }
6344
+ }
6345
+ return { elements, attributeValues, fanned: attributeValues.length > 1, sourceHtml: htmlText };
6346
+ }
6347
+ return { elements, fanned: elements.length > 1, sourceHtml: htmlText };
6375
6348
  }
6376
- function queryJson(data, spec) {
6377
- return evalJsonPath(data, parseJsonPath(spec));
6349
+ function extractNodeText(node, sourceHtml) {
6350
+ if (RAW_TEXT_TAGS.has(node.tag.toLowerCase())) return "";
6351
+ if (sourceHtml && node.startOffset !== void 0 && node.endOffset !== void 0) {
6352
+ const raw = sourceHtml.slice(node.startOffset, node.endOffset);
6353
+ const clean = raw.replace(/<!--[\s\S]*?-->/g, "").replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
6354
+ return clean.replace(/\s+/g, " ").trim();
6355
+ }
6356
+ const parts = [];
6357
+ if (node.text.trim()) parts.push(node.text.trim());
6358
+ for (const child of node.children) {
6359
+ const t = extractNodeText(child);
6360
+ if (t) parts.push(t);
6361
+ }
6362
+ return parts.join(" ");
6363
+ }
6364
+ function serializeHtmlNode(node, indent = 0, sourceHtml) {
6365
+ if (node.tag === "#root") {
6366
+ return node.children.map((c) => serializeHtmlNode(c, indent, sourceHtml)).join("\n");
6367
+ }
6368
+ if (sourceHtml && node.startOffset !== void 0 && node.endOffset !== void 0) {
6369
+ return sourceHtml.slice(node.startOffset, node.endOffset);
6370
+ }
6371
+ const pad = " ".repeat(indent);
6372
+ const attrs = Object.entries(node.attributes).map(([k, v]) => v !== "" ? `${k}="${v.replace(/"/g, "&quot;")}"` : k).join(" ");
6373
+ const attrStr = attrs ? ` ${attrs}` : "";
6374
+ if (node.isVoid) {
6375
+ return `${pad}<${node.tag}${attrStr}>`;
6376
+ }
6377
+ if (node.children.length === 0) {
6378
+ const textTrimmed = node.text.trim();
6379
+ if (!textTrimmed) {
6380
+ return `${pad}<${node.tag}${attrStr}></${node.tag}>`;
6381
+ }
6382
+ if (!textTrimmed.includes("\n") && textTrimmed.length < 80) {
6383
+ return `${pad}<${node.tag}${attrStr}>${textTrimmed}</${node.tag}>`;
6384
+ }
6385
+ return `${pad}<${node.tag}${attrStr}>
6386
+ ${pad} ${textTrimmed}
6387
+ ${pad}</${node.tag}>`;
6388
+ }
6389
+ const inner = node.children.map((c) => serializeHtmlNode(c, indent + 1, sourceHtml)).join("\n");
6390
+ return `${pad}<${node.tag}${attrStr}>
6391
+ ${inner}
6392
+ ${pad}</${node.tag}>`;
6378
6393
  }
6379
6394
 
6380
6395
  // src/openapi_query.ts
@@ -6572,6 +6587,7 @@ function parseXmlTree(xmlText) {
6572
6587
  const namespaces = {};
6573
6588
  let totalElements = 0;
6574
6589
  const doctypeMatch = new RegExp(
6590
+ // eslint-disable-next-line regexp/no-super-linear-backtracking
6575
6591
  `<!DOCTYPE\\s+((?:[^>[\\]"']|"[^"]*"|'[^']*'|\\[[\\s\\S]*?\\])*)\\s*>`,
6576
6592
  "i"
6577
6593
  ).exec(text);
@@ -6594,7 +6610,7 @@ function parseXmlTree(xmlText) {
6594
6610
  return attrs;
6595
6611
  }
6596
6612
  const tagRegex = new RegExp(
6597
- // eslint-disable-next-line no-misleading-character-class
6613
+ // eslint-disable-next-line no-misleading-character-class, regexp/no-super-linear-backtracking
6598
6614
  `<(\\/)?(${XML_NAME})(${XML_ATTR_REGION})(\\/)?>|<!--[\\s\\S]*?-->|<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>|<\\?[\\s\\S]*?\\?>|${XML_DOCTYPE_TOKEN}`,
6599
6615
  "gi"
6600
6616
  );
@@ -6632,6 +6648,13 @@ function parseXmlTree(xmlText) {
6632
6648
  lineScan++;
6633
6649
  }
6634
6650
  const line = lineOffset + 1;
6651
+ let endScan = match.index;
6652
+ let endLineOffset = lineOffset;
6653
+ while (endScan < tagRegex.lastIndex) {
6654
+ if (text.charCodeAt(endScan) === 10) endLineOffset++;
6655
+ endScan++;
6656
+ }
6657
+ const tagEndLine = endLineOffset + 1;
6635
6658
  if (isClosing) {
6636
6659
  if (stack.length > 0) {
6637
6660
  let popIdx = stack.length - 1;
@@ -6640,7 +6663,8 @@ function parseXmlTree(xmlText) {
6640
6663
  }
6641
6664
  if (popIdx >= 0) {
6642
6665
  while (stack.length > popIdx) {
6643
- stack.pop();
6666
+ const popped = stack.pop();
6667
+ popped.lineEnd = tagEndLine;
6644
6668
  }
6645
6669
  }
6646
6670
  }
@@ -6652,7 +6676,8 @@ function parseXmlTree(xmlText) {
6652
6676
  attributes: attrs,
6653
6677
  children: [],
6654
6678
  text: "",
6655
- line
6679
+ line,
6680
+ lineEnd: isSelfClosing ? tagEndLine : line
6656
6681
  };
6657
6682
  if (stack.length === 0) {
6658
6683
  if (!rootNode) rootNode = newNode;
@@ -6665,6 +6690,12 @@ function parseXmlTree(xmlText) {
6665
6690
  }
6666
6691
  lastIndex = tagRegex.lastIndex;
6667
6692
  }
6693
+ while (stack.length > 0) {
6694
+ const unclosed = stack.pop();
6695
+ if (!unclosed.lineEnd || unclosed.lineEnd < unclosed.line) {
6696
+ unclosed.lineEnd = unclosed.line;
6697
+ }
6698
+ }
6668
6699
  return { root: rootNode, namespaces, doctype, totalElements };
6669
6700
  }
6670
6701
  function outlineXml(xmlText, opts = {}) {
@@ -6764,6 +6795,206 @@ function formatXmlOutline(summary) {
6764
6795
  renderNode(summary.tree, 0);
6765
6796
  return lines.join("\n");
6766
6797
  }
6798
+ function getLocalName(tag) {
6799
+ const idx = tag.indexOf(":");
6800
+ return idx === -1 ? tag : tag.slice(idx + 1);
6801
+ }
6802
+ function matchTag(nodeTag, targetTag) {
6803
+ if (targetTag === "*" || targetTag === "") return true;
6804
+ if (nodeTag.toLowerCase() === targetTag.toLowerCase()) return true;
6805
+ if (!targetTag.includes(":")) {
6806
+ return getLocalName(nodeTag).toLowerCase() === targetTag.toLowerCase();
6807
+ }
6808
+ return false;
6809
+ }
6810
+ function getAttrValue(node, targetAttr) {
6811
+ const clean = targetAttr.startsWith("@") ? targetAttr.slice(1) : targetAttr;
6812
+ if (clean === "*") {
6813
+ const vals = Object.values(node.attributes);
6814
+ return vals.length > 0 ? vals[0] : void 0;
6815
+ }
6816
+ if (node.attributes[clean] !== void 0) return node.attributes[clean];
6817
+ const lowerClean = clean.toLowerCase();
6818
+ for (const [k, v] of Object.entries(node.attributes)) {
6819
+ if (k.toLowerCase() === lowerClean) return v;
6820
+ }
6821
+ if (!clean.includes(":")) {
6822
+ for (const [k, v] of Object.entries(node.attributes)) {
6823
+ if (getLocalName(k).toLowerCase() === lowerClean) return v;
6824
+ }
6825
+ }
6826
+ return void 0;
6827
+ }
6828
+ function splitTopLevel2(str, delimiter) {
6829
+ const parts = [];
6830
+ let current = "";
6831
+ let quote = null;
6832
+ let parenDepth = 0;
6833
+ for (let i = 0; i < str.length; i++) {
6834
+ const ch = str[i];
6835
+ if (!quote && (ch === '"' || ch === "'")) {
6836
+ quote = ch;
6837
+ current += ch;
6838
+ } else if (quote && ch === quote) {
6839
+ quote = null;
6840
+ current += ch;
6841
+ } else if (!quote && ch === "(") {
6842
+ parenDepth++;
6843
+ current += ch;
6844
+ } else if (!quote && ch === ")") {
6845
+ if (parenDepth > 0) parenDepth--;
6846
+ current += ch;
6847
+ } else if (!quote && parenDepth === 0 && str.startsWith(delimiter, i)) {
6848
+ parts.push(current.trim());
6849
+ current = "";
6850
+ i += delimiter.length - 1;
6851
+ } else {
6852
+ current += ch;
6853
+ }
6854
+ }
6855
+ if (current.trim()) parts.push(current.trim());
6856
+ return parts;
6857
+ }
6858
+ function parseSinglePredicate(predStr) {
6859
+ const s = predStr.trim();
6860
+ if (!s) return null;
6861
+ const orParts = splitTopLevel2(s, " or ");
6862
+ if (orParts.length > 1) {
6863
+ const predicates = orParts.map(parseSinglePredicate);
6864
+ if (predicates.some((p) => p === null)) return null;
6865
+ return { kind: "or", predicates };
6866
+ }
6867
+ const andParts = splitTopLevel2(s, " and ");
6868
+ if (andParts.length > 1) {
6869
+ const predicates = andParts.map(parseSinglePredicate);
6870
+ if (predicates.some((p) => p === null)) return null;
6871
+ return { kind: "and", predicates };
6872
+ }
6873
+ if (/^-?\d+$/.test(s)) {
6874
+ const n = parseInt(s, 10);
6875
+ return { kind: "index", index: n };
6876
+ }
6877
+ if (s === "last()") {
6878
+ return { kind: "index", index: -1 };
6879
+ }
6880
+ if (s === "*") {
6881
+ return { kind: "all" };
6882
+ }
6883
+ const localMatch = /^local-name\(\)\s*(!?=)\s*(?:"([^"]*)"|'([^']*)'|([^\s"']+))\s*$/i.exec(s);
6884
+ if (localMatch) {
6885
+ const val = localMatch[2] !== void 0 ? localMatch[2] : localMatch[3] !== void 0 ? localMatch[3] : localMatch[4] ?? "";
6886
+ return {
6887
+ kind: "localNameEquals",
6888
+ value: val,
6889
+ ...localMatch[1] === "!=" ? { notEqual: true } : {}
6890
+ };
6891
+ }
6892
+ const containsAttrMatch = /^contains\(\s*(@[a-zA-Z0-9_:.\\-]+|\*)\s*,\s*(?:"([^"]*)"|'([^']*)')\s*\)$/i.exec(s);
6893
+ if (containsAttrMatch) {
6894
+ const attrName = containsAttrMatch[1].replace(/^@/, "");
6895
+ return { kind: "attrContains", name: attrName, value: containsAttrMatch[2] ?? containsAttrMatch[3] };
6896
+ }
6897
+ const containsTextMatch = /^contains\(\s*(?:text\(\)|\.)\s*,\s*(?:"([^"]*)"|'([^']*)')\s*\)$/i.exec(s);
6898
+ if (containsTextMatch) {
6899
+ return { kind: "textContains", value: containsTextMatch[1] ?? containsTextMatch[2] };
6900
+ }
6901
+ const startsWithAttrMatch = /^starts-with\(\s*(@[a-zA-Z0-9_:.\\-]+|\*)\s*,\s*(?:"([^"]*)"|'([^']*)')\s*\)$/i.exec(s);
6902
+ if (startsWithAttrMatch) {
6903
+ const attrName = startsWithAttrMatch[1].replace(/^@/, "");
6904
+ return { kind: "attrStartsWith", name: attrName, value: startsWithAttrMatch[2] ?? startsWithAttrMatch[3] };
6905
+ }
6906
+ const textMatch = /^(?:text\(\)|\.)\s*(!?=)\s*(?:"([^"]*)"|'([^']*)'|([^\s"']+))\s*$/i.exec(s);
6907
+ if (textMatch) {
6908
+ const val = textMatch[2] !== void 0 ? textMatch[2] : textMatch[3] !== void 0 ? textMatch[3] : textMatch[4] ?? "";
6909
+ return {
6910
+ kind: "textEquals",
6911
+ value: val,
6912
+ ...textMatch[1] === "!=" ? { notEqual: true } : {}
6913
+ };
6914
+ }
6915
+ const compMatch = /^(@?[a-zA-Z0-9_:.\\-]+)\s*(!?=)\s*(?:"([^"]*)"|'([^']*)'|([^\s\]]+))$/.exec(s);
6916
+ if (compMatch) {
6917
+ const name = compMatch[1];
6918
+ const op = compMatch[2];
6919
+ const val = compMatch[3] !== void 0 ? compMatch[3] : compMatch[4] !== void 0 ? compMatch[4] : compMatch[5] ?? "";
6920
+ if (name.startsWith("@")) {
6921
+ return {
6922
+ kind: "attrEquals",
6923
+ name: name.slice(1),
6924
+ value: val,
6925
+ ...op === "!=" ? { notEqual: true } : {}
6926
+ };
6927
+ }
6928
+ return {
6929
+ kind: "childEquals",
6930
+ tag: name,
6931
+ value: val,
6932
+ ...op === "!=" ? { notEqual: true } : {}
6933
+ };
6934
+ }
6935
+ if (s.startsWith("@")) {
6936
+ return { kind: "attrExists", name: s.slice(1) };
6937
+ }
6938
+ if (/^[a-zA-Z0-9_:.\\-]+$/.test(s)) {
6939
+ return { kind: "childExists", tag: s };
6940
+ }
6941
+ return null;
6942
+ }
6943
+ function evalPredicate(node, pred, indexInMatch, totalMatching) {
6944
+ switch (pred.kind) {
6945
+ case "index": {
6946
+ const targetIdx = pred.index < 0 ? totalMatching + pred.index : pred.index;
6947
+ return indexInMatch === targetIdx;
6948
+ }
6949
+ case "all":
6950
+ return true;
6951
+ case "and":
6952
+ return pred.predicates.every((p) => evalPredicate(node, p, indexInMatch, totalMatching));
6953
+ case "or":
6954
+ return pred.predicates.some((p) => evalPredicate(node, p, indexInMatch, totalMatching));
6955
+ case "localNameEquals": {
6956
+ const local = getLocalName(node.tag);
6957
+ const eq = local.toLowerCase() === pred.value.toLowerCase();
6958
+ return pred.notEqual ? !eq : eq;
6959
+ }
6960
+ case "attrExists":
6961
+ return getAttrValue(node, pred.name) !== void 0;
6962
+ case "attrEquals": {
6963
+ const val = getAttrValue(node, pred.name);
6964
+ if (val !== void 0) {
6965
+ return pred.notEqual ? val !== pred.value : val === pred.value;
6966
+ }
6967
+ const child = node.children.find((c) => matchTag(c.tag, pred.name));
6968
+ if (child !== void 0) {
6969
+ return pred.notEqual ? child.text.trim() !== pred.value.trim() : child.text.trim() === pred.value.trim();
6970
+ }
6971
+ return pred.notEqual === true;
6972
+ }
6973
+ case "attrContains": {
6974
+ const val = getAttrValue(node, pred.name);
6975
+ if (val === void 0) return false;
6976
+ return val.toLowerCase().includes(pred.value.toLowerCase());
6977
+ }
6978
+ case "attrStartsWith": {
6979
+ const val = getAttrValue(node, pred.name);
6980
+ if (val === void 0) return false;
6981
+ return val.toLowerCase().startsWith(pred.value.toLowerCase());
6982
+ }
6983
+ case "textEquals": {
6984
+ const eq = node.text.trim() === pred.value.trim();
6985
+ return pred.notEqual ? !eq : eq;
6986
+ }
6987
+ case "textContains":
6988
+ return node.text.toLowerCase().includes(pred.value.toLowerCase());
6989
+ case "childExists":
6990
+ return node.children.some((c) => matchTag(c.tag, pred.tag));
6991
+ case "childEquals": {
6992
+ const child = node.children.find((c) => matchTag(c.tag, pred.tag));
6993
+ if (!child) return pred.notEqual === true;
6994
+ return pred.notEqual ? child.text.trim() !== pred.value.trim() : child.text.trim() === pred.value.trim();
6995
+ }
6996
+ }
6997
+ }
6767
6998
  function parseXmlPath(pathStr) {
6768
6999
  let normalized = pathStr.trim();
6769
7000
  if (normalized === "" || normalized === "/") return [];
@@ -6775,16 +7006,23 @@ function parseXmlPath(pathStr) {
6775
7006
  }
6776
7007
  const segments = [];
6777
7008
  let inBracket = false;
7009
+ let quoteChar = null;
6778
7010
  let currentSegment = "";
6779
7011
  for (let i = 0; i < normalized.length; i++) {
6780
7012
  const ch = normalized[i];
6781
- if (ch === "[") {
7013
+ if (!quoteChar && (ch === '"' || ch === "'")) {
7014
+ quoteChar = ch;
7015
+ currentSegment += ch;
7016
+ } else if (quoteChar && ch === quoteChar) {
7017
+ quoteChar = null;
7018
+ currentSegment += ch;
7019
+ } else if (!quoteChar && ch === "[") {
6782
7020
  inBracket = true;
6783
7021
  currentSegment += ch;
6784
- } else if (ch === "]") {
7022
+ } else if (!quoteChar && ch === "]") {
6785
7023
  inBracket = false;
6786
7024
  currentSegment += ch;
6787
- } else if (!inBracket && (ch === "/" || ch === ".")) {
7025
+ } else if (!quoteChar && !inBracket && (ch === "/" || ch === ".")) {
6788
7026
  if (currentSegment) {
6789
7027
  segments.push(currentSegment);
6790
7028
  currentSegment = "";
@@ -6813,54 +7051,167 @@ function parseXmlPath(pathStr) {
6813
7051
  steps.push({ tag: "", isRecursive, attributeSelect: attrSelectMatch[1] });
6814
7052
  continue;
6815
7053
  }
6816
- const indexMatch = /\[(-?\d+)\]$/.exec(s);
6817
- const wildcardMatch = /\[\*\]$/.exec(s);
6818
- const attrMatch = /\[@?([a-zA-Z0-9_:.\\-]+)(?:(!?=)\s*["']?([^"'\]]*)["']?)?\]$/.exec(s);
6819
- let tag = s.replace(/\[.*\]$/, "");
7054
+ let tag = "";
7055
+ const rawPredicates = [];
7056
+ let inB = false;
7057
+ let qChar = null;
7058
+ let curPred = "";
7059
+ for (let cIdx = 0; cIdx < s.length; cIdx++) {
7060
+ const c = s[cIdx];
7061
+ if (!qChar && (c === '"' || c === "'")) {
7062
+ qChar = c;
7063
+ if (inB) curPred += c;
7064
+ } else if (qChar && c === qChar) {
7065
+ qChar = null;
7066
+ if (inB) curPred += c;
7067
+ } else if (!qChar && c === "[") {
7068
+ if (!inB) {
7069
+ inB = true;
7070
+ curPred = "";
7071
+ } else {
7072
+ curPred += c;
7073
+ }
7074
+ } else if (!qChar && c === "]") {
7075
+ if (inB) {
7076
+ inB = false;
7077
+ rawPredicates.push(curPred.trim());
7078
+ curPred = "";
7079
+ }
7080
+ } else if (!inB) {
7081
+ tag += c;
7082
+ } else {
7083
+ curPred += c;
7084
+ }
7085
+ }
7086
+ tag = tag.trim();
6820
7087
  if (!tag) tag = "*";
6821
- let index;
6822
- let allIndices;
6823
- let attributeFilter;
6824
- if (indexMatch) {
6825
- index = parseInt(indexMatch[1], 10);
6826
- } else if (wildcardMatch) {
6827
- allIndices = true;
6828
- } else if (attrMatch) {
6829
- const [, attrName, op, attrVal] = attrMatch;
6830
- attributeFilter = {
6831
- name: attrName,
6832
- ...attrVal !== void 0 ? { value: attrVal } : {},
6833
- ...op === "!=" ? { notEqual: true } : {}
6834
- };
7088
+ if (inB || qChar !== null) {
7089
+ steps.push({ tag: s, isRecursive });
7090
+ continue;
7091
+ }
7092
+ const predicates = [];
7093
+ let legacyIndex;
7094
+ let legacyAllIndices;
7095
+ let legacyAttrFilter;
7096
+ let unreadablePredicate = false;
7097
+ for (const rawP of rawPredicates) {
7098
+ const parsedP = parseSinglePredicate(rawP);
7099
+ if (!parsedP) {
7100
+ unreadablePredicate = true;
7101
+ break;
7102
+ }
7103
+ predicates.push(parsedP);
7104
+ if (parsedP.kind === "index") {
7105
+ legacyIndex = parsedP.index;
7106
+ } else if (parsedP.kind === "all") {
7107
+ legacyAllIndices = true;
7108
+ } else if (parsedP.kind === "attrEquals" || parsedP.kind === "childEquals") {
7109
+ legacyAttrFilter = {
7110
+ name: parsedP.kind === "attrEquals" ? parsedP.name : parsedP.tag,
7111
+ value: parsedP.value,
7112
+ ...parsedP.notEqual ? { notEqual: true } : {}
7113
+ };
7114
+ }
6835
7115
  }
6836
- const step = { tag, isRecursive };
6837
- if (index !== void 0) step.index = index;
6838
- if (allIndices !== void 0) step.allIndices = allIndices;
6839
- if (attributeFilter !== void 0) step.attributeFilter = attributeFilter;
7116
+ if (unreadablePredicate) {
7117
+ steps.push({ tag: s, isRecursive });
7118
+ continue;
7119
+ }
7120
+ const hasComplexPredicates = rawPredicates.length > 1 || predicates.some(
7121
+ (p) => p.kind === "and" || p.kind === "or" || p.kind === "localNameEquals" || p.kind === "attrContains" || p.kind === "textContains" || p.kind === "attrStartsWith" || p.kind === "textEquals" || p.kind === "attrExists" || p.kind === "childExists"
7122
+ );
7123
+ const step = {
7124
+ tag,
7125
+ isRecursive,
7126
+ ...legacyIndex !== void 0 ? { index: legacyIndex } : {},
7127
+ ...legacyAllIndices !== void 0 ? { allIndices: legacyAllIndices } : {},
7128
+ ...legacyAttrFilter !== void 0 ? { attributeFilter: legacyAttrFilter } : {},
7129
+ ...hasComplexPredicates ? { predicates } : {}
7130
+ };
6840
7131
  steps.push(step);
6841
7132
  }
6842
- return steps;
7133
+ return steps;
7134
+ }
7135
+ function tryDecodeEmbeddedXml(content, opts = {}) {
7136
+ const maxLines = opts.maxLines ?? 60;
7137
+ if (!content || typeof content !== "string") return { decoded: false, text: content };
7138
+ let candidate = content.trim();
7139
+ const hadEntities = candidate.includes("&lt;") && candidate.includes("&gt;");
7140
+ if (hadEntities) {
7141
+ candidate = decodeXmlEntities(candidate).trim();
7142
+ }
7143
+ if (!candidate.startsWith("<") || !candidate.endsWith(">")) {
7144
+ return { decoded: false, text: content };
7145
+ }
7146
+ try {
7147
+ const { root } = parseXmlTree(candidate);
7148
+ if (!root) return { decoded: false, text: content };
7149
+ const formatted = serializeXmlNode(root, 0);
7150
+ const lines = formatted.split("\n");
7151
+ if (lines.length > maxLines) {
7152
+ const bounded = lines.slice(0, maxLines);
7153
+ bounded.push(`... (${lines.length - maxLines} more lines of decoded embedded XML elided; bounded)`);
7154
+ return { decoded: true, text: bounded.join("\n") };
7155
+ }
7156
+ return { decoded: true, text: formatted };
7157
+ } catch {
7158
+ return { decoded: false, text: content };
7159
+ }
6843
7160
  }
6844
- function serializeXmlNode(node, indent = 0) {
7161
+ function serializeXmlNode(node, indent = 0, opts = {}) {
6845
7162
  const pad = " ".repeat(indent);
6846
7163
  const attrParts = [];
6847
7164
  for (const [k, v] of Object.entries(node.attributes)) {
7165
+ if (opts.decodeEmbedded) {
7166
+ const decodedAttr = tryDecodeEmbeddedXml(v, { maxLines: 20 });
7167
+ if (decodedAttr.decoded) {
7168
+ attrParts.push(`${k}="[Embedded XML: ${v.length} chars]"`);
7169
+ continue;
7170
+ }
7171
+ }
6848
7172
  attrParts.push(`${k}="${escapeXmlAttr(v)}"`);
6849
7173
  }
6850
7174
  const attrStr = attrParts.length > 0 ? " " + attrParts.join(" ") : "";
7175
+ let textContent = node.text;
7176
+ let decodedTextBanner = "";
7177
+ if (opts.decodeEmbedded && textContent) {
7178
+ const decoded = tryDecodeEmbeddedXml(
7179
+ textContent,
7180
+ opts.maxLines !== void 0 ? { maxLines: opts.maxLines } : {}
7181
+ );
7182
+ if (decoded.decoded) {
7183
+ textContent = decoded.text.split("\n").map((l) => `${pad} ${l}`).join("\n");
7184
+ decodedTextBanner = `${pad} <!-- [Decoded Embedded XML] -->
7185
+ `;
7186
+ } else {
7187
+ textContent = `${pad} ${escapeXmlText(textContent)}`;
7188
+ }
7189
+ } else if (textContent) {
7190
+ textContent = `${pad} ${escapeXmlText(textContent)}`;
7191
+ }
6851
7192
  if (node.children.length === 0 && !node.text) {
6852
7193
  return `${pad}<${node.tag}${attrStr}/>`;
6853
7194
  }
6854
7195
  if (node.children.length === 0) {
7196
+ if (decodedTextBanner) {
7197
+ return `${pad}<${node.tag}${attrStr}>
7198
+ ${decodedTextBanner}${textContent}
7199
+ ${pad}</${node.tag}>`;
7200
+ }
6855
7201
  return `${pad}<${node.tag}${attrStr}>${escapeXmlText(node.text)}</${node.tag}>`;
6856
7202
  }
6857
7203
  const lines = [];
6858
7204
  lines.push(`${pad}<${node.tag}${attrStr}>`);
6859
7205
  if (node.text) {
6860
- lines.push(`${pad} ${escapeXmlText(node.text)}`);
7206
+ if (decodedTextBanner) {
7207
+ lines.push(decodedTextBanner.trimEnd());
7208
+ lines.push(textContent);
7209
+ } else {
7210
+ lines.push(`${pad} ${escapeXmlText(node.text)}`);
7211
+ }
6861
7212
  }
6862
7213
  for (const child of node.children) {
6863
- lines.push(serializeXmlNode(child, indent + 1));
7214
+ lines.push(serializeXmlNode(child, indent + 1, opts));
6864
7215
  }
6865
7216
  lines.push(`${pad}</${node.tag}>`);
6866
7217
  return lines.join("\n");
@@ -6876,23 +7227,24 @@ function getAllDescendants(node) {
6876
7227
  walk(node);
6877
7228
  return desc;
6878
7229
  }
6879
- function xmlNodeToJson(node) {
7230
+ function xmlNodeToJson(node, opts = {}) {
6880
7231
  const hasAttrs = Object.keys(node.attributes).length > 0;
6881
7232
  const hasChildren = node.children.length > 0;
6882
7233
  if (!hasChildren && !hasAttrs) {
6883
- return node.text;
6884
- }
6885
- if (!hasChildren) {
6886
- const result2 = {};
6887
- for (const [k, v] of Object.entries(node.attributes)) {
6888
- result2[`@${k}`] = v;
6889
- }
6890
- if (node.text) {
6891
- result2["#text"] = node.text;
7234
+ if (opts.withLines) {
7235
+ return {
7236
+ lineStart: node.line,
7237
+ lineEnd: node.lineEnd,
7238
+ value: node.text
7239
+ };
6892
7240
  }
6893
- return result2;
7241
+ return node.text;
6894
7242
  }
6895
7243
  const result = {};
7244
+ if (opts.withLines) {
7245
+ result["lineStart"] = node.line;
7246
+ result["lineEnd"] = node.lineEnd;
7247
+ }
6896
7248
  for (const [k, v] of Object.entries(node.attributes)) {
6897
7249
  result[`@${k}`] = v;
6898
7250
  }
@@ -6907,22 +7259,29 @@ function xmlNodeToJson(node) {
6907
7259
  }
6908
7260
  for (const [tag, group] of childGroups) {
6909
7261
  if (group.length === 1) {
6910
- result[tag] = xmlNodeToJson(group[0]);
7262
+ result[tag] = xmlNodeToJson(group[0], opts);
6911
7263
  } else {
6912
- result[tag] = group.map(xmlNodeToJson);
7264
+ result[tag] = group.map((c) => xmlNodeToJson(c, opts));
6913
7265
  }
6914
7266
  }
6915
7267
  if (node.text) {
7268
+ if (opts.decodeEmbedded) {
7269
+ const decoded = tryDecodeEmbeddedXml(node.text, { maxLines: 50 });
7270
+ if (decoded.decoded) {
7271
+ result["#decodedXml"] = decoded.text;
7272
+ }
7273
+ }
6916
7274
  result["#text"] = node.text;
6917
7275
  }
6918
7276
  return result;
6919
7277
  }
6920
- function queryXml(xmlText, pathStr) {
7278
+ function queryXml(xmlText, pathStr, opts = {}) {
6921
7279
  const { root } = parseXmlTree(xmlText);
6922
7280
  if (!root) {
6923
7281
  throw new Error("No valid XML root element found");
6924
7282
  }
6925
- const steps = parseXmlPath(pathStr);
7283
+ const queryExpression = (opts.xpath ?? pathStr).trim();
7284
+ const steps = parseXmlPath(queryExpression);
6926
7285
  if (steps.length === 0) {
6927
7286
  return { items: [root], fanned: false };
6928
7287
  }
@@ -6933,22 +7292,31 @@ function queryXml(xmlText, pathStr) {
6933
7292
  const step = steps[sIdx];
6934
7293
  if (step.attributeSelect !== void 0) {
6935
7294
  const attrVals = [];
7295
+ const attrNodes = [];
6936
7296
  const attrName = step.attributeSelect;
6937
7297
  for (const cand of currentCandidates) {
6938
7298
  if (attrName === "*") {
6939
- pushAll(attrVals, Object.values(cand.attributes));
6940
- } else if (cand.attributes[attrName] !== void 0) {
6941
- attrVals.push(cand.attributes[attrName]);
7299
+ for (const [k, v] of Object.entries(cand.attributes)) {
7300
+ attrVals.push(v);
7301
+ attrNodes.push({ value: v, node: cand, attrName: k });
7302
+ }
7303
+ } else {
7304
+ const val = getAttrValue(cand, attrName);
7305
+ if (val !== void 0) {
7306
+ attrVals.push(val);
7307
+ attrNodes.push({ value: val, node: cand, attrName });
7308
+ }
6942
7309
  }
6943
7310
  }
6944
7311
  return {
6945
7312
  items: currentCandidates,
6946
7313
  attributeValues: attrVals,
7314
+ attributeNodes: attrNodes,
6947
7315
  fanned: hasFanned || attrVals.length > 1
6948
7316
  };
6949
7317
  }
6950
7318
  const nextCandidates = [];
6951
- const evaluateOnCurrentNode = isAtRootLevel && !step.isRecursive && (step.tag === "*" || step.tag.toLowerCase() === root.tag.toLowerCase());
7319
+ const evaluateOnCurrentNode = isAtRootLevel && !step.isRecursive && matchTag(root.tag, step.tag);
6952
7320
  isAtRootLevel = false;
6953
7321
  for (const cand of currentCandidates) {
6954
7322
  let targets;
@@ -6959,34 +7327,35 @@ function queryXml(xmlText, pathStr) {
6959
7327
  } else {
6960
7328
  targets = cand.children;
6961
7329
  }
6962
- let matching = targets.filter((c) => step.tag === "*" || c.tag.toLowerCase() === step.tag.toLowerCase());
6963
- if (step.attributeFilter) {
6964
- const af = step.attributeFilter;
6965
- matching = matching.filter((c) => {
6966
- const attrVal = c.attributes[af.name];
6967
- if (attrVal !== void 0) {
6968
- if (af.value === void 0) return true;
6969
- return af.notEqual ? attrVal !== af.value : attrVal === af.value;
6970
- }
6971
- const childElem = c.children.find((child) => child.tag.toLowerCase() === af.name.toLowerCase());
6972
- if (childElem !== void 0) {
6973
- if (af.value === void 0) return true;
6974
- return af.notEqual ? childElem.text !== af.value : childElem.text === af.value;
6975
- }
6976
- return af.notEqual === true;
6977
- });
6978
- }
6979
- if (step.index !== void 0) {
6980
- const idx = step.index < 0 ? matching.length + step.index : step.index;
6981
- if (idx >= 0 && idx < matching.length) {
6982
- nextCandidates.push(matching[idx]);
7330
+ let matching = targets.filter((c) => matchTag(c.tag, step.tag));
7331
+ const preds = step.predicates && step.predicates.length > 0 ? step.predicates : step.attributeFilter ? [
7332
+ {
7333
+ kind: "attrEquals",
7334
+ name: step.attributeFilter.name,
7335
+ value: step.attributeFilter.value ?? "",
7336
+ ...step.attributeFilter.notEqual ? { notEqual: true } : {}
6983
7337
  }
6984
- } else {
6985
- if (step.allIndices || matching.length > 1 || step.attributeFilter !== void 0) {
7338
+ ] : step.index !== void 0 ? [{ kind: "index", index: step.index }] : step.allIndices ? [{ kind: "all" }] : [];
7339
+ for (const pred of preds) {
7340
+ if (pred.kind === "index") {
7341
+ const total = matching.length;
7342
+ const idx = pred.index < 0 ? total + pred.index : pred.index;
7343
+ if (idx >= 0 && idx < total) {
7344
+ matching = [matching[idx]];
7345
+ } else {
7346
+ matching = [];
7347
+ }
7348
+ } else if (pred.kind === "all") {
6986
7349
  hasFanned = true;
7350
+ } else {
7351
+ const total = matching.length;
7352
+ matching = matching.filter((node, idx) => evalPredicate(node, pred, idx, total));
6987
7353
  }
6988
- pushAll(nextCandidates, matching);
6989
7354
  }
7355
+ if (matching.length > 1 || step.allIndices || step.attributeFilter !== void 0 || step.predicates && step.predicates.length > 0 && step.predicates[0]?.kind !== "index") {
7356
+ hasFanned = true;
7357
+ }
7358
+ pushAll(nextCandidates, matching);
6990
7359
  }
6991
7360
  currentCandidates = nextCandidates;
6992
7361
  }
@@ -7220,6 +7589,11 @@ function runXmlQuery(opts) {
7220
7589
  emitErr4(`Could not read: ${opts.file}`);
7221
7590
  return 1;
7222
7591
  }
7592
+ const queryPath = (opts.xpath ?? opts.path ?? "").trim();
7593
+ if (!queryPath) {
7594
+ emitErr4("Must provide a path or --xpath <expression>");
7595
+ return 1;
7596
+ }
7223
7597
  let head;
7224
7598
  try {
7225
7599
  head = opts.head !== void 0 ? requireNonNegativeStrictInt("--head", opts.head) : void 0;
@@ -7228,7 +7602,7 @@ function runXmlQuery(opts) {
7228
7602
  return 1;
7229
7603
  }
7230
7604
  try {
7231
- const result = queryXml(text, opts.path);
7605
+ const result = queryXml(text, queryPath, { ...opts.xpath !== void 0 ? { xpath: opts.xpath } : {} });
7232
7606
  const fullSourceBytes = sumFileSizes([opts.file]);
7233
7607
  if (result.attributeValues !== void 0) {
7234
7608
  if (result.attributeValues.length === 0) {
@@ -7237,27 +7611,86 @@ function runXmlQuery(opts) {
7237
7611
  emit4(jsonText);
7238
7612
  recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
7239
7613
  } else {
7240
- emit4(`No attributes matched path: '${displaySafeText(opts.path)}'`);
7614
+ emit4(`No attributes matched path: '${displaySafeText(queryPath)}'`);
7241
7615
  }
7242
7616
  return 0;
7243
7617
  }
7244
- if (!result.fanned) {
7245
- const val = result.attributeValues[0] ?? "";
7246
- const outText = opts.json === true ? displaySafeJson(val, 0) : val;
7618
+ const totalCount2 = result.attributeValues.length;
7619
+ const limitedNodes = result.attributeNodes !== void 0 ? head !== void 0 ? result.attributeNodes.slice(0, head) : result.attributeNodes : void 0;
7620
+ const limited2 = head !== void 0 ? result.attributeValues.slice(0, head) : result.attributeValues;
7621
+ const headTruncated2 = limited2.length < totalCount2;
7622
+ if (!result.fanned && totalCount2 === 1) {
7623
+ let val = limited2[0] ?? "";
7624
+ if (opts.decodeEmbeddedXml) {
7625
+ const decoded = tryDecodeEmbeddedXml(val);
7626
+ if (decoded.decoded) val = decoded.text;
7627
+ }
7628
+ let outText = "";
7629
+ if (opts.json === true) {
7630
+ if (opts.withLines && limitedNodes && limitedNodes[0]) {
7631
+ outText = displaySafeJson(
7632
+ { value: val, lineStart: limitedNodes[0].node.line, lineEnd: limitedNodes[0].node.lineEnd },
7633
+ 0
7634
+ );
7635
+ } else {
7636
+ outText = displaySafeJson(val, 0);
7637
+ }
7638
+ } else {
7639
+ if (opts.withLines && limitedNodes && limitedNodes[0]) {
7640
+ outText = `# Line: L${limitedNodes[0].node.line}
7641
+ ${val}`;
7642
+ } else {
7643
+ outText = val;
7644
+ }
7645
+ }
7247
7646
  emit4(outText);
7248
7647
  recordReadStat("xml_query", fullSourceBytes, outText, opts.file);
7249
7648
  return 0;
7250
7649
  }
7251
- const totalCount2 = result.attributeValues.length;
7252
- const limited2 = head !== void 0 ? result.attributeValues.slice(0, head) : result.attributeValues;
7253
- const headTruncated2 = limited2.length < totalCount2;
7254
7650
  if (opts.json === true) {
7255
- const capped = guardJsonRows(limited2);
7256
- const jsonText = displaySafeJson({ items: capped.items, truncated: capped.truncated || headTruncated2, totalCount: totalCount2 }, 0);
7651
+ let itemsToSerialize = limited2;
7652
+ if (opts.withLines && limitedNodes) {
7653
+ itemsToSerialize = limitedNodes.map((an) => {
7654
+ let val = an.value;
7655
+ if (opts.decodeEmbeddedXml) {
7656
+ const decoded = tryDecodeEmbeddedXml(val);
7657
+ if (decoded.decoded) val = decoded.text;
7658
+ }
7659
+ return {
7660
+ value: val,
7661
+ lineStart: an.node.line,
7662
+ lineEnd: an.node.lineEnd
7663
+ };
7664
+ });
7665
+ } else if (opts.decodeEmbeddedXml) {
7666
+ itemsToSerialize = limited2.map((val) => {
7667
+ const decoded = tryDecodeEmbeddedXml(val);
7668
+ return decoded.decoded ? decoded.text : val;
7669
+ });
7670
+ }
7671
+ const capped = guardJsonRows(itemsToSerialize);
7672
+ const jsonText = displaySafeJson(
7673
+ { items: capped.items, truncated: capped.truncated || headTruncated2, totalCount: totalCount2 },
7674
+ 0
7675
+ );
7257
7676
  emit4(jsonText);
7258
7677
  recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
7259
7678
  } else {
7260
- const lines = limited2.map((item) => item);
7679
+ const lines = [];
7680
+ for (let i = 0; i < limited2.length; i++) {
7681
+ let val = limited2[i] ?? "";
7682
+ if (opts.decodeEmbeddedXml) {
7683
+ const decoded = tryDecodeEmbeddedXml(val);
7684
+ if (decoded.decoded) val = decoded.text;
7685
+ }
7686
+ const nodeInfo = limitedNodes ? limitedNodes[i] : void 0;
7687
+ if (opts.withLines && nodeInfo) {
7688
+ lines.push(`# Line: L${nodeInfo.node.line}
7689
+ ${val}`);
7690
+ } else {
7691
+ lines.push(val);
7692
+ }
7693
+ }
7261
7694
  if (headTruncated2) {
7262
7695
  lines.push(`...(${totalCount2 - limited2.length} more items elided; use --head to see more)`);
7263
7696
  }
@@ -7273,19 +7706,30 @@ function runXmlQuery(opts) {
7273
7706
  emit4(jsonText);
7274
7707
  recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
7275
7708
  } else {
7276
- emit4(`No elements matched path: '${displaySafeText(opts.path)}'`);
7709
+ emit4(`No elements matched path: '${displaySafeText(queryPath)}'`);
7277
7710
  }
7278
7711
  return 0;
7279
7712
  }
7280
- if (!result.fanned) {
7713
+ if (!result.fanned && result.items.length === 1) {
7281
7714
  const node = result.items[0];
7282
7715
  if (opts.json === true) {
7283
- const jsonVal = xmlNodeToJson(node);
7716
+ const jsonVal = xmlNodeToJson(node, {
7717
+ ...opts.withLines ? { withLines: true } : {},
7718
+ ...opts.decodeEmbeddedXml ? { decodeEmbedded: true } : {}
7719
+ });
7284
7720
  const jsonText = displaySafeJson(jsonVal);
7285
7721
  emit4(jsonText);
7286
7722
  recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
7287
7723
  } else {
7288
- const xmlText = serializeXmlNode(node);
7724
+ let xmlText = serializeXmlNode(
7725
+ node,
7726
+ 0,
7727
+ opts.decodeEmbeddedXml ? { decodeEmbedded: true } : {}
7728
+ );
7729
+ if (opts.withLines) {
7730
+ xmlText = `# Lines: L${node.line}-L${node.lineEnd}
7731
+ ${xmlText}`;
7732
+ }
7289
7733
  emitGuarded(xmlText, "xml-query");
7290
7734
  recordReadStat("xml_query", fullSourceBytes, xmlText, opts.file);
7291
7735
  }
@@ -7295,13 +7739,32 @@ function runXmlQuery(opts) {
7295
7739
  const limited = head !== void 0 ? result.items.slice(0, head) : result.items;
7296
7740
  const headTruncated = limited.length < totalCount;
7297
7741
  if (opts.json === true) {
7298
- const jsonItems = limited.map(xmlNodeToJson);
7742
+ const jsonItems = limited.map(
7743
+ (n) => xmlNodeToJson(n, {
7744
+ ...opts.withLines ? { withLines: true } : {},
7745
+ ...opts.decodeEmbeddedXml ? { decodeEmbedded: true } : {}
7746
+ })
7747
+ );
7299
7748
  const capped = guardJsonRows(jsonItems);
7300
- const jsonText = displaySafeJson({ items: capped.items, truncated: capped.truncated || headTruncated, totalCount }, 0);
7749
+ const jsonText = displaySafeJson(
7750
+ { items: capped.items, truncated: capped.truncated || headTruncated, totalCount },
7751
+ 0
7752
+ );
7301
7753
  emit4(jsonText);
7302
7754
  recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
7303
7755
  } else {
7304
- const blocks = limited.map((node) => serializeXmlNode(node));
7756
+ const blocks = limited.map((node) => {
7757
+ let block = serializeXmlNode(
7758
+ node,
7759
+ 0,
7760
+ opts.decodeEmbeddedXml ? { decodeEmbedded: true } : {}
7761
+ );
7762
+ if (opts.withLines) {
7763
+ block = `# Lines: L${node.line}-L${node.lineEnd}
7764
+ ${block}`;
7765
+ }
7766
+ return block;
7767
+ });
7305
7768
  if (headTruncated) {
7306
7769
  blocks.push(`...(${totalCount - limited.length} more elements elided; use --head to see more)`);
7307
7770
  }
@@ -8702,7 +9165,7 @@ function runLog(opts) {
8702
9165
 
8703
9166
  // src/read_inspect.ts
8704
9167
  init_define_import_meta_env();
8705
- import * as fs11 from "node:fs";
9168
+ import * as fs10 from "node:fs";
8706
9169
  import * as path10 from "node:path";
8707
9170
 
8708
9171
  // src/archive_query.ts
@@ -9127,317 +9590,10 @@ function isNoteStale(note, dbPath = globalDbPath()) {
9127
9590
  return current === null || current !== note.fingerprint;
9128
9591
  }
9129
9592
 
9130
- // src/sqlite_query.ts
9131
- init_define_import_meta_env();
9132
- import * as fs10 from "node:fs";
9133
- var SQLITE_QUERY_ROW_CAP = 5e3;
9134
- var SQLITE_MAGIC = Buffer.from([83, 81, 76, 105, 116, 101, 32, 102, 111, 114, 109, 97, 116, 32, 51, 0]);
9135
- function readMagicBytes(filePath) {
9136
- let fd;
9137
- try {
9138
- fd = fs10.openSync(filePath, "r");
9139
- } catch {
9140
- return null;
9141
- }
9142
- try {
9143
- const buf = Buffer.alloc(SQLITE_MAGIC.length);
9144
- const bytesRead = fs10.readSync(fd, buf, 0, buf.length, 0);
9145
- return bytesRead === buf.length ? buf : null;
9146
- } catch {
9147
- return null;
9148
- } finally {
9149
- fs10.closeSync(fd);
9150
- }
9151
- }
9152
- function isSqliteFile(filePath) {
9153
- const magic = readMagicBytes(filePath);
9154
- return magic !== null && magic.equals(SQLITE_MAGIC);
9155
- }
9156
- function openReadonlySqlite(filePath) {
9157
- if (!fs10.existsSync(filePath)) {
9158
- throw new Error(`file not found: ${displaySafeText(filePath)}`);
9159
- }
9160
- if (!isSqliteFile(filePath)) {
9161
- throw new Error(`not a valid SQLite database (bad file header): ${displaySafeText(filePath)}`);
9162
- }
9163
- try {
9164
- return new Database(filePath, { readonly: true, fileMustExist: true });
9165
- } catch (e) {
9166
- const msg = e instanceof Error ? e.message : String(e);
9167
- throw new Error(`failed to open SQLite database: ${msg}`, { cause: e });
9168
- }
9169
- }
9170
- function quoteIdent(name) {
9171
- return `"${name.replace(/"/g, '""')}"`;
9172
- }
9173
- function getSqliteSchema(filePath) {
9174
- const db = openReadonlySqlite(filePath);
9175
- try {
9176
- const objects = db.prepare("SELECT name, type FROM sqlite_master WHERE type IN ('table','view') AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' ORDER BY name").all();
9177
- const tables = objects.map((o) => {
9178
- const columns = db.prepare(`PRAGMA table_info(${quoteIdent(o.name)})`).all().map(
9179
- (c) => ({
9180
- name: c.name,
9181
- type: c.type || "",
9182
- notNull: c.notnull === 1,
9183
- primaryKey: c.pk > 0,
9184
- defaultValue: c.dflt_value === null || c.dflt_value === void 0 ? null : String(c.dflt_value)
9185
- })
9186
- );
9187
- const indexes = db.prepare(`PRAGMA index_list(${quoteIdent(o.name)})`).all().map(
9188
- (idx) => {
9189
- const idxColumns = db.prepare(`PRAGMA index_info(${quoteIdent(idx.name)})`).all().map((c) => c.name);
9190
- return { name: idx.name, unique: idx.unique === 1, columns: idxColumns };
9191
- }
9192
- );
9193
- const foreignKeys = db.prepare(`PRAGMA foreign_key_list(${quoteIdent(o.name)})`).all().map((fk) => ({ table: fk.table, from: fk.from, to: fk.to }));
9194
- let rowCount;
9195
- try {
9196
- const row = db.prepare(`SELECT COUNT(*) AS c FROM ${quoteIdent(o.name)}`).get();
9197
- rowCount = row.c;
9198
- } catch {
9199
- rowCount = null;
9200
- }
9201
- return { name: o.name, kind: o.type === "view" ? "view" : "table", columns, indexes, foreignKeys, rowCount };
9202
- });
9203
- return { tables };
9204
- } finally {
9205
- db.close();
9206
- }
9207
- }
9208
- function formatSqliteSchema(result) {
9209
- if (result.tables.length === 0) return "(no tables or views found)";
9210
- return result.tables.map((t) => {
9211
- const lines = [`${t.name} (${t.kind}${t.rowCount !== null ? `, ${t.rowCount} row${t.rowCount === 1 ? "" : "s"}` : ""})`];
9212
- for (const c of t.columns) {
9213
- const flags = [c.primaryKey ? "PK" : null, c.notNull ? "NOT NULL" : null, c.defaultValue !== null ? `DEFAULT ${c.defaultValue}` : null].filter((f) => f !== null).join(" ");
9214
- lines.push(` ${displaySafeText(c.name)} ${displaySafeText(c.type || "(untyped)")}${flags ? ` ${flags}` : ""}`);
9215
- }
9216
- if (t.indexes.length > 0) {
9217
- lines.push(" indexes:");
9218
- for (const idx of t.indexes) {
9219
- lines.push(` ${displaySafeText(idx.name)}${idx.unique ? " (unique)" : ""}: ${displaySafeText(idx.columns.join(", "))}`);
9220
- }
9221
- }
9222
- if (t.foreignKeys.length > 0) {
9223
- lines.push(" foreign keys:");
9224
- for (const fk of t.foreignKeys) {
9225
- lines.push(` ${fk.from} -> ${fk.table}.${fk.to}`);
9226
- }
9227
- }
9228
- return lines.join("\n");
9229
- }).join("\n\n");
9230
- }
9231
- function getSqliteTables(filePath) {
9232
- const db = openReadonlySqlite(filePath);
9233
- try {
9234
- const objects = db.prepare(
9235
- // ESCAPE makes every `_` literal; see getSqliteSchema above. Here it costs three tables rather than one, since `%_fts_%` and `%_vec0_%` exist to hide the shadow tables FTS5 and sqlite-vec create beside a virtual table, and unescaped they also hide anything of the shape `my_ftsx_cache`.
9236
- "SELECT name, type FROM sqlite_master WHERE type IN ('table','view') AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND name NOT LIKE '%\\_fts\\_%' ESCAPE '\\' AND name NOT LIKE '%\\_vec0\\_%' ESCAPE '\\' ORDER BY name"
9237
- ).all();
9238
- return objects.map((o) => {
9239
- let columnCount;
9240
- try {
9241
- const cols = db.prepare(`PRAGMA table_info(${quoteIdent(o.name)})`).all();
9242
- columnCount = cols.length;
9243
- } catch {
9244
- columnCount = 0;
9245
- }
9246
- let rowCount;
9247
- try {
9248
- const row = db.prepare(`SELECT COUNT(*) AS c FROM ${quoteIdent(o.name)}`).get();
9249
- rowCount = row.c;
9250
- } catch {
9251
- rowCount = null;
9252
- }
9253
- return {
9254
- name: o.name,
9255
- kind: o.type === "view" ? "view" : "table",
9256
- rowCount,
9257
- columnCount
9258
- };
9259
- });
9260
- } finally {
9261
- db.close();
9262
- }
9263
- }
9264
- function formatSqliteTables(tables) {
9265
- if (tables.length === 0) return "(no tables or views found)";
9266
- return tables.map((t) => {
9267
- const parts = [t.kind];
9268
- if (t.rowCount !== null) {
9269
- parts.push(`${t.rowCount.toLocaleString()} row${t.rowCount === 1 ? "" : "s"}`);
9270
- }
9271
- parts.push(`${t.columnCount} col${t.columnCount === 1 ? "" : "s"}`);
9272
- return `${displaySafeText(t.name)} (${parts.join(", ")})`;
9273
- }).join("\n");
9274
- }
9275
- var FORBIDDEN_KEYWORDS = [
9276
- "INSERT",
9277
- "UPDATE",
9278
- "DELETE",
9279
- "DROP",
9280
- "ALTER",
9281
- "CREATE",
9282
- "ATTACH",
9283
- "DETACH",
9284
- "TRUNCATE",
9285
- "VACUUM",
9286
- "REINDEX",
9287
- "PRAGMA",
9288
- "BEGIN",
9289
- "COMMIT",
9290
- "ROLLBACK",
9291
- "SAVEPOINT",
9292
- "RELEASE",
9293
- "GRANT",
9294
- "REVOKE"
9295
- ];
9296
- function stripSqlLiteralsAndComments(sql) {
9297
- let out = "";
9298
- let i = 0;
9299
- const n = sql.length;
9300
- while (i < n) {
9301
- const c = sql[i];
9302
- if (c === "'" || c === '"' || c === "`") {
9303
- const quote = c;
9304
- out += " ";
9305
- i++;
9306
- while (i < n) {
9307
- if (sql[i] === quote) {
9308
- if (sql[i + 1] === quote) {
9309
- i += 2;
9310
- continue;
9311
- }
9312
- i++;
9313
- break;
9314
- }
9315
- i++;
9316
- }
9317
- continue;
9318
- }
9319
- if (c === "[") {
9320
- out += " ";
9321
- i++;
9322
- while (i < n && sql[i] !== "]") i++;
9323
- if (i < n) i++;
9324
- continue;
9325
- }
9326
- if (c === "-" && sql[i + 1] === "-") {
9327
- while (i < n && sql[i] !== "\n") i++;
9328
- continue;
9329
- }
9330
- if (c === "/" && sql[i + 1] === "*") {
9331
- i += 2;
9332
- while (i < n && !(sql[i] === "*" && sql[i + 1] === "/")) i++;
9333
- i += 2;
9334
- continue;
9335
- }
9336
- out += c;
9337
- i++;
9338
- }
9339
- return out;
9340
- }
9341
- function validateReadOnlySelect(sql) {
9342
- const stripped = stripSqlLiteralsAndComments(sql);
9343
- const trimmed = stripped.trim();
9344
- if (trimmed === "") {
9345
- throw new Error("empty query");
9346
- }
9347
- const withoutTrailingSemicolons = trimmed.replace(/;+\s*$/, "");
9348
- if (withoutTrailingSemicolons.includes(";")) {
9349
- throw new Error("multiple statements are not allowed (sqlite-query accepts exactly one read-only SELECT statement)");
9350
- }
9351
- const upper = withoutTrailingSemicolons.toUpperCase();
9352
- if (!/^(SELECT|WITH)\b/.test(upper.trimStart())) {
9353
- throw new Error("only SELECT statements are allowed (sqlite-query is read-only)");
9354
- }
9355
- for (const kw of FORBIDDEN_KEYWORDS) {
9356
- if (new RegExp(`\\b${kw}\\b`).test(upper)) {
9357
- throw new Error(`statement contains forbidden keyword '${kw}' (sqlite-query only allows read-only SELECT queries)`);
9358
- }
9359
- }
9360
- if (/\bREPLACE\b(?!\s*\()/.test(upper)) {
9361
- throw new Error("statement contains forbidden keyword 'REPLACE' (sqlite-query only allows read-only SELECT queries)");
9362
- }
9363
- }
9364
- function normalizeSqliteScalar(value) {
9365
- if (typeof value === "bigint") {
9366
- return Number.isSafeInteger(Number(value)) ? Number(value) : value.toString();
9367
- }
9368
- return value;
9369
- }
9370
- function normalizeSqliteRow(row) {
9371
- const out = {};
9372
- for (const [k, v] of Object.entries(row)) out[k] = normalizeSqliteScalar(v);
9373
- return out;
9374
- }
9375
- function runReadOnlySqliteQuery(filePath, sql, opts = {}) {
9376
- validateReadOnlySelect(sql);
9377
- const rowCap = opts.rowCap ?? SQLITE_QUERY_ROW_CAP;
9378
- const db = openReadonlySqlite(filePath);
9379
- try {
9380
- let stmt;
9381
- try {
9382
- stmt = db.prepare(sql);
9383
- } catch (e) {
9384
- const msg = e instanceof Error ? e.message : String(e);
9385
- throw new Error(`invalid SQL: ${msg}`, { cause: e });
9386
- }
9387
- if (!stmt.reader) {
9388
- throw new Error("only read-only SELECT statements are allowed (sqlite-query is read-only)");
9389
- }
9390
- stmt.safeIntegers(true);
9391
- let columns = [];
9392
- try {
9393
- columns = stmt.columns().map((c) => c.name);
9394
- } catch {
9395
- columns = [];
9396
- }
9397
- const rows = [];
9398
- let rowCapped = false;
9399
- for (const row of stmt.iterate()) {
9400
- if (rows.length >= rowCap) {
9401
- rowCapped = true;
9402
- break;
9403
- }
9404
- rows.push(normalizeSqliteRow(row));
9405
- }
9406
- return { columns, rows, rowCapped };
9407
- } finally {
9408
- db.close();
9409
- }
9410
- }
9411
- function formatSqliteCell(value) {
9412
- if (value === null || value === void 0) return "";
9413
- if (value instanceof Uint8Array) return `<blob ${value.length} bytes>`;
9414
- return String(value);
9415
- }
9416
- function formatSqliteQueryTable(result, opts = {}) {
9417
- if (result.columns.length === 0 && result.rows.length === 0) return "(no rows)";
9418
- const lines = [
9419
- result.columns.map(quoteCsvCellLocal).join(","),
9420
- ...result.rows.map((r) => result.columns.map((c) => quoteCsvCellLocal(formatSqliteCell(r[c]))).join(","))
9421
- ];
9422
- if (opts.headTruncated === true) {
9423
- lines.push("...(more rows elided; use --head to see more)");
9424
- }
9425
- if (result.rowCapped) {
9426
- lines.push(`...(result truncated at the ${SQLITE_QUERY_ROW_CAP}-row safety cap; narrow the query with WHERE/LIMIT for a complete result)`);
9427
- }
9428
- return lines.join("\n");
9429
- }
9430
- function quoteCsvCellLocal(cell) {
9431
- if (cell.includes(",") || cell.includes('"') || cell.includes("\n") || cell.includes("\r")) {
9432
- return `"${cell.replace(/"/g, '""')}"`;
9433
- }
9434
- return cell;
9435
- }
9436
-
9437
9593
  // src/read_inspect.ts
9438
9594
  function fileExists(p) {
9439
9595
  try {
9440
- fs11.statSync(p);
9596
+ fs10.statSync(p);
9441
9597
  return true;
9442
9598
  } catch {
9443
9599
  return false;
@@ -10082,7 +10238,7 @@ function runListSections(opts) {
10082
10238
 
10083
10239
  // src/read_section.ts
10084
10240
  init_define_import_meta_env();
10085
- import * as fs12 from "node:fs";
10241
+ import * as fs11 from "node:fs";
10086
10242
  import * as path11 from "node:path";
10087
10243
  var AMBIGUOUS_HEADING_LIMIT = 10;
10088
10244
  function literalHeadingExists(filePath, heading) {
@@ -10107,7 +10263,7 @@ function runSection(opts) {
10107
10263
  }
10108
10264
  const result = readSection(filePath, heading, readFileText);
10109
10265
  if (result === null) {
10110
- if (!fs12.existsSync(filePath)) {
10266
+ if (!fs11.existsSync(filePath)) {
10111
10267
  return { text: `File not found: '${filePath}'`, code: 1 };
10112
10268
  }
10113
10269
  const ordSpec = /^(.*?)#(\d+)$/.exec(heading);
@@ -10162,7 +10318,39 @@ function runSectionMulti(specFilePath, resolvedFilePath, headings, opts) {
10162
10318
  let anyFound = false;
10163
10319
  const jsonOut = {};
10164
10320
  const textBlocks = [];
10321
+ const includedSections = [];
10165
10322
  for (const heading of headings) {
10323
+ const sectionResult = readSection(resolvedFilePath, heading, readFileText);
10324
+ if (sectionResult !== null) {
10325
+ const parent = includedSections.find(
10326
+ (p) => sectionResult.lineStart >= p.lineStart && sectionResult.lineEnd <= p.lineEnd
10327
+ );
10328
+ if (parent !== void 0) {
10329
+ anyFound = true;
10330
+ const notice = `(already included in section '${parent.heading}', lines ${sectionResult.lineStart}-${sectionResult.lineEnd})`;
10331
+ if (opts.json === true) {
10332
+ jsonOut[heading] = {
10333
+ heading: sectionResult.heading,
10334
+ subsumedBy: parent.heading,
10335
+ lineStart: sectionResult.lineStart,
10336
+ lineEnd: sectionResult.lineEnd,
10337
+ notice
10338
+ };
10339
+ continue;
10340
+ }
10341
+ textBlocks.push(
10342
+ `${heading}:
10343
+ # ${sectionResult.heading} \u2014 ${specFilePath}:${sectionResult.lineStart}-${sectionResult.lineEnd}
10344
+ ${notice}`
10345
+ );
10346
+ continue;
10347
+ }
10348
+ includedSections.push({
10349
+ heading: sectionResult.heading,
10350
+ lineStart: sectionResult.lineStart,
10351
+ lineEnd: sectionResult.lineEnd
10352
+ });
10353
+ }
10166
10354
  const sub = runSection({ ...opts, spec: `${specFilePath}::${heading}`, suppressStat: true });
10167
10355
  if (sub.code === 0) anyFound = true;
10168
10356
  if (opts.json === true) {
@@ -10181,12 +10369,48 @@ function runSectionCrossFile(pairs, opts) {
10181
10369
  let anyFound = false;
10182
10370
  const jsonOut = {};
10183
10371
  const textBlocks = [];
10372
+ const includedByFile = /* @__PURE__ */ new Map();
10184
10373
  const distinctFiles = new Set(pairs.map((p) => p.file));
10185
10374
  const keyFor = (p) => distinctFiles.size === 1 ? p.symbol : `${p.file}::${p.symbol}`;
10375
+ const resolvePath = (f) => opts.projectRoot !== void 0 && !path11.isAbsolute(f) ? path11.resolve(opts.projectRoot, f) : f;
10186
10376
  for (const { file, symbol: heading } of pairs) {
10377
+ const resolved = resolvePath(file);
10378
+ const sectionResult = readSection(resolved, heading, readFileText);
10379
+ const key = keyFor({ file, symbol: heading });
10380
+ const fileIncluded = includedByFile.get(resolved) ?? [];
10381
+ if (sectionResult !== null) {
10382
+ const parent = fileIncluded.find(
10383
+ (p) => sectionResult.lineStart >= p.lineStart && sectionResult.lineEnd <= p.lineEnd
10384
+ );
10385
+ if (parent !== void 0) {
10386
+ anyFound = true;
10387
+ const notice = `(already included in section '${parent.heading}', lines ${sectionResult.lineStart}-${sectionResult.lineEnd})`;
10388
+ if (opts.json === true) {
10389
+ jsonOut[key] = {
10390
+ heading: sectionResult.heading,
10391
+ subsumedBy: parent.heading,
10392
+ lineStart: sectionResult.lineStart,
10393
+ lineEnd: sectionResult.lineEnd,
10394
+ notice
10395
+ };
10396
+ continue;
10397
+ }
10398
+ textBlocks.push(
10399
+ `${key}:
10400
+ # ${sectionResult.heading} \u2014 ${file}:${sectionResult.lineStart}-${sectionResult.lineEnd}
10401
+ ${notice}`
10402
+ );
10403
+ continue;
10404
+ }
10405
+ fileIncluded.push({
10406
+ heading: sectionResult.heading,
10407
+ lineStart: sectionResult.lineStart,
10408
+ lineEnd: sectionResult.lineEnd
10409
+ });
10410
+ includedByFile.set(resolved, fileIncluded);
10411
+ }
10187
10412
  const sub = runSection({ ...opts, spec: `${file}::${heading}`, suppressStat: true });
10188
10413
  if (sub.code === 0) anyFound = true;
10189
- const key = keyFor({ file, symbol: heading });
10190
10414
  if (opts.json === true) {
10191
10415
  jsonOut[key] = sub.code === 0 ? JSON.parse(sub.text) : { error: sub.text };
10192
10416
  continue;
@@ -10194,7 +10418,6 @@ function runSectionCrossFile(pairs, opts) {
10194
10418
  textBlocks.push(`${key}:
10195
10419
  ${sub.text}`);
10196
10420
  }
10197
- const resolvePath = (f) => opts.projectRoot !== void 0 && !path11.isAbsolute(f) ? path11.resolve(opts.projectRoot, f) : f;
10198
10421
  const text = opts.json === true ? displaySafeJson(jsonOut) : textBlocks.join("\n\n");
10199
10422
  if (anyFound) {
10200
10423
  const fullSourceBytes = sumFileSizes(Array.from(distinctFiles, resolvePath));
@@ -10210,7 +10433,7 @@ var FIND_SCAN_LIMIT2 = 2e4;
10210
10433
  var REFS_TOP_SCAN_LIMIT = 2e4;
10211
10434
  function fileExists2(p) {
10212
10435
  try {
10213
- fs13.statSync(p);
10436
+ fs12.statSync(p);
10214
10437
  return true;
10215
10438
  } catch {
10216
10439
  return false;
@@ -10241,30 +10464,30 @@ function withPinnedReads(pins, fn) {
10241
10464
  }
10242
10465
  }
10243
10466
  function readPinnedBytes(p, pinned) {
10244
- const fd = fs13.openSync(p, fs13.constants.O_RDONLY);
10467
+ const fd = fs12.openSync(p, fs12.constants.O_RDONLY);
10245
10468
  try {
10246
- const actual = fileIdentity(fs13.fstatSync(fd, { bigint: true }));
10469
+ const actual = fileIdentity(fs12.fstatSync(fd, { bigint: true }));
10247
10470
  if (actual !== pinned) {
10248
10471
  throw new ConfinementIdentityError(
10249
10472
  `refused: "${p}" changed identity between validation and read (validated ${pinned}, opened ${actual}). The file was replaced or redirected after the confinement check, so the read was not performed.`
10250
10473
  );
10251
10474
  }
10252
- return fs13.readFileSync(fd);
10475
+ return fs12.readFileSync(fd);
10253
10476
  } finally {
10254
- fs13.closeSync(fd);
10477
+ fs12.closeSync(fd);
10255
10478
  }
10256
10479
  }
10257
10480
  function verifyPinnedIdentity(p, pinned) {
10258
- const fd = fs13.openSync(p, fs13.constants.O_RDONLY);
10481
+ const fd = fs12.openSync(p, fs12.constants.O_RDONLY);
10259
10482
  try {
10260
- const actual = fileIdentity(fs13.fstatSync(fd, { bigint: true }));
10483
+ const actual = fileIdentity(fs12.fstatSync(fd, { bigint: true }));
10261
10484
  if (actual !== pinned) {
10262
10485
  throw new ConfinementIdentityError(
10263
10486
  `refused: "${p}" changed identity between validation and read (validated ${pinned}, opened ${actual}). The file was replaced or redirected after the confinement check, so the read was not performed.`
10264
10487
  );
10265
10488
  }
10266
10489
  } finally {
10267
- fs13.closeSync(fd);
10490
+ fs12.closeSync(fd);
10268
10491
  }
10269
10492
  }
10270
10493
  function verifyStillAbsent(p) {
@@ -10311,7 +10534,7 @@ function readFileText(p) {
10311
10534
  return null;
10312
10535
  }
10313
10536
  if (pinned !== void 0) return redactIfDotenv(p, decodeSource(readPinnedBytes(p, pinned)));
10314
- return redactIfDotenv(p, decodeSource(fs13.readFileSync(p)));
10537
+ return redactIfDotenv(p, decodeSource(fs12.readFileSync(p)));
10315
10538
  } catch (err) {
10316
10539
  if (err instanceof ConfinementIdentityError) throw err;
10317
10540
  return null;
@@ -10329,9 +10552,9 @@ function readFileBytes(p) {
10329
10552
  if (bytes.length > MAX_ZIP_INPUT_BYTES) throw new ZipInputTooLargeError(p, bytes.length, MAX_ZIP_INPUT_BYTES);
10330
10553
  return bytes;
10331
10554
  }
10332
- const stat = fs13.statSync(p);
10555
+ const stat = fs12.statSync(p);
10333
10556
  if (stat.size > MAX_ZIP_INPUT_BYTES) throw new ZipInputTooLargeError(p, stat.size, MAX_ZIP_INPUT_BYTES);
10334
- return fs13.readFileSync(p);
10557
+ return fs12.readFileSync(p);
10335
10558
  } catch (err) {
10336
10559
  if (err instanceof ConfinementIdentityError || err instanceof ZipInputTooLargeError) throw err;
10337
10560
  return null;
@@ -10349,6 +10572,8 @@ function resolveBody(entry) {
10349
10572
  var STALE_WARNING = "\u26A0 STALE: index is older than the file on disk (worker hasn't reindexed yet \u2014 retry shortly, or read the file directly)";
10350
10573
  var DELETED_WARNING = "\u26A0 DELETED: this file is no longer on disk \u2014 what follows is what the index last saw of it";
10351
10574
  var DELETED_TAG = "\u26A0 DELETED: file no longer on disk";
10575
+ var STALE_TAG = "\u26A0 STALE: file changed on disk and could not be reindexed";
10576
+ var EMPTY_PATH_SET = /* @__PURE__ */ new Set();
10352
10577
  function fileIsGone(absPath) {
10353
10578
  if (!path12.isAbsolute(absPath)) return false;
10354
10579
  return fileIsAbsent(absPath);
@@ -10388,6 +10613,21 @@ function healStaleIndex(resolvedPath) {
10388
10613
  }
10389
10614
  }
10390
10615
  var STALE_CHECK_FILE_CAP = 25;
10616
+ function healStaleResultFiles(filePaths) {
10617
+ const checked = /* @__PURE__ */ new Set();
10618
+ const stillStale = /* @__PURE__ */ new Set();
10619
+ let healed = false;
10620
+ for (const raw of filePaths) {
10621
+ if (checked.size >= STALE_CHECK_FILE_CAP) break;
10622
+ if (checked.has(raw) || fileIsGone(raw)) continue;
10623
+ checked.add(raw);
10624
+ if (staleWarning(raw) === "") continue;
10625
+ healStaleIndex(raw);
10626
+ if (staleWarning(raw) === "") healed = true;
10627
+ else stillStale.add(raw);
10628
+ }
10629
+ return { healed, stillStale };
10630
+ }
10391
10631
  function warnIfFilesStale(filePaths) {
10392
10632
  const checked = /* @__PURE__ */ new Set();
10393
10633
  let staleCount = 0;
@@ -10436,7 +10676,7 @@ function sumFileSizes(filePaths) {
10436
10676
  let total = 0;
10437
10677
  for (const fp of new Set(filePaths)) {
10438
10678
  try {
10439
- total += Math.min(fs13.statSync(fp).size, PER_FILE_COUNTERFACTUAL_CEILING);
10679
+ total += Math.min(fs12.statSync(fp).size, PER_FILE_COUNTERFACTUAL_CEILING);
10440
10680
  } catch {
10441
10681
  }
10442
10682
  }
@@ -10507,7 +10747,13 @@ function runSymbol(opts) {
10507
10747
  }
10508
10748
  if (opts.file === void 0 && opts.projectRoot !== void 0) queryOpts.rootDir = opts.projectRoot;
10509
10749
  if (opts.file === void 0 && queryOpts.rootDir === void 0 && confinedRoot !== null) queryOpts.rootDir = confinedRoot;
10510
- const rawResults = querySymbols(queryOpts);
10750
+ let rawResults = querySymbols(queryOpts);
10751
+ let stillStale = EMPTY_PATH_SET;
10752
+ if (opts.file === void 0) {
10753
+ const heal = healStaleResultFiles(rawResults.map((s) => s.filePath));
10754
+ stillStale = heal.stillStale;
10755
+ if (heal.healed) rawResults = querySymbols(queryOpts);
10756
+ }
10511
10757
  const preFilterCount = rawResults.length;
10512
10758
  const effectiveLimit = opts.limit ?? 100;
10513
10759
  const anyClientFilter = matchesGrep !== void 0 || excludeTests;
@@ -10589,6 +10835,7 @@ ${emptyIndexMessage(emptyIndexRoot)}`;
10589
10835
  // Only present when true, so a result set of live files stays byte-identical to what this
10590
10836
  // command has always emitted and only the genuinely-gone rows grow a field.
10591
10837
  ...fileIsGone(s.filePath) ? { deleted: true } : {},
10838
+ ...stillStale.has(s.filePath) ? { stale: true } : {},
10592
10839
  ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: hasRealDocstring(s.docstring) } : {}
10593
10840
  }));
10594
10841
  const payload = { items, truncated: truncatedFlag, totalCount: trueTotal };
@@ -10599,7 +10846,8 @@ ${emptyIndexMessage(emptyIndexRoot)}`;
10599
10846
  const blocks = results.map((sym) => {
10600
10847
  const statsStr = formatStatsSuffix(refCounts, sym);
10601
10848
  const goneTag = fileIsGone(sym.filePath) ? ` ${DELETED_TAG}` : "";
10602
- const header = `# ${sym.name} (${sym.kind}) \u2014 ${formatSymbolLocation(toDisplayPath(symbolDisplayRoot, sym.filePath), sym.lineStart, sym.lineEnd)}${statsStr}${goneTag}`;
10849
+ const staleTag = stillStale.has(sym.filePath) ? ` ${STALE_TAG}` : "";
10850
+ const header = `# ${sym.name} (${sym.kind}) \u2014 ${formatSymbolLocation(toDisplayPath(symbolDisplayRoot, sym.filePath), sym.lineStart, sym.lineEnd)}${statsStr}${goneTag}${staleTag}`;
10603
10851
  const body = resolveBody(sym);
10604
10852
  const bodyLines = body.split(/\r?\n/);
10605
10853
  const preview = bodyLines.slice(0, SYMBOL_PREVIEW_LINES).join("\n");
@@ -10676,10 +10924,42 @@ function runRead(opts) {
10676
10924
  const scanned = querySymbols({ filePath: resolved, limit: FIND_SCAN_LIMIT2 }).map((s) => s.name);
10677
10925
  const closes = rankSimilarNames(scanned, symbol);
10678
10926
  if (closes.length > 0) messages.push(didYouMean(closes));
10679
- else if (scanned.length > 0) messages.push(`Try: token-goat outline ${file}`);
10680
- else if (fs13.existsSync(resolved)) {
10681
- const gap = symbolExtractorGap(file, resolved);
10682
- if (gap !== void 0) messages.push(gap);
10927
+ else if (scanned.length > 0) {
10928
+ if (/\.(yaml|yml)$/i.test(file)) {
10929
+ messages.push(`Try: token-goat yaml-outline ${file}
10930
+ Query subtree: token-goat yaml-query ${file} '<path>'`);
10931
+ } else if (/\.xml$/i.test(file)) {
10932
+ messages.push(`Try: token-goat xml-outline ${file}
10933
+ Query subtree: token-goat xml-query ${file} '<path>'`);
10934
+ } else if (/\.json$/i.test(file)) {
10935
+ messages.push(`Try: token-goat json-outline ${file}
10936
+ Query subtree: token-goat json-query ${file} '<path>'`);
10937
+ } else {
10938
+ messages.push(`Try: token-goat outline ${file}`);
10939
+ }
10940
+ } else if (fs12.existsSync(resolved)) {
10941
+ if (/\.(yaml|yml)$/i.test(file)) {
10942
+ messages.push(
10943
+ `'${file}' is a YAML file -- YAML keys below top level are not symbols; inspect structure or query values with:
10944
+ token-goat yaml-outline ${file}
10945
+ token-goat yaml-query ${file} '<path>'`
10946
+ );
10947
+ } else if (/\.xml$/i.test(file)) {
10948
+ messages.push(
10949
+ `'${file}' is an XML file -- inspect structure or query nodes with:
10950
+ token-goat xml-outline ${file}
10951
+ token-goat xml-query ${file} '<path>'`
10952
+ );
10953
+ } else if (/\.json$/i.test(file)) {
10954
+ messages.push(
10955
+ `'${file}' is a JSON file -- inspect structure or query values with:
10956
+ token-goat json-outline ${file}
10957
+ token-goat json-query ${file} '<path>'`
10958
+ );
10959
+ } else {
10960
+ const gap = symbolExtractorGap(file, resolved);
10961
+ if (gap !== void 0) messages.push(gap);
10962
+ }
10683
10963
  }
10684
10964
  return { text: messages.join("\n"), code: 1 };
10685
10965
  }
@@ -11228,7 +11508,7 @@ async function runImageMeta(file) {
11228
11508
  if (!isImagePath(file)) {
11229
11509
  throw new Error(`Not an image file: ${file}`);
11230
11510
  }
11231
- const data = fs13.readFileSync(file);
11511
+ const data = fs12.readFileSync(file);
11232
11512
  const bytes = data.length;
11233
11513
  let probe;
11234
11514
  try {
@@ -11240,15 +11520,17 @@ async function runImageMeta(file) {
11240
11520
  throw e;
11241
11521
  }
11242
11522
  if (probe === null) {
11243
- return { width: 0, height: 0, format: null, bytes, decodable: false, wouldShrink: false, shrunkBytes: null };
11523
+ return { width: 0, height: 0, format: null, bytes, decodable: false, shrinkable: false, wouldShrink: false, shrunkBytes: null };
11244
11524
  }
11245
- const shrink = await shrinkImage(data, { sizeThresholdBytes: 0 });
11525
+ const shrinkable = canShrinkFormat(probe.format);
11526
+ const shrink = shrinkable ? await shrinkImage(data, { sizeThresholdBytes: 0 }) : null;
11246
11527
  return {
11247
11528
  width: probe.width,
11248
11529
  height: probe.height,
11249
11530
  format: probe.format,
11250
11531
  bytes,
11251
11532
  decodable: true,
11533
+ shrinkable,
11252
11534
  wouldShrink: shrink !== null,
11253
11535
  shrunkBytes: shrink !== null ? shrink.shrunkBytes : null
11254
11536
  };
@@ -11260,7 +11542,7 @@ async function runImageText(file, lang) {
11260
11542
  if (!isImagePath(file)) {
11261
11543
  throw new Error(`Not an image file: ${file}`);
11262
11544
  }
11263
- const data = fs13.readFileSync(file);
11545
+ const data = fs12.readFileSync(file);
11264
11546
  const ocr = await ocrImage(data, lang);
11265
11547
  if (ocr === null) {
11266
11548
  if (ocrIntegrityFailed()) {
@@ -11333,7 +11615,7 @@ function runGrep(opts) {
11333
11615
  if (activePins !== null) {
11334
11616
  let realDir;
11335
11617
  try {
11336
- realDir = foldRealpath(fs13.realpathSync(dir));
11618
+ realDir = foldRealpath(fs12.realpathSync(dir));
11337
11619
  } catch {
11338
11620
  return;
11339
11621
  }
@@ -11342,12 +11624,12 @@ function runGrep(opts) {
11342
11624
  visitedRealDirs.add(realDir);
11343
11625
  }
11344
11626
  try {
11345
- for (const entry of fs13.readdirSync(dir)) {
11627
+ for (const entry of fs12.readdirSync(dir)) {
11346
11628
  if (entry.startsWith(".")) continue;
11347
11629
  const full = path12.join(dir, entry);
11348
11630
  let lst;
11349
11631
  try {
11350
- lst = fs13.lstatSync(full);
11632
+ lst = fs12.lstatSync(full);
11351
11633
  } catch {
11352
11634
  continue;
11353
11635
  }
@@ -11355,7 +11637,7 @@ function runGrep(opts) {
11355
11637
  if (lst.isSymbolicLink()) {
11356
11638
  let real;
11357
11639
  try {
11358
- real = fs13.realpathSync(full);
11640
+ real = fs12.realpathSync(full);
11359
11641
  } catch {
11360
11642
  continue;
11361
11643
  }
@@ -11364,7 +11646,7 @@ function runGrep(opts) {
11364
11646
  }
11365
11647
  let stat;
11366
11648
  try {
11367
- stat = fs13.statSync(target);
11649
+ stat = fs12.statSync(target);
11368
11650
  } catch {
11369
11651
  continue;
11370
11652
  }
@@ -11384,13 +11666,13 @@ function runGrep(opts) {
11384
11666
  emitErr4(`Path not found: ${searchPath}`);
11385
11667
  return 1;
11386
11668
  }
11387
- const stat = fs13.statSync(searchPath);
11669
+ const stat = fs12.statSync(searchPath);
11388
11670
  if (stat.isDirectory()) {
11389
11671
  const pinned = activePins?.get(pinKey(path12.resolve(searchPath)));
11390
11672
  if (pinned !== void 0) verifyPin(searchPath, pinned);
11391
11673
  let boundaryReal;
11392
11674
  try {
11393
- boundaryReal = fs13.realpathSync(searchPath);
11675
+ boundaryReal = fs12.realpathSync(searchPath);
11394
11676
  } catch (err) {
11395
11677
  if (pinned === void 0) {
11396
11678
  boundaryReal = path12.resolve(searchPath);
@@ -11456,7 +11738,7 @@ async function runSemantic(query, opts) {
11456
11738
  }
11457
11739
  const n = opts.limit !== void 0 && Number.isFinite(opts.limit) ? opts.limit : 20;
11458
11740
  if (opts.projectRoot !== void 0) {
11459
- if (!path12.isAbsolute(opts.projectRoot) || !fs13.existsSync(opts.projectRoot) || !fs13.statSync(opts.projectRoot).isDirectory()) {
11741
+ if (!path12.isAbsolute(opts.projectRoot) || !fs12.existsSync(opts.projectRoot) || !fs12.statSync(opts.projectRoot).isDirectory()) {
11460
11742
  const message = `token-goat: projectRoot must be an absolute, existing directory, got '${opts.projectRoot}'`;
11461
11743
  if (opts.json === true) {
11462
11744
  return { text: displaySafeJson({ error: message }), code: 1 };
@@ -11465,6 +11747,39 @@ async function runSemantic(query, opts) {
11465
11747
  }
11466
11748
  }
11467
11749
  const rootDir = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
11750
+ let projectCoverage;
11751
+ try {
11752
+ projectCoverage = getEmbeddingCoverage(globalDbPath(), rootDir);
11753
+ } catch {
11754
+ }
11755
+ if (opts.preflight === true) {
11756
+ const preflight2 = await checkEmbeddingPreflight({
11757
+ ...opts.warm !== void 0 ? { warm: opts.warm } : {},
11758
+ projectRoot: rootDir,
11759
+ ...projectCoverage !== void 0 ? { coverage: projectCoverage } : {}
11760
+ });
11761
+ if (opts.json === true) {
11762
+ return { text: displaySafeJson(preflight2), code: preflight2.status === "ready" ? 0 : 1 };
11763
+ }
11764
+ const lines = [
11765
+ `Semantic embedding status: ${preflight2.status.toUpperCase()}`,
11766
+ ` Summary: ${preflight2.summary}`,
11767
+ ` Config (indexing.embeddings_enabled): ${preflight2.configEnabled ? "enabled" : "disabled"}`,
11768
+ ` ONNX runtime (onnxruntime-node): ${preflight2.runtimeAvailable ? "available" : "missing"}`,
11769
+ ` Model files (~34 MB): ${preflight2.modelFilesPresent ? "present" : "missing"}`,
11770
+ ` In-memory session: ${preflight2.modelWarmed ? "ready / warmed" : "not loaded"}`,
11771
+ ` Project coverage: ${preflight2.embeddedFiles}/${countNoun(preflight2.indexedFiles, "file")} (${preflight2.coveragePercent}%)`
11772
+ ];
11773
+ if (preflight2.actionRequired) {
11774
+ lines.push(` Action: ${preflight2.actionRequired}`);
11775
+ }
11776
+ return { text: lines.join("\n"), code: preflight2.status === "ready" ? 0 : 1 };
11777
+ }
11778
+ const preflight = await checkEmbeddingPreflight({
11779
+ ...opts.warm !== void 0 ? { warm: opts.warm } : {},
11780
+ projectRoot: rootDir,
11781
+ ...projectCoverage !== void 0 ? { coverage: projectCoverage } : {}
11782
+ });
11468
11783
  const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
11469
11784
  if (embeddingsEnabled && !isAvailable()) {
11470
11785
  console.warn(
@@ -11474,9 +11789,14 @@ async function runSemantic(query, opts) {
11474
11789
  console.warn(
11475
11790
  "Matching on meaning is off (indexing.embeddings_enabled / TOKEN_GOAT_EMBEDDINGS_ENABLED is disabled); these results come from keyword search alone."
11476
11791
  );
11792
+ } else if (preflight.status !== "ready") {
11793
+ console.warn(
11794
+ `Matching on meaning is degraded (${preflight.summary}); these results come from keyword search alone.${preflight.actionRequired ? ` Action: ${preflight.actionRequired}` : ""}`
11795
+ );
11477
11796
  }
11478
11797
  const overFetchForMerge = Math.min(MAX_OVER_FETCH, n * OVER_FETCH_FACTOR);
11479
11798
  let rawHits = [];
11799
+ let searchSemanticError = null;
11480
11800
  if (embeddingsEnabled) {
11481
11801
  try {
11482
11802
  rawHits = await searchSemantic(
@@ -11488,12 +11808,15 @@ async function runSemantic(query, opts) {
11488
11808
  rootDir
11489
11809
  );
11490
11810
  } catch (e) {
11491
- console.warn(
11492
- `Matching on meaning is off (${extractErrorMessage(e)}); these results come from keyword search alone.`
11493
- );
11811
+ searchSemanticError = extractErrorMessage(e);
11812
+ if (preflight.status === "ready") {
11813
+ console.warn(
11814
+ `Matching on meaning is off (${searchSemanticError}); these results come from keyword search alone.`
11815
+ );
11816
+ }
11494
11817
  }
11495
11818
  }
11496
- if (rawHits.length === 0 && isAvailable()) {
11819
+ if (rawHits.length === 0 && isAvailable() && preflight.status === "ready" && !searchSemanticError) {
11497
11820
  try {
11498
11821
  const enabled = loadConfig().indexing.embeddings_enabled;
11499
11822
  const { indexedFiles, embeddedFiles } = enabled ? getEmbeddingCoverage(globalDbPath(), rootDir) : { indexedFiles: 0, embeddedFiles: 0 };
@@ -11571,7 +11894,21 @@ async function runSemantic(query, opts) {
11571
11894
  }));
11572
11895
  const capped = guardJsonRows(items);
11573
11896
  const limitTruncated = hits.length < eligibleCount;
11574
- const text3 = displaySafeJson({ source, ...capped, truncated: capped.truncated || limitTruncated, totalCount: eligibleCount, ...candidatesClipped ? { totalCountIsFloor: true } : {} });
11897
+ const text3 = displaySafeJson({
11898
+ source,
11899
+ ...capped,
11900
+ truncated: capped.truncated || limitTruncated,
11901
+ totalCount: eligibleCount,
11902
+ ...candidatesClipped ? { totalCountIsFloor: true } : {},
11903
+ ...preflight.status !== "ready" ? {
11904
+ preflightStatus: preflight.status,
11905
+ warning: preflight.summary,
11906
+ ...preflight.actionRequired ? { actionRequired: preflight.actionRequired } : {}
11907
+ } : searchSemanticError !== null ? {
11908
+ preflightStatus: "degraded",
11909
+ warning: `Matching on meaning failed (${searchSemanticError}); results come from keyword search alone.`
11910
+ } : {}
11911
+ });
11575
11912
  recordReadStat("semantic_search", sumFileSizes(hits.map((h) => h.filePath)), text3, query);
11576
11913
  return { text: text3, code: 0 };
11577
11914
  }
@@ -11634,19 +11971,54 @@ ${previewLines(entry.text, 3)}`).join("\n\n"),
11634
11971
  }
11635
11972
  const indexEmpty = isIndexEmptyForProject(globalDbPath(), rootDir);
11636
11973
  if (opts.json === true) {
11637
- const payload = indexEmpty ? { source: "fts", items: [], truncated: false, totalCount: 0, indexEmpty: true, hint: emptyIndexMessage(rootDir) } : { source: "fts", items: [], truncated: false, totalCount: 0 };
11974
+ const payload = indexEmpty ? {
11975
+ source: "fts",
11976
+ items: [],
11977
+ truncated: false,
11978
+ totalCount: 0,
11979
+ indexEmpty: true,
11980
+ hint: emptyIndexMessage(rootDir),
11981
+ ...preflight.status !== "ready" ? {
11982
+ preflightStatus: preflight.status,
11983
+ warning: preflight.summary,
11984
+ ...preflight.actionRequired ? { actionRequired: preflight.actionRequired } : {}
11985
+ } : searchSemanticError !== null ? {
11986
+ preflightStatus: "degraded",
11987
+ warning: `Matching on meaning failed (${searchSemanticError}); results come from keyword search alone.`
11988
+ } : {}
11989
+ } : {
11990
+ source: "fts",
11991
+ items: [],
11992
+ truncated: false,
11993
+ totalCount: 0,
11994
+ ...preflight.status !== "ready" ? {
11995
+ preflightStatus: preflight.status,
11996
+ warning: preflight.summary,
11997
+ ...preflight.actionRequired ? { actionRequired: preflight.actionRequired } : {}
11998
+ } : searchSemanticError !== null ? {
11999
+ preflightStatus: "degraded",
12000
+ warning: `Matching on meaning failed (${searchSemanticError}); results come from keyword search alone.`
12001
+ } : {}
12002
+ };
11638
12003
  const text2 = displaySafeJson(payload);
11639
12004
  return { text: text2, code: 1 };
11640
12005
  }
11641
- const text = indexEmpty ? `token-goat: no matches for '${query}'
12006
+ let text = indexEmpty ? `token-goat: no matches for '${query}'
11642
12007
  ${emptyIndexMessage(rootDir)}` : `token-goat: no matches for '${query}'`;
12008
+ if (preflight.status !== "ready") {
12009
+ text += `
12010
+ (note: semantic indexing is degraded [${preflight.status}]: ${preflight.summary}${preflight.actionRequired ? ` \u2014 ${preflight.actionRequired}` : ""})`;
12011
+ } else if (searchSemanticError !== null) {
12012
+ text += `
12013
+ (note: semantic matching degraded: ${searchSemanticError}; results come from keyword search alone)`;
12014
+ }
11643
12015
  return { text, code: 1 };
11644
12016
  }
11645
12017
 
11646
12018
  // src/read_spec.ts
11647
12019
  function fileExists3(p) {
11648
12020
  try {
11649
- return fs14.statSync(p).isFile();
12021
+ return fs13.statSync(p).isFile();
11650
12022
  } catch {
11651
12023
  return false;
11652
12024
  }
@@ -12059,6 +12431,7 @@ export {
12059
12431
  runImpact,
12060
12432
  runAsk,
12061
12433
  load,
12434
+ queryJson,
12062
12435
  runCsvQuery,
12063
12436
  runCsvProfile,
12064
12437
  runJsonOutline,