token-goat 2.6.33 → 2.6.35

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 (25) hide show
  1. package/README.md +4 -0
  2. package/SECURITY.md +11 -10
  3. package/dist/{token-goat-chunk-FN6WLFIX.mjs → token-goat-chunk-D5IZEGN2.mjs} +817 -53
  4. package/dist/{token-goat-hook-chunk-HXHFLKEL.mjs → token-goat-chunk-DQIT5JIF.mjs} +7 -2
  5. package/dist/{token-goat-chunk-72R7M5H7.mjs → token-goat-chunk-DRAIYVUI.mjs} +16 -14
  6. package/dist/{token-goat-chunk-RPOXYJWK.mjs → token-goat-chunk-HW4VUKJ5.mjs} +77 -26
  7. package/dist/{token-goat-chunk-TDYTBCT4.mjs → token-goat-chunk-I2KCEGIP.mjs} +704 -138
  8. package/dist/{token-goat-chunk-WFRR2DBG.mjs → token-goat-chunk-K7W3P2TJ.mjs} +583 -343
  9. package/dist/{token-goat-chunk-OHQ7RBLL.mjs → token-goat-chunk-SIHYQCTM.mjs} +2 -2
  10. package/dist/{token-goat-chunk-77CX3MCN.mjs → token-goat-chunk-UIZVX3HN.mjs} +2 -2
  11. package/dist/{token-goat-chunk-L6FVTDPD.mjs → token-goat-chunk-Y2NH6DGZ.mjs} +564 -31
  12. package/dist/{token-goat-chunk-HPWPUBAK.mjs → token-goat-chunk-YF67EWRH.mjs} +5 -5
  13. package/dist/{token-goat-hook-chunk-E6AIPKNF.mjs → token-goat-hook-chunk-2O7P4Z6Q.mjs} +579 -339
  14. package/dist/{token-goat-hook-chunk-WBBHV6TZ.mjs → token-goat-hook-chunk-3AAH72SO.mjs} +77 -26
  15. package/dist/{token-goat-hook-chunk-QD2DX2U7.mjs → token-goat-hook-chunk-4POU6OHK.mjs} +564 -31
  16. package/dist/{token-goat-hook-chunk-ZT47M3IA.mjs → token-goat-hook-chunk-CRBMPT74.mjs} +2 -2
  17. package/dist/{token-goat-hook-chunk-26DLRODR.mjs → token-goat-hook-chunk-D6UJEFHX.mjs} +2 -2
  18. package/dist/{token-goat-hook-chunk-XBOWDNJB.mjs → token-goat-hook-chunk-J4HKWUWZ.mjs} +5 -5
  19. package/dist/{token-goat-chunk-RQQ4SY2P.mjs → token-goat-hook-chunk-NIPXIAPA.mjs} +7 -2
  20. package/dist/{token-goat-hook-chunk-VDXQXYJK.mjs → token-goat-hook-chunk-NXVT4F3S.mjs} +817 -53
  21. package/dist/{token-goat-hook-chunk-EKGDLI4D.mjs → token-goat-hook-chunk-VU6VGZBM.mjs} +16 -14
  22. package/dist/{token-goat-hook-chunk-UWV4MTYI.mjs → token-goat-hook-chunk-XM4W3WCO.mjs} +704 -138
  23. package/dist/token-goat-hook.mjs +5 -5
  24. package/dist/token-goat.core.mjs +5 -5
  25. package/package.json +11 -10
