@pcircle/memesh 4.5.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/.claude-plugin/marketplace.json +5 -3
  2. package/.claude-plugin/plugin.json +6 -4
  3. package/AGENTS.md +95 -0
  4. package/README.de.md +129 -35
  5. package/README.md +161 -34
  6. package/README.zh-TW.md +130 -35
  7. package/dashboard/dist/index.html +10 -10
  8. package/dist/cli/view-live.js +3 -3
  9. package/dist/core/auto-tagger.d.ts.map +1 -1
  10. package/dist/core/auto-tagger.js +4 -9
  11. package/dist/core/auto-tagger.js.map +1 -1
  12. package/dist/core/briefing.d.ts +8 -0
  13. package/dist/core/briefing.d.ts.map +1 -0
  14. package/dist/core/briefing.js +91 -0
  15. package/dist/core/briefing.js.map +1 -0
  16. package/dist/core/capture-flag.d.ts +5 -0
  17. package/dist/core/capture-flag.d.ts.map +1 -0
  18. package/dist/core/capture-flag.js +10 -0
  19. package/dist/core/capture-flag.js.map +1 -0
  20. package/dist/core/conflict-candidates.d.ts +20 -0
  21. package/dist/core/conflict-candidates.d.ts.map +1 -0
  22. package/dist/core/conflict-candidates.js +79 -0
  23. package/dist/core/conflict-candidates.js.map +1 -0
  24. package/dist/core/conflict-judge.d.ts +47 -0
  25. package/dist/core/conflict-judge.d.ts.map +1 -0
  26. package/dist/core/conflict-judge.js +189 -0
  27. package/dist/core/conflict-judge.js.map +1 -0
  28. package/dist/core/digest-validator.d.ts.map +1 -1
  29. package/dist/core/digest-validator.js +3 -5
  30. package/dist/core/digest-validator.js.map +1 -1
  31. package/dist/core/doctor.d.ts +2 -0
  32. package/dist/core/doctor.d.ts.map +1 -1
  33. package/dist/core/doctor.js +34 -56
  34. package/dist/core/doctor.js.map +1 -1
  35. package/dist/core/dreamer.d.ts +5 -2
  36. package/dist/core/dreamer.d.ts.map +1 -1
  37. package/dist/core/dreamer.js +108 -25
  38. package/dist/core/dreamer.js.map +1 -1
  39. package/dist/core/embedder.d.ts +5 -4
  40. package/dist/core/embedder.d.ts.map +1 -1
  41. package/dist/core/embedder.js +16 -8
  42. package/dist/core/embedder.js.map +1 -1
  43. package/dist/core/failure-analyzer.d.ts.map +1 -1
  44. package/dist/core/failure-analyzer.js +7 -12
  45. package/dist/core/failure-analyzer.js.map +1 -1
  46. package/dist/core/install-channel.d.ts +1 -1
  47. package/dist/core/install-channel.d.ts.map +1 -1
  48. package/dist/core/install-channel.js +16 -5
  49. package/dist/core/install-channel.js.map +1 -1
  50. package/dist/core/install-hooks.d.ts +5 -0
  51. package/dist/core/install-hooks.d.ts.map +1 -1
  52. package/dist/core/install-hooks.js +0 -0
  53. package/dist/core/install-hooks.js.map +1 -1
  54. package/dist/core/json-utils.d.ts +1 -0
  55. package/dist/core/json-utils.d.ts.map +1 -1
  56. package/dist/core/json-utils.js +19 -10
  57. package/dist/core/json-utils.js.map +1 -1
  58. package/dist/core/kg-backfill.d.ts +0 -1
  59. package/dist/core/kg-backfill.d.ts.map +1 -1
  60. package/dist/core/kg-backfill.js +0 -3
  61. package/dist/core/kg-backfill.js.map +1 -1
  62. package/dist/core/lifecycle.d.ts.map +1 -1
  63. package/dist/core/lifecycle.js +14 -21
  64. package/dist/core/lifecycle.js.map +1 -1
  65. package/dist/core/memory-tool.d.ts.map +1 -1
  66. package/dist/core/memory-tool.js +4 -4
  67. package/dist/core/memory-tool.js.map +1 -1
  68. package/dist/core/operations.d.ts.map +1 -1
  69. package/dist/core/operations.js +22 -13
  70. package/dist/core/operations.js.map +1 -1
  71. package/dist/core/prompt-safety.d.ts +1 -0
  72. package/dist/core/prompt-safety.d.ts.map +1 -1
  73. package/dist/core/prompt-safety.js +7 -0
  74. package/dist/core/prompt-safety.js.map +1 -1
  75. package/dist/core/schema-export.d.ts.map +1 -1
  76. package/dist/core/schema-export.js +31 -0
  77. package/dist/core/schema-export.js.map +1 -1
  78. package/dist/core/setup.d.ts +29 -0
  79. package/dist/core/setup.d.ts.map +1 -0
  80. package/dist/core/setup.js +127 -0
  81. package/dist/core/setup.js.map +1 -0
  82. package/dist/core/task-state-store.d.ts +17 -0
  83. package/dist/core/task-state-store.d.ts.map +1 -0
  84. package/dist/core/task-state-store.js +45 -0
  85. package/dist/core/task-state-store.js.map +1 -0
  86. package/dist/core/task-state.d.ts +19 -0
  87. package/dist/core/task-state.d.ts.map +1 -0
  88. package/dist/core/task-state.js +91 -0
  89. package/dist/core/task-state.js.map +1 -0
  90. package/dist/core/time-utils.d.ts +2 -0
  91. package/dist/core/time-utils.d.ts.map +1 -0
  92. package/dist/core/time-utils.js +14 -0
  93. package/dist/core/time-utils.js.map +1 -0
  94. package/dist/core/title.d.ts +5 -0
  95. package/dist/core/title.d.ts.map +1 -0
  96. package/dist/core/title.js +14 -0
  97. package/dist/core/title.js.map +1 -0
  98. package/dist/core/transcript-source.d.ts.map +1 -1
  99. package/dist/core/transcript-source.js +2 -3
  100. package/dist/core/transcript-source.js.map +1 -1
  101. package/dist/core/types.d.ts +4 -0
  102. package/dist/core/types.d.ts.map +1 -1
  103. package/dist/core/work-topology.d.ts +33 -0
  104. package/dist/core/work-topology.d.ts.map +1 -0
  105. package/dist/core/work-topology.js +183 -0
  106. package/dist/core/work-topology.js.map +1 -0
  107. package/dist/db.d.ts +2 -7
  108. package/dist/db.d.ts.map +1 -1
  109. package/dist/db.js +144 -284
  110. package/dist/db.js.map +1 -1
  111. package/dist/knowledge-graph.d.ts +1 -0
  112. package/dist/knowledge-graph.d.ts.map +1 -1
  113. package/dist/knowledge-graph.js +50 -40
  114. package/dist/knowledge-graph.js.map +1 -1
  115. package/dist/skills-manifest.json +48 -18
  116. package/dist/storage/conflicts.d.ts.map +1 -1
  117. package/dist/storage/conflicts.js +2 -7
  118. package/dist/storage/conflicts.js.map +1 -1
  119. package/dist/storage/fts-index.d.ts +4 -2
  120. package/dist/storage/fts-index.d.ts.map +1 -1
  121. package/dist/storage/fts-index.js +16 -4
  122. package/dist/storage/fts-index.js.map +1 -1
  123. package/dist/storage/schema.d.ts +20 -0
  124. package/dist/storage/schema.d.ts.map +1 -0
  125. package/dist/storage/schema.js +274 -0
  126. package/dist/storage/schema.js.map +1 -0
  127. package/dist/transports/cli/cli.d.ts +1 -4
  128. package/dist/transports/cli/cli.d.ts.map +1 -1
  129. package/dist/transports/cli/cli.js +382 -6
  130. package/dist/transports/cli/cli.js.map +1 -1
  131. package/dist/transports/http/server.d.ts.map +1 -1
  132. package/dist/transports/http/server.js +208 -307
  133. package/dist/transports/http/server.js.map +1 -1
  134. package/dist/transports/mcp/handlers.d.ts +46 -0
  135. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  136. package/dist/transports/mcp/handlers.js +57 -2
  137. package/dist/transports/mcp/handlers.js.map +1 -1
  138. package/dist/transports/schemas.d.ts +21 -10
  139. package/dist/transports/schemas.d.ts.map +1 -1
  140. package/dist/transports/schemas.js +26 -8
  141. package/dist/transports/schemas.js.map +1 -1
  142. package/llms-install.md +138 -0
  143. package/package.json +14 -9
  144. package/scripts/hooks/_generated/capture-flag.js +17 -0
  145. package/scripts/hooks/_generated/fts-index.js +16 -4
  146. package/scripts/hooks/_generated/schema.js +281 -0
  147. package/scripts/hooks/_generated/task-state.js +98 -0
  148. package/scripts/hooks/_generated/time-utils.js +21 -0
  149. package/scripts/hooks/_generated/title.js +21 -0
  150. package/scripts/hooks/_generated/work-topology.js +190 -0
  151. package/scripts/hooks/_shared.js +122 -478
  152. package/scripts/hooks/post-commit.js +4 -1
  153. package/scripts/hooks/pre-compact.js +13 -1
  154. package/scripts/hooks/pre-edit-recall.js +5 -3
  155. package/scripts/hooks/session-start.js +135 -59
  156. package/scripts/hooks/session-summary.js +59 -24
  157. package/skills/memesh/SKILL.md +97 -76
  158. package/README.es.md +0 -467
  159. package/README.fr.md +0 -459
  160. package/README.ja.md +0 -467
  161. package/README.ko.md +0 -467
  162. package/README.pt.md +0 -459
  163. package/README.th.md +0 -460
  164. package/README.vi.md +0 -459
  165. package/README.zh-CN.md +0 -466
  166. package/dist/cli/view.d.ts +0 -3
  167. package/dist/cli/view.d.ts.map +0 -1
  168. package/dist/cli/view.js +0 -523
  169. package/dist/cli/view.js.map +0 -1
