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 +64 -2
- package/README.md +53 -41
- package/gen-context.js +162 -50
- package/gen-project-map.js +17 -4
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/config/defaults.js +7 -1
- package/src/extractors/coverage.js +27 -5
- package/src/extractors/css.js +24 -6
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.
|
|
73
|
+
## [3.2.1] — 2026-04-07 — Patch: 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`
|
|
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
|
[](https://www.npmjs.com/package/sigmap)
|
|
14
|
-
[](https://github.com/manojmallick/sigmap/actions/workflows/ci.yml)
|
|
15
17
|
[](package.json)
|
|
16
18
|
[](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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
549
|
-
|
|
557
|
+
sigmap --health Composite health score (0–100, grade A–D)
|
|
558
|
+
sigmap --health --json Machine-readable health JSON
|
|
550
559
|
|
|
551
|
-
|
|
552
|
-
|
|
560
|
+
sigmap --suggest-tool "<task>" Recommend model tier for a task
|
|
561
|
+
sigmap --suggest-tool "<task>" --json Machine-readable tier recommendation
|
|
553
562
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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.
|
|
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
|
-
|
|
5280
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
6371
|
+
${header}
|
|
6278
6372
|
Zero-dependency AI context engine
|
|
6279
6373
|
|
|
6280
6374
|
Usage:
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
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 {
|
package/gen-project-map.js
CHANGED
|
@@ -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
|
-
`
|
|
47
|
+
`SigMap project map generator v${VERSION} (${cmd})`,
|
|
35
48
|
'',
|
|
36
49
|
'Usage:',
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
package/src/config/defaults.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
38
|
-
|
|
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(/\
|
|
42
|
-
if (m[1]
|
|
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
|
|
package/src/extractors/css.js
CHANGED
|
@@ -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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
}
|