@pcircle/memesh 4.4.0 → 4.5.1

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 (198) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.mcp.json +1 -1
  4. package/README.de.md +32 -12
  5. package/README.es.md +13 -12
  6. package/README.fr.md +31 -11
  7. package/README.ja.md +13 -12
  8. package/README.ko.md +14 -13
  9. package/README.md +41 -16
  10. package/README.pt.md +30 -10
  11. package/README.th.md +16 -9
  12. package/README.vi.md +32 -12
  13. package/README.zh-CN.md +14 -13
  14. package/README.zh-TW.md +14 -13
  15. package/dashboard/dist/index.html +10 -10
  16. package/dist/cli/view-live.js +1 -1
  17. package/dist/cli/view.d.ts.map +1 -1
  18. package/dist/cli/view.js +2 -2
  19. package/dist/cli/view.js.map +1 -1
  20. package/dist/core/analytics.d.ts +7 -3
  21. package/dist/core/analytics.d.ts.map +1 -1
  22. package/dist/core/analytics.js +8 -8
  23. package/dist/core/analytics.js.map +1 -1
  24. package/dist/core/auto-tagger.d.ts.map +1 -1
  25. package/dist/core/auto-tagger.js.map +1 -1
  26. package/dist/core/config.d.ts +5 -3
  27. package/dist/core/config.d.ts.map +1 -1
  28. package/dist/core/config.js +30 -14
  29. package/dist/core/config.js.map +1 -1
  30. package/dist/core/demo.d.ts +2 -2
  31. package/dist/core/demo.d.ts.map +1 -1
  32. package/dist/core/demo.js.map +1 -1
  33. package/dist/core/digest-validator.d.ts.map +1 -1
  34. package/dist/core/digest-validator.js +3 -1
  35. package/dist/core/digest-validator.js.map +1 -1
  36. package/dist/core/doctor.d.ts +3 -0
  37. package/dist/core/doctor.d.ts.map +1 -1
  38. package/dist/core/doctor.js +255 -101
  39. package/dist/core/doctor.js.map +1 -1
  40. package/dist/core/dreamer.d.ts +35 -7
  41. package/dist/core/dreamer.d.ts.map +1 -1
  42. package/dist/core/dreamer.js +394 -41
  43. package/dist/core/dreamer.js.map +1 -1
  44. package/dist/core/embedder.d.ts +3 -5
  45. package/dist/core/embedder.d.ts.map +1 -1
  46. package/dist/core/embedder.js +26 -95
  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 +2 -1
  50. package/dist/core/extractor.js.map +1 -1
  51. package/dist/core/failure-analyzer.d.ts.map +1 -1
  52. package/dist/core/failure-analyzer.js +2 -1
  53. package/dist/core/failure-analyzer.js.map +1 -1
  54. package/dist/core/graph.d.ts +4 -4
  55. package/dist/core/graph.d.ts.map +1 -1
  56. package/dist/core/graph.js.map +1 -1
  57. package/dist/core/install-hooks.d.ts +1 -0
  58. package/dist/core/install-hooks.d.ts.map +1 -1
  59. package/dist/core/install-hooks.js +0 -0
  60. package/dist/core/install-hooks.js.map +1 -1
  61. package/dist/core/kg-backfill.d.ts +4 -4
  62. package/dist/core/kg-backfill.d.ts.map +1 -1
  63. package/dist/core/kg-backfill.js +1 -1
  64. package/dist/core/kg-backfill.js.map +1 -1
  65. package/dist/core/lesson-engine.d.ts +1 -0
  66. package/dist/core/lesson-engine.d.ts.map +1 -1
  67. package/dist/core/lesson-engine.js +1 -0
  68. package/dist/core/lesson-engine.js.map +1 -1
  69. package/dist/core/lifecycle.d.ts +4 -4
  70. package/dist/core/lifecycle.d.ts.map +1 -1
  71. package/dist/core/lifecycle.js +1 -1
  72. package/dist/core/lifecycle.js.map +1 -1
  73. package/dist/core/llm-client.d.ts.map +1 -1
  74. package/dist/core/llm-client.js +3 -6
  75. package/dist/core/llm-client.js.map +1 -1
  76. package/dist/core/llm-telemetry.d.ts +4 -4
  77. package/dist/core/llm-telemetry.d.ts.map +1 -1
  78. package/dist/core/llm-telemetry.js +1 -1
  79. package/dist/core/llm-telemetry.js.map +1 -1
  80. package/dist/core/llm-validator.d.ts +1 -0
  81. package/dist/core/llm-validator.d.ts.map +1 -1
  82. package/dist/core/llm-validator.js +33 -10
  83. package/dist/core/llm-validator.js.map +1 -1
  84. package/dist/core/memory-tool.d.ts.map +1 -1
  85. package/dist/core/memory-tool.js +4 -0
  86. package/dist/core/memory-tool.js.map +1 -1
  87. package/dist/core/operations.d.ts.map +1 -1
  88. package/dist/core/operations.js +23 -8
  89. package/dist/core/operations.js.map +1 -1
  90. package/dist/core/output-language.d.ts +6 -0
  91. package/dist/core/output-language.d.ts.map +1 -0
  92. package/dist/core/output-language.js +25 -0
  93. package/dist/core/output-language.js.map +1 -0
  94. package/dist/core/paths.d.ts +3 -0
  95. package/dist/core/paths.d.ts.map +1 -1
  96. package/dist/core/paths.js +67 -1
  97. package/dist/core/paths.js.map +1 -1
  98. package/dist/core/patterns.d.ts +2 -3
  99. package/dist/core/patterns.d.ts.map +1 -1
  100. package/dist/core/patterns.js +1 -5
  101. package/dist/core/patterns.js.map +1 -1
  102. package/dist/core/project-tags.d.ts +3 -3
  103. package/dist/core/project-tags.d.ts.map +1 -1
  104. package/dist/core/project-tags.js.map +1 -1
  105. package/dist/core/projects.d.ts +2 -2
  106. package/dist/core/projects.d.ts.map +1 -1
  107. package/dist/core/projects.js.map +1 -1
  108. package/dist/core/schema-export.d.ts.map +1 -1
  109. package/dist/core/schema-export.js +0 -34
  110. package/dist/core/schema-export.js.map +1 -1
  111. package/dist/core/serializer.d.ts.map +1 -1
  112. package/dist/core/serializer.js +45 -4
  113. package/dist/core/serializer.js.map +1 -1
  114. package/dist/core/stats.d.ts +2 -2
  115. package/dist/core/stats.d.ts.map +1 -1
  116. package/dist/core/stats.js.map +1 -1
  117. package/dist/core/transcript-extractor.d.ts +88 -0
  118. package/dist/core/transcript-extractor.d.ts.map +1 -0
  119. package/dist/core/transcript-extractor.js +417 -0
  120. package/dist/core/transcript-extractor.js.map +1 -0
  121. package/dist/core/transcript-source.d.ts +21 -0
  122. package/dist/core/transcript-source.d.ts.map +1 -0
  123. package/dist/core/transcript-source.js +142 -0
  124. package/dist/core/transcript-source.js.map +1 -0
  125. package/dist/core/types.d.ts +17 -7
  126. package/dist/core/types.d.ts.map +1 -1
  127. package/dist/core/types.js +2 -0
  128. package/dist/core/types.js.map +1 -1
  129. package/dist/db.d.ts +5 -5
  130. package/dist/db.d.ts.map +1 -1
  131. package/dist/db.js +165 -17
  132. package/dist/db.js.map +1 -1
  133. package/dist/knowledge-graph.d.ts +3 -2
  134. package/dist/knowledge-graph.d.ts.map +1 -1
  135. package/dist/knowledge-graph.js +19 -10
  136. package/dist/knowledge-graph.js.map +1 -1
  137. package/dist/mcp/server.js +2 -1
  138. package/dist/mcp/server.js.map +1 -1
  139. package/dist/skills-manifest.json +28 -33
  140. package/dist/storage/conflicts.d.ts +3 -3
  141. package/dist/storage/conflicts.d.ts.map +1 -1
  142. package/dist/storage/conflicts.js.map +1 -1
  143. package/dist/storage/fts-index.d.ts +4 -4
  144. package/dist/storage/fts-index.d.ts.map +1 -1
  145. package/dist/storage/fts-index.js.map +1 -1
  146. package/dist/storage/sqlite.d.ts +20 -0
  147. package/dist/storage/sqlite.d.ts.map +1 -0
  148. package/dist/storage/sqlite.js +64 -0
  149. package/dist/storage/sqlite.js.map +1 -0
  150. package/dist/storage/vector-index.d.ts +3 -0
  151. package/dist/storage/vector-index.d.ts.map +1 -0
  152. package/dist/storage/vector-index.js +7 -0
  153. package/dist/storage/vector-index.js.map +1 -0
  154. package/dist/transports/cli/cli.d.ts.map +1 -1
  155. package/dist/transports/cli/cli.js +278 -91
  156. package/dist/transports/cli/cli.js.map +1 -1
  157. package/dist/transports/http/retired-routes.d.ts.map +1 -1
  158. package/dist/transports/http/retired-routes.js +1 -0
  159. package/dist/transports/http/retired-routes.js.map +1 -1
  160. package/dist/transports/http/server.d.ts +7 -0
  161. package/dist/transports/http/server.d.ts.map +1 -1
  162. package/dist/transports/http/server.js +165 -79
  163. package/dist/transports/http/server.js.map +1 -1
  164. package/dist/transports/mcp/handlers.d.ts +4 -96
  165. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  166. package/dist/transports/mcp/handlers.js +26 -59
  167. package/dist/transports/mcp/handlers.js.map +1 -1
  168. package/dist/transports/schemas.d.ts +10 -30
  169. package/dist/transports/schemas.d.ts.map +1 -1
  170. package/dist/transports/schemas.js +5 -29
  171. package/dist/transports/schemas.js.map +1 -1
  172. package/hooks/hooks.json +0 -10
  173. package/package.json +6 -19
  174. package/scripts/hooks/_generated/core-paths.js +67 -1
  175. package/scripts/hooks/_generated/sqlite.js +71 -0
  176. package/scripts/hooks/_shared.js +286 -195
  177. package/scripts/hooks/post-commit.js +52 -10
  178. package/scripts/hooks/pre-compact.js +10 -7
  179. package/scripts/hooks/pre-edit-recall.js +4 -8
  180. package/scripts/hooks/session-start.js +95 -47
  181. package/scripts/hooks/session-summary.js +120 -52
  182. package/scripts/hooks/user-prompt-intent.js +3 -2
  183. package/skills/memesh/SKILL.md +3 -2
  184. package/dist/core/skill-usage-log.d.ts +0 -11
  185. package/dist/core/skill-usage-log.d.ts.map +0 -1
  186. package/dist/core/skill-usage-log.js +0 -125
  187. package/dist/core/skill-usage-log.js.map +0 -1
  188. package/dist/core/verifier.d.ts +0 -40
  189. package/dist/core/verifier.d.ts.map +0 -1
  190. package/dist/core/verifier.js +0 -206
  191. package/dist/core/verifier.js.map +0 -1
  192. package/dist/mcp/launcher.d.ts +0 -3
  193. package/dist/mcp/launcher.d.ts.map +0 -1
  194. package/dist/mcp/launcher.js +0 -37
  195. package/dist/mcp/launcher.js.map +0 -1
  196. package/scripts/hooks/pre-bash-orchestration-nudge.js +0 -155
  197. package/scripts/postinstall-rebuild.mjs +0 -41
  198. package/skills/agentic-orchestration/SKILL.md +0 -399
