@pcircle/memesh 4.5.0 → 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 (256) hide show
  1. package/.claude-plugin/marketplace.json +5 -3
  2. package/.claude-plugin/plugin.json +6 -4
  3. package/.mcp.json +1 -1
  4. package/AGENTS.md +95 -0
  5. package/README.de.md +130 -39
  6. package/README.md +188 -43
  7. package/README.zh-TW.md +133 -41
  8. package/dashboard/dist/index.html +9 -9
  9. package/dist/cli/view-live.js +3 -3
  10. package/dist/core/analytics.d.ts +3 -3
  11. package/dist/core/analytics.d.ts.map +1 -1
  12. package/dist/core/analytics.js.map +1 -1
  13. package/dist/core/auto-tagger.d.ts.map +1 -1
  14. package/dist/core/auto-tagger.js +4 -9
  15. package/dist/core/auto-tagger.js.map +1 -1
  16. package/dist/core/briefing.d.ts +8 -0
  17. package/dist/core/briefing.d.ts.map +1 -0
  18. package/dist/core/briefing.js +91 -0
  19. package/dist/core/briefing.js.map +1 -0
  20. package/dist/core/capture-flag.d.ts +5 -0
  21. package/dist/core/capture-flag.d.ts.map +1 -0
  22. package/dist/core/capture-flag.js +10 -0
  23. package/dist/core/capture-flag.js.map +1 -0
  24. package/dist/core/config.d.ts +0 -1
  25. package/dist/core/config.d.ts.map +1 -1
  26. package/dist/core/config.js +2 -1
  27. package/dist/core/config.js.map +1 -1
  28. package/dist/core/conflict-candidates.d.ts +20 -0
  29. package/dist/core/conflict-candidates.d.ts.map +1 -0
  30. package/dist/core/conflict-candidates.js +79 -0
  31. package/dist/core/conflict-candidates.js.map +1 -0
  32. package/dist/core/conflict-judge.d.ts +47 -0
  33. package/dist/core/conflict-judge.d.ts.map +1 -0
  34. package/dist/core/conflict-judge.js +189 -0
  35. package/dist/core/conflict-judge.js.map +1 -0
  36. package/dist/core/demo.d.ts +2 -2
  37. package/dist/core/demo.d.ts.map +1 -1
  38. package/dist/core/demo.js.map +1 -1
  39. package/dist/core/digest-validator.d.ts.map +1 -1
  40. package/dist/core/digest-validator.js +3 -5
  41. package/dist/core/digest-validator.js.map +1 -1
  42. package/dist/core/doctor.d.ts +3 -0
  43. package/dist/core/doctor.d.ts.map +1 -1
  44. package/dist/core/doctor.js +207 -85
  45. package/dist/core/doctor.js.map +1 -1
  46. package/dist/core/dreamer.d.ts +20 -9
  47. package/dist/core/dreamer.d.ts.map +1 -1
  48. package/dist/core/dreamer.js +416 -58
  49. package/dist/core/dreamer.js.map +1 -1
  50. package/dist/core/embedder.d.ts +7 -5
  51. package/dist/core/embedder.d.ts.map +1 -1
  52. package/dist/core/embedder.js +38 -9
  53. package/dist/core/embedder.js.map +1 -1
  54. package/dist/core/extractor.d.ts.map +1 -1
  55. package/dist/core/extractor.js +2 -1
  56. package/dist/core/extractor.js.map +1 -1
  57. package/dist/core/failure-analyzer.d.ts.map +1 -1
  58. package/dist/core/failure-analyzer.js +7 -12
  59. package/dist/core/failure-analyzer.js.map +1 -1
  60. package/dist/core/graph.d.ts +4 -4
  61. package/dist/core/graph.d.ts.map +1 -1
  62. package/dist/core/graph.js.map +1 -1
  63. package/dist/core/install-channel.d.ts +1 -1
  64. package/dist/core/install-channel.d.ts.map +1 -1
  65. package/dist/core/install-channel.js +16 -5
  66. package/dist/core/install-channel.js.map +1 -1
  67. package/dist/core/install-hooks.d.ts +6 -0
  68. package/dist/core/install-hooks.d.ts.map +1 -1
  69. package/dist/core/install-hooks.js +0 -0
  70. package/dist/core/install-hooks.js.map +1 -1
  71. package/dist/core/json-utils.d.ts +1 -0
  72. package/dist/core/json-utils.d.ts.map +1 -1
  73. package/dist/core/json-utils.js +19 -10
  74. package/dist/core/json-utils.js.map +1 -1
  75. package/dist/core/kg-backfill.d.ts +3 -4
  76. package/dist/core/kg-backfill.d.ts.map +1 -1
  77. package/dist/core/kg-backfill.js +1 -4
  78. package/dist/core/kg-backfill.js.map +1 -1
  79. package/dist/core/lesson-engine.d.ts +1 -0
  80. package/dist/core/lesson-engine.d.ts.map +1 -1
  81. package/dist/core/lesson-engine.js +1 -0
  82. package/dist/core/lesson-engine.js.map +1 -1
  83. package/dist/core/lifecycle.d.ts +4 -4
  84. package/dist/core/lifecycle.d.ts.map +1 -1
  85. package/dist/core/lifecycle.js +15 -22
  86. package/dist/core/lifecycle.js.map +1 -1
  87. package/dist/core/llm-client.d.ts.map +1 -1
  88. package/dist/core/llm-client.js +3 -6
  89. package/dist/core/llm-client.js.map +1 -1
  90. package/dist/core/llm-telemetry.d.ts +4 -4
  91. package/dist/core/llm-telemetry.d.ts.map +1 -1
  92. package/dist/core/llm-telemetry.js +1 -1
  93. package/dist/core/llm-telemetry.js.map +1 -1
  94. package/dist/core/memory-tool.d.ts.map +1 -1
  95. package/dist/core/memory-tool.js +8 -4
  96. package/dist/core/memory-tool.js.map +1 -1
  97. package/dist/core/operations.d.ts.map +1 -1
  98. package/dist/core/operations.js +41 -17
  99. package/dist/core/operations.js.map +1 -1
  100. package/dist/core/paths.d.ts +3 -0
  101. package/dist/core/paths.d.ts.map +1 -1
  102. package/dist/core/paths.js +67 -1
  103. package/dist/core/paths.js.map +1 -1
  104. package/dist/core/patterns.d.ts +2 -2
  105. package/dist/core/patterns.d.ts.map +1 -1
  106. package/dist/core/patterns.js.map +1 -1
  107. package/dist/core/project-tags.d.ts +3 -3
  108. package/dist/core/project-tags.d.ts.map +1 -1
  109. package/dist/core/project-tags.js.map +1 -1
  110. package/dist/core/projects.d.ts +2 -2
  111. package/dist/core/projects.d.ts.map +1 -1
  112. package/dist/core/projects.js.map +1 -1
  113. package/dist/core/prompt-safety.d.ts +1 -0
  114. package/dist/core/prompt-safety.d.ts.map +1 -1
  115. package/dist/core/prompt-safety.js +7 -0
  116. package/dist/core/prompt-safety.js.map +1 -1
  117. package/dist/core/schema-export.d.ts.map +1 -1
  118. package/dist/core/schema-export.js +27 -30
  119. package/dist/core/schema-export.js.map +1 -1
  120. package/dist/core/serializer.d.ts.map +1 -1
  121. package/dist/core/serializer.js +45 -4
  122. package/dist/core/serializer.js.map +1 -1
  123. package/dist/core/setup.d.ts +29 -0
  124. package/dist/core/setup.d.ts.map +1 -0
  125. package/dist/core/setup.js +127 -0
  126. package/dist/core/setup.js.map +1 -0
  127. package/dist/core/stats.d.ts +2 -2
  128. package/dist/core/stats.d.ts.map +1 -1
  129. package/dist/core/stats.js.map +1 -1
  130. package/dist/core/task-state-store.d.ts +17 -0
  131. package/dist/core/task-state-store.d.ts.map +1 -0
  132. package/dist/core/task-state-store.js +45 -0
  133. package/dist/core/task-state-store.js.map +1 -0
  134. package/dist/core/task-state.d.ts +19 -0
  135. package/dist/core/task-state.d.ts.map +1 -0
  136. package/dist/core/task-state.js +91 -0
  137. package/dist/core/task-state.js.map +1 -0
  138. package/dist/core/time-utils.d.ts +2 -0
  139. package/dist/core/time-utils.d.ts.map +1 -0
  140. package/dist/core/time-utils.js +14 -0
  141. package/dist/core/time-utils.js.map +1 -0
  142. package/dist/core/title.d.ts +5 -0
  143. package/dist/core/title.d.ts.map +1 -0
  144. package/dist/core/title.js +14 -0
  145. package/dist/core/title.js.map +1 -0
  146. package/dist/core/transcript-extractor.d.ts +5 -6
  147. package/dist/core/transcript-extractor.d.ts.map +1 -1
  148. package/dist/core/transcript-extractor.js +4 -24
  149. package/dist/core/transcript-extractor.js.map +1 -1
  150. package/dist/core/transcript-source.d.ts.map +1 -1
  151. package/dist/core/transcript-source.js +2 -3
  152. package/dist/core/transcript-source.js.map +1 -1
  153. package/dist/core/types.d.ts +21 -7
  154. package/dist/core/types.d.ts.map +1 -1
  155. package/dist/core/types.js +2 -0
  156. package/dist/core/types.js.map +1 -1
  157. package/dist/core/work-topology.d.ts +33 -0
  158. package/dist/core/work-topology.d.ts.map +1 -0
  159. package/dist/core/work-topology.js +183 -0
  160. package/dist/core/work-topology.js.map +1 -0
  161. package/dist/db.d.ts +5 -10
  162. package/dist/db.d.ts.map +1 -1
  163. package/dist/db.js +194 -196
  164. package/dist/db.js.map +1 -1
  165. package/dist/knowledge-graph.d.ts +4 -2
  166. package/dist/knowledge-graph.d.ts.map +1 -1
  167. package/dist/knowledge-graph.js +68 -49
  168. package/dist/knowledge-graph.js.map +1 -1
  169. package/dist/mcp/server.js +2 -1
  170. package/dist/mcp/server.js.map +1 -1
  171. package/dist/skills-manifest.json +61 -36
  172. package/dist/storage/conflicts.d.ts +3 -3
  173. package/dist/storage/conflicts.d.ts.map +1 -1
  174. package/dist/storage/conflicts.js +2 -7
  175. package/dist/storage/conflicts.js.map +1 -1
  176. package/dist/storage/fts-index.d.ts +6 -4
  177. package/dist/storage/fts-index.d.ts.map +1 -1
  178. package/dist/storage/fts-index.js +16 -4
  179. package/dist/storage/fts-index.js.map +1 -1
  180. package/dist/storage/schema.d.ts +20 -0
  181. package/dist/storage/schema.d.ts.map +1 -0
  182. package/dist/storage/schema.js +274 -0
  183. package/dist/storage/schema.js.map +1 -0
  184. package/dist/storage/sqlite.d.ts +20 -0
  185. package/dist/storage/sqlite.d.ts.map +1 -0
  186. package/dist/storage/sqlite.js +64 -0
  187. package/dist/storage/sqlite.js.map +1 -0
  188. package/dist/storage/vector-index.d.ts +3 -0
  189. package/dist/storage/vector-index.d.ts.map +1 -0
  190. package/dist/storage/vector-index.js +7 -0
  191. package/dist/storage/vector-index.js.map +1 -0
  192. package/dist/transports/cli/cli.d.ts +1 -4
  193. package/dist/transports/cli/cli.d.ts.map +1 -1
  194. package/dist/transports/cli/cli.js +494 -76
  195. package/dist/transports/cli/cli.js.map +1 -1
  196. package/dist/transports/http/retired-routes.d.ts.map +1 -1
  197. package/dist/transports/http/retired-routes.js +1 -0
  198. package/dist/transports/http/retired-routes.js.map +1 -1
  199. package/dist/transports/http/server.d.ts.map +1 -1
  200. package/dist/transports/http/server.js +243 -323
  201. package/dist/transports/http/server.js.map +1 -1
  202. package/dist/transports/mcp/handlers.d.ts +48 -94
  203. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  204. package/dist/transports/mcp/handlers.js +77 -56
  205. package/dist/transports/mcp/handlers.js.map +1 -1
  206. package/dist/transports/schemas.d.ts +31 -40
  207. package/dist/transports/schemas.d.ts.map +1 -1
  208. package/dist/transports/schemas.js +31 -37
  209. package/dist/transports/schemas.js.map +1 -1
  210. package/hooks/hooks.json +0 -10
  211. package/llms-install.md +138 -0
  212. package/package.json +19 -18
  213. package/scripts/hooks/_generated/capture-flag.js +17 -0
  214. package/scripts/hooks/_generated/core-paths.js +67 -1
  215. package/scripts/hooks/_generated/fts-index.js +16 -4
  216. package/scripts/hooks/_generated/schema.js +281 -0
  217. package/scripts/hooks/_generated/sqlite.js +71 -0
  218. package/scripts/hooks/_generated/task-state.js +98 -0
  219. package/scripts/hooks/_generated/time-utils.js +21 -0
  220. package/scripts/hooks/_generated/title.js +21 -0
  221. package/scripts/hooks/_generated/work-topology.js +190 -0
  222. package/scripts/hooks/_shared.js +269 -534
  223. package/scripts/hooks/post-commit.js +55 -10
  224. package/scripts/hooks/pre-compact.js +22 -7
  225. package/scripts/hooks/pre-edit-recall.js +9 -11
  226. package/scripts/hooks/session-start.js +230 -106
  227. package/scripts/hooks/session-summary.js +176 -73
  228. package/scripts/hooks/user-prompt-intent.js +3 -2
  229. package/skills/memesh/SKILL.md +97 -77
  230. package/README.es.md +0 -470
  231. package/README.fr.md +0 -462
  232. package/README.ja.md +0 -470
  233. package/README.ko.md +0 -470
  234. package/README.pt.md +0 -462
  235. package/README.th.md +0 -463
  236. package/README.vi.md +0 -462
  237. package/README.zh-CN.md +0 -469
  238. package/dist/cli/view.d.ts +0 -3
  239. package/dist/cli/view.d.ts.map +0 -1
  240. package/dist/cli/view.js +0 -523
  241. package/dist/cli/view.js.map +0 -1
  242. package/dist/core/skill-usage-log.d.ts +0 -11
  243. package/dist/core/skill-usage-log.d.ts.map +0 -1
  244. package/dist/core/skill-usage-log.js +0 -125
  245. package/dist/core/skill-usage-log.js.map +0 -1
  246. package/dist/core/verifier.d.ts +0 -40
  247. package/dist/core/verifier.d.ts.map +0 -1
  248. package/dist/core/verifier.js +0 -206
  249. package/dist/core/verifier.js.map +0 -1
  250. package/dist/mcp/launcher.d.ts +0 -3
  251. package/dist/mcp/launcher.d.ts.map +0 -1
  252. package/dist/mcp/launcher.js +0 -37
  253. package/dist/mcp/launcher.js.map +0 -1
  254. package/scripts/hooks/pre-bash-orchestration-nudge.js +0 -155
  255. package/scripts/postinstall-rebuild.mjs +0 -41
  256. package/skills/agentic-orchestration/SKILL.md +0 -399
