sigmap 3.2.1 → 3.3.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 CHANGED
@@ -8,7 +8,46 @@ Format: [Semantic Versioning](https://semver.org/)
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ---
12
+
13
+ ## [3.3.0] — 2026-04-08 — Context-Aware CLI & Command Switcher
14
+
15
+ ### Added
16
+ - **Context-aware `--help` output** — `gen-context.js` and `gen-project-map.js` now detect how they were invoked and show the matching command in every usage example:
17
+ - `npx sigmap --help` shows `npx sigmap <flag>`
18
+ - `sigmap --help` shows `sigmap <flag>`
19
+ - `gen-context --help` shows `gen-context <flag>`
20
+ - `node gen-context.js --help` shows `node gen-context.js <flag>` (unchanged for local users)
21
+ - Detection uses `process.argv[1]` path analysis (npx cache path, basename without `.js`, fallback)
22
+ - **`docs/cli.html` command picker** — four-tab switcher ("How you run it:") above the flags reference terminal updates every code block on the page (all `.tw` spans and `.term-title` bars) to the selected invocation style. Applies equally to `gen-project-map` references. Selection is saved in `localStorage` and restored on next visit.
23
+ - **`docs/readmes/`** — `vscode-extension.md` and `jetbrains-plugin.md` added for docs site cross-linking
24
+ - **`gen-context.config.json`** — example config committed alongside the repo for reference
25
+ - **Gemini adapter context file** — `.github/gemini-context.md` now generated alongside the copilot instructions file
26
+ - **SEO improvements across all docs pages** — structured data, canonical tags, improved meta descriptions, and `sitemap.xml` updated to v3.3.0
27
+
28
+ ### Added (from `fix/defaults-css-coverage-budget-36` · #38)
29
+ - **`--each` flag — multi-repo parent directory support** · [#37](https://github.com/manojmallick/sigmap/issues/37)
30
+ - Running `node gen-context.js --each` (or `sigmap --each`) from a parent directory that contains multiple independent git repos now processes each repo in one shot.
31
+ - Scans immediate subdirectories; a subdirectory qualifies when it contains `.git` or a recognised project manifest (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `build.gradle`, `pom.xml`, `requirements.txt`).
32
+ - Each sub-repo is processed independently: it loads its own `gen-context.config.json` when present, uses its own `srcDirs`, and writes its own context files (`.github/copilot-instructions.md` etc.) inside itself.
33
+ - Summary printed at the end: `[sigmap] --each: done — 3 succeeded`.
34
+ - Distinct from `--monorepo` (which processes workspace packages inside a single repo); `--each` targets sibling repos under a shared parent directory.
35
+
11
36
  ### Fixed
37
+ - **Default excludes expanded + `changesCommits` corrected** · [#36](https://github.com/manojmallick/sigmap/issues/36)
38
+ - `changesCommits` default raised from `5` to `10` to match the documented recommended value.
39
+ - Added `playwright-tmp`, `playwright-report`, `test-results`, `.turbo`, `storybook-static`, `.docusaurus` to the default `exclude` list so they are skipped on modern JS/TS projects without requiring manual config.
40
+ - **CSS extractor: utility-class noise elimination** · [#36](https://github.com/manojmallick/sigmap/issues/36)
41
+ - Files where ≥70% of top-level selectors are single-word (e.g. Tailwind / compiled utility CSS) are now detected automatically and class extraction is skipped entirely, preventing the output from being flooded with low-signal entries like `.p-4`, `.flex`, `.text-sm`.
42
+ - For semantic CSS, BEM/hyphenated class names (e.g. `.modal-header`, `.btn-primary`) fill output slots first; single-word names only fill remaining slots up to 8.
43
+ - **`testCoverage` false-positive coverage markers eliminated** · [#36](https://github.com/manojmallick/sigmap/issues/36)
44
+ - Removed the "all word tokens" pass from `buildTestIndex` that caused common words appearing anywhere in a test file (comments, variable names) to mark unrelated functions as `✓` tested.
45
+ - Index now only includes tokens extracted from test name strings (`it('...')`, `test('...')`, `describe('...')`) and identifiers directly invoked inside `expect(fn())` / `assert(fn())` calls.
46
+ - **Token budget: mock/fixture files drop before test files** · [#36](https://github.com/manojmallick/sigmap/issues/36)
47
+ - Added `isMockFile()` helper and priority-9 drop tier in `applyTokenBudget`. Paths matching `mock`, `mocks`, `stub`, `stubs`, `fake`, `fakes`, `demo`, `__mocks__`, `fixtures` or file suffixes like `.mock.ts` now drop before test files (priority 8) and after generated files (priority 10), keeping real production code in context longer.
48
+ - Fixed `applyTokenBudget` loop direction: generated/mock/test files now drop first (as intended) rather than source files being dropped first.
49
+ - **`--monorepo` now respects configured output adapter** · [#39](https://github.com/manojmallick/sigmap/issues/39)
50
+ - Removed hardcoded `outputs: ['claude']` override — `--monorepo` now inherits `outputs` from the root config, defaulting to `copilot` (writes `copilot-instructions.md` per package).
12
51
  - **IDE command resolution parity (VS Code/Open VSX/JetBrains)** · [#34](https://github.com/manojmallick/sigmap/issues/34)
13
52
  - Unified resolver now checks both `sigmap` and `gen-context` executables with consistent fallback order.
14
53
  - Improved cross-platform probing for local workspace bins, Volta/nvm/npm-global installs, and OS-specific command lookup (`where` on Windows, shell lookup on macOS/Linux).
@@ -20,7 +59,19 @@ Format: [Semantic Versioning](https://semver.org/)
20
59
 
21
60
  ---
22
61
 
23
- ## [3.2.0] — PlannedPhase A: Cross-Platform Standalone Binaries
62
+ ## [3.2.1] — 2026-04-07Patch: IDE Command Resolution & Plugin Parity
63
+
64
+ ### Added
65
+ - **IDE command resolution parity (VS Code / Open VSX / JetBrains)** · [#34](https://github.com/manojmallick/sigmap/issues/34)
66
+ - Unified resolver checks both `sigmap` and `gen-context` executables with consistent fallback order
67
+ - Improved cross-platform probing for local workspace bins, Volta/nvm/npm-global installs, and OS-specific command lookup (`where` on Windows, shell lookup on macOS/Linux)
68
+ - JetBrains plugin resolves commands more reliably outside Node-only projects and provides OS-aware install guidance when command lookup fails
69
+ - **`scripts/sync-versions.mjs`** — one-shot script to bump version across all package manifests and `gen-context.js` in sync
70
+ - **Updated plugin docs** — VS Code/Open VSX and JetBrains setup docs updated with all supported install paths (npm global, npm local, npx, standalone binaries, project-local `gen-context.js`)
71
+
72
+ ---
73
+
74
+ ## [3.2.0] — 2026-04-07 — Cross-Platform Standalone Binaries
24
75
 
25
76
  ### Added
26
77
  - **Standalone binaries** — macOS (arm64 + x64), Linux x64, Windows x64 built via Node.js SEA
@@ -35,7 +86,7 @@ Format: [Semantic Versioning](https://semver.org/)
35
86
 
36
87
  ### Technical
37
88
  - Uses [Node.js SEA](https://nodejs.org/api/single-executable-applications.html) (Node 20 `--experimental-sea-config` + `postject`)
38
- - `gen-context.js` was updated to include previously-missing `src/` modules (`todos`, `coverage`, `prdiff`) in the SEA bundle; existing `requireSourceOrBundled()` fallback and DEFAULTS fallback in `writeInitConfig()` remain SEA-compatible
89
+ - `gen-context.js` updated to include previously-missing `src/` modules (`todos`, `coverage`, `prdiff`) in the SEA bundle; `requireSourceOrBundled()` fallback remains SEA-compatible
39
90
  - Binary builds run natively per OS in GHA (no cross-compilation)
40
91
  - `release-attach` job waits for the npm-publish Release to exist before uploading binary assets
41
92
 
package/README.md CHANGED
@@ -551,7 +551,8 @@ node gen-context.js --health --json Machine-readable health JSON
551
551
  node gen-context.js --suggest-tool "<task>" Recommend model tier for a task
552
552
  node gen-context.js --suggest-tool "<task>" --json Machine-readable tier recommendation
553
553
 
554
- node gen-context.js --monorepo Per-package context for monorepos
554
+ node gen-context.js --monorepo Per-package context for monorepos (packages/, apps/, services/)
555
+ node gen-context.js --each Process each sub-repo under a parent directory
555
556
  node gen-context.js --routing Include model routing hints in output
556
557
  node gen-context.js --format cache Write Anthropic prompt-cache JSON
557
558
  node gen-context.js --track Append run metrics to .context/usage.ndjson
package/gen-context.js CHANGED
@@ -5049,7 +5049,7 @@ const path = require('path');
5049
5049
  const os = require('os');
5050
5050
  const { execSync } = require('child_process');
5051
5051
 
5052
- const VERSION = '3.2.1';
5052
+ const VERSION = '3.3.0';
5053
5053
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
5054
5054
 
5055
5055
  function requireSourceOrBundled(key) {
@@ -5251,6 +5251,13 @@ function isGeneratedFile(filePath) {
5251
5251
  return /(\.generated\.|\.pb\.|_pb\.)/.test(filePath);
5252
5252
  }
5253
5253
 
5254
+ function isMockFile(filePath) {
5255
+ const p = filePath.replace(/\\/g, '/');
5256
+ return /\/(mock|mocks|stub|stubs|fake|fakes|demo|demos|__mocks__|fixtures)\//i.test(p) ||
5257
+ /\.(mock|stub|fake)\.[jt]sx?$/.test(p) ||
5258
+ /mock\.(ts|js|tsx|jsx)$/.test(p);
5259
+ }
5260
+
5254
5261
  function applyTokenBudget(fileEntries, maxTokens) {
5255
5262
  // fileEntries: [{ filePath, sigs, mtime }]
5256
5263
  // Reserve ~10% for formatting overhead (section headers, code fences, top-level header)
@@ -5262,6 +5269,7 @@ function applyTokenBudget(fileEntries, maxTokens) {
5262
5269
  const withPriority = fileEntries.map((e) => {
5263
5270
  let priority = 0;
5264
5271
  if (isGeneratedFile(e.filePath)) priority = 10;
5272
+ else if (isMockFile(e.filePath)) priority = 9;
5265
5273
  else if (isTestFile(e.filePath)) priority = 8;
5266
5274
  else if (isConfigFile(e.filePath)) priority = 6;
5267
5275
  else priority = 4;
@@ -5276,14 +5284,15 @@ function applyTokenBudget(fileEntries, maxTokens) {
5276
5284
 
5277
5285
  const kept = [];
5278
5286
  let dropped = 0;
5279
- for (let i = withPriority.length - 1; i >= 0; i--) {
5280
- const entry = withPriority[i];
5287
+ // Iterate forward: highest drop-priority files (generated=10, mock=9, test=8) are at index 0
5288
+ // Drop those first until we're under budget, then keep everything else
5289
+ for (const entry of withPriority) {
5281
5290
  const entryTokens = estimateTokens(entry.sigs.join('\n'));
5282
- if (total <= effectiveBudget) {
5283
- kept.unshift(entry);
5284
- } else {
5291
+ if (total > effectiveBudget) {
5285
5292
  total -= entryTokens;
5286
5293
  dropped++;
5294
+ } else {
5295
+ kept.push(entry);
5287
5296
  }
5288
5297
  }
5289
5298
  if (dropped > 0) {
@@ -6206,11 +6215,10 @@ function runMonorepo(cwd, config) {
6206
6215
  for (const pkgPath of packages) {
6207
6216
  const pkgName = path.relative(cwd, pkgPath);
6208
6217
  console.warn(`[sigmap] monorepo: processing ${pkgName}`);
6209
- // Per-package config: scan src/ and package root, write CLAUDE.md per package
6218
+ // Per-package config: scan src/ and package root, inherit outputs from parent config
6210
6219
  const pkgConfig = {
6211
6220
  ...config,
6212
6221
  srcDirs: ['src', 'lib', 'app', '.'],
6213
- outputs: ['claude'],
6214
6222
  };
6215
6223
  try {
6216
6224
  runGenerate(pkgPath, pkgConfig, false);
@@ -6218,7 +6226,66 @@ function runMonorepo(cwd, config) {
6218
6226
  console.warn(`[sigmap] monorepo: failed for ${pkgName}: ${err.message}`);
6219
6227
  }
6220
6228
  }
6221
- console.warn(`[sigmap] monorepo: wrote CLAUDE.md for ${packages.length} packages`);
6229
+ console.warn(`[sigmap] monorepo: done — wrote context files for ${packages.length} packages`);
6230
+ }
6231
+
6232
+ // ---------------------------------------------------------------------------
6233
+ // Multi-repo support (--each)
6234
+ // Run sigmap independently for every immediate subdirectory that looks like
6235
+ // an independent git repo or project root. Each sub-repo gets its own
6236
+ // context files written inside it, using its own gen-context.config.json
6237
+ // when present.
6238
+ // ---------------------------------------------------------------------------
6239
+ function detectRepoDirs(cwd) {
6240
+ let entries;
6241
+ try {
6242
+ entries = fs.readdirSync(cwd, { withFileTypes: true });
6243
+ } catch (_) {
6244
+ return [];
6245
+ }
6246
+ const repos = [];
6247
+ for (const entry of entries) {
6248
+ if (!entry.isDirectory()) continue;
6249
+ if (entry.name.startsWith('.')) continue;
6250
+ const abs = path.join(cwd, entry.name);
6251
+ // A directory qualifies if it has its own .git folder
6252
+ // OR contains a recognised project manifest (fallback for un-initialised repos)
6253
+ const hasGit = fs.existsSync(path.join(abs, '.git'));
6254
+ const hasManifest = PKG_MANIFESTS.some((m) => fs.existsSync(path.join(abs, m)));
6255
+ if (hasGit || hasManifest) repos.push(abs);
6256
+ }
6257
+ return repos;
6258
+ }
6259
+
6260
+ function runEach(cwd, baseConfig) {
6261
+ const repos = detectRepoDirs(cwd);
6262
+ if (repos.length === 0) {
6263
+ console.warn('[sigmap] --each: no project subdirectories found');
6264
+ console.warn('[sigmap] A subdirectory qualifies when it contains .git or a package manifest');
6265
+ console.warn(`[sigmap] (package.json / pyproject.toml / Cargo.toml / go.mod / etc.)`);
6266
+ return;
6267
+ }
6268
+ console.warn(`[sigmap] --each: found ${repos.length} repos — ${repos.map((r) => path.basename(r)).join(', ')}`);
6269
+ let ok = 0, failed = 0;
6270
+ for (const repoDir of repos) {
6271
+ const name = path.basename(repoDir);
6272
+ // Load each repo's own config independently, fall back to base config
6273
+ let repoConfig;
6274
+ try {
6275
+ repoConfig = loadConfig(repoDir);
6276
+ } catch (_) {
6277
+ repoConfig = { ...baseConfig };
6278
+ }
6279
+ console.warn(`[sigmap] --each: processing ${name} …`);
6280
+ try {
6281
+ runGenerate(repoDir, repoConfig, false);
6282
+ ok++;
6283
+ } catch (err) {
6284
+ console.warn(`[sigmap] --each: FAILED for ${name}: ${err.message}`);
6285
+ failed++;
6286
+ }
6287
+ }
6288
+ console.warn(`[sigmap] --each: done — ${ok} succeeded${failed > 0 ? `, ${failed} failed` : ''}`);
6222
6289
  }
6223
6290
 
6224
6291
  // ---------------------------------------------------------------------------
@@ -6272,50 +6339,71 @@ function resolveProjectRoot(startDir) {
6272
6339
  return startDir;
6273
6340
  }
6274
6341
 
6275
- function printHelp() {
6342
+ function detectInvokedAs() {
6343
+ const argv1 = process.argv[1] || '';
6344
+ const base = path.basename(argv1);
6345
+ const baseNoExt = base.endsWith('.js') ? base.slice(0, -3) : base;
6346
+ // npx: the script path goes through an _npx cache directory
6347
+ if (argv1.includes('/_npx/') || argv1.includes('\\_npx\\') ||
6348
+ argv1.includes('/.npm/_') || argv1.includes('\\.npm\\_')) {
6349
+ return 'npx sigmap';
6350
+ }
6351
+ // globally-installed bin aliases (no .js extension)
6352
+ if (baseNoExt === 'sigmap') return 'sigmap';
6353
+ if (baseNoExt === 'gen-context') return 'gen-context';
6354
+ // default: local file invocation
6355
+ return 'node gen-context.js';
6356
+ }
6357
+
6358
+ function printHelp(cmd) {
6359
+ cmd = cmd || 'node gen-context.js';
6360
+ const header = cmd === 'node gen-context.js'
6361
+ ? `SigMap — gen-context.js v${VERSION}`
6362
+ : `SigMap v${VERSION} (${cmd})`;
6276
6363
  console.log(`
6277
- SigMap — gen-context.js v${VERSION}
6364
+ ${header}
6278
6365
  Zero-dependency AI context engine
6279
6366
 
6280
6367
  Usage:
6281
- node gen-context.js Generate context once and exit
6282
- node gen-context.js --monorepo Generate per-package context (monorepo)
6283
- node gen-context.js --routing Include model routing hints in output
6284
- node gen-context.js --format cache Also write Anthropic prompt-cache JSON
6285
- node gen-context.js --track Append run metrics to .context/usage.ndjson
6286
- node gen-context.js --watch Generate + watch for file changes
6287
- node gen-context.js --setup Generate + install git hook + watch
6288
- node gen-context.js --mcp Start MCP server on stdio
6289
- node gen-context.js --report Token reduction stats to stdout
6290
- node gen-context.js --report --json Token report as JSON (for CI; exits 1 if over budget)
6291
- node gen-context.js --report --history Print usage log summary from .context/usage.ndjson
6292
- node gen-context.js --report --history --chart Include inline SVG charts + Unicode sparklines
6293
- node gen-context.js --dashboard Write benchmarks/reports/dashboard.html
6294
- node gen-context.js --suggest-tool "<task>" Recommend model tier for a task description
6295
- node gen-context.js --suggest-tool "<task>" --json Machine-readable tier recommendation
6296
- node gen-context.js --health Print composite health score
6297
- node gen-context.js --health --json Machine-readable health score
6298
- node gen-context.js --diff Generate context for git-changed files only
6299
- node gen-context.js --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
6300
- node gen-context.js --diff --staged Generate context for staged files only
6301
- node gen-context.js --benchmark Run retrieval benchmark (benchmarks/tasks/retrieval.jsonl)
6302
- node gen-context.js --adapter <name> Generate for a specific adapter only (v3.0+)
6303
- node gen-context.js --adapter <name> --json Show adapter output path as JSON
6304
- node gen-context.js --benchmark --json Benchmark results as JSON
6305
- node gen-context.js --eval Alias for --benchmark
6306
- node gen-context.js --analyze Per-file breakdown: sigs, tokens, extractor, coverage
6307
- node gen-context.js --analyze --json Breakdown as JSON
6308
- node gen-context.js --analyze --slow Re-time each extractor; flag files >50ms
6309
- node gen-context.js --diagnose-extractors Run all 21 extractors vs fixtures; show pass/fail + diff
6310
- node gen-context.js --query "<text>" Rank files by relevance to a query
6311
- node gen-context.js --query "<text>" --json Ranked results as JSON
6312
- node gen-context.js --query "<text>" --top <n> Limit results to top N files (default 10)
6313
- node gen-context.js --impact <file> Show every file impacted by changing <file>
6314
- node gen-context.js --impact <file> --json Impact as JSON {changed, direct, transitive, tests, routes}
6315
- node gen-context.js --impact <file> --depth <n> BFS depth limit (default 3, 0=unlimited)
6316
- node gen-context.js --init Write example config + .contextignore scaffold
6317
- node gen-context.js --help Show this message
6318
- node gen-context.js --version Show version
6368
+ ${cmd} Generate context once and exit
6369
+ ${cmd} --monorepo Generate per-package context (monorepo)
6370
+ ${cmd} --each Run for every repo in the current directory
6371
+ ${cmd} --routing Include model routing hints in output
6372
+ ${cmd} --format cache Also write Anthropic prompt-cache JSON
6373
+ ${cmd} --track Append run metrics to .context/usage.ndjson
6374
+ ${cmd} --watch Generate + watch for file changes
6375
+ ${cmd} --setup Generate + install git hook + watch
6376
+ ${cmd} --mcp Start MCP server on stdio
6377
+ ${cmd} --report Token reduction stats to stdout
6378
+ ${cmd} --report --json Token report as JSON (for CI; exits 1 if over budget)
6379
+ ${cmd} --report --history Print usage log summary from .context/usage.ndjson
6380
+ ${cmd} --report --history --chart Include inline SVG charts + Unicode sparklines
6381
+ ${cmd} --dashboard Write benchmarks/reports/dashboard.html
6382
+ ${cmd} --suggest-tool "<task>" Recommend model tier for a task description
6383
+ ${cmd} --suggest-tool "<task>" --json Machine-readable tier recommendation
6384
+ ${cmd} --health Print composite health score
6385
+ ${cmd} --health --json Machine-readable health score
6386
+ ${cmd} --diff Generate context for git-changed files only
6387
+ ${cmd} --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
6388
+ ${cmd} --diff --staged Generate context for staged files only
6389
+ ${cmd} --benchmark Run retrieval benchmark (benchmarks/tasks/retrieval.jsonl)
6390
+ ${cmd} --adapter <name> Generate for a specific adapter only (v3.0+)
6391
+ ${cmd} --adapter <name> --json Show adapter output path as JSON
6392
+ ${cmd} --benchmark --json Benchmark results as JSON
6393
+ ${cmd} --eval Alias for --benchmark
6394
+ ${cmd} --analyze Per-file breakdown: sigs, tokens, extractor, coverage
6395
+ ${cmd} --analyze --json Breakdown as JSON
6396
+ ${cmd} --analyze --slow Re-time each extractor; flag files >50ms
6397
+ ${cmd} --diagnose-extractors Run all 21 extractors vs fixtures; show pass/fail + diff
6398
+ ${cmd} --query "<text>" Rank files by relevance to a query
6399
+ ${cmd} --query "<text>" --json Ranked results as JSON
6400
+ ${cmd} --query "<text>" --top <n> Limit results to top N files (default 10)
6401
+ ${cmd} --impact <file> Show every file impacted by changing <file>
6402
+ ${cmd} --impact <file> --json Impact as JSON {changed, direct, transitive, tests, routes}
6403
+ ${cmd} --impact <file> --depth <n> BFS depth limit (default 3, 0=unlimited)
6404
+ ${cmd} --init Write example config + .contextignore scaffold
6405
+ ${cmd} --help Show this message
6406
+ ${cmd} --version Show version
6319
6407
 
6320
6408
  Strategies (set via config "strategy" key):
6321
6409
  "full" Single file, all signatures. Works everywhere. (default)
@@ -6380,7 +6468,7 @@ function main() {
6380
6468
  }
6381
6469
 
6382
6470
  if (args.includes('--help') || args.includes('-h')) {
6383
- printHelp();
6471
+ printHelp(detectInvokedAs());
6384
6472
  process.exit(0);
6385
6473
  }
6386
6474
 
@@ -6779,6 +6867,11 @@ function main() {
6779
6867
  process.exit(0);
6780
6868
  }
6781
6869
 
6870
+ if (args.includes('--each')) {
6871
+ runEach(cwd, config);
6872
+ process.exit(0);
6873
+ }
6874
+
6782
6875
  if (args.includes('--setup')) {
6783
6876
  runGenerate(cwd, config, false);
6784
6877
  installHook(cwd, scriptPath);
@@ -24,19 +24,32 @@ const OUTPUT_FILE = 'PROJECT_MAP.md';
24
24
  // ---------------------------------------------------------------------------
25
25
  const args = process.argv.slice(2);
26
26
 
27
+ function detectInvokedAs() {
28
+ const argv1 = process.argv[1] || '';
29
+ const base = path.basename(argv1);
30
+ const baseNoExt = base.endsWith('.js') ? base.slice(0, -3) : base;
31
+ if (argv1.includes('/_npx/') || argv1.includes('\\_npx\\') ||
32
+ argv1.includes('/.npm/_') || argv1.includes('\\.npm\\_')) {
33
+ return 'npx gen-project-map';
34
+ }
35
+ if (baseNoExt === 'gen-project-map') return 'gen-project-map';
36
+ return 'node gen-project-map.js';
37
+ }
38
+
27
39
  if (args.includes('--version')) {
28
40
  console.log(`gen-project-map.js v${VERSION}`);
29
41
  process.exit(0);
30
42
  }
31
43
 
32
44
  if (args.includes('--help')) {
45
+ const cmd = detectInvokedAs();
33
46
  console.log([
34
- `gen-project-map.js v${VERSION} — SigMap project map generator`,
47
+ `SigMap project map generator v${VERSION} (${cmd})`,
35
48
  '',
36
49
  'Usage:',
37
- ' node gen-project-map.js Generate PROJECT_MAP.md',
38
- ' node gen-project-map.js --version Print version and exit',
39
- ' node gen-project-map.js --help Print this message',
50
+ ` ${cmd} Generate PROJECT_MAP.md`,
51
+ ` ${cmd} --version Print version and exit`,
52
+ ` ${cmd} --help Print this message`,
40
53
  '',
41
54
  'Configuration: gen-context.config.json (same file as gen-context.js)',
42
55
  ' srcDirs — directories to scan (default: ["src","app","lib",...])',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "Zero-dependency AI context engine — 97% token reduction. No npm install. Runs on Node 18+.",
5
5
  "main": "gen-context.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
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": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -29,6 +29,12 @@ const DEFAULTS = {
29
29
  'node_modules', '.git', 'dist', 'build', 'out',
30
30
  '__pycache__', '.next', 'coverage', 'target', 'vendor',
31
31
  '.context',
32
+ // CI/test artifacts
33
+ 'playwright-tmp', 'playwright-report', 'test-results',
34
+ // build/monorepo caches
35
+ '.turbo',
36
+ // documentation build output
37
+ 'storybook-static', '.docusaurus',
32
38
  ],
33
39
 
34
40
  // Maximum directory depth to recurse
@@ -86,7 +92,7 @@ const DEFAULTS = {
86
92
  changes: true,
87
93
 
88
94
  // Number of commits used for changes section
89
- changesCommits: 5,
95
+ changesCommits: 10,
90
96
 
91
97
  // Add test coverage markers to extracted function signatures (opt-in)
92
98
  testCoverage: false,
@@ -34,12 +34,26 @@ function buildTestIndex(cwd, testDirs) {
34
34
  continue;
35
35
  }
36
36
 
37
- for (const m of src.matchAll(/\b(?:test_|it\(|test\(|describe\()\s*['"`]?([\w_]+)/g)) {
38
- if (m[1] && m[1].length >= 3) names.add(m[1].toLowerCase());
37
+ // Extract tokens from JS/TS test name strings (it/test/describe calls)
38
+ for (const m of src.matchAll(/\b(?:it|test|describe)\s*\(\s*['"\`]([\w_ ]+)['"\`]/g)) {
39
+ if (!m[1]) continue;
40
+ const full = m[1].replace(/[\s_]+/g, '_').toLowerCase();
41
+ names.add(full); // also keep whole phrase: "validate_user"
42
+ for (const word of m[1].split(/[\s_]+/)) {
43
+ if (word.length >= 3) names.add(word.toLowerCase());
44
+ }
39
45
  }
40
-
41
- for (const m of src.matchAll(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g)) {
42
- if (m[1] && m[1].length >= 4) names.add(m[1].toLowerCase());
46
+ // Python/Ruby style: def test_funcname() / def test_funcname: → index the suffix
47
+ for (const m of src.matchAll(/\bdef\s+test_([\w]+)\s*[(:]/g)) {
48
+ if (!m[1]) continue;
49
+ names.add(m[1].toLowerCase()); // full suffix: "validate_user"
50
+ for (const word of m[1].split('_')) {
51
+ if (word.length >= 3) names.add(word.toLowerCase());
52
+ }
53
+ }
54
+ // Also capture identifiers directly invoked in expect/assert calls
55
+ for (const m of src.matchAll(/\b(?:expect|assert)\s*\(\s*(?:await\s+)?([\w]+)\s*\(/g)) {
56
+ if (m[1] && m[1].length >= 3) names.add(m[1].toLowerCase());
43
57
  }
44
58
  }
45
59
  }
@@ -50,7 +64,15 @@ function buildTestIndex(cwd, testDirs) {
50
64
  function isTested(funcName, testIndex) {
51
65
  if (!funcName || funcName.length < 3 || !testIndex || testIndex.size === 0) return false;
52
66
  const lower = funcName.toLowerCase();
67
+ // Direct match or test_ prefix match
53
68
  if (testIndex.has(lower) || testIndex.has(`test_${lower}`)) return true;
69
+ // Token-level match: split camelCase and snake_case, check if all meaningful tokens are covered
70
+ const tokens = funcName
71
+ .replace(/([A-Z])/g, '_$1')
72
+ .toLowerCase()
73
+ .split(/[_\s]+/)
74
+ .filter(t => t.length >= 3);
75
+ if (tokens.length >= 2 && tokens.every(t => testIndex.has(t))) return true;
54
76
  return false;
55
77
  }
56
78
 
@@ -37,13 +37,31 @@ function extract(src) {
37
37
  sigs.push(`@function ${m[1]}(${m[2].trim()})`);
38
38
  }
39
39
 
40
- // Key class names (top-level)
41
- const classNames = new Set();
42
- for (const m of stripped.matchAll(/^\.([\w-]+)(?=[^{]*\{)/gm)) {
43
- classNames.add(m[1]);
44
- if (classNames.size >= 10) break;
40
+ // Key class names (top-level) — prefer hyphenated BEM/component names over utilities
41
+ const allClassMatches = [...stripped.matchAll(/^\.([\w-]+)(?=[^{]*\{)/gm)];
42
+ // Utility-class detection: classes are "utility-like" if they have no hyphen (e.g. .flex)
43
+ // OR match Tailwind patterns: -digit suffix (.p-4, .bg-blue-500) or common size abbreviations
44
+ // (.text-sm, .py-lg). Files where ≥70% of selectors are utility-like are skipped to avoid noise.
45
+ function looksLikeUtility(name) {
46
+ if (!name.includes('-')) return true;
47
+ if (/-\d/.test(name)) return true;
48
+ if (/-(?:sm|md|lg|xl|xs|2xl|3xl|px|py|full|auto|none|screen)$/.test(name)) return true;
49
+ return false;
50
+ }
51
+ const utilityCount = allClassMatches.filter(m => looksLikeUtility(m[1])).length;
52
+ const isUtilityFile = allClassMatches.length >= 5 && (utilityCount / allClassMatches.length) >= 0.70;
53
+ if (!isUtilityFile) {
54
+ const hyphenated = [];
55
+ const singleWord = [];
56
+ for (const m of allClassMatches) {
57
+ if (m[1].includes('__') || m[1].includes('--')) hyphenated.push(m[1]); // BEM names first
58
+ else if (m[1].includes('-')) hyphenated.push(m[1]); // other hyphenated component names
59
+ else singleWord.push(m[1]);
60
+ }
61
+ // Up to 8 slots: hyphenated classes (semantic) first, then single-word to fill remaining
62
+ const selected = [...hyphenated, ...singleWord].slice(0, 8);
63
+ for (const name of selected) sigs.push(`.${name}`);
45
64
  }
46
- for (const name of classNames) sigs.push(`.${name}`);
47
65
 
48
66
  return sigs.slice(0, 25);
49
67
  }