@@ -6,10 +6,11 @@ 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
- import { getDbPath } from '../../core/paths.js';
10
+ import { MAX_LANGUAGE_LENGTH, languageValueError } from '../../core/output-language.js';
11
+ import { getDbPath, redactSecrets, redactUserPaths } from '../../core/paths.js';
12
12
  import { flushPendingEmbeddings, canRefillVectorIndex } from '../../core/embedder.js';
13
+ import { NAMESPACES } from '../../core/types.js';
13
14
  async function withDatabase(fn) {
14
15
  openDatabase();
15
16
  try {
@@ -19,6 +20,12 @@ async function withDatabase(fn) {
19
20
  closeDatabase();
20
21
  }
21
22
  }
23
+ function requireOneOf(value, allowed, flag) {
24
+ if (value === undefined || allowed.includes(value))
25
+ return;
26
+ console.error(`Error: ${flag} "${value}" is not valid. Use one of: ${allowed.join(', ')}.`);
27
+ process.exit(1);
28
+ }
22
29
  const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../package.json');
23
30
  const packageRoot = path.dirname(packageJsonPath);
24
31
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
@@ -37,9 +44,12 @@ program
37
44
  .option('--type <type>', 'Entity type')
38
45
  .option('--obs <observations...>', 'Observations (space-separated)')
39
46
  .option('--tags <tags...>', 'Tags (space-separated)')
40
- .option('--namespace <namespace>', 'Namespace: personal, team, or global (default: personal)')
47
+ .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.')
48
+ .option('--supersedes <name...>', 'This memory replaces the named one — ARCHIVES it immediately (recoverable; nothing is deleted)')
49
+ .option('--contradicts <name...>', 'This memory cannot both be true with the named one — both surface as a conflict on every recall')
41
50
  .option('--json', 'Output as JSON')
42
51
  .action(async (text, opts) => {
52
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
43
53
  if (text && !opts.name && !opts.type) {
44
54
  const slug = String(text)
45
55
  .toLowerCase()
@@ -67,6 +77,10 @@ program
67
77
  ' memesh remember "Use OAuth 2.0 with PKCE"');
68
78
  process.exit(1);
69
79
  }
80
+ const relations = [
81
+ ...(opts.supersedes ?? []).map(to => ({ to, type: 'supersedes' })),
82
+ ...(opts.contradicts ?? []).map(to => ({ to, type: 'contradicts' })),
83
+ ];
70
84
  await withDatabase(async () => {
71
85
  const result = remember({
72
86
  name: opts.name,
@@ -74,13 +88,31 @@ program
74
88
  observations: opts.obs,
75
89
  tags: opts.tags,
76
90
  namespace: opts.namespace,
91
+ relations: relations.length > 0 ? relations : undefined,
92
+ sourceHost: 'cli',
77
93
  });
78
94
  if (opts.json) {
79
95
  console.log(JSON.stringify(result));
80
96
  }
81
97
  else {
82
98
  console.log(`✅ Stored "${result.name}" (${result.observations} observations, ${result.tags} tags)`);
99
+ if (result.movedFromNamespace) {
100
+ console.log(` moved: ${result.movedFromNamespace} → ${opts.namespace} (was in ${result.movedFromNamespace}; re-run with --namespace ${result.movedFromNamespace} to put it back)`);
101
+ }
102
+ if (result.superseded?.length) {
103
+ console.log(` archived as superseded: ${result.superseded.join(', ')}`);
104
+ }
105
+ const contradicted = (result.relationsCreated ?? [])
106
+ .filter(r => r.type === 'contradicts')
107
+ .map(r => r.to);
108
+ if (contradicted.length > 0) {
109
+ console.log(` conflicts stated: ${contradicted.join(', ')}`);
110
+ }
111
+ for (const err of result.relationErrors ?? [])
112
+ console.error(` ⚠️ ${err}`);
83
113
  }
114
+ if (result.relationErrors?.length)
115
+ process.exitCode = 1;
84
116
  await flushPendingEmbeddings();
85
117
  });
86
118
  });
