sigmap 7.0.0 → 7.0.1

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 CHANGED
@@ -10,6 +10,20 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [7.0.1] — 2026-06-14
14
+
15
+ Patch release — supply-chain hardening and package hygiene, plus a wider star nudge.
16
+
17
+ ### Fixed
18
+ - **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.
19
+
20
+ ### Changed
21
+ - **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.
22
+ - **`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.
23
+ - **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.
24
+
25
+ ---
26
+
13
27
  ## [7.0.0] — 2026-06-14
14
28
 
15
29
  Major release — **Squeeze** makes `ask` minimize pasted input by default, a behavioral change to the core command.
package/gen-context.js CHANGED
@@ -236,12 +236,11 @@ __factories["./src/config/loader"] = function(module, exports) {
236
236
  }
237
237
  }
238
238
  try {
239
- const { execSync } = require('child_process');
240
- const proto = extendsVal.startsWith('https') ? 'https' : 'http';
241
- const out = execSync(
242
- `node -e "const h=require('${proto}');let d='';h.get(${JSON.stringify(extendsVal)},r=>{r.on('data',c=>d+=c);r.on('end',()=>process.stdout.write(d))}).on('error',()=>process.exit(1))"`,
243
- { timeout: 10000, encoding: 'utf8' }
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 = execSync('git rev-parse --abbrev-ref HEAD', {
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 = execSync(
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.0.0',
6257
+ version: '7.0.1',
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
- try {
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 execSync('git rev-parse --short HEAD', { cwd, timeout: 2000 }).toString().trim(); }
8693
+ try { return __tryGit(['rev-parse', '--short', 'HEAD'], { cwd, timeout: 2000 }); }
8710
8694
  catch (_) { return ''; }
8711
8695
  }
8712
8696
  function detectVersion(cwd) {
@@ -9031,14 +9015,13 @@ __factories["./src/discovery/source-root-scorer"] = function(module, exports) {
9031
9015
  'use strict';
9032
9016
  const fs = require('fs');
9033
9017
  const path = require('path');
9034
- const { execSync } = require('child_process');
9035
9018
  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
9019
  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
9020
  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
9021
  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
9022
  function getRecentlyChangedDirs(cwd) {
9040
9023
  try {
9041
- const out = execSync('git log --name-only --format="" HEAD~10 2>/dev/null', { cwd, timeout: 3000 }).toString();
9024
+ const out = __git(['log', '--name-only', '--format=', 'HEAD~10'], { cwd, timeout: 3000 }).toString();
9042
9025
  return new Set(out.split('\n').filter(Boolean).map(f => f.split('/')[0]));
9043
9026
  } catch { return new Set(); }
9044
9027
  }
@@ -10027,8 +10010,6 @@ __factories["./src/nudge"] = function(module, exports) {
10027
10010
 
10028
10011
  const fs = require('fs');
10029
10012
  const path = require('path');
10030
- const os = require('os');
10031
- const crypto = require('crypto');
10032
10013
 
10033
10014
  const RUN_THRESHOLD = 10;
10034
10015
  const SUCCESS_THRESHOLD = 8;
@@ -10038,7 +10019,7 @@ function usagePath(cwd) { return path.join(cwd, '.context', 'usage.json'); }
10038
10019
  function defaultUsage() {
10039
10020
  return {
10040
10021
  totalRuns: 0, successfulRuns: 0, squeezeOffered: 0, squeezeAccepted: 0,
10041
- starNudgeShown: false, machineId: '', firstRunDate: null, lastRunDate: null,
10022
+ starNudgeShown: false, firstRunDate: null, lastRunDate: null,
10042
10023
  };
10043
10024
  }
10044
10025
 
@@ -10091,9 +10072,6 @@ function checkStarNudge(cwd, runSuccess, opts = {}) {
10091
10072
  const today = opts.today || new Date().toISOString().slice(0, 10);
10092
10073
  if (!usage.firstRunDate) usage.firstRunDate = today;
10093
10074
  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
10075
 
10098
10076
  let nudged = false;
10099
10077
  if (!usage.starNudgeShown && usage.totalRuns >= RUN_THRESHOLD && usage.successfulRuns >= SUCCESS_THRESHOLD) {
@@ -10956,9 +10934,20 @@ module.exports = { verify, buildSymbolSet, loadDeps, loadScripts, isTestPath };
10956
10934
  const fs = require('fs');
10957
10935
  const path = require('path');
10958
10936
  const os = require('os');
10959
- const { execSync } = require('child_process');
10960
10937
 
10961
- const VERSION = '7.0.0';
10938
+ // Shell-free subprocess helpers. execFileSync runs the binary directly (never
10939
+ // /bin/sh), so there is no shell-injection surface and no "Shell access"
10940
+ // capability for supply-chain scanners. stderr is discarded by default.
10941
+ function __git(args, opts = {}) {
10942
+ const { execFileSync } = require('child_process');
10943
+ return execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], ...opts });
10944
+ }
10945
+ function __tryGit(args, opts = {}) {
10946
+ try { return __git(args, opts).toString().trim(); }
10947
+ catch (_) { return ''; }
10948
+ }
10949
+
10950
+ const VERSION = '7.0.1';
10962
10951
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
10963
10952
 
10964
10953
  function requireSourceOrBundled(key) {
@@ -11340,9 +11329,7 @@ function applyTokenBudget(fileEntries, maxTokens) {
11340
11329
  function getRecentlyCommittedFiles(cwd, count) {
11341
11330
  const n = count || 10;
11342
11331
  try {
11343
- const out = execSync(`git log --name-only --format="" -n ${n}`, {
11344
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
11345
- });
11332
+ const out = __git(['log', '--name-only', '--format=', '-n', String(n)], { cwd });
11346
11333
  return new Set(out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
11347
11334
  } catch (_) {
11348
11335
  return new Set();
@@ -11354,8 +11341,8 @@ function getRecentlyCommittedFiles(cwd, count) {
11354
11341
  // ---------------------------------------------------------------------------
11355
11342
  function getDiffFiles(cwd, stagedOnly) {
11356
11343
  try {
11357
- const cmd = stagedOnly ? 'git diff --cached --name-only' : 'git diff HEAD --name-only';
11358
- const out = execSync(cmd, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
11344
+ const gitArgs = stagedOnly ? ['diff', '--cached', '--name-only'] : ['diff', 'HEAD', '--name-only'];
11345
+ const out = __git(gitArgs, { cwd });
11359
11346
  return new Set(
11360
11347
  out.split('\n').map((f) => f.trim()).filter(Boolean).map((f) => path.resolve(cwd, f))
11361
11348
  );
@@ -11453,17 +11440,13 @@ function buildChangesSection(cwd, config, fileEntries) {
11453
11440
  try {
11454
11441
  let range = `HEAD~${n}..HEAD`;
11455
11442
  try {
11456
- execSync(`git rev-parse --verify HEAD~${n} 2>/dev/null`, {
11457
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
11458
- });
11443
+ __git(['rev-parse', '--verify', `HEAD~${n}`], { cwd });
11459
11444
  } catch (_) {
11460
11445
  // HEAD~n doesn't exist (shallow repo) — clamp to deepest valid ancestor
11461
11446
  let best = 1;
11462
11447
  for (let k = n - 1; k >= 2; k--) {
11463
11448
  try {
11464
- execSync(`git rev-parse --verify HEAD~${k} 2>/dev/null`, {
11465
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
11466
- });
11449
+ __git(['rev-parse', '--verify', `HEAD~${k}`], { cwd });
11467
11450
  best = k;
11468
11451
  break;
11469
11452
  } catch (_) {}
@@ -11471,15 +11454,11 @@ function buildChangesSection(cwd, config, fileEntries) {
11471
11454
  range = `HEAD~${best}..HEAD`;
11472
11455
  }
11473
11456
 
11474
- const namesOnly = execSync(`git diff ${range} --name-only 2>/dev/null`, {
11475
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
11476
- });
11457
+ const namesOnly = __git(['diff', range, '--name-only'], { cwd });
11477
11458
  const changed = new Set(namesOnly.split('\n').map((l) => l.trim()).filter(Boolean).map((f) => path.resolve(cwd, f)));
11478
11459
  if (changed.size === 0) return [];
11479
11460
 
11480
- const timeAgo = execSync('git log -1 --format="%cr" 2>/dev/null', {
11481
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
11482
- }).trim();
11461
+ const timeAgo = __git(['log', '-1', '--format=%cr'], { cwd }).trim();
11483
11462
 
11484
11463
  const lines = [`## changes (last ${n} commits — ${timeAgo})`, '```'];
