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/CHANGELOG.md +39 -0
- package/README.md +35 -17
- package/gen-context.js +715 -7
- package/package.json +1 -1
- package/packages/adapters/claude.js +71 -0
- package/packages/adapters/copilot.js +47 -0
- package/packages/adapters/cursor.js +45 -0
- package/packages/adapters/gemini.js +59 -0
- package/packages/adapters/index.js +79 -0
- package/packages/adapters/openai.js +60 -0
- package/packages/adapters/windsurf.js +45 -0
- package/packages/cli/package.json +1 -1
- package/packages/core/README.md +16 -4
- package/packages/core/index.js +29 -0
- package/packages/core/package.json +1 -1
- package/src/config/defaults.js +4 -0
- package/src/config/loader.js +7 -0
- package/src/format/dashboard.js +400 -0
- package/src/health/scorer.js +30 -1
- package/src/mcp/server.js +1 -1
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
|
-
|
|
1986
|
-
strategy =
|
|
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
|
-
|
|
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 {
|
|
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: '
|
|
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 = '
|
|
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
|
-
|
|
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}`);
|