@@ -50,7 +50,7 @@ import {
50
50
  walkProject,
51
51
  yamlLineClosesQuote,
52
52
  yamlOpenQuoteAfter
53
- } from "./token-goat-chunk-TDYTBCT4.mjs";
53
+ } from "./token-goat-chunk-I2KCEGIP.mjs";
54
54
  import {
55
55
  PER_FILE_COUNTERFACTUAL_CEILING,
56
56
  _detectOpenQuote,
@@ -61,11 +61,14 @@ import {
61
61
  colorStdout,
62
62
  compileGrepMatcher,
63
63
  countNoun,
64
+ decodeSource,
64
65
  detectLanguage,
66
+ displaySafeText,
65
67
  ensureNewline,
66
68
  escapeRegExp,
67
69
  excludeTestsHiddenNote,
68
70
  extractErrorMessage,
71
+ fileIsAbsent,
69
72
  findHtmlHeadingMatches,
70
73
  findProject,
71
74
  fingerprintContent,
@@ -97,7 +100,7 @@ import {
97
100
  unsupportedLanguageName,
98
101
  windowsCmdQuoteArg,
99
102
  withExtension
100
- } from "./token-goat-chunk-WFRR2DBG.mjs";
103
+ } from "./token-goat-chunk-K7W3P2TJ.mjs";
101
104
  import {
102
105
  registerReset
103
106
  } from "./token-goat-chunk-AO2QD2AG.mjs";
@@ -387,7 +390,7 @@ function readTextForSections(filePath, readFn) {
387
390
  text = read;
388
391
  } else {
389
392
  try {
390
- text = readFileSync(filePath, "utf-8");
393
+ text = decodeSource(readFileSync(filePath));
391
394
  } catch {
392
395
  return null;
393
396
  }
@@ -489,13 +492,13 @@ function formatJsonOutline(outline) {
489
492
  if (outline.kind === "primitive") return `(scalar ${outline.type})`;
490
493
  if (outline.kind === "object") {
491
494
  if (outline.fields.length === 0) return "(empty object)";
492
- return outline.fields.map((f) => `${f.name}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`).join("\n");
495
+ return outline.fields.map((f) => `${displaySafeText(f.name)}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`).join("\n");
493
496
  }
494
497
  const lines = [`array of ${outline.length} element${outline.length === 1 ? "" : "s"} (${outline.elementType})`];
495
498
  if (outline.sampleKeys !== void 0) {
496
499
  lines.push(outline.heterogeneous === true ? "keys (from first elements, shape varies across sample):" : "keys (from first elements):");
497
500
  for (const f of outline.sampleKeys) {
498
- lines.push(` ${f.name}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`);
501
+ lines.push(` ${displaySafeText(f.name)}: ${f.type}${f.size !== void 0 ? ` (${f.size})` : ""}`);
499
502
  }
500
503
  }
501
504
  return lines.join("\n");
@@ -583,6 +586,474 @@ function queryJson(data, spec) {
583
586
  return evalJsonPath(data, parseJsonPath(spec));
584
587
  }
585
588
 
589
+ // src/xml_query.ts
590
+ var XML_NAME_START = "A-Za-z_:\xC0-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD";
591
+ var XML_NAME_REST = "A-Za-z_:\xC0-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uD800-\uDFFF\uF900-\uFDCF\uFDF0-\uFFFD0-9.\xB7\u0300-\u036F\u203F-\u2040-";
592
+ var XML_NAME = `[${XML_NAME_START}][${XML_NAME_REST}]*`;
593
+ var XML_ATTR_REGION = `(?:[^>"']|"[^"]*"|'[^']*'|["'])*?`;
594
+ var XML_DOCTYPE_TOKEN = `<!DOCTYPE(?:[^>"']|"[^"]*"|'[^']*')*>`;
595
+ function decodeXmlEntities(text) {
596
+ return text.replace(/&(?:quot|apos|lt|gt|amp|#x([0-9a-fA-F]+)|#([0-9]+));/g, (match, hex, dec) => {
597
+ if (match === "&quot;") return '"';
598
+ if (match === "&apos;") return "'";
599
+ if (match === "&lt;") return "<";
600
+ if (match === "&gt;") return ">";
601
+ if (match === "&amp;") return "&";
602
+ if (hex !== void 0) {
603
+ const code = parseInt(hex, 16);
604
+ try {
605
+ return code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : match;
606
+ } catch {
607
+ return match;
608
+ }
609
+ }
610
+ if (dec !== void 0) {
611
+ const code = parseInt(dec, 10);
612
+ try {
613
+ return code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : match;
614
+ } catch {
615
+ return match;
616
+ }
617
+ }
618
+ return match;
619
+ });
620
+ }
621
+ function escapeXmlText(text) {
622
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
623
+ }
624
+ function escapeXmlAttr(text) {
625
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\n/g, "&#xA;").replace(/\r/g, "&#xD;").replace(/\t/g, "&#x9;");
626
+ }
627
+ function parseXmlTree(xmlText) {
628
+ const leadingTrim = xmlText.length - xmlText.trimStart().length;
629
+ let text = xmlText.trim();
630
+ let lineOffset = (xmlText.slice(0, leadingTrim).match(/\n/g) ?? []).length;
631
+ if (text.charCodeAt(0) === 65279) text = text.slice(1);
632
+ let doctype = null;
633
+ const namespaces = {};
634
+ let totalElements = 0;
635
+ const doctypeMatch = new RegExp(
636
+ `<!DOCTYPE\\s+((?:[^>[\\]"']|"[^"]*"|'[^']*'|\\[[\\s\\S]*?\\])*)\\s*>`,
637
+ "i"
638
+ ).exec(text);
639
+ if (doctypeMatch) {
640
+ doctype = doctypeMatch[1]?.trim() ?? null;
641
+ }
642
+ function parseAttributes(attrString) {
643
+ const attrs = {};
644
+ const attrRegex = new RegExp(`(${XML_NAME})(?:\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+)))?`, "g");
645
+ let m;
646
+ while ((m = attrRegex.exec(attrString)) !== null) {
647
+ const name = m[1];
648
+ const rawVal = m[2] !== void 0 ? m[2] : m[3] !== void 0 ? m[3] : m[4] ?? "";
649
+ const val = decodeXmlEntities(rawVal);
650
+ attrs[name] = val;
651
+ if (name === "xmlns" || name.startsWith("xmlns:")) {
652
+ namespaces[name] = val;
653
+ }
654
+ }
655
+ return attrs;
656
+ }
657
+ const tagRegex = new RegExp(
658
+ // eslint-disable-next-line no-misleading-character-class
659
+ `<(\\/)?(${XML_NAME})(${XML_ATTR_REGION})(\\/)?>|<!--[\\s\\S]*?-->|<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>|<\\?[\\s\\S]*?\\?>|${XML_DOCTYPE_TOKEN}`,
660
+ "gi"
661
+ );
662
+ const stack = [];
663
+ let rootNode = null;
664
+ let match;
665
+ let lastIndex = 0;
666
+ let lineScan = 0;
667
+ while ((match = tagRegex.exec(text)) !== null) {
668
+ const fullMatch = match[0];
669
+ const isClosing = match[1] === "/";
670
+ const tagName = match[2];
671
+ const attrText = match[3] ?? "";
672
+ const isSelfClosing = match[4] === "/" || fullMatch.endsWith("/>");
673
+ const cdataContent = match[5];
674
+ if (stack.length > 0 && match.index > lastIndex) {
675
+ const raw = text.slice(lastIndex, match.index);
676
+ if (raw.trim()) {
677
+ stack[stack.length - 1].text += decodeXmlEntities(raw);
678
+ }
679
+ }
680
+ if (cdataContent !== void 0) {
681
+ if (stack.length > 0) {
682
+ stack[stack.length - 1].text += cdataContent;
683
+ }
684
+ lastIndex = tagRegex.lastIndex;
685
+ continue;
686
+ }
687
+ if (!tagName) {
688
+ lastIndex = tagRegex.lastIndex;
689
+ continue;
690
+ }
691
+ while (lineScan < match.index) {
692
+ if (text.charCodeAt(lineScan) === 10) lineOffset++;
693
+ lineScan++;
694
+ }
695
+ const line = lineOffset + 1;
696
+ if (isClosing) {
697
+ if (stack.length > 0) {
698
+ let popIdx = stack.length - 1;
699
+ while (popIdx >= 0 && stack[popIdx].tag !== tagName) {
700
+ popIdx--;
701
+ }
702
+ if (popIdx >= 0) {
703
+ while (stack.length > popIdx) {
704
+ stack.pop();
705
+ }
706
+ }
707
+ }
708
+ } else {
709
+ totalElements++;
710
+ const attrs = parseAttributes(attrText);
711
+ const newNode = {
712
+ tag: tagName,
713
+ attributes: attrs,
714
+ children: [],
715
+ text: "",
716
+ line
717
+ };
718
+ if (stack.length === 0) {
719
+ if (!rootNode) rootNode = newNode;
720
+ } else {
721
+ stack[stack.length - 1].children.push(newNode);
722
+ }
723
+ if (!isSelfClosing) {
724
+ stack.push(newNode);
725
+ }
726
+ }
727
+ lastIndex = tagRegex.lastIndex;
728
+ }
729
+ return { root: rootNode, namespaces, doctype, totalElements };
730
+ }
731
+ function outlineXml(xmlText, opts = {}) {
732
+ const maxDepth = opts.maxDepth ?? 4;
733
+ const { root, namespaces, doctype, totalElements } = parseXmlTree(xmlText);
734
+ if (!root) {
735
+ throw new Error("No valid XML root element found");
736
+ }
737
+ const uniqueTags = /* @__PURE__ */ new Set();
738
+ let maxTreeDepth = 1;
739
+ function buildElementOutline(node, currentDepth) {
740
+ uniqueTags.add(node.tag);
741
+ if (currentDepth > maxTreeDepth) maxTreeDepth = currentDepth;
742
+ const keyAttrs = {};
743
+ for (const [k, v] of Object.entries(node.attributes)) {
744
+ if (!k.startsWith("xmlns")) {
745
+ keyAttrs[k] = v;
746
+ }
747
+ }
748
+ const children = [];
749
+ if (currentDepth < maxDepth) {
750
+ const tagCounts = {};
751
+ for (const child of node.children) {
752
+ tagCounts[child.tag] = (tagCounts[child.tag] || 0) + 1;
753
+ }
754
+ for (const child of node.children) {
755
+ const count = tagCounts[child.tag] || 0;
756
+ const existingWithTag = children.filter((c) => c.tag === child.tag).length;
757
+ if (count > 5 && existingWithTag >= 3) {
758
+ if (existingWithTag === 3) {
759
+ children.push({
760
+ tag: `... (${count - 3} more <${child.tag}> elements)`,
761
+ attributes: {},
762
+ childCount: 0,
763
+ children: [],
764
+ textLength: 0
765
+ });
766
+ }
767
+ continue;
768
+ }
769
+ children.push(buildElementOutline(child, currentDepth + 1));
770
+ }
771
+ }
772
+ return {
773
+ tag: node.tag,
774
+ attributes: keyAttrs,
775
+ childCount: node.children.length,
776
+ children,
777
+ textLength: node.text.length
778
+ };
779
+ }
780
+ const tree = buildElementOutline(root, 1);
781
+ return {
782
+ rootTag: root.tag,
783
+ namespaces,
784
+ doctype,
785
+ totalElements,
786
+ uniqueTags: Array.from(uniqueTags).sort(),
787
+ maxDepth: maxTreeDepth,
788
+ tree
789
+ };
790
+ }
791
+ function formatXmlOutline(summary) {
792
+ const lines = [];
793
+ lines.push(
794
+ `Root element: <${summary.rootTag}> (${summary.totalElements} total elements, ${summary.uniqueTags.length} unique tags, max depth ${summary.maxDepth})`
795
+ );
796
+ if (summary.doctype) {
797
+ lines.push(`DOCTYPE: ${summary.doctype}`);
798
+ }
799
+ const nsKeys = Object.keys(summary.namespaces);
800
+ if (nsKeys.length > 0) {
801
+ lines.push("Namespaces:");
802
+ for (const [k, v] of Object.entries(summary.namespaces)) {
803
+ lines.push(` ${k}: ${v}`);
804
+ }
805
+ }
806
+ lines.push("");
807
+ lines.push("Element hierarchy:");
808
+ function renderNode(node, indent) {
809
+ const pad = " ".repeat(indent);
810
+ const attrParts = [];
811
+ const attrKeys = Object.keys(node.attributes);
812
+ for (const [k, v] of Object.entries(node.attributes)) {
813
+ if (["id", "name", "type", "key", "class", "code", "status", "value"].includes(k.toLowerCase()) || attrKeys.length <= 3) {
814
+ const valPreview = v.length > 35 ? `${v.slice(0, 32)}...` : v;
815
+ attrParts.push(`${k}="${valPreview}"`);
816
+ }
817
+ }
818
+ const attrStr = attrParts.length > 0 ? ` [${attrParts.join(" ")}]` : "";
819
+ const countStr = node.childCount > 0 ? ` (${node.childCount} children)` : node.textLength > 0 ? ` (~${node.textLength} chars text)` : " (empty)";
820
+ lines.push(`${pad}<${node.tag}>${attrStr}${countStr}`);
821
+ for (const child of node.children) {
822
+ renderNode(child, indent + 1);
823
+ }
824
+ }
825
+ renderNode(summary.tree, 0);
826
+ return lines.join("\n");
827
+ }
828
+ function parseXmlPath(pathStr) {
829
+ let normalized = pathStr.trim();
830
+ if (normalized === "" || normalized === "/") return [];
831
+ const isGlobalRecursive = normalized.startsWith("//");
832
+ if (isGlobalRecursive) {
833
+ normalized = normalized.slice(2);
834
+ } else if (normalized.startsWith("/")) {
835
+ normalized = normalized.slice(1);
836
+ }
837
+ const segments = [];
838
+ let inBracket = false;
839
+ let currentSegment = "";
840
+ for (let i = 0; i < normalized.length; i++) {
841
+ const ch = normalized[i];
842
+ if (ch === "[") {
843
+ inBracket = true;
844
+ currentSegment += ch;
845
+ } else if (ch === "]") {
846
+ inBracket = false;
847
+ currentSegment += ch;
848
+ } else if (!inBracket && (ch === "/" || ch === ".")) {
849
+ if (currentSegment) {
850
+ segments.push(currentSegment);
851
+ currentSegment = "";
852
+ }
853
+ if (ch === "/" && normalized[i + 1] === "/") {
854
+ segments.push("//");
855
+ i++;
856
+ }
857
+ } else {
858
+ currentSegment += ch;
859
+ }
860
+ }
861
+ if (currentSegment) segments.push(currentSegment);
862
+ const steps = [];
863
+ let nextIsRecursive = isGlobalRecursive;
864
+ for (let i = 0; i < segments.length; i++) {
865
+ const s = segments[i];
866
+ if (s === "//") {
867
+ nextIsRecursive = true;
868
+ continue;
869
+ }
870
+ const isRecursive = nextIsRecursive;
871
+ nextIsRecursive = false;
872
+ const attrSelectMatch = /^@([a-zA-Z0-9_:.\\-]+|\*)$/.exec(s);
873
+ if (attrSelectMatch) {
874
+ steps.push({ tag: "", isRecursive, attributeSelect: attrSelectMatch[1] });
875
+ continue;
876
+ }
877
+ const indexMatch = /\[(-?\d+)\]$/.exec(s);
878
+ const wildcardMatch = /\[\*\]$/.exec(s);
879
+ const attrMatch = /\[@?([a-zA-Z0-9_:.\\-]+)(?:(!?=)\s*["']?([^"'\]]*)["']?)?\]$/.exec(s);
880
+ let tag = s.replace(/\[.*\]$/, "");
881
+ if (!tag) tag = "*";
882
+ let index;
883
+ let allIndices;
884
+ let attributeFilter;
885
+ if (indexMatch) {
886
+ index = parseInt(indexMatch[1], 10);
887
+ } else if (wildcardMatch) {
888
+ allIndices = true;
889
+ } else if (attrMatch) {
890
+ const [, attrName, op, attrVal] = attrMatch;
891
+ attributeFilter = {
892
+ name: attrName,
893
+ ...attrVal !== void 0 ? { value: attrVal } : {},
894
+ ...op === "!=" ? { notEqual: true } : {}
895
+ };
896
+ }
897
+ const step = { tag, isRecursive };
898
+ if (index !== void 0) step.index = index;
899
+ if (allIndices !== void 0) step.allIndices = allIndices;
900
+ if (attributeFilter !== void 0) step.attributeFilter = attributeFilter;
901
+ steps.push(step);
902
+ }
903
+ return steps;
904
+ }
905
+ function serializeXmlNode(node, indent = 0) {
906
+ const pad = " ".repeat(indent);
907
+ const attrParts = [];
908
+ for (const [k, v] of Object.entries(node.attributes)) {
909
+ attrParts.push(`${k}="${escapeXmlAttr(v)}"`);
910
+ }
911
+ const attrStr = attrParts.length > 0 ? " " + attrParts.join(" ") : "";
912
+ if (node.children.length === 0 && !node.text) {
913
+ return `${pad}<${node.tag}${attrStr}/>`;
914
+ }
915
+ if (node.children.length === 0) {
916
+ return `${pad}<${node.tag}${attrStr}>${escapeXmlText(node.text)}</${node.tag}>`;
917
+ }
918
+ const lines = [];
919
+ lines.push(`${pad}<${node.tag}${attrStr}>`);
920
+ if (node.text) {
921
+ lines.push(`${pad} ${escapeXmlText(node.text)}`);
922
+ }
923
+ for (const child of node.children) {
924
+ lines.push(serializeXmlNode(child, indent + 1));
925
+ }
926
+ lines.push(`${pad}</${node.tag}>`);
927
+ return lines.join("\n");
928
+ }
929
+ function getAllDescendants(node) {
930
+ const desc = [];
931
+ function walk(n) {
932
+ for (const c of n.children) {
933
+ desc.push(c);
934
+ walk(c);
935
+ }
936
+ }
937
+ walk(node);
938
+ return desc;
939
+ }
940
+ function xmlNodeToJson(node) {
941
+ const hasAttrs = Object.keys(node.attributes).length > 0;
942
+ const hasChildren = node.children.length > 0;
943
+ if (!hasChildren && !hasAttrs) {
944
+ return node.text;
945
+ }
946
+ if (!hasChildren) {
947
+ const result2 = {};
948
+ for (const [k, v] of Object.entries(node.attributes)) {
949
+ result2[`@${k}`] = v;
950
+ }
951
+ if (node.text) {
952
+ result2["#text"] = node.text;
953
+ }
954
+ return result2;
955
+ }
956
+ const result = {};
957
+ for (const [k, v] of Object.entries(node.attributes)) {
958
+ result[`@${k}`] = v;
959
+ }
960
+ const childGroups = /* @__PURE__ */ new Map();
961
+ for (const child of node.children) {
962
+ const existing = childGroups.get(child.tag);
963
+ if (existing !== void 0) {
964
+ existing.push(child);
965
+ } else {
966
+ childGroups.set(child.tag, [child]);
967
+ }
968
+ }
969
+ for (const [tag, group] of childGroups) {
970
+ if (group.length === 1) {
971
+ result[tag] = xmlNodeToJson(group[0]);
972
+ } else {
973
+ result[tag] = group.map(xmlNodeToJson);
974
+ }
975
+ }
976
+ if (node.text) {
977
+ result["#text"] = node.text;
978
+ }
979
+ return result;
980
+ }
981
+ function queryXml(xmlText, pathStr) {
982
+ const { root } = parseXmlTree(xmlText);
983
+ if (!root) {
984
+ throw new Error("No valid XML root element found");
985
+ }
986
+ const steps = parseXmlPath(pathStr);
987
+ if (steps.length === 0) {
988
+ return { items: [root], fanned: false };
989
+ }
990
+ let currentCandidates = [root];
991
+ let hasFanned = false;
992
+ let isAtRootLevel = true;
993
+ for (let sIdx = 0; sIdx < steps.length; sIdx++) {
994
+ const step = steps[sIdx];
995
+ if (step.attributeSelect !== void 0) {
996
+ const attrVals = [];
997
+ const attrName = step.attributeSelect;
998
+ for (const cand of currentCandidates) {
999
+ if (attrName === "*") {
1000
+ attrVals.push(...Object.values(cand.attributes));
1001
+ } else if (cand.attributes[attrName] !== void 0) {
1002
+ attrVals.push(cand.attributes[attrName]);
1003
+ }
1004
+ }
1005
+ return {
1006
+ items: currentCandidates,
1007
+ attributeValues: attrVals,
1008
+ fanned: hasFanned || attrVals.length > 1
1009
+ };
1010
+ }
1011
+ const nextCandidates = [];
1012
+ const evaluateOnCurrentNode = isAtRootLevel && !step.isRecursive && (step.tag === "*" || step.tag.toLowerCase() === root.tag.toLowerCase());
1013
+ isAtRootLevel = false;
1014
+ for (const cand of currentCandidates) {
1015
+ let targets;
1016
+ if (evaluateOnCurrentNode) {
1017
+ targets = [cand];
1018
+ } else if (step.isRecursive) {
1019
+ targets = [cand, ...getAllDescendants(cand)];
1020
+ } else {
1021
+ targets = cand.children;
1022
+ }
1023
+ let matching = targets.filter((c) => step.tag === "*" || c.tag.toLowerCase() === step.tag.toLowerCase());
1024
+ if (step.attributeFilter) {
1025
+ const af = step.attributeFilter;
1026
+ matching = matching.filter((c) => {
1027
+ const attrVal = c.attributes[af.name];
1028
+ if (attrVal !== void 0) {
1029
+ if (af.value === void 0) return true;
1030
+ return af.notEqual ? attrVal !== af.value : attrVal === af.value;
1031
+ }
1032
+ const childElem = c.children.find((child) => child.tag.toLowerCase() === af.name.toLowerCase());
1033
+ if (childElem !== void 0) {
1034
+ if (af.value === void 0) return true;
1035
+ return af.notEqual ? childElem.text !== af.value : childElem.text === af.value;
1036
+ }
1037
+ return af.notEqual === true;
1038
+ });
1039
+ }
1040
+ if (step.index !== void 0) {
1041
+ const idx = step.index < 0 ? matching.length + step.index : step.index;
1042
+ if (idx >= 0 && idx < matching.length) {
1043
+ nextCandidates.push(matching[idx]);
1044
+ }
1045
+ } else {
1046
+ if (step.allIndices || matching.length > 1 || step.attributeFilter !== void 0) {
1047
+ hasFanned = true;
1048
+ }
1049
+ nextCandidates.push(...matching);
1050
+ }
1051
+ }
1052
+ currentCandidates = nextCandidates;
1053
+ }
1054
+ return { items: currentCandidates, fanned: hasFanned || currentCandidates.length > 1 };
1055
+ }
1056
+
586
1057
  // node_modules/js-yaml/dist/js-yaml.mjs
587
1058
  var NOT_RESOLVED = /* @__PURE__ */ Symbol("NOT_RESOLVED");
588
1059
  function defineScalarTag(tagName, options) {
@@ -3094,7 +3565,7 @@ async function listZipEntries(data) {
3094
3565
  }
3095
3566
  function formatZipList(entries) {
3096
3567
  if (entries.length === 0) return "(no entries found)";
3097
- return entries.map((e) => `${String(e.size).padStart(10)} ${e.path}`).join("\n");
3568
+ return entries.map((e) => `${String(e.size).padStart(10)} ${displaySafeText(e.path)}`).join("\n");
3098
3569
  }
3099
3570
  async function extractZipEntry(data, entryPath) {
3100
3571
  const fflate = await requireFflate();
@@ -4733,8 +5204,8 @@ function readFileText(p) {
4733
5204
  verifyStillAbsent(p);
4734
5205
  return null;
4735
5206
  }
4736
- if (pinned !== void 0) return redactIfDotenv(p, readPinnedBytes(p, pinned).toString("utf-8"));
4737
- return redactIfDotenv(p, fs5.readFileSync(p, "utf-8"));
5207
+ if (pinned !== void 0) return redactIfDotenv(p, decodeSource(readPinnedBytes(p, pinned)));
5208
+ return redactIfDotenv(p, decodeSource(fs5.readFileSync(p)));
4738
5209
  } catch (err) {
4739
5210
  if (err instanceof ConfinementIdentityError) throw err;
4740
5211
  return null;
@@ -4764,11 +5235,21 @@ function resolveBody(entry) {
4764
5235
  return source.split(/\r?\n/).slice(Math.max(0, entry.lineStart - 1), entry.lineEnd).join("\n");
4765
5236
  }
4766
5237
  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)";
5238
+ var DELETED_WARNING = "\u26A0 DELETED: this file is no longer on disk \u2014 what follows is what the index last saw of it";
5239
+ var DELETED_TAG = "\u26A0 DELETED: file no longer on disk";
5240
+ function fileIsGone(absPath) {
5241
+ if (!path5.isAbsolute(absPath)) return false;
5242
+ return fileIsAbsent(absPath);
5243
+ }
4767
5244
  function staleWarning(resolvedPath) {
4768
5245
  const entry = getFileEntry(resolvedPath);
4769
5246
  if (entry === null || entry.sha === "") return "";
4770
5247
  const diskSha = fingerprintFile(resolvedPath);
4771
- if (diskSha === null || diskSha === entry.sha) return "";
5248
+ if (diskSha === null) {
5249
+ return fileIsGone(resolvedPath) ? `${DELETED_WARNING}
5250
+ ` : "";
5251
+ }
5252
+ if (diskSha === entry.sha) return "";
4772
5253
  return `${STALE_WARNING}
4773
5254
  `;
4774
5255
  }
@@ -5055,18 +5536,14 @@ function runSymbol(opts) {
5055
5536
  }
5056
5537
  const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
5057
5538
  const excludeTests = opts.excludeTests === true;
5058
- const confineToProject = !loadConfig().indexing.cross_project_symbols;
5059
- const confinedRoot = confineToProject ? resolveProjectRoot() : null;
5539
+ const confinedRoot = confinedProjectRoot();
5060
5540
  if (confinedRoot !== null) {
5061
5541
  const requested = opts.projectRoot;
5062
- if (requested !== void 0 && !isInsideRoot(requested, confinedRoot)) {
5063
- return { text: `--project is outside this project root, and indexing.cross_project_symbols = false confines symbol lookups to it: ${toDisplayPath(confinedRoot, requested)}`, code: 1 };
5064
- }
5542
+ const projectDenial = requested === void 0 ? null : confinementRefusal("--project", requested, confinedRoot);
5543
+ if (projectDenial !== null) return { text: projectDenial, code: 1 };
5065
5544
  if (opts.file !== void 0) {
5066
- const resolved = resolveIndexPath(opts.file, requested ?? process.cwd());
5067
- if (!isInsideRoot(resolved, confinedRoot)) {
5068
- return { text: `--file is outside this project root, and indexing.cross_project_symbols = false confines symbol lookups to it: ${toDisplayPath(confinedRoot, resolved)}`, code: 1 };
5069
- }
5545
+ const fileDenial = confinementRefusal("--file", resolveIndexPath(opts.file, requested ?? process.cwd()), confinedRoot);
5546
+ if (fileDenial !== null) return { text: fileDenial, code: 1 };
5070
5547
  }
5071
5548
  }
5072
5549
  const queryOpts = {};
@@ -5151,6 +5628,9 @@ ${emptyIndexMessage(emptyIndexRoot)}`;
5151
5628
  const items = capped.items.map((s) => ({
5152
5629
  ...s,
5153
5630
  filePath: toDisplayPath(symbolDisplayRoot, s.filePath),
5631
+ // Only present when true, so a result set of live files stays byte-identical to what this
5632
+ // command has always emitted and only the genuinely-gone rows grow a field.
5633
+ ...fileIsGone(s.filePath) ? { deleted: true } : {},
5154
5634
  ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: hasRealDocstring(s.docstring) } : {}
5155
5635
  }));
5156
5636
  const payload = { items, truncated: truncatedFlag, totalCount: trueTotal };
@@ -5160,7 +5640,8 @@ ${emptyIndexMessage(emptyIndexRoot)}`;
5160
5640
  }
5161
5641
  const blocks = results.map((sym) => {
5162
5642
  const statsStr = formatStatsSuffix(refCounts, sym);
5163
- const header = `# ${sym.name} (${sym.kind}) \u2014 ${toDisplayPath(symbolDisplayRoot, sym.filePath)}:${sym.lineStart}-${sym.lineEnd}${statsStr}`;
5643
+ const goneTag = fileIsGone(sym.filePath) ? ` ${DELETED_TAG}` : "";
5644
+ const header = `# ${sym.name} (${sym.kind}) \u2014 ${toDisplayPath(symbolDisplayRoot, sym.filePath)}:${sym.lineStart}-${sym.lineEnd}${statsStr}${goneTag}`;
5164
5645
  const body = resolveBody(sym);
5165
5646
  const preview = body.split(/\r?\n/).slice(0, 5).join("\n");
5166
5647
  return preview.trim() !== "" ? `${header}
@@ -5169,7 +5650,24 @@ ${preview}` : header;
5169
5650
  const warning = opts.file !== void 0 ? staleWarning(resolveIndexPath(opts.file, opts.projectRoot ?? process.cwd())) : "";
5170
5651
  const text = guardText(warning + blocks.join("\n\n"), "symbol");
5171
5652
  recordReadStat("symbol_lookup", fullSourceBytes, text, opts.name ?? opts.file ?? opts.grep);
5172
- return { text, code: 0 };
5653
+ const symbolTotal = () => anyClientFilter ? { count: filtered.length, exact: rawResults.length < FIND_SCAN_LIMIT } : { count: countSymbols(queryOpts), exact: true };
5654
+ return { text: text + truncationFooter(results.length, effectiveLimit, symbolTotal, "matches", "--limit"), code: 0 };
5655
+ }
5656
+ function truncationFooter(shown, limit, total, plural, flag) {
5657
+ const notice = truncationNotice(shown, limit, total, plural, flag);
5658
+ return notice === null ? "" : `
5659
+
5660
+ token-goat: ${notice}`;
5661
+ }
5662
+ function refsTotal(clientFiltered, filteredTotal, shown, countExact, preScanCount) {
5663
+ if (!clientFiltered) return { count: countExact(), exact: true };
5664
+ return { count: filteredTotal ?? shown, exact: preScanCount < REFS_TOP_SCAN_LIMIT };
5665
+ }
5666
+ function truncationNotice(shown, limit, total, plural, flag) {
5667
+ if (shown < limit) return null;
5668
+ const { count, exact } = total();
5669
+ if (count <= shown) return null;
5670
+ return exact ? `showing ${shown} of ${count} ${plural}; rerun with ${flag} ${count} to see them all` : `showing ${shown} of at least ${count} ${plural}; rerun with ${flag} ${count} and a narrower filter to see more`;
5173
5671
  }
5174
5672
  function parseReadSpec(spec) {
5175
5673
  const colonIdx = findSpecSeparator(spec);
@@ -5311,6 +5809,18 @@ function formatAmbiguity(symbol, file, candidates, explicitRoot, commandName = "
5311
5809
  }
5312
5810
  return lines.join("\n");
5313
5811
  }
5812
+ function confinedProjectRoot() {
5813
+ return loadConfig().indexing.cross_project_symbols ? null : resolveProjectRoot();
5814
+ }
5815
+ function confinementRefusal(label, resolved, root) {
5816
+ if (root === null || isInsideRoot(resolved, root)) return null;
5817
+ return `${label} is outside this project root, and indexing.cross_project_symbols = false confines symbol lookups to it: ${toDisplayPath(root, resolved)}`;
5818
+ }
5819
+ function fileConfinementRefusal(label, file, projectRoot) {
5820
+ const root = confinedProjectRoot();
5821
+ if (root === null) return null;
5822
+ return confinementRefusal(label, resolveIndexPath(file, projectRoot ?? process.cwd()), root);
5823
+ }
5314
5824
  function resolveSymbolSpec(spec, forceRefresh, projectRoot) {
5315
5825
  const { file, symbol: rawSymbol } = parseReadSpec(spec);
5316
5826
  if (rawSymbol === void 0 || rawSymbol === "") return { kind: "none" };
@@ -5318,6 +5828,8 @@ function resolveSymbolSpec(spec, forceRefresh, projectRoot) {
5318
5828
  const symbol = anchorMatch !== null ? anchorMatch[1] : rawSymbol;
5319
5829
  const lineAnchor = anchorMatch !== null ? parseInt(anchorMatch[2], 10) : void 0;
5320
5830
  const resolved = resolveIndexPath(file, projectRoot ?? process.cwd());
5831
+ const confined = confinementRefusal("This file", resolved, confinedProjectRoot());
5832
+ if (confined !== null) return { kind: "confined", message: confined };
5321
5833
  if (forceRefresh === true) {
5322
5834
  indexFileSyncPinned(resolved, globalDbPath());
5323
5835
  enqueueDirtyPathSafe(resolved, { alreadyResolved: true });
@@ -5399,6 +5911,7 @@ function runRead(opts) {
5399
5911
  return { text: guardText(text2, "symbol"), code: 0 };
5400
5912
  }
5401
5913
  const resolution = resolveSymbolSpec(opts.spec, opts.forceRefresh, opts.projectRoot);
5914
+ if (resolution.kind === "confined") return { text: resolution.message, code: 1 };
5402
5915
  if (resolution.kind === "ambiguous") {
5403
5916
  return {
5404
5917
  text: formatAmbiguity(
@@ -5433,6 +5946,9 @@ function runRead(opts) {
5433
5946
  {
5434
5947
  ...match,
5435
5948
  body: resolveBody(match),
5949
+ // The text branch below prepends staleWarning's DELETED line; without this the JSON form
5950
+ // would be the one surface that still passes a deleted file's body off as a live read.
5951
+ ...fileIsGone(match.filePath) ? { deleted: true } : {},
5436
5952
  ...refCounts !== void 0 ? { refCount: refCounts.get(match.name) ?? 0 } : {}
5437
5953
  },
5438
5954
  null,
@@ -5619,6 +6135,15 @@ function parseMultiRefsSpec(spec) {
5619
6135
  const symbols = symPart.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
5620
6136
  return { file, symbols };
5621
6137
  }
6138
+ function refsRootDir(opts) {
6139
+ return opts.projectRoot ?? confinedProjectRoot() ?? void 0;
6140
+ }
6141
+ function refsSpecFiles(spec) {
6142
+ const crossFile = parseCrossFileMultiSpec(spec);
6143
+ if (crossFile !== null) return [...new Set(crossFile.map((p) => p.file))];
6144
+ const { file } = parseMultiRefsSpec(spec);
6145
+ return file === void 0 ? [] : [file];
6146
+ }
5622
6147
  function runRefs(opts) {
5623
6148
  if (opts.limit !== void 0 && opts.limit <= 0) {
5624
6149
  emitErr(`--limit must be a positive number, got: ${opts.limit}`);
@@ -5628,6 +6153,21 @@ function runRefs(opts) {
5628
6153
  emitErr(`--top must be a positive number, got: ${opts.top}`);
5629
6154
  return 1;
5630
6155
  }
6156
+ const confinedRoot = confinedProjectRoot();
6157
+ if (confinedRoot !== null) {
6158
+ const projectDenial = opts.projectRoot === void 0 ? null : confinementRefusal("--project", opts.projectRoot, confinedRoot);
6159
+ if (projectDenial !== null) {
6160
+ emitErr(projectDenial);
6161
+ return 1;
6162
+ }
6163
+ for (const file2 of refsSpecFiles(opts.spec)) {
6164
+ const denial = confinementRefusal("This file", resolveIndexPath(file2, opts.projectRoot ?? process.cwd()), confinedRoot);
6165
+ if (denial !== null) {
6166
+ emitErr(denial);
6167
+ return 1;
6168
+ }
6169
+ }
6170
+ }
5631
6171
  const crossFilePairs = parseCrossFileMultiSpec(opts.spec);
5632
6172
  if (crossFilePairs !== null) return runRefsCrossFile(crossFilePairs, opts);
5633
6173
  const { file, symbols } = parseMultiRefsSpec(opts.spec);
@@ -5641,8 +6181,11 @@ function runRefs(opts) {
5641
6181
  if (opts.excludeTests === true || opts.grep !== void 0) queryOpts.limit = REFS_TOP_SCAN_LIMIT;
5642
6182
  else if (opts.limit !== void 0) queryOpts.limit = opts.limit;
5643
6183
  else if (opts.top !== void 0) queryOpts.limit = REFS_TOP_SCAN_LIMIT;
5644
- if (opts.projectRoot !== void 0) queryOpts.rootDir = opts.projectRoot;
5645
- let results = applyTypedRefsTier(sym, file, queryRefs(queryOpts));
6184
+ const rootDir = refsRootDir(opts);
6185
+ if (rootDir !== void 0) queryOpts.rootDir = rootDir;
6186
+ const scanned = queryRefs(queryOpts);
6187
+ const preScanCount = scanned.length;
6188
+ let results = applyTypedRefsTier(sym, file, scanned);
5646
6189
  let suppressed = 0;
5647
6190
  if (opts.excludeTests === true) {
5648
6191
  const f = applyExcludeTestsFilter(results);
@@ -5689,6 +6232,16 @@ function runRefs(opts) {
5689
6232
  if (opts.excludeTests === true && suppressed > 0) lines.push(` ${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`);
5690
6233
  for (const ref of results) lines.push(...renderRefLines(ref, opts.context ?? 0));
5691
6234
  }
6235
+ if (opts.top === void 0) {
6236
+ const notice = truncationNotice(
6237
+ results.length,
6238
+ opts.limit ?? 100,
6239
+ () => refsTotal(opts.excludeTests === true || matchesGrep !== void 0, filteredTotal, results.length, () => countRefs(queryOpts), preScanCount),
6240
+ "references",
6241
+ "--limit"
6242
+ );
6243
+ if (notice !== null) lines.push(` token-goat: ${notice}`);
6244
+ }
5692
6245
  }
5693
6246
  const fullSourceBytes = sumFileSizes(refFilePaths);
5694
6247
  if (opts.json === true) {
@@ -5715,8 +6268,11 @@ function runRefsCrossFile(pairs, opts) {
5715
6268
  if (opts.excludeTests === true || opts.grep !== void 0) queryOpts.limit = REFS_TOP_SCAN_LIMIT;
5716
6269
  else if (opts.limit !== void 0) queryOpts.limit = opts.limit;
5717
6270
  else if (opts.top !== void 0) queryOpts.limit = REFS_TOP_SCAN_LIMIT;
5718
- if (opts.projectRoot !== void 0) queryOpts.rootDir = opts.projectRoot;
5719
- let results = applyTypedRefsTier(symbol, file, queryRefs(queryOpts));
6271
+ const rootDir = refsRootDir(opts);
6272
+ if (rootDir !== void 0) queryOpts.rootDir = rootDir;
6273
+ const scanned = queryRefs(queryOpts);
6274
+ const preScanCount = scanned.length;
6275
+ let results = applyTypedRefsTier(symbol, file, scanned);
5720
6276
  let suppressed = 0;
5721
6277
  if (opts.excludeTests === true) {
5722
6278
  const f = applyExcludeTestsFilter(results);
@@ -5761,6 +6317,16 @@ function runRefsCrossFile(pairs, opts) {
5761
6317
  if (opts.excludeTests === true && suppressed > 0) lines.push(` ${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`);
5762
6318
  for (const ref of results) lines.push(...renderRefLines(ref, opts.context ?? 0));
5763
6319
  }
6320
+ if (opts.top === void 0) {
6321
+ const notice = truncationNotice(
6322
+ results.length,
6323
+ opts.limit ?? 100,
6324
+ () => refsTotal(opts.excludeTests === true || matchesGrep !== void 0, filteredTotal, results.length, () => countRefs(queryOpts), preScanCount),
6325
+ "references",
6326
+ "--limit"
6327
+ );
6328
+ if (notice !== null) lines.push(` token-goat: ${notice}`);
6329
+ }
5764
6330
  }
5765
6331
  const fullSourceBytes = sumFileSizes(refFilePaths);
5766
6332
  if (opts.json === true) {
@@ -5782,8 +6348,11 @@ function runRefsSingle(opts) {
5782
6348
  if (opts.excludeTests === true || opts.grep !== void 0) queryOpts.limit = REFS_TOP_SCAN_LIMIT;
5783
6349
  else if (opts.limit !== void 0) queryOpts.limit = opts.limit;
5784
6350
  else if (opts.top !== void 0) queryOpts.limit = REFS_TOP_SCAN_LIMIT;
5785
- if (opts.projectRoot !== void 0) queryOpts.rootDir = opts.projectRoot;
5786
- let results = applyTypedRefsTier(symName, defFileHint, queryRefs(queryOpts));
6351
+ const rootDir = refsRootDir(opts);
6352
+ if (rootDir !== void 0) queryOpts.rootDir = rootDir;
6353
+ const scanned = queryRefs(queryOpts);
6354
+ const preScanCount = scanned.length;
6355
+ let results = applyTypedRefsTier(symName, defFileHint, scanned);
5787
6356
  let suppressed = 0;
5788
6357
  if (opts.excludeTests === true) {
5789
6358
  const f = applyExcludeTestsFilter(results);
@@ -5811,15 +6380,15 @@ function runRefsSingle(opts) {
5811
6380
  emitErr(`No non-test references found for '${symName}' (${excludeTestsHiddenNote(suppressed)})`);
5812
6381
  return 1;
5813
6382
  }
5814
- const rootDir = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
5815
- if (querySymbols({ name: symName, rootDir, limit: 1 }).length === 0) {
5816
- emitErr(`Symbol not found: ${symName}${unknownSymbolSuggestion(symName, rootDir)}`);
5817
- if (opts.json !== true && isIndexEmptyForProject(globalDbPath(), rootDir)) emitErr(emptyIndexMessage(rootDir));
6383
+ const rootDir2 = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
6384
+ if (querySymbols({ name: symName, rootDir: rootDir2, limit: 1 }).length === 0) {
6385
+ emitErr(`Symbol not found: ${symName}${unknownSymbolSuggestion(symName, rootDir2)}`);
6386
+ if (opts.json !== true && isIndexEmptyForProject(globalDbPath(), rootDir2)) emitErr(emptyIndexMessage(rootDir2));
5818
6387
  return 1;
5819
6388
  }
5820
6389
  emitErr(`No references found for '${symName}'`);
5821
6390
  if (opts.json !== true) {
5822
- if (isIndexEmptyForProject(globalDbPath(), rootDir)) emitErr(emptyIndexMessage(rootDir));
6391
+ if (isIndexEmptyForProject(globalDbPath(), rootDir2)) emitErr(emptyIndexMessage(rootDir2));
5823
6392
  }
5824
6393
  return 1;
5825
6394
  }
@@ -5840,9 +6409,10 @@ function runRefsSingle(opts) {
5840
6409
  return 0;
5841
6410
  }
5842
6411
  const lines = opts.top !== void 0 ? renderTopFilesSummary(results, opts.top, suppressed) : opts.callers === true ? [...opts.excludeTests === true && suppressed > 0 ? [`${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`] : [], ...renderCallerGroups(results, opts.context ?? 0)] : [...opts.excludeTests === true && suppressed > 0 ? [`${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`] : [], ...results.flatMap((ref) => renderRefLines(ref, opts.context ?? 0, ""))];
6412
+ const refsFooter = opts.top !== void 0 ? "" : truncationFooter(results.length, opts.limit ?? 100, () => refsTotal(opts.excludeTests === true || matchesGrep !== void 0, filteredTotal, results.length, () => countRefs(queryOpts), preScanCount), "references", "--limit");
5843
6413
  const text = lines.join("\n");
5844
- emitGuarded(text, "symbol");
5845
- recordReadStat("symbol_read", fullSourceBytes, text, symName);
6414
+ emit(guardText(text, "symbol") + refsFooter);
6415
+ recordReadStat("symbol_read", fullSourceBytes, text + refsFooter, symName);
5846
6416
  return 0;
5847
6417
  }
5848
6418
  function applyExcludeTestsFilter(refs) {
@@ -5924,6 +6494,8 @@ function filteredToEmptyNotice(preFilterCount, minLines, grep) {
5924
6494
  }
5925
6495
  function prepareSymbolListing(file, opts) {
5926
6496
  const resolved = resolveIndexPath(file, opts.projectRoot ?? process.cwd());
6497
+ const confined = confinementRefusal("This file", resolved, confinedProjectRoot());
6498
+ if (confined !== null) return { kind: "confined", text: confined };
5927
6499
  if (opts.forceRefresh === true) {
5928
6500
  indexFileSyncPinned(resolved, globalDbPath());
5929
6501
  enqueueDirtyPathSafe(resolved, { alreadyResolved: true });
@@ -5984,7 +6556,7 @@ function runSkeleton(opts) {
5984
6556
  const multiFiles = parseMultiFileSpec(opts.file);
5985
6557
  if (multiFiles !== null) return runPerFileListing(multiFiles, (file) => runSkeleton({ ...opts, file, includeFilePath: true }), opts.json === true);
5986
6558
  const prep = prepareSymbolListing(opts.file, opts);
5987
- if (prep.kind === "empty") {
6559
+ if (prep.kind === "confined" || prep.kind === "empty") {
5988
6560
  return { text: prep.text, code: 1 };
5989
6561
  }
5990
6562
  const { resolved, displayRoot, filtered, preFilterCount, refCounts, fullSourceBytes, symbolsTruncated, trueSymbolCount } = prep;
@@ -6001,7 +6573,11 @@ function runSkeleton(opts) {
6001
6573
  const payload = {
6002
6574
  items: capped.items,
6003
6575
  truncated: capped.truncated || symbolsTruncated,
6004
- totalCount: symbolsTruncated ? Math.max(trueSymbolCount ?? 0, capped.totalCount) : capped.totalCount
6576
+ totalCount: symbolsTruncated ? Math.max(trueSymbolCount ?? 0, capped.totalCount) : capped.totalCount,
6577
+ // Envelope-level, not per row: this listing is one named file, so the fact belongs to the
6578
+ // whole payload. Only added when true, so live output keeps the exact three-key shape
6579
+ // tests/json_envelope_shape.test.ts pins.
6580
+ ...fileIsGone(resolved) ? { deleted: true } : {}
6005
6581
  };
6006
6582
  const text2 = JSON.stringify(payload, null, 2);
6007
6583
  recordReadStat("stub_view", fullSourceBytes, text2, opts.file);
@@ -6023,7 +6599,7 @@ function runOutline(opts) {
6023
6599
  const multiFiles = parseMultiFileSpec(opts.file);
6024
6600
  if (multiFiles !== null) return runPerFileListing(multiFiles, (file) => runOutline({ ...opts, file, includeFilePath: true }), opts.json === true);
6025
6601
  const prep = prepareSymbolListing(opts.file, opts);
6026
- if (prep.kind === "empty") {
6602
+ if (prep.kind === "confined" || prep.kind === "empty") {
6027
6603
  return { text: prep.text, code: 1 };
6028
6604
  }
6029
6605
  const { resolved, displayRoot, filtered, preFilterCount, refCounts, fullSourceBytes, symbolsTruncated, trueSymbolCount } = prep;
@@ -6042,7 +6618,9 @@ function runOutline(opts) {
6042
6618
  const payload = {
6043
6619
  items: capped.items,
6044
6620
  truncated: capped.truncated || symbolsTruncated,
6045
- totalCount: symbolsTruncated ? Math.max(trueSymbolCount ?? 0, capped.totalCount) : capped.totalCount
6621
+ totalCount: symbolsTruncated ? Math.max(trueSymbolCount ?? 0, capped.totalCount) : capped.totalCount,
6622
+ // Same envelope-level flag, and same reason, as runSkeleton's payload above.
6623
+ ...fileIsGone(resolved) ? { deleted: true } : {}
6046
6624
  };
6047
6625
  const text2 = JSON.stringify(payload, null, 2);
6048
6626
  recordReadStat("outline", fullSourceBytes, text2, opts.file);
@@ -6228,6 +6806,133 @@ function runYamlOutline(opts) {
6228
6806
  function runYamlQuery(opts) {
6229
6807
  return runQueryCommand(opts, parseYamlDocument, "YAML", "yaml-query", "yaml_query");
6230
6808
  }
6809
+ function runXmlOutline(opts) {
6810
+ const text = readFileText(opts.file);
6811
+ if (text === null) {
6812
+ emitErr(`Could not read: ${opts.file}`);
6813
+ return 1;
6814
+ }
6815
+ let summary;
6816
+ try {
6817
+ summary = outlineXml(text, { ...opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {} });
6818
+ } catch (e) {
6819
+ emitErr(`Failed to parse XML: ${opts.file}
6820
+ ${extractErrorMessage(e)}`);
6821
+ return 1;
6822
+ }
6823
+ const fullSourceBytes = sumFileSizes([opts.file]);
6824
+ if (opts.json === true) {
6825
+ const jsonText = JSON.stringify(summary, null, 2);
6826
+ emit(jsonText);
6827
+ recordReadStat("xml_outline", fullSourceBytes, jsonText, opts.file);
6828
+ } else {
6829
+ const outlineText = formatXmlOutline(summary);
6830
+ emitGuarded(outlineText, "xml-outline");
6831
+ recordReadStat("xml_outline", fullSourceBytes, outlineText, opts.file);
6832
+ }
6833
+ return 0;
6834
+ }
6835
+ function runXmlQuery(opts) {
6836
+ const text = readFileText(opts.file);
6837
+ if (text === null) {
6838
+ emitErr(`Could not read: ${opts.file}`);
6839
+ return 1;
6840
+ }
6841
+ let head;
6842
+ try {
6843
+ head = opts.head !== void 0 ? requireNonNegativeStrictInt("--head", opts.head) : void 0;
6844
+ } catch (e) {
6845
+ emitErr(extractErrorMessage(e));
6846
+ return 1;
6847
+ }
6848
+ try {
6849
+ const result = queryXml(text, opts.path);
6850
+ const fullSourceBytes = sumFileSizes([opts.file]);
6851
+ if (result.attributeValues !== void 0) {
6852
+ if (result.attributeValues.length === 0) {
6853
+ if (opts.json === true) {
6854
+ const jsonText = JSON.stringify({ items: [], truncated: false, totalCount: 0 });
6855
+ emit(jsonText);
6856
+ recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
6857
+ } else {
6858
+ emit(`No attributes matched path: '${opts.path}'`);
6859
+ }
6860
+ return 0;
6861
+ }
6862
+ if (!result.fanned) {
6863
+ const val = result.attributeValues[0] ?? "";
6864
+ const outText = opts.json === true ? JSON.stringify(val) : val;
6865
+ emit(outText);
6866
+ recordReadStat("xml_query", fullSourceBytes, outText, opts.file);
6867
+ return 0;
6868
+ }
6869
+ const totalCount2 = result.attributeValues.length;
6870
+ const limited2 = head !== void 0 ? result.attributeValues.slice(0, head) : result.attributeValues;
6871
+ const headTruncated2 = limited2.length < totalCount2;
6872
+ if (opts.json === true) {
6873
+ const capped = guardJsonRows(limited2);
6874
+ const jsonText = JSON.stringify({ items: capped.items, truncated: capped.truncated || headTruncated2, totalCount: totalCount2 });
6875
+ emit(jsonText);
6876
+ recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
6877
+ } else {
6878
+ const lines = limited2.map((item) => item);
6879
+ if (headTruncated2) {
6880
+ lines.push(`...(${totalCount2 - limited2.length} more items elided; use --head to see more)`);
6881
+ }
6882
+ const plainText = lines.join("\n");
6883
+ emitGuarded(plainText, "xml-query");
6884
+ recordReadStat("xml_query", fullSourceBytes, plainText, opts.file);
6885
+ }
6886
+ return 0;
6887
+ }
6888
+ if (result.items.length === 0) {
6889
+ if (opts.json === true) {
6890
+ const jsonText = JSON.stringify({ items: [], truncated: false, totalCount: 0 });
6891
+ emit(jsonText);
6892
+ recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
6893
+ } else {
6894
+ emit(`No elements matched path: '${opts.path}'`);
6895
+ }
6896
+ return 0;
6897
+ }
6898
+ if (!result.fanned) {
6899
+ const node = result.items[0];
6900
+ if (opts.json === true) {
6901
+ const jsonVal = xmlNodeToJson(node);
6902
+ const jsonText = JSON.stringify(jsonVal, null, 2);
6903
+ emit(jsonText);
6904
+ recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
6905
+ } else {
6906
+ const xmlText = serializeXmlNode(node);
6907
+ emitGuarded(xmlText, "xml-query");
6908
+ recordReadStat("xml_query", fullSourceBytes, xmlText, opts.file);
6909
+ }
6910
+ return 0;
6911
+ }
6912
+ const totalCount = result.items.length;
6913
+ const limited = head !== void 0 ? result.items.slice(0, head) : result.items;
6914
+ const headTruncated = limited.length < totalCount;
6915
+ if (opts.json === true) {
6916
+ const jsonItems = limited.map(xmlNodeToJson);
6917
+ const capped = guardJsonRows(jsonItems);
6918
+ const jsonText = JSON.stringify({ items: capped.items, truncated: capped.truncated || headTruncated, totalCount });
6919
+ emit(jsonText);
6920
+ recordReadStat("xml_query", fullSourceBytes, jsonText, opts.file);
6921
+ } else {
6922
+ const blocks = limited.map((node) => serializeXmlNode(node));
6923
+ if (headTruncated) {
6924
+ blocks.push(`...(${totalCount - limited.length} more elements elided; use --head to see more)`);
6925
+ }
6926
+ const plainText = blocks.join("\n");
6927
+ emitGuarded(plainText, "xml-query");
6928
+ recordReadStat("xml_query", fullSourceBytes, plainText, opts.file);
6929
+ }
6930
+ return 0;
6931
+ } catch (e) {
6932
+ emitErr(extractErrorMessage(e));
6933
+ return 1;
6934
+ }
6935
+ }
6231
6936
  function loadOpenApiOperations(file) {
6232
6937
  const text = readFileText(file);
6233
6938
  if (text === null) {
@@ -6646,6 +7351,7 @@ async function runScreenshot(url, destPath, opts) {
6646
7351
  }
6647
7352
  function runBriefCore(opts) {
6648
7353
  const resolution = resolveSymbolSpec(opts.spec, void 0, opts.projectRoot);
7354
+ if (resolution.kind === "confined") return { text: resolution.message, code: 1 };
6649
7355
  if (resolution.kind === "ambiguous") {
6650
7356
  return {
6651
7357
  // Name the command explicitly: formatAmbiguity defaults to 'read', so brief's retry lines would otherwise tell the user to run `token-goat read`, which answers a different question than the one they asked.
@@ -6819,8 +7525,10 @@ function runFind(opts) {
6819
7525
  fuzzyNames = rankSimilarNames(rawSymbols.map((s) => s.name), opts.pattern);
6820
7526
  }
6821
7527
  const matched = fuzzyNames.length > 0 ? fuzzyNames.flatMap((n) => rawSymbols.filter((s) => s.name === n)) : symbols;
6822
- const files = [...new Set(matched.map((s) => s.filePath))].slice(0, opts.limit ?? 50);
6823
- const truncated = rawSymbols.length === FIND_SCAN_LIMIT;
7528
+ const allFiles = [...new Set(matched.map((s) => s.filePath))];
7529
+ const files = allFiles.slice(0, opts.limit ?? 50);
7530
+ const limitDropped = allFiles.length - files.length;
7531
+ const truncated = rawSymbols.length === FIND_SCAN_LIMIT || limitDropped > 0;
6824
7532
  if (files.length === 0) {
6825
7533
  emitErr(`No indexed files match '${opts.pattern}'`);
6826
7534
  return 1;
@@ -6836,7 +7544,10 @@ function runFind(opts) {
6836
7544
  for (const f of files) {
6837
7545
  emit(toDisplayPath(rootDir, f));
6838
7546
  }
6839
- if (truncated) {
7547
+ if (limitDropped > 0) {
7548
+ emitErr(`Showing ${files.length} of ${allFiles.length} matching files; rerun with --limit ${allFiles.length} to see them all`);
7549
+ }
7550
+ if (rawSymbols.length === FIND_SCAN_LIMIT) {
6840
7551
  emitErr(`Results may be incomplete; index scan hit limit of ${FIND_SCAN_LIMIT} symbols`);
6841
7552
  }
6842
7553
  return 0;
@@ -6933,10 +7644,16 @@ function buildChangedRefHint(cwd, ref) {
6933
7644
  const commitWord = commitCount === 1 ? "1 commit" : `${commitCount} commits`;
6934
7645
  return `Hint: this repo has only ${commitWord}; '${ref}' does not exist. Try: token-goat changed --since ${suggestedRef}`;
6935
7646
  }
7647
+ function refIsSafe(ref) {
7648
+ return !ref.startsWith("-");
7649
+ }
7650
+ function emitUnsafeRef(ref) {
7651
+ emitErr(`Refusing a git ref that starts with '-': ${ref}`);
7652
+ }
6936
7653
  function runChanged(opts = {}) {
6937
7654
  const ref = opts.ref ?? "HEAD~5";
6938
- if (ref.startsWith("-")) {
6939
- emitErr(`Refusing a git ref that starts with '-': ${ref}`);
7655
+ if (!refIsSafe(ref)) {
7656
+ emitUnsafeRef(ref);
6940
7657
  return 1;
6941
7658
  }
6942
7659
  const cwd = opts.projectRoot ?? process.cwd();
@@ -7082,6 +7799,10 @@ function resolveSymbolSpecOrEmitError(commandName, spec, projectRoot) {
7082
7799
  return null;
7083
7800
  }
7084
7801
  const resolution = resolveSymbolSpec(spec, void 0, projectRoot);
7802
+ if (resolution.kind === "confined") {
7803
+ emitErr(resolution.message);
7804
+ return null;
7805
+ }
7085
7806
  if (resolution.kind === "ambiguous") {
7086
7807
  emitErr(
7087
7808
  formatAmbiguity(
@@ -7109,6 +7830,10 @@ function resolveSymbolSpecOrEmitError(commandName, spec, projectRoot) {
7109
7830
  return resolution.entry;
7110
7831
  }
7111
7832
  function runDiff(opts) {
7833
+ if (opts.ref !== void 0 && !refIsSafe(opts.ref)) {
7834
+ emitUnsafeRef(opts.ref);
7835
+ return 1;
7836
+ }
7112
7837
  const match = resolveSymbolSpecOrEmitError("diff", opts.spec, opts.projectRoot);
7113
7838
  if (match === null) return 1;
7114
7839
  const cwd = opts.projectRoot ?? process.cwd();
@@ -7196,6 +7921,10 @@ function parseLogDashLOutput(stdout) {
7196
7921
  return entries;
7197
7922
  }
7198
7923
  function runLog(opts) {
7924
+ if (opts.ref !== void 0 && !refIsSafe(opts.ref)) {
7925
+ emitUnsafeRef(opts.ref);
7926
+ return 1;
7927
+ }
7199
7928
  const match = resolveSymbolSpecOrEmitError("log", opts.spec, opts.projectRoot);
7200
7929
  if (match === null) return 1;
7201
7930
  const cwd = opts.projectRoot ?? process.cwd();
@@ -7589,6 +8318,11 @@ function runPerFileEmitting(files, label, run) {
7589
8318
  function runExports(opts) {
7590
8319
  const multiFiles = parseMultiFileSpec(opts.file);
7591
8320
  if (multiFiles !== null) return runPerFileEmitting(multiFiles, "Exports", (file) => runExports({ ...opts, file }));
8321
+ const confined = fileConfinementRefusal("This file", opts.file, opts.projectRoot);
8322
+ if (confined !== null) {
8323
+ emitErr(confined);
8324
+ return 1;
8325
+ }
7592
8326
  const diskPath = resolveAgainstProjectRoot(opts.file, opts.projectRoot);
7593
8327
  const symbols = querySymbols({ filePath: resolveIndexPath(diskPath), limit: 500 });
7594
8328
  const kindOf = (name) => symbols.find((s) => s.name === name)?.kind ?? "export";
@@ -8612,29 +9346,49 @@ function hasAncestorDispatchRef(methodName, scopeName, classFile, rootDir) {
8612
9346
  }
8613
9347
  return false;
8614
9348
  }
8615
- var CORE_SYMBOL_KINDS = [
9349
+ var GENERIC_SYMBOL_KINDS = [
8616
9350
  "function",
8617
9351
  "method",
8618
9352
  "class",
8619
- "interface",
8620
- "enum",
8621
- "type",
8622
9353
  "variable",
8623
9354
  "const",
8624
- "struct",
8625
- "trait",
8626
9355
  "namespace",
8627
9356
  "module",
8628
9357
  "property",
8629
9358
  "field",
8630
9359
  "constructor",
8631
9360
  "macro",
8632
- "union",
8633
9361
  "alias",
8634
9362
  "directive",
8635
- "extension",
8636
- "mixin",
8637
- "protocol"
9363
+ "var",
9364
+ "impl"
9365
+ ];
9366
+ var OTHER_ADAPTER_SYMBOL_KINDS = [
9367
+ "heading",
9368
+ "selector",
9369
+ "env_key",
9370
+ "key",
9371
+ "section",
9372
+ "html_class",
9373
+ "html_id",
9374
+ "apex_method",
9375
+ "sf_record_type",
9376
+ "sf_apex_class",
9377
+ "sf_flow",
9378
+ "sf_flow_record_lookup",
9379
+ "sf_flow_action",
9380
+ "sf_flow_subflow",
9381
+ "sf_mystery_type",
9382
+ "sf_lightning_component_bundle",
9383
+ "sf_lwc_target",
9384
+ "sf_lwc_property",
9385
+ "liquid_schema",
9386
+ "liquid_section_file",
9387
+ "lwc_bundle",
9388
+ "lwc_component_alias",
9389
+ "lwc_api_property",
9390
+ "lwc_api_method",
9391
+ "lwc_ref"
8638
9392
  ];
8639
9393
  function runDead(opts) {
8640
9394
  if (opts.top !== void 0 && opts.top <= 0) {
@@ -8735,7 +9489,7 @@ var SOURCE_EXTENSIONS = [
8735
9489
  function runDeps(opts) {
8736
9490
  let text;
8737
9491
  try {
8738
- text = fs6.readFileSync(opts.file, "utf-8");
9492
+ text = decodeSource(fs6.readFileSync(opts.file));
8739
9493
  } catch {
8740
9494
  emitErr2(`Could not read: ${opts.file}`);
8741
9495
  return 1;
@@ -8884,6 +9638,9 @@ var TYPE_KINDS = [
8884
9638
  // Kotlin/Scala object.
8885
9639
  "graphql_extend"
8886
9640
  ];
9641
+ var CORE_SYMBOL_KINDS = [
9642
+ .../* @__PURE__ */ new Set([...GENERIC_SYMBOL_KINDS, ...TYPE_KINDS, ...OTHER_ADAPTER_SYMBOL_KINDS])
9643
+ ];
8887
9644
  var TYPES_EXCLUDE_TESTS_SCAN_LIMIT = 5e3;
8888
9645
  function runTypes(opts) {
8889
9646
  if (opts.limit !== void 0 && opts.limit <= 0) {
@@ -8991,6 +9748,11 @@ function runScope(opts) {
8991
9748
  emitErr2(`Invalid line number: ${lineStr}`);
8992
9749
  return 1;
8993
9750
  }
9751
+ const confined = fileConfinementRefusal("This file", file, void 0);
9752
+ if (confined !== null) {
9753
+ emitErr2(confined);
9754
+ return 1;
9755
+ }
8994
9756
  const filePath = resolveIndexPath(file);
8995
9757
  const syms = querySymbols({ filePath, limit: ALL_SYMBOLS_IN_FILE_LIMIT });
8996
9758
  const enclosing = syms.filter((s) => s.lineStart <= line && line <= s.lineEnd).sort((a, b) => b.lineStart - a.lineStart);
@@ -9600,6 +10362,8 @@ export {
9600
10362
  runJsonQuery,
9601
10363
  runYamlOutline,
9602
10364
  runYamlQuery,
10365
+ runXmlOutline,
10366
+ runXmlQuery,
9603
10367
  runOpenApiOutline,
9604
10368
  runOpenApiOp,
9605
10369
  runZipList,