11485
11464
  let hasDelta = false;
@@ -11488,9 +11467,7 @@ function buildChangesSection(cwd, config, fileEntries) {
11488
11467
  const rel = path.relative(cwd, entry.filePath);
11489
11468
  let diff = '';
11490
11469
  try {
11491
- diff = execSync(`git diff ${range} -- "${rel}" 2>/dev/null`, {
11492
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
11493
- });
11470
+ diff = __git(['diff', range, '--', rel], { cwd });
11494
11471
  } catch (_) {
11495
11472
  continue;
11496
11473
  }
@@ -12470,6 +12447,23 @@ function runGenerate(cwd, config, reportMode, reportJson = false) {
12470
12447
  }
12471
12448
  }
12472
12449
 
12450
+ // v7.0.1: count a plain context-generation run for the one-time star nudge.
12451
+ // Most users only ever run `sigmap` to build the context file — count that too,
12452
+ // not just ask/squeeze. Guard so a monorepo (many runGenerate calls per process)
12453
+ // counts as a single run. Interactive only — silent under --json/--report or non-TTY.
12454
+ if (!global.__sigmapGenCounted) {
12455
+ global.__sigmapGenCounted = true;
12456
+ try {
12457
+ const { checkStarNudge } = requireSourceOrBundled('./src/nudge');
12458
+ const showNudge = !!process.stderr.isTTY
12459
+ && !reportJson
12460
+ && !process.argv.includes('--json')
12461
+ && !process.argv.includes('--report')
12462
+ && !process.argv.includes('--quiet');
12463
+ checkStarNudge(global.__sigmapRootCwd || cwd, true, { silent: !showNudge });
12464
+ } catch (_) {}
12465
+ }
12466
+
12473
12467
  return result;
