@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.
Files changed (128) hide show
  1. package/.mcp.json +1 -3
  2. package/README.de.md +12 -1
  3. package/README.es.md +14 -2
  4. package/README.fr.md +14 -2
  5. package/README.ja.md +12 -1
  6. package/README.ko.md +12 -1
  7. package/README.md +17 -2
  8. package/README.pt.md +14 -2
  9. package/README.th.md +14 -2
  10. package/README.vi.md +14 -2
  11. package/README.zh-CN.md +12 -1
  12. package/README.zh-TW.md +12 -1
  13. package/dashboard/dist/index.html +13 -4
  14. package/dist/cli/view-live.d.ts +2 -0
  15. package/dist/cli/view-live.d.ts.map +1 -0
  16. package/dist/cli/view-live.js +2177 -0
  17. package/dist/cli/view-live.js.map +1 -0
  18. package/dist/cli/view.d.ts +0 -1
  19. package/dist/cli/view.d.ts.map +1 -1
  20. package/dist/cli/view.js +0 -2169
  21. package/dist/cli/view.js.map +1 -1
  22. package/dist/core/analytics.d.ts +23 -0
  23. package/dist/core/analytics.d.ts.map +1 -1
  24. package/dist/core/analytics.js +79 -0
  25. package/dist/core/analytics.js.map +1 -1
  26. package/dist/core/config.d.ts +9 -4
  27. package/dist/core/config.d.ts.map +1 -1
  28. package/dist/core/config.js +16 -9
  29. package/dist/core/config.js.map +1 -1
  30. package/dist/core/consolidator.d.ts.map +1 -1
  31. package/dist/core/consolidator.js +1 -0
  32. package/dist/core/consolidator.js.map +1 -1
  33. package/dist/core/demo.d.ts +9 -0
  34. package/dist/core/demo.d.ts.map +1 -0
  35. package/dist/core/demo.js +79 -0
  36. package/dist/core/demo.js.map +1 -0
  37. package/dist/core/doctor.d.ts +2 -1
  38. package/dist/core/doctor.d.ts.map +1 -1
  39. package/dist/core/doctor.js +198 -8
  40. package/dist/core/doctor.js.map +1 -1
  41. package/dist/core/dreamer.d.ts +65 -0
  42. package/dist/core/dreamer.d.ts.map +1 -0
  43. package/dist/core/dreamer.js +477 -0
  44. package/dist/core/dreamer.js.map +1 -0
  45. package/dist/core/embedder.d.ts.map +1 -1
  46. package/dist/core/embedder.js +9 -5
  47. package/dist/core/embedder.js.map +1 -1
  48. package/dist/core/extractor.d.ts.map +1 -1
  49. package/dist/core/extractor.js +44 -22
  50. package/dist/core/extractor.js.map +1 -1
  51. package/dist/core/graph.d.ts +1 -0
  52. package/dist/core/graph.d.ts.map +1 -1
  53. package/dist/core/graph.js +8 -2
  54. package/dist/core/graph.js.map +1 -1
  55. package/dist/core/install-hooks.d.ts +41 -0
  56. package/dist/core/install-hooks.d.ts.map +1 -0
  57. package/dist/core/install-hooks.js +181 -0
  58. package/dist/core/install-hooks.js.map +1 -0
  59. package/dist/core/install-id.d.ts +9 -0
  60. package/dist/core/install-id.d.ts.map +1 -0
  61. package/dist/core/install-id.js +51 -0
  62. package/dist/core/install-id.js.map +1 -0
  63. package/dist/core/lesson-engine.d.ts +1 -5
  64. package/dist/core/lesson-engine.d.ts.map +1 -1
  65. package/dist/core/lesson-engine.js +13 -13
  66. package/dist/core/lesson-engine.js.map +1 -1
  67. package/dist/core/llm-validator.d.ts +16 -0
  68. package/dist/core/llm-validator.d.ts.map +1 -0
  69. package/dist/core/llm-validator.js +182 -0
  70. package/dist/core/llm-validator.js.map +1 -0
  71. package/dist/core/operations.d.ts.map +1 -1
  72. package/dist/core/operations.js +6 -2
  73. package/dist/core/operations.js.map +1 -1
  74. package/dist/core/projects.d.ts +14 -0
  75. package/dist/core/projects.d.ts.map +1 -0
  76. package/dist/core/projects.js +70 -0
  77. package/dist/core/projects.js.map +1 -0
  78. package/dist/core/query-expander.js.map +1 -1
  79. package/dist/core/scoring.d.ts +0 -4
  80. package/dist/core/scoring.d.ts.map +1 -1
  81. package/dist/core/scoring.js +3 -10
  82. package/dist/core/scoring.js.map +1 -1
  83. package/dist/core/serializer.d.ts.map +1 -1
  84. package/dist/core/serializer.js +2 -0
  85. package/dist/core/serializer.js.map +1 -1
  86. package/dist/core/signal-scorer.d.ts +9 -0
  87. package/dist/core/signal-scorer.d.ts.map +1 -0
  88. package/dist/core/signal-scorer.js +70 -0
  89. package/dist/core/signal-scorer.js.map +1 -0
  90. package/dist/core/types.d.ts +2 -30
  91. package/dist/core/types.d.ts.map +1 -1
  92. package/dist/core/updater.d.ts +16 -0
  93. package/dist/core/updater.d.ts.map +1 -1
  94. package/dist/core/updater.js +85 -0
  95. package/dist/core/updater.js.map +1 -1
  96. package/dist/core/version-check.d.ts +2 -1
  97. package/dist/core/version-check.d.ts.map +1 -1
  98. package/dist/core/version-check.js +154 -30
  99. package/dist/core/version-check.js.map +1 -1
  100. package/dist/db.d.ts +7 -0
  101. package/dist/db.d.ts.map +1 -1
  102. package/dist/db.js +91 -0
  103. package/dist/db.js.map +1 -1
  104. package/dist/knowledge-graph.d.ts +7 -3
  105. package/dist/knowledge-graph.d.ts.map +1 -1
  106. package/dist/knowledge-graph.js +43 -18
  107. package/dist/knowledge-graph.js.map +1 -1
  108. package/dist/skills-manifest.json +23 -18
  109. package/dist/storage/conflicts.d.ts +4 -1
  110. package/dist/storage/conflicts.d.ts.map +1 -1
  111. package/dist/storage/conflicts.js +5 -2
  112. package/dist/storage/conflicts.js.map +1 -1
  113. package/dist/transports/cli/cli.js +393 -37
  114. package/dist/transports/cli/cli.js.map +1 -1
  115. package/dist/transports/http/server.d.ts.map +1 -1
  116. package/dist/transports/http/server.js +167 -13
  117. package/dist/transports/http/server.js.map +1 -1
  118. package/dist/transports/schemas.d.ts +2 -2
  119. package/hooks/hooks.json +12 -0
  120. package/package.json +16 -6
  121. package/plugin.json +1 -1
  122. package/scripts/hooks/_shared.js +257 -5
  123. package/scripts/hooks/pre-compact.js +19 -9
  124. package/scripts/hooks/session-start.js +364 -15
  125. package/scripts/hooks/session-summary.js +131 -29
  126. package/scripts/hooks/user-prompt-intent.js +159 -0
  127. package/skills/memesh/SKILL.md +9 -5
  128. package/skills/memesh-review/SKILL.md +24 -0
@@ -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(homedir(), '.memesh', 'config.json');
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(homedir(), '.memesh', 'knowledge-graph.db');
192
- const dbDir = env.MEMESH_DB_PATH ? dirname(env.MEMESH_DB_PATH) : join(homedir(), '.memesh');
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(homedir(), '.memesh');
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
- // Count tool uses from assistant message content blocks
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 === 'tool_use') {
47
- toolCallCount++;
48
- // Track file edits
49
- const name = block.name || '';
50
- if (name === 'Edit' || name === 'Write' || name === 'MultiEdit') {
51
- const filePath = block.input?.file_path || block.input?.path || '';
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
  }