sigmap 8.26.2 → 8.28.0
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.
- package/CHANGELOG.md +25 -0
- package/README.md +6 -6
- package/gen-context.js +468 -61
- package/llms-full.txt +4 -4
- package/llms.txt +4 -4
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/extractors/javascript.js +57 -27
- package/src/extractors/scan.js +91 -0
- package/src/extractors/typescript.js +96 -31
- package/src/mcp/server.js +1 -1
- package/src/verify/arity.js +180 -0
- package/src/verify/hallucination-guard.js +34 -1
package/gen-context.js
CHANGED
|
@@ -6566,6 +6566,7 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
6566
6566
|
|
|
6567
6567
|
const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
|
|
6568
6568
|
const { capWithNotice, capMembersWithNotice } = __require('./src/util/truncate');
|
|
6569
|
+
const { stripComments, maskCode, readBalanced } = __require('./src/extractors/scan');
|
|
6569
6570
|
|
|
6570
6571
|
/**
|
|
6571
6572
|
* Extract signatures from JavaScript source code.
|
|
@@ -6585,16 +6586,29 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
6585
6586
|
const returnHints = buildReturnHints(src);
|
|
6586
6587
|
const docHints = buildDocHints(src);
|
|
6587
6588
|
|
|
6588
|
-
//
|
|
6589
|
-
//
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6589
|
+
// stripComments is string-aware (a `//` inside a string literal survives);
|
|
6590
|
+
// maskCode additionally blanks string/template contents so every delimiter
|
|
6591
|
+
// found on it is structural. Both are length- and newline-preserving, so
|
|
6592
|
+
// offsets and line anchors align across all three views (#526).
|
|
6593
|
+
const stripped = stripComments(src);
|
|
6594
|
+
const masked = maskCode(src);
|
|
6595
|
+
|
|
6596
|
+
// Full params for a declaration whose `(` sits at openIdx: depth-matched
|
|
6597
|
+
// close over masked text; TEXT sliced from stripped so string defaults keep
|
|
6598
|
+
// their real content. Falls back to first-`)` when unbalanced (cap hit).
|
|
6599
|
+
const paramsFrom = (openIdx) => {
|
|
6600
|
+
const closeIdx = readBalanced(masked, openIdx);
|
|
6601
|
+
if (closeIdx === -1) {
|
|
6602
|
+
const naive = stripped.indexOf(')', openIdx);
|
|
6603
|
+
return { params: stripped.slice(openIdx + 1, naive === -1 ? openIdx + 1 : naive), closeIdx: naive };
|
|
6604
|
+
}
|
|
6605
|
+
return { params: stripped.slice(openIdx + 1, closeIdx), closeIdx };
|
|
6606
|
+
};
|
|
6593
6607
|
|
|
6594
|
-
const blockEndIdx = (bodyStart) => bodyStart + extractBlock(
|
|
6595
|
-
// End line for a function whose
|
|
6608
|
+
const blockEndIdx = (bodyStart) => bodyStart + extractBlock(masked, bodyStart).length;
|
|
6609
|
+
// End line for a function whose params close just before `matchEnd`.
|
|
6596
6610
|
const fnEndLine = (matchEnd, startLn) => {
|
|
6597
|
-
const brace =
|
|
6611
|
+
const brace = masked.indexOf('{', matchEnd);
|
|
6598
6612
|
return brace !== -1 ? lineAt(stripped, blockEndIdx(brace + 1)) : startLn;
|
|
6599
6613
|
};
|
|
6600
6614
|
|
|
@@ -6603,33 +6617,38 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
6603
6617
|
for (const m of stripped.matchAll(classRegex)) {
|
|
6604
6618
|
const prefix = m[1] ? m[1].trim() + ' ' : '';
|
|
6605
6619
|
const bodyStart = m.index + m[0].length;
|
|
6620
|
+
const blockEnd = blockEndIdx(bodyStart);
|
|
6606
6621
|
sigs.push(`${prefix}class ${m[2]}`);
|
|
6607
|
-
anchors.push([lineAt(stripped, m.index), lineAt(stripped,
|
|
6608
|
-
const block =
|
|
6609
|
-
|
|
6622
|
+
anchors.push([lineAt(stripped, m.index), lineAt(stripped, blockEnd)]);
|
|
6623
|
+
const block = stripped.slice(bodyStart, blockEnd);
|
|
6624
|
+
const maskedBlock = masked.slice(bodyStart, blockEnd);
|
|
6625
|
+
for (const meth of extractClassMembers(block, maskedBlock, returnHints)) {
|
|
6610
6626
|
sigs.push(` ${meth.text}`);
|
|
6611
6627
|
anchors.push([lineAt(stripped, bodyStart + meth.start), lineAt(stripped, bodyStart + meth.end)]);
|
|
6612
6628
|
}
|
|
6613
6629
|
}
|
|
6614
6630
|
|
|
6615
6631
|
// Exported named functions
|
|
6616
|
-
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(
|
|
6632
|
+
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*\(/gm)) {
|
|
6617
6633
|
const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
|
|
6618
6634
|
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
6619
6635
|
const startLn = lineAt(stripped, m.index);
|
|
6620
|
-
|
|
6636
|
+
const { params, closeIdx } = paramsFrom(m.index + m[0].length - 1);
|
|
6637
|
+
sigs.push(`export ${asyncKw}function ${m[1]}(${normalizeParams(params)})${retStr}`);
|
|
6621
6638
|
docHintFor[sigs.length - 1] = docHints.get(m[1]);
|
|
6622
|
-
anchors.push([startLn, fnEndLine(
|
|
6639
|
+
anchors.push([startLn, fnEndLine(closeIdx + 1, startLn)]);
|
|
6623
6640
|
}
|
|
6624
6641
|
|
|
6625
6642
|
// Exported arrow functions
|
|
6626
|
-
for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(
|
|
6643
|
+
for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*=\s*(?:async\s+)?\(/gm)) {
|
|
6644
|
+
const { params, closeIdx } = paramsFrom(m.index + m[0].length - 1);
|
|
6645
|
+
if (closeIdx === -1 || !/^\s*=>/.test(masked.slice(closeIdx + 1, closeIdx + 40))) continue;
|
|
6627
6646
|
const asyncKw = m[0].includes('async') ? 'async ' : '';
|
|
6628
6647
|
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
6629
6648
|
const startLn = lineAt(stripped, m.index);
|
|
6630
|
-
sigs.push(`export const ${m[1]} = ${asyncKw}(${normalizeParams(
|
|
6649
|
+
sigs.push(`export const ${m[1]} = ${asyncKw}(${normalizeParams(params)}) =>${retStr}`);
|
|
6631
6650
|
docHintFor[sigs.length - 1] = docHints.get(m[1]);
|
|
6632
|
-
anchors.push([startLn, fnEndLine(
|
|
6651
|
+
anchors.push([startLn, fnEndLine(closeIdx + 1, startLn)]);
|
|
6633
6652
|
}
|
|
6634
6653
|
|
|
6635
6654
|
// module.exports = { ... }
|
|
@@ -6644,13 +6663,14 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
6644
6663
|
}
|
|
6645
6664
|
|
|
6646
6665
|
// Top-level named functions (non-exported)
|
|
6647
|
-
for (const m of stripped.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(
|
|
6666
|
+
for (const m of stripped.matchAll(/^(?:async\s+)?function\s+(\w+)\s*\(/gm)) {
|
|
6648
6667
|
const asyncKw = m[0].startsWith('async') ? 'async ' : '';
|
|
6649
6668
|
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
6650
6669
|
const startLn = lineAt(stripped, m.index);
|
|
6651
|
-
|
|
6670
|
+
const { params, closeIdx } = paramsFrom(m.index + m[0].length - 1);
|
|
6671
|
+
sigs.push(`${asyncKw}function ${m[1]}(${normalizeParams(params)})${retStr}`);
|
|
6652
6672
|
docHintFor[sigs.length - 1] = docHints.get(m[1]);
|
|
6653
|
-
anchors.push([startLn, fnEndLine(
|
|
6673
|
+
anchors.push([startLn, fnEndLine(closeIdx + 1, startLn)]);
|
|
6654
6674
|
}
|
|
6655
6675
|
|
|
6656
6676
|
const withAnchors = sigs.map((s, i) => {
|
|
@@ -6672,21 +6692,31 @@ __factories["./src/extractors/javascript"] = function(module, exports) {
|
|
|
6672
6692
|
return src.slice(startIndex, i - 1);
|
|
6673
6693
|
}
|
|
6674
6694
|
|
|
6695
|
+
const _CTRL_KEYWORDS = new Set(['if', 'for', 'while', 'switch', 'do', 'try', 'catch', 'finally', 'else', 'return']);
|
|
6696
|
+
|
|
6675
6697
|
// Returns members as { text, start, end } where start/end are char offsets
|
|
6676
6698
|
// WITHIN `block` (end = the method's closing brace), so the caller can resolve
|
|
6677
|
-
// per-method line anchors that span the method body.
|
|
6678
|
-
|
|
6699
|
+
// per-method line anchors that span the method body. `maskedBlock` is the
|
|
6700
|
+
// same-offset maskCode slice used for balanced-delimiter scanning.
|
|
6701
|
+
function extractClassMembers(block, maskedBlock, returnHints) {
|
|
6679
6702
|
const members = [];
|
|
6680
|
-
for (const m of
|
|
6703
|
+
for (const m of maskedBlock.matchAll(/^\s+(?:static\s+|async\s+|get\s+|set\s+)*(\w+)\s*\(/gm)) {
|
|
6681
6704
|
if (/^_/.test(m[1])) continue;
|
|
6682
|
-
|
|
6683
|
-
const
|
|
6705
|
+
if (_CTRL_KEYWORDS.has(m[1])) continue;
|
|
6706
|
+
const openIdx = m.index + m[0].length - 1;
|
|
6707
|
+
const closeIdx = readBalanced(maskedBlock, openIdx);
|
|
6708
|
+
if (closeIdx === -1) continue;
|
|
6709
|
+
const braceMatch = maskedBlock.slice(closeIdx + 1, closeIdx + 40).match(/^\s*\{/);
|
|
6710
|
+
if (!braceMatch) continue;
|
|
6711
|
+
const params = block.slice(openIdx + 1, closeIdx);
|
|
6712
|
+
const bodyStart = closeIdx + 1 + braceMatch[0].length; // just past the opening brace
|
|
6713
|
+
const end = bodyStart + extractBlock(maskedBlock, bodyStart).length;
|
|
6684
6714
|
const start = m.index + (m[0].length - m[0].replace(/^\s+/, '').length);
|
|
6685
|
-
if (m[1] === 'constructor') { members.push({ text: `constructor(${normalizeParams(
|
|
6715
|
+
if (m[1] === 'constructor') { members.push({ text: `constructor(${normalizeParams(params)})`, start, end }); continue; }
|
|
6686
6716
|
const isAsync = m[0].includes('async ') ? 'async ' : '';
|
|
6687
6717
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
6688
6718
|
const retStr = formatReturnHint(returnHints.get(m[1]));
|
|
6689
|
-
members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(
|
|
6719
|
+
members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(params)})${retStr}`, start, end });
|
|
6690
6720
|
}
|
|
6691
6721
|
return capMembersWithNotice(members, 8, 'methods');
|
|
6692
6722
|
}
|
|
@@ -8274,6 +8304,101 @@ __factories["./src/extractors/scala"] = function(module, exports) {
|
|
|
8274
8304
|
|
|
8275
8305
|
};
|
|
8276
8306
|
|
|
8307
|
+
// ── ./src/extractors/scan ──
|
|
8308
|
+
__factories["./src/extractors/scan"] = function(module, exports) {
|
|
8309
|
+
|
|
8310
|
+
/**
|
|
8311
|
+
* Shared tokenizer-grade scanning core (G4 increment 1, #526).
|
|
8312
|
+
*
|
|
8313
|
+
* Hand-rolled string/comment state + delimiter depth — generalizes the
|
|
8314
|
+
* masking in src/graph/call-graph.js (maskJs) and the balanced reader in the
|
|
8315
|
+
* R extractor. NOT a parser, NOT tree-sitter: three small, deterministic,
|
|
8316
|
+
* zero-dependency passes that let extractors find real declaration
|
|
8317
|
+
* boundaries instead of truncating at the first `)`.
|
|
8318
|
+
*
|
|
8319
|
+
* All transforms are length- and newline-preserving, so character offsets
|
|
8320
|
+
* and line anchors computed on the output align 1:1 with the input.
|
|
8321
|
+
*/
|
|
8322
|
+
|
|
8323
|
+
/**
|
|
8324
|
+
* Blank comments only — string-aware, so `//` or `/*` INSIDE a string
|
|
8325
|
+
* literal survives (the naive regex strip corrupted e.g. `url = "https://x"`).
|
|
8326
|
+
* Comment bytes become spaces; newlines and everything else are preserved.
|
|
8327
|
+
* @param {string} src
|
|
8328
|
+
* @returns {string} same length, comments blanked
|
|
8329
|
+
*/
|
|
8330
|
+
function stripComments(src) {
|
|
8331
|
+
const out = src.split('');
|
|
8332
|
+
const blank = (a, b) => { for (let k = a; k < b; k++) if (out[k] !== '\n') out[k] = ' '; };
|
|
8333
|
+
let i = 0; const n = src.length;
|
|
8334
|
+
while (i < n) {
|
|
8335
|
+
const c = src[i], d = src[i + 1];
|
|
8336
|
+
if (c === '/' && d === '/') { let j = i + 2; while (j < n && src[j] !== '\n') j++; blank(i, j); i = j; continue; }
|
|
8337
|
+
if (c === '/' && d === '*') { let j = i + 2; while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j++; j = Math.min(n, j + 2); blank(i, j); i = j; continue; }
|
|
8338
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
8339
|
+
let j = i + 1;
|
|
8340
|
+
while (j < n) { if (src[j] === '\\') { j += 2; continue; } if (src[j] === c) break; if (c !== '`' && src[j] === '\n') break; j++; }
|
|
8341
|
+
i = Math.min(n, j + 1); continue;
|
|
8342
|
+
}
|
|
8343
|
+
i++;
|
|
8344
|
+
}
|
|
8345
|
+
return out.join('');
|
|
8346
|
+
}
|
|
8347
|
+
|
|
8348
|
+
/**
|
|
8349
|
+
* Blank comments AND string/template contents (quotes included) — the
|
|
8350
|
+
* boundary-scanning surface: delimiters found here are always structural.
|
|
8351
|
+
* @param {string} src
|
|
8352
|
+
* @returns {string} same length, comments + strings blanked
|
|
8353
|
+
*/
|
|
8354
|
+
function maskCode(src) {
|
|
8355
|
+
const out = src.split('');
|
|
8356
|
+
const blank = (a, b) => { for (let k = a; k < b; k++) if (out[k] !== '\n') out[k] = ' '; };
|
|
8357
|
+
let i = 0; const n = src.length;
|
|
8358
|
+
while (i < n) {
|
|
8359
|
+
const c = src[i], d = src[i + 1];
|
|
8360
|
+
if (c === '/' && d === '/') { let j = i + 2; while (j < n && src[j] !== '\n') j++; blank(i, j); i = j; continue; }
|
|
8361
|
+
if (c === '/' && d === '*') { let j = i + 2; while (j < n && !(src[j] === '*' && src[j + 1] === '/')) j++; j = Math.min(n, j + 2); blank(i, j); i = j; continue; }
|
|
8362
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
8363
|
+
let j = i + 1;
|
|
8364
|
+
while (j < n) { if (src[j] === '\\') { j += 2; continue; } if (src[j] === c) break; if (c !== '`' && src[j] === '\n') break; j++; }
|
|
8365
|
+
j = Math.min(n, j + 1); blank(i, j); i = j; continue;
|
|
8366
|
+
}
|
|
8367
|
+
i++;
|
|
8368
|
+
}
|
|
8369
|
+
return out.join('');
|
|
8370
|
+
}
|
|
8371
|
+
|
|
8372
|
+
/**
|
|
8373
|
+
* Index of the delimiter that closes the one open at `openIdx`, matched by
|
|
8374
|
+
* depth over MASKED text (strings/comments already blanked, so every
|
|
8375
|
+
* delimiter seen is structural). -1 when unbalanced within the cap.
|
|
8376
|
+
* @param {string} masked output of maskCode
|
|
8377
|
+
* @param {number} openIdx index of the opening delimiter
|
|
8378
|
+
* @param {string} [open='(']
|
|
8379
|
+
* @param {string} [close=')']
|
|
8380
|
+
* @param {number} [cap=4000] scan ceiling in chars
|
|
8381
|
+
* @returns {number}
|
|
8382
|
+
*/
|
|
8383
|
+
function readBalanced(masked, openIdx, open = '(', close = ')', cap = 4000) {
|
|
8384
|
+
if (masked[openIdx] !== open) return -1;
|
|
8385
|
+
let depth = 1;
|
|
8386
|
+
const end = Math.min(masked.length, openIdx + cap);
|
|
8387
|
+
for (let i = openIdx + 1; i < end; i++) {
|
|
8388
|
+
const ch = masked[i];
|
|
8389
|
+
if (ch === open) depth++;
|
|
8390
|
+
else if (ch === close) {
|
|
8391
|
+
depth--;
|
|
8392
|
+
if (depth === 0) return i;
|
|
8393
|
+
}
|
|
8394
|
+
}
|
|
8395
|
+
return -1;
|
|
8396
|
+
}
|
|
8397
|
+
|
|
8398
|
+
module.exports = { stripComments, maskCode, readBalanced };
|
|
8399
|
+
|
|
8400
|
+
};
|
|
8401
|
+
|
|
8277
8402
|
// ── ./src/extractors/shell ──
|
|
8278
8403
|
__factories["./src/extractors/shell"] = function(module, exports) {
|
|
8279
8404
|
|
|
@@ -8740,6 +8865,7 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8740
8865
|
|
|
8741
8866
|
const { lineAt, withAnchor } = __require('./src/extractors/line-anchor');
|
|
8742
8867
|
const { capWithNotice, capMembersWithNotice } = __require('./src/util/truncate');
|
|
8868
|
+
const { stripComments, maskCode, readBalanced } = __require('./src/extractors/scan');
|
|
8743
8869
|
|
|
8744
8870
|
/**
|
|
8745
8871
|
* Extract signatures from TypeScript source code.
|
|
@@ -8761,15 +8887,25 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8761
8887
|
// anchors are applied once at return.
|
|
8762
8888
|
const anchors = [];
|
|
8763
8889
|
|
|
8764
|
-
//
|
|
8765
|
-
//
|
|
8766
|
-
//
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8890
|
+
// stripComments is string-aware (a `//` inside a string literal survives);
|
|
8891
|
+
// maskCode additionally blanks string/template contents so every delimiter
|
|
8892
|
+
// found on it is structural. Both are length- and newline-preserving, so
|
|
8893
|
+
// offsets and line anchors align across all three views (#526).
|
|
8894
|
+
const stripped = stripComments(src);
|
|
8895
|
+
const masked = maskCode(src);
|
|
8896
|
+
|
|
8897
|
+
// Full params for a declaration whose `(` sits at openIdx (see javascript.js).
|
|
8898
|
+
const paramsFrom = (openIdx) => {
|
|
8899
|
+
const closeIdx = readBalanced(masked, openIdx);
|
|
8900
|
+
if (closeIdx === -1) {
|
|
8901
|
+
const naive = stripped.indexOf(')', openIdx);
|
|
8902
|
+
return { params: stripped.slice(openIdx + 1, naive === -1 ? openIdx + 1 : naive), closeIdx: naive };
|
|
8903
|
+
}
|
|
8904
|
+
return { params: stripped.slice(openIdx + 1, closeIdx), closeIdx };
|
|
8905
|
+
};
|
|
8770
8906
|
|
|
8771
8907
|
// Index of the closing brace for a block whose body starts at bodyStart.
|
|
8772
|
-
const blockEndIdx = (bodyStart) => bodyStart + extractBlock(
|
|
8908
|
+
const blockEndIdx = (bodyStart) => bodyStart + extractBlock(masked, bodyStart).length;
|
|
8773
8909
|
|
|
8774
8910
|
// Exported interfaces
|
|
8775
8911
|
for (const m of stripped.matchAll(/^export\s+interface\s+(\w+)(?:<[^{]*>)?\s*(?:extends\s+[^{]+)?\{/gm)) {
|
|
@@ -8804,10 +8940,12 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8804
8940
|
const prefix = m[1] ? 'export ' : '';
|
|
8805
8941
|
const abs = m[2] ? 'abstract ' : '';
|
|
8806
8942
|
const bodyStart = m.index + m[0].length;
|
|
8943
|
+
const blockEnd = blockEndIdx(bodyStart);
|
|
8807
8944
|
sigs.push(`${prefix}${abs}class ${m[3]}`);
|
|
8808
|
-
anchors.push([lineAt(stripped, m.index), lineAt(stripped,
|
|
8809
|
-
const block =
|
|
8810
|
-
const
|
|
8945
|
+
anchors.push([lineAt(stripped, m.index), lineAt(stripped, blockEnd)]);
|
|
8946
|
+
const block = stripped.slice(bodyStart, blockEnd);
|
|
8947
|
+
const maskedBlock = masked.slice(bodyStart, blockEnd);
|
|
8948
|
+
const methods = extractClassMembers(block, maskedBlock);
|
|
8811
8949
|
for (const meth of methods) {
|
|
8812
8950
|
sigs.push(` ${meth.text}`);
|
|
8813
8951
|
anchors.push([lineAt(stripped, bodyStart + meth.start), lineAt(stripped, bodyStart + meth.end)]);
|
|
@@ -8815,13 +8953,19 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8815
8953
|
}
|
|
8816
8954
|
|
|
8817
8955
|
// Exported top-level functions (not methods)
|
|
8818
|
-
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*(?:<[^(]*>)?\s*\(
|
|
8956
|
+
for (const m of stripped.matchAll(/^export\s+(?:async\s+)?function\s+(\w+)\s*(?:<[^(]*>)?\s*\(/gm)) {
|
|
8957
|
+
const { params: rawParams, closeIdx } = paramsFrom(m.index + m[0].length - 1);
|
|
8958
|
+
if (closeIdx === -1) continue;
|
|
8959
|
+
// Declaration shape check + return-type capture, mirroring the old
|
|
8960
|
+
// `\)(?:\s*:\s*[^{]+)?\s*\{` tail against the text after the real close.
|
|
8961
|
+
const tail = masked.slice(closeIdx + 1, closeIdx + 200).match(/^(\s*:\s*[^{]+?)?\s*\{/);
|
|
8962
|
+
if (!tail) continue;
|
|
8819
8963
|
const asyncKw = /export\s+async/.test(m[0]) ? 'async ' : '';
|
|
8820
|
-
const params = normalizeParams(
|
|
8821
|
-
const
|
|
8822
|
-
const retType =
|
|
8964
|
+
const params = normalizeParams(rawParams);
|
|
8965
|
+
const retRaw = tail[1] ? stripped.slice(closeIdx + 1, closeIdx + 1 + tail[1].length).replace(/^\s*:\s*/, '') : '';
|
|
8966
|
+
const retType = retRaw ? retRaw.trim().replace(/\s+/g, ' ').slice(0, 30) : '';
|
|
8823
8967
|
const retStr = retType ? ` → ${retType}` : '';
|
|
8824
|
-
const bodyStart =
|
|
8968
|
+
const bodyStart = closeIdx + 1 + tail[0].length;
|
|
8825
8969
|
sigs.push(`export ${asyncKw}function ${m[1]}(${params})${retStr}`);
|
|
8826
8970
|
docHintFor[sigs.length - 1] = docHints.get(m[1]);
|
|
8827
8971
|
anchors.push([lineAt(stripped, m.index), lineAt(stripped, blockEndIdx(bodyStart))]);
|
|
@@ -8844,15 +8988,21 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8844
8988
|
}
|
|
8845
8989
|
|
|
8846
8990
|
// Exported arrow functions / const functions
|
|
8847
|
-
for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\(
|
|
8991
|
+
for (const m of stripped.matchAll(/^export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\(/gm)) {
|
|
8992
|
+
const { params: rawParams, closeIdx } = paramsFrom(m.index + m[0].length - 1);
|
|
8993
|
+
if (closeIdx === -1) continue;
|
|
8994
|
+
// Arrow shape check, mirroring the old `\)\s*(?::\s*[^=>{]+)?\s*=>` tail.
|
|
8995
|
+
const tail = masked.slice(closeIdx + 1, closeIdx + 200).match(/^\s*(?::\s*[^=>{]+)?\s*=>/);
|
|
8996
|
+
if (!tail) continue;
|
|
8848
8997
|
const asyncKw = /=\s*async\s+/.test(m[0]) ? 'async ' : '';
|
|
8849
|
-
const params = normalizeParams(
|
|
8998
|
+
const params = normalizeParams(rawParams);
|
|
8850
8999
|
sigs.push(`export const ${m[1]} = ${asyncKw}(${params}) =>`);
|
|
8851
9000
|
docHintFor[sigs.length - 1] = docHints.get(m[1]);
|
|
8852
|
-
const
|
|
9001
|
+
const matchEnd = closeIdx + 1 + tail[0].length;
|
|
9002
|
+
const bodyStart = masked.indexOf('{', matchEnd);
|
|
8853
9003
|
const endLn = bodyStart !== -1
|
|
8854
9004
|
? lineAt(stripped, blockEndIdx(bodyStart + 1))
|
|
8855
|
-
: lineAt(stripped,
|
|
9005
|
+
: lineAt(stripped, matchEnd);
|
|
8856
9006
|
anchors.push([lineAt(stripped, m.index), endLn]);
|
|
8857
9007
|
|
|
8858
9008
|
// Hooks: capture compact return object shape for use* functions.
|
|
@@ -8920,6 +9070,7 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8920
9070
|
// Returns members as { text, start, end } where start/end are char offsets
|
|
8921
9071
|
// WITHIN `block`, so the caller can resolve member line anchors.
|
|
8922
9072
|
function extractInterfaceMembers(block) {
|
|
9073
|
+
const maskedBlock = maskCode(block);
|
|
8923
9074
|
const members = [];
|
|
8924
9075
|
for (const m of block.matchAll(/^\s+(readonly\s+)?(\w+)(\??):\s*([^;]+);/gm)) {
|
|
8925
9076
|
const readonly = m[1] ? 'readonly ' : '';
|
|
@@ -8928,9 +9079,12 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8928
9079
|
const start = m.index + (m[0].length - m[0].replace(/^\s+/, '').length);
|
|
8929
9080
|
members.push({ text: `${readonly}${m[2]}${optional}: ${typeStr}`, start, end: m.index + m[0].length });
|
|
8930
9081
|
}
|
|
8931
|
-
for (const m of
|
|
9082
|
+
for (const m of maskedBlock.matchAll(/^\s+(\w+)\s*(?:<[^(]*>)?\s*\(/gm)) {
|
|
9083
|
+
const openIdx = m.index + m[0].length - 1;
|
|
9084
|
+
const closeIdx = readBalanced(maskedBlock, openIdx);
|
|
9085
|
+
if (closeIdx === -1 || !/^\s*:/.test(maskedBlock.slice(closeIdx + 1, closeIdx + 40))) continue;
|
|
8932
9086
|
const start = m.index + (m[0].length - m[0].replace(/^\s+/, '').length);
|
|
8933
|
-
members.push({ text: `${m[1]}(${normalizeParams(
|
|
9087
|
+
members.push({ text: `${m[1]}(${normalizeParams(block.slice(openIdx + 1, closeIdx))})`, start, end: closeIdx + 1 });
|
|
8934
9088
|
}
|
|
8935
9089
|
return capMembersWithNotice(members, 8, 'members');
|
|
8936
9090
|
}
|
|
@@ -8940,30 +9094,66 @@ __factories["./src/extractors/typescript"] = function(module, exports) {
|
|
|
8940
9094
|
// Returns members as { text, start, end } where start/end are char offsets
|
|
8941
9095
|
// WITHIN `block` (end = the method's closing brace), so the caller can resolve
|
|
8942
9096
|
// per-method line anchors that span the method body.
|
|
8943
|
-
function extractClassMembers(block) {
|
|
9097
|
+
function extractClassMembers(block, maskedBlock) {
|
|
9098
|
+
const masked = maskedBlock || maskCode(block);
|
|
8944
9099
|
const members = [];
|
|
8945
9100
|
// Public methods (skip private/protected/_ prefixed and control-flow keywords)
|
|
8946
|
-
const methodRe = /^\s+(?:public\s+|static\s+|async\s+|override\s+)*(\w+)\s*(?:<[^(]*>)?\s*\(
|
|
8947
|
-
for (const m of
|
|
9101
|
+
const methodRe = /^\s+(?:public\s+|static\s+|async\s+|override\s+)*(\w+)\s*(?:<[^(]*>)?\s*\(/gm;
|
|
9102
|
+
for (const m of masked.matchAll(methodRe)) {
|
|
8948
9103
|
if (_CTRL_KEYWORDS.has(m[1])) continue;
|
|
8949
9104
|
if (/^(private|protected|_)/.test(m[1])) continue;
|
|
8950
|
-
const
|
|
8951
|
-
const
|
|
9105
|
+
const openIdx = m.index + m[0].length - 1;
|
|
9106
|
+
const closeIdx = readBalanced(masked, openIdx);
|
|
9107
|
+
if (closeIdx === -1) continue;
|
|
9108
|
+
// Declaration tail check + return-type capture, mirroring the old
|
|
9109
|
+
// `\)(?:\s*:\s*[^{;]+)?\s*\{` shape against the text after the real close.
|
|
9110
|
+
const tail = masked.slice(closeIdx + 1, closeIdx + 200).match(/^(\s*:\s*[^{;]+?)?\s*\{/);
|
|
9111
|
+
if (!tail) continue;
|
|
9112
|
+
const params = block.slice(openIdx + 1, closeIdx);
|
|
9113
|
+
const bodyStart = closeIdx + 1 + tail[0].length; // just past the opening brace
|
|
9114
|
+
const end = bodyStart + extractBlock(masked, bodyStart).length;
|
|
8952
9115
|
const start = m.index + (m[0].length - m[0].replace(/^\s+/, '').length);
|
|
8953
|
-
if (m[1] === 'constructor') { members.push({ text: `constructor(${normalizeParams(
|
|
9116
|
+
if (m[1] === 'constructor') { members.push({ text: `constructor(${normalizeParams(params)})`, start, end }); continue; }
|
|
8954
9117
|
const isAsync = m[0].includes('async ') ? 'async ' : '';
|
|
8955
9118
|
const isStatic = m[0].includes('static ') ? 'static ' : '';
|
|
8956
|
-
const
|
|
8957
|
-
const retType =
|
|
9119
|
+
const retRaw = tail[1] ? block.slice(closeIdx + 1, closeIdx + 1 + tail[1].length).replace(/^\s*:\s*/, '') : '';
|
|
9120
|
+
const retType = retRaw ? retRaw.trim().replace(/\s+/g, ' ').slice(0, 20) : '';
|
|
8958
9121
|
const retStr = retType ? ` → ${retType}` : '';
|
|
8959
|
-
members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(
|
|
9122
|
+
members.push({ text: `${isStatic}${isAsync}${m[1]}(${normalizeParams(params)})${retStr}`, start, end });
|
|
8960
9123
|
}
|
|
8961
9124
|
return capMembersWithNotice(members, 8, 'methods');
|
|
8962
9125
|
}
|
|
8963
9126
|
|
|
8964
9127
|
function normalizeParams(params) {
|
|
8965
9128
|
if (!params) return '';
|
|
8966
|
-
|
|
9129
|
+
const compact = params.trim().replace(/\s+/g, ' ');
|
|
9130
|
+
// Strip `: type` annotations at top level only — nested delimiters in types
|
|
9131
|
+
// (e.g. `cb: (x: number) => void`, `m: Map<string, X>`) are consumed with
|
|
9132
|
+
// their annotation instead of truncating at the first `)` or `,` (#526).
|
|
9133
|
+
let out = '';
|
|
9134
|
+
let depth = 0;
|
|
9135
|
+
let inType = false;
|
|
9136
|
+
let quote = null;
|
|
9137
|
+
for (let i = 0; i < compact.length; i++) {
|
|
9138
|
+
const ch = compact[i];
|
|
9139
|
+
if (quote) {
|
|
9140
|
+
if (ch === '\\') { if (!inType) out += ch + (compact[i + 1] || ''); i++; continue; }
|
|
9141
|
+
if (ch === quote) quote = null;
|
|
9142
|
+
if (!inType) out += ch;
|
|
9143
|
+
continue;
|
|
9144
|
+
}
|
|
9145
|
+
if (ch === '"' || ch === "'" || ch === '`') { quote = ch; if (!inType) out += ch; continue; }
|
|
9146
|
+
if (ch === '(' || ch === '[' || ch === '{' || ch === '<') depth++;
|
|
9147
|
+
else if (ch === ')' || ch === ']' || ch === '}' || (ch === '>' && compact[i - 1] !== '=')) depth--;
|
|
9148
|
+
if (inType) {
|
|
9149
|
+
if (depth <= 0 && ch === ',') { inType = false; out += ch; }
|
|
9150
|
+
else if (depth <= 0 && ch === '=' && compact[i + 1] !== '>') { inType = false; out += ' ='; }
|
|
9151
|
+
continue;
|
|
9152
|
+
}
|
|
9153
|
+
if (depth === 0 && ch === ':') { inType = true; continue; }
|
|
9154
|
+
out += ch;
|
|
9155
|
+
}
|
|
9156
|
+
return out.replace(/\s*,\s*/g, ', ').replace(/\s+/g, ' ').trim();
|
|
8967
9157
|
}
|
|
8968
9158
|
|
|
8969
9159
|
// First prose sentence of the JSDoc block immediately preceding an exported
|
|
@@ -15195,7 +15385,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
|
|
|
15195
15385
|
|
|
15196
15386
|
const SERVER_INFO = {
|
|
15197
15387
|
name: 'sigmap',
|
|
15198
|
-
version: '8.
|
|
15388
|
+
version: '8.28.0',
|
|
15199
15389
|
description: 'SigMap MCP server — code signatures on demand',
|
|
15200
15390
|
};
|
|
15201
15391
|
|
|
@@ -19389,6 +19579,190 @@ __factories["./src/util/truncate"] = function(module, exports) {
|
|
|
19389
19579
|
|
|
19390
19580
|
};
|
|
19391
19581
|
|
|
19582
|
+
// ── ./src/verify/arity ──
|
|
19583
|
+
__factories["./src/verify/arity"] = function(module, exports) {
|
|
19584
|
+
|
|
19585
|
+
/**
|
|
19586
|
+
* Arity-checked verification (D1, #529).
|
|
19587
|
+
*
|
|
19588
|
+
* With JS/TS params exact (v8.27 balanced scanner) and Python params from the
|
|
19589
|
+
* AST, the signature index carries real parameter lists — so verification can
|
|
19590
|
+
* check not just "does this function exist" but "is this call's argument
|
|
19591
|
+
* count plausible". Deliberately conservative: only uniquely-resolved,
|
|
19592
|
+
* non-variadic, top-level functions from exact-param languages are checked,
|
|
19593
|
+
* and dotted method calls are never flagged.
|
|
19594
|
+
*/
|
|
19595
|
+
|
|
19596
|
+
const path = require('path');
|
|
19597
|
+
const { maskCode, readBalanced } = __require('./src/extractors/scan');
|
|
19598
|
+
|
|
19599
|
+
// Files whose signature params are exact (JS/TS via scan.js, Python via AST).
|
|
19600
|
+
const EXACT_PARAM_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py']);
|
|
19601
|
+
|
|
19602
|
+
const CTRL_KEYWORDS = new Set([
|
|
19603
|
+
'if', 'for', 'while', 'switch', 'catch', 'return', 'typeof', 'await',
|
|
19604
|
+
'do', 'else', 'try', 'finally', 'new', 'in', 'of', 'not', 'and', 'or',
|
|
19605
|
+
'print', 'super', 'this',
|
|
19606
|
+
]);
|
|
19607
|
+
|
|
19608
|
+
// Top-level callable sig shapes (indented member sigs are excluded on purpose
|
|
19609
|
+
// — method calls are dotted in answers and dotted calls are skipped anyway).
|
|
19610
|
+
const CALLABLE_RES = [
|
|
19611
|
+
/^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/,
|
|
19612
|
+
/^(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?\(/,
|
|
19613
|
+
/^(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/,
|
|
19614
|
+
];
|
|
19615
|
+
|
|
19616
|
+
/** Strip the ` :start-end` anchor and ` # hint` tail from a sig line. */
|
|
19617
|
+
function cleanSig(sig) {
|
|
19618
|
+
return String(sig).replace(/\s{2}#\s.*$/, '').replace(/\s*:\d+(?:-\d+)?\s*$/, '');
|
|
19619
|
+
}
|
|
19620
|
+
|
|
19621
|
+
/**
|
|
19622
|
+
* Parse a parameter-list string into an arity range.
|
|
19623
|
+
* Depth- and quote-aware top-level comma split; `=` defaults and trailing `?`
|
|
19624
|
+
* lower `min`; `...rest` / `*args` / `**kwargs` mark the signature variadic;
|
|
19625
|
+
* destructuring patterns count as one parameter.
|
|
19626
|
+
* @param {string} paramText text between the signature's parens
|
|
19627
|
+
* @returns {{ min: number, max: number, variadic: boolean }}
|
|
19628
|
+
*/
|
|
19629
|
+
function parseParams(paramText) {
|
|
19630
|
+
const text = String(paramText || '').trim();
|
|
19631
|
+
if (!text) return { min: 0, max: 0, variadic: false };
|
|
19632
|
+
const masked = maskCode(text);
|
|
19633
|
+
const pieces = [];
|
|
19634
|
+
let depth = 0;
|
|
19635
|
+
let start = 0;
|
|
19636
|
+
for (let i = 0; i < masked.length; i++) {
|
|
19637
|
+
const ch = masked[i];
|
|
19638
|
+
if (ch === '(' || ch === '[' || ch === '{') depth++;
|
|
19639
|
+
else if (ch === ')' || ch === ']' || ch === '}') depth--;
|
|
19640
|
+
else if (ch === ',' && depth === 0) { pieces.push({ raw: text.slice(start, i), masked: masked.slice(start, i) }); start = i + 1; }
|
|
19641
|
+
}
|
|
19642
|
+
pieces.push({ raw: text.slice(start), masked: masked.slice(start) });
|
|
19643
|
+
|
|
19644
|
+
let min = 0;
|
|
19645
|
+
let max = 0;
|
|
19646
|
+
let variadic = false;
|
|
19647
|
+
for (const piece of pieces) {
|
|
19648
|
+
const p = piece.raw.trim();
|
|
19649
|
+
if (!p) continue;
|
|
19650
|
+
if (/^(\.\.\.|\*)/.test(p)) { variadic = true; continue; }
|
|
19651
|
+
max++;
|
|
19652
|
+
// Optional: a top-level `=` default (scan the masked piece at depth 0) or
|
|
19653
|
+
// a `?`-suffixed name (TS optional, survives type stripping as `x?`).
|
|
19654
|
+
let d = 0;
|
|
19655
|
+
let optional = /^[A-Za-z_$][\w$]*\s*\?$/.test(p);
|
|
19656
|
+
const pm = piece.masked;
|
|
19657
|
+
for (let i = 0; i < pm.length && !optional; i++) {
|
|
19658
|
+
const ch = pm[i];
|
|
19659
|
+
if (ch === '(' || ch === '[' || ch === '{') d++;
|
|
19660
|
+
else if (ch === ')' || ch === ']' || ch === '}') d--;
|
|
19661
|
+
else if (ch === '=' && d === 0 && pm[i + 1] !== '>' && pm[i - 1] !== '=' && pm[i - 1] !== '!' && pm[i - 1] !== '<' && pm[i - 1] !== '>') optional = true;
|
|
19662
|
+
}
|
|
19663
|
+
if (!optional) min++;
|
|
19664
|
+
}
|
|
19665
|
+
return { min, max, variadic };
|
|
19666
|
+
}
|
|
19667
|
+
|
|
19668
|
+
/**
|
|
19669
|
+
* Build a per-name arity index from a SigMap signature index.
|
|
19670
|
+
* Only top-level callables from exact-param languages are included; a name
|
|
19671
|
+
* whose signatures disagree across files is marked ambiguous (never checked).
|
|
19672
|
+
* @param {Map<string, string[]>} sigIndex Map<file, sigs[]>
|
|
19673
|
+
* @returns {Map<string, { min, max, variadic, file, sig } | 'ambiguous'>}
|
|
19674
|
+
*/
|
|
19675
|
+
function buildArityIndex(sigIndex) {
|
|
19676
|
+
const index = new Map();
|
|
19677
|
+
if (!sigIndex || !(sigIndex instanceof Map)) return index;
|
|
19678
|
+
for (const [file, sigs] of sigIndex.entries()) {
|
|
19679
|
+
if (!EXACT_PARAM_EXTS.has(path.extname(file))) continue;
|
|
19680
|
+
for (const sig of sigs || []) {
|
|
19681
|
+
const cleaned = cleanSig(sig);
|
|
19682
|
+
let name = null;
|
|
19683
|
+
for (const re of CALLABLE_RES) {
|
|
19684
|
+
const m = cleaned.match(re);
|
|
19685
|
+
if (m) { name = m[1]; break; }
|
|
19686
|
+
}
|
|
19687
|
+
if (!name) continue;
|
|
19688
|
+
const openIdx = cleaned.indexOf('(', cleaned.indexOf(name));
|
|
19689
|
+
if (openIdx === -1) continue;
|
|
19690
|
+
const masked = maskCode(cleaned);
|
|
19691
|
+
const closeIdx = readBalanced(masked, openIdx);
|
|
19692
|
+
if (closeIdx === -1) continue;
|
|
19693
|
+
const arity = parseParams(cleaned.slice(openIdx + 1, closeIdx));
|
|
19694
|
+
const entry = { ...arity, file, sig: cleaned.trim() };
|
|
19695
|
+
const existing = index.get(name);
|
|
19696
|
+
if (existing === undefined) index.set(name, entry);
|
|
19697
|
+
else if (existing === 'ambiguous') continue;
|
|
19698
|
+
else if (existing.min !== entry.min || existing.max !== entry.max || existing.variadic !== entry.variadic) {
|
|
19699
|
+
index.set(name, 'ambiguous');
|
|
19700
|
+
}
|
|
19701
|
+
}
|
|
19702
|
+
}
|
|
19703
|
+
return index;
|
|
19704
|
+
}
|
|
19705
|
+
|
|
19706
|
+
/**
|
|
19707
|
+
* Extract call sites with argument counts from answer code.
|
|
19708
|
+
* Dotted/property calls and keyword-preceded definitions are skipped for
|
|
19709
|
+
* precision; nested calls and comma-containing strings count correctly
|
|
19710
|
+
* because the scan is over masked text.
|
|
19711
|
+
* @param {string} code
|
|
19712
|
+
* @returns {{ name: string, args: number, line: number }[]}
|
|
19713
|
+
*/
|
|
19714
|
+
function extractCallArgCounts(code) {
|
|
19715
|
+
const src = String(code || '');
|
|
19716
|
+
const masked = maskCode(src);
|
|
19717
|
+
const calls = [];
|
|
19718
|
+
const re = /([A-Za-z_$][\w$]*)\s*\(/g;
|
|
19719
|
+
let m;
|
|
19720
|
+
while ((m = re.exec(masked)) !== null) {
|
|
19721
|
+
const name = m[1];
|
|
19722
|
+
if (CTRL_KEYWORDS.has(name)) continue;
|
|
19723
|
+
let k = m.index - 1;
|
|
19724
|
+
while (k >= 0 && (masked[k] === ' ' || masked[k] === '\t')) k--;
|
|
19725
|
+
if (k >= 0 && (masked[k] === '.' || masked[k] === '$')) continue;
|
|
19726
|
+
const before = masked.slice(Math.max(0, m.index - 12), m.index);
|
|
19727
|
+
if (/(?:function|def|class|new)\s+$/.test(before)) continue;
|
|
19728
|
+
const openIdx = m.index + m[0].length - 1;
|
|
19729
|
+
const closeIdx = readBalanced(masked, openIdx);
|
|
19730
|
+
if (closeIdx === -1) continue;
|
|
19731
|
+
const inner = masked.slice(openIdx + 1, closeIdx);
|
|
19732
|
+
let args = 0;
|
|
19733
|
+
// Emptiness is judged on the ORIGINAL text — masking blanks string
|
|
19734
|
+
// contents, so `f("a,b")` would otherwise look like zero arguments.
|
|
19735
|
+
if (src.slice(openIdx + 1, closeIdx).trim()) {
|
|
19736
|
+
args = 1;
|
|
19737
|
+
let depth = 0;
|
|
19738
|
+
for (let i = 0; i < inner.length; i++) {
|
|
19739
|
+
const ch = inner[i];
|
|
19740
|
+
if (ch === '(' || ch === '[' || ch === '{') depth++;
|
|
19741
|
+
else if (ch === ')' || ch === ']' || ch === '}') depth--;
|
|
19742
|
+
else if (ch === ',' && depth === 0) args++;
|
|
19743
|
+
}
|
|
19744
|
+
}
|
|
19745
|
+
calls.push({ name, args, line: src.slice(0, m.index).split('\n').length });
|
|
19746
|
+
}
|
|
19747
|
+
return calls;
|
|
19748
|
+
}
|
|
19749
|
+
|
|
19750
|
+
/**
|
|
19751
|
+
* Check one call against the arity index.
|
|
19752
|
+
* @returns {null | { min, max, variadic, file, sig }} the offended entry, or null when fine/unknowable
|
|
19753
|
+
*/
|
|
19754
|
+
function checkArity(name, argCount, arityIndex) {
|
|
19755
|
+
const entry = arityIndex.get(name);
|
|
19756
|
+
if (!entry || entry === 'ambiguous') return null;
|
|
19757
|
+
if (entry.variadic) return argCount < entry.min ? entry : null;
|
|
19758
|
+
if (argCount < entry.min || argCount > entry.max) return entry;
|
|
19759
|
+
return null;
|
|
19760
|
+
}
|
|
19761
|
+
|
|
19762
|
+
module.exports = { parseParams, buildArityIndex, extractCallArgCounts, checkArity, cleanSig, EXACT_PARAM_EXTS };
|
|
19763
|
+
|
|
19764
|
+
};
|
|
19765
|
+
|
|
19392
19766
|
// ── ./src/verify/closest-match ──
|
|
19393
19767
|
__factories["./src/verify/closest-match"] = function(module, exports) {
|
|
19394
19768
|
|
|
@@ -19563,6 +19937,7 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
19563
19937
|
const parsers = __require('./src/verify/parsers');
|
|
19564
19938
|
const { closestMatch, buildSymbolCandidates, formatSuggestion } = __require('./src/verify/closest-match');
|
|
19565
19939
|
const { buildLibraryIndex } = __require('./src/verify/lib-index');
|
|
19940
|
+
const { buildArityIndex, extractCallArgCounts, checkArity } = __require('./src/verify/arity');
|
|
19566
19941
|
|
|
19567
19942
|
// A path that looks like a test file (JS/TS spec/test, Python test_/_test, or
|
|
19568
19943
|
// a tests/__tests__ directory). Used to flag fake-test-file separately.
|
|
@@ -19618,9 +19993,11 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
19618
19993
|
const set = new Set();
|
|
19619
19994
|
let fileKeys = [];
|
|
19620
19995
|
let symbolCandidates = [];
|
|
19996
|
+
let sigIndex = null;
|
|
19621
19997
|
try {
|
|
19622
19998
|
const { buildSigIndex } = __require('./src/retrieval/ranker');
|
|
19623
19999
|
const idx = buildSigIndex(cwd);
|
|
20000
|
+
sigIndex = idx;
|
|
19624
20001
|
fileKeys = [...idx.keys()];
|
|
19625
20002
|
for (const sigs of idx.values()) {
|
|
19626
20003
|
for (const sig of sigs) {
|
|
@@ -19631,7 +20008,7 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
19631
20008
|
}
|
|
19632
20009
|
symbolCandidates = buildSymbolCandidates(idx);
|
|
19633
20010
|
} catch (_) {}
|
|
19634
|
-
return { set, fileKeys, symbolCandidates };
|
|
20011
|
+
return { set, fileKeys, symbolCandidates, sigIndex };
|
|
19635
20012
|
}
|
|
19636
20013
|
|
|
19637
20014
|
/** Load declared dependency names from package.json. */
|
|
@@ -19720,6 +20097,7 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
19720
20097
|
let fileBasenames = opts.fileBasenames;
|
|
19721
20098
|
let symbolCandidates = opts.symbolCandidates || [];
|
|
19722
20099
|
let fileCandidates = opts.fileCandidates || [];
|
|
20100
|
+
let arityIndex = opts.arityIndex || null;
|
|
19723
20101
|
if (!symbolSet) {
|
|
19724
20102
|
const built = buildSymbolSet(cwd);
|
|
19725
20103
|
symbolSet = built.set;
|
|
@@ -19728,6 +20106,9 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
19728
20106
|
));
|
|
19729
20107
|
symbolCandidates = built.symbolCandidates;
|
|
19730
20108
|
fileCandidates = built.fileKeys;
|
|
20109
|
+
if (!arityIndex && built.sigIndex) {
|
|
20110
|
+
try { arityIndex = buildArityIndex(built.sigIndex); } catch (_) {}
|
|
20111
|
+
}
|
|
19731
20112
|
}
|
|
19732
20113
|
if (!fileBasenames) fileBasenames = new Set();
|
|
19733
20114
|
|
|
@@ -19846,6 +20227,32 @@ __factories["./src/verify/hallucination-guard"] = function(module, exports) {
|
|
|
19846
20227
|
}
|
|
19847
20228
|
}
|
|
19848
20229
|
|
|
20230
|
+
// 3b. arity-mismatch (D1, #529) — a call to a KNOWN repo function whose
|
|
20231
|
+
// argument count falls outside the signature's [min, max]. Conservative by
|
|
20232
|
+
// construction: only uniquely-resolved, top-level functions from
|
|
20233
|
+
// exact-param languages (JS/TS via the balanced scanner, Python via AST);
|
|
20234
|
+
// variadic signatures only flag too-few; dotted calls never flag.
|
|
20235
|
+
if (arityIndex && arityIndex.size > 0) {
|
|
20236
|
+
for (const block of parsers.extractCodeBlocks(answerText)) {
|
|
20237
|
+
if (block.lang && !/^(js|jsx|ts|tsx|javascript|typescript|python|py)$/i.test(block.lang)) continue;
|
|
20238
|
+
for (const call of extractCallArgCounts(block.content)) {
|
|
20239
|
+
if (!symbolSet.has(call.name)) continue; // unknown symbols stay fake-symbol territory
|
|
20240
|
+
const entry = checkArity(call.name, call.args, arityIndex);
|
|
20241
|
+
if (!entry) continue;
|
|
20242
|
+
const range = entry.variadic ? `at least ${entry.min}`
|
|
20243
|
+
: (entry.min === entry.max ? String(entry.max) : `${entry.min}–${entry.max}`);
|
|
20244
|
+
add({
|
|
20245
|
+
type: 'arity-mismatch',
|
|
20246
|
+
value: `${call.name}(${call.args} args)`,
|
|
20247
|
+
line: block.line + call.line - 1,
|
|
20248
|
+
message: `${call.name}() called with ${call.args} argument(s) — repo signature takes ${range}`,
|
|
20249
|
+
confidence: 'medium',
|
|
20250
|
+
suggestion: `${entry.sig} (${entry.file})`,
|
|
20251
|
+
});
|
|
20252
|
+
}
|
|
20253
|
+
}
|
|
20254
|
+
}
|
|
20255
|
+
|
|
19849
20256
|
// 4. fake-npm-script
|
|
19850
20257
|
if (hasPkg && scripts.size > 0) {
|
|
19851
20258
|
for (const { name, line } of parsers.extractNpmScripts(answerText)) {
|
|
@@ -20807,7 +21214,7 @@ function __tryGit(args, opts = {}) {
|
|
|
20807
21214
|
catch (_) { return ''; }
|
|
20808
21215
|
}
|
|
20809
21216
|
|
|
20810
|
-
const VERSION = '8.
|
|
21217
|
+
const VERSION = '8.28.0';
|
|
20811
21218
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
20812
21219
|
|
|
20813
21220
|
function requireSourceOrBundled(key) {
|