@@ -6,11 +6,16 @@ import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { openDatabase, closeDatabase, getDatabase, reindexFts, allowVectorIndexRebuild } from '../../db.js';
8
8
  import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn, reindex, setPinned } from '../../core/operations.js';
9
- import { verifyAgentWork } from '../../core/verifier.js';
10
9
  import { readConfig, writeConfig, maskApiKey, detectCapabilities } from '../../core/config.js';
11
10
  import { MAX_LANGUAGE_LENGTH, languageValueError } from '../../core/output-language.js';
12
- import { getDbPath } from '../../core/paths.js';
11
+ import { getDbPath, homeDir, redactSecrets, redactUserPaths } from '../../core/paths.js';
13
12
  import { flushPendingEmbeddings, canRefillVectorIndex } from '../../core/embedder.js';
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 {
@@ -20,13 +25,40 @@ async function withDatabase(fn) {
20
25
  closeDatabase();
21
26
  }
22
27
  }
28
+ function requireOneOf(value, allowed, flag) {
29
+ if (value === undefined || allowed.includes(value))
30
+ return;
31
+ console.error(`Error: ${flag} "${value}" is not valid. Use one of: ${allowed.join(', ')}.`);
32
+ process.exit(1);
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
+ }
23
55
  const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../package.json');
