sigmap 7.1.0 → 7.2.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 +18 -0
- package/gen-context.js +553 -14
- package/llms-full.txt +1 -1
- package/llms.txt +1 -1
- package/package.json +4 -2
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/mcp/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,24 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [7.2.0] — 2026-06-17
|
|
14
|
+
|
|
15
|
+
Minor release — release-pipeline robustness. Hardens the bundle/release machinery that produced the v7.1.0 binary failure, with no user-facing CLI changes.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Bundle integrity check (#266):** `scripts/check-bundle.mjs` verifies every `src/` module is registered in `gen-context.js` `__factories` (the standalone/SEA-binary code path). Runs in CI on every PR (Node 18/20/22) and in `prepublishOnly`; `--fix` inserts missing factories from source. `build-binary.mjs` reuses the same check. Catches — before merge — the gap that broke the v7.1.0 binaries.
|
|
19
|
+
- **version.json metadata gate (#268):** `scripts/check-version-meta.mjs` derives `mcp_tools` (from `src/mcp/tools.js`) and `tests` (test-file count) and fails on drift; wired into `prepublishOnly`. `languages` stays editorial. Corrected stale `tests` count.
|
|
20
|
+
- **Standalone-bundle smoke test (#274):** runs `gen-context.js` from a temp dir with no `src/` present (the binary path) — `generate` + `--health` + `gain` — in the Node 18/20/22 matrix. Functional complement to the presence check.
|
|
21
|
+
- **`docs/RELEASING.md` (#274):** documents the release flow, branch model, tag triggers, and the prepublish/CI gates.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **`--health` clarity (#270):** the per-repo "extractor coverage" line (languages present in this repo ÷ supported) read as contradictory beside a 100/100 score. Relabeled as informational ("repo languages … not scored") with a "score basis" line. The `--health --json` `extractorCoverage` field is unchanged.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- **Repo declutter (#272):** removed the committed, unreferenced `TESTING_IMPORT_GRAPH.md` (gitignored); stale planning docs archived out of the repo. `PROJECT_MAP.md` kept (live generated artifact).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
13
31
|
## [7.1.0] — 2026-06-16
|
|
14
32
|
|
|
15
33
|
Minor release — a token-savings dashboard in the terminal, plus domain and sponsorship docs.
|
package/gen-context.js
CHANGED
|
@@ -6254,7 +6254,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
|
|
|
6254
6254
|
|
|
6255
6255
|
const SERVER_INFO = {
|
|
6256
6256
|
name: 'sigmap',
|
|
6257
|
-
version: '7.
|
|
6257
|
+
version: '7.2.0',
|
|
6258
6258
|
description: 'SigMap MCP server — code signatures on demand',
|
|
6259
6259
|
};
|
|
6260
6260
|
|
|
@@ -7023,12 +7023,15 @@ __factories["./src/tracking/logger"] = function(module, exports) {
|
|
|
7023
7023
|
* config.tracking: true (gen-context.config.json)
|
|
7024
7024
|
* --track CLI flag
|
|
7025
7025
|
*/
|
|
7026
|
-
|
|
7026
|
+
|
|
7027
7027
|
const fs = require('fs');
|
|
7028
7028
|
const path = require('path');
|
|
7029
|
-
|
|
7029
|
+
|
|
7030
7030
|
const LOG_FILE = path.join('.context', 'usage.ndjson');
|
|
7031
|
-
|
|
7031
|
+
// Dedicated log for the `gain` dashboard (extended schema). Kept separate from
|
|
7032
|
+
// usage.ndjson so the legacy health/nudge history never collides with it.
|
|
7033
|
+
const GAIN_FILE = path.join('.context', 'gain.ndjson');
|
|
7034
|
+
|
|
7032
7035
|
/**
|
|
7033
7036
|
* Append one run entry to the usage log.
|
|
7034
7037
|
* @param {object} entry - Run metrics from runGenerate()
|
|
@@ -7039,7 +7042,7 @@ __factories["./src/tracking/logger"] = function(module, exports) {
|
|
|
7039
7042
|
const logPath = path.join(cwd, LOG_FILE);
|
|
7040
7043
|
const dir = path.dirname(logPath);
|
|
7041
7044
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
7042
|
-
|
|
7045
|
+
|
|
7043
7046
|
const record = {
|
|
7044
7047
|
ts: new Date().toISOString(),
|
|
7045
7048
|
version: entry.version || '0.9.0',
|
|
@@ -7053,14 +7056,14 @@ __factories["./src/tracking/logger"] = function(module, exports) {
|
|
|
7053
7056
|
overBudget: entry.overBudget || false,
|
|
7054
7057
|
budgetLimit: entry.budgetLimit || 6000,
|
|
7055
7058
|
};
|
|
7056
|
-
|
|
7059
|
+
|
|
7057
7060
|
fs.appendFileSync(logPath, JSON.stringify(record) + '\n', 'utf8');
|
|
7058
7061
|
} catch (err) {
|
|
7059
7062
|
// Never crash the main process — tracking is optional
|
|
7060
7063
|
process.stderr.write(`[sigmap] tracking: could not write log: ${err.message}\n`);
|
|
7061
7064
|
}
|
|
7062
7065
|
}
|
|
7063
|
-
|
|
7066
|
+
|
|
7064
7067
|
/**
|
|
7065
7068
|
* Read and parse all usage log entries.
|
|
7066
7069
|
* @param {string} cwd - Project root (absolute path)
|
|
@@ -7082,7 +7085,26 @@ __factories["./src/tracking/logger"] = function(module, exports) {
|
|
|
7082
7085
|
return [];
|
|
7083
7086
|
}
|
|
7084
7087
|
}
|
|
7085
|
-
|
|
7088
|
+
|
|
7089
|
+
/**
|
|
7090
|
+
* Read and parse all `gain` dashboard records (oldest first).
|
|
7091
|
+
* @param {string} cwd
|
|
7092
|
+
* @returns {object[]}
|
|
7093
|
+
*/
|
|
7094
|
+
function readGainLog(cwd) {
|
|
7095
|
+
try {
|
|
7096
|
+
const logPath = path.join(cwd, GAIN_FILE);
|
|
7097
|
+
if (!fs.existsSync(logPath)) return [];
|
|
7098
|
+
return fs.readFileSync(logPath, 'utf8')
|
|
7099
|
+
.split('\n')
|
|
7100
|
+
.filter(Boolean)
|
|
7101
|
+
.map((line) => { try { return JSON.parse(line); } catch (_) { return null; } })
|
|
7102
|
+
.filter(Boolean);
|
|
7103
|
+
} catch (_) {
|
|
7104
|
+
return [];
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
|
|
7086
7108
|
/**
|
|
7087
7109
|
* Compute summary statistics from an array of log records.
|
|
7088
7110
|
* @param {object[]} entries
|
|
@@ -7102,13 +7124,13 @@ __factories["./src/tracking/logger"] = function(module, exports) {
|
|
|
7102
7124
|
overBudgetRuns: 0,
|
|
7103
7125
|
};
|
|
7104
7126
|
}
|
|
7105
|
-
|
|
7127
|
+
|
|
7106
7128
|
const reductions = entries.map((e) => e.reductionPct || 0);
|
|
7107
7129
|
const finals = entries.map((e) => e.finalTokens || 0);
|
|
7108
7130
|
const raws = entries.map((e) => e.rawTokens || 0);
|
|
7109
|
-
|
|
7131
|
+
|
|
7110
7132
|
const avg = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
7111
|
-
|
|
7133
|
+
|
|
7112
7134
|
return {
|
|
7113
7135
|
totalRuns: entries.length,
|
|
7114
7136
|
avgReductionPct: parseFloat(avg(reductions).toFixed(1)),
|
|
@@ -7121,8 +7143,524 @@ __factories["./src/tracking/logger"] = function(module, exports) {
|
|
|
7121
7143
|
overBudgetRuns: entries.filter((e) => e.overBudget).length,
|
|
7122
7144
|
};
|
|
7123
7145
|
}
|
|
7146
|
+
|
|
7147
|
+
/**
|
|
7148
|
+
* Whether `gain` savings capture is enabled. Default: ON (privacy-safe,
|
|
7149
|
+
* local-only, counts only — no paths, source, or query text). This is
|
|
7150
|
+
* intentionally decoupled from the legacy `config.tracking` flag (which gates
|
|
7151
|
+
* the usage.ndjson health log and defaults OFF). Opt out of gain capture via:
|
|
7152
|
+
* config.gainTracking === false · --no-track · SIGMAP_NO_TRACK=1
|
|
7153
|
+
* @param {object} [config]
|
|
7154
|
+
* @param {string[]} [argv]
|
|
7155
|
+
* @returns {boolean}
|
|
7156
|
+
*/
|
|
7157
|
+
function isTrackingEnabled(config, argv) {
|
|
7158
|
+
const a = argv || (typeof process !== 'undefined' ? process.argv : []);
|
|
7159
|
+
if (process.env && process.env.SIGMAP_NO_TRACK) return false;
|
|
7160
|
+
if (a && a.includes('--no-track')) return false;
|
|
7161
|
+
if (config && config.gainTracking === false) return false;
|
|
7162
|
+
return true;
|
|
7163
|
+
}
|
|
7164
|
+
|
|
7165
|
+
/**
|
|
7166
|
+
* Append one operation to the usage log using the extended `gain` schema.
|
|
7167
|
+
* Reuses the same NDJSON file as logRun and is tolerant of partial input.
|
|
7168
|
+
* Never throws — tracking must never break the main process.
|
|
7169
|
+
*
|
|
7170
|
+
* @param {object} entry
|
|
7171
|
+
* @param {string} entry.op e.g. 'ask' | 'generate' | 'query' | 'mcp:get_map'
|
|
7172
|
+
* @param {number} entry.baselineTokens whole-file / candidate baseline (counterfactual)
|
|
7173
|
+
* @param {number} entry.actualTokens tokens SigMap actually emitted
|
|
7174
|
+
* @param {number} [entry.durationMs]
|
|
7175
|
+
* @param {string} [entry.model]
|
|
7176
|
+
* @param {string} [entry.version]
|
|
7177
|
+
* @param {string} cwd
|
|
7178
|
+
*/
|
|
7179
|
+
function recordUsage(entry, cwd) {
|
|
7180
|
+
try {
|
|
7181
|
+
const logPath = path.join(cwd, GAIN_FILE);
|
|
7182
|
+
const dir = path.dirname(logPath);
|
|
7183
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
7184
|
+
|
|
7185
|
+
const baseline = Math.max(0, Number(entry.baselineTokens) || 0);
|
|
7186
|
+
const actual = Math.max(0, Number(entry.actualTokens) || 0);
|
|
7187
|
+
const saved = Math.max(0, baseline - actual);
|
|
7188
|
+
const record = {
|
|
7189
|
+
ts: new Date().toISOString(),
|
|
7190
|
+
v: entry.version || '0.9.0',
|
|
7191
|
+
op: entry.op || 'generate',
|
|
7192
|
+
baselineTokens: baseline,
|
|
7193
|
+
actualTokens: actual,
|
|
7194
|
+
savedTokens: saved,
|
|
7195
|
+
savedPct: baseline > 0 ? parseFloat(((saved / baseline) * 100).toFixed(1)) : 0,
|
|
7196
|
+
durationMs: Math.max(0, Math.round(Number(entry.durationMs) || 0)),
|
|
7197
|
+
model: entry.model || null,
|
|
7198
|
+
ok: entry.ok !== false,
|
|
7199
|
+
};
|
|
7200
|
+
fs.appendFileSync(logPath, JSON.stringify(record) + '\n', 'utf8');
|
|
7201
|
+
} catch (err) {
|
|
7202
|
+
// Never crash the main process — tracking is optional.
|
|
7203
|
+
if (process.stderr) process.stderr.write(`[sigmap] tracking: could not write log: ${err.message}\n`);
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
|
|
7207
|
+
module.exports = { logRun, recordUsage, readLog, readGainLog, summarize, isTrackingEnabled, GAIN_FILE };
|
|
7208
|
+
|
|
7209
|
+
};
|
|
7210
|
+
|
|
7211
|
+
// ── ./src/tracking/pricing ──
|
|
7212
|
+
__factories["./src/tracking/pricing"] = function(module, exports) {
|
|
7213
|
+
|
|
7214
|
+
/**
|
|
7215
|
+
* SigMap pricing table — input-token $/Mtok assumptions for the `gain` dashboard.
|
|
7216
|
+
*
|
|
7217
|
+
* These are ASSUMPTIONS used only to translate "tokens saved" into an estimated
|
|
7218
|
+
* dollar figure. They are deliberately conservative and configurable via
|
|
7219
|
+
* --model <name> or config.pricingModel
|
|
7220
|
+
* The `gain` views always print the model + rate inline so the $ is never
|
|
7221
|
+
* presented as exact. Zero npm dependencies.
|
|
7222
|
+
*/
|
|
7223
|
+
|
|
7224
|
+
// USD per 1,000,000 input tokens.
|
|
7225
|
+
const PRICES = {
|
|
7226
|
+
'claude-sonnet': 3.0,
|
|
7227
|
+
'claude-opus': 15.0,
|
|
7228
|
+
'claude-haiku': 0.8,
|
|
7229
|
+
'gpt-4o': 2.5,
|
|
7230
|
+
'gpt-4o-mini': 0.15,
|
|
7231
|
+
'gemini-1.5-pro': 1.25,
|
|
7232
|
+
'gemini-1.5-flash': 0.075,
|
|
7233
|
+
};
|
|
7234
|
+
|
|
7235
|
+
const DEFAULT_MODEL = 'claude-sonnet';
|
|
7236
|
+
|
|
7237
|
+
/**
|
|
7238
|
+
* Resolve a price (USD per token) for a model name.
|
|
7239
|
+
* @param {string} [model]
|
|
7240
|
+
* @returns {{ model: string, perMtok: number, perToken: number }}
|
|
7241
|
+
*/
|
|
7242
|
+
function resolvePrice(model) {
|
|
7243
|
+
const key = (model || DEFAULT_MODEL).toLowerCase();
|
|
7244
|
+
const perMtok = PRICES[key] != null ? PRICES[key] : PRICES[DEFAULT_MODEL];
|
|
7245
|
+
const resolved = PRICES[key] != null ? key : DEFAULT_MODEL;
|
|
7246
|
+
return { model: resolved, perMtok, perToken: perMtok / 1_000_000 };
|
|
7247
|
+
}
|
|
7248
|
+
|
|
7249
|
+
/** @returns {string[]} known model keys */
|
|
7250
|
+
function listModels() {
|
|
7251
|
+
return Object.keys(PRICES);
|
|
7252
|
+
}
|
|
7253
|
+
|
|
7254
|
+
module.exports = { PRICES, DEFAULT_MODEL, resolvePrice, listModels };
|
|
7255
|
+
|
|
7256
|
+
};
|
|
7257
|
+
|
|
7258
|
+
// ── ./src/tracking/aggregate ──
|
|
7259
|
+
__factories["./src/tracking/aggregate"] = function(module, exports) {
|
|
7260
|
+
|
|
7261
|
+
/**
|
|
7262
|
+
* SigMap usage aggregation for the `gain` dashboard.
|
|
7263
|
+
*
|
|
7264
|
+
* Pure, zero-dependency functions that turn raw NDJSON usage records into the
|
|
7265
|
+
* totals / by-operation / time-bucket shapes the terminal renderer consumes.
|
|
7266
|
+
*
|
|
7267
|
+
* Tolerant of BOTH schemas:
|
|
7268
|
+
* - new: { op, baselineTokens, actualTokens, savedTokens, savedPct, durationMs, model }
|
|
7269
|
+
* - legacy: { rawTokens, finalTokens, reductionPct } (from logger.js v0.9)
|
|
7270
|
+
*
|
|
7271
|
+
* "saved" is a counterfactual estimate (baseline − actual), never a measured
|
|
7272
|
+
* delta. Callers are responsible for labeling it as such in the UI.
|
|
7273
|
+
*/
|
|
7274
|
+
|
|
7275
|
+
const { resolvePrice } = __require('./src/tracking/pricing');
|
|
7276
|
+
|
|
7277
|
+
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
|
7278
|
+
|
|
7279
|
+
/**
|
|
7280
|
+
* Normalize one raw record into a canonical shape.
|
|
7281
|
+
* @param {object} rec
|
|
7282
|
+
*/
|
|
7283
|
+
function normalize(rec) {
|
|
7284
|
+
const baseline = num(rec.baselineTokens != null ? rec.baselineTokens : rec.rawTokens);
|
|
7285
|
+
const actual = num(rec.actualTokens != null ? rec.actualTokens : rec.finalTokens);
|
|
7286
|
+
const saved = rec.savedTokens != null ? num(rec.savedTokens) : Math.max(0, baseline - actual);
|
|
7287
|
+
const savedPct = rec.savedPct != null
|
|
7288
|
+
? num(rec.savedPct)
|
|
7289
|
+
: rec.reductionPct != null
|
|
7290
|
+
? num(rec.reductionPct)
|
|
7291
|
+
: baseline > 0 ? (saved / baseline) * 100 : 0;
|
|
7292
|
+
return {
|
|
7293
|
+
ts: rec.ts || null,
|
|
7294
|
+
op: normalizeOp(rec.op),
|
|
7295
|
+
baseline,
|
|
7296
|
+
actual,
|
|
7297
|
+
saved,
|
|
7298
|
+
savedPct: clamp(savedPct, 0, 100),
|
|
7299
|
+
durationMs: num(rec.durationMs),
|
|
7300
|
+
model: rec.model || null,
|
|
7301
|
+
};
|
|
7302
|
+
}
|
|
7303
|
+
|
|
7304
|
+
function normalizeOp(op) {
|
|
7305
|
+
if (!op) return 'generate';
|
|
7306
|
+
return String(op);
|
|
7307
|
+
}
|
|
7308
|
+
|
|
7309
|
+
function num(v) {
|
|
7310
|
+
const n = Number(v);
|
|
7311
|
+
return Number.isFinite(n) ? n : 0;
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
/**
|
|
7315
|
+
* Parse a --since value into a cutoff Date (or null for "all time").
|
|
7316
|
+
* Accepts: "7d", "30d", "12h", or an ISO date "2026-06-01".
|
|
7317
|
+
* @param {string} since
|
|
7318
|
+
* @param {number} [nowMs] - injectable clock for tests
|
|
7319
|
+
* @returns {Date|null}
|
|
7320
|
+
*/
|
|
7321
|
+
function parseSince(since, nowMs) {
|
|
7322
|
+
if (!since) return null;
|
|
7323
|
+
const now = nowMs != null ? nowMs : Date.now();
|
|
7324
|
+
const rel = /^(\d+)([dhw])$/.exec(String(since).trim());
|
|
7325
|
+
if (rel) {
|
|
7326
|
+
const n = parseInt(rel[1], 10);
|
|
7327
|
+
const unit = rel[2];
|
|
7328
|
+
const ms = unit === 'h' ? 3.6e6 : unit === 'w' ? 6.048e8 : 8.64e7;
|
|
7329
|
+
return new Date(now - n * ms);
|
|
7330
|
+
}
|
|
7331
|
+
const d = new Date(since);
|
|
7332
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
7333
|
+
}
|
|
7334
|
+
|
|
7335
|
+
/**
|
|
7336
|
+
* Bucket records by calendar granularity.
|
|
7337
|
+
* @param {object[]} records - normalized records
|
|
7338
|
+
* @param {'day'|'week'|'month'} granularity
|
|
7339
|
+
* @returns {Array<{key,count,baseline,actual,saved,savedPct,ms}>} ascending by key
|
|
7340
|
+
*/
|
|
7341
|
+
function bucketBy(records, granularity) {
|
|
7342
|
+
const map = new Map();
|
|
7343
|
+
for (const r of records) {
|
|
7344
|
+
if (!r.ts) continue;
|
|
7345
|
+
const key = bucketKey(r.ts, granularity);
|
|
7346
|
+
if (!key) continue;
|
|
7347
|
+
let b = map.get(key);
|
|
7348
|
+
if (!b) { b = { key, count: 0, baseline: 0, actual: 0, saved: 0, ms: 0 }; map.set(key, b); }
|
|
7349
|
+
b.count += 1;
|
|
7350
|
+
b.baseline += r.baseline;
|
|
7351
|
+
b.actual += r.actual;
|
|
7352
|
+
b.saved += r.saved;
|
|
7353
|
+
b.ms += r.durationMs;
|
|
7354
|
+
}
|
|
7355
|
+
return [...map.values()]
|
|
7356
|
+
.map((b) => ({ ...b, savedPct: b.baseline > 0 ? clamp((b.saved / b.baseline) * 100, 0, 100) : 0 }))
|
|
7357
|
+
.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
7358
|
+
}
|
|
7359
|
+
|
|
7360
|
+
function bucketKey(ts, granularity) {
|
|
7361
|
+
const d = new Date(ts);
|
|
7362
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
7363
|
+
const y = d.getUTCFullYear();
|
|
7364
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
7365
|
+
const day = String(d.getUTCDate()).padStart(2, '0');
|
|
7366
|
+
if (granularity === 'month') return `${y}-${m}`;
|
|
7367
|
+
if (granularity === 'week') {
|
|
7368
|
+
// ISO-ish: key by the Monday (UTC) of that week.
|
|
7369
|
+
const tmp = new Date(Date.UTC(y, d.getUTCMonth(), d.getUTCDate()));
|
|
7370
|
+
const dow = (tmp.getUTCDay() + 6) % 7; // 0 = Monday
|
|
7371
|
+
tmp.setUTCDate(tmp.getUTCDate() - dow);
|
|
7372
|
+
return `${tmp.getUTCFullYear()}-${String(tmp.getUTCMonth() + 1).padStart(2, '0')}-${String(tmp.getUTCDate()).padStart(2, '0')}`;
|
|
7373
|
+
}
|
|
7374
|
+
return `${y}-${m}-${day}`;
|
|
7375
|
+
}
|
|
7376
|
+
|
|
7377
|
+
/**
|
|
7378
|
+
* Full aggregation for the `gain` dashboard.
|
|
7379
|
+
* @param {object[]} rawRecords
|
|
7380
|
+
* @param {object} [opts]
|
|
7381
|
+
* @param {string} [opts.model] pricing model
|
|
7382
|
+
* @param {string} [opts.since] window filter
|
|
7383
|
+
* @param {number} [opts.top] limit byOp rows (0 = all)
|
|
7384
|
+
* @param {number} [opts.nowMs] injectable clock
|
|
7385
|
+
* @returns {object}
|
|
7386
|
+
*/
|
|
7387
|
+
function aggregate(rawRecords, opts = {}) {
|
|
7388
|
+
const price = resolvePrice(opts.model);
|
|
7389
|
+
const cutoff = parseSince(opts.since, opts.nowMs);
|
|
7390
|
+
|
|
7391
|
+
let records = (rawRecords || []).map(normalize);
|
|
7392
|
+
if (cutoff) records = records.filter((r) => r.ts && new Date(r.ts) >= cutoff);
|
|
7393
|
+
|
|
7394
|
+
const totals = {
|
|
7395
|
+
count: records.length,
|
|
7396
|
+
baseline: 0,
|
|
7397
|
+
actual: 0,
|
|
7398
|
+
saved: 0,
|
|
7399
|
+
totalMs: 0,
|
|
7400
|
+
savedPct: 0,
|
|
7401
|
+
avgMs: 0,
|
|
7402
|
+
usdSaved: 0,
|
|
7403
|
+
firstTs: null,
|
|
7404
|
+
lastTs: null,
|
|
7405
|
+
};
|
|
7406
|
+
|
|
7407
|
+
const opMap = new Map();
|
|
7408
|
+
for (const r of records) {
|
|
7409
|
+
totals.baseline += r.baseline;
|
|
7410
|
+
totals.actual += r.actual;
|
|
7411
|
+
totals.saved += r.saved;
|
|
7412
|
+
totals.totalMs += r.durationMs;
|
|
7413
|
+
if (r.ts) {
|
|
7414
|
+
if (!totals.firstTs || r.ts < totals.firstTs) totals.firstTs = r.ts;
|
|
7415
|
+
if (!totals.lastTs || r.ts > totals.lastTs) totals.lastTs = r.ts;
|
|
7416
|
+
}
|
|
7417
|
+
let o = opMap.get(r.op);
|
|
7418
|
+
if (!o) { o = { op: r.op, count: 0, baseline: 0, saved: 0, ms: 0 }; opMap.set(r.op, o); }
|
|
7419
|
+
o.count += 1;
|
|
7420
|
+
o.baseline += r.baseline;
|
|
7421
|
+
o.saved += r.saved;
|
|
7422
|
+
o.ms += r.durationMs;
|
|
7423
|
+
}
|
|
7424
|
+
|
|
7425
|
+
totals.savedPct = totals.baseline > 0 ? clamp((totals.saved / totals.baseline) * 100, 0, 100) : 0;
|
|
7426
|
+
totals.avgMs = totals.count > 0 ? Math.round(totals.totalMs / totals.count) : 0;
|
|
7427
|
+
totals.usdSaved = totals.saved * price.perToken;
|
|
7428
|
+
|
|
7429
|
+
let byOp = [...opMap.values()].map((o) => ({
|
|
7430
|
+
op: o.op,
|
|
7431
|
+
count: o.count,
|
|
7432
|
+
saved: o.saved,
|
|
7433
|
+
avgPct: o.baseline > 0 ? clamp((o.saved / o.baseline) * 100, 0, 100) : 0,
|
|
7434
|
+
avgMs: o.count > 0 ? Math.round(o.ms / o.count) : 0,
|
|
7435
|
+
usdSaved: o.saved * price.perToken,
|
|
7436
|
+
sharePct: totals.saved > 0 ? (o.saved / totals.saved) * 100 : 0,
|
|
7437
|
+
})).sort((a, b) => b.saved - a.saved);
|
|
7438
|
+
|
|
7439
|
+
if (opts.top && opts.top > 0) byOp = byOp.slice(0, opts.top);
|
|
7440
|
+
|
|
7441
|
+
return {
|
|
7442
|
+
price,
|
|
7443
|
+
totals,
|
|
7444
|
+
byOp,
|
|
7445
|
+
buckets: {
|
|
7446
|
+
daily: bucketBy(records, 'day'),
|
|
7447
|
+
weekly: bucketBy(records, 'week'),
|
|
7448
|
+
monthly: bucketBy(records, 'month'),
|
|
7449
|
+
},
|
|
7450
|
+
};
|
|
7451
|
+
}
|
|
7452
|
+
|
|
7453
|
+
module.exports = { aggregate, bucketBy, parseSince, normalize };
|
|
7124
7454
|
|
|
7125
|
-
|
|
7455
|
+
};
|
|
7456
|
+
|
|
7457
|
+
// ── ./src/format/gain-terminal ──
|
|
7458
|
+
__factories["./src/format/gain-terminal"] = function(module, exports) {
|
|
7459
|
+
|
|
7460
|
+
/**
|
|
7461
|
+
* SigMap `gain` — terminal renderer.
|
|
7462
|
+
*
|
|
7463
|
+
* Zero-dependency ANSI dashboard for token savings. Honors NO_COLOR and
|
|
7464
|
+
* non-TTY output (plain text for pipes / --json consumers elsewhere).
|
|
7465
|
+
*
|
|
7466
|
+
* "saved" is a counterfactual estimate (whole-file baseline − actual context),
|
|
7467
|
+
* never a measured delta — the footer says so on every view.
|
|
7468
|
+
*/
|
|
7469
|
+
|
|
7470
|
+
const USE_COLOR = !process.env.NO_COLOR && process.stdout.isTTY;
|
|
7471
|
+
|
|
7472
|
+
const C = {
|
|
7473
|
+
reset: USE_COLOR ? '\x1b[0m' : '',
|
|
7474
|
+
dim: USE_COLOR ? '\x1b[2m' : '',
|
|
7475
|
+
bold: USE_COLOR ? '\x1b[1m' : '',
|
|
7476
|
+
green: USE_COLOR ? '\x1b[32m' : '',
|
|
7477
|
+
yellow: USE_COLOR ? '\x1b[33m' : '',
|
|
7478
|
+
red: USE_COLOR ? '\x1b[31m' : '',
|
|
7479
|
+
cyan: USE_COLOR ? '\x1b[36m' : '',
|
|
7480
|
+
gray: USE_COLOR ? '\x1b[90m' : '',
|
|
7481
|
+
};
|
|
7482
|
+
|
|
7483
|
+
// ── formatters ───────────────────────────────────────────────────────────
|
|
7484
|
+
function humanTokens(n) {
|
|
7485
|
+
n = Number(n) || 0;
|
|
7486
|
+
if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B';
|
|
7487
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
7488
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
|
|
7489
|
+
return String(Math.round(n));
|
|
7490
|
+
}
|
|
7491
|
+
|
|
7492
|
+
function fmtInt(n) {
|
|
7493
|
+
return (Number(n) || 0).toLocaleString('en-US');
|
|
7494
|
+
}
|
|
7495
|
+
|
|
7496
|
+
function fmtUSD(n) {
|
|
7497
|
+
n = Number(n) || 0;
|
|
7498
|
+
if (n >= 1000) return '$' + n.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
|
7499
|
+
return '$' + n.toFixed(2);
|
|
7500
|
+
}
|
|
7501
|
+
|
|
7502
|
+
function fmtDuration(ms) {
|
|
7503
|
+
ms = Number(ms) || 0;
|
|
7504
|
+
if (ms >= 3.6e6) return (ms / 3.6e6).toFixed(1) + 'h';
|
|
7505
|
+
if (ms >= 60000) return (ms / 60000).toFixed(1) + 'm';
|
|
7506
|
+
if (ms >= 1000) return (ms / 1000).toFixed(1) + 's';
|
|
7507
|
+
return Math.round(ms) + 'ms';
|
|
7508
|
+
}
|
|
7509
|
+
|
|
7510
|
+
function fmtPct(p) {
|
|
7511
|
+
return (Number(p) || 0).toFixed(1) + '%';
|
|
7512
|
+
}
|
|
7513
|
+
|
|
7514
|
+
function colorPct(p, text) {
|
|
7515
|
+
if (!USE_COLOR) return text;
|
|
7516
|
+
const c = p >= 85 ? C.green : p >= 60 ? C.yellow : C.red;
|
|
7517
|
+
return c + text + C.reset;
|
|
7518
|
+
}
|
|
7519
|
+
|
|
7520
|
+
function pad(s, w, align) {
|
|
7521
|
+
s = String(s);
|
|
7522
|
+
const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
7523
|
+
const gap = Math.max(0, w - visible.length);
|
|
7524
|
+
return align === 'right' ? ' '.repeat(gap) + s : s + ' '.repeat(gap);
|
|
7525
|
+
}
|
|
7526
|
+
|
|
7527
|
+
/** Solid horizontal efficiency bar with a dotted remainder. */
|
|
7528
|
+
function bar(pct, width) {
|
|
7529
|
+
const filled = Math.round((clamp(pct, 0, 100) / 100) * width);
|
|
7530
|
+
const solid = '█'.repeat(filled);
|
|
7531
|
+
const rest = '░'.repeat(Math.max(0, width - filled));
|
|
7532
|
+
return (USE_COLOR ? C.green : '') + solid + (USE_COLOR ? C.gray : '') + rest + C.reset;
|
|
7533
|
+
}
|
|
7534
|
+
|
|
7535
|
+
/** Proportional impact bar (share of total saved). */
|
|
7536
|
+
function impactBar(sharePct, width) {
|
|
7537
|
+
const n = Math.max(1, Math.round((clamp(sharePct, 0, 100) / 100) * width));
|
|
7538
|
+
return (USE_COLOR ? C.cyan : '') + '█'.repeat(n) + C.reset;
|
|
7539
|
+
}
|
|
7540
|
+
|
|
7541
|
+
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
|
7542
|
+
const rule = (w) => C.gray + '─'.repeat(w) + C.reset;
|
|
7543
|
+
|
|
7544
|
+
// ── views ──────────────────────────────────────────────────────────────
|
|
7545
|
+
/**
|
|
7546
|
+
* Render the global summary + by-operation table.
|
|
7547
|
+
* @param {object} agg output of aggregate()
|
|
7548
|
+
* @param {object} [opts] { version, scope }
|
|
7549
|
+
* @returns {string}
|
|
7550
|
+
*/
|
|
7551
|
+
function renderSummary(agg, opts = {}) {
|
|
7552
|
+
const W = 74;
|
|
7553
|
+
const t = agg.totals;
|
|
7554
|
+
const L = [];
|
|
7555
|
+
const scope = opts.scope || 'this repo';
|
|
7556
|
+
const ver = opts.version ? `v${opts.version}` : '';
|
|
7557
|
+
|
|
7558
|
+
L.push('');
|
|
7559
|
+
const heading = `⚡ SigMap — Token Savings (${scope})`;
|
|
7560
|
+
L.push(' ' + C.bold + C.cyan + heading + C.reset +
|
|
7561
|
+
pad(`${C.green}✓${C.reset} ${C.dim}${ver}${C.reset}`, W - heading.length, 'right'));
|
|
7562
|
+
L.push(' ' + rule(W));
|
|
7563
|
+
L.push('');
|
|
7564
|
+
|
|
7565
|
+
if (t.count === 0) {
|
|
7566
|
+
L.push(` ${C.yellow}No usage recorded yet.${C.reset}`);
|
|
7567
|
+
L.push(` ${C.dim}Run a few queries (sigmap ask "...") or seed a demo:${C.reset}`);
|
|
7568
|
+
L.push(` ${C.dim} node scripts/gain.mjs --demo${C.reset}`);
|
|
7569
|
+
L.push('');
|
|
7570
|
+
return L.join('\n');
|
|
7571
|
+
}
|
|
7572
|
+
|
|
7573
|
+
const label = (k) => C.dim + pad(k, 21) + C.reset;
|
|
7574
|
+
L.push(' ' + label('Total operations') + ': ' + C.bold + fmtInt(t.count) + C.reset);
|
|
7575
|
+
L.push(' ' + label('Whole-file baseline') + ': ' + humanTokens(t.baseline) + ' tok' +
|
|
7576
|
+
` ${C.dim}← est. cost of feeding full files${C.reset}`);
|
|
7577
|
+
L.push(' ' + label('SigMap context') + ': ' + humanTokens(t.actual) + ' tok');
|
|
7578
|
+
L.push(' ' + label('Tokens saved') + ': ' + C.bold + humanTokens(t.saved) + C.reset +
|
|
7579
|
+
' (' + colorPct(t.savedPct, fmtPct(t.savedPct)) + ')');
|
|
7580
|
+
L.push(' ' + label('Est. money saved') + ': ' + C.bold + C.green + fmtUSD(t.usdSaved) + C.reset +
|
|
7581
|
+
` ${C.dim}(${agg.price.model} input @ $${agg.price.perMtok}/M · --model to change)${C.reset}`);
|
|
7582
|
+
L.push(' ' + label('Avg latency') + ': ' + fmtDuration(t.avgMs) + ' / op' +
|
|
7583
|
+
` ${C.dim}(local, no API round-trip)${C.reset}`);
|
|
7584
|
+
L.push('');
|
|
7585
|
+
L.push(' ' + label('Efficiency') + ': ▕' + bar(t.savedPct, 30) + '▏ ' +
|
|
7586
|
+
colorPct(t.savedPct, C.bold + fmtPct(t.savedPct) + C.reset));
|
|
7587
|
+
L.push('');
|
|
7588
|
+
|
|
7589
|
+
// By operation table
|
|
7590
|
+
L.push(` ${C.bold}By operation${C.reset}`);
|
|
7591
|
+
L.push(' ' + rule(W));
|
|
7592
|
+
L.push(' ' + C.dim +
|
|
7593
|
+
pad('#', 3) + pad('Operation', 24) + pad('Count', 8, 'right') +
|
|
7594
|
+
pad('Saved', 9, 'right') + pad('Avg%', 8, 'right') + pad('Time', 8, 'right') +
|
|
7595
|
+
' Impact' + C.reset);
|
|
7596
|
+
agg.byOp.forEach((o, i) => {
|
|
7597
|
+
L.push(' ' +
|
|
7598
|
+
pad(`${i + 1}.`, 3) +
|
|
7599
|
+
pad(o.op, 24) +
|
|
7600
|
+
pad(fmtInt(o.count), 8, 'right') +
|
|
7601
|
+
pad(humanTokens(o.saved), 9, 'right') +
|
|
7602
|
+
pad(colorPct(o.avgPct, fmtPct(o.avgPct)), 8, 'right') +
|
|
7603
|
+
pad(fmtDuration(o.avgMs), 8, 'right') +
|
|
7604
|
+
' ' + impactBar(o.sharePct, 12));
|
|
7605
|
+
});
|
|
7606
|
+
L.push(' ' + rule(W));
|
|
7607
|
+
L.push(` ${C.dim}saved = baseline − actual · estimated vs whole-file reads · local-only${C.reset}`);
|
|
7608
|
+
L.push('');
|
|
7609
|
+
return L.join('\n');
|
|
7610
|
+
}
|
|
7611
|
+
|
|
7612
|
+
/**
|
|
7613
|
+
* Render daily / weekly / monthly trend tables.
|
|
7614
|
+
* @param {object} agg
|
|
7615
|
+
* @returns {string}
|
|
7616
|
+
*/
|
|
7617
|
+
function renderBreakdown(agg) {
|
|
7618
|
+
const L = [];
|
|
7619
|
+
if (agg.totals.count === 0) return renderSummary(agg);
|
|
7620
|
+
|
|
7621
|
+
const section = (icon, title, rows, keyHeader) => {
|
|
7622
|
+
L.push('');
|
|
7623
|
+
L.push(` ${C.bold}${icon} ${title}${C.reset}`);
|
|
7624
|
+
L.push(' ' + C.dim +
|
|
7625
|
+
pad(keyHeader, 14) + pad('Ops', 7, 'right') + pad('Baseline', 11, 'right') +
|
|
7626
|
+
pad('Actual', 10, 'right') + pad('Saved', 10, 'right') + pad('Save%', 8, 'right') +
|
|
7627
|
+
pad('Time', 8, 'right') + C.reset);
|
|
7628
|
+
let TB = 0, TA = 0, TS = 0, TC = 0, TM = 0;
|
|
7629
|
+
for (const r of rows) {
|
|
7630
|
+
TB += r.baseline; TA += r.actual; TS += r.saved; TC += r.count; TM += r.ms;
|
|
7631
|
+
L.push(' ' +
|
|
7632
|
+
pad(r.key, 14) + pad(fmtInt(r.count), 7, 'right') +
|
|
7633
|
+
pad(humanTokens(r.baseline), 11, 'right') + pad(humanTokens(r.actual), 10, 'right') +
|
|
7634
|
+
pad(humanTokens(r.saved), 10, 'right') +
|
|
7635
|
+
pad(colorPct(r.savedPct, fmtPct(r.savedPct)), 8, 'right') +
|
|
7636
|
+
pad(fmtDuration(r.ms), 8, 'right'));
|
|
7637
|
+
}
|
|
7638
|
+
const tp = TB > 0 ? (TS / TB) * 100 : 0;
|
|
7639
|
+
L.push(' ' + C.bold +
|
|
7640
|
+
pad('TOTAL', 14) + pad(fmtInt(TC), 7, 'right') +
|
|
7641
|
+
pad(humanTokens(TB), 11, 'right') + pad(humanTokens(TA), 10, 'right') +
|
|
7642
|
+
pad(humanTokens(TS), 10, 'right') + pad(fmtPct(tp), 8, 'right') +
|
|
7643
|
+
pad(fmtDuration(TM), 8, 'right') + C.reset);
|
|
7644
|
+
};
|
|
7645
|
+
|
|
7646
|
+
const daily = agg.buckets.daily.slice(-30);
|
|
7647
|
+
section('📅', `Daily (last ${daily.length})`, daily, 'Date');
|
|
7648
|
+
const weekly = agg.buckets.weekly.slice(-6);
|
|
7649
|
+
section('📆', `Weekly (last ${weekly.length})`, weekly, 'Week of');
|
|
7650
|
+
const monthly = agg.buckets.monthly.slice(-3);
|
|
7651
|
+
section('🗓️', `Monthly (last ${monthly.length})`, monthly, 'Month');
|
|
7652
|
+
L.push('');
|
|
7653
|
+
L.push(` ${C.dim}saved = baseline − actual · estimated vs whole-file reads · local-only${C.reset}`);
|
|
7654
|
+
L.push('');
|
|
7655
|
+
return L.join('\n');
|
|
7656
|
+
}
|
|
7657
|
+
|
|
7658
|
+
module.exports = {
|
|
7659
|
+
renderSummary,
|
|
7660
|
+
renderBreakdown,
|
|
7661
|
+
// exported for tests
|
|
7662
|
+
humanTokens, fmtUSD, fmtDuration, fmtPct,
|
|
7663
|
+
};
|
|
7126
7664
|
|
|
7127
7665
|
};
|
|
7128
7666
|
|
|
@@ -10961,7 +11499,7 @@ function __tryGit(args, opts = {}) {
|
|
|
10961
11499
|
catch (_) { return ''; }
|
|
10962
11500
|
}
|
|
10963
11501
|
|
|
10964
|
-
const VERSION = '7.
|
|
11502
|
+
const VERSION = '7.2.0';
|
|
10965
11503
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
10966
11504
|
|
|
10967
11505
|
function requireSourceOrBundled(key) {
|
|
@@ -14376,7 +14914,8 @@ function main() {
|
|
|
14376
14914
|
console.log(` p50 token count : ${result.p50TokenCount}`);
|
|
14377
14915
|
console.log(` p95 token count : ${result.p95TokenCount}`);
|
|
14378
14916
|
console.log(` overbudget streak: ${result.overBudgetStreak}`);
|
|
14379
|
-
console.log(`
|
|
14917
|
+
console.log(` repo languages : ${result.extractorCoverage}% of supported langs used here (informational — not scored)`);
|
|
14918
|
+
console.log(` ${'score basis'.padEnd(16)}: staleness · token reduction · over-budget rate`);
|
|
14380
14919
|
}
|
|
14381
14920
|
process.exit(0);
|
|
14382
14921
|
}
|
package/llms-full.txt
CHANGED
|
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.2.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
package/llms.txt
CHANGED
|
@@ -9,7 +9,7 @@ the files relevant to the task — cutting tokens ~97% while keeping answers
|
|
|
9
9
|
grounded. Deterministic, offline, no embeddings or vector database. Works with
|
|
10
10
|
Claude, Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
|
|
11
11
|
|
|
12
|
-
# Version: 7.
|
|
12
|
+
# Version: 7.2.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
|
|
13
13
|
# Source: auto-generated from package.json, version.json, src/mcp/tools.js, src/config/defaults.js
|
|
14
14
|
# Regenerate: npm run generate:llms | Validate: npm run validate:llms
|
|
15
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sigmap",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "97% token reduction for AI coding. Extracts function & class signatures with TF-IDF ranking to feed only the right files to Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP. Zero dependencies, runs offline via npx.",
|
|
5
5
|
"main": "packages/core/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -31,12 +31,14 @@
|
|
|
31
31
|
"health": "node gen-context.js --health",
|
|
32
32
|
"map": "node gen-project-map.js",
|
|
33
33
|
"mcp": "node gen-context.js --mcp",
|
|
34
|
+
"check:bundle": "node scripts/check-bundle.mjs",
|
|
35
|
+
"check:version-meta": "node scripts/check-version-meta.mjs",
|
|
34
36
|
"build:binary": "node scripts/build-binary.mjs",
|
|
35
37
|
"verify:binary": "node scripts/verify-binary.mjs",
|
|
36
38
|
"version:sync": "node scripts/sync-versions.mjs",
|
|
37
39
|
"generate:llms": "node scripts/generate-llms.mjs",
|
|
38
40
|
"validate:llms": "node scripts/validate-llms.mjs",
|
|
39
|
-
"prepublishOnly": "node scripts/generate-llms.mjs"
|
|
41
|
+
"prepublishOnly": "node scripts/check-bundle.mjs && node scripts/check-version-meta.mjs && node scripts/generate-llms.mjs"
|
|
40
42
|
},
|
|
41
43
|
"files": [
|
|
42
44
|
"gen-context.js",
|
package/src/mcp/server.js
CHANGED