@@ -8,9 +8,14 @@ import { openDatabase, closeDatabase, getDatabase, reindexFts, allowVectorIndexR
8
8
  import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn, reindex, setPinned } from '../../core/operations.js';
9
9
  import { readConfig, writeConfig, maskApiKey, detectCapabilities } from '../../core/config.js';
10
10
  import { MAX_LANGUAGE_LENGTH, languageValueError } from '../../core/output-language.js';
11
- import { getDbPath, redactSecrets, redactUserPaths } from '../../core/paths.js';
11
+ import { getDbPath, homeDir, redactSecrets, redactUserPaths } from '../../core/paths.js';
12
12
  import { flushPendingEmbeddings, canRefillVectorIndex } from '../../core/embedder.js';
13
13
  import { NAMESPACES } from '../../core/types.js';
14
+ import { assembleBriefing } from '../../core/briefing.js';
15
+ import { inspectHosts, allWired } from '../../core/setup.js';
16
+ import { installHooks } from '../../core/install-hooks.js';
17
+ import { getTaskState, setTaskState } from '../../core/task-state-store.js';
18
+ import { TASK_STATE_FIELDS, taskStateLines } from '../../core/task-state.js';
14
19
  async function withDatabase(fn) {
15
20
  openDatabase();
16
21
  try {
@@ -26,13 +31,34 @@ function requireOneOf(value, allowed, flag) {
26
31
  console.error(`Error: ${flag} "${value}" is not valid. Use one of: ${allowed.join(', ')}.`);
27
32
  process.exit(1);
28
33
  }
34
+ function isOnPath(tool) {
35
+ const exts = process.platform === 'win32'
36
+ ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
37
+ : [''];
38
+ for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {
39
+ if (!dir)
40
+ continue;
41
+ for (const ext of exts) {
42
+ try {
43
+ fs.accessSync(path.join(dir, tool + ext), fs.constants.X_OK);
44
+ return true;
45
+ }
46
+ catch { }
47
+ }
48
+ }
49
+ return false;
50
+ }
51
+ function wireUserHooks() {
52
+ const r = installHooks({ pluginRoot: packageRoot, pluginVersion: pkg.version, scope: 'user' });
53
+ return `hooks: added ${r.added}, skipped ${r.skipped} already-installed${r.backupPath ? ` (backup: ${r.backupPath})` : ''}`;
54
+ }
29
55
  const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../package.json');
30
56
  const packageRoot = path.dirname(packageJsonPath);
31
57
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
32
58
  const program = new Command();
33
59
  program
34
60
  .name('memesh')
35
- .description('MeMesh — Local memory for Claude Code and MCP coding agents')
61
+ .description('MeMesh — Agentic memory for coding agents')
36
62
  .version(pkg.version)
37
63
  .allowExcessArguments(true)
38
64
  .showSuggestionAfterError(true);
@@ -42,6 +68,7 @@ program
42
68
  .description('Store knowledge as an entity (use flags for explicit form, or positional text for quick capture)')
43
69
  .option('--name <name>', 'Entity name')
44
70
  .option('--type <type>', 'Entity type')
71
+ .option('--title <title>', 'Short human-readable label shown as the headline (name stays the stable machine key)')
45
72
  .option('--obs <observations...>', 'Observations (space-separated)')
46
73
  .option('--tags <tags...>', 'Tags (space-separated)')
47
74
  .option('--namespace <namespace>', 'Namespace: personal, team, or global. On a NEW memory this places it (default personal); on one that already exists it MOVES it out of the scope it is in — omit the flag to leave it alone.')
@@ -60,6 +87,8 @@ program
60
87
  const suffix = randomBytes(3).toString('hex');
61
88
  opts.name = `quick-${date}-${slug || 'note'}-${suffix}`;
62
89
  opts.type = 'note';
90
+ if (!opts.title)
91
+ opts.title = String(text).slice(0, 200);
63
92
  if (!opts.obs || opts.obs.length === 0)
64
93
  opts.obs = [String(text)];
65
94
  else
@@ -85,6 +114,7 @@ program
85
114
  const result = remember({
86
115
  name: opts.name,
87
116
  type: opts.type,
117
+ title: opts.title,
88
118
  observations: opts.obs,
89
119
  tags: opts.tags,
90
120
  namespace: opts.namespace,
@@ -400,6 +430,165 @@ program
400
430
  }
401
431
  });
402
432
  });