12474
12468
  }
12475
12469
 
@@ -12651,11 +12645,7 @@ function suggestTool(description) {
12651
12645
 
12652
12646
  function resolveProjectRoot(startDir) {
12653
12647
  try {
12654
- const gitRoot = execSync('git rev-parse --show-toplevel', {
12655
- cwd: startDir,
12656
- encoding: 'utf8',
12657
- stdio: ['ignore', 'pipe', 'ignore'],
12658
- }).trim();
12648
+ const gitRoot = __git(['rev-parse', '--show-toplevel'], { cwd: startDir }).trim();
12659
12649
  if (gitRoot) return gitRoot;
12660
12650
  } catch (_) {}
12661
12651
  return startDir;
@@ -12926,8 +12916,7 @@ function buildIndexContext(ranked, cwd) {
12926
12916
 
12927
12917
  function computeCurrentRisk(cwd) {
12928
12918
  try {
12929
- const { execSync } = require('child_process');
12930
- const out = execSync('git diff --name-only HEAD', { cwd, timeout: 3000, encoding: 'utf8' });
12919
+ const out = __git(['diff', '--name-only', 'HEAD'], { cwd, timeout: 3000 });
12931
12920
  const count = out.trim().split('\n').filter(Boolean).length;
12932
12921
  if (count === 0) return 'NONE';
12933
12922
  if (count <= 3) return 'LOW';
@@ -13011,6 +13000,9 @@ function main() {
13011
13000
  const cwd = cwdFlag
13012
13001
  ? path.resolve(invokedFrom, cwdFlag)
13013
13002
  : resolveProjectRoot(invokedFrom);
13003
+ // v7.0.1: remember the invocation root so the generation star-nudge always tracks
13004
+ // usage at the repo root, even when runGenerate is called per-package (monorepo).
13005
+ global.__sigmapRootCwd = cwd;
13014
13006
  const scriptPath = process.argv[1] || path.join(invokedFrom, 'gen-context.js');
13015
13007
 
13016
13008
  if (cwdFlag) {
@@ -13286,9 +13278,8 @@ function main() {
13286
13278
  const short = args.includes('--short');
13287
13279
  let msg = '', diff = '';
13288
13280
  try {
13289
- const { execSync } = require('child_process');
13290
- msg = execSync('git log -1 --format=%s', { cwd, timeout: 3000, encoding: 'utf8' }).trim();
13291
- diff = execSync('git diff --cached --name-only', { cwd, timeout: 3000, encoding: 'utf8' });
13281
+ msg = __git(['log', '-1', '--format=%s'], { cwd, timeout: 3000 }).trim();
13282
+ diff = __git(['diff', '--cached', '--name-only'], { cwd, timeout: 3000 });
13292
13283
  } catch (_) {}
13293
13284
 
13294
13285
  let profile = 'default';
@@ -13309,13 +13300,15 @@ function main() {
13309
13300
 
13310
13301
  // v4.2: `sigmap compare` — human-readable benchmark CLI
13311
13302
  if (args[0] === 'compare') {
13312
- const { execSync } = require('child_process');
13303
+ const { execFileSync } = require('child_process');
13313
13304
  console.log('[sigmap] Running comparison benchmark (this may take ~30s)...\n');
13314
13305
 
13315
13306
  let raw = '';
13316
13307
  try {
13317
- raw = execSync(
13318
- `node ${JSON.stringify(path.join(__dirname, 'scripts', 'run-retrieval-benchmark.mjs'))} --compare`,
13308
+ // Shell-free: run the node binary directly with the script path as an argv.
13309
+ raw = execFileSync(
13310
+ process.execPath,
13311
+ [path.join(__dirname, 'scripts', 'run-retrieval-benchmark.mjs'), '--compare'],
13319
13312
  { cwd, timeout: 90_000, encoding: 'utf8' }
13320
13313
  );
13321
13314
  } catch (e) { raw = (e && e.stdout) ? e.stdout : ''; }
@@ -13372,9 +13365,13 @@ function main() {
13372
13365
  console.log(shareText);
13373
13366
 
13374
13367
  try {
13375
- const { execSync } = require('child_process');
13376
- const clipCmd = process.platform === 'darwin' ? 'pbcopy' : 'xclip -selection clipboard';
13377
- execSync(`printf '%s' ${JSON.stringify(shareText)} | ${clipCmd}`, { timeout: 2000 });
13368
+ // Shell-free: spawn the clipboard binary directly and write the text to its
13369
+ // stdin no shell, no pipe, no string-built command.
13370
+ const { execFileSync } = require('child_process');
13371
+ const [clipBin, clipArgs] = process.platform === 'darwin'
13372
+ ? ['pbcopy', []]
13373
+ : ['xclip', ['-selection', 'clipboard']];
13374
+ execFileSync(clipBin, clipArgs, { input: shareText, timeout: 2000 });
13378
13375
  console.log('\n[sigmap] Copied to clipboard.');
13379
13376
  } catch (_) {}
13380
13377
 
@@ -13955,16 +13952,16 @@ function main() {
13955
13952
  // `sigmap status` — environment / repo state at a glance.
13956
13953
  if (args[0] === 'status') {
13957
13954
  const jsonOut = args.includes('--json');
13958
- const gitOpts = { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] };
13955
+ const gitOpts = { cwd };
13959
13956
  const st = { branch: null, dirty: 0, lastIndex: null, indexVersion: null, indexFiles: null, changedSinceIndex: null, notes: 0, lastNote: null };
13960
13957
 
13961
- try { st.branch = execSync('git rev-parse --abbrev-ref HEAD', gitOpts).trim() || null; } catch (_) {}
13958
+ st.branch = __tryGit(['rev-parse', '--abbrev-ref', 'HEAD'], gitOpts) || null;
13962
13959
  // Fallback for an unborn branch (fresh repo, no commits yet).
13963
13960
  if (!st.branch || st.branch === 'HEAD') {
13964
- try { st.branch = execSync('git symbolic-ref --short HEAD', gitOpts).trim() || st.branch; } catch (_) {}
13961
+ st.branch = __tryGit(['symbolic-ref', '--short', 'HEAD'], gitOpts) || st.branch;
13965
13962
  }
13966
13963
  try {
13967
- const porcelain = execSync('git status --porcelain', gitOpts).trim();
13964
+ const porcelain = __git(['status', '--porcelain'], gitOpts).trim();
13968
13965
  st.dirty = porcelain ? porcelain.split('\n').filter(Boolean).length : 0;
13969
13966
  } catch (_) {}
13970
13967
 
@@ -13983,7 +13980,7 @@ function main() {
13983
13980
  if (st.lastIndex) {
13984
13981
  try {
13985
13982
  const since = Date.parse(st.lastIndex);
13986
- const tracked = execSync('git ls-files', gitOpts).split('\n').filter(Boolean);
13983
+ const tracked = __git(['ls-files'], gitOpts).split('\n').filter(Boolean);
13987
13984
  let changed = 0;
13988
13985
  for (const f of tracked.slice(0, 5000)) {
13989
13986
  try { if (fs.statSync(path.join(cwd, f)).mtimeMs > since) changed++; } catch (_) {}
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.0.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
12
+ # Version: 7.0.1 | 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.0.0 | Benchmark: sigmap-v7.0-main (2026-06-14)
12
+ # Version: 7.0.1 | 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,8 +1,8 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "7.0.0",
3
+ "version": "7.0.1",
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
- "main": "gen-context.js",
5
+ "main": "packages/core/index.js",
6
6
  "exports": {
7
7
  ".": "./packages/core/index.js",
8
8
  "./cli": "./packages/cli/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "7.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "SigMap CLI wrapper — thin adapter for programmatic CLI invocation",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-core",
3
- "version": "7.0.0",
3
+ "version": "7.0.1",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -34,12 +34,12 @@ function loadBaseConfig(extendsVal, cwd) {
34
34
  }).on('error', reject);
35
35
  });
36
36
  })();
37
- // sync fallback: use execSync with node -e
38
- const { execSync } = require('child_process');
39
- const out = execSync(
40
- `node -e "const h=require('${extendsVal.startsWith('https') ? 'https' : 'http'}');let d='';h.get(${JSON.stringify(extendsVal)},r=>{r.on('data',c=>d+=c);r.on('end',()=>process.stdout.write(d))}).on('error',()=>process.exit(1))"`,
41
- { timeout: 10000, encoding: 'utf8' }
42
- );
37
+ // Sync fallback: run a tiny fetch in a child node process. Shell-free —
38
+ // execFileSync runs the node binary directly (no /bin/sh) and the URL is
39
+ // passed as an argv value, never interpolated into a command string.
40
+ const { execFileSync } = require('child_process');
41
+ 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))";
42
+ const out = execFileSync(process.execPath, ['-e', SYNC_FETCH, extendsVal], { timeout: 10000, encoding: 'utf8' });
43
43
  const parsed = JSON.parse(out);
44
44
  if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
45
45
  fs.writeFileSync(cachePath, JSON.stringify(parsed), 'utf8');
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { execSync } = require('child_process');
5
+ const { git } = require('../util/git');
6
6
 
7
7
  const CODE_EXTS = new Set([
8
8
  '.js','.mjs','.cjs','.ts','.tsx','.jsx',
@@ -35,7 +35,7 @@ const ROOT_ENTRYPOINTS = {
35
35
 
36
36
  function getRecentlyChangedDirs(cwd) {
37
37
  try {
38
- const out = execSync('git log --name-only --format="" HEAD~10 2>/dev/null', { cwd, timeout: 3000 }).toString();
38
+ const out = git(['log', '--name-only', '--format=', 'HEAD~10'], { cwd, timeout: 3000 }).toString();
39
39
  return new Set(out.split('\n').filter(Boolean).map(f => f.split('/')[0]));
40
40
  } catch { return new Set(); }
41
41
  }
@@ -1,14 +1,13 @@
1
1
  'use strict';
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
- const { execSync } = require('child_process');
4
+ const { tryGit } = require('../util/git');
5
5
  module.exports = { format, outputPath };
6
6
 
7
7
  function outputPath(cwd) { return path.join(cwd, 'llms.txt'); }
8
8
 
9
9
  function getShortCommit(cwd) {
10
- try { return execSync('git rev-parse --short HEAD', { cwd, timeout: 2000 }).toString().trim(); }
11
- catch (_) { return ''; }
10
+ return tryGit(['rev-parse', '--short', 'HEAD'], { cwd, timeout: 2000 });
12
11
  }
13
12
 
14
13
  function detectVersion(cwd) {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { execSync } = require('child_process');
5
+ const { git } = require('../util/git');
6
6
 
7
7
  const CONTEXT_FILE = path.join('.github', 'copilot-instructions.md');
8
8
  const CONTEXT_COLD_FILE = path.join('.github', 'context-cold.md');
@@ -149,19 +149,14 @@ function createCheckpoint(args, cwd) {
149
149
  // ── Git info ────────────────────────────────────────────────────────────
150
150
  lines.push('## Git state');
151
151
  try {
152
- const branch = execSync('git rev-parse --abbrev-ref HEAD', {
153
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
154
- }).trim();
152
+ const branch = git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }).trim();
155
153
  lines.push(`**Branch:** ${branch}`);
156
154
  } catch (_) {
157
155
  lines.push('**Branch:** (not a git repo)');
158
156
  }
159
157
 
160
158
  try {
161
- const log = execSync(
162
- 'git log --oneline -5 --no-decorate 2>/dev/null',
163
- { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
164
- ).trim();
159
+ const log = git(['log', '--oneline', '-5', '--no-decorate'], { cwd }).trim();
165
160
  if (log) {
166
161
  lines.push('');
167
162
  lines.push('**Recent commits:**');
package/src/mcp/server.js CHANGED
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '7.0.0',
21
+ version: '7.0.1',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24
 
package/src/nudge.js CHANGED
@@ -11,8 +11,6 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const os = require('os');
15
- const crypto = require('crypto');
16
14
 
17
15
  const RUN_THRESHOLD = 10;
18
16
  const SUCCESS_THRESHOLD = 8;
@@ -22,7 +20,7 @@ function usagePath(cwd) { return path.join(cwd, '.context', 'usage.json'); }
22
20
  function defaultUsage() {
23
21
  return {
24
22
  totalRuns: 0, successfulRuns: 0, squeezeOffered: 0, squeezeAccepted: 0,
25
- starNudgeShown: false, machineId: '', firstRunDate: null, lastRunDate: null,
23
+ starNudgeShown: false, firstRunDate: null, lastRunDate: null,
26
24
  };
27
25
  }
28
26
 
@@ -75,9 +73,6 @@ function checkStarNudge(cwd, runSuccess, opts = {}) {
75
73
  const today = opts.today || new Date().toISOString().slice(0, 10);
76
74
  if (!usage.firstRunDate) usage.firstRunDate = today;
77
75
  usage.lastRunDate = today;
78
- if (!usage.machineId) {
79
- try { usage.machineId = 'sha256-' + crypto.createHash('sha256').update(os.hostname()).digest('hex').slice(0, 16); } catch (_) {}
80
- }
81
76
 
82
77
  let nudged = false;
83
78
  if (!usage.starNudgeShown && usage.totalRuns >= RUN_THRESHOLD && usage.successfulRuns >= SUCCESS_THRESHOLD) {
@@ -15,7 +15,7 @@
15
15
 
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
- const { execSync } = require('child_process');
18
+ const { tryGit } = require('../util/git');
19
19
 
20
20
  const NOTES_FILE = path.join('.context', 'notes.ndjson');
21
21
  const MAX_TEXT = 2000;
@@ -25,13 +25,7 @@ function notesPath(cwd) {
25
25
  }
26
26
 
27
27
  function _currentBranch(cwd) {
28
- try {
29
- return execSync('git rev-parse --abbrev-ref HEAD', {
30
- cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
31
- }).trim() || null;
32
- } catch (_) {
33
- return null;
34
- }
28
+ return tryGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd }) || null;
35
29
  }
36
30
 
37
31
  /**
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Shell-free git invocation.
5
+ *
6
+ * Uses `execFileSync('git', [...])`, which executes the git binary directly —
7
+ * it never spawns a system shell (`/bin/sh -c`). That means:
8
+ * - no shell-injection surface (arguments are passed as an array, never
9
+ * interpolated into a command string), and
10
+ * - supply-chain scanners (e.g. Socket) do not flag a "Shell access" capability.
11
+ *
12
+ * stderr is discarded by default (replaces the old `2>/dev/null` redirects).
13
+ */
14
+
15
+ const { execFileSync } = require('child_process');
16
+
17
+ function git(args, opts = {}) {
18
+ return execFileSync('git', args, {
19
+ encoding: 'utf8',
20
+ stdio: ['ignore', 'pipe', 'ignore'],
21
+ ...opts,
22
+ });
23
+ }
24
+
25
+ // Convenience: run git and return trimmed stdout, or '' on any failure.
26
+ function tryGit(args, opts = {}) {
27
+ try { return git(args, opts).toString().trim(); }
28
+ catch (_) { return ''; }
29
+ }
30
+
31
+ module.exports = { git, tryGit };