@gitsense/gsc-utils 0.2.24 → 0.2.27

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 (40) hide show
  1. package/README.md +380 -62
  2. package/dist/gsc-utils.cjs.js +14270 -523
  3. package/dist/gsc-utils.esm.js +14270 -523
  4. package/package.json +1 -1
  5. package/src/AnalyzerUtils/cloner.js +149 -0
  6. package/src/AnalyzerUtils/constants.js +1 -1
  7. package/src/AnalyzerUtils/defaultPromptLoader.js +10 -10
  8. package/src/AnalyzerUtils/discovery.js +48 -39
  9. package/src/AnalyzerUtils/index.js +13 -7
  10. package/src/AnalyzerUtils/instructionLoader.js +6 -6
  11. package/src/AnalyzerUtils/jsonParser.js +35 -0
  12. package/src/AnalyzerUtils/management.js +6 -6
  13. package/src/AnalyzerUtils/saver.js +5 -5
  14. package/src/AnalyzerUtils/schemaLoader.js +194 -26
  15. package/src/AnalyzerUtils/updater.js +187 -0
  16. package/src/CodeBlockUtils/blockProcessor.js +14 -32
  17. package/src/CodeBlockUtils/constants.js +8 -4
  18. package/src/CodeBlockUtils/headerUtils.js +19 -3
  19. package/src/CodeBlockUtils/index.js +7 -6
  20. package/src/CodeBlockUtils/lineageTracer.js +95 -0
  21. package/src/CompactChatUtils/CompactedMessageUtils.js +224 -0
  22. package/src/CompactChatUtils/README.md +321 -0
  23. package/src/CompactChatUtils/ReferenceMessageUtils.js +143 -0
  24. package/src/CompactChatUtils/index.js +40 -0
  25. package/src/ContextUtils.js +41 -5
  26. package/src/DomUtils.js +559 -0
  27. package/src/GSToolBlockUtils.js +66 -1
  28. package/src/GitSenseChatUtils.js +108 -16
  29. package/src/LanguageNameUtils.js +171 -0
  30. package/src/MarkdownUtils.js +127 -0
  31. package/src/MessageUtils.js +1 -1
  32. package/src/MetaRawResultUtils.js +244 -0
  33. package/src/ObjectUtils.js +54 -0
  34. package/src/PatchUtils/constants.js +9 -3
  35. package/src/PatchUtils/patchParser.js +60 -37
  36. package/src/PatchUtils/patchProcessor.js +8 -5
  37. package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +1 -1
  38. package/src/SVGUtils.js +1467 -0
  39. package/src/SharedUtils/stringUtils.js +303 -0
  40. package/src/CodeBlockUtils/blockProcessor.js.rej +0 -8