24
56
  const packageRoot = path.dirname(packageJsonPath);
25
57
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
26
58
  const program = new Command();
27
59
  program
28
60
  .name('memesh')
29
- .description('MeMesh — Local memory for Claude Code and MCP coding agents')
61
+ .description('MeMesh — Agentic memory for coding agents')
30
62
  .version(pkg.version)
31
63
  .allowExcessArguments(true)
32
64
  .showSuggestionAfterError(true);
@@ -36,11 +68,15 @@ program
36
68
  .description('Store knowledge as an entity (use flags for explicit form, or positional text for quick capture)')
37
69
  .option('--name <name>', 'Entity name')
38
70
  .option('--type <type>', 'Entity type')
71
+ .option('--title <title>', 'Short human-readable label shown as the headline (name stays the stable machine key)')
39
72
  .option('--obs <observations...>', 'Observations (space-separated)')
40
73
  .option('--tags <tags...>', 'Tags (space-separated)')
41
- .option('--namespace <namespace>', 'Namespace: personal, team, or global (default: personal)')
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.')
75
+ .option('--supersedes <name...>', 'This memory replaces the named one — ARCHIVES it immediately (recoverable; nothing is deleted)')
76
+ .option('--contradicts <name...>', 'This memory cannot both be true with the named one — both surface as a conflict on every recall')
42
77
  .option('--json', 'Output as JSON')
43
78
  .action(async (text, opts) => {
79
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
44
80
  if (text && !opts.name && !opts.type) {
45
81
  const slug = String(text)
46
82
  .toLowerCase()
@@ -51,6 +87,8 @@ program
51
87
  const suffix = randomBytes(3).toString('hex');
52
88
  opts.name = `quick-${date}-${slug || 'note'}-${suffix}`;
53
89
  opts.type = 'note';
90
+ if (!opts.title)
91
+ opts.title = String(text).slice(0, 200);
54
92
  if (!opts.obs || opts.obs.length === 0)
55
93
  opts.obs = [String(text)];
56
94
  else
@@ -68,20 +106,43 @@ program
68
106
  ' memesh remember "Use OAuth 2.0 with PKCE"');
69
107
  process.exit(1);
70
108
  }
109
+ const relations = [
110
+ ...(opts.supersedes ?? []).map(to => ({ to, type: 'supersedes' })),
111
+ ...(opts.contradicts ?? []).map(to => ({ to, type: 'contradicts' })),
112
+ ];
71
113
  await withDatabase(async () => {
72
114
  const result = remember({
73
115
  name: opts.name,
74
116
  type: opts.type,
117
+ title: opts.title,
75
118
  observations: opts.obs,
76
119
  tags: opts.tags,
77
120
  namespace: opts.namespace,
121
+ relations: relations.length > 0 ? relations : undefined,
122
+ sourceHost: 'cli',
78
123
  });
79
124
  if (opts.json) {
80
125
  console.log(JSON.stringify(result));
81
126
  }
82
127
  else {
83
128
  console.log(`✅ Stored "${result.name}" (${result.observations} observations, ${result.tags} tags)`);
129
+ if (result.movedFromNamespace) {
130
+ console.log(` moved: ${result.movedFromNamespace} → ${opts.namespace} (was in ${result.movedFromNamespace}; re-run with --namespace ${result.movedFromNamespace} to put it back)`);
131
+ }
132
+ if (result.superseded?.length) {
133
+ console.log(` archived as superseded: ${result.superseded.join(', ')}`);
134
+ }
135
+ const contradicted = (result.relationsCreated ?? [])
136
+ .filter(r => r.type === 'contradicts')
137
+ .map(r => r.to);
138
+ if (contradicted.length > 0) {
139
+ console.log(` conflicts stated: ${contradicted.join(', ')}`);
140
+ }
141
+ for (const err of result.relationErrors ?? [])
142
+ console.error(` ⚠️ ${err}`);
84
143
  }
144
+ if (result.relationErrors?.length)
145
+ process.exitCode = 1;
85
146
  await flushPendingEmbeddings();
86
147
  });
87
148
  });
@@ -96,6 +157,7 @@ program
96
157
  .option('--cross-project', 'Search across all project tags (ignores --tag filter)')
97
158
  .option('--json', 'Output as JSON')
