sigmap 6.10.7 → 6.10.10

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/gen-context.js CHANGED
@@ -2761,7 +2761,27 @@ __factories["./src/extractors/deps"] = function(module, exports) {
2761
2761
  return reverse;
2762
2762
  }
2763
2763
 
2764
- module.exports = { extractPythonDeps, extractTSDeps, buildReverseDepMap };
2764
+ const R_BASE_PKGS = new Set([
2765
+ 'base', 'stats', 'utils', 'graphics', 'grDevices', 'methods', 'datasets',
2766
+ 'parallel', 'splines', 'stats4', 'tools', 'tcltk', 'grid', 'compiler',
2767
+ ]);
2768
+
2769
+ function extractRDeps(src) {
2770
+ const deps = new Set();
2771
+ const stripped = (src || '').replace(/#.*$/gm, '');
2772
+ for (const m of stripped.matchAll(/\b(?:library|require)\s*\(\s*["']?([\w.]+)["']?\s*\)/g)) {
2773
+ if (m[1] && !R_BASE_PKGS.has(m[1])) deps.add(m[1]);
2774
+ }
2775
+ for (const m of stripped.matchAll(/\brequireNamespace\s*\(\s*["']([\w.]+)["']/g)) {
2776
+ if (m[1] && !R_BASE_PKGS.has(m[1])) deps.add(m[1]);
2777
+ }
2778
+ for (const m of stripped.matchAll(/\b([A-Za-z][\w.]*)::[A-Za-z]/g)) {
2779
+ if (m[1] && !R_BASE_PKGS.has(m[1])) deps.add(m[1]);
2780
+ }
2781
+ return [...deps].slice(0, 5);
2782
+ }
2783
+
2784
+ module.exports = { extractPythonDeps, extractTSDeps, extractRDeps, buildReverseDepMap };
2765
2785
 
2766
2786
  };
2767
2787
 
@@ -3063,67 +3083,169 @@ __factories["./src/extractors/r"] = function(module, exports) {
3063
3083
 
3064
3084
  'use strict';
3065
3085
 
3066
- /**
3067
- * Extract signatures from R source code.
3068
- * @param {string} src - Raw file content
3069
- * @returns {string[]} Array of signature strings
3070
- */
3071
3086
  function extract(src) {
3072
3087
  if (!src || typeof src !== 'string') return [];
3073
3088
  const sigs = [];
3074
-
3075
- // Strip line comments. R uses # comments. Roxygen2 (#') comments are
3076
- // stripped along with regular ones; Phase 2 may parse them.
3089
+ const docHints = collectRoxygenHints(src);
3077
3090
  const stripped = src.replace(/#.*$/gm, '');
3091
+ const consumedRanges = [];
3078
3092
 
3079
- // Function definitions:
3080
- // name <- function(args) { ... }
3081
- // name = function(args) { ... }
3082
- // name <<- function(args) { ... }
3083
- // Args may span multiple lines and contain default values, so we need to
3084
- // match a balanced parenthesis group rather than a single line.
3085
- const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
3093
+ const r6Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\s*\(/g;
3086
3094
  let m;
3087
- while ((m = funcRe.exec(stripped)) !== null) {
3095
+ while ((m = r6Re.exec(stripped)) !== null && sigs.length < 30) {
3096
+ const name = m[1];
3097
+ if (name.startsWith('.')) continue;
3098
+ const openIdx = r6Re.lastIndex - 1;
3099
+ const body = readBalancedParens(stripped, openIdx);
3100
+ if (body === null) continue;
3101
+ const closeIdx = openIdx + body.length + 1;
3102
+ const classNameLit = readFirstStringArg(body) || name;
3103
+ sigs.push(`${name} <- R6Class("${classNameLit}")` + applyHint(docHints, name));
3104
+ for (const memberSig of extractListMethods(body, 8)) {
3105
+ sigs.push(' ' + memberSig);
3106
+ if (sigs.length >= 30) break;
3107
+ }
3108
+ consumedRanges.push([m.index, closeIdx]);
3109
+ r6Re.lastIndex = closeIdx;
3110
+ }
3111
+
3112
+ const s7Classes = new Set();
3113
+ const s7Re = /([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\s*\(/g;
3114
+ while ((m = s7Re.exec(stripped)) !== null && sigs.length < 30) {
3115
+ const name = m[1];
3116
+ if (name.startsWith('.')) continue;
3117
+ const openIdx = s7Re.lastIndex - 1;
3118
+ const body = readBalancedParens(stripped, openIdx);
3119
+ if (body === null) continue;
3120
+ const closeIdx = openIdx + body.length + 1;
3121
+ const classNameLit = readFirstStringArg(body) || name;
3122
+ s7Classes.add(classNameLit);
3123
+ s7Classes.add(name);
3124
+ sigs.push(`${name} <- new_class("${classNameLit}")` + applyHint(docHints, name));
3125
+ consumedRanges.push([m.index, closeIdx]);
3126
+ s7Re.lastIndex = closeIdx;
3127
+ }
3128
+
3129
+ const s7MethodRe = /^[ \t]*method\s*\(\s*([\w.]+)\s*,\s*([\w.]+)\s*\)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
3130
+ while ((m = s7MethodRe.exec(stripped)) !== null && sigs.length < 30) {
3131
+ if (!s7Classes.has(m[2])) continue;
3132
+ const argsStart = s7MethodRe.lastIndex - 1;
3133
+ const args = readBalancedParens(stripped, argsStart);
3134
+ if (args === null) continue;
3135
+ sigs.push(` method(${m[1]}, ${m[2]}) <- function(${normalizeParams(args)})`);
3136
+ }
3137
+
3138
+ const funcRe = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*function\s*\(/gm;
3139
+ while ((m = funcRe.exec(stripped)) !== null && sigs.length < 30) {
3088
3140
  const name = m[1];
3089
- if (name.startsWith('.')) continue; // private convention
3090
- const argsStart = funcRe.lastIndex;
3091
- const args = readBalancedParens(stripped, argsStart - 1);
3141
+ if (name.startsWith('.')) continue;
3142
+ if (inAnyRange(m.index, consumedRanges)) continue;
3143
+ const argsStart = funcRe.lastIndex - 1;
3144
+ const args = readBalancedParens(stripped, argsStart);
3092
3145
  if (args === null) continue;
3093
- sigs.push(`${name} <- function(${normalizeParams(args)})`);
3146
+ sigs.push(`${name} <- function(${normalizeParams(args)})` + applyHint(docHints, name));
3094
3147
  }
3095
3148
 
3096
- // S4 setMethod / setGeneric:
3097
- // setGeneric("name", function(args) standardGeneric("name"))
3098
- // setMethod("name", "ClassName", function(args) { ... })
3099
3149
  for (const sm of stripped.matchAll(/^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm)) {
3150
+ if (sigs.length >= 30) break;
3100
3151
  sigs.push(`setGeneric("${sm[1]}")`);
3101
3152
  }
3102
3153
  for (const sm of stripped.matchAll(/^[ \t]*setMethod\s*\(\s*["']([\w.]+)["']\s*,\s*["']([\w.]+)["']/gm)) {
3154
+ if (sigs.length >= 30) break;
3103
3155
  sigs.push(`setMethod("${sm[1]}", "${sm[2]}")`);
3104
3156
  }
3105
-
3106
- // S4 class definitions:
3107
- // setClass("Name", representation(...), ...)
3108
3157
  for (const sm of stripped.matchAll(/^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm)) {
3158
+ if (sigs.length >= 30) break;
3109
3159
  sigs.push(`setClass("${sm[1]}")`);
3110
3160
  }
3111
3161
 
3112
3162
  return sigs.slice(0, 30);
3113
3163
  }
3114
3164
 
3115
- /**
3116
- * Read a parenthesis-balanced substring starting at the position of the
3117
- * opening '(' character, returning the inner content (without the outer
3118
- * parens). Returns null if no matching close paren is found within `cap`
3119
- * characters, which guards against runaway scans on malformed input.
3120
- */
3121
- function readBalancedParens(src, openIdx, cap = 4096) {
3165
+ function collectRoxygenHints(src) {
3166
+ const hints = new Map();
3167
+ const lines = src.split('\n');
3168
+ let block = [];
3169
+ for (let i = 0; i < lines.length; i++) {
3170
+ const line = lines[i];
3171
+ if (/^\s*#'/.test(line)) {
3172
+ block.push(line.replace(/^\s*#'\s?/, ''));
3173
+ continue;
3174
+ }
3175
+ if (block.length > 0) {
3176
+ const mm = line.match(/^[ \t]*([\w.]+)\s*(?:<<-|<-|=)\s*(?:R6::)?R6Class\b/)
3177
+ || line.match(/^[ \t]*([\w.]+)\s*(?:<<-|<-|=)\s*(?:S7::)?new_class\b/)
3178
+ || line.match(/^[ \t]*([\w.]+)\s*(?:<<-|<-|=)\s*function\b/);
3179
+ if (mm) {
3180
+ const name = mm[1];
3181
+ let hint = pickRoxygenLine(block, '@title')
3182
+ || pickRoxygenLine(block, '@description')
3183
+ || pickRoxygenLine(block, null);
3184
+ if (hint) {
3185
+ hint = hint.replace(/\s+/g, ' ').trim().slice(0, 60).replace(/[.,;:!?]+$/, '').trim();
3186
+ if (hint) hints.set(name, hint);
3187
+ }
3188
+ }
3189
+ block = [];
3190
+ }
3191
+ }
3192
+ return hints;
3193
+ }
3194
+
3195
+ function pickRoxygenLine(block, tag) {
3196
+ for (const raw of block) {
3197
+ const b = raw.trim();
3198
+ if (!b) continue;
3199
+ if (tag) {
3200
+ if (b.startsWith(tag)) {
3201
+ const rest = b.slice(tag.length).trim();
3202
+ if (rest) return rest;
3203
+ }
3204
+ } else if (!b.startsWith('@')) {
3205
+ return b;
3206
+ }
3207
+ }
3208
+ return null;
3209
+ }
3210
+
3211
+ function applyHint(hints, name) {
3212
+ const h = hints.get(name);
3213
+ return h ? ` # ${h}` : '';
3214
+ }
3215
+
3216
+ function extractListMethods(body, cap) {
3217
+ const out = [];
3218
+ const re = /(?:^|[\n,])\s*([\w.]+)\s*=\s*function\s*\(/g;
3219
+ let m;
3220
+ while ((m = re.exec(body)) !== null && out.length < cap) {
3221
+ const name = m[1];
3222
+ if (name.startsWith('.')) continue;
3223
+ const argsStart = re.lastIndex - 1;
3224
+ const args = readBalancedParens(body, argsStart);
3225
+ if (args === null) continue;
3226
+ out.push(`${name} <- function(${normalizeParams(args)})`);
3227
+ }
3228
+ return out;
3229
+ }
3230
+
3231
+ function inAnyRange(pos, ranges) {
3232
+ for (const [s, e] of ranges) {
3233
+ if (pos >= s && pos < e) return true;
3234
+ }
3235
+ return false;
3236
+ }
3237
+
3238
+ function readFirstStringArg(body) {
3239
+ const m = body.match(/^\s*["']([\w.]+)["']/);
3240
+ return m ? m[1] : null;
3241
+ }
3242
+
3243
+ function readBalancedParens(src, openIdx, cap = 16384) {
3122
3244
  if (src[openIdx] !== '(') return null;
3123
3245
  let depth = 1;
3124
3246
  let i = openIdx + 1;
3125
3247
  const end = Math.min(src.length, openIdx + cap);
3126
- let inString = null; // null | '"' | "'"
3248
+ let inString = null;
3127
3249
  while (i < end) {
3128
3250
  const ch = src[i];
3129
3251
  if (inString) {
@@ -3143,38 +3265,14 @@ __factories["./src/extractors/r"] = function(module, exports) {
3143
3265
  return null;
3144
3266
  }
3145
3267
 
3146
- /**
3147
- * Compress whitespace inside a parameter list, collapse multi-line default
3148
- * expressions onto a single line, and trim. The goal is one-line readable
3149
- * signatures, not a faithful AST.
3150
- *
3151
- * String literals are protected so that commas/equals inside default values
3152
- * like sep = "," don't get respaced.
3153
- */
3154
3268
  function normalizeParams(raw) {
3155
- const tokens = [];
3156
- let buf = '';
3269
+ let out = '';
3157
3270
  let inString = null;
3158
3271
  for (let i = 0; i < raw.length; i++) {
3159
3272
  const ch = raw[i];
3160
- if (inString) {
3161
- buf += ch;
3162
- if (ch === '\\' && i + 1 < raw.length) { buf += raw[i + 1]; i++; continue; }
3163
- if (ch === inString) inString = null;
3164
- continue;
3165
- }
3166
- if (ch === '"' || ch === "'") { inString = ch; buf += ch; continue; }
3167
- buf += ch;
3168
- }
3169
- // Now buf === raw with strings preserved character-for-character.
3170
- // Walk again: collapse non-string runs of whitespace, normalize ', ' and ' = '.
3171
- let out = '';
3172
- inString = null;
3173
- for (let i = 0; i < buf.length; i++) {
3174
- const ch = buf[i];
3175
3273
  if (inString) {
3176
3274
  out += ch;
3177
- if (ch === '\\' && i + 1 < buf.length) { out += buf[i + 1]; i++; continue; }
3275
+ if (ch === '\\' && i + 1 < raw.length) { out += raw[i + 1]; i++; continue; }
3178
3276
  if (ch === inString) inString = null;
3179
3277
  continue;
3180
3278
  }
@@ -4916,13 +5014,33 @@ __factories["./src/graph/builder"] = function(module, exports) {
4916
5014
  const RS_EXTS = new Set(['.rs']);
4917
5015
  const JVM_EXTS = new Set(['.java', '.kt', '.kts', '.scala', '.sc']);
4918
5016
  const RB_EXTS = new Set(['.rb', '.rake']);
5017
+ const R_EXTS = new Set(['.r', '.R']);
4919
5018
  function resolveJsPath(dir, importStr, fileSet) {
4920
5019
  const base = path.resolve(dir, importStr);
4921
5020
  const candidates = [base, base+'.ts', base+'.tsx', base+'.js', base+'.jsx', base+'.mjs', base+'.cjs', path.join(base,'index.ts'), path.join(base,'index.js')];
4922
5021
  for (const c of candidates) { if (fileSet.has(c)) return c; }
4923
5022
  return null;
4924
5023
  }
4925
- function extractFileDeps(filePath, content, fileSet) {
5024
+ function normalizePath(p) {
5025
+ return path.normalize(p).toLowerCase();
5026
+ }
5027
+ function resolveRPath(dir, importStr, fileSet, cwd) {
5028
+ const tried = new Set();
5029
+ const bases = [path.resolve(dir, importStr)];
5030
+ if (cwd) bases.push(path.resolve(cwd, importStr));
5031
+ for (const base of bases) {
5032
+ for (const c of [base, base + '.R', base + '.r']) {
5033
+ const normC = normalizePath(c);
5034
+ if (tried.has(normC)) continue;
5035
+ tried.add(normC);
5036
+ if (fileSet.has(c)) return c;
5037
+ if (fileSet.has(normC)) return normC;
5038
+ }
5039
+ }
5040
+ return null;
5041
+ }
5042
+ function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
5043
+ function extractFileDeps(filePath, content, fileSet, cwd, ctx) {
4926
5044
  const ext = path.extname(filePath).toLowerCase();
4927
5045
  const dir = path.dirname(filePath);
4928
5046
  const found = [];
@@ -4941,7 +5059,13 @@ __factories["./src/graph/builder"] = function(module, exports) {
4941
5059
  const modPart = m[1].slice(dotCount).replace(/\./g,'/');
4942
5060
  let base = dir; for (let i=1;i<dotCount;i++) base=path.dirname(base);
4943
5061
  const candidate = modPart ? path.join(base,modPart+'.py') : null;
4944
- if (candidate && fileSet.has(candidate)) found.push(candidate);
5062
+ if (candidate) { const normC = normalizePath(candidate); if (fileSet.has(normC)) found.push(normC); }
5063
+ }
5064
+ const reAbs = /^[ \t]*from\s+([\w.]+)\s+import/gm;
5065
+ while ((m = reAbs.exec(content)) !== null) {
5066
+ const modulePath = m[1].replace(/\./g,'/');
5067
+ const candidates = [path.join(dir,modulePath+'.py'),path.join(dir,modulePath,'__init__.py'),path.resolve(dir,'..',modulePath+'.py'),path.resolve(dir,'..',modulePath,'__init__.py')];
5068
+ for (const c of candidates) { const normC = normalizePath(c); if (fileSet.has(normC)) { found.push(normC); break; } }
4945
5069
  }
4946
5070
  }
4947
5071
  if (GO_EXTS.has(ext)) {
@@ -4952,14 +5076,14 @@ __factories["./src/graph/builder"] = function(module, exports) {
4952
5076
  while ((m = reInline.exec(content)) !== null) imports.push(m[1]);
4953
5077
  for (const imp of imports) {
4954
5078
  const suffix = imp.split('/').pop();
4955
- for (const f of fileSet) { if (f.endsWith(path.sep+suffix+'.go')||f.includes(path.sep+suffix+path.sep)) { found.push(f); break; } }
5079
+ for (const f of fileSet) { const normF = normalizePath(f); if (normF.endsWith(path.sep+suffix+'.go')||normF.includes(path.sep+suffix+path.sep)) { found.push(normF); break; } }
4956
5080
  }
4957
5081
  }
4958
5082
  if (RS_EXTS.has(ext)) {
4959
5083
  const reMod = /^\s*(?:pub\s+)?mod\s+(\w+)\s*;/gm; let m;
4960
5084
  while ((m = reMod.exec(content)) !== null) {
4961
- const c1 = path.join(dir,m[1]+'.rs'); if (fileSet.has(c1)) found.push(c1);
4962
- const c2 = path.join(dir,m[1],'mod.rs'); if (fileSet.has(c2)) found.push(c2);
5085
+ const c1 = path.join(dir,m[1]+'.rs'); const normC1 = normalizePath(c1); if (fileSet.has(normC1)) found.push(normC1);
5086
+ const c2 = path.join(dir,m[1],'mod.rs'); const normC2 = normalizePath(c2); if (fileSet.has(normC2)) found.push(normC2);
4963
5087
  }
4964
5088
  }
4965
5089
  if (JVM_EXTS.has(ext)) {
@@ -4967,7 +5091,7 @@ __factories["./src/graph/builder"] = function(module, exports) {
4967
5091
  while ((m = re.exec(content)) !== null) {
4968
5092
  const asPath = m[1].replace(/\./g,path.sep);
4969
5093
  for (const jvmExt of ['.java','.kt','.kts','.scala','.sc']) {
4970
- for (const f of fileSet) { if (f.endsWith(asPath+jvmExt)) { found.push(f); break; } }
5094
+ for (const f of fileSet) { const normF = normalizePath(f); if (normF.endsWith(normalizePath(asPath+jvmExt))) { found.push(normF); break; } }
4971
5095
  }
4972
5096
  }
4973
5097
  }
@@ -4976,27 +5100,90 @@ __factories["./src/graph/builder"] = function(module, exports) {
4976
5100
  while ((m = re.exec(content)) !== null) {
4977
5101
  const base = path.resolve(dir,m[1]);
4978
5102
  const candidate = base.endsWith('.rb') ? base : base+'.rb';
4979
- if (fileSet.has(candidate)) found.push(candidate);
5103
+ const normC = normalizePath(candidate);
5104
+ if (fileSet.has(normC)) found.push(normC);
5105
+ }
5106
+ }
5107
+ if (R_EXTS.has(ext)) {
5108
+ const stripped = content.replace(/#.*$/gm, '');
5109
+ const reSrc = /(?:^|[^\w.])source\s*\(\s*["']([^"']+)["']/g; let m;
5110
+ while ((m = reSrc.exec(stripped)) !== null) {
5111
+ const r = resolveRPath(dir, m[1], fileSet, cwd); if (r) found.push(r);
5112
+ }
5113
+ if (ctx && ctx.rPackage && ctx.rLocalDefs && ctx.rLocalDefs.size > 0) {
5114
+ const reNs = new RegExp('\\b' + escapeRegex(ctx.rPackage) + ':::?([A-Za-z][\\w.]*)', 'g');
5115
+ while ((m = reNs.exec(stripped)) !== null) {
5116
+ const target = ctx.rLocalDefs.get(m[1]);
5117
+ if (!target) continue;
5118
+ const normTarget = normalizePath(target);
5119
+ const normFilePath = normalizePath(filePath);
5120
+ if (normTarget === normFilePath) continue;
5121
+ if (fileSet.has(target)) {
5122
+ found.push(target);
5123
+ } else if (fileSet.has(normTarget)) {
5124
+ found.push(normTarget);
5125
+ }
5126
+ }
4980
5127
  }
4981
5128
  }
4982
5129
  return [...new Set(found)];
4983
5130
  }
4984
- function build(files, cwd) {
5131
+ function build(files, cwd, ctx) {
4985
5132
  const fileSet = new Set(files.map((f) => path.resolve(f)));
4986
- const forward = new Map(); const reverse = new Map();
4987
- for (const f of fileSet) { if (!forward.has(f)) forward.set(f,[]); if (!reverse.has(f)) reverse.set(f,[]); }
5133
+ const fileSetNormalized = new Set([...fileSet].map(normalizePath));
5134
+ const forward = new Map();
5135
+ const reverse = new Map();
5136
+ for (const f of fileSet) {
5137
+ const normF = normalizePath(f);
5138
+ if (!forward.has(normF)) forward.set(normF, []);
5139
+ if (!reverse.has(normF)) reverse.set(normF, []);
5140
+ }
4988
5141
  for (const filePath of fileSet) {
4989
- let content; try { content = fs.readFileSync(filePath,'utf8'); } catch(_) { continue; }
4990
- const deps = extractFileDeps(filePath, content, fileSet);
5142
+ let content;
5143
+ try { content = fs.readFileSync(filePath,'utf8'); } catch(_) { continue; }
5144
+ const normFilePath = normalizePath(filePath);
5145
+ const deps = extractFileDeps(filePath, content, fileSetNormalized, cwd, ctx);
4991
5146
  if (deps.length > 0) {
4992
- forward.set(filePath, deps);
4993
- for (const dep of deps) { if (!reverse.has(dep)) reverse.set(dep,[]); reverse.get(dep).push(filePath); }
5147
+ forward.set(normFilePath, deps);
5148
+ for (const dep of deps) {
5149
+ if (!reverse.has(dep)) reverse.set(dep, []);
5150
+ reverse.get(dep).push(normFilePath);
5151
+ }
4994
5152
  }
4995
5153
  }
4996
5154
  return { forward, reverse };
4997
5155
  }
5156
+ function _readDescriptionPackage(cwd) {
5157
+ try {
5158
+ const raw = fs.readFileSync(path.join(cwd, 'DESCRIPTION'), 'utf8');
5159
+ const m = raw.match(/^Package\s*:\s*(\S+)/m);
5160
+ return m ? m[1] : null;
5161
+ } catch (_) { return null; }
5162
+ }
5163
+ function _collectRLocalDefs(rFiles) {
5164
+ const defs = new Map();
5165
+ const reAssign = /^(?:[ \t]*)([\w.]+)\s*(?:<<-|<-|=)\s*(?:(?:R6::)?R6Class|(?:S7::)?new_class|function)\b/gm;
5166
+ const reS4Generic = /^[ \t]*setGeneric\s*\(\s*["']([\w.]+)["']/gm;
5167
+ const reS4Class = /^[ \t]*setClass\s*\(\s*["']([\w.]+)["']/gm;
5168
+ for (const filePath of rFiles) {
5169
+ let content; try { content = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
5170
+ const stripped = content.replace(/#.*$/gm, '');
5171
+ let m;
5172
+ while ((m = reAssign.exec(stripped)) !== null) {
5173
+ if (m[1].startsWith('.')) continue;
5174
+ if (!defs.has(m[1])) defs.set(m[1], filePath);
5175
+ }
5176
+ while ((m = reS4Generic.exec(stripped)) !== null) {
5177
+ if (!defs.has(m[1])) defs.set(m[1], filePath);
5178
+ }
5179
+ while ((m = reS4Class.exec(stripped)) !== null) {
5180
+ if (!defs.has(m[1])) defs.set(m[1], filePath);
5181
+ }
5182
+ }
5183
+ return defs;
5184
+ }
4998
5185
  function buildFromCwd(cwd, opts) {
4999
- const { srcDirs=['src','app','lib'], exclude=['node_modules','.git','dist','build'] } = opts||{};
5186
+ const { srcDirs=['src','app','lib','R','inst'], exclude=['node_modules','.git','dist','build'] } = opts||{};
5000
5187
  const excludeSet = new Set(exclude);
5001
5188
  function walkDir(dir,depth) {
5002
5189
  if (depth>8) return [];
@@ -5008,19 +5195,25 @@ __factories["./src/graph/builder"] = function(module, exports) {
5008
5195
  if (e.isDirectory()) out.push(...walkDir(full,depth+1));
5009
5196
  else if (e.isFile()) {
5010
5197
  const ext = path.extname(e.name).toLowerCase();
5011
- if (JS_EXTS.has(ext)||PY_EXTS.has(ext)||GO_EXTS.has(ext)||RS_EXTS.has(ext)||JVM_EXTS.has(ext)||RB_EXTS.has(ext)) out.push(full);
5198
+ if (JS_EXTS.has(ext)||PY_EXTS.has(ext)||GO_EXTS.has(ext)||RS_EXTS.has(ext)||JVM_EXTS.has(ext)||RB_EXTS.has(ext)||R_EXTS.has(ext)) out.push(full);
5012
5199
  }
5013
5200
  }
5014
5201
  return out;
5015
5202
  }
5016
5203
  const files=[];
5017
5204
  for (const sd of srcDirs) { const absDir=path.resolve(cwd,sd); if (fs.existsSync(absDir)) files.push(...walkDir(absDir,0)); }
5018
- for (const rootFile of ['gen-context.js','index.js','main.js','app.js']) {
5205
+ for (const rootFile of ['gen-context.js','index.js','main.js','app.js','app.R','server.R','ui.R','global.R']) {
5019
5206
  const abs=path.resolve(cwd,rootFile); if (fs.existsSync(abs)) files.push(abs);
5020
5207
  }
5021
- return build(files, cwd);
5208
+ let ctx;
5209
+ const rPackage = _readDescriptionPackage(cwd);
5210
+ if (rPackage) {
5211
+ const rFiles = files.filter((f) => R_EXTS.has(path.extname(f).toLowerCase()));
5212
+ if (rFiles.length > 0) ctx = { rPackage, rLocalDefs: _collectRLocalDefs(rFiles) };
5213
+ }
5214
+ return build(files, cwd, ctx);
5022
5215
  }
5023
- module.exports = { build, buildFromCwd, extractFileDeps };
5216
+ module.exports = { build, buildFromCwd, extractFileDeps, normalizePath };
5024
5217
  };
5025
5218
 
5026
5219
  // ── ./src/graph/impact ──
@@ -5032,6 +5225,7 @@ __factories["./src/graph/impact"] = function(module, exports) {
5032
5225
  const ROUTE_PATTERNS = [/router?\.[jt]sx?$/i,/routes?\.[jt]sx?$/i,/controller\.[jt]sx?$/i,/views?\.[jt]sx?$/i,/handlers?\.[jt]sx?$/i];
5033
5226
  function isTestFile(f) { return TEST_PATTERNS.some((re) => re.test(f.replace(/\\/g,'/'))); }
5034
5227
  function isRouteFile(f) { return ROUTE_PATTERNS.some((re) => re.test(f.replace(/\\/g,'/'))); }
5228
+ function normalizePath(p) { return path.normalize(p).toLowerCase(); }
5035
5229
  function bfs(startFile, reverseGraph, maxDepth) {
5036
5230
  const direct=new Set(); const transitive=new Set(); const visited=new Set([startFile]);
5037
5231
  const firstLevel = reverseGraph.get(startFile)||[];
@@ -5050,7 +5244,7 @@ __factories["./src/graph/impact"] = function(module, exports) {
5050
5244
  }
5051
5245
  function getImpact(changedFile, graph, opts) {
5052
5246
  const {depth=0,cwd=process.cwd()} = opts||{};
5053
- const absChanged = path.resolve(cwd,changedFile);
5247
+ const absChanged = normalizePath(path.resolve(cwd,changedFile));
5054
5248
  if (!graph||!graph.reverse) return {changed:changedFile,direct:[],transitive:[],tests:[],routes:[],totalImpact:0};
5055
5249
  const {direct,transitive} = bfs(absChanged,graph.reverse,depth);
5056
5250
  const allImpacted=[...direct,...transitive];
@@ -5707,7 +5901,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
5707
5901
 
5708
5902
  const SERVER_INFO = {
5709
5903
  name: 'sigmap',
5710
- version: '6.10.6',
5904
+ version: '6.10.10',
5711
5905
  description: 'SigMap MCP server — code signatures on demand',
5712
5906
  };
5713
5907
 
@@ -6727,6 +6921,21 @@ __factories["./src/retrieval/ranker"] = function(module, exports) {
6727
6921
  if (learnedWeights && score > 0) score *= learnedWeights[file] || 1.0;
6728
6922
  scored.push({ file, score, sigs, tokens: Math.ceil(sigs.join('\n').length / 4) });
6729
6923
  }
6924
+ // Compute confidence levels based on score distribution
6925
+ if (scored.length > 0) {
6926
+ const scores = scored.map(s => s.score);
6927
+ const maxScore = Math.max(...scores);
6928
+ const minScore = Math.min(...scores);
6929
+ const scoreRange = maxScore - minScore || 1;
6930
+ for (const entry of scored) {
6931
+ if (entry.score <= 0) {
6932
+ entry.confidence = 'low';
6933
+ } else {
6934
+ const normalized = (entry.score - minScore) / scoreRange;
6935
+ entry.confidence = normalized > 0.66 ? 'high' : normalized > 0.33 ? 'medium' : 'low';
6936
+ }
6937
+ }
6938
+ }
6730
6939
  scored.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
6731
6940
  return scored.slice(0, topK);
6732
6941
  }
@@ -6894,6 +7103,7 @@ __factories["./src/eval/analyzer"] = function(module, exports) {
6894
7103
  '.swift': 'swift',
6895
7104
  '.dart': 'dart',
6896
7105
  '.scala': 'scala', '.sc': 'scala',
7106
+ '.r': 'r', '.R': 'r',
6897
7107
  '.vue': 'vue_sfc',
6898
7108
  '.svelte': 'svelte',
6899
7109
  '.html': 'html', '.htm': 'html',
@@ -7934,7 +8144,7 @@ __factories["./src/discovery/language-detector"] = function(module, exports) {
7934
8144
  if (e.isDirectory()) {
7935
8145
  _walkDepth(path.join(dir, e.name), depth - 1, extCount);
7936
8146
  } else if (e.isFile()) {
7937
- const EXT_TO_LANG = {'.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript', '.ts': 'typescript', '.tsx': 'typescript', '.jsx': 'javascript', '.py': 'python', '.rb': 'ruby', '.go': 'go', '.rs': 'rust', '.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift', '.dart': 'dart', '.scala': 'scala', '.php': 'php'};
8147
+ const EXT_TO_LANG = {'.js': 'javascript', '.mjs': 'javascript', '.cjs': 'javascript', '.ts': 'typescript', '.tsx': 'typescript', '.jsx': 'javascript', '.py': 'python', '.rb': 'ruby', '.go': 'go', '.rs': 'rust', '.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp', '.c': 'cpp', '.h': 'cpp', '.hpp': 'cpp', '.swift': 'swift', '.dart': 'dart', '.scala': 'scala', '.php': 'php', '.r': 'r', '.R': 'r'};
7938
8148
  const ext = path.extname(e.name).toLowerCase();
7939
8149
  if (EXT_TO_LANG[ext]) extCount[ext] = (extCount[ext] || 0) + 1;
7940
8150
  }
@@ -8334,7 +8544,7 @@ const path = require('path');
8334
8544
  const os = require('os');
8335
8545
  const { execSync } = require('child_process');
8336
8546
 
8337
- const VERSION = '6.10.6';
8547
+ const VERSION = '6.10.10';
8338
8548
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
8339
8549
 
8340
8550
  function requireSourceOrBundled(key) {
@@ -8370,6 +8580,7 @@ const EXT_MAP = {
8370
8580
  '.swift': 'swift',
8371
8581
  '.dart': 'dart',
8372
8582
  '.scala': 'scala', '.sc': 'scala',
8583
+ '.r': 'r', '.R': 'r',
8373
8584
  '.vue': 'vue_sfc',
8374
8585
  '.svelte': 'svelte',
8375
8586
  '.html': 'html', '.htm': 'html',
@@ -8509,10 +8720,11 @@ function detectAndExtract(filePath, content, maxSigsPerFile) {
8509
8720
  function extractFileDeps(filePath, content, config) {
8510
8721
  if (config && config.depMap === false) return [];
8511
8722
  try {
8512
- const { extractPythonDeps, extractTSDeps } = requireSourceOrBundled('./src/extractors/deps');
8723
+ const { extractPythonDeps, extractTSDeps, extractRDeps } = requireSourceOrBundled('./src/extractors/deps');
8513
8724
  const ext = path.extname(filePath).toLowerCase();
8514
8725
  if (ext === '.py' || ext === '.pyw') return extractPythonDeps(content);
8515
8726
  if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext)) return extractTSDeps(content);
8727
+ if (ext === '.r') return extractRDeps ? extractRDeps(content) : [];
8516
8728
  } catch (_) {}
8517
8729
  return [];
8518
8730
  }
@@ -11425,6 +11637,7 @@ function main() {
11425
11637
  '.java': 'java', '.kt': 'kotlin', '.go': 'go', '.rs': 'rust',
11426
11638
  '.cs': 'csharp', '.cpp': 'cpp', '.rb': 'ruby', '.php': 'php',
11427
11639
  '.swift': 'swift', '.dart': 'dart', '.scala': 'scala',
11640
+ '.r': 'r', '.R': 'r',
11428
11641
  '.vue': 'vue', '.svelte': 'svelte', '.html': 'html',
11429
11642
  '.css': 'css', '.yml': 'yaml', '.sh': 'shell',
11430
11643
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "6.10.7",
3
+ "version": "6.10.10",
4
4
  "description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
5
5
  "main": "gen-context.js",
6
6
  "exports": {
@@ -14,10 +14,10 @@
14
14
  "gen-project-map": "./gen-project-map.js"
15
15
  },
16
16
  "scripts": {
17
- "test": "node test/run.js",
17
+ "test": "node test/run.js && node test/r-language.test.js",
18
18
  "test:integration": "node test/integration/strategy.test.js && node test/integration/secret-scan.test.js && node test/integration/token-budget.test.js && node test/integration/auto-budget.test.js && node test/integration/mcp-server.test.js",
19
19
  "test:integration:all": "node test/integration/all.js",
20
- "test:all": "node test/run.js && node test/integration/strategy.test.js && node test/integration/secret-scan.test.js",
20
+ "test:all": "node test/run.js && node test/r-language.test.js && node test/integration/strategy.test.js && node test/integration/secret-scan.test.js",
21
21
  "generate": "node gen-context.js",
22
22
  "watch": "node gen-context.js --watch",
23
23
  "setup": "node gen-context.js --setup",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "6.10.6",
3
+ "version": "6.10.10",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "6.10.6",
3
+ "version": "6.10.10",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [