@mnemonik/scanner 5.120.1 → 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":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AACvB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;AAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAClD,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AACzD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,+CAA+C,CAAC,CAAC;AAS9E,SAAS,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,SAAS,GAAG,IAAI;IACvD,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB;IACvB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAE7E,CAAC;QACF,OAAO,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,mCAAmC;IACnC,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3F,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,oCAAoC,EAAE,CAAC,CAAC;IACxG,CAAC;SAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,uBAAuB;YAC9B,MAAM,EAAE,SAAS,QAAQ,CAAC,MAAM,8CAA8C,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;SACxG,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,2BAA2B;YAClC,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,4BAA4B,MAAM,EAAE;SAC3D,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,uBAAuB,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAE5D,4CAA4C;IAC5C,IAAI,WAAW,GAAkB,IAAI,CAAC;IACtC,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,EAAE;YAC3B,QAAQ;YACR,MAAM;YACN,kBAAkB;YAClB,IAAI;YACJ,aAAa;YACb,IAAI;YACJ,SAAS;YACT,IAAI;YACJ,WAAW;SACZ,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACtD,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAElD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC7F,CAAC;aAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YACvD,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,MAAM;gBACb,KAAK,EAAE,0BAA0B;gBACjC,MAAM,EAAE,aAAa,QAAQ,sBAAsB,OAAO,EAAE;aAC7D,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,WAAW,WAAW,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QACtG,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,sDAAsD,EAAE,CAAC,CAAC;IACxH,CAAC;IAED,8DAA8D;IAC9D,IAAI,OAAO,GAAkB,IAAI,CAAC;IAClC,IAAI,CAAC;QACH,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,MAAM,KAAK,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;SAC7B,KAAK,CAAC,IAAI,CAAC;QACZ,2EAA2E;QAC3E,yEAAyE;QACzE,oCAAoC;SACnC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,6CAA6C,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAC3F,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;SACnD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACxD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;IAC7F,CAAC;SAAM,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,eAAe;YACtB,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,wCAAwC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SAC5F,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,OAAO,IAAI,WAAW,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,WAAW,EAAE,CAAC;QACnE,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,2BAA2B;YAClC,MAAM,EAAE,eAAe,UAAU,CAAC,CAAC,CAAC,oBAAoB,WAAW,6BAA6B;SACjG,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,QAAQ,SAAS,OAAO,QAAQ,EAAE,CAAC,CAAC;IAC7G,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,iCAAiC;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,WAAW,CAAC,kBAAkB,CAAC;aAC3C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;aACnF,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,qCAAqC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/H,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;IAClC,CAAC;IAED,4CAA4C;IAC5C,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1E,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,KAAK,gBAAgB,MAAM,6BAA6B,EAAE,CAAC,CAAC;IACrI,CAAC;SAAM,IAAI,KAAK,EAAE,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,UAAU;IACV,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAW,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CACT,KAAK,MAAM,CAAC,MAAM,kIAAkI;YAClJ,8FAA8F,CACjG,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
@@ -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
@@ -4,6 +4,8 @@ import { existsSync } from 'fs';
4
4
  import { join } from 'path';
5
5
  import { homedir } from 'os';
6
6
  import { ScannerDaemon } from './daemon.js';
7
+ import { installFileLogging } from './fileLog.js';
8
+ import { runDoctor } from './doctor.js';
7
9
  const DEFAULT_SERVER = 'https://api.mnemonik.dev';
8
10
  const MNEMONIK_DIR = join(homedir(), '.mnemonik');
9
11
  const PID_FILE = join(MNEMONIK_DIR, 'daemon.pid');
