sigmap 3.2.1 → 3.3.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
@@ -8,7 +8,57 @@ Format: [Semantic Versioning](https://semver.org/)
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ---
12
+
13
+ ## [3.3.1] — 2026-04-10 — Patch: `--each --adapter` flag combination
14
+
11
15
  ### Fixed
16
+ - **`--each --adapter <name>` now works correctly** · [#37](https://github.com/manojmallick/sigmap/issues/37)
17
+ - Running `sigmap --each --adapter claude` (or any adapter) from a parent directory containing multiple git repos now correctly writes the chosen adapter output (e.g. `CLAUDE.md`) inside each sub-repo.
18
+ - Root cause: the `--adapter` handler ran before `--each` in `main()`, so `--each` was never reached when both flags were supplied together. The `--each` block is now evaluated first.
19
+ - `runEach()` accepts an optional `adapterOverride` parameter that merges `outputs`/`adapters` into each sub-repo's config before calling `runGenerate`, mirroring how the standalone `--adapter` flag works.
20
+ - Invalid adapter names passed alongside `--each` now exit non-zero with a clear error message listing valid adapters.
21
+
22
+ ---
23
+
24
+ ## [3.3.0] — 2026-04-08 — Context-Aware CLI & Command Switcher
25
+
26
+ ### Added
27
+ - **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:
28
+ - `npx sigmap --help` shows `npx sigmap <flag>`
29
+ - `sigmap --help` shows `sigmap <flag>`
30
+ - `gen-context --help` shows `gen-context <flag>`
31
+ - `node gen-context.js --help` shows `node gen-context.js <flag>` (unchanged for local users)
32
+ - Detection uses `process.argv[1]` path analysis (npx cache path, basename without `.js`, fallback)
33
+ - **`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.
34
+ - **`docs/readmes/`** — `vscode-extension.md` and `jetbrains-plugin.md` added for docs site cross-linking
35
+ - **`gen-context.config.json`** — example config committed alongside the repo for reference
36
+ - **Gemini adapter context file** — `.github/gemini-context.md` now generated alongside the copilot instructions file
37
+ - **SEO improvements across all docs pages** — structured data, canonical tags, improved meta descriptions, and `sitemap.xml` updated to v3.3.0
38
+
39
+ ### Added (from `fix/defaults-css-coverage-budget-36` · #38)
40
+ - **`--each` flag — multi-repo parent directory support** · [#37](https://github.com/manojmallick/sigmap/issues/37)
41
+ - 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.
42
+ - 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`).
43
+ - 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.
44
+ - Summary printed at the end: `[sigmap] --each: done — 3 succeeded`.
45
+ - Distinct from `--monorepo` (which processes workspace packages inside a single repo); `--each` targets sibling repos under a shared parent directory.
46
+
47
+ ### Fixed
48
+ - **Default excludes expanded + `changesCommits` corrected** · [#36](https://github.com/manojmallick/sigmap/issues/36)
49
+ - `changesCommits` default raised from `5` to `10` to match the documented recommended value.
50
+ - 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.
51
+ - **CSS extractor: utility-class noise elimination** · [#36](https://github.com/manojmallick/sigmap/issues/36)
52
+ - 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`.
53
+ - 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.
54
+ - **`testCoverage` false-positive coverage markers eliminated** · [#36](https://github.com/manojmallick/sigmap/issues/36)
55
+ - 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.
56
+ - Index now only includes tokens extracted from test name strings (`it('...')`, `test('...')`, `describe('...')`) and identifiers directly invoked inside `expect(fn())` / `assert(fn())` calls.
57
+ - **Token budget: mock/fixture files drop before test files** · [#36](https://github.com/manojmallick/sigmap/issues/36)
58
+ - 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.
59
+ - Fixed `applyTokenBudget` loop direction: generated/mock/test files now drop first (as intended) rather than source files being dropped first.
60
+ - **`--monorepo` now respects configured output adapter** · [#39](https://github.com/manojmallick/sigmap/issues/39)
61
+ - Removed hardcoded `outputs: ['claude']` override — `--monorepo` now inherits `outputs` from the root config, defaulting to `copilot` (writes `copilot-instructions.md` per package).
12
62
  - **IDE command resolution parity (VS Code/Open VSX/JetBrains)** · [#34](https://github.com/manojmallick/sigmap/issues/34)
13
63
  - Unified resolver now checks both `sigmap` and `gen-context` executables with consistent fallback order.
14
64
  - 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 +70,19 @@ Format: [Semantic Versioning](https://semver.org/)
20
70
 
21
71
  ---
22
72
 
23
- ## [3.2.0] — PlannedPhase A: Cross-Platform Standalone Binaries
73
+ ## [3.2.1] — 2026-04-07Patch: IDE Command Resolution & Plugin Parity
74
+
75
+ ### Added
76
+ - **IDE command resolution parity (VS Code / Open VSX / JetBrains)** · [#34](https://github.com/manojmallick/sigmap/issues/34)
77
+ - Unified resolver checks both `sigmap` and `gen-context` executables with consistent fallback order
78
+ - 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)
79
+ - JetBrains plugin resolves commands more reliably outside Node-only projects and provides OS-aware install guidance when command lookup fails
80
+ - **`scripts/sync-versions.mjs`** — one-shot script to bump version across all package manifests and `gen-context.js` in sync
81
+ - **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`)
82
+
83
+ ---
84
+
85
+ ## [3.2.0] — 2026-04-07 — Cross-Platform Standalone Binaries
24
86
 
25
87
  ### Added
26
88
  - **Standalone binaries** — macOS (arm64 + x64), Linux x64, Windows x64 built via Node.js SEA
@@ -35,7 +97,7 @@ Format: [Semantic Versioning](https://semver.org/)
35
97
 
36
98
  ### Technical
37
99
  - 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
100
+ - `gen-context.js` updated to include previously-missing `src/` modules (`todos`, `coverage`, `prdiff`) in the SEA bundle; `requireSourceOrBundled()` fallback remains SEA-compatible
39
101
  - Binary builds run natively per OS in GHA (no cross-compilation)
40
102
  - `release-attach` job waits for the npm-publish Release to exist before uploading binary assets
41
103
 
package/README.md CHANGED
@@ -9,9 +9,11 @@
9
9
  Multiple install options. Zero runtime dependencies. Requires only Node.js 18+.
10
10
  </p>
11
11
 
12
+ <img src="docs/sigmap-banner.png" alt="SigMap — AI context engine" width="700">
13
+
12
14
  <!-- Status -->
13
15
  [![npm version](https://img.shields.io/npm/v/sigmap?color=7c6af7&label=latest&logo=npm)](https://www.npmjs.com/package/sigmap)
14
- [![Tests](https://img.shields.io/badge/tests-340%20passing-22c55e)](https://github.com/manojmallick/sigmap/tree/main/test)
16
+ [![CI](https://github.com/manojmallick/sigmap/actions/workflows/ci.yml/badge.svg)](https://github.com/manojmallick/sigmap/actions/workflows/ci.yml)
15
17
  [![Zero deps](https://img.shields.io/badge/dependencies-zero-22c55e)](package.json)
16
18
  [![Last commit](https://img.shields.io/github/last-commit/manojmallick/sigmap?color=7c6af7)](https://github.com/manojmallick/sigmap/commits/main)
17
19
 
@@ -39,7 +41,7 @@
39
41
  |---|---|
40
42
  | [What it does](#-what-it-does) | Token reduction table, pipeline overview |
41
43
  | [Quick start](#-quick-start) | Install (binary or npm), generate in 60 seconds |
42
- | [Standalone binaries](docs/binaries.md) | macOS, Linux, Windows — no Node required |
44
+ | [Standalone binaries](docs/readmes/binaries.md) | macOS, Linux, Windows — no Node required |
43
45
  | [VS Code extension](#-vs-code-extension) | Status bar, stale alerts, commands |
44
46
  | [JetBrains plugin](#-jetbrains-plugin) | IntelliJ IDEA, WebStorm, PyCharm support |
45
47
  | [Languages supported](#-languages-supported) | 21 languages |
@@ -53,7 +55,7 @@
53
55
  | [Project structure](#-project-structure) | File-by-file map |
54
56
  | [Principles](#-principles) | Design decisions |
55
57
 
56
- > 📖 **New to SigMap?** Read the **[Complete Getting Started Guide](docs/GETTING_STARTED.md)** — token savings walkthrough, every command, VS Code plugin, and CI setup.
58
+ > 📖 **New to SigMap?** Read the **[Complete Getting Started Guide](docs/readmes/GETTING_STARTED.md)** — token savings walkthrough, every command, VS Code plugin, and CI setup.
57
59
 
58
60
  ---
59
61
 
@@ -198,7 +200,7 @@ shasum -a 256 sigmap-darwin-arm64
198
200
  # Compare with sigmap-checksums.txt
199
201
  ```
200
202
 
201
- Full guide: [docs/binaries.md](docs/binaries.md)
203
+ Full guide: [docs/readmes/binaries.md](docs/readmes/binaries.md)
202
204
 
203
205
  </details>
204
206
 
@@ -327,7 +329,7 @@ chmod +x ./sigmap-darwin-arm64
327
329
  ./sigmap-darwin-arm64
328
330
  ```
329
331
 
330
- See [docs/binaries.md](docs/binaries.md) for Gatekeeper / SmartScreen notes and checksum verification.
332
+ See [docs/readmes/binaries.md](docs/readmes/binaries.md) for Gatekeeper / SmartScreen notes and checksum verification.
331
333
 
332
334
  **npm** (requires Node.js 18+):
333
335
 
@@ -402,7 +404,7 @@ The `jetbrains-plugin/` directory contains a Kotlin-based plugin for JetBrains I
402
404
 
403
405
  Compatible with **IntelliJ IDEA 2024.1+** (Community & Ultimate), **WebStorm**, **PyCharm**, **GoLand**, **RubyMine**, **PhpStorm**, and all other IntelliJ-based IDEs.
404
406
 
405
- **Install:** [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/31109-sigmap--ai-context-engine/) | [Manual setup guide](docs/JETBRAINS_SETUP.md)
407
+ **Install:** [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/31109-sigmap--ai-context-engine/) | [Manual setup guide](docs/readmes/JETBRAINS_SETUP.md)
406
408
 
407
409
  ---
408
410
 
@@ -484,7 +486,7 @@ One `.github/context-<module>.md` per top-level source directory, plus a tiny ov
484
486
 
485
487
  Recently committed files are **hot** (auto-injected). Everything else is **cold** (on-demand via MCP). Best reduction available — ~200 tokens always-on.
486
488
 
487
- 📖 Full guide: [docs/CONTEXT_STRATEGIES.md](docs/CONTEXT_STRATEGIES.md) — decision tree, scenario comparisons, migration steps.
489
+ 📖 Full guide: [docs/readmes/CONTEXT_STRATEGIES.md](docs/readmes/CONTEXT_STRATEGIES.md) — decision tree, scenario comparisons, migration steps.
488
490
 
489
491
  ---
490
492
 
@@ -511,7 +513,7 @@ node gen-context.js --mcp
511
513
 
512
514
  Reads files on every call — no stale state, no restart needed.
513
515
 
514
- 📖 Setup guide: [docs/MCP_SETUP.md](docs/MCP_SETUP.md)
516
+ 📖 Setup guide: [docs/readmes/MCP_SETUP.md](docs/readmes/MCP_SETUP.md)
515
517
 
516
518
  ---
517
519
 
@@ -519,47 +521,57 @@ Reads files on every call — no stale state, no restart needed.
519
521
 
520
522
  > See [CHANGELOG.md](CHANGELOG.md) for the full history.
521
523
 
524
+ All flags are the same regardless of how you invoke SigMap — swap the prefix to match your install:
525
+
526
+ > `sigmap` · `npx sigmap` · `gen-context` · `node gen-context.js`
527
+
528
+ <details>
529
+ <summary><strong>All flags</strong></summary>
530
+
522
531
  ```
523
- node gen-context.js Generate once and exit
524
- node gen-context.js --watch Generate and watch for file changes
525
- node gen-context.js --setup Generate + install git hook + start watcher
526
- node gen-context.js --diff Generate context for git-changed files only
527
- node gen-context.js --diff --staged Staged files only (pre-commit check)
528
- node gen-context.js --mcp Start MCP server on stdio
532
+ sigmap Generate once and exit
533
+ sigmap --watch Generate and watch for file changes
534
+ sigmap --setup Generate + install git hook + start watcher
535
+ sigmap --diff Generate context for git-changed files only
536
+ sigmap --diff --staged Staged files only (pre-commit check)
537
+ sigmap --mcp Start MCP server on stdio
529
538
 
530
- node gen-context.js --query "<text>" Rank files by relevance to a query
531
- node gen-context.js --query "<text>" --json Ranked results as JSON
532
- node gen-context.js --query "<text>" --top <n> Limit results to top N files (default 10)
539
+ sigmap --query "<text>" Rank files by relevance to a query
540
+ sigmap --query "<text>" --json Ranked results as JSON
541
+ sigmap --query "<text>" --top <n> Limit results to top N files (default 10)
533
542
 
534
- node gen-context.js --analyze Per-file breakdown (sigs / tokens / extractor / coverage)
535
- node gen-context.js --analyze --json Analysis as JSON
536
- node gen-context.js --analyze --slow Include extraction timing per file
537
- node gen-context.js --diagnose-extractors Self-test all 21 extractors against fixtures
543
+ sigmap --analyze Per-file breakdown (sigs / tokens / extractor / coverage)
544
+ sigmap --analyze --json Analysis as JSON
545
+ sigmap --analyze --slow Include extraction timing per file
546
+ sigmap --diagnose-extractors Self-test all 21 extractors against fixtures
538
547
 
539
- node gen-context.js --benchmark Run retrieval quality benchmark (hit@5 / MRR)
540
- node gen-context.js --benchmark --json Benchmark results as JSON
541
- node gen-context.js --eval Alias for --benchmark
548
+ sigmap --benchmark Run retrieval quality benchmark (hit@5 / MRR)
549
+ sigmap --benchmark --json Benchmark results as JSON
550
+ sigmap --eval Alias for --benchmark
542
551
 
543
- node gen-context.js --report Token reduction stats
544
- node gen-context.js --report --json Structured JSON report (exits 1 if over budget)
545
- node gen-context.js --report --history Usage log summary
546
- node gen-context.js --report --history --json Usage history as JSON
552
+ sigmap --report Token reduction stats
553
+ sigmap --report --json Structured JSON report (exits 1 if over budget)
554
+ sigmap --report --history Usage log summary
555
+ sigmap --report --history --json Usage history as JSON
547
556
 
548
- node gen-context.js --health Composite health score (0–100, grade A–D)
549
- node gen-context.js --health --json Machine-readable health JSON
557
+ sigmap --health Composite health score (0–100, grade A–D)
558
+ sigmap --health --json Machine-readable health JSON
550
559
 
551
- node gen-context.js --suggest-tool "<task>" Recommend model tier for a task
552
- node gen-context.js --suggest-tool "<task>" --json Machine-readable tier recommendation
560
+ sigmap --suggest-tool "<task>" Recommend model tier for a task
561
+ sigmap --suggest-tool "<task>" --json Machine-readable tier recommendation
553
562
 
554
- node gen-context.js --monorepo Per-package context for monorepos
555
- node gen-context.js --routing Include model routing hints in output
556
- node gen-context.js --format cache Write Anthropic prompt-cache JSON
557
- node gen-context.js --track Append run metrics to .context/usage.ndjson
563
+ sigmap --monorepo Per-package context for monorepos (packages/, apps/, services/)
564
+ sigmap --each Process each sub-repo under a parent directory
565
+ sigmap --routing Include model routing hints in output
566
+ sigmap --format cache Write Anthropic prompt-cache JSON
567
+ sigmap --track Append run metrics to .context/usage.ndjson
558
568
 
559
- node gen-context.js --init Write config + .contextignore scaffold
560
- node gen-context.js --version Version string
561
- node gen-context.js --help Usage information
569
+ sigmap --init Write config + .contextignore scaffold
570
+ sigmap --version Version string
571
+ sigmap --help Usage information
562
572
  ```
573
+ </details>
574
+
563
575
 
564
576
  ### Task classification — `--suggest-tool`
565
577
 
@@ -688,7 +700,7 @@ Copy `examples/self-healing-github-action.yml` to `.github/workflows/` to auto-r
688
700
  run: node gen-context.js
689
701
  ```
690
702
 
691
- 📖 Full guide: [docs/ENTERPRISE_SETUP.md](docs/ENTERPRISE_SETUP.md)
703
+ 📖 Full guide: [docs/readmes/ENTERPRISE_SETUP.md](docs/readmes/ENTERPRISE_SETUP.md)
692
704
 
693
705
  ### Prompt caching — 60% API cost reduction
694
706
 
@@ -698,7 +710,7 @@ node gen-context.js --format cache
698
710
  # Format: { type: 'text', text: '...', cache_control: { type: 'ephemeral' } }
699
711
  ```
700
712
 
701
- 📖 Full guide: [docs/REPOMIX_CACHE.md](docs/REPOMIX_CACHE.md)
713
+ 📖 Full guide: [docs/readmes/REPOMIX_CACHE.md](docs/readmes/REPOMIX_CACHE.md)
702
714
 
703
715
  ---
704
716
 
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.1';
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,73 @@ 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, adapterOverride) {
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
+ // --adapter override: apply on top of per-repo config
6280
+ if (adapterOverride) {
6281
+ repoConfig = Object.assign({}, repoConfig, {
6282
+ outputs: adapterOverride === 'claude' ? ['claude'] : [adapterOverride],
6283
+ adapters: [adapterOverride],
6284
+ });
6285
+ }
6286
+ console.warn(`[sigmap] --each: processing ${name} …`);
6287
+ try {
6288
+ runGenerate(repoDir, repoConfig, false);
6289
+ ok++;
6290
+ } catch (err) {
6291
+ console.warn(`[sigmap] --each: FAILED for ${name}: ${err.message}`);
6292
+ failed++;
6293
+ }
6294
+ }
6295
+ console.warn(`[sigmap] --each: done — ${ok} succeeded${failed > 0 ? `, ${failed} failed` : ''}`);
6222
6296
  }
6223
6297
 
6224
6298
  // ---------------------------------------------------------------------------
@@ -6272,50 +6346,71 @@ function resolveProjectRoot(startDir) {
6272
6346
  return startDir;
6273
6347
  }
6274
6348
 
6275
- function printHelp() {
6349
+ function detectInvokedAs() {
6350
+ const argv1 = process.argv[1] || '';
6351
+ const base = path.basename(argv1);
6352
+ const baseNoExt = base.endsWith('.js') ? base.slice(0, -3) : base;
6353
+ // npx: the script path goes through an _npx cache directory
6354
+ if (argv1.includes('/_npx/') || argv1.includes('\\_npx\\') ||
6355
+ argv1.includes('/.npm/_') || argv1.includes('\\.npm\\_')) {
6356
+ return 'npx sigmap';
6357
+ }
6358
+ // globally-installed bin aliases (no .js extension)
6359
+ if (baseNoExt === 'sigmap') return 'sigmap';
6360
+ if (baseNoExt === 'gen-context') return 'gen-context';
6361
+ // default: local file invocation
6362
+ return 'node gen-context.js';
6363
+ }
6364
+
6365
+ function printHelp(cmd) {
6366
+ cmd = cmd || 'node gen-context.js';
6367
+ const header = cmd === 'node gen-context.js'
6368
+ ? `SigMap — gen-context.js v${VERSION}`
6369
+ : `SigMap v${VERSION} (${cmd})`;
6276
6370
  console.log(`
6277
- SigMap — gen-context.js v${VERSION}
6371
+ ${header}
6278
6372
  Zero-dependency AI context engine
6279
6373
 
6280
6374
  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
6375
+ ${cmd} Generate context once and exit
6376
+ ${cmd} --monorepo Generate per-package context (monorepo)
6377
+ ${cmd} --each Run for every repo in the current directory
6378
+ ${cmd} --routing Include model routing hints in output
6379
+ ${cmd} --format cache Also write Anthropic prompt-cache JSON
6380
+ ${cmd} --track Append run metrics to .context/usage.ndjson
6381
+ ${cmd} --watch Generate + watch for file changes
6382
+ ${cmd} --setup Generate + install git hook + watch
6383
+ ${cmd} --mcp Start MCP server on stdio
6384
+ ${cmd} --report Token reduction stats to stdout
6385
+ ${cmd} --report --json Token report as JSON (for CI; exits 1 if over budget)
6386
+ ${cmd} --report --history Print usage log summary from .context/usage.ndjson
6387
+ ${cmd} --report --history --chart Include inline SVG charts + Unicode sparklines
6388
+ ${cmd} --dashboard Write benchmarks/reports/dashboard.html
6389
+ ${cmd} --suggest-tool "<task>" Recommend model tier for a task description
6390
+ ${cmd} --suggest-tool "<task>" --json Machine-readable tier recommendation
6391
+ ${cmd} --health Print composite health score
6392
+ ${cmd} --health --json Machine-readable health score
6393
+ ${cmd} --diff Generate context for git-changed files only
6394
+ ${cmd} --diff <base-ref> Generate context + structural diff vs base ref (e.g. main)
6395
+ ${cmd} --diff --staged Generate context for staged files only
6396
+ ${cmd} --benchmark Run retrieval benchmark (benchmarks/tasks/retrieval.jsonl)
6397
+ ${cmd} --adapter <name> Generate for a specific adapter only (v3.0+)
6398
+ ${cmd} --adapter <name> --json Show adapter output path as JSON
6399
+ ${cmd} --benchmark --json Benchmark results as JSON
6400
+ ${cmd} --eval Alias for --benchmark
6401
+ ${cmd} --analyze Per-file breakdown: sigs, tokens, extractor, coverage
6402
+ ${cmd} --analyze --json Breakdown as JSON
6403
+ ${cmd} --analyze --slow Re-time each extractor; flag files >50ms
6404
+ ${cmd} --diagnose-extractors Run all 21 extractors vs fixtures; show pass/fail + diff
6405
+ ${cmd} --query "<text>" Rank files by relevance to a query
6406
+ ${cmd} --query "<text>" --json Ranked results as JSON
6407
+ ${cmd} --query "<text>" --top <n> Limit results to top N files (default 10)
6408
+ ${cmd} --impact <file> Show every file impacted by changing <file>
6409
+ ${cmd} --impact <file> --json Impact as JSON {changed, direct, transitive, tests, routes}
6410
+ ${cmd} --impact <file> --depth <n> BFS depth limit (default 3, 0=unlimited)
6411
+ ${cmd} --init Write example config + .contextignore scaffold
6412
+ ${cmd} --help Show this message
6413
+ ${cmd} --version Show version
6319
6414
 
6320
6415
  Strategies (set via config "strategy" key):
6321
6416
  "full" Single file, all signatures. Works everywhere. (default)
@@ -6380,7 +6475,7 @@ function main() {
6380
6475
  }
6381
6476
 
6382
6477
  if (args.includes('--help') || args.includes('-h')) {
6383
- printHelp();
6478
+ printHelp(detectInvokedAs());
6384
6479
  process.exit(0);
6385
6480
  }
6386
6481
 
@@ -6660,6 +6755,23 @@ function main() {
6660
6755
  process.exit(0);
6661
6756
  }
6662
6757
 
6758
+ // ── --each [--adapter <name>] ────────────────────────────────────────────
6759
+ // Must be checked before --adapter so that --each --adapter <name> is
6760
+ // handled here (per-repo) rather than running a single generate on the
6761
+ // parent directory.
6762
+ if (args.includes('--each')) {
6763
+ const VALID_ADAPTERS = ['copilot', 'claude', 'cursor', 'windsurf', 'openai', 'gemini'];
6764
+ const adpIdx = args.indexOf('--adapter');
6765
+ const adapterOverride = adpIdx >= 0 ? (args[adpIdx + 1] || '').trim().toLowerCase() : null;
6766
+ if (adapterOverride && !VALID_ADAPTERS.includes(adapterOverride)) {
6767
+ console.error(`[sigmap] --each: unknown adapter "${adapterOverride}"`);
6768
+ console.error(` Valid adapters: ${VALID_ADAPTERS.join(', ')}`);
6769
+ process.exit(1);
6770
+ }
6771
+ runEach(cwd, config, adapterOverride || null);
6772
+ process.exit(0);
6773
+ }
6774
+
6663
6775
  // ── --adapter <name> ───────────────────────────────────────────────────────
6664
6776
  if (args.includes('--adapter')) {
6665
6777
  try {
@@ -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.1",
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.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": "3.2.1",
3
+ "version": "3.3.1",
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
  }