@@ -95,6 +127,7 @@ program
95
127
  .option('--cross-project', 'Search across all project tags (ignores --tag filter)')
96
128
  .option('--json', 'Output as JSON')
97
129
  .action(async (query, opts) => {
130
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
98
131
  await withDatabase(async () => {
99
132
  const { entities, conflicts } = await recallWithConflicts({
100
133
  query: query || undefined,
@@ -172,8 +205,14 @@ program
172
205
  else if (result.observation_removed) {
173
206
  console.log(`✂️ Removed observation (${result.remaining_observations} remaining)`);
174
207
  }
208
+ else if (opts.observation && result.entity_found) {
209
+ console.log(`Entity "${opts.name}" has no observation matching that text (${result.remaining_observations} observation(s) present).`);
210
+ console.log(`See them with: memesh recall "${opts.name}" --json`);
211
+ process.exitCode = 1;
212
+ }
175
213
  else {
176
214
  console.log(`Entity "${opts.name}" not found`);
215
+ process.exitCode = 1;
177
216
  }
178
217
  });
179
218
  });
@@ -218,6 +257,31 @@ program
218
257
  console.error('so it is not a like-for-like replacement for compressing one named entity.');
219
258
  process.exitCode = 1;
220
259
  });
260
+ program
261
+ .command('verify')
262
+ .description('(retired) Removed with the agentic-orchestration experiment — see the message this prints')
263
+ .allowUnknownOption()
264
+ .action(() => {
265
+ console.error('`memesh verify` has been retired, along with the agentic-orchestration experiment.');
266
+ console.error('');
267
+ console.error('It recorded a verification report for background-agent work. The protocol it');
268
+ console.error('served was removed without ever leaving opt-in. Run your own verification');
269
+ console.error('(typecheck / tests / lint) and store conclusions with `memesh remember` if you');
270
+ console.error('want them remembered. Existing verification_record entities are untouched.');
271
+ process.exitCode = 1;
272
+ });
273
+ program
274
+ .command('patterns')
275
+ .description('(retired) Removed with the agentic-orchestration experiment — see the message this prints')
276
+ .allowUnknownOption()
277
+ .action(() => {
278
+ console.error('`memesh patterns` has been retired, along with the agentic-orchestration experiment.');
279
+ console.error('');
280
+ console.error('It displayed the experiment\'s local skill-usage telemetry, which is no longer');
281
+ console.error('written. A leftover ~/.memesh/skill-usage.jsonl is inert and safe to delete.');
282
+ console.error('For work-pattern insights, use the `user_patterns` MCP tool or GET /v1/patterns.');
283
+ process.exitCode = 1;
284
+ });
221
285
  program
222
286
  .command('export')
223
287
  .description('Export memories as JSON. Defaults to stdout (pipe-friendly); use `-o <file>` to write directly.')
@@ -226,6 +290,7 @@ program
226
290
  .option('--limit <n>', 'Max entities to export', '1000')
227
291
  .option('-o, --out <file>', 'Write JSON to <file> instead of stdout. Parent directory must exist.')
228
292
  .action(async (opts) => {
293
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
229
294
  await withDatabase(() => {
230
295
  const result = exportMemories({
231
296
  tag: opts.tag,
@@ -234,6 +299,12 @@ program
234
299
  });
235
300
  const json = JSON.stringify(result, null, 2);
236
301
  if (opts.out) {
302
+ const outDir = path.dirname(path.resolve(opts.out));
303
+ if (!fs.existsSync(outDir)) {
304
+ console.error(`Error: cannot write ${opts.out} — the directory ${outDir} does not exist.`);
305
+ console.error(` Create it first (mkdir -p "${outDir}"), or drop -o to write to stdout.`);
306
+ process.exit(1);
307
+ }
237
308
  fs.writeFileSync(opts.out, json + '\n');
238
309
  process.stderr.write(`✅ Exported ${result.entity_count} entities to ${opts.out}\n`);
239
310
  }
@@ -249,6 +320,8 @@ program
249
320
  .option('--namespace <ns>', 'Override namespace for all imported entities')
250
321
  .option('--merge <strategy>', 'Merge strategy: skip | overwrite | append', 'skip')
251
322
  .action(async (file, opts) => {
323
+ requireOneOf(opts.merge, ['skip', 'overwrite', 'append'], '--merge');
324
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
252
325
  await withDatabase(() => {
253
326
  let raw;
254
327
  try {
@@ -280,11 +353,18 @@ program
280
353
  console.error(` Try: memesh export > my-export.json && memesh import my-export.json`);
281
354
  process.exit(1);
282
355
  }
283
- const result = importMemories({
284
- data: data,
285
- namespace: opts.namespace,
286
- merge_strategy: opts.merge,
287
- });
356
+ let result;
357
+ try {
358
+ result = importMemories({
359
+ data: data,
360
+ namespace: opts.namespace,
361
+ merge_strategy: opts.merge,
362
+ });
363
+ }
364
+ catch (err) {
365
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
366
+ process.exit(1);
367
+ }
288
368
  console.log(`Imported: ${result.imported}, Skipped: ${result.skipped}, Appended: ${result.appended}`);
289
369
  if (result.errors.length > 0) {
290
370
  console.error(`Errors:\n ${result.errors.join('\n ')}`);
@@ -302,6 +382,7 @@ program
302
382
  .option('--severity <level>', 'Severity: critical|major|minor', 'minor')
303
383
  .option('--json', 'Output as JSON')
304
384
  .action(async (opts) => {
385
+ requireOneOf(opts.severity, ['critical', 'major', 'minor'], '--severity');
305
386
  await withDatabase(() => {
306
387
  const result = learn({
307
388
  error: opts.error,
@@ -309,6 +390,7 @@ program
309
390
  root_cause: opts.rootCause,
310
391
  prevention: opts.prevention,
311
392
  severity: opts.severity,
393
+ sourceHost: 'cli',
312
394
  });
313
395
  if (opts.json) {
314
396
  console.log(JSON.stringify(result));
@@ -318,50 +400,6 @@ program
318
400
  }
319
401
  });
320
402
  });
321
- program
322
- .command('verify <workdir>')
323
- .description('Record a verification report for agent work in <workdir>. ' +
324
- 'Exit codes: 0 pass, 1 fail, 2 unverified (nothing was checked).')
325
- .requiredOption('--agent-id <id>', 'Identifier for the agent being verified')
326
- .option('--base <ref>', 'Git ref to diff against (default: merge-base with origin/main)')
327
- .option('--expected-files <n>', 'Number of files the agent claimed to change', (v) => parseInt(v, 10))
328
- .option('--report <path>', 'Path to a JSON file with pre-computed report (typecheck/tests/lint/build)')
329
- .option('--json', 'Output as JSON')
330
- .action(async (workdir, opts) => {
331
- await withDatabase(() => {
332
- let externalReport;
333
- let result;
334
- try {
335
- if (opts.report) {
336
- const raw = fs.readFileSync(opts.report, 'utf8');
337
- externalReport = JSON.parse(raw);
338
- }
339
- result = verifyAgentWork({
340
- agent_id: opts.agentId,
341
- workdir: path.resolve(workdir),
342
- base: opts.base,
343
- claim: opts.expectedFiles != null ? { expected_files: opts.expectedFiles } : undefined,
344
- report: externalReport,
345
- });
346
- }
347
- catch (err) {
348
- console.error(err instanceof Error ? err.message : String(err));
349
- console.error('Nothing was checked (exit 2 = unverified).');
350
- process.exit(2);
351
- }
352
- if (opts.json) {
353
- console.log(JSON.stringify(result, null, 2));
354
- }
355
- else {
356
- console.log(`${result.verdict.toUpperCase()} agent=${opts.agentId} entity=${result.entity_name}`);
357
- console.log(` reality: ${result.reality_check.summary}`);
358
- if (result.verdict === 'unverified') {
359
- console.log(' nothing was checked — pass --expected-files <n> and/or --report <file> to verify something.');
360
- }
361
- }
362
- process.exit(result.verdict === 'pass' ? 0 : result.verdict === 'fail' ? 1 : 2);
363
- });
364
- });
365
403
  const configCmd = program.command('config').description('Manage configuration');
366
404
  configCmd
367
405
  .command('list')
@@ -391,13 +429,20 @@ const ALLOWED_KEYS = new Set([
391
429
  'embedder.model',
392
430
  'autoUpdate',
393
431
  'sessionLimit',
394
- 'enableAgenticOrchestration',
395
432
  'autoCapture',
396
433
  'llmFallbacks',
434
+ 'language',
397
435
  ]);
398
436
  const KEY_VALIDATORS = {
399
437
  'llm.provider': (v) => ['anthropic', 'openai', 'ollama'].includes(v) ? null : `must be one of: anthropic, openai, ollama`,
400
- 'embedder.provider': (v) => ['onnx', 'openai', 'ollama'].includes(v) ? null : `must be one of: onnx, openai, ollama`,
438
+ 'language': (v) => {
439
+ if (v.trim().length === 0)
440
+ return 'must not be blank — use `memesh config unset language` to clear it';
441
+ if (v.length > MAX_LANGUAGE_LENGTH)
442
+ return `must be ${MAX_LANGUAGE_LENGTH} characters or fewer (a language name or locale code)`;
443
+ return languageValueError(v);
444
+ },
445
+ 'embedder.provider': (v) => ['openai', 'ollama'].includes(v) ? null : `must be one of: openai, ollama`,
401
446
  'autoUpdate': (v) => ['off', 'patch', 'minor', 'major'].includes(v) ? null : `must be one of: off, patch, minor, major`,
402
447
  'llmFallbacks': (v) => {
403
448
  let parsed;
@@ -507,7 +552,7 @@ configCmd
507
552
  coerced = parseInt(value, 10);
508
553
  if (canonical === 'llmFallbacks')
509
554
  coerced = JSON.parse(value);
510
- if (canonical === 'enableAgenticOrchestration' || canonical === 'autoCapture') {
555
+ if (canonical === 'autoCapture') {
511
556
  coerced = value === 'true' || value === '1';
512
557
  }
513
558
  const config = readConfig();
@@ -515,6 +560,10 @@ configCmd
515
560
  writeConfig(config);
516
561
  const displayValue = canonical.toLowerCase().includes('key') ? maskApiKey(String(value)) : String(value);
517
562
  console.log(`✅ Set ${canonical} = ${displayValue}`);
563
+ if (canonical === 'llm.apiKey' && !config.llm?.provider) {
564
+ console.log('⚠️ No llm.provider is set, so this key is not used yet and LLM features stay off.');
565
+ console.log(' Set one with: memesh config set llm.provider <anthropic|openai|ollama>');
566
+ }
518
567
  });
519
568
  configCmd
520
569
  .command('unset')
@@ -583,10 +632,16 @@ program
583
632
  .description('Start the HTTP API server and web dashboard')
584
633
  .option('--port <port>', 'Port number', '3737')
585
634
  .option('--host <host>', 'Host to bind', '127.0.0.1')
586
- .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.')
635
+ .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.')
587
636
  .action(async (opts) => {
588
637
  const { startServer } = await import('../http/server.js');
589
- startServer(opts.host, parseInt(opts.port, 10), { allowRemote: opts.allowRemote });
638
+ try {
639
+ startServer(opts.host, parseInt(opts.port, 10), { allowRemote: opts.allowRemote, autoUpdateCheck: true });
640
+ }
641
+ catch (err) {
642
+ console.error(`MeMesh: ${err instanceof Error ? err.message : String(err)}`);
643
+ process.exit(1);
644
+ }
590
645
  });
591
646
  program
592
647
  .command('update')
@@ -632,6 +687,12 @@ program
632
687
  .option('--prune <days>', 'Delete rows older than N days BEFORE rendering (closes v4.2.0 retention gap)', (v) => parseInt(v, 10))
633
688
  .option('--json', 'Output as JSON')
634
689
  .action(async (opts) => {
690
+ for (const [flag, value] of [['--window', opts.window], ['--prune', opts.prune]]) {
691
+ if (value !== undefined && !Number.isFinite(value)) {
692
+ console.error(`Error: ${flag} needs a number of days.`);
693
+ process.exit(1);
694
+ }
695
+ }
635
696
  await withDatabase(async () => {
636
697
  const { summariseTelemetry, pruneTelemetry } = await import('../../core/llm-telemetry.js');
637
698
  let pruneResult = null;
@@ -881,7 +942,107 @@ dreamCmd
881
942
  .option('--max-llm-calls <n>', 'Hard cap on LLM calls (default 100)', (v) => parseInt(v, 10))
882
943
  .option('--window-days <n>', 'Look-back window in days (default 56 = 8 weeks)', (v) => parseInt(v, 10))
883
944
  .option('--validate', 'Run a second LLM pass to cross-check each digest against its sources (doubles LLM calls per proposal; surfaces under flow=digest_validator in `memesh telemetry`)')
945
+ .option('--from-transcripts', 'EXPERIMENTAL: mine Claude Code session transcripts for this project (decisions/lessons/facts hidden in the conversation) and STAGE them as proposals for `dream accept`, instead of clustering existing entities. Scoped to the current project only — --project does not apply here. With --dry-run, lists sessions and conversation-turn counts without calling an LLM.')
946
+ .option('--if-due', 'For a scheduler (cron/launchd): only mine if `transcriptMining` is enabled in config AND at least --min-interval-hours have passed since this project was last mined; otherwise exit 0 doing nothing. Lets one frequently-firing entry self-throttle. Only meaningful with --from-transcripts.')
947
+ .option('--min-interval-hours <n>', 'With --if-due: minimum hours between mined runs for this project (default 24).', (v) => parseInt(v, 10))
884
948
  .action(async (opts) => {
949
+ if (opts.fromTranscripts) {
950
+ const windowDays = typeof opts.windowDays === 'number' && !Number.isNaN(opts.windowDays) ? opts.windowDays : 3;
951
+ if (opts.dryRun) {
952
+ const { scanTranscripts } = await import('../../core/transcript-source.js');
953
+ const { countConversationTurns } = await import('../../core/transcript-extractor.js');
954
+ const sessions = scanTranscripts({ windowDays });
955
+ console.log(`[dry-run] transcript sessions for this project in the last ${windowDays} day(s): ${sessions.length}`);
956
+ let totalTurns = 0;
957
+ for (const s of sessions) {
958
+ const turns = countConversationTurns(s.path);
959
+ totalTurns += turns;
960
+ console.log(` ${s.sessionId} ${turns} conversation turns ${s.lineCount} lines ${(s.sizeBytes / 1024).toFixed(0)}KB ${s.modifiedAt}`);
961
+ }
962
+ console.log(` total: ${totalTurns} conversation turns across ${sessions.length} session(s)`);
963
+ console.log('');
964
+ console.log('Run without --dry-run to extract high-value memories and stage them as proposals.');
965
+ return;
966
+ }
967
+ if (opts.ifDue) {
968
+ const { isTranscriptMiningEnabled } = await import('../../core/config.js');
969
+ if (!isTranscriptMiningEnabled(readConfig())) {
970
+ console.log('Scheduled transcript mining is off (opt-in). Enable it with `memesh config set transcriptMining true`; this scheduled run will then start mining when due.');
971
+ return;
972
+ }
973
+ const { getProjectName } = await import('../../core/paths.js');
974
+ const { lastTranscriptMineAt, transcriptMiningDue } = await import('../../core/transcript-source.js');
975
+ const projectKey = getProjectName(process.cwd());
976
+ const intervalH = typeof opts.minIntervalHours === 'number' && !Number.isNaN(opts.minIntervalHours) ? opts.minIntervalHours : 24;
977
+ const last = lastTranscriptMineAt(projectKey);
978
+ if (!transcriptMiningDue(Date.now(), last, intervalH)) {
979
+ const agoH = last === null ? null : (Date.now() - last) / 3600_000;
980
+ console.log(`Not due yet: this project was mined ${agoH === null ? 'recently' : `${agoH.toFixed(1)}h ago`}; interval is ${intervalH}h. Nothing to do.`);
981
+ return;
982
+ }
983
+ }
984
+ if (opts.project) {
985
+ console.log('note: --project does not scope --from-transcripts — the transcript source is always the current project. Ignoring --project.');
986
+ }
987
+ await withDatabase(async () => {
988
+ const { runTranscriptSource } = await import('../../core/transcript-extractor.js');
989
+ const { getDatabase } = await import('../../db.js');
990
+ const cfg = readConfig();
991
+ const llm = detectCapabilities().llm;
992
+ if (!llm) {
993
+ console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
994
+ console.error('LLM is required for `--from-transcripts` because extracting durable memory from prose is a semantic decision, not a rule.');
995
+ process.exit(1);
996
+ }
997
+ const result = await runTranscriptSource(getDatabase(), llm, {
998
+ windowDays,
999
+ maxLlmCalls: opts.maxLlmCalls,
1000
+ fallbacks: cfg.llmFallbacks,
1001
+ });
1002
+ const { getProjectName } = await import('../../core/paths.js');
1003
+ const { recordTranscriptMine } = await import('../../core/transcript-source.js');
1004
+ recordTranscriptMine(getProjectName(process.cwd()), Date.now());
1005
+ console.log(`Transcript mining complete in ${result.durationMs}ms`);
1006
+ console.log(` sessions scanned: ${result.sessionsScanned}`);
1007
+ console.log(` LLM calls: ${result.llmCalls}`);
1008
+ console.log(` candidates extracted: ${result.candidatesExtracted}`);
1009
+ console.log(` proposals created: ${result.proposalsCreated}`);
1010
+ if (result.duplicatesSkipped > 0)
1011
+ console.log(` duplicates skipped: ${result.duplicatesSkipped} (already a pending proposal)`);
1012
+ if (result.nearDuplicatesSkipped > 0) {
1013
+ console.log(` near-duplicates skipped: ${result.nearDuplicatesSkipped} candidate(s) skipped as near-duplicates of existing memories`);
1014
+ for (const d of result.nearDuplicates) {
1015
+ console.log(` - "${d.candidateName}" ~= existing "${d.matchedEntityName}" (distance ${d.distance.toFixed(3)})`);
1016
+ }
1017
+ }
1018
+ if (result.secretsDropped > 0)
1019
+ console.log(` secret-bearing candidates dropped: ${result.secretsDropped}`);
1020
+ if (result.llmFailures > 0)
1021
+ console.log(` LLM call failures: ${result.llmFailures} (sessions not mined — retry when the provider is reachable)`);
1022
+ if (result.parseFailures > 0)
1023
+ console.log(` unparsable replies: ${result.parseFailures} (chunk reply not valid JSON — likely truncated; those candidates were lost, retry)`);
1024
+ if (result.truncatedTurns > 0) {
1025
+ console.log(` size-cap truncation: ${result.truncatedTurns} conversation turn(s) beyond the cap were NOT analysed`);
1026
+ for (const t of result.truncatedSessions) {
1027
+ console.log(` - session ${t.sessionId}: ${t.truncatedTurns} tail turn(s) not analysed`);
1028
+ }
1029
+ }
1030
+ if (result.skipped.length > 0) {
1031
+ const reasonCounts = new Map();
1032
+ for (const s of result.skipped)
1033
+ reasonCounts.set(s.reason, (reasonCounts.get(s.reason) ?? 0) + 1);
1034
+ console.log(` skipped: ${result.skipped.length}`);
1035
+ for (const [reason, n] of reasonCounts)
1036
+ console.log(` - ${reason}${n > 1 ? ` (×${n})` : ''}`);
1037
+ }
1038
+ if (result.proposalsCreated > 0) {
1039
+ console.log('');
1040
+ console.log('Review with: memesh dream list');
1041
+ console.log('Accept: memesh dream accept <id>');
1042
+ }
1043
+ });
1044
+ return;
1045
+ }
885
1046
  await withDatabase(async () => {
886
1047
  const { runDreamer } = await import('../../core/dreamer.js');
887
1048
  const { getDatabase } = await import('../../db.js');
@@ -902,6 +1063,11 @@ dreamCmd
902
1063
  });
903
1064
  console.log(`${opts.dryRun ? '[dry-run] ' : ''}Dream pass complete in ${result.durationMs}ms`);
904
1065
  console.log(` clusters scanned: ${result.clustersScanned}`);
1066
+ if (result.clusteringMode) {
1067
+ console.log(` grouped by: ${result.clusteringMode === 'semantic' ? 'meaning (embeddings)' : 'calendar week (no embeddings)'}`);
1068
+ }
1069
+ if (result.clusteringNote)
1070
+ console.log(` note: ${result.clusteringNote}`);
905
1071
  console.log(` LLM calls: ${result.llmCalls}`);
906
1072
  console.log(` proposals created: ${result.proposalsCreated}`);
907
1073
  if (result.skipped.length > 0) {
@@ -985,12 +1151,48 @@ dreamCmd
985
1151
  console.log(`${proposals.length} ${opts.status} proposal(s):`);
986
1152
  console.log('');
987
1153
  for (const p of proposals) {
988
- console.log(` #${p.id} [${p.project}/${p.cluster_key}] ${p.source_count} sources "${p.digest_name}"`);
989
- console.log(` ${p.digest_observations_preview}`);
1154
+ const srcLabel = p.source_kind === 'transcript' ? ' (transcript)' : '';
1155
+ console.log(` #${p.id} [${p.project}/${p.cluster_key}]${srcLabel} ${p.source_count} source(s) → "${p.digest_name}"`);
1156
+ if (p.digest_observations_preview !== null) {
1157
+ console.log(` ${p.digest_observations_preview}`);
1158
+ }
990
1159
  console.log(` created: ${p.created_at}`);
991
1160
  console.log('');
992
1161
  }
993
- console.log(`Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
1162
+ console.log(`Inspect: memesh dream show <id> | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
1163
+ });
1164
+ });
1165
+ dreamCmd
1166
+ .command('show <id>')
1167
+ .description('Show a proposal in full — name, type, ALL observations, tags, source — so you can review the whole thing before accepting')
1168
+ .option('--json', 'Output JSON')
1169
+ .action(async (id, opts) => {
1170
+ await withDatabase(async () => {
1171
+ const { getProposalDetail } = await import('../../core/dreamer.js');
1172
+ const { getDatabase } = await import('../../db.js');
1173
+ const detail = getProposalDetail(getDatabase(), parseInt(id, 10));
1174
+ if (!detail) {
1175
+ console.error(`proposal #${id} not found`);
1176
+ console.error('See ids with: memesh dream list');
1177
+ process.exit(1);
1178
+ }
1179
+ if (opts.json) {
1180
+ console.log(JSON.stringify(detail, null, 2));
1181
+ return;
1182
+ }
1183
+ console.log(`Proposal #${detail.id} [${detail.project}/${detail.cluster_key}] source: ${detail.source_kind} status: ${detail.status}`);
1184
+ console.log(`created: ${detail.created_at}`);
1185
+ console.log('');
1186
+ console.log(`name: ${detail.digest.name}`);
1187
+ console.log(`type: ${detail.digest.type}`);
1188
+ console.log(`observations (${detail.digest.observations.length}):`);
1189
+ for (const o of detail.digest.observations)
1190
+ console.log(` - ${o}`);
1191
+ if (detail.digest.tags.length > 0)
1192
+ console.log(`tags: ${detail.digest.tags.join(', ')}`);
1193
+ console.log(`source: ${JSON.stringify(detail.source)}`);
1194
+ console.log('');
1195
+ console.log(`Accept: memesh dream accept ${detail.id} | Reject: memesh dream reject ${detail.id}`);
994
1196
  });
995
1197
  });
996
1198
  dreamCmd
@@ -1011,6 +1213,7 @@ dreamCmd
1011
1213
  console.error('See pending ids with: memesh dream list');
1012
1214
  process.exit(1);
1013
1215
  }
1216
+ await flushPendingEmbeddings();
1014
1217
  console.log(`Applied proposal #${result.proposalId}`);
1015
1218
  console.log(` digest entity: ${result.digestEntityName}`);
1016
1219
  console.log(` sources archived: ${result.sourcesArchived}`);
@@ -1063,6 +1266,9 @@ program
1063
1266
  }
1064
1267
  console.log(`${opts.dryRun ? '[dry-run] ' : ''}Settings: ${result.settingsPath}`);
1065
1268
  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.`);
1269
+ if (result.pruned > 0) {
1270
+ 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.`);
1271
+ }
1066
1272
  if (result.backupPath)
1067
1273
  console.log(`Backup: ${result.backupPath}`);
1068
1274
  if (result.conflicts.length > 0) {
@@ -1143,11 +1349,19 @@ program
1143
1349
  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._`;
1144
1350
  }
1145
1351
  }
1352
+ body = redactUserPaths(redactSecrets(body));
1146
1353
  const url = `https://github.com/PCIRCLE-AI/memesh-llm-memory/issues/new?title=${encodeURIComponent(`[${typeLabel}] `)}&body=${encodeURIComponent(body)}&labels=${encodeURIComponent(labels)}`;
1147
1354
  if (opts.open === false) {
1148
1355
  console.log(url);
1149
1356
  return;
1150
1357
  }
1358
+ console.log('This will be pre-filled into a PUBLIC GitHub issue:');
1359
+ console.log('---');
1360
+ console.log(body);
1361
+ console.log('---');
1362
+ if (opts.diagnostics !== false) {
1363
+ console.log('Re-run with --no-diagnostics to leave out the install ID and the doctor report.');
1364
+ }
1151
1365
  const { spawn } = await import('child_process');
1152
1366
  const cmd = process.platform === 'darwin' ? 'open'
1153
1367
  : process.platform === 'win32' ? 'cmd'
@@ -1173,6 +1387,7 @@ program
1173
1387
  'stored embedding first. Needed only when switching embedding providers.')
1174
1388
  .option('--json', 'Output as JSON')
1175
1389
  .action(async (opts) => {
1390
+ requireOneOf(opts.namespace, NAMESPACES, '--namespace');
1176
1391
  try {
1177
1392
  if (opts.fts) {
1178
1393
  if (opts.vectors) {
@@ -1203,9 +1418,8 @@ program
1203
1418
  console.error('❌ Could not produce a test embedding at this database\'s vector width, so\n' +
1204
1419
  ' the index was left untouched. Rebuilding it deletes every stored\n' +
1205
1420
  ' embedding, and nothing here could regenerate them.\n' +
1206
- ' Check that your OpenAI API key is valid, or that Ollama is running, or\n' +
1207
- ' install @huggingface/transformers for local embeddings then run this\n' +
1208
- ' again. `memesh doctor` reports which provider is configured.');
1421
+ ' Check that Ollama is running (or that your OpenAI API key is valid) —\n' +
1422
+ ' then run this again. `memesh doctor` reports which provider is configured.');
1209
1423
  process.exit(1);
1210
1424
  }
1211
1425
  }
@@ -1258,33 +1472,6 @@ program
1258
1472
  throw err;
1259
1473
  }
1260
1474
  });
1261
- program
1262
- .command('patterns')
1263
- .description('Show local skill-usage telemetry (agentic-orchestration banner injections, verify_agent_work invocations). Local-only — never uploaded.')
1264
- .option('--json', 'Output as JSON')
1265
- .action(async (opts) => {
1266
- const { summariseSkillUsage } = await import('../../core/skill-usage-log.js');
1267
- const summary = summariseSkillUsage();
1268
- if (opts.json) {
1269
- console.log(JSON.stringify(summary, null, 2));
1270
- return;
1271
- }
1272
- console.log(`Skill usage log: ${summary.log_path}`);
1273
- console.log(` total events: ${summary.total_events}`);
1274
- console.log(` log size: ${summary.log_bytes} bytes`);
1275
- if (summary.first_event)
1276
- console.log(` first event: ${summary.first_event}`);
1277
- if (summary.last_event)
1278
- console.log(` last event: ${summary.last_event}`);
1279
- if (summary.total_events === 0) {
1280
- console.log(' (no events recorded yet — open a Claude Code session with memesh installed, or run a verification, to start collecting)');
1281
- return;
1282
- }
1283
- console.log(' events by name:');
1284
- for (const [name, count] of Object.entries(summary.events_by_name).sort((a, b) => b[1] - a[1])) {
1285
- console.log(` ${count.toString().padStart(6)} ${name}`);
1286
- }
1287
- });
1288
1475
  program
1289
1476
  .command('status')
1290
1477
  .description('Show MeMesh status and capabilities')
@@ -1299,7 +1486,7 @@ program
1299
1486
  console.log(`MeMesh v${pkg.version}`);
1300
1487
  console.log(`Search level: ${caps.searchLevel} (${caps.searchLevel === 1 ? 'Smart Mode' : 'Core'})`);
1301
1488
  console.log(`Embeddings: ${caps.embeddings}`);
1302
- console.log(`LLM: ${caps.llm ? `${caps.llm.provider} (${caps.llm.model})` : 'not configured'}`);
1489
+ console.log(`LLM: ${caps.llm ? `${caps.llm.provider} (${caps.llm.model ?? 'default'})` : 'not configured'}`);
1303
1490
  console.log(`Install method: ${installSupport.label}`);
1304
1491
  for (const line of formatUpdateCheckStatus(update)) {
1305
1492
  console.log(`\n${line}`);