sigmap 2.9.1 → 3.0.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/gen-context.js CHANGED
@@ -33,6 +33,10 @@ __factories["./src/config/defaults"] = function(module, exports) {
33
33
 
34
34
  // Output targets: 'copilot' | 'claude' | 'cursor' | 'windsurf'
35
35
  outputs: ['copilot'],
36
+
37
+ // Adapter targets (v3.0+): replaces 'outputs'. Same names, adds 'openai' | 'gemini'.
38
+ // Old 'outputs' config key is still accepted and silently maps to 'adapters'.
39
+ adapters: null, // null means: use 'outputs' for backward compat
36
40
 
37
41
  // Directories to scan (relative to project root)
38
42
  srcDirs: [
@@ -163,6 +167,14 @@ __factories["./src/config/loader"] = function(module, exports) {
163
167
  merged[key] = val;
164
168
  }
165
169
  }
170
+ // Backward compat (v3.0+): if user specified 'adapters', use it as 'outputs' too.
171
+ // If user specified only 'outputs' (old configs), mirror to 'adapters'.
172
+ if (merged.adapters && !Array.isArray(merged.adapters)) merged.adapters = null;
173
+ if (!merged.adapters && Array.isArray(merged.outputs)) {
174
+ merged.adapters = merged.outputs.slice();
175
+ } else if (Array.isArray(merged.adapters) && !userConfig.outputs) {
176
+ merged.outputs = merged.adapters.filter((a) => ['copilot','claude','cursor','windsurf'].includes(a));
177
+ }
166
178
  return merged;
167
179
  }
168
180
 
@@ -1937,6 +1949,316 @@ __factories["./src/format/cache"] = function(module, exports) {
1937
1949
 
1938
1950
  };
1939
1951
 
