sigmap 8.8.1 → 8.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/gen-context.js +171 -2
- package/llms-full.txt +3 -2
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/src/daemon/daemon.js +124 -0
- package/src/mcp/server.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,13 @@ Format: [Semantic Versioning](https://semver.org/)
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## [8.9.0] — 2026-07-06
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **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`.
|
|
19
|
+
|
|
13
20
|
## [8.8.1] — 2026-07-05
|
|
14
21
|
|
|
15
22
|
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.
|
|
124
|
-
Date : 2026-07-
|
|
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.
|
|
13697
|
+
version: '8.9.0',
|
|
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.
|
|
18289
|
+
const VERSION = '8.9.0';
|
|
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.
|
|
14
|
+
# Version: 8.9.0 | 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.
|
|
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.
|
|
14
|
+
# Version: 8.9.0 | 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.
|
|
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.
|
|
3
|
+
"version": "8.9.0",
|
|
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": {
|
|
@@ -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