@pcircle/memesh 4.1.2 → 4.1.4
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/.mcp.json +1 -3
- package/README.de.md +12 -1
- package/README.es.md +14 -2
- package/README.fr.md +14 -2
- package/README.ja.md +12 -1
- package/README.ko.md +12 -1
- package/README.md +17 -2
- package/README.pt.md +14 -2
- package/README.th.md +14 -2
- package/README.vi.md +14 -2
- package/README.zh-CN.md +12 -1
- package/README.zh-TW.md +12 -1
- package/dashboard/dist/index.html +13 -4
- package/dist/cli/view-live.d.ts +2 -0
- package/dist/cli/view-live.d.ts.map +1 -0
- package/dist/cli/view-live.js +2177 -0
- package/dist/cli/view-live.js.map +1 -0
- package/dist/cli/view.d.ts +0 -1
- package/dist/cli/view.d.ts.map +1 -1
- package/dist/cli/view.js +0 -2169
- package/dist/cli/view.js.map +1 -1
- package/dist/core/analytics.d.ts +23 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +79 -0
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/config.d.ts +9 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +16 -9
- package/dist/core/config.js.map +1 -1
- package/dist/core/consolidator.d.ts.map +1 -1
- package/dist/core/consolidator.js +1 -0
- package/dist/core/consolidator.js.map +1 -1
- package/dist/core/demo.d.ts +9 -0
- package/dist/core/demo.d.ts.map +1 -0
- package/dist/core/demo.js +79 -0
- package/dist/core/demo.js.map +1 -0
- package/dist/core/doctor.d.ts +2 -1
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +198 -8
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +65 -0
- package/dist/core/dreamer.d.ts.map +1 -0
- package/dist/core/dreamer.js +477 -0
- package/dist/core/dreamer.js.map +1 -0
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +9 -5
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +44 -22
- package/dist/core/extractor.js.map +1 -1
- package/dist/core/graph.d.ts +1 -0
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js +8 -2
- package/dist/core/graph.js.map +1 -1
- package/dist/core/install-hooks.d.ts +41 -0
- package/dist/core/install-hooks.d.ts.map +1 -0
- package/dist/core/install-hooks.js +181 -0
- package/dist/core/install-hooks.js.map +1 -0
- package/dist/core/install-id.d.ts +9 -0
- package/dist/core/install-id.d.ts.map +1 -0
- package/dist/core/install-id.js +51 -0
- package/dist/core/install-id.js.map +1 -0
- package/dist/core/lesson-engine.d.ts +1 -5
- package/dist/core/lesson-engine.d.ts.map +1 -1
- package/dist/core/lesson-engine.js +13 -13
- package/dist/core/lesson-engine.js.map +1 -1
- package/dist/core/llm-validator.d.ts +16 -0
- package/dist/core/llm-validator.d.ts.map +1 -0
- package/dist/core/llm-validator.js +182 -0
- package/dist/core/llm-validator.js.map +1 -0
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +6 -2
- package/dist/core/operations.js.map +1 -1
- package/dist/core/projects.d.ts +14 -0
- package/dist/core/projects.d.ts.map +1 -0
- package/dist/core/projects.js +70 -0
- package/dist/core/projects.js.map +1 -0
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/scoring.d.ts +0 -4
- package/dist/core/scoring.d.ts.map +1 -1
- package/dist/core/scoring.js +3 -10
- package/dist/core/scoring.js.map +1 -1
- package/dist/core/serializer.d.ts.map +1 -1
- package/dist/core/serializer.js +2 -0
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/signal-scorer.d.ts +9 -0
- package/dist/core/signal-scorer.d.ts.map +1 -0
- package/dist/core/signal-scorer.js +70 -0
- package/dist/core/signal-scorer.js.map +1 -0
- package/dist/core/types.d.ts +2 -30
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/updater.d.ts +16 -0
- package/dist/core/updater.d.ts.map +1 -1
- package/dist/core/updater.js +85 -0
- package/dist/core/updater.js.map +1 -1
- package/dist/core/version-check.d.ts +2 -1
- package/dist/core/version-check.d.ts.map +1 -1
- package/dist/core/version-check.js +154 -30
- package/dist/core/version-check.js.map +1 -1
- package/dist/db.d.ts +7 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +91 -0
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +7 -3
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +43 -18
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/skills-manifest.json +23 -18
- package/dist/storage/conflicts.d.ts +4 -1
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js +5 -2
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/transports/cli/cli.js +393 -37
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +167 -13
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/schemas.d.ts +2 -2
- package/hooks/hooks.json +12 -0
- package/package.json +16 -6
- package/plugin.json +1 -1
- package/scripts/hooks/_shared.js +257 -5
- package/scripts/hooks/pre-compact.js +19 -9
- package/scripts/hooks/session-start.js +364 -15
- package/scripts/hooks/session-summary.js +131 -29
- package/scripts/hooks/user-prompt-intent.js +159 -0
- package/skills/memesh/SKILL.md +9 -5
- package/skills/memesh-review/SKILL.md +24 -0
package/scripts/hooks/_shared.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
1
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
2
3
|
import { createRequire } from 'module';
|
|
3
4
|
import { homedir } from 'os';
|
|
4
5
|
import { dirname, join } from 'path';
|
|
@@ -6,6 +7,22 @@ import { fileURLToPath } from 'url';
|
|
|
6
7
|
|
|
7
8
|
const require = createRequire(import.meta.url);
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Return the user's home directory, honoring HOME env var first.
|
|
12
|
+
*
|
|
13
|
+
* On POSIX, os.homedir() already consults HOME. On Windows, it ignores
|
|
14
|
+
* env vars and reads GetUserProfileDirectoryW directly — which makes
|
|
15
|
+
* tests unable to redirect home-dir lookups to a tmp dir. Honoring HOME
|
|
16
|
+
* first lets tests set HOME=<tmpdir> and have it actually take effect
|
|
17
|
+
* across platforms. Production users on Windows almost never set HOME,
|
|
18
|
+
* so this falls through to os.homedir() as before.
|
|
19
|
+
*
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function homeDir() {
|
|
23
|
+
return process.env.HOME ?? homedir();
|
|
24
|
+
}
|
|
25
|
+
|
|
9
26
|
/**
|
|
10
27
|
* Resolve the package root from a hook file's `import.meta.url`.
|
|
11
28
|
*
|
|
@@ -47,7 +64,7 @@ export function resolvePluginRoot(metaUrl) {
|
|
|
47
64
|
* @returns {Record<string, any>}
|
|
48
65
|
*/
|
|
49
66
|
export function readHookConfig(_env = process.env) {
|
|
50
|
-
const path = join(
|
|
67
|
+
const path = join(homeDir(), '.memesh', 'config.json');
|
|
51
68
|
if (!existsSync(path)) return {};
|
|
52
69
|
try {
|
|
53
70
|
const raw = readFileSync(path, 'utf8');
|
|
@@ -112,6 +129,35 @@ export function resolveSessionLimit(env = process.env) {
|
|
|
112
129
|
return 10;
|
|
113
130
|
}
|
|
114
131
|
|
|
132
|
+
const VALID_AUTO_UPDATE_POLICIES = new Set(['off', 'patch', 'minor', 'major']);
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolve the auto-update policy.
|
|
136
|
+
* Precedence: env > config > default('off').
|
|
137
|
+
*
|
|
138
|
+
* The session-start hook uses this to decide whether to kick off a
|
|
139
|
+
* background `npm install -g @pcircle/memesh@<latest>` when an update
|
|
140
|
+
* is available. Default is 'off' so a fresh install never silently
|
|
141
|
+
* upgrades the user's global binary; opting in is a deliberate
|
|
142
|
+
* config write or env export.
|
|
143
|
+
*
|
|
144
|
+
* @param {NodeJS.ProcessEnv} [env=process.env]
|
|
145
|
+
* @returns {'off' | 'patch' | 'minor' | 'major'}
|
|
146
|
+
*/
|
|
147
|
+
export function resolveAutoUpdatePolicy(env = process.env) {
|
|
148
|
+
const envVal = env.MEMESH_AUTO_UPDATE;
|
|
149
|
+
if (typeof envVal === 'string') {
|
|
150
|
+
const lowered = envVal.toLowerCase();
|
|
151
|
+
if (VALID_AUTO_UPDATE_POLICIES.has(lowered)) return lowered;
|
|
152
|
+
}
|
|
153
|
+
const cfg = readHookConfig(env);
|
|
154
|
+
if (typeof cfg.autoUpdate === 'string') {
|
|
155
|
+
const lowered = cfg.autoUpdate.toLowerCase();
|
|
156
|
+
if (VALID_AUTO_UPDATE_POLICIES.has(lowered)) return lowered;
|
|
157
|
+
}
|
|
158
|
+
return 'off';
|
|
159
|
+
}
|
|
160
|
+
|
|
115
161
|
// Canonical SQLite schema for hook-written entities. Mirrors src/db.ts.
|
|
116
162
|
// Hooks must NOT depend on dist/ (F5 security boundary), so this is a
|
|
117
163
|
// duplicate string by necessity. When src/db.ts changes, this must
|
|
@@ -188,8 +234,8 @@ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
|
188
234
|
* @returns {{ db: any, dbPath: string }}
|
|
189
235
|
*/
|
|
190
236
|
export function openHookDb(env = process.env, opts = {}) {
|
|
191
|
-
const dbPath = env.MEMESH_DB_PATH || join(
|
|
192
|
-
const dbDir = env.MEMESH_DB_PATH ? dirname(env.MEMESH_DB_PATH) : join(
|
|
237
|
+
const dbPath = env.MEMESH_DB_PATH || join(homeDir(), '.memesh', 'knowledge-graph.db');
|
|
238
|
+
const dbDir = env.MEMESH_DB_PATH ? dirname(env.MEMESH_DB_PATH) : join(homeDir(), '.memesh');
|
|
193
239
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
194
240
|
|
|
195
241
|
const Database = require('better-sqlite3');
|
|
@@ -212,7 +258,7 @@ const PRIVATE_DIR_MODE = 0o700;
|
|
|
212
258
|
const PRIVATE_FILE_MODE = 0o600;
|
|
213
259
|
|
|
214
260
|
export function getMemeshDir(env = process.env) {
|
|
215
|
-
return env.MEMESH_DB_PATH ? dirname(env.MEMESH_DB_PATH) : join(
|
|
261
|
+
return env.MEMESH_DB_PATH ? dirname(env.MEMESH_DB_PATH) : join(homeDir(), '.memesh');
|
|
216
262
|
}
|
|
217
263
|
|
|
218
264
|
export function ensurePrivateDir(dirPath) {
|
|
@@ -266,3 +312,209 @@ export function buildReferenceContext(memoryLines) {
|
|
|
266
312
|
'```',
|
|
267
313
|
].join('\n');
|
|
268
314
|
}
|
|
315
|
+
|
|
316
|
+
// ─── Auto-update shared helpers ─────────────────────────────────────────────
|
|
317
|
+
// Shared between SessionStart and Stop hooks for auto-update coordination.
|
|
318
|
+
|
|
319
|
+
export function readUpdateCheckCache(installedVersion) {
|
|
320
|
+
if (process.env.MEMESH_UPDATE_CHECK_PATH) {
|
|
321
|
+
try {
|
|
322
|
+
const overridePath = process.env.MEMESH_UPDATE_CHECK_PATH;
|
|
323
|
+
if (!existsSync(overridePath)) return null;
|
|
324
|
+
const parsed = JSON.parse(readFileSync(overridePath, 'utf8'));
|
|
325
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
326
|
+
return parsed;
|
|
327
|
+
} catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const versionTag = typeof installedVersion === 'string'
|
|
332
|
+
&& /^[0-9A-Za-z.+-]+$/.test(installedVersion)
|
|
333
|
+
? installedVersion
|
|
334
|
+
: 'unknown';
|
|
335
|
+
const cachePath = join(homeDir(), '.memesh', `update-check.${versionTag}.json`);
|
|
336
|
+
try {
|
|
337
|
+
if (!existsSync(cachePath)) return null;
|
|
338
|
+
const parsed = JSON.parse(readFileSync(cachePath, 'utf8'));
|
|
339
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
340
|
+
return parsed;
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:[-+].+)?$/;
|
|
347
|
+
|
|
348
|
+
export function classifyBumpHook(from, to) {
|
|
349
|
+
const a = SEMVER_RE.exec((from || '').trim());
|
|
350
|
+
const b = SEMVER_RE.exec((to || '').trim());
|
|
351
|
+
if (!a || !b) return null;
|
|
352
|
+
const ai = a.slice(1, 4).map(Number);
|
|
353
|
+
const bi = b.slice(1, 4).map(Number);
|
|
354
|
+
if (bi[0] > ai[0]) return 'major';
|
|
355
|
+
if (bi[0] < ai[0]) return null;
|
|
356
|
+
if (bi[1] > ai[1]) return 'minor';
|
|
357
|
+
if (bi[1] < ai[1]) return null;
|
|
358
|
+
if (bi[2] > ai[2]) return 'patch';
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const POLICY_RANK = { off: 0, patch: 1, minor: 2, major: 3 };
|
|
363
|
+
const BUMP_RANK = { patch: 1, minor: 2, major: 3 };
|
|
364
|
+
const AUTO_UPDATE_CACHE_FRESHNESS_MS = 24 * 60 * 60 * 1000;
|
|
365
|
+
|
|
366
|
+
export function decideAutoUpdateHook(currentVersion, cache, policy) {
|
|
367
|
+
if (!cache || cache.currentVersion !== currentVersion) return { run: false };
|
|
368
|
+
const latest = cache.latestVersion;
|
|
369
|
+
if (typeof latest !== 'string' || !latest) return { run: false };
|
|
370
|
+
const bump = classifyBumpHook(currentVersion, latest);
|
|
371
|
+
if (!bump) return { run: false };
|
|
372
|
+
|
|
373
|
+
const lastSuccessAt = cache.lastSuccessfulCheckAt;
|
|
374
|
+
const lastSuccessMs = typeof lastSuccessAt === 'string' ? Date.parse(lastSuccessAt) : NaN;
|
|
375
|
+
const cacheAgeMs = Number.isFinite(lastSuccessMs) ? Date.now() - lastSuccessMs : Infinity;
|
|
376
|
+
if (cacheAgeMs > AUTO_UPDATE_CACHE_FRESHNESS_MS) {
|
|
377
|
+
return { run: false, reason: 'stale-cache' };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const policyAllows = (POLICY_RANK[policy] ?? 0) >= BUMP_RANK[bump];
|
|
381
|
+
if (policyAllows) return { run: true, latest, bump, deprecationOverride: false };
|
|
382
|
+
|
|
383
|
+
const deprecated = typeof cache.currentVersionDeprecation === 'string'
|
|
384
|
+
&& cache.currentVersionDeprecation.length > 0;
|
|
385
|
+
if (deprecated && bump === 'patch') {
|
|
386
|
+
return { run: true, latest, bump, deprecationOverride: true };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return { run: false };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function logAutoUpdate(line) {
|
|
393
|
+
try {
|
|
394
|
+
const dir = getMemeshDir(process.env);
|
|
395
|
+
ensurePrivateDir(dir);
|
|
396
|
+
const path = join(dir, 'auto-update.log');
|
|
397
|
+
appendFileSync(path, `[${new Date().toISOString()}] ${line}\n`);
|
|
398
|
+
try { chmodSync(path, 0o600); } catch { /* non-POSIX */ }
|
|
399
|
+
} catch {
|
|
400
|
+
// Logging is best-effort.
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export const AUTO_UPDATE_LOCK_TTL_MS = 10 * 60 * 1000;
|
|
405
|
+
|
|
406
|
+
export function tryAcquireAutoUpdateLock(version) {
|
|
407
|
+
try {
|
|
408
|
+
const dir = join(homeDir(), '.memesh');
|
|
409
|
+
ensurePrivateDir(dir);
|
|
410
|
+
const lockPath = join(dir, 'auto-update.lock');
|
|
411
|
+
const fs = require('fs');
|
|
412
|
+
const myToken = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
413
|
+
const payload = `${myToken}\n${process.pid}\n${Date.now()}\n${version}\n`;
|
|
414
|
+
try {
|
|
415
|
+
// O_CREAT|O_EXCL is the POSIX atomic create primitive — only one process wins.
|
|
416
|
+
const O_FLAGS = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL;
|
|
417
|
+
const fd = fs.openSync(lockPath, O_FLAGS, 0o600);
|
|
418
|
+
try { fs.writeFileSync(fd, payload); } finally { fs.closeSync(fd); }
|
|
419
|
+
return { acquired: true, lockPath };
|
|
420
|
+
} catch (err) {
|
|
421
|
+
if (err?.code !== 'EEXIST') throw err;
|
|
422
|
+
}
|
|
423
|
+
let stat;
|
|
424
|
+
try { stat = fs.statSync(lockPath); } catch { return { acquired: false, lockPath }; }
|
|
425
|
+
if (Date.now() - stat.mtimeMs <= AUTO_UPDATE_LOCK_TTL_MS) {
|
|
426
|
+
return { acquired: false, lockPath };
|
|
427
|
+
}
|
|
428
|
+
const tempPath = `${lockPath}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
429
|
+
try {
|
|
430
|
+
fs.writeFileSync(tempPath, payload, { mode: 0o600 });
|
|
431
|
+
try { fs.unlinkSync(lockPath); } catch (e2) {
|
|
432
|
+
if (e2?.code !== 'ENOENT') {
|
|
433
|
+
try { fs.unlinkSync(tempPath); } catch { /* best-effort */ }
|
|
434
|
+
return { acquired: false, lockPath };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
fs.renameSync(tempPath, lockPath);
|
|
438
|
+
} catch {
|
|
439
|
+
try { fs.unlinkSync(tempPath); } catch { /* best-effort */ }
|
|
440
|
+
return { acquired: false, lockPath };
|
|
441
|
+
}
|
|
442
|
+
// The "race" here is the lock primitive itself: we just renamed our
|
|
443
|
+
// temp file onto lockPath, then immediately read back to see whether
|
|
444
|
+
// our token won. If a concurrent caller's rename landed in the
|
|
445
|
+
// window between our rename and our read, their token is now there
|
|
446
|
+
// and we correctly conclude we did NOT acquire the lock. That race
|
|
447
|
+
// outcome IS the contract — CodeQL flags it as TOCTOU but it is the
|
|
448
|
+
// standard last-writer-wins lock-file pattern.
|
|
449
|
+
// codeql[js/file-system-race]: justified — the read is the lock
|
|
450
|
+
// verification step, not a post-check before mutation.
|
|
451
|
+
let recorded;
|
|
452
|
+
try { recorded = fs.readFileSync(lockPath, 'utf8'); } catch { return { acquired: false, lockPath }; }
|
|
453
|
+
return recorded.split('\n')[0] === myToken
|
|
454
|
+
? { acquired: true, lockPath }
|
|
455
|
+
: { acquired: false, lockPath };
|
|
456
|
+
} catch {
|
|
457
|
+
return { acquired: false, lockPath: null };
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Spawn `npm install -g @pcircle/memesh@<version>` detached so the upgrade
|
|
463
|
+
* can finish after this hook returns. Never blocks the session.
|
|
464
|
+
*
|
|
465
|
+
* @param {string} version - Target version to install
|
|
466
|
+
* @param {boolean} deprecationOverride - Whether this is a security-override install
|
|
467
|
+
* @param {object|null} installChannelMod - Preloaded dist/core/install-channel.js module
|
|
468
|
+
* @returns {{ state: 'spawned'|'in-progress'|'channel'|'failed' }}
|
|
469
|
+
*/
|
|
470
|
+
export function spawnAutoUpdate(version, deprecationOverride, installChannelMod) {
|
|
471
|
+
let lock = null;
|
|
472
|
+
try {
|
|
473
|
+
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
474
|
+
let channel = 'unknown';
|
|
475
|
+
if (installChannelMod) {
|
|
476
|
+
try {
|
|
477
|
+
channel = installChannelMod.getCurrentInstallChannel({ packageRoot: pluginRoot });
|
|
478
|
+
} catch { /* best-effort */ }
|
|
479
|
+
}
|
|
480
|
+
if (channel !== 'npm-global') {
|
|
481
|
+
logAutoUpdate(
|
|
482
|
+
`auto-update SKIPPED: install channel '${channel}' does not support self-update via npm install -g`
|
|
483
|
+
);
|
|
484
|
+
return { state: 'channel' };
|
|
485
|
+
}
|
|
486
|
+
lock = tryAcquireAutoUpdateLock(version);
|
|
487
|
+
if (!lock.acquired) {
|
|
488
|
+
logAutoUpdate(
|
|
489
|
+
`auto-update SKIPPED: another session already holds ${lock.lockPath ?? 'auto-update.lock'} for this upgrade`
|
|
490
|
+
);
|
|
491
|
+
return { state: 'in-progress' };
|
|
492
|
+
}
|
|
493
|
+
const dir = getMemeshDir(process.env);
|
|
494
|
+
ensurePrivateDir(dir);
|
|
495
|
+
const logPath = join(dir, 'auto-update.log');
|
|
496
|
+
let fd = -1;
|
|
497
|
+
try { fd = openSync(logPath, 'a', 0o600); } catch { fd = -1; }
|
|
498
|
+
const stdio = fd >= 0 ? ['ignore', fd, fd] : 'ignore';
|
|
499
|
+
const child = spawn(
|
|
500
|
+
'npm',
|
|
501
|
+
['install', '-g', `@pcircle/memesh@${version}`],
|
|
502
|
+
// windowsHide avoids a flashing console window on Windows; harmless on POSIX.
|
|
503
|
+
{ detached: true, stdio, env: process.env, windowsHide: true },
|
|
504
|
+
);
|
|
505
|
+
child.unref();
|
|
506
|
+
if (fd >= 0) {
|
|
507
|
+
try { closeSync(fd); } catch { /* ignore */ }
|
|
508
|
+
}
|
|
509
|
+
logAutoUpdate(
|
|
510
|
+
`auto-update spawn: target=${version}${deprecationOverride ? ' (deprecation-override)' : ''} pid=${child.pid ?? 'unknown'} lock=${lock.lockPath}`
|
|
511
|
+
);
|
|
512
|
+
return { state: 'spawned' };
|
|
513
|
+
} catch (err) {
|
|
514
|
+
logAutoUpdate(`auto-update spawn FAILED: ${err?.message ?? err}`);
|
|
515
|
+
if (lock?.acquired && lock.lockPath) {
|
|
516
|
+
try { require('fs').unlinkSync(lock.lockPath); } catch { /* best-effort */ }
|
|
517
|
+
}
|
|
518
|
+
return { state: 'failed' };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
@@ -40,17 +40,27 @@ process.stdin.on('end', () => {
|
|
|
40
40
|
if (!line.trim()) continue;
|
|
41
41
|
try {
|
|
42
42
|
const entry = JSON.parse(line);
|
|
43
|
-
//
|
|
43
|
+
// Current format: {type:'assistant', message:{content:[{type:'tool_use',...}]}}
|
|
44
|
+
if (entry.type === 'assistant' && Array.isArray(entry.message?.content)) {
|
|
45
|
+
for (const block of entry.message.content) {
|
|
46
|
+
if (block.type !== 'tool_use') continue;
|
|
47
|
+
toolCallCount++;
|
|
48
|
+
const name = block.name || '';
|
|
49
|
+
if (name === 'Edit' || name === 'Write' || name === 'MultiEdit') {
|
|
50
|
+
const filePath = block.input?.file_path || block.input?.path || '';
|
|
51
|
+
if (filePath) editedFiles.add(basename(filePath));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Legacy format: {role:'assistant', content:[{type:'tool_use',...}]}
|
|
44
56
|
if (entry.role === 'assistant' && Array.isArray(entry.content)) {
|
|
45
57
|
for (const block of entry.content) {
|
|
46
|
-
if (block.type
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (filePath) editedFiles.add(basename(filePath));
|
|
53
|
-
}
|
|
58
|
+
if (block.type !== 'tool_use') continue;
|
|
59
|
+
toolCallCount++;
|
|
60
|
+
const name = block.name || '';
|
|
61
|
+
if (name === 'Edit' || name === 'Write' || name === 'MultiEdit') {
|
|
62
|
+
const filePath = block.input?.file_path || block.input?.path || '';
|
|
63
|
+
if (filePath) editedFiles.add(basename(filePath));
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
}
|