1952
+ // ── ./src/format/dashboard ──
1953
+ __factories["./src/format/dashboard"] = function(module, exports) {
1954
+ const fs = require('fs');
1955
+ const path = require('path');
1956
+ const { readLog } = __require('./src/tracking/logger');
1957
+
1958
+ const LANGUAGE_KEYS = [
1959
+ 'typescript', 'javascript', 'python', 'java', 'kotlin', 'go', 'rust',
1960
+ 'csharp', 'cpp', 'ruby', 'php', 'swift', 'dart', 'scala', 'vue',
1961
+ 'svelte', 'html', 'css', 'yaml', 'shell', 'dockerfile',
1962
+ ];
1963
+
1964
+ function toNumber(v) {
1965
+ const n = Number(v);
1966
+ return Number.isFinite(n) ? n : null;
1967
+ }
1968
+
1969
+ function percentile(values, p) {
1970
+ if (!Array.isArray(values) || values.length === 0) return 0;
1971
+ const sorted = values.filter(Number.isFinite).slice().sort((a, b) => a - b);
1972
+ if (sorted.length === 0) return 0;
1973
+ if (p <= 0) return sorted[0];
1974
+ if (p >= 100) return sorted[sorted.length - 1];
1975
+ const idx = (p / 100) * (sorted.length - 1);
1976
+ const lo = Math.floor(idx);
1977
+ const hi = Math.ceil(idx);
1978
+ if (lo === hi) return sorted[lo];
1979
+ const frac = idx - lo;
1980
+ return sorted[lo] + (sorted[hi] - sorted[lo]) * frac;
1981
+ }
1982
+
1983
+ function overBudgetStreak(entries) {
1984
+ if (!Array.isArray(entries) || entries.length === 0) return 0;
1985
+ let streak = 0;
1986
+ for (let i = entries.length - 1; i >= 0; i--) {
1987
+ if (entries[i] && entries[i].overBudget) streak++;
1988
+ else break;
1989
+ }
1990
+ return streak;
1991
+ }
1992
+
1993
+ function walkFiles(dir, maxDepth, depth, out, excludeSet) {
1994
+ if (depth > maxDepth) return;
1995
+ let entries = [];
1996
+ try {
1997
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1998
+ } catch (_) {
1999
+ return;
2000
+ }
2001
+ for (const entry of entries) {
2002
+ const abs = path.join(dir, entry.name);
2003
+ const rel = abs.replace(/\\/g, '/');
2004
+ if (excludeSet.has(entry.name) || rel.includes('/node_modules/') || rel.includes('/.git/')) continue;
2005
+ if (entry.isDirectory()) walkFiles(abs, maxDepth, depth + 1, out, excludeSet);
2006
+ else if (entry.isFile()) out.push(abs);
2007
+ }
2008
+ }
2009
+
2010
+ function detectLanguage(filePath) {
2011
+ const base = path.basename(filePath);
2012
+ const ext = path.extname(filePath).toLowerCase();
2013
+ if (base === 'Dockerfile' || /^Dockerfile\./.test(base)) return 'dockerfile';
2014
+ if (ext === '.ts' || ext === '.tsx') return 'typescript';
2015
+ if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') return 'javascript';
2016
+ if (ext === '.py' || ext === '.pyw') return 'python';
2017
+ if (ext === '.java') return 'java';
2018
+ if (ext === '.kt' || ext === '.kts') return 'kotlin';
2019
+ if (ext === '.go') return 'go';
2020
+ if (ext === '.rs') return 'rust';
2021
+ if (ext === '.cs') return 'csharp';
2022
+ if (ext === '.cpp' || ext === '.c' || ext === '.h' || ext === '.hpp' || ext === '.cc') return 'cpp';
2023
+ if (ext === '.rb' || ext === '.rake') return 'ruby';
2024
+ if (ext === '.php') return 'php';
2025
+ if (ext === '.swift') return 'swift';
2026
+ if (ext === '.dart') return 'dart';
2027
+ if (ext === '.scala' || ext === '.sc') return 'scala';
2028
+ if (ext === '.vue') return 'vue';
2029
+ if (ext === '.svelte') return 'svelte';
2030
+ if (ext === '.html' || ext === '.htm') return 'html';
2031
+ if (ext === '.css' || ext === '.scss' || ext === '.sass' || ext === '.less') return 'css';
2032
+ if (ext === '.yml' || ext === '.yaml') return 'yaml';
2033
+ if (ext === '.sh' || ext === '.bash' || ext === '.zsh' || ext === '.fish') return 'shell';
2034
+ return null;
2035
+ }
2036
+
2037
+ function computeExtractorCoverage(cwd) {
2038
+ let cfg = {};
2039
+ try {
2040
+ const cfgPath = path.join(cwd, 'gen-context.config.json');
2041
+ if (fs.existsSync(cfgPath)) cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
2042
+ } catch (_) {}
2043
+ const srcDirs = Array.isArray(cfg.srcDirs) && cfg.srcDirs.length > 0
2044
+ ? cfg.srcDirs
2045
+ : ['src', 'app', 'lib', 'packages', 'services', 'api'];
2046
+ const exclude = new Set(['node_modules', '.git', 'dist', 'build', 'out', '__pycache__', '.next', 'coverage', 'target', 'vendor', '.context']);
2047
+ if (Array.isArray(cfg.exclude)) for (const x of cfg.exclude) exclude.add(String(x));
2048
+ const counts = {};
2049
+ for (const k of LANGUAGE_KEYS) counts[k] = 0;
2050
+ const files = [];
2051
+ for (const relDir of srcDirs) {
2052
+ const absDir = path.join(cwd, relDir);
2053
+ if (!fs.existsSync(absDir)) continue;
2054
+ walkFiles(absDir, 8, 0, files, exclude);
2055
+ }
2056
+ for (const f of files) {
2057
+ const lang = detectLanguage(f);
2058
+ if (lang) counts[lang]++;
2059
+ }
2060
+ const covered = LANGUAGE_KEYS.filter((k) => counts[k] > 0).length;
2061
+ const supported = LANGUAGE_KEYS.length;
2062
+ const pct = supported > 0 ? parseFloat(((covered / supported) * 100).toFixed(1)) : 0;
2063
+ return { supported, covered, pct, perLanguage: counts };
2064
+ }
2065
+
2066
+ function readBenchmarkTrend(cwd) {
2067
+ const resultDir = path.join(cwd, 'benchmarks', 'results');
2068
+ if (!fs.existsSync(resultDir)) return [];
2069
+ const files = [];
2070
+ walkFiles(resultDir, 6, 0, files, new Set());
2071
+ const values = [];
2072
+ for (const filePath of files) {
2073
+ const base = path.basename(filePath).toLowerCase();
2074
+ if (!base.endsWith('.json') && !base.endsWith('.jsonl') && !base.endsWith('.ndjson')) continue;
2075
+ let raw = '';
2076
+ try { raw = fs.readFileSync(filePath, 'utf8'); } catch (_) { continue; }
2077
+ if (base.endsWith('.json')) {
2078
+ try {
2079
+ const obj = JSON.parse(raw);
2080
+ const v = toNumber(obj && obj.hitAt5);
2081
+ const m = toNumber(obj && obj.metrics && obj.metrics.hitAt5);
2082
+ if (v !== null) values.push(v);
2083
+ else if (m !== null) values.push(m);
2084
+ } catch (_) {}
2085
+ continue;
2086
+ }
2087
+ for (const line of raw.split('\n').filter(Boolean)) {
2088
+ try {
2089
+ const obj = JSON.parse(line);
2090
+ const v = toNumber(obj && obj.hitAt5);
2091
+ const m = toNumber(obj && obj.metrics && obj.metrics.hitAt5);
2092
+ if (v !== null) values.push(v);
2093
+ else if (m !== null) values.push(m);
2094
+ } catch (_) {}
2095
+ }
2096
+ }
2097
+ return values.slice(-30);
2098
+ }
2099
+
2100
+ function lineChartSvg(values, title, ySuffix) {
2101
+ const width = 760;
2102
+ const height = 210;
2103
+ const left = 38;
2104
+ const right = 18;
2105
+ const top = 22;
2106
+ const bottom = 30;
2107
+ const innerW = width - left - right;
2108
+ const innerH = height - top - bottom;
2109
+ const clean = values.filter((n) => Number.isFinite(n));
2110
+ if (clean.length === 0) {
2111
+ return `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="${title}"><rect x="0" y="0" width="100%" height="100%" fill="#0f1320" rx="12"/><text x="20" y="36" fill="#d7defa" font-size="14" font-family="monospace">${title}</text><text x="20" y="96" fill="#8ea0d9" font-size="13" font-family="monospace">No data yet. Run with --track and --benchmark.</text></svg>`;
2112
+ }
2113
+ const min = Math.min(...clean);
2114
+ const max = Math.max(...clean);
2115
+ const span = max - min || 1;
2116
+ const points = clean.map((v, i) => {
2117
+ const x = left + ((clean.length === 1 ? 0 : i / (clean.length - 1)) * innerW);
2118
+ const y = top + (1 - ((v - min) / span)) * innerH;
2119
+ return `${x.toFixed(1)},${y.toFixed(1)}`;
2120
+ }).join(' ');
2121
+ const latest = clean[clean.length - 1];
2122
+ const grid = [];
2123
+ for (let i = 0; i <= 4; i++) {
2124
+ const gy = top + (i / 4) * innerH;
2125
+ grid.push(`<line x1="${left}" y1="${gy.toFixed(1)}" x2="${left + innerW}" y2="${gy.toFixed(1)}" stroke="#223056" stroke-width="1"/>`);
2126
+ }
2127
+ return `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="${title}"><rect x="0" y="0" width="100%" height="100%" fill="#0f1320" rx="12"/><text x="20" y="26" fill="#d7defa" font-size="13" font-family="monospace">${title}</text>${grid.join('')}<polyline fill="none" stroke="#42d392" stroke-width="2.5" points="${points}"/><text x="20" y="${height - 8}" fill="#8ea0d9" font-size="12" font-family="monospace">latest: ${latest.toFixed(2)}${ySuffix || ''}</text></svg>`;
2128
+ }
2129
+
2130
+ function barChartSvg(perLanguage) {
2131
+ const width = 760;
2132
+ const height = 260;
2133
+ const left = 20;
2134
+ const top = 34;
2135
+ const usableW = width - left * 2;
2136
+ const max = Math.max(1, ...LANGUAGE_KEYS.map((k) => perLanguage[k] || 0));
2137
+ const barW = usableW / LANGUAGE_KEYS.length;
2138
+ const labels = ['ts', 'js', 'py', 'java', 'kt', 'go', 'rs', 'cs', 'cpp', 'rb', 'php', 'swift', 'dart', 'scala', 'vue', 'sv', 'html', 'css', 'yaml', 'sh', 'df'];
2139
+ const bars = [];
2140
+ for (let i = 0; i < LANGUAGE_KEYS.length; i++) {
2141
+ const key = LANGUAGE_KEYS[i];
2142
+ const v = perLanguage[key] || 0;
2143
+ const h = (v / max) * 160;
2144
+ const x = left + i * barW + 2;
2145
+ const y = top + 160 - h;
2146
+ bars.push(`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${Math.max(2, barW - 4).toFixed(1)}" height="${h.toFixed(1)}" fill="#7aa2ff" rx="2"/>`);
2147
+ }
2148
+ const xLabels = labels.map((lbl, i) => {
2149
+ const x = left + i * barW + barW / 2;
2150
+ return `<text x="${x.toFixed(1)}" y="222" fill="#8ea0d9" font-size="9" font-family="monospace" text-anchor="middle">${lbl}</text>`;
2151
+ });
2152
+ return `<svg viewBox="0 0 760 260" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Extractor coverage by language"><rect x="0" y="0" width="100%" height="100%" fill="#0f1320" rx="12"/><text x="20" y="24" fill="#d7defa" font-size="13" font-family="monospace">Per-language extractor coverage (file counts)</text><line x1="20" y1="194" x2="740" y2="194" stroke="#223056" stroke-width="1"/>${bars.join('')}${xLabels.join('')}</svg>`;
2153
+ }
2154
+
2155
+ function sparkline(values) {
2156
+ const clean = values.filter((n) => Number.isFinite(n));
2157
+ if (clean.length === 0) return 'n/a';
2158
+ const ticks = '▁▂▃▄▅▆▇█';
2159
+ const min = Math.min(...clean);
2160
+ const max = Math.max(...clean);
2161
+ const span = max - min || 1;
2162
+ return clean.map((v) => {
2163
+ const idx = Math.max(0, Math.min(ticks.length - 1, Math.round(((v - min) / span) * (ticks.length - 1))));
2164
+ return ticks[idx];
2165
+ }).join('');
2166
+ }
2167
+
2168
+ function buildDashboardData(cwd, health) {
2169
+ const entries = readLog(cwd);
2170
+ const recent = entries.slice(-30);
2171
+ const tokenReductionTrend = recent.map((e) => toNumber(e.reductionPct)).filter((n) => n !== null);
2172
+ const hitAt5Trend = readBenchmarkTrend(cwd);
2173
+ const coverage = computeExtractorCoverage(cwd);
2174
+ const finals = entries.map((e) => toNumber(e.finalTokens)).filter((n) => n !== null);
2175
+ const summary = {
2176
+ grade: health.grade,
2177
+ score: health.score,
2178
+ daysSinceRegen: health.daysSinceRegen,
2179
+ totalRuns: entries.length,
2180
+ overBudgetRate: entries.length > 0 ? parseFloat(((entries.filter((e) => e.overBudget).length / entries.length) * 100).toFixed(1)) : 0,
2181
+ p50TokenCount: Math.round(percentile(finals, 50)),
2182
+ p95TokenCount: Math.round(percentile(finals, 95)),
2183
+ overBudgetStreak: overBudgetStreak(entries),
2184
+ extractorCoverage: coverage.pct,
2185
+ };
2186
+ return {
2187
+ summary,
2188
+ tokenReductionTrend,
2189
+ hitAt5Trend,
2190
+ coverage,
2191
+ charts: {
2192
+ tokenReductionSvg: lineChartSvg(tokenReductionTrend, 'Token reduction trend (last 30 tracked runs)', '%'),
2193
+ hitAt5Svg: lineChartSvg(hitAt5Trend, 'hit@5 trend (last 30 benchmark runs)', ''),
2194
+ coverageSvg: barChartSvg(coverage.perLanguage),
2195
+ },
2196
+ };
2197
+ }
2198
+
2199
+ function generateDashboardHtml(cwd, health) {
2200
+ const data = buildDashboardData(cwd, health);
2201
+ const cards = [
2202
+ { label: 'Current grade', value: `${data.summary.grade} (${data.summary.score}/100)` },
2203
+ { label: 'Days since regen', value: data.summary.daysSinceRegen === null ? 'n/a' : String(data.summary.daysSinceRegen) },
2204
+ { label: 'Total tracked runs', value: String(data.summary.totalRuns) },
2205
+ { label: 'Over-budget %', value: `${data.summary.overBudgetRate}%` },
2206
+ { label: 'p50 token count', value: String(data.summary.p50TokenCount) },
2207
+ { label: 'p95 token count', value: String(data.summary.p95TokenCount) },
2208
+ { label: 'Over-budget streak', value: String(data.summary.overBudgetStreak) },
2209
+ { label: 'Extractor coverage', value: `${data.summary.extractorCoverage}%` },
2210
+ ];
2211
+ const cardHtml = cards.map((c) => `<div class="card"><div class="label">${c.label}</div><div class="value">${c.value}</div></div>`).join('');
2212
+ const html = [
2213
+ '<!doctype html>', '<html lang="en">', '<head>', '<meta charset="utf-8"/>',
2214
+ '<meta name="viewport" content="width=device-width,initial-scale=1"/>', '<title>SigMap Dashboard</title>',
2215
+ '<style>',
2216
+ 'body{margin:0;background:#0a0f1e;color:#e6ecff;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}',
2217
+ '.wrap{max-width:980px;margin:0 auto;padding:24px}',
2218
+ 'h1{font-size:22px;margin:0 0 6px 0}', '.sub{color:#8ea0d9;font-size:12px;margin-bottom:20px}',
2219
+ '.grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:16px}',
2220
+ '.card{background:#111a33;border:1px solid #223056;border-radius:10px;padding:10px}',
2221
+ '.label{font-size:11px;color:#8ea0d9;margin-bottom:6px}', '.value{font-size:16px;color:#f5f7ff}',
2222
+ '.panel{background:#111a33;border:1px solid #223056;border-radius:12px;padding:10px;margin-top:12px}',
2223
+ '@media (max-width:900px){.grid{grid-template-columns:repeat(2,minmax(0,1fr));}}',
2224
+ '</style>', '</head>', '<body>', '<div class="wrap">',
2225
+ '<h1>SigMap v2.10 dashboard</h1>',
2226
+ '<div class="sub">Self-contained report. No external scripts, styles, or network calls.</div>',
2227
+ `<div class="grid">${cardHtml}</div>`,
2228
+ `<div class="panel">${data.charts.tokenReductionSvg}</div>`,
2229
+ `<div class="panel">${data.charts.hitAt5Svg}</div>`,
2230
+ `<div class="panel">${data.charts.coverageSvg}</div>`,
2231
+ '</div>', '</body>', '</html>',
2232
+ ].join('');
2233
+ return { html, data };
2234
+ }
2235
+
2236
+ function renderHistoryCharts(cwd, health) {
2237
+ const data = buildDashboardData(cwd, health);
2238
+ const lines = [
2239
+ '[sigmap] history charts:',
2240
+ ` token reduction trend : ${sparkline(data.tokenReductionTrend)}`,
2241
+ ` hit@5 trend : ${sparkline(data.hitAt5Trend)}`,
2242
+ ` extractor coverage : ${data.coverage.covered}/${data.coverage.supported} (${data.coverage.pct}%)`,
2243
+ '',
2244
+ '[sigmap] inline svg: token reduction', data.charts.tokenReductionSvg,
2245
+ '',
2246
+ '[sigmap] inline svg: hit@5', data.charts.hitAt5Svg,
2247
+ '',
2248
+ '[sigmap] inline svg: coverage', data.charts.coverageSvg,
2249
+ ];
2250
+ return {
2251
+ text: lines.join('\n'),
2252
+ tokenReductionSparkline: sparkline(data.tokenReductionTrend),
2253
+ hitAt5Sparkline: sparkline(data.hitAt5Trend),
2254
+ summary: data.summary,
2255
+ charts: data.charts,
2256
+ };
2257
+ }
2258
+
2259
+ module.exports = { generateDashboardHtml, renderHistoryCharts, computeExtractorCoverage, percentile, overBudgetStreak };
2260
+ };
2261
+
1940
2262
  // ── ./src/health/scorer ──
