sigmap 8.8.1 → 8.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,20 @@ Format: [Semantic Versioning](https://semver.org/)
10
10
 
11
11
  ---
12
12
 
13
+ ## [8.9.1] — 2026-07-06
14
+
15
+ Patch release — **CI only; the published package is unchanged from 8.9.0.** Makes the GitHub Pages docs deploy self-healing so releases stop going red on a GitHub-side transient.
16
+
17
+ ### Fixed
18
+ - **Pages deploy retries GitHub's transient "try again later" (#451):** GitHub's Pages backend intermittently returns `Deployment failed, try again later` within seconds of a push-triggered deploy, and `actions/deploy-pages` treats that status as terminal — so every release's first Pages deploy went red and needed a manual re-run (the identical artifact re-deployed ~1 min later always succeeded). `.github/workflows/pages.yml` now lets the first attempt fail softly (`continue-on-error`), waits 60s for the backend to settle, and retries once with the same official action. The job is green if either attempt deploys, and only red if both fail (the rare stuck-sha case, which still needs a fresh sha). No new dependencies; no change to the npm package.
19
+
20
+ ## [8.9.0] — 2026-07-06
21
+
22
+ Minor release — **the watcher, detached (D1).** `sigmap --watch` keeps the signature index fresh but held a terminal in the foreground. This adds a managed background daemon so you can start it once and forget it — the roadmap's #1 friction win. Zero-dependency, shell-free, deterministic.
23
+
24
+ ### Added
25
+ - **Detached watch daemon — `sigmap daemon start|stop|status` (#447, PR #448):** runs the existing `--watch` mode as a background process, launched with `spawn(process.execPath, ['gen-context.js', '--watch'], { detached: true })` — an arguments array, never a shell command string — and tracked by a PID file under `.context/` (`daemon.pid`; output → `.context/daemon.log`). `start` does an initial generate then detaches, is idempotent (a second start reports "already running", spawns no second process), and clears a stale PID file first; `stop` sends SIGTERM and removes the PID file (a no-op when nothing is running); `status` reports the running PID and log path, exits 0 when running / 1 when not, and self-cleans a stale PID file. Every subcommand supports `--json`. New module `src/daemon/daemon.js`; CLI dispatch mirrors `sigmap mcp <sub>` and is listed in `--help`.
26
+
13
27
  ## [8.8.1] — 2026-07-05
14
28
 
15
29
  Patch release — **byte-stable context, reproducible benchmark.** Closes the determinism residual tracked in #440: `gen-context` output is now byte-identical run-to-run across all 43 benchmark repos, and the retrieval benchmark reproduces a single hit@5 (87.8%) instead of flapping 85.6–87.8%. Plus a test-count derivation fix, docs/CI serving fixes, and repo hygiene.
package/README.md CHANGED
@@ -120,8 +120,8 @@ Ask → Rank → Context → Validate → Judge → Learn
120
120
 
121
121
  <!--SM:benchmarkBlock-->
122
122
  ```
123
- Benchmark : sigmap-v8.8-main (21 repositories, including R language)
124
- Date : 2026-07-05
123
+ Benchmark : sigmap-v8.9-main (21 repositories, including R language)
124
+ Date : 2026-07-06
125
125
 
126
126
  Hit@5 : 87.8% (baseline 13.6% — 6.5× lift)
127
127
  Token reduction: 97.0% (across 21 repos)
package/gen-context.js CHANGED
@@ -2592,6 +2592,134 @@ __factories["./src/create/orchestrate"] = function(module, exports) {
2592
2592
 
2593
2593
  };
2594
2594
 
2595
+ // ── ./src/daemon/daemon ──
2596
+ __factories["./src/daemon/daemon"] = function(module, exports) {
2597
+
2598
+ /**
2599
+ * Detached watch daemon (D1).
2600
+ *
2601
+ * Runs the existing `--watch` mode as a background process so the index stays
2602
+ * fresh without holding a terminal. State lives under `.context/` (consistent
2603
+ * with session/usage): `daemon.pid` records the watcher's PID, `daemon.log`
2604
+ * captures its output.
2605
+ *
2606
+ * Zero-dependency and shell-free: the watcher is launched with
2607
+ * `spawn(process.execPath, [gen-context.js, '--watch'], { detached: true })` —
2608
+ * an arguments array, never a shell command string.
2609
+ */
2610
+
2611
+ const fs = require('fs');
2612
+ const path = require('path');
2613
+ const { spawn } = require('child_process');
2614
+
2615
+ module.exports = { start, stop, status, pidFile, logFile, isAlive, readPid };
2616
+
2617
+ function daemonDir(cwd) {
2618
+ return path.join(cwd, '.context');
2619
+ }
2620
+
2621
+ function pidFile(cwd) {
2622
+ return path.join(daemonDir(cwd), 'daemon.pid');
2623
+ }
2624
+
2625
+ function logFile(cwd) {
2626
+ return path.join(daemonDir(cwd), 'daemon.log');
2627
+ }
2628
+
2629
+ /** True if a process with this PID exists (signal 0 probes without killing). */
2630
+ function isAlive(pid) {
2631
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2632
+ try {
2633
+ process.kill(pid, 0);
2634
+ return true;
2635
+ } catch (err) {
2636
+ // EPERM = the process exists but is owned by another user — treat as alive.
2637
+ return err.code === 'EPERM';
2638
+ }
2639
+ }
2640
+
2641
+ /** Read the recorded PID, or null if the file is missing/unparseable. */
2642
+ function readPid(cwd) {
2643
+ try {
2644
+ const raw = fs.readFileSync(pidFile(cwd), 'utf8').trim();
2645
+ const pid = parseInt(raw, 10);
2646
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
2647
+ } catch (_) {
2648
+ return null;
2649
+ }
2650
+ }
2651
+
2652
+ function removePidFile(cwd) {
2653
+ try {
2654
+ fs.unlinkSync(pidFile(cwd));
2655
+ } catch (_) {}
2656
+ }
2657
+
2658
+ /**
2659
+ * @returns {{ running: boolean, pid: number|null, pidFile: string, logFile: string }}
2660
+ */
2661
+ function status(cwd) {
2662
+ const pid = readPid(cwd);
2663
+ const running = pid != null && isAlive(pid);
2664
+ // A recorded-but-dead PID is stale — clean it up so the state stays truthful.
2665
+ if (pid != null && !running) removePidFile(cwd);
2666
+ return { running, pid: running ? pid : null, pidFile: pidFile(cwd), logFile: logFile(cwd) };
2667
+ }
2668
+
2669
+ /**
2670
+ * Launch a detached `--watch` process. Idempotent: if one is already running
2671
+ * this is a no-op that reports the existing PID.
2672
+ *
2673
+ * @param {string} cwd
2674
+ * @param {{ scriptPath: string }} opts - path to gen-context.js (the CLI entry)
2675
+ * @returns {{ status: 'started'|'already', pid: number, logFile: string }}
2676
+ */
2677
+ function start(cwd, opts = {}) {
2678
+ const scriptPath = opts.scriptPath;
2679
+ if (!scriptPath) throw new Error('daemon.start requires opts.scriptPath');
2680
+
2681
+ const current = status(cwd);
2682
+ if (current.running) {
2683
+ return { status: 'already', pid: current.pid, logFile: logFile(cwd) };
2684
+ }
2685
+
2686
+ fs.mkdirSync(daemonDir(cwd), { recursive: true });
2687
+ const out = fs.openSync(logFile(cwd), 'a');
2688
+ try {
2689
+ const child = spawn(process.execPath, [scriptPath, '--watch'], {
2690
+ cwd,
2691
+ detached: true,
2692
+ stdio: ['ignore', out, out],
2693
+ });
2694
+ child.unref();
2695
+ fs.writeFileSync(pidFile(cwd), String(child.pid) + '\n');
2696
+ return { status: 'started', pid: child.pid, logFile: logFile(cwd) };
2697
+ } finally {
2698
+ try { fs.closeSync(out); } catch (_) {}
2699
+ }
2700
+ }
2701
+
2702
+ /**
2703
+ * Stop the running watcher (SIGTERM) and clear its PID file.
2704
+ *
2705
+ * @returns {{ status: 'stopped'|'not-running'|'stale', pid?: number }}
2706
+ */
2707
+ function stop(cwd) {
2708
+ const pid = readPid(cwd);
2709
+ if (pid == null) return { status: 'not-running' };
2710
+ if (!isAlive(pid)) {
2711
+ removePidFile(cwd);
2712
+ return { status: 'stale', pid };
2713
+ }
2714
+ try {
2715
+ process.kill(pid, 'SIGTERM');
2716
+ } catch (_) {}
2717
+ removePidFile(cwd);
2718
+ return { status: 'stopped', pid };
2719
+ }
2720
+
2721
+ };
2722
+
2595
2723
  // ── ./src/discovery/framework-detector ──
2596
2724
  __factories["./src/discovery/framework-detector"] = function(module, exports) {
2597
2725
 
@@ -13566,7 +13694,7 @@ __factories["./src/mcp/server"] = function(module, exports) {
13566
13694
 
13567
13695
  const SERVER_INFO = {
13568
13696
  name: 'sigmap',
13569
- version: '8.8.1',
13697
+ version: '8.9.1',
13570
13698
  description: 'SigMap MCP server — code signatures on demand',
13571
13699
  };
13572
13700
 
@@ -18158,7 +18286,7 @@ function __tryGit(args, opts = {}) {
18158
18286
  catch (_) { return ''; }
18159
18287
  }
18160
18288
 
18161
- const VERSION = '8.8.1';
18289
+ const VERSION = '8.9.1';
18162
18290
  const MARKER = '\n\n## Auto-generated signatures\n<!-- Updated by gen-context.js -->\n';
18163
18291
 
18164
18292
  function requireSourceOrBundled(key) {
@@ -19962,6 +20090,7 @@ Usage:
19962
20090
  ${cmd} --track Append run metrics to .context/usage.ndjson
19963
20091
  ${cmd} --watch Generate + watch for file changes
19964
20092
  ${cmd} --setup Generate + install git hook + watch
20093
+ ${cmd} daemon start|stop|status Run --watch as a detached background daemon
19965
20094
  ${cmd} --mcp Start MCP server on stdio
19966
20095
  ${cmd} --report Token reduction stats to stdout
19967
20096
  ${cmd} --report --json Token report as JSON (for CI; exits 1 if over budget)
@@ -21503,6 +21632,46 @@ function main() {
21503
21632
  process.exit(1);
21504
21633
  }
21505
21634
 
21635
+ // `sigmap daemon start|stop|status` — run `--watch` as a detached background
21636
+ // process (D1). PID + log live under .context/. Mirrors the `mcp` sub-dispatch.
21637
+ if (args[0] === 'daemon') {
21638
+ const daemon = requireSourceOrBundled('./src/daemon/daemon');
21639
+ const sub = args[1];
21640
+ const jsonOut = args.includes('--json');
21641
+
21642
+ if (sub === 'start') {
21643
+ const r = daemon.start(cwd, { scriptPath });
21644
+ if (jsonOut) { process.stdout.write(JSON.stringify(r) + '\n'); process.exit(0); }
21645
+ if (r.status === 'already') {
21646
+ console.log(`[sigmap] daemon already running (pid ${r.pid})`);
21647
+ } else {
21648
+ console.log(`[sigmap] daemon started (pid ${r.pid}) — watching for changes`);
21649
+ console.log(`[sigmap] logs: ${_displayPath(r.logFile, cwd)} stop with: sigmap daemon stop`);
21650
+ }
21651
+ process.exit(0);
21652
+ }
21653
+
21654
+ if (sub === 'stop') {
21655
+ const r = daemon.stop(cwd);
21656
+ if (jsonOut) { process.stdout.write(JSON.stringify(r) + '\n'); process.exit(0); }
21657
+ if (r.status === 'not-running') console.log('[sigmap] daemon not running');
21658
+ else if (r.status === 'stale') console.log(`[sigmap] removed stale pid file (pid ${r.pid} no longer alive)`);
21659
+ else console.log(`[sigmap] daemon stopped (pid ${r.pid})`);
21660
+ process.exit(0);
21661
+ }
21662
+
21663
+ if (sub === 'status') {
21664
+ const r = daemon.status(cwd);
21665
+ if (jsonOut) { process.stdout.write(JSON.stringify(r) + '\n'); process.exit(r.running ? 0 : 1); }
21666
+ if (r.running) console.log(`[sigmap] daemon running (pid ${r.pid}) — logs: ${_displayPath(r.logFile, cwd)}`);
21667
+ else console.log('[sigmap] daemon not running');
21668
+ process.exit(r.running ? 0 : 1);
21669
+ }
21670
+
21671
+ console.error('[sigmap] usage: sigmap daemon start | stop | status');
21672
+ process.exit(1);
21673
+ }
21674
+
21506
21675
  // `sigmap doctor` — diagnose config, index, freshness, coverage, and MCP
21507
21676
  // wiring; print an actionable fix for anything wrong. Exit 1 on a hard
21508
21677
  // failure (no context file / invalid config) so it is usable in CI.
package/llms-full.txt CHANGED
@@ -11,13 +11,13 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
11
11
  effect), with no LLM calls, embeddings, or vector database. Works with Claude,
12
12
  Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
13
13
 
14
- # Version: 8.8.1 | Benchmark: sigmap-v8.8-main (2026-07-05)
14
+ # Version: 8.9.1 | Benchmark: sigmap-v8.9-main (2026-07-06)
15
15
  # Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
16
16
  # Regenerate: npm run generate:llms | Validate: npm run validate:llms
17
17
 
18
18
  ---
19
19
 
20
- ## Core metrics (benchmark: sigmap-v8.8-main, 2026-07-05)
20
+ ## Core metrics (benchmark: sigmap-v8.9-main, 2026-07-06)
21
21
 
22
22
  | Metric | Without SigMap | With SigMap |
23
23
  |--------|----------------|-------------|
@@ -59,6 +59,7 @@ sigmap --format cache Also write Anthropic prompt-cache JSON
59
59
  sigmap --track Append run metrics to .context/usage.ndjson
60
60
  sigmap --watch Generate + watch for file changes
61
61
  sigmap --setup Generate + install git hook + watch
62
+ sigmap daemon start|stop|status Run --watch as a detached background daemon
62
63
  sigmap --mcp Start MCP server on stdio
63
64
  sigmap --report Token reduction stats to stdout
64
65
  sigmap --report --json Token report as JSON (for CI; exits 1 if over budget)
package/llms.txt CHANGED
@@ -11,7 +11,7 @@ ranking keeps the relevant context in scope (cutting tokens ~97% as a side
11
11
  effect), with no LLM calls, embeddings, or vector database. Works with Claude,
12
12
  Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
13
13
 
14
- # Version: 8.8.1 | Benchmark: sigmap-v8.8-main (2026-07-05)
14
+ # Version: 8.9.1 | Benchmark: sigmap-v8.9-main (2026-07-06)
15
15
  # Source: auto-generated from package.json, version.json, benchmarks/latest.json, src/mcp/tools.js, src/config/defaults.js
16
16
  # Regenerate: npm run generate:llms | Validate: npm run validate:llms
17
17
 
@@ -23,7 +23,7 @@ Cursor, GitHub Copilot, Aider, Windsurf, local LLMs, and MCP.
23
23
  - No blast-radius awareness before editing a hub file — `--impact` shows every file a change touches.
24
24
  - Pasted stack traces, CI logs, and JSON bloat the prompt — `squeeze` minimizes them and enriches the top frame from the symbol index.
25
25
 
26
- ## Core metrics (benchmark: sigmap-v8.8-main, 2026-07-05)
26
+ ## Core metrics (benchmark: sigmap-v8.9-main, 2026-07-06)
27
27
 
28
28
  - hit@5 retrieval: 87.8% vs 13.6% random baseline (6.5× lift)
29
29
  - Token reduction: 97.0% average across benchmark repos
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap",
3
- "version": "8.8.1",
3
+ "version": "8.9.1",
4
4
  "description": "97% token reduction for AI coding. Extracts function & class signatures with TF-IDF ranking to feed only the right files to Claude, Cursor, Copilot, Aider, Windsurf, local LLMs & MCP. Zero dependencies, runs offline via npx.",
5
5
  "main": "packages/core/index.js",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigmap-cli",
3
- "version": "8.8.1",
3
+ "version": "8.9.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": "8.8.1",
3
+ "version": "8.9.1",
4
4
  "description": "SigMap core library — zero-dependency code signature extraction, retrieval, and security scanning",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Detached watch daemon (D1).
5
+ *
6
+ * Runs the existing `--watch` mode as a background process so the index stays
7
+ * fresh without holding a terminal. State lives under `.context/` (consistent
8
+ * with session/usage): `daemon.pid` records the watcher's PID, `daemon.log`
9
+ * captures its output.
10
+ *
11
+ * Zero-dependency and shell-free: the watcher is launched with
12
+ * `spawn(process.execPath, [gen-context.js, '--watch'], { detached: true })` —
13
+ * an arguments array, never a shell command string.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { spawn } = require('child_process');
19
+
20
+ module.exports = { start, stop, status, pidFile, logFile, isAlive, readPid };
21
+
22
+ function daemonDir(cwd) {
23
+ return path.join(cwd, '.context');
24
+ }
25
+
26
+ function pidFile(cwd) {
27
+ return path.join(daemonDir(cwd), 'daemon.pid');
28
+ }
29
+
30
+ function logFile(cwd) {
31
+ return path.join(daemonDir(cwd), 'daemon.log');
32
+ }
33
+
34
+ /** True if a process with this PID exists (signal 0 probes without killing). */
35
+ function isAlive(pid) {
36
+ if (!Number.isInteger(pid) || pid <= 0) return false;
37
+ try {
38
+ process.kill(pid, 0);
39
+ return true;
40
+ } catch (err) {
41
+ // EPERM = the process exists but is owned by another user — treat as alive.
42
+ return err.code === 'EPERM';
43
+ }
44
+ }
45
+
46
+ /** Read the recorded PID, or null if the file is missing/unparseable. */
47
+ function readPid(cwd) {
48
+ try {
49
+ const raw = fs.readFileSync(pidFile(cwd), 'utf8').trim();
50
+ const pid = parseInt(raw, 10);
51
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
52
+ } catch (_) {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ function removePidFile(cwd) {
58
+ try {
59
+ fs.unlinkSync(pidFile(cwd));
60
+ } catch (_) {}
61
+ }
62
+
63
+ /**
64
+ * @returns {{ running: boolean, pid: number|null, pidFile: string, logFile: string }}
65
+ */
66
+ function status(cwd) {
67
+ const pid = readPid(cwd);
68
+ const running = pid != null && isAlive(pid);
69
+ // A recorded-but-dead PID is stale — clean it up so the state stays truthful.
70
+ if (pid != null && !running) removePidFile(cwd);
71
+ return { running, pid: running ? pid : null, pidFile: pidFile(cwd), logFile: logFile(cwd) };
72
+ }
73
+
74
+ /**
75
+ * Launch a detached `--watch` process. Idempotent: if one is already running
76
+ * this is a no-op that reports the existing PID.
77
+ *
78
+ * @param {string} cwd
79
+ * @param {{ scriptPath: string }} opts - path to gen-context.js (the CLI entry)
80
+ * @returns {{ status: 'started'|'already', pid: number, logFile: string }}
81
+ */
82
+ function start(cwd, opts = {}) {
83
+ const scriptPath = opts.scriptPath;
84
+ if (!scriptPath) throw new Error('daemon.start requires opts.scriptPath');
85
+
86
+ const current = status(cwd);
87
+ if (current.running) {
88
+ return { status: 'already', pid: current.pid, logFile: logFile(cwd) };
89
+ }
90
+
91
+ fs.mkdirSync(daemonDir(cwd), { recursive: true });
92
+ const out = fs.openSync(logFile(cwd), 'a');
93
+ try {
94
+ const child = spawn(process.execPath, [scriptPath, '--watch'], {
95
+ cwd,
96
+ detached: true,
97
+ stdio: ['ignore', out, out],
98
+ });
99
+ child.unref();
100
+ fs.writeFileSync(pidFile(cwd), String(child.pid) + '\n');
101
+ return { status: 'started', pid: child.pid, logFile: logFile(cwd) };
102
+ } finally {
103
+ try { fs.closeSync(out); } catch (_) {}
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Stop the running watcher (SIGTERM) and clear its PID file.
109
+ *
110
+ * @returns {{ status: 'stopped'|'not-running'|'stale', pid?: number }}
111
+ */
112
+ function stop(cwd) {
113
+ const pid = readPid(cwd);
114
+ if (pid == null) return { status: 'not-running' };
115
+ if (!isAlive(pid)) {
116
+ removePidFile(cwd);
117
+ return { status: 'stale', pid };
118
+ }
119
+ try {
120
+ process.kill(pid, 'SIGTERM');
121
+ } catch (_) {}
122
+ removePidFile(cwd);
123
+ return { status: 'stopped', pid };
124
+ }
package/src/mcp/server.js CHANGED
@@ -18,7 +18,7 @@ const { readContext, searchSignatures, getMap, createCheckpoint, getRouting, exp
18
18
 
19
19
  const SERVER_INFO = {
20
20
  name: 'sigmap',
21
- version: '8.8.1',
21
+ version: '8.9.1',
22
22
  description: 'SigMap MCP server — code signatures on demand',
23
23
  };
24
24