98
159
  .action(async (query, opts) => {
160
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
99
161
  await withDatabase(async () => {
100
162
  const { entities, conflicts } = await recallWithConflicts({
101
163
  query: query || undefined,
@@ -173,8 +235,14 @@ program
173
235
  else if (result.observation_removed) {
174
236
  console.log(`✂️ Removed observation (${result.remaining_observations} remaining)`);
175
237
  }
238
+ else if (opts.observation && result.entity_found) {
239
+ console.log(`Entity "${opts.name}" has no observation matching that text (${result.remaining_observations} observation(s) present).`);
240
+ console.log(`See them with: memesh recall "${opts.name}" --json`);
241
+ process.exitCode = 1;
242
+ }
176
243
  else {
177
244
  console.log(`Entity "${opts.name}" not found`);
245
+ process.exitCode = 1;
178
246
  }
179
247
  });
180
248
  });
@@ -219,6 +287,31 @@ program
219
287
  console.error('so it is not a like-for-like replacement for compressing one named entity.');
220
288
  process.exitCode = 1;
221
289
  });
290
+ program
291
+ .command('verify')
292
+ .description('(retired) Removed with the agentic-orchestration experiment — see the message this prints')
293
+ .allowUnknownOption()
294
+ .action(() => {
295
+ console.error('`memesh verify` has been retired, along with the agentic-orchestration experiment.');
296
+ console.error('');
297
+ console.error('It recorded a verification report for background-agent work. The protocol it');
298
+ console.error('served was removed without ever leaving opt-in. Run your own verification');
299
+ console.error('(typecheck / tests / lint) and store conclusions with `memesh remember` if you');
300
+ console.error('want them remembered. Existing verification_record entities are untouched.');
301
+ process.exitCode = 1;
302
+ });
303
+ program
304
+ .command('patterns')
305
+ .description('(retired) Removed with the agentic-orchestration experiment — see the message this prints')
306
+ .allowUnknownOption()
307
+ .action(() => {
308
+ console.error('`memesh patterns` has been retired, along with the agentic-orchestration experiment.');
309
+ console.error('');
310
+ console.error('It displayed the experiment\'s local skill-usage telemetry, which is no longer');
311
+ console.error('written. A leftover ~/.memesh/skill-usage.jsonl is inert and safe to delete.');
312
+ console.error('For work-pattern insights, use the `user_patterns` MCP tool or GET /v1/patterns.');
313
+ process.exitCode = 1;
314
+ });
222
315
  program
223
316
  .command('export')
224
317
  .description('Export memories as JSON. Defaults to stdout (pipe-friendly); use `-o <file>` to write directly.')
@@ -227,6 +320,7 @@ program
227
320
  .option('--limit <n>', 'Max entities to export', '1000')
228
321
  .option('-o, --out <file>', 'Write JSON to <file> instead of stdout. Parent directory must exist.')