1941
2263
  __factories["./src/health/scorer"] = function(module, exports) {
1942
2264
 
@@ -1970,36 +2292,148 @@ __factories["./src/health/scorer"] = function(module, exports) {
1970
2292
  function score(cwd) {
1971
2293
  const fs = require('fs');
1972
2294
  const path = require('path');
2295
+
2296
+ function toNumber(v) {
2297
+ const n = Number(v);
2298
+ return Number.isFinite(n) ? n : null;
2299
+ }
2300
+
2301
+ function percentile(values, p) {
2302
+ if (!Array.isArray(values) || values.length === 0) return 0;
2303
+ const sorted = values.filter(Number.isFinite).slice().sort((a, b) => a - b);
2304
+ if (sorted.length === 0) return 0;
2305
+ if (p <= 0) return sorted[0];
2306
+ if (p >= 100) return sorted[sorted.length - 1];
2307
+ const idx = (p / 100) * (sorted.length - 1);
2308
+ const lo = Math.floor(idx);
2309
+ const hi = Math.ceil(idx);
2310
+ if (lo === hi) return sorted[lo];
2311
+ const frac = idx - lo;
2312
+ return sorted[lo] + (sorted[hi] - sorted[lo]) * frac;
2313
+ }
2314
+
2315
+ function overBudgetStreak(entries) {
2316
+ if (!Array.isArray(entries) || entries.length === 0) return 0;
2317
+ let streak = 0;
2318
+ for (let i = entries.length - 1; i >= 0; i--) {
2319
+ if (entries[i] && entries[i].overBudget) streak++;
2320
+ else break;
2321
+ }
2322
+ return streak;
2323
+ }
2324
+
2325
+ function walkFiles(dir, maxDepth, depth, out, excludeSet) {
2326
+ if (depth > maxDepth) return;
2327
+ let entries = [];
2328
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
2329
+ for (const entry of entries) {
2330
+ const abs = path.join(dir, entry.name);
2331
+ const rel = abs.replace(/\\/g, '/');
2332
+ if (excludeSet.has(entry.name) || rel.includes('/node_modules/') || rel.includes('/.git/')) continue;
2333
+ if (entry.isDirectory()) walkFiles(abs, maxDepth, depth + 1, out, excludeSet);
2334
+ else if (entry.isFile()) out.push(abs);
2335
+ }
2336
+ }
2337
+
2338
+ function detectLanguage(filePath) {
2339
+ const base = path.basename(filePath);
2340
+ const ext = path.extname(filePath).toLowerCase();
2341
+ if (base === 'Dockerfile' || /^Dockerfile\./.test(base)) return 'dockerfile';
2342
+ if (ext === '.ts' || ext === '.tsx') return 'typescript';
2343
+ if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') return 'javascript';
2344
+ if (ext === '.py' || ext === '.pyw') return 'python';
2345
+ if (ext === '.java') return 'java';
2346
+ if (ext === '.kt' || ext === '.kts') return 'kotlin';
2347
+ if (ext === '.go') return 'go';
2348
+ if (ext === '.rs') return 'rust';
2349
+ if (ext === '.cs') return 'csharp';
2350
+ if (ext === '.cpp' || ext === '.c' || ext === '.h' || ext === '.hpp' || ext === '.cc') return 'cpp';
2351
+ if (ext === '.rb' || ext === '.rake') return 'ruby';
2352
+ if (ext === '.php') return 'php';
2353
+ if (ext === '.swift') return 'swift';
2354
+ if (ext === '.dart') return 'dart';
2355
+ if (ext === '.scala' || ext === '.sc') return 'scala';
2356
+ if (ext === '.vue') return 'vue';
2357
+ if (ext === '.svelte') return 'svelte';
2358
+ if (ext === '.html' || ext === '.htm') return 'html';
2359
+ if (ext === '.css' || ext === '.scss' || ext === '.sass' || ext === '.less') return 'css';
2360
+ if (ext === '.yml' || ext === '.yaml') return 'yaml';
2361
+ if (ext === '.sh' || ext === '.bash' || ext === '.zsh' || ext === '.fish') return 'shell';
2362
+ return null;
2363
+ }
2364
+
2365
+ function computeExtractorCoverage(cwd, cfg) {
2366
+ const LANGUAGE_KEYS = [
2367
+ 'typescript', 'javascript', 'python', 'java', 'kotlin', 'go', 'rust',
2368
+ 'csharp', 'cpp', 'ruby', 'php', 'swift', 'dart', 'scala', 'vue',
2369
+ 'svelte', 'html', 'css', 'yaml', 'shell', 'dockerfile',
2370
+ ];
2371
+ const srcDirs = Array.isArray(cfg && cfg.srcDirs) && cfg.srcDirs.length > 0
2372
+ ? cfg.srcDirs
2373
+ : ['src', 'app', 'lib', 'packages', 'services', 'api'];
2374
+ const exclude = new Set(['node_modules', '.git', 'dist', 'build', 'out', '__pycache__', '.next', 'coverage', 'target', 'vendor', '.context']);
2375
+ if (cfg && Array.isArray(cfg.exclude)) for (const x of cfg.exclude) exclude.add(String(x));
2376
+ const counts = {};
2377
+ for (const k of LANGUAGE_KEYS) counts[k] = 0;
2378
+ const files = [];
2379
+ for (const relDir of srcDirs) {
2380
+ const absDir = path.join(cwd, relDir);
2381
+ if (!fs.existsSync(absDir)) continue;
2382
+ walkFiles(absDir, 8, 0, files, exclude);
2383
+ }
2384
+ for (const f of files) {
2385
+ const lang = detectLanguage(f);
2386
+ if (lang) counts[lang]++;
2387
+ }
2388
+ const covered = LANGUAGE_KEYS.filter((k) => counts[k] > 0).length;
2389
+ return LANGUAGE_KEYS.length > 0 ? parseFloat(((covered / LANGUAGE_KEYS.length) * 100).toFixed(1)) : 0;
2390
+ }
1973
2391
 
1974
2392
  let tokenReductionPct = null;
1975
2393
  let daysSinceRegen = null;
1976
2394
  let strategyFreshnessDays = null;
1977
2395
  let overBudgetRuns = 0;
1978
2396
  let totalRuns = 0;
2397
+ let p50TokenCount = 0;
2398
+ let p95TokenCount = 0;
2399
+ let overBudgetStreakCount = 0;
2400
+ let extractorCoverage = 0;
2401
+ let cfgObj = null;
2402
+ let entries = [];
1979
2403
 
1980
2404
  // ── Detect active strategy ──────────────────────────────────────────────
1981
2405
  let strategy = 'full';
1982
2406
  try {
1983
2407
  const cfgPath = path.join(cwd, 'gen-context.config.json');
1984
2408
  if (fs.existsSync(cfgPath)) {
1985
- const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
1986
- strategy = cfg.strategy || 'full';
2409
+ cfgObj = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
2410
+ strategy = cfgObj.strategy || 'full';
1987
2411
  }
1988
2412
  } catch (_) {}
1989
2413
 
1990
2414
  // ── Read usage log via tracking logger ──────────────────────────────────
1991
2415
  try {
1992
2416
  const { readLog, summarize } = __require('./src/tracking/logger');
1993
- const entries = readLog(cwd);
2417
+ entries = readLog(cwd);
1994
2418
  const s = summarize(entries);
1995
2419
  // Only set tokenReductionPct when there is actual history; a brand-new/
1996
2420
  // untracked project should not be penalised for "0% reduction".
1997
2421
  if (s.totalRuns > 0) tokenReductionPct = s.avgReductionPct;
1998
2422
  overBudgetRuns = s.overBudgetRuns;
1999
2423
  totalRuns = s.totalRuns;
2424
+ const finals = entries.map((e) => toNumber(e.finalTokens)).filter((n) => n !== null);
2425
+ p50TokenCount = Math.round(percentile(finals, 50));
2426
+ p95TokenCount = Math.round(percentile(finals, 95));
2427
+ overBudgetStreakCount = overBudgetStreak(entries);
2000
2428
  } catch (_) {
2001
2429
  // No usage log yet — proceed with nulls
2002
2430
  }
2431
+
2432
+ try {
2433
+ extractorCoverage = computeExtractorCoverage(cwd, cfgObj || {});
2434
+ } catch (_) {
2435
+ extractorCoverage = 0;
2436
+ }
2003
2437
 
2004
2438
  // ── Days since primary context file was last regenerated ─────────────────
2005
2439
  try {
@@ -2057,7 +2491,20 @@ __factories["./src/health/scorer"] = function(module, exports) {
2057
2491
  else if (points >= 60) grade = 'C';
2058
2492
  else grade = 'D';
2059
2493
 
2060
- return { score: points, grade, strategy, tokenReductionPct, daysSinceRegen, strategyFreshnessDays, totalRuns, overBudgetRuns };
2494
+ return {
2495
+ score: points,
2496
+ grade,
2497
+ strategy,
2498
+ tokenReductionPct,
2499
+ daysSinceRegen,
2500
+ strategyFreshnessDays,
2501
+ totalRuns,
2502
+ overBudgetRuns,
2503
+ p50TokenCount,
2504
+ p95TokenCount,
2505
+ overBudgetStreak: overBudgetStreakCount,
2506
+ extractorCoverage,
2507
+ };
2061
2508
  }
2062
2509
 
2063
2510
  module.exports = { score };
@@ -3107,7 +3554,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
3107
3554
 
3108
3555
  const SERVER_INFO = {
3109
3556
  name: 'sigmap',
3110
- version: '1.5.0',
3557
+ version: '2.10.0',
3111
3558
  description: 'SigMap MCP server — code signatures on demand',
3112
3559
  };
3113
3560
 
@@ -4292,6 +4739,158 @@ __factories["./src/eval/runner"] = function(module, exports) {
4292
4739
  };
4293
4740
 
4294
4741
 
4742
+ // ── ./packages/adapters/copilot (bundled) ──
4743
+ __factories["./packages/adapters/copilot"] = function(module, exports) {
4744
+ const path = require('path');
4745
+ const name = 'copilot';
4746
+ function format(context, opts = {}) {
4747
+ if (!context || typeof context !== 'string') return '';
4748
+ const version = (opts && opts.version) || 'unknown';
4749
+ const timestamp = new Date().toISOString();
4750
+ return [
4751
+ `<!-- Generated by SigMap gen-context.js v${version} -->`,
4752
+ `<!-- Updated: ${timestamp} -->`,
4753
+ `<!-- Do not edit below — regenerate with: node gen-context.js -->`,
4754
+ '',
4755
+ '# Code signatures',
4756
+ '',
4757
+ context,
4758
+ ].join('\n');
4759
+ }
4760
+ function outputPath(cwd) { return path.join(cwd, '.github', 'copilot-instructions.md'); }
4761
+ module.exports = { name, format, outputPath };
4762
+ };
4763
+
4764
+ // ── ./packages/adapters/claude (bundled) ──
4765
+ __factories["./packages/adapters/claude"] = function(module, exports) {
4766
+ const path = require('path');
4767
+ const fs = require('fs');
4768
+ const name = 'claude';
4769
+ const CLAUDE_MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
4770
+ function format(context, opts = {}) {
4771
+ if (!context || typeof context !== 'string') return '';
4772
+ const version = (opts && opts.version) || 'unknown';
4773
+ const timestamp = new Date().toISOString();
4774
+ return [`<!-- Generated by SigMap v${version} — ${timestamp} -->`, '', context].join('\n');
4775
+ }
4776
+ function outputPath(cwd) { return path.join(cwd, 'CLAUDE.md'); }
4777
+ function write(context, cwd, opts = {}) {
4778
+ const filePath = outputPath(cwd);
4779
+ let existing = '';
4780
+ if (fs.existsSync(filePath)) existing = fs.readFileSync(filePath, 'utf8');
4781
+ const formatted = format(context, opts);
4782
+ const markerIdx = existing.indexOf('## Auto-generated signatures');
4783
+ const newContent = markerIdx !== -1
4784
+ ? existing.slice(0, markerIdx) + CLAUDE_MARKER.trimStart() + formatted
4785
+ : existing + CLAUDE_MARKER + formatted;
4786
+ fs.writeFileSync(filePath, newContent, 'utf8');
4787
+ }
4788
+ module.exports = { name, format, outputPath, write };
4789
+ };
4790
+
4791
+ // ── ./packages/adapters/cursor (bundled) ──
4792
+ __factories["./packages/adapters/cursor"] = function(module, exports) {
4793
+ const path = require('path');
4794
+ const name = 'cursor';
4795
+ function format(context, opts = {}) {
4796
+ if (!context || typeof context !== 'string') return '';
4797
+ const version = (opts && opts.version) || 'unknown';
4798
+ const timestamp = new Date().toISOString();
4799
+ return [`# Code signatures — generated by SigMap v${version}`, `# Updated: ${timestamp}`, `# Regenerate: node gen-context.js`, '', context].join('\n');
4800
+ }
4801
+ function outputPath(cwd) { return path.join(cwd, '.cursorrules'); }
4802
+ module.exports = { name, format, outputPath };
4803
+ };
4804
+
4805
+ // ── ./packages/adapters/windsurf (bundled) ──
4806
+ __factories["./packages/adapters/windsurf"] = function(module, exports) {
4807
+ const path = require('path');
4808
+ const name = 'windsurf';
4809
+ function format(context, opts = {}) {
4810
+ if (!context || typeof context !== 'string') return '';
4811
+ const version = (opts && opts.version) || 'unknown';
4812
+ const timestamp = new Date().toISOString();
4813
+ return [`# Code signatures — generated by SigMap v${version}`, `# Updated: ${timestamp}`, `# Regenerate: node gen-context.js`, '', context].join('\n');
4814
+ }
4815
+ function outputPath(cwd) { return path.join(cwd, '.windsurfrules'); }
4816
+ module.exports = { name, format, outputPath };
4817
+ };
4818
+
4819
+ // ── ./packages/adapters/openai (bundled) ──
4820
+ __factories["./packages/adapters/openai"] = function(module, exports) {
4821
+ const path = require('path');
4822
+ const name = 'openai';
4823
+ function format(context, opts = {}) {
4824
+ if (!context || typeof context !== 'string') return '';
4825
+ const version = (opts && opts.version) || 'unknown';
4826
+ const timestamp = new Date().toISOString();
4827
+ const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
4828
+ return [
4829
+ `You are a coding assistant with full knowledge of this codebase.`,
4830
+ `Below are the code signatures extracted by SigMap v${version} on ${timestamp}.`,
4831
+ projectLine,
4832
+ `Use these signatures to answer questions about the code accurately.`,
4833
+ ``,
4834
+ `## Code Signatures`,
4835
+ ``,
4836
+ context,
4837
+ ].join('\n');
4838
+ }
4839
+ function outputPath(cwd) { return path.join(cwd, '.github', 'openai-context.md'); }
4840
+ module.exports = { name, format, outputPath };
4841
+ };
4842
+
4843
+ // ── ./packages/adapters/gemini (bundled) ──
4844
+ __factories["./packages/adapters/gemini"] = function(module, exports) {
4845
+ const path = require('path');
4846
+ const name = 'gemini';
4847
+ function format(context, opts = {}) {
4848
+ if (!context || typeof context !== 'string') return '';
4849
+ const version = (opts && opts.version) || 'unknown';
4850
+ const timestamp = new Date().toISOString();
4851
+ const projectLine = (opts && opts.projectName) ? `Project: ${opts.projectName}\n` : '';
4852
+ return [
4853
+ `You are a coding assistant with complete knowledge of this codebase.`,
4854
+ `The following code signatures were extracted by SigMap v${version} on ${timestamp}.`,
4855
+ projectLine,
4856
+ `These signatures represent every public function, class, and type in the project.`,
4857
+ ``,
4858
+ `## Code Signatures`,
4859
+ ``,
4860
+ context,
4861
+ ].join('\n');
4862
+ }
4863
+ function outputPath(cwd) { return path.join(cwd, '.github', 'gemini-context.md'); }
4864
+ module.exports = { name, format, outputPath };
4865
+ };
4866
+
4867
+ // ── ./packages/adapters/index (bundled) ──
4868
+ __factories["./packages/adapters/index"] = function(module, exports) {
4869
+ const ADAPTER_NAMES = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini'];
4870
+ const _adapterCache = {};
4871
+ function getAdapter(name) {
4872
+ if (!name || typeof name !== 'string') return null;
4873
+ const key = name.toLowerCase();
4874
+ if (!ADAPTER_NAMES.includes(key)) return null;
4875
+ if (!_adapterCache[key]) {
4876
+ try { _adapterCache[key] = __require('./packages/adapters/' + key); } catch (_) { return null; }
4877
+ }
4878
+ return _adapterCache[key];
4879
+ }
4880
+ function listAdapters() { return ADAPTER_NAMES.slice(); }
4881
+ function adapt(context, adapterName, opts = {}) {
4882
+ if (!context || typeof context !== 'string') return '';
4883
+ const adapter = getAdapter(adapterName);
4884
+ if (!adapter || typeof adapter.format !== 'function') return '';
4885
+ try { return adapter.format(context, opts); } catch (_) { return ''; }
4886
+ }
4887
+ function outputsToAdapters(outputs) {
4888
+ if (!Array.isArray(outputs)) return ['copilot'];
4889
+ return outputs.map((o) => ADAPTER_NAMES.includes(o) ? o : o);
4890
+ }
4891
+ module.exports = { getAdapter, listAdapters, adapt, outputsToAdapters };
4892
+ };
4893
+
4295
4894
  /**
4296
4895
  * SigMap — gen-context.js v1.2.0
4297
4896
  * Zero-dependency AI context engine.
@@ -4304,7 +4903,7 @@ const path = require('path');
4304
4903
  const os = require('os');
4305
4904
  const { execSync } = require('child_process');
4306
4905
 
4307
- const VERSION = '2.9.1';
4906
+ const VERSION = '3.0.0';
4308
4907
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
4309
4908
 
4310
4909
  function requireSourceOrBundled(key) {
@@ -4862,6 +5461,9 @@ function writeCacheOutput(content, cwd) {
4862
5461
  }
4863
5462
 
4864
5463
  function writeOutputs(content, targets, cwd) {
5464
+ // v3.0+: adapter-aware output targets
5465
+ const ADAPTER_TARGETS = new Set(['openai', 'gemini']);
5466
+
4865
5467
  const targetMap = {
4866
5468
  copilot: path.join(cwd, '.github', 'copilot-instructions.md'),
4867
5469
  cursor: path.join(cwd, '.cursorrules'),
@@ -4873,6 +5475,20 @@ function writeOutputs(content, targets, cwd) {
4873
5475
  writeClaude(content, cwd);
4874
5476
  continue;
4875
5477
  }
5478
+ // v3.0+ adapter targets (openai, gemini) — use bundled adapter to format + write
5479
+ if (ADAPTER_TARGETS.has(target)) {
5480
+ try {
5481
+ const adapterMod = __require('./packages/adapters/' + target);
5482
+ const formatted = adapterMod.format(content, { version: VERSION });
5483
+ const outPath = adapterMod.outputPath(cwd);
5484
+ ensureDir(outPath);
5485
+ fs.writeFileSync(outPath, formatted, 'utf8');
5486
+ console.warn(`[sigmap] wrote ${path.relative(cwd, outPath)}`);
5487
+ } catch (err) {
5488
+ console.warn(`[sigmap] adapter "${target}" failed: ${err.message}`);
5489
+ }
5490
+ continue;
5491
+ }
4876
5492
  const outPath = targetMap[target];
4877
5493
  if (!outPath) {
4878
5494
  console.warn(`[sigmap] unknown output target: ${target}`);
@@ -5503,6 +6119,8 @@ Usage:
5503
6119
  node gen-context.js --report Token reduction stats to stdout
5504
6120
  node gen-context.js --report --json Token report as JSON (for CI; exits 1 if over budget)
5505
6121
  node gen-context.js --report --history Print usage log summary from .context/usage.ndjson
6122
+ node gen-context.js --report --history --chart Include inline SVG charts + Unicode sparklines
6123
+ node gen-context.js --dashboard Write benchmarks/reports/dashboard.html
5506
6124
  node gen-context.js --suggest-tool "<task>" Recommend model tier for a task description
5507
6125
  node gen-context.js --suggest-tool "<task>" --json Machine-readable tier recommendation
5508
6126
  node gen-context.js --health Print composite health score
@@ -5511,6 +6129,8 @@ Usage:
5511
6129
  node gen-context.js --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
5512
6130
  node gen-context.js --diff --staged Generate context for staged files only
5513
6131
  node gen-context.js --benchmark Run retrieval benchmark (benchmarks/tasks/retrieval.jsonl)
6132
+ node gen-context.js --adapter <name> Generate for a specific adapter only (v3.0+)
6133
+ node gen-context.js --adapter <name> --json Show adapter output path as JSON
5514
6134
  node gen-context.js --benchmark --json Benchmark results as JSON
5515
6135
  node gen-context.js --eval Alias for --benchmark
5516
6136
  node gen-context.js --analyze Per-file breakdown: sigs, tokens, extractor, coverage
@@ -5535,6 +6155,10 @@ Strategies (set via config "strategy" key):
5535
6155
  ~90% fewer tokens. Best with MCP (Claude Code, Cursor).
5536
6156
  Set "hotCommits": N to control how many commits count as hot (default 10).
5537
6157
 
6158
+ Adapters (v3.0+): copilot | claude | cursor | windsurf | openai | gemini
6159
+ Set "adapters": ["copilot","openai"] in config to write multiple adapter outputs.
6160
+ Old "outputs" config key is still accepted (maps to adapters automatically).
6161
+
5538
6162
  Config: gen-context.config.json
5539
6163
  Ignore: .contextignore, .repomixignore
5540
6164
  Output: .github/copilot-instructions.md (default)
@@ -5625,6 +6249,31 @@ function main() {
5625
6249
  }
5626
6250
  console.log(` total runs : ${result.totalRuns}`);
5627
6251
  console.log(` over-budget runs: ${result.overBudgetRuns}`);
6252
+ console.log(` p50 token count : ${result.p50TokenCount}`);
6253
+ console.log(` p95 token count : ${result.p95TokenCount}`);
6254
+ console.log(` overbudget streak: ${result.overBudgetStreak}`);
6255
+ console.log(` extractor cover.: ${result.extractorCoverage}%`);
6256
+ }
6257
+ process.exit(0);
6258
+ }
6259
+
6260
+ if (args.includes('--dashboard')) {
6261
+ try {
6262
+ const { score } = __require('./src/health/scorer');
6263
+ const health = score(cwd);
6264
+ const { generateDashboardHtml } = requireSourceOrBundled('./src/format/dashboard');
6265
+ const out = generateDashboardHtml(cwd, health);
6266
+ const outPath = path.join(cwd, 'benchmarks', 'reports', 'dashboard.html');
6267
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
6268
+ fs.writeFileSync(outPath, out.html, 'utf8');
6269
+ if (args.includes('--json')) {
6270
+ process.stdout.write(JSON.stringify({ ok: true, file: path.relative(cwd, outPath), summary: out.data.summary }) + '\n');
6271
+ } else {
6272
+ console.log(`[sigmap] dashboard written: ${path.relative(cwd, outPath)}`);
6273
+ }
6274
+ } catch (err) {
6275
+ console.error(`[sigmap] dashboard error: ${err.message}`);
6276
+ process.exit(1);
5628
6277
  }
5629
6278
  process.exit(0);
5630
6279
  }
@@ -5841,6 +6490,44 @@ function main() {
5841
6490
  process.exit(0);
5842
6491
  }
5843
6492
 
6493
+ // ── --adapter <name> ───────────────────────────────────────────────────────
6494
+ if (args.includes('--adapter')) {
6495
+ try {
6496
+ const adpIdx = args.indexOf('--adapter');
6497
+ const adapterName = (args[adpIdx + 1] || '').trim().toLowerCase();
6498
+ const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini'];
6499
+ if (!adapterName || adapterName.startsWith('--')) {
6500
+ console.error('[sigmap] --adapter requires an adapter name');
6501
+ console.error(` Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
6502
+ console.error(' Example: node gen-context.js --adapter copilot');
6503
+ process.exit(1);
6504
+ }
6505
+ if (!VALID_ADAPTERS.includes(adapterName)) {
6506
+ console.error(`[sigmap] unknown adapter: "${adapterName}"`);
6507
+ console.error(` Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
6508
+ process.exit(1);
6509
+ }
6510
+ if (args.includes('--json')) {
6511
+ const adapterMod = __require('./packages/adapters/' + adapterName);
6512
+ const outPath = adapterMod.outputPath(cwd);
6513
+ process.stdout.write(JSON.stringify({ adapter: adapterName, outputPath: outPath, version: VERSION }) + '\n');
6514
+ process.exit(0);
6515
+ }
6516
+ // Run generate with this adapter's output targets
6517
+ const singleAdapterConfig = Object.assign({}, config, {
6518
+ outputs: adapterName === 'claude' ? ['claude'] : [adapterName],
6519
+ adapters: [adapterName],
6520
+ });
6521
+ runGenerate(cwd, singleAdapterConfig, false);
6522
+ const adapterMod = __require('./packages/adapters/' + adapterName);
6523
+ console.warn(`[sigmap] adapter "${adapterName}" → ${path.relative(cwd, adapterMod.outputPath(cwd))}`);
6524
+ } catch (err) {
6525
+ console.error(`[sigmap] adapter error: ${err.message}`);
6526
+ process.exit(1);
6527
+ }
6528
+ process.exit(0);
6529
+ }
6530
+
5844
6531
  // ── --impact <file> ────────────────────────────────────────────────────────
5845
6532
  if (args.includes('--impact')) {
5846
6533
  try {
@@ -5876,8 +6563,25 @@ function main() {
5876
6563
  const { readLog, summarize } = __require('./src/tracking/logger');
5877
6564
  const entries = readLog(cwd);
5878
6565
  const summary = summarize(entries);
6566
+ let chartInfo = null;
6567
+ if (args.includes('--chart')) {
6568
+ const { score } = __require('./src/health/scorer');
6569
+ const { renderHistoryCharts } = requireSourceOrBundled('./src/format/dashboard');
6570
+ chartInfo = renderHistoryCharts(cwd, score(cwd));
6571
+ }
5879
6572
  if (args.includes('--json')) {
5880
- process.stdout.write(JSON.stringify(summary) + '\n');
6573
+ const payload = chartInfo
6574
+ ? {
6575
+ ...summary,
6576
+ chart: {
6577
+ tokenReductionSparkline: chartInfo.tokenReductionSparkline,
6578
+ hitAt5Sparkline: chartInfo.hitAt5Sparkline,
6579
+ summary: chartInfo.summary,
6580
+ charts: chartInfo.charts,
6581
+ },
6582
+ }
6583
+ : summary;
6584
+ process.stdout.write(JSON.stringify(payload) + '\n');
5881
6585
  } else {
5882
6586
  console.log('[sigmap] usage history:');
5883
6587
  console.log(` total runs : ${summary.totalRuns}`);
@@ -5886,6 +6590,10 @@ function main() {
5886
6590
  console.log(` over-budget runs: ${summary.overBudgetRuns}`);
5887
6591
  if (summary.firstRun) console.log(` first run : ${summary.firstRun}`);
5888
6592
  if (summary.lastRun) console.log(` last run : ${summary.lastRun}`);
6593
+ if (chartInfo) {
6594
+ console.log('');
6595
+ console.log(chartInfo.text);
6596
+ }
5889
6597
  }
5890
6598
  } catch (err) {
5891
6599
  console.warn(`[sigmap] tracking: ${err.message}`);