433
+ program
434
+ .command('briefing')
435
+ .description('The assembled work topology for a project — task state, decisions, lessons, knowledge, recent activity')
436
+ .option('--project <name>', 'Project name (default: the current directory’s project)')
437
+ .option('--json', 'Output as JSON')
438
+ .action(async (opts) => {
439
+ await withDatabase(() => {
440
+ const result = assembleBriefing(opts.project);
441
+ if (opts.json) {
442
+ console.log(JSON.stringify(result));
443
+ return;
444
+ }
445
+ if (!result.text) {
446
+ console.log(`No memories for "${result.project}" yet.\n` +
447
+ `Capture happens automatically as you work; or set the task state: memesh task --goal "…"`);
448
+ return;
449
+ }
450
+ console.log(result.text);
451
+ });
452
+ });
453
+ program
454
+ .command('setup')
455
+ .description('Detect Claude Code / Codex / Gemini on this machine, wire memesh into each, and verify')
456
+ .option('--check', 'Only report wiring status per host; change nothing (exit 1 if a present host is unwired)')
457
+ .option('--yes', 'Apply every wiring action without asking')
458
+ .action(async (opts) => {
459
+ const { spawnSync, execFileSync } = await import('child_process');
460
+ const runSeam = (cmd, args) => {
461
+ try {
462
+ let target = cmd;
463
+ let useShell = false;
464
+ if (process.platform === 'win32') {
465
+ const resolved = execFileSync('where', [cmd], { encoding: 'utf8' }).split(/\r?\n/)[0]?.trim();
466
+ if (resolved) {
467
+ target = resolved;
468
+ useShell = /\.(cmd|bat)$/i.test(resolved);
469
+ }
470
+ }
471
+ const r = spawnSync(target, args, { encoding: 'utf8', shell: useShell });
472
+ return { status: r.status, stderr: r.stderr ?? '' };
473
+ }
474
+ catch (err) {
475
+ return { status: null, stderr: err instanceof Error ? err.message : String(err) };
476
+ }
477
+ };
478
+ const seams = { home: () => homeDir(), isOnPath, run: runSeam };
479
+ const render = (statuses) => {
480
+ for (const st of statuses) {
481
+ if (!st.present) {
482
+ console.log(` ${st.title}: not found (looked for ${st.presenceDetail}) — skipped`);
483
+ continue;
484
+ }
485
+ const mark = st.wired === true ? '✅' : st.wired === false ? '❌' : '❓';
486
+ console.log(`${mark} ${st.title}: ${st.wiredDetail}`);
487
+ }
488
+ };
489
+ let statuses = inspectHosts(seams);
490
+ if (opts.check) {
491
+ render(statuses);
492
+ process.exit(allWired(statuses) ? 0 : 1);
493
+ }
494
+ const pending = statuses.filter((st) => st.present && st.actions.length > 0);
495
+ if (pending.length === 0) {
496
+ render(statuses);
497
+ console.log(allWired(statuses)
498
+ ? '\nEverything present is wired. Nothing to do.'
499
+ : '\nNothing to wire automatically — see the lines above.');
500
+ process.exit(allWired(statuses) ? 0 : 1);
501
+ }
502
+ render(statuses);
503
+ console.log('\nPlanned actions:');
504
+ for (const st of pending)
505
+ for (const a of st.actions) {
506
+ console.log(` • [${st.title}] ${a.label}${a.cmd ? `\n ${a.cmd} ${(a.args ?? []).join(' ')}` : ''}`);
507
+ }
508
+ if (!opts.yes && !process.stdin.isTTY) {
509
+ console.error('\nNot a terminal and --yes not given — nothing was changed. Re-run with: memesh setup --yes');
510
+ process.exit(1);
511
+ }
512
+ const confirmAll = Boolean(opts.yes);
513
+ let rl = null;
514
+ if (!confirmAll) {
515
+ const { createInterface } = await import('node:readline/promises');
516
+ rl = createInterface({ input: process.stdin, output: process.stdout });
517
+ }
518
+ let failed = false;
519
+ for (const st of pending) {
520
+ for (const action of st.actions) {
521
+ if (!confirmAll && rl) {
522
+ const answer = (await rl.question(`\n[${st.title}] ${action.label} — proceed? [y/N] `)).trim().toLowerCase();
523
+ if (answer !== 'y' && answer !== 'yes') {
524
+ console.log(' skipped');
525
+ continue;
526
+ }
527
+ }
528
+ if (action.kind === 'install-hooks') {
529
+ console.log(` ✅ ${wireUserHooks()}`);
530
+ }
531
+ else if (action.cmd) {
532
+ const r = runSeam(action.cmd, action.args ?? []);
533
+ if (r.status === 0)
534
+ console.log(` ✅ done (${action.cmd} ${(action.args ?? []).join(' ')})`);
535
+ else {
536
+ console.error(` ❌ ${action.cmd} exited ${r.status ?? 'without running'}${r.stderr ? `: ${r.stderr.trim()}` : ''}`);
537
+ failed = true;
538
+ }
539
+ }
540
+ }
541
+ }
542
+ rl?.close();
543
+ console.log('\nAfter wiring:');
544
+ statuses = inspectHosts(seams);
545
+ render(statuses);
546
+ process.exit(failed || !allWired(statuses) ? 1 : 0);
547
+ });
548
+ program
549
+ .command('task')
550
+ .description('Show or update where the work stands on this project')
551
+ .option('--project <name>', 'Project name (default: the current directory’s project)')
552
+ .option('--goal <text>', 'What this work is FOR — the outcome being aimed at')
553
+ .option('--next <text>', 'The next concrete step')
554
+ .option('--blocked <text>', 'What is standing in the way (pass "" to clear it once resolved)')
555
+ .option('--done <text>', 'What was just finished')
556
+ .option('--json', 'Output as JSON')
557
+ .action(async (opts) => {
558
+ await withDatabase(() => {
559
+ const patch = {};
560
+ for (const field of TASK_STATE_FIELDS) {
561
+ if (opts[field] !== undefined)
562
+ patch[field] = opts[field];
563
+ }
564
+ if (Object.keys(patch).length === 0) {
565
+ const { project, state } = getTaskState(opts.project);
566
+ if (opts.json) {
567
+ console.log(JSON.stringify({ project, state }));
568
+ return;
569
+ }
570
+ const lines = taskStateLines(state, project);
571
+ if (lines.length === 0) {
572
+ console.log(`Nothing recorded for "${project}" yet.\n` +
573
+ `Set it with: memesh task --goal "…" --next "…"`);
574
+ return;
575
+ }
576
+ console.log(lines.join('\n'));
577
+ return;
578
+ }
579
+ const result = setTaskState({ project: opts.project, patch, sourceHost: 'cli' });
580
+ if (opts.json) {
581
+ console.log(JSON.stringify(result));
582
+ return;
583
+ }
584
+ if (result.changed.length === 0) {
585
+ console.log(`No change — "${result.project}" already said exactly that.`);
586
+ return;
587
+ }
588
+ console.log(`Updated ${result.changed.join(', ')} for "${result.project}".`);
589
+ console.log(taskStateLines(result.state, result.project).join('\n'));
590
+ });
591
+ });
403
592
  const configCmd = program.command('config').description('Manage configuration');