@@ -0,0 +1,559 @@
1
+ /**
2
+ * Component: DomUtils Helper Functions
3
+ * Block-UUID: 9a8e8e8e-8b15-4346-bbfa-740e6a5d2d79
4
+ * Parent-UUID: 8a528728-ce54-4445-946d-1743fb4b16be
5
+ * Version: 1.3.0
6
+ * Description: Provides helper functions for creating and manipulating DOM elements.
7
+ * Language: JavaScript
8
+ * Created-at: 2025-12-14T04:18:40.180Z
9
+ * Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash (v1.1.0), Qwen 3 Coder 480B - Cerebras (v1.2.0), GLM-4.6 (v1.2.1), GLM-4.6 (v1.3.0)
10
+ */
11
+
12
+
13
+ function createElement(type, params) {
14
+ let elem = document.createElement(type);
15
+
16
+ if (!params) return elem;
17
+
18
+ // Extract all standard properties
19
+ let {
20
+ id,
21
+ ariaLabel,
22
+ xmlns,
23
+ role,
24
+ html,
25
+ text,
26
+ cls,
27
+ className,
28
+ colSpan,
29
+ pointerEvents,
30
+ title,
31
+ style,
32
+ append,
33
+ type: inputType,
34
+ value,
35
+ name,
36
+ checked,
37
+ selected,
38
+ disabled,
39
+ placeholder,
40
+ "for": _for,
41
+ "data-message-id": dmi,
42
+ "data-message-role": dmr,
43
+ attrs = {},
44
+ dataset,
45
+ rows
46
+ } = params;
47
+
48
+ // Set standard attributes
49
+ if (id != null) elem.id = id;
50
+ if (ariaLabel) elem.setAttribute("aria-label", ariaLabel);
51
+ if (xmlns) elem.setAttribute("xmlns", xmlns);
52
+ if (params.viewBox) elem.setAttribute("viewBox", params.viewBox);
53
+ if (params.fill) elem.setAttribute("fill", params.fill);
54
+ if (role) elem.setAttribute("role", role);
55
+ if (placeholder) elem.setAttribute("placeholder", placeholder);
56
+ if (cls || className) elem.setAttribute("class", cls || className);
57
+ if (pointerEvents) elem.setAttribute("pointer-events", pointerEvents);
58
+ if (title != null) elem.setAttribute("title", title);
59
+ if (inputType) elem.setAttribute("type", inputType);
60
+ if (name != null) elem.setAttribute("name", name);
61
+ if (params.width) elem.setAttribute("width", params.width);
62
+ if (params.height) elem.setAttribute("height", params.height);
63
+ if (params.stroke) elem.setAttribute("stroke", params.stroke);
64
+ if (value != null) elem.value = value;
65
+ if (checked != null && checked) elem.checked = true; // Use property for checked
66
+ if (selected != null && selected) elem.selected = true; // Use property for selected
67
+ if (disabled != null) elem.disabled = disabled; // Use property for disabled
68
+ if (dmi != null) elem.setAttribute("data-message-id", dmi);
69
+ if (dmr != null) elem.setAttribute("data-message-role", dmr);
70
+ if (_for != null ) elem.setAttribute("for", _for);
71
+ if (params.strokeWidth) elem.setAttribute("stroke-width", params.strokeWidth);
72
+ if (params.strokeLinecap) elem.setAttribute("stroke-linecap", params.strokeLinecap);
73
+ if (params.strokeLinejoin) elem.setAttribute("stroke-linejoin", params.strokeLinejoin);
74
+ if (colSpan != null) elem.colSpan = colSpan;
75
+ if (rows) elem.setAttribute("rows", params.rows);
76
+
77
+ // Set content
78
+ if (html != null) {
79
+ if (typeof html === "object" && html instanceof Node) { // Ensure html is a Node
80
+ elem.appendChild(html);
81
+ } else if (typeof html === "string") {
82
+ // For SVG elements, use innerHTML to support path elements
83
+ if (type === 'svg' && params.innerHTML) {
84
+ elem.innerHTML = params.innerHTML;
85
+ }
86
+ elem.innerHTML = html;
87
+ }
88
+ } else if (text != null) {
89
+ elem.innerText = text;
90
+ }
91
+
92
+ // Set style properties
93
+ if (style) {
94
+ for (const name in style) {
95
+ elem.style[name] = style[name];
96
+ }
97
+ }
98
+
99
+ // Handle append (now supports both arrays and single elements)
100
+ if (append) {
101
+ const appendItems = Array.isArray(append) ? append : [append];
102
+ appendItems.forEach(e => {
103
+ if (e instanceof Node) { // Ensure item to append is a Node
104
+ elem.appendChild(e);
105
+ } else if (e !== undefined && e !== null) {
106
+ console.warn("Attempted to append non-Node item:", e);
107
+ }
108
+ });
109
+ }
110
+
111
+ // Set additional attributes from attrs object
112
+ if (attrs && typeof attrs === 'object') {
113
+ for (const [key, value] of Object.entries(attrs)) {
114
+ if (value !== undefined && value !== null) {
115
+ // Skip if already set by standard properties or handled directly
116
+ if (!['id', 'class', 'role', 'aria-label', 'title', 'style', 'selected', 'disabled', 'value', 'name', 'type', 'for'].includes(key) && !key.startsWith('data-')) {
117
+ elem.setAttribute(key, value);
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ // Handle dataset properties
124
+ if (dataset && typeof dataset === 'object') {
125
+ for (const [key, value] of Object.entries(dataset)) {
126
+ if (value !== undefined && value !== null) {
127
+ elem.dataset[key] = value;
128
+ }
129
+ }
130
+ }
131
+
132
+ // Handle event handlers (like onclick, onchange, etc.)
133
+ for (const [key, value] of Object.entries(params)) {
134
+ // Check if the property is an event handler (starts with 'on')
135
+ if (key.startsWith('on') && typeof value === 'function') {
136
+ const eventName = key.slice(2).toLowerCase(); // Remove 'on' prefix and convert to lowercase
137
+ elem.addEventListener(eventName, value);
138
+ }
139
+ }
140
+
141
+ return elem;
142
+ }
143
+
144
+ // Update all creator functions to pass through attrs:
145
+ const h = {
146
+ elemById: (id) => {
147
+ return document.getElementById(id);
148
+ },
149
+ createA: (params) => {
150
+ return h.createLink(params);
151
+ },
152
+ createArticle: (params) => {
153
+ return createElement("article", params);
154
+ },
155
+ createBr: () => {
156
+ return createElement("br");
157
+ },
158
+ createBreak: () => {
159
+ return createElement("br");
160
+ },
161
+ createButton: (params) => {
162
+ return createElement("button", params);
163
+ },
164
+ createCheckbox: (params) => {
165
+ // Use createElement and let it handle attributes like checked/disabled
166
+ return createElement("input", { ...params, type: "checkbox" });
167
+ },
168
+ createCode: (params) => {
169
+ return createElement("code", params);
170
+ },
171
+ createDetail: (params) => {
172
+ return createElement("details", params);
173
+ },
174
+ createDiv: (params) => {
175
+ return createElement("div", params);
176
+ },
177
+ createEm: (params) => { // Added
178
+ return createElement("em", params);
179
+ },
180
+ createForm: (params) => {
181
+ return createElement("form", params);
182
+ },
183
+ createInput: (params) => {
184
+ return createElement("input", params);
185
+ },
186
+ createImg : (params) => {
187
+ const img = createElement("img", params);
188
+ // src is handled by createElement via attrs or direct property if needed
189
+ return img;
190
+ },
191
+ createH1: (params) => {
192
+ return createElement("h1", params);
193
+ },
194
+ createH2: (params) => {
195
+ return createElement("h2", params);
196
+ },
197
+ createH3: (params) => {
198
+ return createElement("h3", params);
199
+ },
200
+ createH4: (params) => {
201
+ return createElement("h4", params);
202
+ },
203
+ createH5: (params) => {
204
+ return createElement("h5", params);
205
+ },
206
+ createHeader: (header, params) => {
207
+ // Ensure header is a string like 'h1', 'h2' etc.
208
+ if (typeof header === 'string' && /^h[1-6]$/i.test(header)) {
209
+ return createElement(header, params);
210
+ }
211
+ console.error("Invalid header type provided to createHeader:", header);
212
+ return createElement("div", params); // Fallback or throw error
213
+ },
214
+ createHr: (params) => {
215
+ return createElement("hr", params);
216
+ },
217
+ createLabel : (params) => {
218
+ // 'for' is handled by createElement
219
+ return createElement("label", params);
220
+ },
221
+ createLI: (params) => {
222
+ return createElement("li", params);
223
+ },
224
+ createLi: (params) => { // Alias for createLI
225
+ return createElement("li", params);
226
+ },
227
+ createLink: (params) => {
228
+ const link = createElement("a", params);
229
+ if (params?.href) link.href = params.href;
230
+ if (params?.target) link.target = params.target;
231
+ return link;
232
+ },
233
+ createNav: (params) => {
234
+ return createElement("nav", params);
235
+ },
236
+ createOl: (params) => {
237
+ return createElement("ol", params);
238
+ },
239
+ createOL: (params) => {
240
+ return createElement("ol", params);
241
+ },
242
+ createOption: (params) => { // Added
243
+ // value, text, selected are handled by createElement
244
+ return createElement("option", params);
245
+ },
246
+ createP: (params) => {
247
+ return createElement("p", params);
248
+ },
249
+ createParagraph: (params) => { // Alias for createP
250
+ return createElement("p", params);
251
+ },
252
+ createPre: (params) => {
253
+ return createElement("pre", params);
254
+ },
255
+ createSelect: (params) => { // Added
256
+ // name, disabled etc handled by createElement
257
+ const select = createElement("select", params);
258
+
259
+ // Handle options array if provided
260
+ if (params && params.options && Array.isArray(params.options)) {
261
+ params.options.forEach(option => {
262
+ if (option && typeof option === 'object') {
263
+ const optionElement = createElement("option", {
264
+ value: option.value,
265
+ text: option.text,
266
+ selected: option.selected
267
+ });
268
+ select.appendChild(optionElement);
269
+ }
270
+ });
271
+ }
272
+
273
+ return select;
274
+ },
275
+ createSpan: (params) => {
276
+ return createElement("span", params);
277
+ },
278
+ createSvg: (params) => {
279
+ // Create SVG element with xmlns attribute
280
+ const svgParams = { ...params, xmlns: "http://www.w3.org/2000/svg" };
281
+ const svg = createElement("svg", svgParams);
282
+ return svg;
283
+ },
284
+ createStrong: (params) => { // Added
285
+ return createElement("strong", params);
286
+ },
287
+ createSummary: (params) => {
288
+ return createElement("summary", params);
289
+ },
290
+ createSup: (params) => {
291
+ return createElement("sup", params);
292
+ },
293
+ createText: (text) => {
294
+ return document.createTextNode(text);
295
+ },
296
+ createTextarea: (params) => {
297
+ const textArea = createElement("textarea", params);
298
+ return textArea;
299
+ },
300
+ createTextArea: (params) => {
301
+ const textArea = createElement("textarea", params);
302
+ return textArea;
303
+ },
304
+ createTextNode: (text) => {
305
+ return document.createTextNode(text);
306
+ },
307
+ createTable: (params) => {
308
+ return createElement("table", params);
309
+ },
310
+ createTableBody: (params) => {
311
+ return createElement("tbody", params);
312
+ },
313
+ createTableCell: (params) => {
314
+ return createElement("td", params);
315
+ },
316
+ createTableHead: (params) => {
317
+ return createElement("thead", params);
318
+ },
319
+ createTableHeadCell: (params) => {
320
+ return createElement("th", params);
321
+ },
322
+ createTbody: (params) => {
323
+ return createElement("tbody", params);
324
+ },
325
+ createTd: (params) => {
326
+ return createElement("td", params);
327
+ },
328
+ createTh: (params) => {
329
+ return createElement("th", params);
330
+ },
331
+ createThead: (params) => {
332
+ return createElement("thead", params);
333
+ },
334
+ createTr: (params) => {
335
+ return createElement("tr", params);
336
+ },
337
+ createTableRow: (params) => {
338
+ return createElement("tr", params);
339
+ },
340
+ createTextInput: (params) => {
341
+ // Use createElement and let it handle attributes like value/disabled
342
+ return createElement("input", { ...params, type: "text" });
343
+ },
344
+ createUl: (params) => {
345
+ return createElement("ul", params);
346
+ },
347
+ createUL: (params) => {
348
+ return createElement("ul", params);
349
+ },
350
+ hide: (elem) => {
351
+ if (elem && elem.style) {
352
+ elem.style.display = "none";
353
+ }
354
+ },
355
+ show: (elem) => {
356
+ if (elem && elem.style) {
357
+ elem.style.display = ""; // Reset to default display
358
+ }
359
+ },
360
+ updateDOM: (sourceNode, targetNode) => {
361
+ // Step 1: Check if the node type or tag name is different
362
+ if (!sourceNode || !targetNode) return; // Basic guard
363
+ if (sourceNode.nodeType !== targetNode.nodeType || sourceNode.nodeName !== targetNode.nodeName) {
364
+ targetNode.replaceWith(sourceNode.cloneNode(true));
365
+ return;
366
+ }
367
+
368
+ // Step 2: Check if it's an element node
369
+ if (sourceNode.nodeType === Node.ELEMENT_NODE) {
370
+ // Step 3: Compare and update attributes
371
+ let sourceAttributes = sourceNode.attributes;
372
+ let targetAttributes = targetNode.attributes;
373
+ const targetAttrMap = new Map();
374
+ for (let i = 0; i < targetAttributes.length; i++) {
375
+ targetAttrMap.set(targetAttributes[i].name, targetAttributes[i].value);
376
+ }
377
+
378
+ // Update or add attributes from sourceNode to targetNode
379
+ for (let i = 0; i < sourceAttributes.length; i++) {
380
+ let attr = sourceAttributes[i];
381
+ if (targetAttrMap.get(attr.name) !== attr.value) {
382
+ targetNode.setAttribute(attr.name, attr.value);
383
+ }
384
+ targetAttrMap.delete(attr.name); // Mark as processed
385
+ }
386
+
387
+ // Remove attributes that are in target but not in source
388
+ for (const attrName of targetAttrMap.keys()) {
389
+ targetNode.removeAttribute(attrName);
390
+ }
391
+
392
+ // Special handling for properties like value, checked, selected, disabled
393
+ if (sourceNode.value !== targetNode.value) {
394
+ targetNode.value = sourceNode.value;
395
+ }
396
+ if (sourceNode.checked !== targetNode.checked) {
397
+ targetNode.checked = sourceNode.checked;
398
+ }
399
+ if (sourceNode.selected !== targetNode.selected) {
400
+ targetNode.selected = sourceNode.selected;
401
+ }
402
+ if (sourceNode.disabled !== targetNode.disabled) {
403
+ targetNode.disabled = sourceNode.disabled;
404
+ }
405
+
406
+
407
+ // Step 4: Recursively compare and update children
408
+ let sourceChildren = sourceNode.childNodes;
409
+ let targetChildren = targetNode.childNodes;
410
+ let maxLen = Math.max(sourceChildren.length, targetChildren.length);
411
+
412
+ for (let i = 0; i < maxLen; i++) {
413
+ if (i >= targetChildren.length) {
414
+ // If the target has fewer children, append the new child
415
+ if (sourceChildren[i]) {
416
+ targetNode.appendChild(sourceChildren[i].cloneNode(true));
417
+ }
418
+ } else if (i >= sourceChildren.length) {
419
+ // If the source has fewer children, remove extra target child
420
+ targetNode.removeChild(targetChildren[i]);
421
+ } else {
422
+ // Otherwise, update the existing child
423
+ h.updateDOM(sourceChildren[i], targetChildren[i]);
424
+ }
425
+ }
426
+
427
+ } else if (sourceNode.nodeType === Node.TEXT_NODE) {
428
+ // Step 5: Update text content if the text nodes are different
429
+ if (sourceNode.nodeValue !== targetNode.nodeValue) {
430
+ targetNode.nodeValue = sourceNode.nodeValue;
431
+ }
432
+ }
433
+ // Other node types (comments, etc.) are ignored for updates in this version
434
+ },
435
+ safeEscape: (html, x) => {
436
+ if ( x )
437
+ console.log(x);
438
+
439
+ if (typeof html !== 'string') return html; // Return non-strings as-is
440
+
441
+ // Generated by Claude 3.5 Sonnet
442
+ const preserveTags = [
443
+ 'pre',
444
+ 'code',
445
+ 'script',
446
+ 'style',
447
+ 'textarea',
448
+ 'math',
449
+ 'mi',
450
+ 'mo',
451
+ 'mn',
452
+ 'ms',
453
+ 'mtext',
454
+ 'samp',
455
+ 'kbd',
456
+ 'var',
457
+ 'svg',
458
+ 'xmp'
459
+ ].join('|');
460
+
461
+ // Track whether we're inside a preserve tag
462
+ let isInPreserveTag = false;
463
+ let currentTag = '';
464
+
465
+ // Split the input into tokens that are either tags or text
466
+ // Improved regex to handle self-closing tags and attributes better
467
+ return html.split(/(<\/?(?:${preserveTags})(?:\s+[^>]*)?\/?>|<\/?[a-zA-Z][^>]*>)/i)
468
+ .filter(part => part) // Remove empty strings from split
469
+ .map(part => {
470
+ // Check if this part is a preserve tag
471
+ const preserveTagMatch = part.match(new RegExp(`^<(/?)(${preserveTags})([\\s>/])`, 'i'));
472
+
473
+ if (preserveTagMatch) {
474
+ // Toggle the preserve state based on opening/closing tags
475
+ if (!preserveTagMatch[1] && !part.endsWith('/>')) { // Opening tag, not self-closing
476
+ isInPreserveTag = true;
477
+ currentTag = preserveTagMatch[2];
478
+ } else if (preserveTagMatch[1]) { // Closing tag
479
+ // Only close if it matches the current open tag
480
+ if (isInPreserveTag && currentTag.toLowerCase() === preserveTagMatch[2].toLowerCase()) {
481
+ isInPreserveTag = false;
482
+ currentTag = '';
483
+ }
484
+ }
485
+ return part; // Return the tag itself unmodified
486
+ }
487
+
488
+ // If we're not in a preserve tag and this isn't an HTML tag, escape <
489
+ if (!isInPreserveTag && !part.startsWith('<')) {
490
+ return part.replace(/</g, '&lt;');
491
+ }
492
+
493
+ // Return other tags or content within preserve tags unmodified
494
+ return part;
495
+ })
496
+ .join('');
497
+ },
498
+
499
+ /**
500
+ * Injects CSS styles into the document head.
501
+ * @param {string} cssString - The CSS string to inject.
502
+ * @param {string} [id] - Optional ID for the style element to prevent duplicates.
503
+ * @returns {HTMLStyleElement} The created or existing style element.
504
+ */
505
+ injectStyles: (cssString, id) => {
506
+ // If an ID is provided, check if styles with this ID already exist
507
+ if (id) {
508
+ const existingStyle = document.getElementById(id);
509
+ if (existingStyle) {
510
+ // Styles already injected, return the existing element
511
+ return existingStyle;
512
+ }
513
+ }
514
+
515
+ // Create a new style element
516
+ const styleElement = document.createElement('style');
517
+ if (id) {
518
+ styleElement.id = id;
519
+ }
520
+ styleElement.textContent = cssString;
521
+
522
+ // Append to the document head
523
+ document.head.appendChild(styleElement);
524
+
525
+ return styleElement;
526
+ },
527
+
528
+ /**
529
+ * Calculates the distance between an element's edge and the viewport's corresponding edge
530
+ * @param {HTMLElement} elem - The element to measure from
531
+ * @param {"left"|"right"} edge - Which edge to measure from
532
+ * @returns {number} The distance in pixels
533
+ * @throws {Error} If invalid edge parameter is provided or element not found
534
+ */
535
+ getDistanceFromViewportEdge: (elem, edge = "right") => {
536
+ // Input validation
537
+ if (!elem || typeof elem.getBoundingClientRect !== 'function') {
538
+ throw new Error('Invalid element provided.');
539
+ }
540
+ if ( !["left", "right"].includes(edge) ) {
541
+ throw new Error('Edge parameter must be either "left" or "right"');
542
+ }
543
+
544
+ // Get the bounding rectangle of the elem
545
+ const rect = elem.getBoundingClientRect();
546
+
547
+ // Get the width of the viewport
548
+ const viewportWidth = window.innerWidth;
549
+
550
+ // Calculate distance based on specified edge
551
+ if (edge === "right") {
552
+ return viewportWidth - rect.right;
553
+ } else { // edge === "left"
554
+ return rect.left;
555
+ }
556
+ }
557
+ };
558
+
559
+ module.exports = { h };
@@ -10,6 +10,8 @@
10
10
  */
11
11
 
12
12
 
13
+ const GS_TOOL_BLOCK_TYPE = 'gs-tool';
14
+
13
15
  /**
14
16
  * Checks if a given code block content represents a GitSense Chat Tool Block.
15
17
  * It verifies if the first non-empty line starts with "# GitSense Chat Tool".
@@ -28,6 +30,65 @@ function isToolBlock(content) {
28
30
  return false;
29
31
  }
30
32
 
33
+
34
+ // TODO: Add JSDocs
35
+ function getToolBlocksByTool(content, tool, CodeBlockUtils) {
36
+ const { blocks, warnings } = CodeBlockUtils.extractCodeBlocks(content, { silent: true });
37
+
38
+ return blocks.filter((block, index) => {
39
+ if (block.type !== GS_TOOL_BLOCK_TYPE)
40
+ return;
41
+
42
+ try {
43
+ const data = parseToolBlock(block.content);
44
+
45
+ if (data.tool === tool) {
46
+ block.index = index;
47
+ return block;
48
+ }
49
+ } catch(error) {
50
+ // FIXME: We need a more elegant way to identify examples.
51
+ if (!content.includes(/Internal INTEGER IDs/)) {
52
+ console.warn(`Invalid tool block JSON: ${error.message}`);
53
+ }
54
+ }
55
+ });
56
+ }
57
+
58
+ // TODO: Add JSDocs
59
+ function getToolBlockElemsByTool(dom, tool) {
60
+ const elems = dom.querySelectorAll('pre');
61
+ const toolBockElems = [];
62
+
63
+ for ( let i = 0; i < elems.length; i++ ) {
64
+ const elem = elems[i];
65
+ let content = elem.textContent;
66
+
67
+ // We need to strip out the first two lines since this is not part of the actual code
68
+ // block. These two lines are designed to make it easy to identify the language
69
+ content = content.split('\n').slice(2).join('\n');
70
+
71
+ if (!isToolBlock(content))
72
+ continue;
73
+
74
+ try {
75
+ const data = parseToolBlock(content, { silent: true });
76
+
77
+ if (data.tool === tool)
78
+ toolBockElems.push(elem);
79
+ } catch(error) {
80
+ // FIXME: We need a more elegant way to identify examples.
81
+ if (!content.match(/Internal INTEGER IDs/)) {
82
+ console.warn("getToolBlockElemsByTool: ",error.message);
83
+ }
84
+ continue;
85
+ }
86
+ }
87
+
88
+ return toolBockElems;
89
+ }
90
+
91
+
31
92
  /**
32
93
  * Parses the content of a GitSense Chat Tool Block to extract the JSON payload.
33
94
  * It strips the marker line and any subsequent lines starting with '#' (comments)
@@ -136,7 +197,7 @@ function replaceToolBlock(markdownContent, toolName, newToolData, CodeBlockUtils
136
197
  // 2. Iterate through the processed blocks to find the target GitSense Chat Tool Block
137
198
  for (let i = 0; i < blocks.length; i++) {
138
199
  const block = blocks[i];
139
- if (block.type === 'gs-tool' && block.toolData && block.toolData.tool === toolName) {
200
+ if (block.type === GS_TOOL_BLOCK_TYPE && block.toolData && block.toolData.tool === toolName) {
140
201
  targetBlockIndex = i;
141
202
  break; // Found the first matching tool block
142
203
  }
@@ -270,10 +331,14 @@ function detectAndFormatUnfencedToolBlock(messageContent) {
270
331
  };
271
332
  }
272
333
 
334
+
335
+
273
336
  module.exports = {
274
337
  isToolBlock,
275
338
  parseToolBlock,
276
339
  formatToolBlock,
277
340
  replaceToolBlock,
341
+ getToolBlocksByTool,
342
+ getToolBlockElemsByTool,
278
343
  detectAndFormatUnfencedToolBlock,
279
344
  }