@mnemonik/scanner 5.119.5 → 5.131.2

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.
@@ -0,0 +1 @@
1
+ export declare function runDoctor(): Promise<void>;
package/dist/doctor.js ADDED
@@ -0,0 +1,201 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync, readFileSync, readdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { fileURLToPath } from 'url';
6
+ /**
7
+ * `mnemonik-scanner doctor` — the guard that turns the prose upgrade procedure
8
+ * into an executable check. It asserts the invariants that every past botched
9
+ * reinstall violated, and prints the one blessed remediation. Any single ✗
10
+ * exits non-zero so a wrapper (or a human) can react.
11
+ *
12
+ * Invariants:
13
+ * 1. Exactly one `mnemonik-scanner` on PATH, inside the user npm prefix
14
+ * (a second copy under /usr is the classic PATH-shadow version skew).
15
+ * 2. If a systemd user unit exists, it is active and its ExecStart points at
16
+ * that same binary.
17
+ * 3. Exactly one live daemon, and under systemd its PID == the unit MainPID
18
+ * (a stray `node …/scanner/dist/index.js` is a duplicate local-build daemon).
19
+ * 4. No leftover legacy per-project daemons.
20
+ * 5. Installed version is not behind npm latest (warning, not failure).
21
+ */
22
+ const HOME = homedir();
23
+ const MNEMONIK_DIR = join(HOME, '.mnemonik');
24
+ const PID_FILE = join(MNEMONIK_DIR, 'daemon.pid');
25
+ const LEGACY_DAEMONS_DIR = join(MNEMONIK_DIR, 'daemons');
26
+ const UNIT_FILE = join(HOME, '.config/systemd/user/mnemonik-scanner.service');
27
+ function sh(cmd, args, timeoutMs = 5000) {
28
+ try {
29
+ return execFileSync(cmd, args, {
30
+ encoding: 'utf-8',
31
+ stdio: ['ignore', 'pipe', 'ignore'],
32
+ timeout: timeoutMs,
33
+ }).trim();
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ function pidAlive(pid) {
40
+ try {
41
+ process.kill(pid, 0);
42
+ return true;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ function installedVersion() {
49
+ try {
50
+ const here = fileURLToPath(new URL('.', import.meta.url));
51
+ const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf-8'));
52
+ return pkg.version ?? null;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ export async function runDoctor() {
59
+ const checks = [];
60
+ // 1. Binary uniqueness + location.
61
+ const prefix = sh('npm', ['config', 'get', 'prefix']);
62
+ const whichOut = sh('which', ['-a', 'mnemonik-scanner']);
63
+ const binPaths = whichOut ? whichOut.split('\n').map((s) => s.trim()).filter(Boolean) : [];
64
+ if (binPaths.length === 0) {
65
+ checks.push({ level: 'fail', label: 'binary on PATH', detail: 'mnemonik-scanner not found on PATH' });
66
+ }
67
+ else if (binPaths.length > 1) {
68
+ checks.push({
69
+ level: 'fail',
70
+ label: 'single binary on PATH',
71
+ detail: `found ${binPaths.length} copies (PATH shadow → version skew):\n ${binPaths.join('\n ')}`,
72
+ });
73
+ }
74
+ else if (prefix && !binPaths[0].startsWith(prefix)) {
75
+ checks.push({
76
+ level: 'warn',
77
+ label: 'binary in user npm prefix',
78
+ detail: `${binPaths[0]} is not under npm prefix ${prefix}`,
79
+ });
80
+ }
81
+ else {
82
+ checks.push({ level: 'ok', label: 'single binary on PATH', detail: binPaths[0] });
83
+ }
84
+ const binPath = binPaths.length === 1 ? binPaths[0] : null;
85
+ // 2. systemd unit health + ExecStart match.
86
+ let unitMainPid = null;
87
+ const hasUnit = existsSync(UNIT_FILE);
88
+ if (hasUnit) {
89
+ const show = sh('systemctl', [
90
+ '--user',
91
+ 'show',
92
+ 'mnemonik-scanner',
93
+ '-p',
94
+ 'ActiveState',
95
+ '-p',
96
+ 'MainPID',
97
+ '-p',
98
+ 'ExecStart',
99
+ ]);
100
+ const active = /ActiveState=active/.test(show ?? '');
101
+ const mainPidMatch = /MainPID=(\d+)/.exec(show ?? '');
102
+ unitMainPid = mainPidMatch ? parseInt(mainPidMatch[1], 10) : null;
103
+ const execMatch = /path=([^\s;]+)/.exec(show ?? '');
104
+ const execPath = execMatch ? execMatch[1] : null;
105
+ if (!active) {
106
+ checks.push({ level: 'fail', label: 'systemd unit active', detail: 'unit is not active' });
107
+ }
108
+ else if (binPath && execPath && execPath !== binPath) {
109
+ checks.push({
110
+ level: 'fail',
111
+ label: 'ExecStart matches binary',
112
+ detail: `unit runs ${execPath} but PATH resolves ${binPath}`,
113
+ });
114
+ }
115
+ else {
116
+ checks.push({ level: 'ok', label: 'systemd unit active', detail: `MainPID ${unitMainPid ?? '?'}` });
117
+ }
118
+ }
119
+ else {
120
+ checks.push({ level: 'warn', label: 'systemd unit', detail: 'no user unit — daemon must be supervised another way' });
121
+ }
122
+ // 3. Single live daemon; under systemd it must equal MainPID.
123
+ let filePid = null;
124
+ try {
125
+ filePid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
126
+ if (Number.isNaN(filePid))
127
+ filePid = null;
128
+ }
129
+ catch {
130
+ filePid = null;
131
+ }
132
+ const psOut = sh('ps', ['-eo', 'pid,cmd']);
133
+ const daemonPids = (psOut ?? '')
134
+ .split('\n')
135
+ // Only the long-running `start` daemon counts — never a `doctor`/`status`/
136
+ // `log`/`stop` invocation (which also runs …/scanner/dist/index.js), and
137
+ // never this doctor process itself.
138
+ .filter((l) => /(mnemonik-scanner|scanner\/dist\/index\.js)/.test(l) && /\bstart\b/.test(l))
139
+ .map((l) => parseInt(l.trim().split(/\s+/)[0], 10))
140
+ .filter((n) => !Number.isNaN(n) && n !== process.pid);
141
+ if (daemonPids.length === 0) {
142
+ checks.push({ level: 'fail', label: 'daemon running', detail: 'no daemon process found' });
143
+ }
144
+ else if (daemonPids.length > 1) {
145
+ checks.push({
146
+ level: 'fail',
147
+ label: 'single daemon',
148
+ detail: `${daemonPids.length} daemon processes (duplicate) — PIDs ${daemonPids.join(', ')}`,
149
+ });
150
+ }
151
+ else if (hasUnit && unitMainPid && daemonPids[0] !== unitMainPid) {
152
+ checks.push({
153
+ level: 'fail',
154
+ label: 'daemon is the systemd one',
155
+ detail: `running PID ${daemonPids[0]} != unit MainPID ${unitMainPid} (stray local-build daemon)`,
156
+ });
157
+ }
158
+ else if (filePid && !pidAlive(filePid)) {
159
+ checks.push({ level: 'warn', label: 'PID file fresh', detail: `stale ${PID_FILE} (PID ${filePid} dead)` });
160
+ }
161
+ else {
162
+ checks.push({ level: 'ok', label: 'single daemon', detail: `PID ${daemonPids[0]}` });
163
+ }
164
+ // 4. Legacy per-project daemons.
165
+ try {
166
+ const legacy = readdirSync(LEGACY_DAEMONS_DIR)
167
+ .filter((f) => f.endsWith('.pid'))
168
+ .map((f) => parseInt(readFileSync(join(LEGACY_DAEMONS_DIR, f), 'utf-8').trim(), 10))
169
+ .filter((n) => !Number.isNaN(n) && pidAlive(n));
170
+ if (legacy.length > 0) {
171
+ checks.push({ level: 'fail', label: 'no legacy daemons', detail: `legacy per-project daemons alive: ${legacy.join(', ')}` });
172
+ }
173
+ }
174
+ catch {
175
+ // dir absent — nothing to check
176
+ }
177
+ // 5. Version vs npm latest (informational).
178
+ const local = installedVersion();
179
+ const latest = sh('npm', ['view', '@mnemonik/scanner', 'version'], 15000);
180
+ if (local && latest && local !== latest) {
181
+ checks.push({ level: 'warn', label: 'up to date', detail: `installed ${local}, npm latest ${latest} — run: make scanner-deploy` });
182
+ }
183
+ else if (local) {
184
+ checks.push({ level: 'ok', label: 'up to date', detail: `v${local}` });
185
+ }
186
+ // Report.
187
+ const icon = { ok: '✓', warn: '⚠', fail: '✗' };
188
+ console.log('mnemonik-scanner doctor\n');
189
+ for (const c of checks) {
190
+ console.log(` ${icon[c.level]} ${c.label}${c.detail ? `: ${c.detail}` : ''}`);
191
+ }
192
+ const failed = checks.filter((c) => c.level === 'fail');
193
+ if (failed.length > 0) {
194
+ console.log(`\n${failed.length} problem(s). Canonical fix: \`make scanner-deploy\` (build → publish shared-first → npm i -g → systemctl restart → re-verify).\n` +
195
+ 'Do NOT hand-copy dist/ or run `make daemon-*` on a systemd host — those are what cause this.');
196
+ process.exit(1);
197
+ }
198
+ console.log('\nAll invariants hold.');
199
+ process.exit(0);
200
+ }
201
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Append-only sink that mirrors the daemon's console output to a file the
3
+ * `mnemonik-scanner log` command can tail. Rotates to `<file>.old` once the
4
+ * file would pass `maxSize`, so a daemon that runs for weeks can't grow it
5
+ * without bound. Every operation is best-effort: a logging failure must never
6
+ * crash the daemon, so all filesystem errors are swallowed.
7
+ */
8
+ export declare function createRotatingFileSink(logFile: string, maxSize: number): (chunk: string) => void;
9
+ /**
10
+ * Tee `process.stdout`/`process.stderr` into `logFile` on top of their normal
11
+ * destination. This is what makes `mnemonik-scanner log` reflect live activity
12
+ * regardless of how the daemon is supervised: under systemd stdout is wired to
13
+ * the journal (a socket), under a bare shell to the terminal — either way the
14
+ * file now receives the same lines. Call once at daemon start, before the
15
+ * daemon writes anything worth capturing.
16
+ */
17
+ export declare function installFileLogging(logFile: string, maxSize: number): void;
@@ -0,0 +1,70 @@
1
+ import { appendFileSync, renameSync, statSync } from 'fs';
2
+ /**
3
+ * Append-only sink that mirrors the daemon's console output to a file the
4
+ * `mnemonik-scanner log` command can tail. Rotates to `<file>.old` once the
5
+ * file would pass `maxSize`, so a daemon that runs for weeks can't grow it
6
+ * without bound. Every operation is best-effort: a logging failure must never
7
+ * crash the daemon, so all filesystem errors are swallowed.
8
+ */
9
+ export function createRotatingFileSink(logFile, maxSize) {
10
+ let size = 0;
11
+ try {
12
+ size = statSync(logFile).size;
13
+ }
14
+ catch {
15
+ size = 0;
16
+ }
17
+ return (chunk) => {
18
+ if (!chunk)
19
+ return;
20
+ try {
21
+ const bytes = Buffer.byteLength(chunk);
22
+ if (size > 0 && size + bytes > maxSize) {
23
+ try {
24
+ renameSync(logFile, logFile + '.old');
25
+ }
26
+ catch {
27
+ // Rotation failed (e.g. cross-device) — keep appending to the
28
+ // current file rather than losing the line.
29
+ }
30
+ size = 0;
31
+ }
32
+ appendFileSync(logFile, chunk);
33
+ size += bytes;
34
+ }
35
+ catch {
36
+ // Never let logging break the daemon.
37
+ }
38
+ };
39
+ }
40
+ /**
41
+ * Tee `process.stdout`/`process.stderr` into `logFile` on top of their normal
42
+ * destination. This is what makes `mnemonik-scanner log` reflect live activity
43
+ * regardless of how the daemon is supervised: under systemd stdout is wired to
44
+ * the journal (a socket), under a bare shell to the terminal — either way the
45
+ * file now receives the same lines. Call once at daemon start, before the
46
+ * daemon writes anything worth capturing.
47
+ */
48
+ export function installFileLogging(logFile, maxSize) {
49
+ const sink = createRotatingFileSink(logFile, maxSize);
50
+ for (const stream of [process.stdout, process.stderr]) {
51
+ const original = stream.write.bind(stream);
52
+ stream.write = ((chunk, encoding, cb) => {
53
+ try {
54
+ if (typeof chunk === 'string') {
55
+ sink(chunk);
56
+ }
57
+ else if (Buffer.isBuffer(chunk)) {
58
+ const enc = typeof encoding === 'string' ? encoding : 'utf8';
59
+ sink(chunk.toString(enc));
60
+ }
61
+ }
62
+ catch {
63
+ // ignore — mirroring is best-effort
64
+ }
65
+ // Preserve the real stream's overloads (chunk, cb) / (chunk, enc, cb).
66
+ return original(chunk, encoding, cb);
67
+ });
68
+ }
69
+ }
70
+ //# sourceMappingURL=fileLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileLog.js","sourceRoot":"","sources":["../src/fileLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAe,EACf,OAAe;IAEf,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,KAAa,EAAQ,EAAE;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACvC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;gBACvC,IAAI,CAAC;oBACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;gBACxC,CAAC;gBAAC,MAAM,CAAC;oBACP,8DAA8D;oBAC9D,4CAA4C;gBAC9C,CAAC;gBACD,IAAI,GAAG,CAAC,CAAC;YACX,CAAC;YACD,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,OAAe;IACjE,MAAM,IAAI,GAAG,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEtD,KAAK,MAAM,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAoC,CAAC;QAC9E,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,KAAc,EAAE,QAAkB,EAAE,EAAY,EAAW,EAAE;YAC5E,IAAI,CAAC;gBACH,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC9B,IAAI,CAAC,KAAK,CAAC,CAAC;gBACd,CAAC;qBAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBAClC,MAAM,GAAG,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAE,QAA2B,CAAC,CAAC,CAAC,MAAM,CAAC;oBACjF,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,oCAAoC;YACtC,CAAC;YACD,uEAAuE;YACvE,OAAO,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QACvC,CAAC,CAAwB,CAAC;IAC5B,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile, writeFile, unlink, mkdir, stat, chmod } from 'fs/promises';
3
+ import { existsSync } from 'fs';
3
4
  import { join } from 'path';
4
5
  import { homedir } from 'os';
5
6
  import { ScannerDaemon } from './daemon.js';
7
+ import { installFileLogging } from './fileLog.js';
8
+ import { runDoctor } from './doctor.js';
6
9
  const DEFAULT_SERVER = 'https://api.mnemonik.dev';
7
10
  const MNEMONIK_DIR = join(homedir(), '.mnemonik');
8
11
  const PID_FILE = join(MNEMONIK_DIR, 'daemon.pid');
@@ -12,7 +15,7 @@ const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
12
15
  function parseCliArgs() {
13
16
  const args = process.argv.slice(2);
14
17
  const command = (args[0] ?? 'help');
15
- if (!['start', 'stop', 'status', 'log', 'help'].includes(command)) {
18
+ if (!['start', 'stop', 'status', 'log', 'doctor', 'help'].includes(command)) {
16
19
  return { command: 'help' };
17
20
  }
18
21
  let key;
@@ -57,18 +60,6 @@ async function checkConfigPermissions() {
57
60
  // Config file doesn't exist yet
58
61
  }
59
62
  }
60
- async function rotateLogIfNeeded() {
61
- try {
62
- const { rename } = await import('fs/promises');
63
- const s = await stat(LOG_FILE);
64
- if (s.size > MAX_LOG_SIZE) {
65
- await rename(LOG_FILE, LOG_FILE + '.old');
66
- }
67
- }
68
- catch {
69
- // Log file doesn't exist yet
70
- }
71
- }
72
63
  async function acquireLock(retried = false) {
73
64
  try {
74
65
  const { open: fsOpen } = await import('fs/promises');
@@ -129,6 +120,7 @@ Usage:
129
120
  mnemonik-scanner stop Stop the running daemon
130
121
  mnemonik-scanner status Show daemon status
131
122
  mnemonik-scanner log Tail the scanner log file
123
+ mnemonik-scanner doctor Check install health (drift detection)
132
124
  mnemonik-scanner help Show this help
133
125
 
134
126
  Options (for start):
@@ -149,6 +141,26 @@ Environment variables:
149
141
  `);
150
142
  }
151
143
  async function handleStart(cli) {
144
+ // Guardrail — this daemon is meant to run under the systemd user service
145
+ // (`systemctl --user start mnemonik-scanner`; see BUILD_AND_DEPLOY.md
146
+ // "Scanner daemon"). systemd sets INVOCATION_ID on processes it spawns. A
147
+ // manual foreground `start` attaches the daemon to the invoking shell: it
148
+ // dies when the shell exits and can spawn duplicate/stale daemons — the exact
149
+ // failure mode behind version skew and apparent "restart loops". If a unit
150
+ // exists but we were NOT launched by systemd, refuse (overridable).
151
+ if (!process.env.INVOCATION_ID && !process.env.MNEMONIK_SCANNER_FOREGROUND) {
152
+ const unitPath = join(homedir(), '.config/systemd/user/mnemonik-scanner.service');
153
+ if (existsSync(unitPath)) {
154
+ console.error('[mnemonik] A systemd unit already manages this scanner.\n' +
155
+ ' Start/restart it with:\n' +
156
+ ' systemctl --user restart mnemonik-scanner\n' +
157
+ ' Running `mnemonik-scanner start` by hand attaches the daemon to your shell — it\n' +
158
+ ' dies when the shell exits and can leave duplicate/stale daemons running (version\n' +
159
+ ' skew, apparent "restart loops"). To force a foreground run, set\n' +
160
+ ' MNEMONIK_SCANNER_FOREGROUND=1.');
161
+ process.exit(1);
162
+ }
163
+ }
152
164
  const config = await readConfig();
153
165
  // Resolve API key: env var > CLI > config
154
166
  const apiKey = process.env.MNEMONIK_API_KEY || cli.key || config?.apiKey;
@@ -167,6 +179,12 @@ async function handleStart(cli) {
167
179
  const server = cli.server || config?.server || DEFAULT_SERVER;
168
180
  // Ensure directories exist
169
181
  await mkdir(MNEMONIK_DIR, { recursive: true });
182
+ // Mirror all console output into scanner.log so `mnemonik-scanner log`
183
+ // reflects live activity regardless of supervisor. Under systemd, stdout is
184
+ // wired to the journal socket, not this file — without the tee the on-disk
185
+ // log goes stale and `log` shows nothing current. Install before any daemon
186
+ // output so the whole session is captured.
187
+ installFileLogging(LOG_FILE, MAX_LOG_SIZE);
170
188
  // Save config for future runs (never save env var key to file)
171
189
  const configToSave = {
172
190
  roots,
@@ -182,7 +200,6 @@ async function handleStart(cli) {
182
200
  }
183
201
  await writeConfig(configToSave);
184
202
  await checkConfigPermissions();
185
- await rotateLogIfNeeded();
186
203
  const locked = await acquireLock();
187
204
  if (!locked) {
188
205
  const pid = await readPid();
@@ -320,6 +337,9 @@ async function main() {
320
337
  case 'log':
321
338
  await handleLog();
322
339
  break;
340
+ case 'doctor':
341
+ await runDoctor();
342
+ break;
323
343
  case 'help':
324
344
  default:
325
345
  printHelp();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemonik/scanner",
3
- "version": "5.119.5",
3
+ "version": "5.131.2",
4
4
  "description": "Automatic codebase indexing daemon for Mnemonik",
5
5
  "type": "module",
6
6
  "bin": {
package/src/doctor.ts ADDED
@@ -0,0 +1,207 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync, readFileSync, readdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ /**
8
+ * `mnemonik-scanner doctor` — the guard that turns the prose upgrade procedure
9
+ * into an executable check. It asserts the invariants that every past botched
10
+ * reinstall violated, and prints the one blessed remediation. Any single ✗
11
+ * exits non-zero so a wrapper (or a human) can react.
12
+ *
13
+ * Invariants:
14
+ * 1. Exactly one `mnemonik-scanner` on PATH, inside the user npm prefix
15
+ * (a second copy under /usr is the classic PATH-shadow version skew).
16
+ * 2. If a systemd user unit exists, it is active and its ExecStart points at
17
+ * that same binary.
18
+ * 3. Exactly one live daemon, and under systemd its PID == the unit MainPID
19
+ * (a stray `node …/scanner/dist/index.js` is a duplicate local-build daemon).
20
+ * 4. No leftover legacy per-project daemons.
21
+ * 5. Installed version is not behind npm latest (warning, not failure).
22
+ */
23
+
24
+ const HOME = homedir();
25
+ const MNEMONIK_DIR = join(HOME, '.mnemonik');
26
+ const PID_FILE = join(MNEMONIK_DIR, 'daemon.pid');
27
+ const LEGACY_DAEMONS_DIR = join(MNEMONIK_DIR, 'daemons');
28
+ const UNIT_FILE = join(HOME, '.config/systemd/user/mnemonik-scanner.service');
29
+
30
+ type Level = 'ok' | 'warn' | 'fail';
31
+ interface Check {
32
+ level: Level;
33
+ label: string;
34
+ detail?: string;
35
+ }
36
+
37
+ function sh(cmd: string, args: string[], timeoutMs = 5000): string | null {
38
+ try {
39
+ return execFileSync(cmd, args, {
40
+ encoding: 'utf-8',
41
+ stdio: ['ignore', 'pipe', 'ignore'],
42
+ timeout: timeoutMs,
43
+ }).trim();
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function pidAlive(pid: number): boolean {
50
+ try {
51
+ process.kill(pid, 0);
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ function installedVersion(): string | null {
59
+ try {
60
+ const here = fileURLToPath(new URL('.', import.meta.url));
61
+ const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf-8')) as {
62
+ version?: string;
63
+ };
64
+ return pkg.version ?? null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ export async function runDoctor(): Promise<void> {
71
+ const checks: Check[] = [];
72
+
73
+ // 1. Binary uniqueness + location.
74
+ const prefix = sh('npm', ['config', 'get', 'prefix']);
75
+ const whichOut = sh('which', ['-a', 'mnemonik-scanner']);
76
+ const binPaths = whichOut ? whichOut.split('\n').map((s) => s.trim()).filter(Boolean) : [];
77
+ if (binPaths.length === 0) {
78
+ checks.push({ level: 'fail', label: 'binary on PATH', detail: 'mnemonik-scanner not found on PATH' });
79
+ } else if (binPaths.length > 1) {
80
+ checks.push({
81
+ level: 'fail',
82
+ label: 'single binary on PATH',
83
+ detail: `found ${binPaths.length} copies (PATH shadow → version skew):\n ${binPaths.join('\n ')}`,
84
+ });
85
+ } else if (prefix && !binPaths[0]!.startsWith(prefix)) {
86
+ checks.push({
87
+ level: 'warn',
88
+ label: 'binary in user npm prefix',
89
+ detail: `${binPaths[0]} is not under npm prefix ${prefix}`,
90
+ });
91
+ } else {
92
+ checks.push({ level: 'ok', label: 'single binary on PATH', detail: binPaths[0] });
93
+ }
94
+ const binPath = binPaths.length === 1 ? binPaths[0]! : null;
95
+
96
+ // 2. systemd unit health + ExecStart match.
97
+ let unitMainPid: number | null = null;
98
+ const hasUnit = existsSync(UNIT_FILE);
99
+ if (hasUnit) {
100
+ const show = sh('systemctl', [
101
+ '--user',
102
+ 'show',
103
+ 'mnemonik-scanner',
104
+ '-p',
105
+ 'ActiveState',
106
+ '-p',
107
+ 'MainPID',
108
+ '-p',
109
+ 'ExecStart',
110
+ ]);
111
+ const active = /ActiveState=active/.test(show ?? '');
112
+ const mainPidMatch = /MainPID=(\d+)/.exec(show ?? '');
113
+ unitMainPid = mainPidMatch ? parseInt(mainPidMatch[1]!, 10) : null;
114
+ const execMatch = /path=([^\s;]+)/.exec(show ?? '');
115
+ const execPath = execMatch ? execMatch[1]! : null;
116
+
117
+ if (!active) {
118
+ checks.push({ level: 'fail', label: 'systemd unit active', detail: 'unit is not active' });
119
+ } else if (binPath && execPath && execPath !== binPath) {
120
+ checks.push({
121
+ level: 'fail',
122
+ label: 'ExecStart matches binary',
123
+ detail: `unit runs ${execPath} but PATH resolves ${binPath}`,
124
+ });
125
+ } else {
126
+ checks.push({ level: 'ok', label: 'systemd unit active', detail: `MainPID ${unitMainPid ?? '?'}` });
127
+ }
128
+ } else {
129
+ checks.push({ level: 'warn', label: 'systemd unit', detail: 'no user unit — daemon must be supervised another way' });
130
+ }
131
+
132
+ // 3. Single live daemon; under systemd it must equal MainPID.
133
+ let filePid: number | null = null;
134
+ try {
135
+ filePid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
136
+ if (Number.isNaN(filePid)) filePid = null;
137
+ } catch {
138
+ filePid = null;
139
+ }
140
+ const psOut = sh('ps', ['-eo', 'pid,cmd']);
141
+ const daemonPids = (psOut ?? '')
142
+ .split('\n')
143
+ // Only the long-running `start` daemon counts — never a `doctor`/`status`/
144
+ // `log`/`stop` invocation (which also runs …/scanner/dist/index.js), and
145
+ // never this doctor process itself.
146
+ .filter((l) => /(mnemonik-scanner|scanner\/dist\/index\.js)/.test(l) && /\bstart\b/.test(l))
147
+ .map((l) => parseInt(l.trim().split(/\s+/)[0]!, 10))
148
+ .filter((n) => !Number.isNaN(n) && n !== process.pid);
149
+ if (daemonPids.length === 0) {
150
+ checks.push({ level: 'fail', label: 'daemon running', detail: 'no daemon process found' });
151
+ } else if (daemonPids.length > 1) {
152
+ checks.push({
153
+ level: 'fail',
154
+ label: 'single daemon',
155
+ detail: `${daemonPids.length} daemon processes (duplicate) — PIDs ${daemonPids.join(', ')}`,
156
+ });
157
+ } else if (hasUnit && unitMainPid && daemonPids[0] !== unitMainPid) {
158
+ checks.push({
159
+ level: 'fail',
160
+ label: 'daemon is the systemd one',
161
+ detail: `running PID ${daemonPids[0]} != unit MainPID ${unitMainPid} (stray local-build daemon)`,
162
+ });
163
+ } else if (filePid && !pidAlive(filePid)) {
164
+ checks.push({ level: 'warn', label: 'PID file fresh', detail: `stale ${PID_FILE} (PID ${filePid} dead)` });
165
+ } else {
166
+ checks.push({ level: 'ok', label: 'single daemon', detail: `PID ${daemonPids[0]}` });
167
+ }
168
+
169
+ // 4. Legacy per-project daemons.
170
+ try {
171
+ const legacy = readdirSync(LEGACY_DAEMONS_DIR)
172
+ .filter((f) => f.endsWith('.pid'))
173
+ .map((f) => parseInt(readFileSync(join(LEGACY_DAEMONS_DIR, f), 'utf-8').trim(), 10))
174
+ .filter((n) => !Number.isNaN(n) && pidAlive(n));
175
+ if (legacy.length > 0) {
176
+ checks.push({ level: 'fail', label: 'no legacy daemons', detail: `legacy per-project daemons alive: ${legacy.join(', ')}` });
177
+ }
178
+ } catch {
179
+ // dir absent — nothing to check
180
+ }
181
+
182
+ // 5. Version vs npm latest (informational).
183
+ const local = installedVersion();
184
+ const latest = sh('npm', ['view', '@mnemonik/scanner', 'version'], 15000);
185
+ if (local && latest && local !== latest) {
186
+ checks.push({ level: 'warn', label: 'up to date', detail: `installed ${local}, npm latest ${latest} — run: make scanner-deploy` });
187
+ } else if (local) {
188
+ checks.push({ level: 'ok', label: 'up to date', detail: `v${local}` });
189
+ }
190
+
191
+ // Report.
192
+ const icon = { ok: '✓', warn: '⚠', fail: '✗' } as const;
193
+ console.log('mnemonik-scanner doctor\n');
194
+ for (const c of checks) {
195
+ console.log(` ${icon[c.level]} ${c.label}${c.detail ? `: ${c.detail}` : ''}`);
196
+ }
197
+ const failed = checks.filter((c) => c.level === 'fail');
198
+ if (failed.length > 0) {
199
+ console.log(
200
+ `\n${failed.length} problem(s). Canonical fix: \`make scanner-deploy\` (build → publish shared-first → npm i -g → systemctl restart → re-verify).\n` +
201
+ 'Do NOT hand-copy dist/ or run `make daemon-*` on a systemd host — those are what cause this.'
202
+ );
203
+ process.exit(1);
204
+ }
205
+ console.log('\nAll invariants hold.');
206
+ process.exit(0);
207
+ }
package/src/fileLog.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { appendFileSync, renameSync, statSync } from 'fs';
2
+
3
+ /**
4
+ * Append-only sink that mirrors the daemon's console output to a file the
5
+ * `mnemonik-scanner log` command can tail. Rotates to `<file>.old` once the
6
+ * file would pass `maxSize`, so a daemon that runs for weeks can't grow it
7
+ * without bound. Every operation is best-effort: a logging failure must never
8
+ * crash the daemon, so all filesystem errors are swallowed.
9
+ */
10
+ export function createRotatingFileSink(
11
+ logFile: string,
12
+ maxSize: number
13
+ ): (chunk: string) => void {
14
+ let size = 0;
15
+ try {
16
+ size = statSync(logFile).size;
17
+ } catch {
18
+ size = 0;
19
+ }
20
+
21
+ return (chunk: string): void => {
22
+ if (!chunk) return;
23
+ try {
24
+ const bytes = Buffer.byteLength(chunk);
25
+ if (size > 0 && size + bytes > maxSize) {
26
+ try {
27
+ renameSync(logFile, logFile + '.old');
28
+ } catch {
29
+ // Rotation failed (e.g. cross-device) — keep appending to the
30
+ // current file rather than losing the line.
31
+ }
32
+ size = 0;
33
+ }
34
+ appendFileSync(logFile, chunk);
35
+ size += bytes;
36
+ } catch {
37
+ // Never let logging break the daemon.
38
+ }
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Tee `process.stdout`/`process.stderr` into `logFile` on top of their normal
44
+ * destination. This is what makes `mnemonik-scanner log` reflect live activity
45
+ * regardless of how the daemon is supervised: under systemd stdout is wired to
46
+ * the journal (a socket), under a bare shell to the terminal — either way the
47
+ * file now receives the same lines. Call once at daemon start, before the
48
+ * daemon writes anything worth capturing.
49
+ */
50
+ export function installFileLogging(logFile: string, maxSize: number): void {
51
+ const sink = createRotatingFileSink(logFile, maxSize);
52
+
53
+ for (const stream of [process.stdout, process.stderr]) {
54
+ const original = stream.write.bind(stream) as (...args: unknown[]) => boolean;
55
+ stream.write = ((chunk: unknown, encoding?: unknown, cb?: unknown): boolean => {
56
+ try {
57
+ if (typeof chunk === 'string') {
58
+ sink(chunk);
59
+ } else if (Buffer.isBuffer(chunk)) {
60
+ const enc = typeof encoding === 'string' ? (encoding as BufferEncoding) : 'utf8';
61
+ sink(chunk.toString(enc));
62
+ }
63
+ } catch {
64
+ // ignore — mirroring is best-effort
65
+ }
66
+ // Preserve the real stream's overloads (chunk, cb) / (chunk, enc, cb).
67
+ return original(chunk, encoding, cb);
68
+ }) as typeof stream.write;
69
+ }
70
+ }
package/src/index.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readFile, writeFile, unlink, mkdir, stat, chmod } from 'fs/promises';
4
+ import { existsSync } from 'fs';
4
5
  import { join } from 'path';
5
6
  import { homedir } from 'os';
6
7
  import { ScannerDaemon } from './daemon.js';
8
+ import { installFileLogging } from './fileLog.js';
9
+ import { runDoctor } from './doctor.js';
7
10
 
8
11
  const DEFAULT_SERVER = 'https://api.mnemonik.dev';
9
12
  const MNEMONIK_DIR = join(homedir(), '.mnemonik');
@@ -20,7 +23,7 @@ interface ScannerConfig {
20
23
  }
21
24
 
22
25
  interface CliArgs {
23
- command: 'start' | 'stop' | 'status' | 'log' | 'help';
26
+ command: 'start' | 'stop' | 'status' | 'log' | 'doctor' | 'help';
24
27
  key?: string;
25
28
  server?: string;
26
29
  roots?: string[];
@@ -30,7 +33,7 @@ function parseCliArgs(): CliArgs {
30
33
  const args = process.argv.slice(2);
31
34
  const command = (args[0] ?? 'help') as CliArgs['command'];
32
35
 
33
- if (!['start', 'stop', 'status', 'log', 'help'].includes(command)) {
36
+ if (!['start', 'stop', 'status', 'log', 'doctor', 'help'].includes(command)) {
34
37
  return { command: 'help' };
35
38
  }
36
39
 
@@ -80,18 +83,6 @@ async function checkConfigPermissions(): Promise<void> {
80
83
  }
81
84
  }
82
85
 
83
- async function rotateLogIfNeeded(): Promise<void> {
84
- try {
85
- const { rename } = await import('fs/promises');
86
- const s = await stat(LOG_FILE);
87
- if (s.size > MAX_LOG_SIZE) {
88
- await rename(LOG_FILE, LOG_FILE + '.old');
89
- }
90
- } catch {
91
- // Log file doesn't exist yet
92
- }
93
- }
94
-
95
86
  async function acquireLock(retried = false): Promise<boolean> {
96
87
  try {
97
88
  const { open: fsOpen } = await import('fs/promises');
@@ -153,6 +144,7 @@ Usage:
153
144
  mnemonik-scanner stop Stop the running daemon
154
145
  mnemonik-scanner status Show daemon status
155
146
  mnemonik-scanner log Tail the scanner log file
147
+ mnemonik-scanner doctor Check install health (drift detection)
156
148
  mnemonik-scanner help Show this help
157
149
 
158
150
  Options (for start):
@@ -174,6 +166,29 @@ Environment variables:
174
166
  }
175
167
 
176
168
  async function handleStart(cli: CliArgs): Promise<void> {
169
+ // Guardrail — this daemon is meant to run under the systemd user service
170
+ // (`systemctl --user start mnemonik-scanner`; see BUILD_AND_DEPLOY.md
171
+ // "Scanner daemon"). systemd sets INVOCATION_ID on processes it spawns. A
172
+ // manual foreground `start` attaches the daemon to the invoking shell: it
173
+ // dies when the shell exits and can spawn duplicate/stale daemons — the exact
174
+ // failure mode behind version skew and apparent "restart loops". If a unit
175
+ // exists but we were NOT launched by systemd, refuse (overridable).
176
+ if (!process.env.INVOCATION_ID && !process.env.MNEMONIK_SCANNER_FOREGROUND) {
177
+ const unitPath = join(homedir(), '.config/systemd/user/mnemonik-scanner.service');
178
+ if (existsSync(unitPath)) {
179
+ console.error(
180
+ '[mnemonik] A systemd unit already manages this scanner.\n' +
181
+ ' Start/restart it with:\n' +
182
+ ' systemctl --user restart mnemonik-scanner\n' +
183
+ ' Running `mnemonik-scanner start` by hand attaches the daemon to your shell — it\n' +
184
+ ' dies when the shell exits and can leave duplicate/stale daemons running (version\n' +
185
+ ' skew, apparent "restart loops"). To force a foreground run, set\n' +
186
+ ' MNEMONIK_SCANNER_FOREGROUND=1.'
187
+ );
188
+ process.exit(1);
189
+ }
190
+ }
191
+
177
192
  const config = await readConfig();
178
193
 
179
194
  // Resolve API key: env var > CLI > config
@@ -201,6 +216,13 @@ async function handleStart(cli: CliArgs): Promise<void> {
201
216
  // Ensure directories exist
202
217
  await mkdir(MNEMONIK_DIR, { recursive: true });
203
218
 
219
+ // Mirror all console output into scanner.log so `mnemonik-scanner log`
220
+ // reflects live activity regardless of supervisor. Under systemd, stdout is
221
+ // wired to the journal socket, not this file — without the tee the on-disk
222
+ // log goes stale and `log` shows nothing current. Install before any daemon
223
+ // output so the whole session is captured.
224
+ installFileLogging(LOG_FILE, MAX_LOG_SIZE);
225
+
204
226
  // Save config for future runs (never save env var key to file)
205
227
  const configToSave: ScannerConfig = {
206
228
  roots,
@@ -216,8 +238,6 @@ async function handleStart(cli: CliArgs): Promise<void> {
216
238
  await writeConfig(configToSave);
217
239
  await checkConfigPermissions();
218
240
 
219
- await rotateLogIfNeeded();
220
-
221
241
  const locked = await acquireLock();
222
242
  if (!locked) {
223
243
  const pid = await readPid();
@@ -364,6 +384,9 @@ async function main(): Promise<void> {
364
384
  case 'log':
365
385
  await handleLog();
366
386
  break;
387
+ case 'doctor':
388
+ await runDoctor();
389
+ break;
367
390
  case 'help':
368
391
  default:
369
392
  printHelp();