404
593
  configCmd
405
594
  .command('list')
@@ -432,6 +621,7 @@ const ALLOWED_KEYS = new Set([
432
621
  'autoCapture',
433
622
  'llmFallbacks',
434
623
  'language',
624
+ 'transcriptMining',
435
625
  ]);
436
626
  const KEY_VALIDATORS = {
437
627
  'llm.provider': (v) => ['anthropic', 'openai', 'ollama'].includes(v) ? null : `must be one of: anthropic, openai, ollama`,
@@ -498,7 +688,7 @@ function formatConfigValue(key, raw) {
498
688
  return JSON.stringify(raw);
499
689
  return String(raw);
500
690
  }
501
- export function buildConfigListing(config) {
691
+ function buildConfigListing(config) {
502
692
  const rows = [];
503
693
  for (const key of Array.from(ALLOWED_KEYS).sort()) {
504
694
  const raw = getNested(config, key.split('.'));
@@ -555,6 +745,9 @@ configCmd
555
745
  if (canonical === 'autoCapture') {
556
746
  coerced = value === 'true' || value === '1';
557
747
  }
748
+ if (canonical === 'transcriptMining') {
749
+ coerced = value === 'true' || value === '1';
750
+ }
558
751
  const config = readConfig();
559
752
  setNested(config, canonical.split('.'), coerced);
560
753
  writeConfig(config);
@@ -680,6 +873,53 @@ program
680
873
  process.exit(1);
681
874
  }
682
875
  });
876
+ program
877
+ .command('upgrade-plugin')
878
+ .description('Upgrade the Claude Code plugin install (finds and runs its bundled upgrade script)')
879
+ .action(async () => {
880
+ const { spawnSync } = await import('child_process');
881
+ const cacheRoot = path.join(homeDir(), '.claude', 'plugins', 'cache', 'pcircle-memesh', 'memesh');
882
+ let versions = [];
883
+ try {
884
+ versions = fs.readdirSync(cacheRoot, { withFileTypes: true })
885
+ .filter((entry) => entry.isDirectory() && /^\d+\.\d+\.\d+/.test(entry.name))
886
+ .map((entry) => entry.name);
887
+ }
888
+ catch { }
889
+ if (versions.length === 0) {
890
+ console.error('No Claude Code plugin install found (looked in ~/.claude/plugins/cache/pcircle-memesh).');
891
+ console.error('If you installed via npm, upgrade with: memesh update');
892
+ process.exit(1);
893
+ }
894
+ versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
895
+ const newest = versions[versions.length - 1];
896
+ const script = path.join(cacheRoot, newest, 'scripts', 'upgrade-plugin.sh');
897
+ if (!fs.existsSync(script)) {
898
+ console.error(`Plugin install found (v${newest}), but it has no scripts/upgrade-plugin.sh — plugin versions before 4.2.5 shipped without it.`);
899
+ console.error('Reinstall once from the Claude Code /plugin UI, or run the npm-global copy directly:');
900
+ console.error(' bash "$(npm prefix -g)/lib/node_modules/@pcircle/memesh/scripts/upgrade-plugin.sh"');
901
+ process.exit(1);
902
+ }
903
+ const installHints = {
904
+ node: 'node is required by the upgrade script. Install Node.js from https://nodejs.org',
905
+ npm: 'npm is required by the upgrade script. It ships with Node.js — reinstall from https://nodejs.org',
906
+ rsync: 'rsync is required by the upgrade script. macOS: already installed; Debian/Ubuntu: sudo apt install rsync',
907
+ };
908
+ const missing = Object.keys(installHints).filter((tool) => !isOnPath(tool));
909
+ if (missing.length > 0) {
910
+ for (const tool of missing)
911
+ console.error(installHints[tool]);
912
+ process.exit(1);
913
+ }
914
+ const run = spawnSync('bash', [script], { stdio: 'inherit' });
915
+ if (run.error) {
916
+ console.error(`Could not run the upgrade script: ${run.error.message}`);
917
+ console.error('bash is required to run it. If bash is available under another name, run it yourself:');
918
+ console.error(` bash ${script}`);
919
+ process.exit(1);
920
+ }
921
+ process.exit(run.status ?? 1);
922
+ });
683
923
  program
684
924
  .command('telemetry')
685
925
  .description('Show LLM call telemetry (per-flow scorecard for the last N days)')
@@ -908,15 +1148,82 @@ program
908
1148
  .option('--probe-http', 'Also probe the local HTTP server health endpoint')
909
1149
  .option('--probe', 'Make one small live call to the configured LLM to confirm it actually answers')
910
1150
  .option('--url <url>', 'Base URL for --probe-http', 'http://127.0.0.1:3737')
1151
+ .option('--fix', 'Apply the whitelisted fixes doctor prescribes (asks per fix; --yes skips asking)')
1152
+ .option('--yes', 'With --fix: apply without asking')
911
1153
  .action(async (opts) => {
912
1154
  const { formatDoctorReport, runDoctor } = await import('../../core/doctor.js');
913
- const result = await runDoctor({
1155
+ let result = await runDoctor({
914
1156
  packageRoot,
915
1157
  packageVersion: pkg.version,
916
1158
  probeHttp: opts.probeHttp,
917
1159
  probeCapabilities: opts.probe,
918
1160
  httpBaseUrl: opts.url,
919
1161
  });
1162
+ if (opts.fix) {
1163
+ const FIX_ACTIONS = {
1164
+ 'install-hooks': wireUserHooks,
1165
+ 'fts-rebuild': () => {
1166
+ openDatabase();
1167
+ try {
1168
+ return `keyword index rebuilt (${reindexFts().entities} entities)`;
1169
+ }
1170
+ finally {
1171
+ closeDatabase();
1172
+ }
1173
+ },
1174
+ 'chmod-db': () => {
1175
+ fs.chmodSync(getDbPath(), 0o600);
1176
+ return `permissions restored: chmod 600 ${getDbPath()}`;
1177
+ },
1178
+ };
1179
+ const fixable = result.checks.filter((c) => c.fixId && (c.status === 'warn' || c.status === 'fail'));
1180
+ if (fixable.length === 0) {
1181
+ console.log('Nothing on the --fix whitelist to apply.');
1182
+ }
1183
+ else {
1184
+ if (!opts.yes && !process.stdin.isTTY) {
1185
+ console.error('Not a terminal and --yes not given — nothing was changed. Re-run with: memesh doctor --fix --yes');
1186
+ process.exit(1);
1187
+ }
1188
+ let rl = null;
1189
+ if (!opts.yes) {
1190
+ const { createInterface } = await import('node:readline/promises');
1191
+ rl = createInterface({ input: process.stdin, output: process.stdout });
1192
+ }
1193
+ for (const check of fixable) {
1194
+ console.log(`\n${check.label}: ${check.summary}`);
1195
+ if (rl) {
1196
+ const answer = (await rl.question(`Apply fix (${check.fixId})? [y/N] `)).trim().toLowerCase();
1197
+ if (answer !== 'y' && answer !== 'yes') {
1198
+ console.log(' skipped');
1199
+ continue;
1200
+ }
1201
+ }
1202
+ try {
1203
+ console.log(` ✅ ${FIX_ACTIONS[check.fixId]()}`);
1204
+ }
1205
+ catch (err) {
1206
+ console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
1207
+ }
1208
+ }
1209
+ rl?.close();
1210
+ console.log('\nAfter fixes:');
1211
+ const before = new Map(result.checks.map((c) => [c.id, c.status]));
1212
+ const fixedIds = new Set(fixable.map((c) => c.id));
1213
+ result = await runDoctor({
1214
+ packageRoot,
1215
+ packageVersion: pkg.version,
1216
+ probeHttp: false,
1217
+ probeCapabilities: false,
1218
+ httpBaseUrl: opts.url,
1219
+ });
1220
+ for (const c of result.checks) {
1221
+ const was = before.get(c.id);
1222
+ if (fixedIds.has(c.id) && was && was !== c.status)
1223
+ console.log(` ${c.label}: ${was} → ${c.status}`);
1224
+ }
1225
+ }
1226
+ }
920
1227
  if (opts.json) {
921
1228
  console.log(JSON.stringify(result, null, 2));
922
1229
  }
@@ -1151,7 +1458,8 @@ dreamCmd
1151
1458
  console.log(`${proposals.length} ${opts.status} proposal(s):`);
1152
1459
  console.log('');
1153
1460
  for (const p of proposals) {
1154
- const srcLabel = p.source_kind === 'transcript' ? ' (transcript)' : '';
1461
+ const srcLabel = p.kind === 'relation' ? ' (conflict)'
1462
+ : p.source_kind === 'transcript' ? ' (transcript)' : '';
1155
1463
  console.log(` #${p.id} [${p.project}/${p.cluster_key}]${srcLabel} ${p.source_count} source(s) → "${p.digest_name}"`);
1156
1464
  if (p.digest_observations_preview !== null) {
1157
1465
  console.log(` ${p.digest_observations_preview}`);
@@ -1162,6 +1470,49 @@ dreamCmd
1162
1470
  console.log(`Inspect: memesh dream show <id> | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
1163
1471
  });
1164
1472
  });
1473
+ dreamCmd
1474
+ .command('conflicts')
1475
+ .description('Judge semantically-close memory pairs for contradiction / supersession / duplication (LLM) and stage relation proposals for review')
1476
+ .option('--max-pairs <n>', 'Judge at most N of the tightest candidate pairs this run (default 20)', (v) => parseInt(v, 10))
1477
+ .option('--dry-run', 'Show how many candidates are queued without calling an LLM or writing anything')
1478
+ .action(async (opts) => {
1479
+ await withDatabase(async () => {
1480
+ const cfg = readConfig();
1481
+ const llm = detectCapabilities().llm;
1482
+ if (!llm) {
1483
+ console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
1484
+ console.error('LLM is required for `memesh dream conflicts` because "do these two entries disagree" is a semantic judgement, not a rule.');
1485
+ process.exit(1);
1486
+ }
1487
+ const { judgeConflicts, CONFLICT_JUDGE_MAX_PAIRS } = await import('../../core/conflict-judge.js');
1488
+ const { getDatabase } = await import('../../db.js');
1489
+ const result = await judgeConflicts(getDatabase(), llm, {
1490
+ maxPairs: typeof opts.maxPairs === 'number' && !Number.isNaN(opts.maxPairs) ? opts.maxPairs : CONFLICT_JUDGE_MAX_PAIRS,
1491
+ dryRun: !!opts.dryRun,
1492
+ fallbacks: cfg.llmFallbacks,
1493
+ });
1494
+ console.log(`${opts.dryRun ? '[dry-run] ' : ''}Conflict pass complete in ${result.durationMs}ms`);
1495
+ console.log(` candidate pairs available: ${result.candidatesAvailable}`);
1496
+ if (opts.dryRun) {
1497
+ console.log(' (dry run — no LLM called, nothing judged or written)');
1498
+ return;
1499
+ }
1500
+ console.log(` LLM calls: ${result.llmCalls}`);
1501
+ console.log(` judged: ${result.judged} (${result.unrelated} unrelated, remembered so they are never re-bought)`);
1502
+ console.log(` staged: ${result.staged} relation proposal(s)`);
1503
+ if (result.llmFailures > 0) {
1504
+ console.log(` failed: ${result.llmFailures} (unparseable or errored LLM responses; those pairs stay at the head of the candidate list and are retried next run)`);
1505
+ }
1506
+ if (result.aborted) {
1507
+ console.error(` ABORTED after the work above: ${result.aborted}`);
1508
+ process.exit(1);
1509
+ }
1510
+ if (result.staged > 0) {
1511
+ console.log('');
1512
+ console.log('Review: memesh dream list | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>');
1513
+ }
1514
+ });
1515
+ });
1165
1516
  dreamCmd
1166
1517
  .command('show <id>')
1167
1518
  .description('Show a proposal in full — name, type, ALL observations, tags, source — so you can review the whole thing before accepting')
@@ -1183,6 +1534,31 @@ dreamCmd
1183
1534
  console.log(`Proposal #${detail.id} [${detail.project}/${detail.cluster_key}] source: ${detail.source_kind} status: ${detail.status}`);
1184
1535
  console.log(`created: ${detail.created_at}`);
1185
1536
  console.log('');
1537
+ if (detail.kind === 'relation') {
1538
+ const rel = detail.relation;
1539
+ if (!rel) {
1540
+ console.error('relation payload is corrupt — reject this proposal');
1541
+ process.exit(1);
1542
+ }
1543
+ const [fromE, toE] = rel.relation_type === 'supersedes' && rel.direction === 'b_supersedes_a' ? [rel.b, rel.a] : [rel.a, rel.b];
1544
+ console.log(`verdict: ${rel.verdict} (severity: ${rel.severity ?? 'unknown'})`);
1545
+ console.log(`accepting creates: ${fromE?.name} —${rel.relation_type}→ ${toE?.name}`);
1546
+ if (rel.relation_type === 'supersedes') {
1547
+ console.log(` survivor: ${fromE?.name} (the arrow points from the surviving claim to the obsolete one)`);
1548
+ }
1549
+ console.log(`rationale: ${rel.rationale ?? '(none given)'}`);
1550
+ if (rel.recommended_action)
1551
+ console.log(`recommended action: ${rel.recommended_action}`);
1552
+ if (rel.excerpts?.a || rel.excerpts?.b) {
1553
+ console.log(`excerpt A (${rel.a?.name}): ${rel.excerpts?.a ?? ''}`);
1554
+ console.log(`excerpt B (${rel.b?.name}): ${rel.excerpts?.b ?? ''}`);
1555
+ }
1556
+ if (typeof rel.cosine_distance === 'number')
1557
+ console.log(`cosine distance: ${rel.cosine_distance.toFixed(3)}`);
1558
+ console.log('');
1559
+ console.log(`Accept: memesh dream accept ${detail.id} | Reject: memesh dream reject ${detail.id}`);
1560
+ return;
1561
+ }
1186
1562
  console.log(`name: ${detail.digest.name}`);
1187
1563
  console.log(`type: ${detail.digest.type}`);
1188
1564
  console.log(`observations (${detail.digest.observations.length}):`);
@@ -1350,7 +1726,7 @@ program
1350
1726
  }
1351
1727
  }
1352
1728
  body = redactUserPaths(redactSecrets(body));
1353
- const url = `https://github.com/PCIRCLE-AI/memesh-llm-memory/issues/new?title=${encodeURIComponent(`[${typeLabel}] `)}&body=${encodeURIComponent(body)}&labels=${encodeURIComponent(labels)}`;
1729
+ const url = `https://github.com/PCIRCLE-AI/memesh/issues/new?title=${encodeURIComponent(`[${typeLabel}] `)}&body=${encodeURIComponent(body)}&labels=${encodeURIComponent(labels)}`;
1354
1730
  if (opts.open === false) {
1355
1731
  console.log(url);
1356
1732
  return;