@@ -13,7 +15,7 @@ const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
13
15
  function parseCliArgs() {
14
16
  const args = process.argv.slice(2);
15
17
  const command = (args[0] ?? 'help');
16
- if (!['start', 'stop', 'status', 'log', 'help'].includes(command)) {
18
+ if (!['start', 'stop', 'status', 'log', 'doctor', 'help'].includes(command)) {
17
19
  return { command: 'help' };
18
20
  }
19
21
  let key;
@@ -58,18 +60,6 @@ async function checkConfigPermissions() {
58
60
  // Config file doesn't exist yet
59
61
  }
60
62
  }
61
- async function rotateLogIfNeeded() {
62
- try {
63
- const { rename } = await import('fs/promises');
64
- const s = await stat(LOG_FILE);
65
- if (s.size > MAX_LOG_SIZE) {
66
- await rename(LOG_FILE, LOG_FILE + '.old');
67
- }
68
- }
69
- catch {
70
- // Log file doesn't exist yet
71
- }
72
- }
73
63
  async function acquireLock(retried = false) {
74
64
  try {
75
65
  const { open: fsOpen } = await import('fs/promises');
@@ -130,6 +120,7 @@ Usage:
130
120
  mnemonik-scanner stop Stop the running daemon
131
121
  mnemonik-scanner status Show daemon status
132
122
  mnemonik-scanner log Tail the scanner log file
123
+ mnemonik-scanner doctor Check install health (drift detection)
133
124
  mnemonik-scanner help Show this help
134
125
 
135
126
  Options (for start):
@@ -188,6 +179,12 @@ async function handleStart(cli) {
188
179
  const server = cli.server || config?.server || DEFAULT_SERVER;
189
180
  // Ensure directories exist
190
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);
191
188
  // Save config for future runs (never save env var key to file)
192
189
  const configToSave = {
193
190
  roots,
@@ -203,7 +200,6 @@ async function handleStart(cli) {
203
200
  }
204
201
  await writeConfig(configToSave);
205
202
  await checkConfigPermissions();
206
- await rotateLogIfNeeded();
207
203
  const locked = await acquireLock();
208
204
  if (!locked) {
209
205
  const pid = await readPid();
@@ -341,6 +337,9 @@ async function main() {
341
337
  case 'log':
342
338
  await handleLog();
343
339
  break;
340
+ case 'doctor':
341
+ await runDoctor();
342
+ break;
344
343
  case 'help':
345
344
  default:
346
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.120.1",
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
@@ -5,6 +5,8 @@ import { existsSync } from 'fs';
5
5
  import { join } from 'path';
6
6
  import { homedir } from 'os';
7
7
  import { ScannerDaemon } from './daemon.js';
8
+ import { installFileLogging } from './fileLog.js';
9
+ import { runDoctor } from './doctor.js';
8
10
 
9
11
  const DEFAULT_SERVER = 'https://api.mnemonik.dev';
10
12
  const MNEMONIK_DIR = join(homedir(), '.mnemonik');
@@ -21,7 +23,7 @@ interface ScannerConfig {
21
23
  }
22
24
 
23
25
  interface CliArgs {
24
- command: 'start' | 'stop' | 'status' | 'log' | 'help';
26
+ command: 'start' | 'stop' | 'status' | 'log' | 'doctor' | 'help';
25
27
  key?: string;
26
28
  server?: string;
27
29
  roots?: string[];
@@ -31,7 +33,7 @@ function parseCliArgs(): CliArgs {
31
33
  const args = process.argv.slice(2);
32
34
  const command = (args[0] ?? 'help') as CliArgs['command'];
33
35
 
34
- if (!['start', 'stop', 'status', 'log', 'help'].includes(command)) {
36
+ if (!['start', 'stop', 'status', 'log', 'doctor', 'help'].includes(command)) {
35
37
  return { command: 'help' };
36
38
  }
37
39
 
@@ -81,18 +83,6 @@ async function checkConfigPermissions(): Promise<void> {
81
83
  }
82
84
  }
83
85
 
84
- async function rotateLogIfNeeded(): Promise<void> {
85
- try {
86
- const { rename } = await import('fs/promises');
87
- const s = await stat(LOG_FILE);
88
- if (s.size > MAX_LOG_SIZE) {
89
- await rename(LOG_FILE, LOG_FILE + '.old');
90
- }
91
- } catch {
92
- // Log file doesn't exist yet
93
- }
94
- }
95
-
96
86
  async function acquireLock(retried = false): Promise<boolean> {
97
87
  try {
98
88
  const { open: fsOpen } = await import('fs/promises');
@@ -154,6 +144,7 @@ Usage:
154
144
  mnemonik-scanner stop Stop the running daemon
155
145
  mnemonik-scanner status Show daemon status
156
146
  mnemonik-scanner log Tail the scanner log file
147
+ mnemonik-scanner doctor Check install health (drift detection)
157
148
  mnemonik-scanner help Show this help
158
149
 
159
150
  Options (for start):
@@ -225,6 +216,13 @@ async function handleStart(cli: CliArgs): Promise<void> {
225
216
  // Ensure directories exist
226
217
  await mkdir(MNEMONIK_DIR, { recursive: true });
227
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
+
228
226
  // Save config for future runs (never save env var key to file)
229
227
  const configToSave: ScannerConfig = {
230
228
  roots,
@@ -240,8 +238,6 @@ async function handleStart(cli: CliArgs): Promise<void> {
240
238
  await writeConfig(configToSave);
241
239
  await checkConfigPermissions();
242
240
 
243
- await rotateLogIfNeeded();
244
-
245
241
  const locked = await acquireLock();
246
242
  if (!locked) {
247
243
  const pid = await readPid();
@@ -388,6 +384,9 @@ async function main(): Promise<void> {
388
384
  case 'log':
389
385
  await handleLog();
390
386
  break;
387
+ case 'doctor':
388
+ await runDoctor();
389
+ break;
391
390
  case 'help':
392
391
  default:
393
392
  printHelp();