229
322
  .action(async (opts) => {
323
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
230
324
  await withDatabase(() => {
231
325
  const result = exportMemories({
232
326
  tag: opts.tag,
@@ -235,6 +329,12 @@ program
235
329
  });
236
330
  const json = JSON.stringify(result, null, 2);
237
331
  if (opts.out) {
332
+ const outDir = path.dirname(path.resolve(opts.out));
333
+ if (!fs.existsSync(outDir)) {
334
+ console.error(`Error: cannot write ${opts.out} — the directory ${outDir} does not exist.`);
335
+ console.error(` Create it first (mkdir -p "${outDir}"), or drop -o to write to stdout.`);
336
+ process.exit(1);
337
+ }
238
338
  fs.writeFileSync(opts.out, json + '\n');
239
339
  process.stderr.write(`✅ Exported ${result.entity_count} entities to ${opts.out}\n`);
240
340
  }
@@ -250,6 +350,8 @@ program
250
350
  .option('--namespace <ns>', 'Override namespace for all imported entities')
251
351
  .option('--merge <strategy>', 'Merge strategy: skip | overwrite | append', 'skip')
252
352
  .action(async (file, opts) => {
353
+ requireOneOf(opts.merge, ['skip', 'overwrite', 'append'], '--merge');
354
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
253
355
  await withDatabase(() => {
254
356
  let raw;
255
357
  try {
@@ -281,11 +383,18 @@ program
281
383
  console.error(` Try: memesh export > my-export.json && memesh import my-export.json`);
282
384
  process.exit(1);
283
385
  }
284
- const result = importMemories({
285
- data: data,
286
- namespace: opts.namespace,
287
- merge_strategy: opts.merge,
288
- });
386
+ let result;
387
+ try {
388
+ result = importMemories({
389
+ data: data,
390
+ namespace: opts.namespace,
391
+ merge_strategy: opts.merge,
392
+ });
393
+ }
394
+ catch (err) {
395
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
396
+ process.exit(1);
397
+ }
289
398
  console.log(`Imported: ${result.imported}, Skipped: ${result.skipped}, Appended: ${result.appended}`);
290
399
  if (result.errors.length > 0) {
291
400
  console.error(`Errors:\n ${result.errors.join('\n ')}`);
@@ -303,6 +412,7 @@ program
303
412
  .option('--severity <level>', 'Severity: critical|major|minor', 'minor')
304
413
  .option('--json', 'Output as JSON')
305
414
  .action(async (opts) => {
415
+ requireOneOf(opts.severity, ['critical', 'major', 'minor'], '--severity');
306
416
  await withDatabase(() => {
307
417
  const result = learn({
308
418
  error: opts.error,
@@ -310,6 +420,7 @@ program
310
420
  root_cause: opts.rootCause,
311
421
  prevention: opts.prevention,
312
422
  severity: opts.severity,
423
+ sourceHost: 'cli',
313
424
  });
314
425
  if (opts.json) {
315
426
  console.log(JSON.stringify(result));
@@ -320,47 +431,162 @@ program
320
431
  });
321
432
  });
322
433
  program
323
- .command('verify <workdir>')
324
- .description('Record a verification report for agent work in <workdir>. ' +
325
- 'Exit codes: 0 pass, 1 fail, 2 unverified (nothing was checked).')
326
- .requiredOption('--agent-id <id>', 'Identifier for the agent being verified')
327
- .option('--base <ref>', 'Git ref to diff against (default: merge-base with origin/main)')
328
- .option('--expected-files <n>', 'Number of files the agent claimed to change', (v) => parseInt(v, 10))
329
- .option('--report <path>', 'Path to a JSON file with pre-computed report (typecheck/tests/lint/build)')
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)')
330
437
  .option('--json', 'Output as JSON')
331
- .action(async (workdir, opts) => {
438
+ .action(async (opts) => {
332
439
  await withDatabase(() => {
333
- let externalReport;
334
- let result;
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) => {
335
461
  try {
336
- if (opts.report) {
337
- const raw = fs.readFileSync(opts.report, 'utf8');
338
- externalReport = JSON.parse(raw);
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
+ }
339
470
  }
340
- result = verifyAgentWork({
341
- agent_id: opts.agentId,
342
- workdir: path.resolve(workdir),
343
- base: opts.base,
344
- claim: opts.expectedFiles != null ? { expected_files: opts.expectedFiles } : undefined,
345
- report: externalReport,
346
- });
471
+ const r = spawnSync(target, args, { encoding: 'utf8', shell: useShell });
472
+ return { status: r.status, stderr: r.stderr ?? '' };
347
473
  }
348
474
  catch (err) {
349
- console.error(err instanceof Error ? err.message : String(err));
350
- console.error('Nothing was checked (exit 2 = unverified).');
351
- process.exit(2);
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
+ }
352
540
  }
353
- if (opts.json) {
354
- console.log(JSON.stringify(result, null, 2));
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];
355
563
  }
356
- else {
357
- console.log(`${result.verdict.toUpperCase()} agent=${opts.agentId} entity=${result.entity_name}`);
358
- console.log(` reality: ${result.reality_check.summary}`);
359
- if (result.verdict === 'unverified') {
360
- console.log(' nothing was checked — pass --expected-files <n> and/or --report <file> to verify something.');
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;
361
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;
362
583
  }
363
- process.exit(result.verdict === 'pass' ? 0 : result.verdict === 'fail' ? 1 : 2);
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'));
364
590
  });
365
591
  });
366
592
  const configCmd = program.command('config').description('Manage configuration');
@@ -392,10 +618,10 @@ const ALLOWED_KEYS = new Set([
392
618
  'embedder.model',
393
619
  'autoUpdate',
394
620
  'sessionLimit',
395
- 'enableAgenticOrchestration',
396
621
  'autoCapture',
397
622
  'llmFallbacks',
398
623
  'language',
624
+ 'transcriptMining',
399
625
  ]);
400
626
  const KEY_VALIDATORS = {
401
627
  'llm.provider': (v) => ['anthropic', 'openai', 'ollama'].includes(v) ? null : `must be one of: anthropic, openai, ollama`,
@@ -462,7 +688,7 @@ function formatConfigValue(key, raw) {
462
688
  return JSON.stringify(raw);
463
689
  return String(raw);
464
690
  }
465
- export function buildConfigListing(config) {
691
+ function buildConfigListing(config) {
466
692
  const rows = [];
467
693
  for (const key of Array.from(ALLOWED_KEYS).sort()) {
468
694
  const raw = getNested(config, key.split('.'));
@@ -516,7 +742,10 @@ configCmd
516
742
  coerced = parseInt(value, 10);
517
743
  if (canonical === 'llmFallbacks')
518
744
  coerced = JSON.parse(value);
519
- if (canonical === 'enableAgenticOrchestration' || canonical === 'autoCapture') {
745
+ if (canonical === 'autoCapture') {
746
+ coerced = value === 'true' || value === '1';
747
+ }
748
+ if (canonical === 'transcriptMining') {
520
749
  coerced = value === 'true' || value === '1';
521
750
  }
522
751
  const config = readConfig();
@@ -524,6 +753,10 @@ configCmd
524
753
  writeConfig(config);
525
754
  const displayValue = canonical.toLowerCase().includes('key') ? maskApiKey(String(value)) : String(value);
526
755
  console.log(`✅ Set ${canonical} = ${displayValue}`);
756
+ if (canonical === 'llm.apiKey' && !config.llm?.provider) {
757
+ console.log('⚠️ No llm.provider is set, so this key is not used yet and LLM features stay off.');
758
+ console.log(' Set one with: memesh config set llm.provider <anthropic|openai|ollama>');
759
+ }
527
760
  });
528
761
  configCmd
529
762
  .command('unset')
@@ -592,10 +825,16 @@ program
592
825
  .description('Start the HTTP API server and web dashboard')
593
826
  .option('--port <port>', 'Port number', '3737')
594
827
  .option('--host <host>', 'Host to bind', '127.0.0.1')
595
- .option('--allow-remote', 'Allow binding to non-loopback hosts. A bearer token is generated and REQUIRED for every /v1 request the startup output shows where it lives and how to rotate it.')
828
+ .option('--allow-remote', 'Permit binding to a non-loopback host. Pair it with --host; on a non-loopback bind a bearer token is generated and REQUIRED for every /v1 request, and the startup output says where it lives. On the default loopback host this flag changes nothing.')
596
829
  .action(async (opts) => {
597
830
  const { startServer } = await import('../http/server.js');
598
- startServer(opts.host, parseInt(opts.port, 10), { allowRemote: opts.allowRemote, autoUpdateCheck: true });
831
+ try {
832
+ startServer(opts.host, parseInt(opts.port, 10), { allowRemote: opts.allowRemote, autoUpdateCheck: true });
833
+ }
834
+ catch (err) {
835
+ console.error(`MeMesh: ${err instanceof Error ? err.message : String(err)}`);
836
+ process.exit(1);
837
+ }
599
838
  });
600
839
  program
601
840
  .command('update')
@@ -634,6 +873,53 @@ program
634
873
  process.exit(1);
635
874
  }
636
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
+ });
637
923
  program
638
924
  .command('telemetry')
639
925
  .description('Show LLM call telemetry (per-flow scorecard for the last N days)')
@@ -641,6 +927,12 @@ program
641
927
  .option('--prune <days>', 'Delete rows older than N days BEFORE rendering (closes v4.2.0 retention gap)', (v) => parseInt(v, 10))
642
928
  .option('--json', 'Output as JSON')
643
929
  .action(async (opts) => {
930
+ for (const [flag, value] of [['--window', opts.window], ['--prune', opts.prune]]) {
931
+ if (value !== undefined && !Number.isFinite(value)) {
932
+ console.error(`Error: ${flag} needs a number of days.`);
933
+ process.exit(1);
934
+ }
935
+ }
644
936
  await withDatabase(async () => {
645
937
  const { summariseTelemetry, pruneTelemetry } = await import('../../core/llm-telemetry.js');
646
938
  let pruneResult = null;
@@ -856,15 +1148,82 @@ program
856
1148
  .option('--probe-http', 'Also probe the local HTTP server health endpoint')
857
1149
  .option('--probe', 'Make one small live call to the configured LLM to confirm it actually answers')
858
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')
859
1153
  .action(async (opts) => {
860
1154
  const { formatDoctorReport, runDoctor } = await import('../../core/doctor.js');
861
- const result = await runDoctor({
1155
+ let result = await runDoctor({
862
1156
  packageRoot,
863
1157
  packageVersion: pkg.version,
864
1158
  probeHttp: opts.probeHttp,
865
1159
  probeCapabilities: opts.probe,
866
1160
  httpBaseUrl: opts.url,
867
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
+ }
868
1227
  if (opts.json) {
869
1228
  console.log(JSON.stringify(result, null, 2));
870
1229
  }
@@ -1011,6 +1370,11 @@ dreamCmd
1011
1370
  });
1012
1371
  console.log(`${opts.dryRun ? '[dry-run] ' : ''}Dream pass complete in ${result.durationMs}ms`);
1013
1372
  console.log(` clusters scanned: ${result.clustersScanned}`);
1373
+ if (result.clusteringMode) {
1374
+ console.log(` grouped by: ${result.clusteringMode === 'semantic' ? 'meaning (embeddings)' : 'calendar week (no embeddings)'}`);
1375
+ }
1376
+ if (result.clusteringNote)
1377
+ console.log(` note: ${result.clusteringNote}`);
1014
1378
  console.log(` LLM calls: ${result.llmCalls}`);
1015
1379
  console.log(` proposals created: ${result.proposalsCreated}`);
1016
1380
  if (result.skipped.length > 0) {
@@ -1094,7 +1458,8 @@ dreamCmd
1094
1458
  console.log(`${proposals.length} ${opts.status} proposal(s):`);
1095
1459
  console.log('');
1096
1460
  for (const p of proposals) {
1097
- const srcLabel = p.source_kind === 'transcript' ? ' (transcript)' : '';
1461
+ const srcLabel = p.kind === 'relation' ? ' (conflict)'
1462
+ : p.source_kind === 'transcript' ? ' (transcript)' : '';
1098
1463
  console.log(` #${p.id} [${p.project}/${p.cluster_key}]${srcLabel} ${p.source_count} source(s) → "${p.digest_name}"`);
1099
1464
  if (p.digest_observations_preview !== null) {
1100
1465
  console.log(` ${p.digest_observations_preview}`);
@@ -1105,6 +1470,49 @@ dreamCmd
1105
1470
  console.log(`Inspect: memesh dream show <id> | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
1106
1471
  });
1107
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
+ });
1108
1516
  dreamCmd
1109
1517
  .command('show <id>')
1110
1518
  .description('Show a proposal in full — name, type, ALL observations, tags, source — so you can review the whole thing before accepting')
@@ -1126,6 +1534,31 @@ dreamCmd
1126
1534
  console.log(`Proposal #${detail.id} [${detail.project}/${detail.cluster_key}] source: ${detail.source_kind} status: ${detail.status}`);
1127
1535
  console.log(`created: ${detail.created_at}`);
1128
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
+ }
1129
1562
  console.log(`name: ${detail.digest.name}`);
1130
1563
  console.log(`type: ${detail.digest.type}`);
1131
1564
  console.log(`observations (${detail.digest.observations.length}):`);
@@ -1209,6 +1642,9 @@ program
1209
1642
  }
1210
1643
  console.log(`${opts.dryRun ? '[dry-run] ' : ''}Settings: ${result.settingsPath}`);
1211
1644
  console.log(`${opts.dryRun ? '[dry-run] Would add ' : 'Added '}${result.added} hook entr${result.added === 1 ? 'y' : 'ies'}, ${opts.dryRun ? 'would skip ' : 'skipped '}${result.skipped} already-installed.`);
1645
+ if (result.pruned > 0) {
1646
+ console.log(`${opts.dryRun ? '[dry-run] Would remove ' : 'Removed '}${result.pruned} retired memesh hook entr${result.pruned === 1 ? 'y' : 'ies'} no longer shipped by this version.`);
1647
+ }
1212
1648
  if (result.backupPath)
1213
1649
  console.log(`Backup: ${result.backupPath}`);
1214
1650
  if (result.conflicts.length > 0) {
@@ -1289,11 +1725,19 @@ program
1289
1725
  body += `\n\n---\n**System Info**\n- Version: \`${pkg.version}\`\n- Node: \`${process.version}\`\n- Platform: \`${process.platform} ${process.arch}\`\n_Diagnostics unavailable: doctor probe failed._`;
1290
1726
  }
1291
1727
  }
1292
- const url = `https://github.com/PCIRCLE-AI/memesh-llm-memory/issues/new?title=${encodeURIComponent(`[${typeLabel}] `)}&body=${encodeURIComponent(body)}&labels=${encodeURIComponent(labels)}`;
1728
+ body = redactUserPaths(redactSecrets(body));
1729
+ const url = `https://github.com/PCIRCLE-AI/memesh/issues/new?title=${encodeURIComponent(`[${typeLabel}] `)}&body=${encodeURIComponent(body)}&labels=${encodeURIComponent(labels)}`;
1293
1730
  if (opts.open === false) {
1294
1731
  console.log(url);
1295
1732
  return;
1296
1733
  }
1734
+ console.log('This will be pre-filled into a PUBLIC GitHub issue:');
1735
+ console.log('---');
1736
+ console.log(body);
1737
+ console.log('---');
1738
+ if (opts.diagnostics !== false) {
1739
+ console.log('Re-run with --no-diagnostics to leave out the install ID and the doctor report.');
1740
+ }
1297
1741
  const { spawn } = await import('child_process');
1298
1742
  const cmd = process.platform === 'darwin' ? 'open'
1299
1743
  : process.platform === 'win32' ? 'cmd'
@@ -1319,6 +1763,7 @@ program
1319
1763
  'stored embedding first. Needed only when switching embedding providers.')
1320
1764
  .option('--json', 'Output as JSON')
1321
1765
  .action(async (opts) => {
1766
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
1322
1767
  try {
1323
1768
  if (opts.fts) {
1324
1769
  if (opts.vectors) {
@@ -1403,33 +1848,6 @@ program
1403
1848
  throw err;
1404
1849
  }
1405
1850
  });
1406
- program
1407
- .command('patterns')
1408
- .description('Show local skill-usage telemetry (agentic-orchestration banner injections, verify_agent_work invocations). Local-only — never uploaded.')
1409
- .option('--json', 'Output as JSON')
1410
- .action(async (opts) => {
1411
- const { summariseSkillUsage } = await import('../../core/skill-usage-log.js');
1412
- const summary = summariseSkillUsage();
1413
- if (opts.json) {
1414
- console.log(JSON.stringify(summary, null, 2));
1415
- return;
1416
- }
1417
- console.log(`Skill usage log: ${summary.log_path}`);
1418
- console.log(` total events: ${summary.total_events}`);
1419
- console.log(` log size: ${summary.log_bytes} bytes`);
1420
- if (summary.first_event)
1421
- console.log(` first event: ${summary.first_event}`);
1422
- if (summary.last_event)
1423
- console.log(` last event: ${summary.last_event}`);
1424
- if (summary.total_events === 0) {
1425
- console.log(' (no events recorded yet — open a Claude Code session with memesh installed, or run a verification, to start collecting)');
1426
- return;
1427
- }
1428
- console.log(' events by name:');
1429
- for (const [name, count] of Object.entries(summary.events_by_name).sort((a, b) => b[1] - a[1])) {
1430
- console.log(` ${count.toString().padStart(6)} ${name}`);
1431
- }
1432
- });
1433
1851
  program
1434
1852
  .command('status')
1435
1853
  .description('Show MeMesh status and capabilities')
@@ -1444,7 +1862,7 @@ program
1444
1862
  console.log(`MeMesh v${pkg.version}`);
1445
1863
  console.log(`Search level: ${caps.searchLevel} (${caps.searchLevel === 1 ? 'Smart Mode' : 'Core'})`);
1446
1864
  console.log(`Embeddings: ${caps.embeddings}`);
1447
- console.log(`LLM: ${caps.llm ? `${caps.llm.provider} (${caps.llm.model})` : 'not configured'}`);
1865
+ console.log(`LLM: ${caps.llm ? `${caps.llm.provider} (${caps.llm.model ?? 'default'})` : 'not configured'}`);
1448
1866
  console.log(`Install method: ${installSupport.label}`);
1449
1867
  for (const line of formatUpdateCheckStatus(update)) {
1450
1868
  console.log(`\n${line}`);