sigmap 7.0.0 → 7.1.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 +29 -0
- package/README.md +20 -12
- package/gen-context.js +168 -79
- package/llms-full.txt +9 -2
- package/llms.txt +7 -7
- package/package.json +3 -3
- package/packages/cli/package.json +2 -2
- package/packages/core/README.md +2 -2
- package/packages/core/package.json +2 -2
- package/src/config/loader.js +6 -6
- package/src/discovery/source-root-scorer.js +2 -2
- package/src/format/gain-terminal.js +206 -0
- package/src/format/llms-txt.js +2 -3
- package/src/mcp/handlers.js +3 -8
- package/src/mcp/server.js +1 -1
- package/src/nudge.js +1 -6
- package/src/session/notes.js +2 -8
- package/src/tracking/aggregate.js +195 -0
- package/src/tracking/logger.js +83 -1
- package/src/tracking/pricing.js +43 -0
- package/src/util/git.js +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,35 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [7.1.0] — 2026-06-16
|
|
14
|
+
|
|
15
|
+
Minor release — a token-savings dashboard in the terminal, plus domain and sponsorship docs.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Token-savings dashboard — `sigmap gain` (#260):** surfaces cumulative savings right in the terminal — total tokens saved, % efficiency, estimated $ saved, latency, and a by-operation breakdown, plus `gain --all` for daily / weekly / monthly trends. Savings are captured per operation (`ask`, `generate`) into a dedicated local log `.context/gain.ndjson` (counts only — no paths, source, or query text). Capture is **default-on** and privacy-safe; opt out via `--no-track`, `SIGMAP_NO_TRACK=1`, or `config.gainTracking:false`. The legacy `usage.ndjson` / `--track` health log is unchanged. New `src/tracking/{aggregate,pricing}.js` (zero-dep aggregation + model→$/Mtok pricing) and `src/format/gain-terminal.js` (ANSI renderer, `NO_COLOR`/non-TTY safe). Flags: `gain --all | --json | --since <7d|ISO> | --top <n> | --model <name> | --reset`. "Saved" is labeled everywhere as an estimate vs the whole-file baseline.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Docs served at the sigmap.io root (#258):** the docs site now builds with base `/` (dropped the `/sigmap/` path prefix) and the project domain points to sigmap.io.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Sponsorship transparency (#257):** README gained a Sponsor section with a tier ladder and funding goal; detail moved into `SPONSOR.md`, with an "About the maintainer" note and a low-barrier welcome.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## [7.0.1] — 2026-06-14
|
|
29
|
+
|
|
30
|
+
Patch release — supply-chain hardening and package hygiene, plus a wider star nudge.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- **Eliminated system-shell access (#252):** every `child_process.execSync` call (which runs via `/bin/sh -c`) was converted to shell-free `execFileSync` with an arguments array. Several commands had previously interpolated values into the command string (`git diff ${range}`, `HEAD~${n}`, `printf '%s' … | ${clipCmd}`, `node -e "…http.get…"`) — a real shell-injection surface. New `src/util/git.js` (`git()`/`tryGit()`) centralizes shell-free git; the `extends` config fetch passes the URL as an argv to node; `compare` spawns node by argv; clipboard copy writes via stdin. Net: zero `execSync`/`exec`/`shell:true` in the published surface, which clears Socket's "Shell access" capability alert.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- **Star nudge now counts plain `sigmap` runs (#251):** the one-time GitHub-star nudge previously only counted `ask`/`squeeze`. Users who only run `sigmap` to generate the context file now also reach the 10-run threshold. Counted once per process at the end of generation (monorepo-safe, tracked at the repo root); interactive-only — silent under `--json`/`--report`/`--quiet`/non-TTY.
|
|
37
|
+
- **`main` points at the importable core (#252):** `package.json` `main` changed from the CLI bundle (`gen-context.js`, which runs `main()` + exits on `require`) to `packages/core/index.js`, matching `exports["."]`. Bundlephobia and legacy resolvers now see the real zero-dep API.
|
|
38
|
+
- **Removed unused device fingerprint (#252):** the star nudge no longer records `machineId = sha256(os.hostname())` in `.context/usage.json` — it was never read or transmitted. Dropped the now-unused `os`/`crypto` requires.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
13
42
|
## [7.0.0] — 2026-06-14
|
|
14
43
|
|
|
15
44
|
Major release — **Squeeze** makes `ask` minimize pasted input by default, a behavioral change to the core command.
|
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ Repos tested : 21 (JavaScript, Python, Go, Rust, Java, R, C++, C#, Dart, Swift
|
|
|
100
100
|
Measured on 90 coding tasks across 18 real public repos. No LLM API — fully reproducible.
|
|
101
101
|
|
|
102
102
|
**Resources:**
|
|
103
|
-
- [Full methodology →](https://
|
|
103
|
+
- [Full methodology →](https://sigmap.io/guide/benchmark.html)
|
|
104
104
|
- [Benchmark suite (GitHub)](https://github.com/manojmallick/sigmap-benchmark-suite) — scripts, tasks, and raw data
|
|
105
105
|
- [Benchmark data (Zenodo)](https://zenodo.org/records/19898842) — archived results for reproducibility
|
|
106
106
|
|
|
@@ -172,8 +172,8 @@ sigmap --adapter claude # works with Claude Code
|
|
|
172
172
|
**Open-source agents & local LLMs:**
|
|
173
173
|
|
|
174
174
|
Use SigMap with open-source tools and fully self-hosted setups:
|
|
175
|
-
- **[Open-source agents guide →](https://
|
|
176
|
-
- **[Local LLMs guide →](https://
|
|
175
|
+
- **[Open-source agents guide →](https://sigmap.io/guide/agents)** — OpenCode, Aider, OpenHands, Cline
|
|
176
|
+
- **[Local LLMs guide →](https://sigmap.io/guide/local-llms)** — Ollama, llama.cpp, vLLM (no API keys, full privacy)
|
|
177
177
|
|
|
178
178
|
**IDE extensions:**
|
|
179
179
|
|
|
@@ -219,21 +219,21 @@ sigmap --health
|
|
|
219
219
|
| 👶 **New** | [Quick start guide](docs/readmes/GETTING_STARTED.md) — setup in 60 seconds |
|
|
220
220
|
| ⚡ **Daily** | `sigmap ask` / `sigmap validate` / `sigmap judge` |
|
|
221
221
|
| 🧠 **Advanced** | [Context strategies](docs/readmes/CONTEXT_STRATEGIES.md) · [MCP setup](docs/readmes/MCP_SETUP.md) |
|
|
222
|
-
| 🏢 **Teams** | [Config reference](https://
|
|
222
|
+
| 🏢 **Teams** | [Config reference](https://sigmap.io/guide/config.html) · [CI setup](docs/readmes/ENTERPRISE_SETUP.md) |
|
|
223
223
|
|
|
224
224
|
---
|
|
225
225
|
|
|
226
226
|
## Docs
|
|
227
227
|
|
|
228
|
-
**[
|
|
228
|
+
**[sigmap.io](https://sigmap.io)**
|
|
229
229
|
|
|
230
230
|
| Section | Link |
|
|
231
231
|
|---|---|
|
|
232
|
-
| CLI reference (32 commands) | [cli.html](https://
|
|
233
|
-
| Benchmark methodology | [benchmark.html](https://
|
|
234
|
-
| Config reference | [config.html](https://
|
|
235
|
-
| Roadmap | [roadmap.html](https://
|
|
236
|
-
| 31 languages | [generalization.html](https://
|
|
232
|
+
| CLI reference (32 commands) | [cli.html](https://sigmap.io/guide/cli.html) |
|
|
233
|
+
| Benchmark methodology | [benchmark.html](https://sigmap.io/guide/benchmark.html) |
|
|
234
|
+
| Config reference | [config.html](https://sigmap.io/guide/config.html) |
|
|
235
|
+
| Roadmap | [roadmap.html](https://sigmap.io/guide/roadmap.html) |
|
|
236
|
+
| 31 languages | [generalization.html](https://sigmap.io/guide/generalization.html) |
|
|
237
237
|
|
|
238
238
|
---
|
|
239
239
|
|
|
@@ -245,6 +245,14 @@ If SigMap saves you context or API spend, a ⭐ on [GitHub](https://github.com/m
|
|
|
245
245
|
|
|
246
246
|
---
|
|
247
247
|
|
|
248
|
+
## Sponsor
|
|
249
|
+
|
|
250
|
+
SigMap is built and maintained by one developer, kept **zero-dependency**, offline, and free. If it saves your team context or API spend, sponsoring keeps it that way — and funds the benchmark CI, the `sigmap.io` domain, and ongoing supply-chain hardening.
|
|
251
|
+
|
|
252
|
+
💜 **[Become a sponsor →](https://github.com/sponsors/manojmallick)** · see **[SPONSOR.md](SPONSOR.md)** for tiers and exactly where your support goes. Any amount helps — even $1/mo — and a ⭐ or a share counts too.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
248
256
|
## Contributing
|
|
249
257
|
|
|
250
258
|
SigMap welcomes contributions!
|
|
@@ -284,7 +292,7 @@ TypeScript · JavaScript · Python · Java · Kotlin · Go · Rust · C# · C/C+
|
|
|
284
292
|
|
|
285
293
|
All implemented with zero external dependencies.
|
|
286
294
|
|
|
287
|
-
[Full language table →](https://
|
|
295
|
+
[Full language table →](https://sigmap.io/guide/generalization.html)
|
|
288
296
|
|
|
289
297
|
---
|
|
290
298
|
|
|
@@ -296,7 +304,7 @@ MIT © 2026 [Manoj Mallick](https://github.com/manojmallick) · Made in Amsterda
|
|
|
296
304
|
|
|
297
305
|
<div align="center">
|
|
298
306
|
|
|
299
|
-
**[Docs](https://
|
|
307
|
+
**[Docs](https://sigmap.io) · [Changelog](CHANGELOG.md) · [Roadmap](https://sigmap.io/roadmap.html) · [npm](https://www.npmjs.com/package/sigmap)**
|
|
300
308
|
|
|
301
309
|
⭐ [Star on GitHub](https://github.com/manojmallick/sigmap) if SigMap saves you tokens.
|
|
302
310
|
|
package/gen-context.js
CHANGED
|
@@ -236,12 +236,11 @@ __factories["./src/config/loader"] = function(module, exports) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
try {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
);
|
|
239
|
+
// Shell-free: run the node binary directly with the URL passed as an argv
|
|
240
|
+
// value (never interpolated into a command string, never via /bin/sh).
|
|
241
|
+
const { execFileSync } = require('child_process');
|
|
242
|
+
const SYNC_FETCH = "const u=process.argv[1];const h=require(u.startsWith('https')?'https':'http');let d='';h.get(u,r=>{r.on('data',c=>d+=c);r.on('end',()=>process.stdout.write(d))}).on('error',()=>process.exit(1))";
|
|
243
|
+
const out = execFileSync(process.execPath, ['-e', SYNC_FETCH, extendsVal], { timeout: 10000, encoding: 'utf8' });
|
|
245
244
|
const parsed = JSON.parse(out);
|
|
246
245
|
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
247
246
|
fs.writeFileSync(cachePath, JSON.stringify(parsed), 'utf8');
|
|
@@ -5533,7 +5532,6 @@ __factories["./src/mcp/handlers"] = function(module, exports) {
|
|
|
5533
5532
|
|
|
5534
5533
|
const fs = require('fs');
|
|
5535
5534
|
const path = require('path');
|
|
5536
|
-
const { execSync } = require('child_process');
|
|
5537
5535
|
|
|
5538
5536
|
const CONTEXT_FILE = path.join('.github', 'copilot-instructions.md');
|
|
5539
5537
|
const CONTEXT_COLD_FILE = path.join('.github', 'context-cold.md');
|
|
@@ -5680,19 +5678,14 @@ function createCheckpoint(args, cwd) {
|
|
|
5680
5678
|
// ── Git info ────────────────────────────────────────────────────────────
|
|
5681
5679
|
lines.push('## Git state');
|
|
5682
5680
|
try {
|
|
5683
|
-
const branch =
|
|
5684
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
5685
|
-
}).trim();
|
|
5681
|
+
const branch = __git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }).trim();
|
|
5686
5682
|
lines.push(`**Branch:** ${branch}`);
|
|
5687
5683
|
} catch (_) {
|
|
5688
5684
|
lines.push('**Branch:** (not a git repo)');
|
|
5689
5685
|
}
|
|
5690
5686
|
|
|
5691
5687
|
try {
|
|
5692
|
-
const log =
|
|
5693
|
-
'git log --oneline -5 --no-decorate 2>/dev/null',
|
|
5694
|
-
{ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
5695
|
-
).trim();
|
|
5688
|
+
const log = __git(['log', '--oneline', '-5', '--no-decorate'], { cwd }).trim();
|
|
5696
5689
|
if (log) {
|
|
5697
5690
|
lines.push('');
|
|
5698
5691
|
lines.push('**Recent commits:**');
|
|
@@ -6261,7 +6254,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
|
|
|
6261
6254
|
|
|
6262
6255
|
const SERVER_INFO = {
|
|
6263
6256
|
name: 'sigmap',
|
|
6264
|
-
version: '7.
|
|
6257
|
+
version: '7.1.0',
|
|
6265
6258
|
description: 'SigMap MCP server — code signatures on demand',
|
|
6266
6259
|
};
|
|
6267
6260
|
|
|
@@ -7152,8 +7145,6 @@ __factories["./src/session/notes"] = function(module, exports) {
|
|
|
7152
7145
|
|
|
7153
7146
|
const fs = require('fs');
|
|
7154
7147
|
const path = require('path');
|
|
7155
|
-
const { execSync } = require('child_process');
|
|
7156
|
-
|
|
7157
7148
|
const NOTES_FILE = path.join('.context', 'notes.ndjson');
|
|
7158
7149
|
const MAX_TEXT = 2000;
|
|
7159
7150
|
|
|
@@ -7162,13 +7153,7 @@ function notesPath(cwd) {
|
|
|
7162
7153
|
}
|
|
7163
7154
|
|
|
7164
7155
|
function _currentBranch(cwd) {
|
|
7165
|
-
|
|
7166
|
-
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
7167
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
7168
|
-
}).trim() || null;
|
|
7169
|
-
} catch (_) {
|
|
7170
|
-
return null;
|
|
7171
|
-
}
|
|
7156
|
+
return __tryGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }) || null;
|
|
7172
7157
|
}
|
|
7173
7158
|
|
|
7174
7159
|
/**
|
|
@@ -8703,10 +8688,9 @@ __factories["./src/format/llms-txt"] = function(module, exports) {
|
|
|
8703
8688
|
'use strict';
|
|
8704
8689
|
const path = require('path');
|
|
8705
8690
|
const fs = require('fs');
|
|
8706
|
-
const { execSync } = require('child_process');
|
|
8707
8691
|
function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
|
|
8708
8692
|
function getShortCommit(cwd) {
|
|
8709
|
-
try { return
|
|
8693
|
+
try { return __tryGit(['rev-parse', '--short', 'HEAD'], { cwd, timeout: 2000 }); }
|
|
8710
8694
|
catch (_) { return ''; }
|
|
8711
8695
|
}
|
|
8712
8696
|
function detectVersion(cwd) {
|
|
@@ -8944,6 +8928,20 @@ __factories["./src/discovery/source-root-registry"] = function(module, exports)
|
|
|
8944
8928
|
module.exports = { REGISTRY };
|
|
8945
8929
|
};
|
|
8946
8930
|
|
|
8931
|
+
// ── ./src/util/git ──
|
|
8932
|
+
__factories["./src/util/git"] = function(module, exports) {
|
|
8933
|
+
'use strict';
|
|
8934
|
+
const { execFileSync } = require('child_process');
|
|
8935
|
+
function git(args, opts = {}) {
|
|
8936
|
+
return execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], ...opts });
|
|
8937
|
+
}
|
|
8938
|
+
function tryGit(args, opts = {}) {
|
|
8939
|
+
try { return git(args, opts).toString().trim(); }
|
|
8940
|
+
catch (_) { return ''; }
|
|
8941
|
+
}
|
|
8942
|
+
module.exports = { git, tryGit };
|
|
8943
|
+
};
|
|
8944
|
+
|
|
8947
8945
|
// ── ./src/discovery/sigmapignore ──
|
|
8948
8946
|
__factories["./src/discovery/sigmapignore"] = function(module, exports) {
|
|
8949
8947
|
'use strict';
|
|
@@ -9031,14 +9029,13 @@ __factories["./src/discovery/source-root-scorer"] = function(module, exports) {
|
|
|
9031
9029
|
'use strict';
|
|
9032
9030
|
const fs = require('fs');
|
|
9033
9031
|
const path = require('path');
|
|
9034
|
-
const { execSync } = require('child_process');
|
|
9035
9032
|
const CODE_EXTS = new Set(['.js','.mjs','.cjs','.ts','.tsx','.jsx','.py','.rb','.go','.rs','.java','.kt','.cs','.cpp','.c','.h','.swift','.dart','.scala','.php']);
|
|
9036
9033
|
const AUTO_SKIP = new Set(['node_modules','dist','build','.git','.next','.nuxt','vendor','DerivedData','Pods','target','coverage','__pycache__','.venv','venv','.build','Carthage','storybook-static','.gradle','bin','obj','.vs']);
|
|
9037
9034
|
const PENALTY_DIRS = new Set(['test','tests','spec','__tests__','e2e','docs','doc','docs-vp','examples','example','fixtures','mocks','__mocks__','demo','samples','migrations','benchmarks','scripts']);
|
|
9038
9035
|
const ROOT_ENTRYPOINTS = { go: ['main.go'], python: ['app.py','main.py','wsgi.py','asgi.py'], javascript: ['index.js','server.js','app.js'], typescript: ['index.ts','main.ts'], rust: [], php: ['index.php'] };
|
|
9039
9036
|
function getRecentlyChangedDirs(cwd) {
|
|
9040
9037
|
try {
|
|
9041
|
-
const out =
|
|
9038
|
+
const out = __git(['log', '--name-only', '--format=', 'HEAD~10'], { cwd, timeout: 3000 }).toString();
|
|
9042
9039
|
return new Set(out.split('\n').filter(Boolean).map(f => f.split('/')[0]));
|
|
9043
9040
|
} catch { return new Set(); }
|
|
9044
9041
|
}
|
|
@@ -10027,8 +10024,6 @@ __factories["./src/nudge"] = function(module, exports) {
|
|
|
10027
10024
|
|
|
10028
10025
|
const fs = require('fs');
|
|
10029
10026
|
const path = require('path');
|
|
10030
|
-
const os = require('os');
|
|
10031
|
-
const crypto = require('crypto');
|
|
10032
10027
|
|
|
10033
10028
|
const RUN_THRESHOLD = 10;
|
|
10034
10029
|
const SUCCESS_THRESHOLD = 8;
|
|
@@ -10038,7 +10033,7 @@ function usagePath(cwd) { return path.join(cwd, '.context', 'usage.json'); }
|
|
|
10038
10033
|
function defaultUsage() {
|
|
10039
10034
|
return {
|
|
10040
10035
|
totalRuns: 0, successfulRuns: 0, squeezeOffered: 0, squeezeAccepted: 0,
|
|
10041
|
-
starNudgeShown: false,
|
|
10036
|
+
starNudgeShown: false, firstRunDate: null, lastRunDate: null,
|
|
10042
10037
|
};
|
|
10043
10038
|
}
|
|
10044
10039
|
|
|
@@ -10091,9 +10086,6 @@ function checkStarNudge(cwd, runSuccess, opts = {}) {
|
|
|
10091
10086
|
const today = opts.today || new Date().toISOString().slice(0, 10);
|
|
10092
10087
|
if (!usage.firstRunDate) usage.firstRunDate = today;
|
|
10093
10088
|
usage.lastRunDate = today;
|
|
10094
|
-
if (!usage.machineId) {
|
|
10095
|
-
try { usage.machineId = 'sha256-' + crypto.createHash('sha256').update(os.hostname()).digest('hex').slice(0, 16); } catch (_) {}
|
|
10096
|
-
}
|
|
10097
10089
|
|
|
10098
10090
|
let nudged = false;
|
|
10099
10091
|
if (!usage.starNudgeShown && usage.totalRuns >= RUN_THRESHOLD && usage.successfulRuns >= SUCCESS_THRESHOLD) {
|
|
@@ -10956,9 +10948,20 @@ module.exports = { verify, buildSymbolSet, loadDeps, loadScripts, isTestPath };
|
|
|
10956
10948
|
const fs = require('fs');
|
|
10957
10949
|
const path = require('path');
|
|
10958
10950
|
const os = require('os');
|
|
10959
|
-
const { execSync } = require('child_process');
|
|
10960
10951
|
|
|
10961
|
-
|
|
10952
|
+
// Shell-free subprocess helpers. execFileSync runs the binary directly (never
|
|
10953
|
+
// /bin/sh), so there is no shell-injection surface and no "Shell access"
|
|
10954
|
+
// capability for supply-chain scanners. stderr is discarded by default.
|
|
10955
|
+
function __git(args, opts = {}) {
|
|
10956
|
+
const { execFileSync } = require('child_process');
|
|
10957
|
+
return execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], ...opts });
|
|
10958
|
+
}
|
|
10959
|
+
function __tryGit(args, opts = {}) {
|
|
10960
|
+
try { return __git(args, opts).toString().trim(); }
|
|
10961
|
+
catch (_) { return ''; }
|
|
10962
|
+
}
|
|
10963
|
+
|
|
10964
|
+
const VERSION = '7.1.0';
|
|
10962
10965
|
const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
|
|
10963
10966
|
|
|
10964
10967
|
function requireSourceOrBundled(key) {
|
|
@@ -11340,9 +11343,7 @@ function applyTokenBudget(fileEntries, maxTokens) {
|
|
|
11340
11343
|
function getRecentlyCommittedFiles(cwd, count) {
|
|
11341
11344
|
const n = count || 10;
|
|
11342
11345
|
try {
|
|
11343
|
-
const out =
|
|
11344
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
11345
|
-
});
|
|
11346
|
+
const out = __git(['log', '--name-only', '--format=', '-n', String(n)], { cwd });
|
|
11346
11347
|
return new Set(out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
|
|
11347
11348
|
} catch (_) {
|
|
11348
11349
|
return new Set();
|
|
@@ -11354,8 +11355,8 @@ function getRecentlyCommittedFiles(cwd, count) {
|
|
|
11354
11355
|
// ---------------------------------------------------------------------------
|
|
11355
11356
|
function getDiffFiles(cwd, stagedOnly) {
|
|
11356
11357
|
try {
|
|
11357
|
-
const
|
|
11358
|
-
const out =
|
|
11358
|
+
const gitArgs = stagedOnly ? ['diff', '--cached', '--name-only'] : ['diff', 'HEAD', '--name-only'];
|
|
11359
|
+
const out = __git(gitArgs, { cwd });
|
|
11359
11360
|
return new Set(
|
|
11360
11361
|
out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f))
|
|
11361
11362
|
);
|
|
@@ -11453,17 +11454,13 @@ function buildChangesSection(cwd, config, fileEntries) {
|
|
|
11453
11454
|
try {
|
|
11454
11455
|
let range = `HEAD~${n}..HEAD`;
|
|
11455
11456
|
try {
|
|
11456
|
-
|
|
11457
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
11458
|
-
});
|
|
11457
|
+
__git(['rev-parse', '--verify', `HEAD~${n}`], { cwd });
|
|
11459
11458
|
} catch (_) {
|
|
11460
11459
|
// HEAD~n doesn't exist (shallow repo) — clamp to deepest valid ancestor
|
|
11461
11460
|
let best = 1;
|
|
11462
11461
|
for (let k = n - 1; k >= 2; k--) {
|
|
11463
11462
|
try {
|
|
11464
|
-
|
|
11465
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
11466
|
-
});
|
|
11463
|
+
__git(['rev-parse', '--verify', `HEAD~${k}`], { cwd });
|
|
11467
11464
|
best = k;
|
|
11468
11465
|
break;
|
|
11469
11466
|
} catch (_) {}
|
|
@@ -11471,15 +11468,11 @@ function buildChangesSection(cwd, config, fileEntries) {
|
|
|
11471
11468
|
range = `HEAD~${best}..HEAD`;
|
|
11472
11469
|
}
|
|
11473
11470
|
|
|
11474
|
-
const namesOnly =
|
|
11475
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
11476
|
-
});
|
|
11471
|
+
const namesOnly = __git(['diff', range, '--name-only'], { cwd });
|
|
11477
11472
|
const changed = new Set(namesOnly.split('\n').map((l) => l.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
|
|
11478
11473
|
if (changed.size === 0) return [];
|
|
11479
11474
|
|
|
11480
|
-
const timeAgo =
|
|
11481
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
11482
|
-
}).trim();
|
|
11475
|
+
const timeAgo = __git(['log', '-1', '--format=%cr'], { cwd }).trim();
|
|
11483
11476
|
|
|
11484
11477
|
const lines = [`## changes (last ${n} commits — ${timeAgo})`, '```'];
|
|
11485
11478
|
let hasDelta = false;
|
|
@@ -11488,9 +11481,7 @@ function buildChangesSection(cwd, config, fileEntries) {
|
|
|
11488
11481
|
const rel = path.relative(cwd, entry.filePath);
|
|
11489
11482
|
let diff = '';
|
|
11490
11483
|
try {
|
|
11491
|
-
diff =
|
|
11492
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
11493
|
-
});
|
|
11484
|
+
diff = __git(['diff', range, '--', rel], { cwd });
|
|
11494
11485
|
} catch (_) {
|
|
11495
11486
|
continue;
|
|
11496
11487
|
}
|
|
@@ -12203,6 +12194,7 @@ function runDiff(cwd, config, stagedOnly, baseRef) {
|
|
|
12203
12194
|
// Core generate pipeline
|
|
12204
12195
|
// ---------------------------------------------------------------------------
|
|
12205
12196
|
function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
12197
|
+
const __genT0 = Date.now();
|
|
12206
12198
|
const ignorePatterns = loadIgnorePatterns(cwd);
|
|
12207
12199
|
let allFiles = buildFileList(cwd, config);
|
|
12208
12200
|
|
|
@@ -12402,7 +12394,7 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
12402
12394
|
printReport(result.inputTokenTotal, result.finalTokens, result.fileCount, result.droppedCount, reportJson, effectiveMaxTokens, result.coverageResult, config.autoMaxTokens !== false && effectiveMaxTokens !== config.maxTokens, config.maxTokens);
|
|
12403
12395
|
}
|
|
12404
12396
|
|
|
12405
|
-
// Usage tracking (v0.9) —
|
|
12397
|
+
// Usage tracking (v0.9) — legacy health log, opt-in via config.tracking / --track.
|
|
12406
12398
|
const trackingEnabled = !!(config.tracking || process.argv.includes('--track'));
|
|
12407
12399
|
if (trackingEnabled && !reportMode) {
|
|
12408
12400
|
try {
|
|
@@ -12422,6 +12414,22 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
12422
12414
|
}
|
|
12423
12415
|
}
|
|
12424
12416
|
|
|
12417
|
+
// gain: capture generate savings (separate gain.ndjson; default ON, opt-out).
|
|
12418
|
+
if (!reportMode) {
|
|
12419
|
+
try {
|
|
12420
|
+
const { recordUsage, isTrackingEnabled } = requireSourceOrBundled('./src/tracking/logger');
|
|
12421
|
+
if (isTrackingEnabled(config, process.argv)) {
|
|
12422
|
+
recordUsage({
|
|
12423
|
+
version: VERSION,
|
|
12424
|
+
op: 'generate',
|
|
12425
|
+
baselineTokens: result.inputTokenTotal,
|
|
12426
|
+
actualTokens: result.finalTokens,
|
|
12427
|
+
durationMs: Date.now() - __genT0,
|
|
12428
|
+
}, cwd);
|
|
12429
|
+
}
|
|
12430
|
+
} catch (_) { /* gain capture is best-effort */ }
|
|
12431
|
+
}
|
|
12432
|
+
|
|
12425
12433
|
// Feature 8: post-run summary — 6-line stdout after every normal run
|
|
12426
12434
|
if (!reportMode
|
|
12427
12435
|
&& !process.argv.includes('--json')
|
|
@@ -12470,6 +12478,23 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
|
|
|
12470
12478
|
}
|
|
12471
12479
|
}
|
|
12472
12480
|
|
|
12481
|
+
// v7.0.1: count a plain context-generation run for the one-time star nudge.
|
|
12482
|
+
// Most users only ever run `sigmap` to build the context file — count that too,
|
|
12483
|
+
// not just ask/squeeze. Guard so a monorepo (many runGenerate calls per process)
|
|
12484
|
+
// counts as a single run. Interactive only — silent under --json/--report or non-TTY.
|
|
12485
|
+
if (!global.__sigmapGenCounted) {
|
|
12486
|
+
global.__sigmapGenCounted = true;
|
|
12487
|
+
try {
|
|
12488
|
+
const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
|
|
12489
|
+
const showNudge = !!process.stderr.isTTY
|
|
12490
|
+
&& !reportJson
|
|
12491
|
+
&& !process.argv.includes('--json')
|
|
12492
|
+
&& !process.argv.includes('--report')
|
|
12493
|
+
&& !process.argv.includes('--quiet');
|
|
12494
|
+
checkStarNudge(global.__sigmapRootCwd || cwd, true, { silent: !showNudge });
|
|
12495
|
+
} catch (_) {}
|
|
12496
|
+
}
|
|
12497
|
+
|
|
12473
12498
|
return result;
|
|
12474
12499
|
}
|
|
12475
12500
|
|
|
@@ -12651,11 +12676,7 @@ function suggestTool(description) {
|
|
|
12651
12676
|
|
|
12652
12677
|
function resolveProjectRoot(startDir) {
|
|
12653
12678
|
try {
|
|
12654
|
-
const gitRoot =
|
|
12655
|
-
cwd: startDir,
|
|
12656
|
-
encoding: 'utf8',
|
|
12657
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
12658
|
-
}).trim();
|
|
12679
|
+
const gitRoot = __git(['rev-parse', '--show-toplevel'], { cwd: startDir }).trim();
|
|
12659
12680
|
if (gitRoot) return gitRoot;
|
|
12660
12681
|
} catch (_) {}
|
|
12661
12682
|
return startDir;
|
|
@@ -12705,6 +12726,13 @@ Usage:
|
|
|
12705
12726
|
${cmd} --suggest-tool "<task>" --json Machine-readable tier recommendation
|
|
12706
12727
|
${cmd} --health Print composite health score
|
|
12707
12728
|
${cmd} --health --json Machine-readable health score
|
|
12729
|
+
${cmd} gain Token-savings dashboard (totals + by-operation)
|
|
12730
|
+
${cmd} gain --all Add daily / weekly / monthly trend tables
|
|
12731
|
+
${cmd} gain --json Aggregate savings as JSON
|
|
12732
|
+
${cmd} gain --since 7d Window filter (7d, 30d, 12h, or ISO date)
|
|
12733
|
+
${cmd} gain --top <n> | --model <name> Limit rows / set $ pricing model
|
|
12734
|
+
${cmd} gain --reset Clear the local savings log (.context/gain.ndjson)
|
|
12735
|
+
${cmd} ... --no-track Disable gain savings capture for this run
|
|
12708
12736
|
${cmd} --diff Generate context for git-changed files only
|
|
12709
12737
|
${cmd} --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
|
|
12710
12738
|
${cmd} --diff --staged Generate context for staged files only
|
|
@@ -12926,8 +12954,7 @@ function buildIndexContext(ranked, cwd) {
|
|
|
12926
12954
|
|
|
12927
12955
|
function computeCurrentRisk(cwd) {
|
|
12928
12956
|
try {
|
|
12929
|
-
const
|
|
12930
|
-
const out = execSync('git diff --name-only HEAD', { cwd, timeout: 3000, encoding: 'utf8' });
|
|
12957
|
+
const out = __git(['diff', '--name-only', 'HEAD'], { cwd, timeout: 3000 });
|
|
12931
12958
|
const count = out.trim().split('\n').filter(Boolean).length;
|
|
12932
12959
|
if (count === 0) return 'NONE';
|
|
12933
12960
|
if (count <= 3) return 'LOW';
|
|
@@ -13011,6 +13038,9 @@ function main() {
|
|
|
13011
13038
|
const cwd = cwdFlag
|
|
13012
13039
|
? path.resolve(invokedFrom, cwdFlag)
|
|
13013
13040
|
: resolveProjectRoot(invokedFrom);
|
|
13041
|
+
// v7.0.1: remember the invocation root so the generation star-nudge always tracks
|
|
13042
|
+
// usage at the repo root, even when runGenerate is called per-package (monorepo).
|
|
13043
|
+
global.__sigmapRootCwd = cwd;
|
|
13014
13044
|
const scriptPath = process.argv[1] || path.join(invokedFrom, 'gen-context.js');
|
|
13015
13045
|
|
|
13016
13046
|
if (cwdFlag) {
|
|
@@ -13102,6 +13132,7 @@ function main() {
|
|
|
13102
13132
|
|
|
13103
13133
|
// v4.2: `sigmap ask "<query>"` — unified pipeline
|
|
13104
13134
|
if (args[0] === 'ask') {
|
|
13135
|
+
const __askT0 = Date.now();
|
|
13105
13136
|
// v6.8: Handle --followup flag which may appear before or after query
|
|
13106
13137
|
let query = args[1];
|
|
13107
13138
|
if (query === '--followup' && args[2]) {
|
|
@@ -13269,6 +13300,21 @@ function main() {
|
|
|
13269
13300
|
bar,
|
|
13270
13301
|
].join('\n'));
|
|
13271
13302
|
}
|
|
13303
|
+
// gain: capture this query's savings for the `sigmap gain` dashboard.
|
|
13304
|
+
try {
|
|
13305
|
+
const { recordUsage, isTrackingEnabled } = requireSourceOrBundled('./src/tracking/logger');
|
|
13306
|
+
if (isTrackingEnabled(config, args)) {
|
|
13307
|
+
recordUsage({
|
|
13308
|
+
version: VERSION,
|
|
13309
|
+
op: 'ask',
|
|
13310
|
+
baselineTokens: rawTok,
|
|
13311
|
+
actualTokens: ctxTok,
|
|
13312
|
+
durationMs: Date.now() - __askT0,
|
|
13313
|
+
model,
|
|
13314
|
+
}, cwd);
|
|
13315
|
+
}
|
|
13316
|
+
} catch (_) { /* gain capture is best-effort */ }
|
|
13317
|
+
|
|
13272
13318
|
// v7.0.0: record the run and show the one-time star nudge (interactive only).
|
|
13273
13319
|
try {
|
|
13274
13320
|
const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
|
|
@@ -13281,14 +13327,51 @@ function main() {
|
|
|
13281
13327
|
process.exit(0);
|
|
13282
13328
|
}
|
|
13283
13329
|
|
|
13330
|
+
// `sigmap gain` — token-savings dashboard (totals, by-operation, trends).
|
|
13331
|
+
if (args[0] === 'gain') {
|
|
13332
|
+
const valOf = (f, d) => { const i = args.indexOf(f); return i >= 0 && args[i + 1] ? args[i + 1] : d; };
|
|
13333
|
+
if (args.includes('--reset')) {
|
|
13334
|
+
try {
|
|
13335
|
+
const { GAIN_FILE } = requireSourceOrBundled('./src/tracking/logger');
|
|
13336
|
+
const p = path.join(cwd, GAIN_FILE);
|
|
13337
|
+
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
13338
|
+
console.log('[sigmap] gain: usage log cleared.');
|
|
13339
|
+
} catch (e) { console.error(`[sigmap] gain: ${e.message}`); }
|
|
13340
|
+
process.exit(0);
|
|
13341
|
+
}
|
|
13342
|
+
try {
|
|
13343
|
+
const { readGainLog } = requireSourceOrBundled('./src/tracking/logger');
|
|
13344
|
+
const { aggregate } = requireSourceOrBundled('./src/tracking/aggregate');
|
|
13345
|
+
const { renderSummary, renderBreakdown } = requireSourceOrBundled('./src/format/gain-terminal');
|
|
13346
|
+
const records = readGainLog(cwd);
|
|
13347
|
+
const agg = aggregate(records, {
|
|
13348
|
+
model: valOf('--model', 'claude-sonnet'),
|
|
13349
|
+
since: valOf('--since', null),
|
|
13350
|
+
top: parseInt(valOf('--top', args.includes('--all') ? '0' : '10'), 10),
|
|
13351
|
+
});
|
|
13352
|
+
if (args.includes('--json')) {
|
|
13353
|
+
process.stdout.write(JSON.stringify(agg, null, 2) + '\n');
|
|
13354
|
+
} else {
|
|
13355
|
+
const out = args.includes('--all')
|
|
13356
|
+
? renderSummary(agg, { version: VERSION }) + '\n' + renderBreakdown(agg)
|
|
13357
|
+
: renderSummary(agg, { version: VERSION });
|
|
13358
|
+
process.stdout.write(out + '\n');
|
|
13359
|
+
}
|
|
13360
|
+
} catch (e) {
|
|
13361
|
+
console.error(`[sigmap] gain: ${e.message}`);
|
|
13362
|
+
console.error(' (gain modules require src/ — for single-file installs, regenerate the bundle)');
|
|
13363
|
+
process.exit(1);
|
|
13364
|
+
}
|
|
13365
|
+
process.exit(0);
|
|
13366
|
+
}
|
|
13367
|
+
|
|
13284
13368
|
// v4.2: `sigmap suggest-profile` — auto-detect task type from git state
|
|
13285
13369
|
if (args[0] === 'suggest-profile') {
|
|
13286
13370
|
const short = args.includes('--short');
|
|
13287
13371
|
let msg = '', diff = '';
|
|
13288
13372
|
try {
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
diff = execSync('git diff --cached --name-only', { cwd, timeout: 3000, encoding: 'utf8' });
|
|
13373
|
+
msg = __git(['log', '-1', '--format=%s'], { cwd, timeout: 3000 }).trim();
|
|
13374
|
+
diff = __git(['diff', '--cached', '--name-only'], { cwd, timeout: 3000 });
|
|
13292
13375
|
} catch (_) {}
|
|
13293
13376
|
|
|
13294
13377
|
let profile = 'default';
|
|
@@ -13309,13 +13392,15 @@ function main() {
|
|
|
13309
13392
|
|
|
13310
13393
|
// v4.2: `sigmap compare` — human-readable benchmark CLI
|
|
13311
13394
|
if (args[0] === 'compare') {
|
|
13312
|
-
const {
|
|
13395
|
+
const { execFileSync } = require('child_process');
|
|
13313
13396
|
console.log('[sigmap] Running comparison benchmark (this may take ~30s)...\n');
|
|
13314
13397
|
|
|
13315
13398
|
let raw = '';
|
|
13316
13399
|
try {
|
|
13317
|
-
|
|
13318
|
-
|
|
13400
|
+
// Shell-free: run the node binary directly with the script path as an argv.
|
|
13401
|
+
raw = execFileSync(
|
|
13402
|
+
process.execPath,
|
|
13403
|
+
[path.join(__dirname, 'scripts', 'run-retrieval-benchmark.mjs'), '--compare'],
|
|
13319
13404
|
{ cwd, timeout: 90_000, encoding: 'utf8' }
|
|
13320
13405
|
);
|
|
13321
13406
|
} catch (e) { raw = (e && e.stdout) ? e.stdout : ''; }
|
|
@@ -13366,15 +13451,19 @@ function main() {
|
|
|
13366
13451
|
const shareText = [
|
|
13367
13452
|
'Generated with SigMap — zero-dependency AI context engine',
|
|
13368
13453
|
`${reduction}% fewer tokens · ${hitAt5}% retrieval accuracy · 6× better results`,
|
|
13369
|
-
'https://sigmap.
|
|
13454
|
+
'https://sigmap.io',
|
|
13370
13455
|
].join('\n');
|
|
13371
13456
|
|
|
13372
13457
|
console.log(shareText);
|
|
13373
13458
|
|
|
13374
13459
|
try {
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13460
|
+
// Shell-free: spawn the clipboard binary directly and write the text to its
|
|
13461
|
+
// stdin — no shell, no pipe, no string-built command.
|
|
13462
|
+
const { execFileSync } = require('child_process');
|
|
13463
|
+
const [clipBin, clipArgs] = process.platform === 'darwin'
|
|
13464
|
+
? ['pbcopy', []]
|
|
13465
|
+
: ['xclip', ['-selection', 'clipboard']];
|
|
13466
|
+
execFileSync(clipBin, clipArgs, { input: shareText, timeout: 2000 });
|
|
13378
13467
|
console.log('\n[sigmap] Copied to clipboard.');
|
|
13379
13468
|
} catch (_) {}
|
|
13380
13469
|
|
|
@@ -13955,16 +14044,16 @@ function main() {
|
|
|
13955
14044
|
// `sigmap status` — environment / repo state at a glance.
|
|
13956
14045
|
if (args[0] === 'status') {
|
|
13957
14046
|
const jsonOut = args.includes('--json');
|
|
13958
|
-
const gitOpts = { cwd
|
|
14047
|
+
const gitOpts = { cwd };
|
|
13959
14048
|
const st = { branch: null, dirty: 0, lastIndex: null, indexVersion: null, indexFiles: null, changedSinceIndex: null, notes: 0, lastNote: null };
|
|
13960
14049
|
|
|
13961
|
-
|
|
14050
|
+
st.branch = __tryGit(['rev-parse', '--abbrev-ref', 'HEAD'], gitOpts) || null;
|
|
13962
14051
|
// Fallback for an unborn branch (fresh repo, no commits yet).
|
|
13963
14052
|
if (!st.branch || st.branch === 'HEAD') {
|
|
13964
|
-
|
|
14053
|
+
st.branch = __tryGit(['symbolic-ref', '--short', 'HEAD'], gitOpts) || st.branch;
|
|
13965
14054
|
}
|
|
13966
14055
|
try {
|
|
13967
|
-
const porcelain =
|
|
14056
|
+
const porcelain = __git(['status', '--porcelain'], gitOpts).trim();
|
|
13968
14057
|
st.dirty = porcelain ? porcelain.split('\n').filter(Boolean).length : 0;
|
|
13969
14058
|
} catch (_) {}
|
|
13970
14059
|
|
|
@@ -13983,7 +14072,7 @@ function main() {
|
|
|
13983
14072
|
if (st.lastIndex) {
|
|
13984
14073
|
try {
|
|
13985
14074
|
const since = Date.parse(st.lastIndex);
|
|
13986
|
-
const tracked =
|
|
14075
|
+
const tracked = __git(['ls-files'], gitOpts).split('\n').filter(Boolean);
|
|
13987
14076
|
let changed = 0;
|
|
13988
14077
|
for (const f of tracked.slice(0, 5000)) {
|
|
13989
14078
|
try { if (fs.statSync(path.join(cwd, f)).mtimeMs > since) changed++; } catch (_) {}
|