@pcircle/memesh 4.1.3 → 4.1.5

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 (123) hide show
  1. package/{plugin.json → .claude-plugin/plugin.json} +3 -6
  2. package/.mcp.json +1 -3
  3. package/README.de.md +12 -1
  4. package/README.es.md +14 -2
  5. package/README.fr.md +14 -2
  6. package/README.ja.md +12 -1
  7. package/README.ko.md +12 -1
  8. package/README.md +16 -4
  9. package/README.pt.md +14 -2
  10. package/README.th.md +14 -2
  11. package/README.vi.md +14 -2
  12. package/README.zh-CN.md +12 -1
  13. package/README.zh-TW.md +12 -1
  14. package/dashboard/dist/index.html +13 -4
  15. package/dist/cli/view-live.d.ts +2 -0
  16. package/dist/cli/view-live.d.ts.map +1 -0
  17. package/dist/cli/view-live.js +2177 -0
  18. package/dist/cli/view-live.js.map +1 -0
  19. package/dist/cli/view.d.ts +0 -1
  20. package/dist/cli/view.d.ts.map +1 -1
  21. package/dist/cli/view.js +0 -2169
  22. package/dist/cli/view.js.map +1 -1
  23. package/dist/core/analytics.d.ts +23 -0
  24. package/dist/core/analytics.d.ts.map +1 -1
  25. package/dist/core/analytics.js +79 -0
  26. package/dist/core/analytics.js.map +1 -1
  27. package/dist/core/config.d.ts +8 -4
  28. package/dist/core/config.d.ts.map +1 -1
  29. package/dist/core/config.js +16 -9
  30. package/dist/core/config.js.map +1 -1
  31. package/dist/core/consolidator.d.ts.map +1 -1
  32. package/dist/core/consolidator.js +1 -0
  33. package/dist/core/consolidator.js.map +1 -1
  34. package/dist/core/demo.d.ts +9 -0
  35. package/dist/core/demo.d.ts.map +1 -0
  36. package/dist/core/demo.js +79 -0
  37. package/dist/core/demo.js.map +1 -0
  38. package/dist/core/doctor.d.ts +2 -1
  39. package/dist/core/doctor.d.ts.map +1 -1
  40. package/dist/core/doctor.js +147 -5
  41. package/dist/core/doctor.js.map +1 -1
  42. package/dist/core/dreamer.d.ts +65 -0
  43. package/dist/core/dreamer.d.ts.map +1 -0
  44. package/dist/core/dreamer.js +477 -0
  45. package/dist/core/dreamer.js.map +1 -0
  46. package/dist/core/embedder.d.ts.map +1 -1
  47. package/dist/core/embedder.js +9 -5
  48. package/dist/core/embedder.js.map +1 -1
  49. package/dist/core/extractor.d.ts.map +1 -1
  50. package/dist/core/extractor.js +44 -22
  51. package/dist/core/extractor.js.map +1 -1
  52. package/dist/core/graph.d.ts +1 -0
  53. package/dist/core/graph.d.ts.map +1 -1
  54. package/dist/core/graph.js +8 -2
  55. package/dist/core/graph.js.map +1 -1
  56. package/dist/core/install-hooks.d.ts +41 -0
  57. package/dist/core/install-hooks.d.ts.map +1 -0
  58. package/dist/core/install-hooks.js +181 -0
  59. package/dist/core/install-hooks.js.map +1 -0
  60. package/dist/core/install-id.d.ts +9 -0
  61. package/dist/core/install-id.d.ts.map +1 -0
  62. package/dist/core/install-id.js +51 -0
  63. package/dist/core/install-id.js.map +1 -0
  64. package/dist/core/lesson-engine.d.ts +1 -5
  65. package/dist/core/lesson-engine.d.ts.map +1 -1
  66. package/dist/core/lesson-engine.js +13 -13
  67. package/dist/core/lesson-engine.js.map +1 -1
  68. package/dist/core/llm-validator.d.ts +16 -0
  69. package/dist/core/llm-validator.d.ts.map +1 -0
  70. package/dist/core/llm-validator.js +182 -0
  71. package/dist/core/llm-validator.js.map +1 -0
  72. package/dist/core/operations.d.ts.map +1 -1
  73. package/dist/core/operations.js +6 -2
  74. package/dist/core/operations.js.map +1 -1
  75. package/dist/core/projects.d.ts +14 -0
  76. package/dist/core/projects.d.ts.map +1 -0
  77. package/dist/core/projects.js +70 -0
  78. package/dist/core/projects.js.map +1 -0
  79. package/dist/core/query-expander.js.map +1 -1
  80. package/dist/core/scoring.d.ts +0 -4
  81. package/dist/core/scoring.d.ts.map +1 -1
  82. package/dist/core/scoring.js +3 -10
  83. package/dist/core/scoring.js.map +1 -1
  84. package/dist/core/serializer.d.ts.map +1 -1
  85. package/dist/core/serializer.js +2 -0
  86. package/dist/core/serializer.js.map +1 -1
  87. package/dist/core/signal-scorer.d.ts +9 -0
  88. package/dist/core/signal-scorer.d.ts.map +1 -0
  89. package/dist/core/signal-scorer.js +70 -0
  90. package/dist/core/signal-scorer.js.map +1 -0
  91. package/dist/core/types.d.ts +2 -30
  92. package/dist/core/types.d.ts.map +1 -1
  93. package/dist/core/version-check.d.ts +0 -1
  94. package/dist/core/version-check.d.ts.map +1 -1
  95. package/dist/core/version-check.js +0 -4
  96. package/dist/core/version-check.js.map +1 -1
  97. package/dist/db.d.ts +7 -0
  98. package/dist/db.d.ts.map +1 -1
  99. package/dist/db.js +91 -0
  100. package/dist/db.js.map +1 -1
  101. package/dist/knowledge-graph.d.ts +7 -3
  102. package/dist/knowledge-graph.d.ts.map +1 -1
  103. package/dist/knowledge-graph.js +43 -18
  104. package/dist/knowledge-graph.js.map +1 -1
  105. package/dist/skills-manifest.json +27 -22
  106. package/dist/storage/conflicts.d.ts +4 -1
  107. package/dist/storage/conflicts.d.ts.map +1 -1
  108. package/dist/storage/conflicts.js +5 -2
  109. package/dist/storage/conflicts.js.map +1 -1
  110. package/dist/transports/cli/cli.js +382 -32
  111. package/dist/transports/cli/cli.js.map +1 -1
  112. package/dist/transports/http/server.d.ts.map +1 -1
  113. package/dist/transports/http/server.js +160 -12
  114. package/dist/transports/http/server.js.map +1 -1
  115. package/hooks/hooks.json +12 -0
  116. package/package.json +18 -8
  117. package/scripts/hooks/_shared.js +228 -5
  118. package/scripts/hooks/pre-compact.js +19 -9
  119. package/scripts/hooks/session-start.js +7 -309
  120. package/scripts/hooks/session-summary.js +131 -29
  121. package/scripts/hooks/user-prompt-intent.js +159 -0
  122. package/skills/memesh/SKILL.md +9 -5
  123. package/skills/memesh-review/SKILL.md +24 -0
@@ -7,12 +7,58 @@
7
7
  import { createRequire } from 'module';
8
8
  import { join, basename } from 'path';
9
9
  import { existsSync, readFileSync } from 'fs';
10
- import { getMemeshDir, isAutoCaptureEnabled, openHookDb, resolvePluginRoot } from './_shared.js';
10
+ import { pathToFileURL } from 'url';
11
+ import {
12
+ decideAutoUpdateHook,
13
+ getMemeshDir,
14
+ isAutoCaptureEnabled,
15
+ openHookDb,
16
+ readUpdateCheckCache,
17
+ resolveAutoUpdatePolicy,
18
+ resolvePluginRoot,
19
+ spawnAutoUpdate,
20
+ } from './_shared.js';
11
21
 
12
22
  const require = createRequire(import.meta.url);
13
23
 
24
+ // Pre-load dist/core/install-channel.js for auto-update channel detection.
25
+ // Same pattern as session-start.js: async ESM import at process init,
26
+ // falls back to null if dist is missing (source checkout pre-build).
27
+ let _installChannelMod = null;
28
+ try {
29
+ const _pluginRootForInit = resolvePluginRoot(import.meta.url);
30
+ const _modPath = join(_pluginRootForInit, 'dist/core/install-channel.js');
31
+ if (existsSync(_modPath)) {
32
+ _installChannelMod = await import(pathToFileURL(_modPath).href);
33
+ }
34
+ } catch { /* best-effort */ }
35
+
36
+ /**
37
+ * Run auto-update at Stop hook: reads cache, evaluates policy, and spawns
38
+ * npm install -g if warranted. Runs after all session work completes,
39
+ * avoiding the TOCTOU race where install would overwrite dist/ mid-session.
40
+ */
41
+ async function runAutoUpdateAtStop() {
42
+ try {
43
+ const pluginRoot = resolvePluginRoot(import.meta.url);
44
+ const pkg = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf8'));
45
+ const installedVersion = typeof pkg.version === 'string' ? pkg.version : null;
46
+ if (!installedVersion) return;
47
+
48
+ const cache = readUpdateCheckCache(installedVersion);
49
+ const policy = resolveAutoUpdatePolicy(process.env);
50
+ const decision = decideAutoUpdateHook(installedVersion, cache, policy);
51
+ if (decision.run) {
52
+ spawnAutoUpdate(decision.latest, decision.deprecationOverride, _installChannelMod);
53
+ }
54
+ } catch {
55
+ // Best-effort — never crash the hook.
56
+ }
57
+ }
58
+
14
59
  // Parse a JSONL transcript file.
15
- // Mirrors logic in src/core/extractor.ts parseTranscript().
60
+ // Handles the current Claude Code transcript format where tool_use/tool_result
61
+ // are nested inside assistant/user message entries, not top-level entries.
16
62
  // Defensive: never throws — malformed lines are silently skipped.
17
63
  function parseTranscript(transcriptPath) {
18
64
  const filesEdited = new Set();
@@ -26,30 +72,67 @@ function parseTranscript(transcriptPath) {
26
72
  try {
27
73
  const entry = JSON.parse(line);
28
74
 
29
- // Count tool calls
30
- if (entry.type === 'tool_use' || entry.tool_name) toolCallCount++;
75
+ // Current format: assistant entries contain tool_use blocks in message.content
76
+ if (entry.type === 'assistant' && Array.isArray(entry.message?.content)) {
77
+ for (const block of entry.message.content) {
78
+ if (block.type !== 'tool_use') continue;
79
+ toolCallCount++;
31
80
 
32
- // Track file edits (Write, Edit tools)
33
- if (entry.tool_name === 'Write' || entry.tool_name === 'Edit') {
34
- const inp = entry.tool_input ?? {};
35
- const fp = (inp.file_path ?? inp.path);
36
- if (fp && typeof fp === 'string') filesEdited.add(basename(fp));
81
+ if (block.name === 'Write' || block.name === 'Edit') {
82
+ const fp = block.input?.file_path ?? block.input?.path;
83
+ if (fp && typeof fp === 'string') filesEdited.add(basename(fp));
84
+ }
85
+ if (block.name === 'Bash') {
86
+ const cmd = block.input?.command ?? '';
87
+ if (typeof cmd === 'string' && cmd.length > 10 && !cmd.startsWith('ls') && !cmd.startsWith('cd')) {
88
+ bashCommands.push(cmd.slice(0, 100));
89
+ }
90
+ }
91
+ }
37
92
  }
38
93
 
39
- // Track meaningful bash commands
40
- if (entry.tool_name === 'Bash') {
41
- const cmd = (entry.tool_input?.command) ?? '';
42
- if (typeof cmd === 'string' && cmd.length > 10 && !cmd.startsWith('ls') && !cmd.startsWith('cd')) {
43
- bashCommands.push(cmd.slice(0, 100));
94
+ // Current format: user entries contain tool_result blocks in message.content.
95
+ //
96
+ // Use the explicit `is_error` flag the transcript records on each
97
+ // tool_result instead of substring-matching the result text. The
98
+ // earlier substring approach treated any Read/Bash output that
99
+ // happened to contain the word "Error" (READMEs documenting errors,
100
+ // CHANGELOG entries, source files mentioning "Error", grep over docs)
101
+ // as a real error, drowning analyzeFailure() in noise — a 47MB
102
+ // transcript reported 315 "errors" against ~28 real ones. The flag
103
+ // is the canonical signal Claude Code itself uses to mark a tool as
104
+ // having failed.
105
+ if (entry.type === 'user' && Array.isArray(entry.message?.content)) {
106
+ for (const block of entry.message.content) {
107
+ if (block.type !== 'tool_result') continue;
108
+ if (block.is_error !== true) continue;
109
+ const text = typeof block.content === 'string'
110
+ ? block.content
111
+ : JSON.stringify(block.content);
112
+ errorsEncountered.push(text.slice(0, 200));
44
113
  }
45
114
  }
46
115
 
47
- // Track errors from tool results
116
+ // Legacy format (older Claude Code versions): top-level tool_use/tool_result
117
+ if (entry.type === 'tool_use' || entry.tool_name) {
118
+ toolCallCount++;
119
+ if (entry.tool_name === 'Write' || entry.tool_name === 'Edit') {
120
+ const fp = (entry.tool_input?.file_path ?? entry.tool_input?.path);
121
+ if (fp && typeof fp === 'string') filesEdited.add(basename(fp));
122
+ }
123
+ if (entry.tool_name === 'Bash') {
124
+ const cmd = entry.tool_input?.command ?? '';
125
+ if (typeof cmd === 'string' && cmd.length > 10 && !cmd.startsWith('ls') && !cmd.startsWith('cd')) {
126
+ bashCommands.push(cmd.slice(0, 100));
127
+ }
128
+ }
129
+ }
48
130
  if (entry.type === 'tool_result' && entry.content != null) {
49
- const text = typeof entry.content === 'string'
50
- ? entry.content
51
- : JSON.stringify(entry.content);
52
- if (text.includes('Error') || text.includes('FAIL') || text.includes('error:')) {
131
+ // Same fix as the current-format branch: trust the flag, not text.
132
+ if (entry.is_error !== true) {
133
+ // empty
134
+ } else {
135
+ const text = typeof entry.content === 'string' ? entry.content : JSON.stringify(entry.content);
53
136
  errorsEncountered.push(text.slice(0, 200));
54
137
  }
55
138
  }
@@ -99,6 +182,14 @@ process.stdin.on('end', async () => {
99
182
  // Skip sessions with too little activity
100
183
  if (toolCallCount < 3) return exit0();
101
184
 
185
+ // Hoisted to outer-try scope so the LLM failure-analysis block
186
+ // below (which runs AFTER db.close()) can reference it. Earlier
187
+ // version defined projectName inside the inner try-finally and the
188
+ // LLM path threw `projectName is not defined` silently — caught by
189
+ // the LLM try/catch but logged to stderr. Result: lesson_learned
190
+ // creation never actually happened in production.
191
+ const projectName = basename(cwd);
192
+
102
193
  // Open DB via shared helper — applies SCHEMA_SQL + status migration.
103
194
  // sqlite-vec is loaded separately because only this hook needs it
104
195
  // (for embedding-aware recall-effectiveness tracking).
@@ -107,14 +198,19 @@ process.stdin.on('end', async () => {
107
198
  try {
108
199
  sqliteVec.load(db);
109
200
 
110
- // Duplicate detection: if we already captured this session, bail
111
- const shortId = sessionId.slice(0, 8);
112
- const alreadyCaptured = db.prepare("SELECT id FROM entities WHERE name = ?").get(`session-${shortId}-files`);
201
+ // Duplicate detection: if we already captured this session, bail.
202
+ //
203
+ // Use the FULL session_id rather than the first 8 chars: real
204
+ // Claude Code UUIDs collide on 8 chars only with cosmically small
205
+ // probability, but artificial test IDs (verify-fix-001 vs -002)
206
+ // share the prefix and silently skipped the second session
207
+ // entirely. The contract is one stored capture per distinct
208
+ // session_id, so the dedup key has to be the full id.
209
+ const alreadyCaptured = db.prepare("SELECT id FROM entities WHERE name = ?").get(`session-${sessionId}-files`);
113
210
  if (alreadyCaptured) return exit0();
114
211
 
115
212
  // Build and store session memories
116
- const projectName = basename(cwd);
117
- const baseTags = ['source:auto-capture', `session:${shortId}`, `project:${projectName}`];
213
+ const baseTags = ['source:auto-capture', `session:${sessionId}`, `project:${projectName}`];
118
214
 
119
215
  const insertEntity = db.prepare('INSERT OR IGNORE INTO entities (name, type) VALUES (?, ?)');
120
216
  const selectEntity = db.prepare('SELECT id FROM entities WHERE name = ?');
@@ -132,7 +228,7 @@ process.stdin.on('end', async () => {
132
228
  // Rule 1: File editing session summary
133
229
  if (filesEdited.length > 0) {
134
230
  storeMemory(
135
- `session-${shortId}-files`,
231
+ `session-${sessionId}-files`,
136
232
  'session-insight',
137
233
  [
138
234
  `Session edited ${filesEdited.length} file(s): ${filesEdited.join(', ')}`,
@@ -145,7 +241,7 @@ process.stdin.on('end', async () => {
145
241
  // Rule 2: Error -> Fix pattern detection
146
242
  if (errorsEncountered.length > 0 && filesEdited.length > 0) {
147
243
  storeMemory(
148
- `session-${shortId}-fixes`,
244
+ `session-${sessionId}-fixes`,
149
245
  'session-insight',
150
246
  [
151
247
  `Fixed ${errorsEncountered.length} error(s) by editing ${filesEdited.join(', ')}`,
@@ -158,7 +254,7 @@ process.stdin.on('end', async () => {
158
254
  // Rule 3: Heavy session summary (20+ tool calls = significant work)
159
255
  if (toolCallCount >= 20) {
160
256
  storeMemory(
161
- `session-${shortId}-summary`,
257
+ `session-${sessionId}-summary`,
162
258
  'session-insight',
163
259
  [
164
260
  `Significant session: ${toolCallCount} tool calls, ${filesEdited.length} files edited`,
@@ -277,8 +373,10 @@ process.stdin.on('end', async () => {
277
373
  closeDatabase();
278
374
  }
279
375
  }
280
- } catch {
281
- // LLM analysis failed — rule-based extraction already captured the session
376
+ } catch (llmErr) {
377
+ // LLM analysis failed — rule-based extraction already captured the session.
378
+ // Log to stderr so config issues (e.g. invalid API key) are visible.
379
+ try { process.stderr.write(`[memesh] LLM failure analysis skipped: ${llmErr?.message || llmErr}\n`); } catch {}
282
380
  }
283
381
  }
284
382
  } catch (err) {
@@ -286,6 +384,10 @@ process.stdin.on('end', async () => {
286
384
  try { process.stderr.write(`[memesh session-summary] ${err?.message || err}\n`); } catch {}
287
385
  }
288
386
 
387
+ // Spawn auto-update if policy + cache permit. Runs after all session work
388
+ // so npm install -g doesn't overwrite dist/ while peer hooks are reading it.
389
+ await runAutoUpdateAtStop();
390
+
289
391
  // Silent output — don't clutter Claude's response
290
392
  console.log(JSON.stringify({ suppressOutput: true }));
291
393
  exit0();
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+
3
+ // User Prompt Intent — UserPromptSubmit hook
4
+ //
5
+ // Detects when the user explicitly asks Claude to remember / save / memorize
6
+ // content from the current session, and injects a context hint reminding Claude
7
+ // to use `mcp__memesh__remember` for cross-project recall.
8
+ //
9
+ // Why a hint instead of autonomous capture? The user's intent is clear, but
10
+ // "what to remember" usually depends on the surrounding conversation —
11
+ // extracting that requires an LLM round and policy decisions (name, type,
12
+ // observations, namespace). A polite reminder keeps the calling agent in the
13
+ // loop with full conversation context, while still preventing the
14
+ // "I forgot to use memesh" failure mode that motivated this hook.
15
+ //
16
+ // Defensive: never blocks user prompts, even on hook failure. Errors are
17
+ // surfaced to stderr (visible in Claude Code debug logs) rather than
18
+ // swallowed — stderr does not affect prompt submission.
19
+ // Gated by `autoCapture` flag (same as other memesh write hooks).
20
+
21
+ import { pathToFileURL } from 'url';
22
+ import { isAutoCaptureEnabled } from './_shared.js';
23
+
24
+ // Patterns compiled at module load — invalid regex MUST fail loudly. Do
25
+ // NOT move into a try block "for safety": a regex compile error is a
26
+ // programmer error, not a runtime condition, and silencing it would hide
27
+ // real bugs.
28
+ //
29
+ // Design principle: when in doubt between matching and not matching, do
30
+ // NOT match — a missed hint is recoverable (user repeats themselves), but
31
+ // a false hint pollutes context and pressures the LLM into a wrong action.
32
+ //
33
+ // Disambiguation policy:
34
+ // - All imperatives anchored to sentence start (^ or after .!?\n) to skip
35
+ // interrogatives ("do you remember X?", "What does save to memesh do?").
36
+ // - For save-class verbs, "memesh" suffix required to avoid false positives
37
+ // on generic "save this" (could mean clipboard, file, bookmark, etc.).
38
+ // - Supported languages: English, Spanish, French, Portuguese, Traditional Chinese.
39
+ // Additional languages welcome via PR (provide native-speaker validation).
40
+ export const INTENT_PATTERNS = [
41
+ // English: "Remember/memorize this|that"
42
+ /(?:^|[.!?\n]\s*)(?:please\s+)?(?:remember|memorize)\s+(?:this|that)\b/im,
43
+ // English: "save/add/store to memesh"
44
+ /(?:^|[.!?\n]\s*)(?:please\s+)?(?:save|add|put|store|write)\s+(?:(?:this|that|it)\s+)?(?:to|in|into)\s+memesh\b/im,
45
+
46
+ // Spanish: "Recordar/memorizar esto|eso"
47
+ /(?:^|[.!?\n]\s*)(?:por favor\s+)?(?:recordar|memorizar)\s+(?:esto|eso)\b/im,
48
+ // Spanish: "guardar en memesh"
49
+ /(?:^|[.!?\n]\s*)(?:por favor\s+)?(?:guardar|añadir|almacenar)\s+(?:(?:esto|eso)\s+)?(?:en|a)\s+memesh\b/im,
50
+
51
+ // French: "Rappeler/mémoriser ceci|cela"
52
+ /(?:^|[.!?\n]\s*)(?:s'il vous plaît\s+)?(?:rappeler|mémoriser)\s+(?:ceci|cela|ça)\b/im,
53
+ // French: "sauvegarder dans memesh"
54
+ /(?:^|[.!?\n]\s*)(?:s'il vous plaît\s+)?(?:sauvegarder|enregistrer|ajouter)\s+(?:(?:ceci|cela|ça)\s+)?(?:dans|à)\s+memesh\b/im,
55
+
56
+ // Portuguese: "Lembrar/memorizar isto|isso"
57
+ /(?:^|[.!?\n]\s*)(?:por favor\s+)?(?:lembrar|memorizar)\s+(?:isto|isso)\b/im,
58
+ // Portuguese: "salvar em memesh"
59
+ /(?:^|[.!?\n]\s*)(?:por favor\s+)?(?:salvar|guardar|adicionar|armazenar)\s+(?:(?:isto|isso)\s+)?(?:em|no)\s+memesh\b/im,
60
+
61
+ // Traditional Chinese: 記下來, 記到/存到/寫進/存進 memesh|記憶
62
+ /記下來|記到\s*(?:memesh|記憶)|存到\s*(?:memesh|記憶)|寫進\s*(?:memesh|記憶)|存進\s*(?:memesh|記憶)/,
63
+ ];
64
+
65
+ export function detectRememberIntent(prompt) {
66
+ if (!prompt || typeof prompt !== 'string') return false;
67
+ for (const re of INTENT_PATTERNS) {
68
+ if (re.test(prompt)) return true;
69
+ }
70
+ return false;
71
+ }
72
+
73
+ // The returned string is consumed BY THE LLM as additionalContext, NOT by
74
+ // configuration or by Claude Code itself. Edits here change LLM behavior,
75
+ // not hook behavior.
76
+ export function buildHint() {
77
+ return [
78
+ '<memesh-remember-intent>',
79
+ 'The user just asked you to save / remember content. Use memesh for cross-project recall:',
80
+ '',
81
+ '1. Decide WHAT to remember from the conversation context. Be specific — pick observations',
82
+ ' that will be useful in *future* sessions, not session-local state.',
83
+ '',
84
+ '2. Decide the SCOPE (this drives namespace + tags):',
85
+ ' • Machine-level / cross-project / preferences → memesh namespace=personal',
86
+ ' • Project-internal decision / pattern / lesson → memesh + project tag (e.g. tag:project:memesh)',
87
+ ' • Universal / public best practice → memesh namespace=global (rare)',
88
+ '',
89
+ '3. Call `mcp__memesh__remember` with:',
90
+ ' • name: descriptive entity name (e.g., "aws-cdk-stack-pattern")',
91
+ ' • type: one of (decision, pattern, lesson_learned, bug, process, preference, etc.)',
92
+ ' • observations: array of specific facts / steps / rationale',
93
+ ' • tags: relevant tags (programming language, framework, domain)',
94
+ ' • namespace: personal (default) | team | global',
95
+ '',
96
+ '4. Confirm to the user with: entity name + memesh ID returned by the tool.',
97
+ '</memesh-remember-intent>',
98
+ ].join('\n');
99
+ }
100
+
101
+ function logError(scope, msg) {
102
+ // Hooks may write to stderr without blocking prompt submission. Use this
103
+ // to surface failures in Claude Code debug logs instead of swallowing.
104
+ try {
105
+ process.stderr.write(`[memesh:${scope}] ${msg}\n`);
106
+ } catch {
107
+ // stderr itself failing is unrecoverable; stay silent.
108
+ }
109
+ }
110
+
111
+ // Only run the stdin pipeline when invoked directly as a script — not when
112
+ // imported by the test suite for in-process unit testing. On Windows,
113
+ // `file://${process.argv[1]}` produces an invalid URL because the path uses
114
+ // backslashes; pathToFileURL() correctly normalizes to a file:// URL on
115
+ // every platform, so the comparison is portable.
116
+ const isMainModule = import.meta.url === pathToFileURL(process.argv[1]).href;
117
+ if (isMainModule) {
118
+ let input = '';
119
+ process.stdin.setEncoding('utf8');
120
+ process.stdin.on('data', (chunk) => { input += chunk; });
121
+ process.stdin.on('end', () => {
122
+ try {
123
+ if (!isAutoCaptureEnabled(process.env)) return process.exit(0);
124
+
125
+ // Distinguish empty stdin (legitimate degenerate event) from malformed
126
+ // input (protocol drift). Both stay non-blocking, but only malformed
127
+ // input is logged — empty is normal, garbage indicates a real bug.
128
+ let data = {};
129
+ const trimmed = input.trim();
130
+ if (trimmed) {
131
+ try {
132
+ data = JSON.parse(trimmed);
133
+ } catch (parseErr) {
134
+ logError('user-prompt-intent', `malformed stdin JSON (len=${input.length}): ${parseErr.message}`);
135
+ return process.exit(0);
136
+ }
137
+ }
138
+
139
+ // Claude Code sends `prompt`. The `user_prompt` fallback is defensive:
140
+ // Claude Code's transcript format changed once before (2026-05-07), so
141
+ // we accept either name to survive a similar rename. If both are absent
142
+ // or non-string, detectRememberIntent's type guard returns false safely.
143
+ const prompt = data.prompt ?? data.user_prompt ?? '';
144
+ if (!detectRememberIntent(prompt)) return process.exit(0);
145
+
146
+ const out = {
147
+ hookSpecificOutput: {
148
+ hookEventName: 'UserPromptSubmit',
149
+ additionalContext: buildHint(),
150
+ },
151
+ };
152
+ process.stdout.write(JSON.stringify(out));
153
+ process.exit(0);
154
+ } catch (err) {
155
+ logError('user-prompt-intent', err?.message || err);
156
+ process.exit(0);
157
+ }
158
+ });
159
+ }
@@ -29,15 +29,19 @@ If MeMesh is installed as a Claude Code plugin, these happen **without any actio
29
29
 
30
30
  | Hook | When | What it does |
31
31
  |------|------|-------------|
32
- | **SessionStart** | Every session begins | Auto-recalls top memories for current project + surfaces lesson warnings |
33
- | **PostToolUse** | After `git commit` | Auto-tracks commit with diff stats as a memory entity |
32
+ | **SessionStart** | Every session begins | Auto-recalls top memories for current project + surfaces lesson warnings + (opt-in) agentic-orchestration banner |
33
+ | **PreToolUse (Edit)** | Before editing files | Injects memories related to the file or project |
34
+ | **PreToolUse (Bash)** | Before bash commands | (Opt-in) Nudges Claude to dispatch high-verifiability commands as background agents |
35
+ | **UserPromptSubmit** | When you submit a prompt | Detects "remember this" intent (5 languages: en, es, fr, pt, zh-TW) and reminds Claude to use memesh |
36
+ | **PostToolUse (Commit)** | After `git commit` | Auto-tracks commit with diff stats as a memory entity |
34
37
  | **Stop** | Session ends | Auto-captures session knowledge + runs LLM failure analysis → lessons |
35
38
  | **PreCompact** | Before context compaction | Saves important knowledge before conversation history is compressed |
36
39
 
37
40
  **You do NOT need to manually:**
38
- - Recall at session start (hook does it)
39
- - Remember commits (hook does it)
40
- - Summarize sessions (hook does it)
41
+ - Recall at session start (SessionStart hook does it)
42
+ - Remember commits (PostToolUse hook does it)
43
+ - Summarize sessions (Stop hook does it)
44
+ - Remember when you say "記下來" / "remember this" (UserPromptSubmit hook reminds Claude)
41
45
 
42
46
  **You DO need to manually** use the commands below for intentional knowledge management.
43
47
 
@@ -89,9 +89,33 @@ memesh remember --name "missing-knowledge" --type decision --obs "..."
89
89
  memesh recall --limit 5 --json # confirm changes took effect
90
90
  ```
91
91
 
92
+ ## Documentation & Code Quality Checks
93
+
94
+ **For comprehensive documentation synchronization and lint checks**, use:
95
+ ```bash
96
+ @sa:comprehensive-code-review
97
+ ```
98
+
99
+ This skill includes:
100
+ - **Dim 17: Documentation Synchronization** — version consistency, API docs, architecture docs, feature docs, deprecated terms, breaking changes
101
+ - **Dim 18: Code Style & Lint** — lint errors/warnings, security rules, disabled rules, style consistency
102
+
103
+ **MeMesh-specific automation**:
104
+ ```bash
105
+ # Quick verification (memesh-specific checks)
106
+ ./scripts/verify-docs-sync.sh
107
+ # Exit code 0 = all checks pass
108
+
109
+ # Lint check
110
+ npm run lint # 0 errors expected, ~83 warnings (technical debt)
111
+ ```
112
+
113
+ ---
114
+
92
115
  ## Tips
93
116
 
94
117
  - Run every 1-2 weeks to keep memory healthy
95
118
  - Health score < 50 → too many stale or low-quality memories
96
119
  - Noise > 80% → encourage deliberate `memesh remember` for decisions
97
120
  - Dashboard available at: http://localhost:3737/dashboard (run `memesh serve` first)
121
+ - **Before merge**: Run documentation sync checklist above to prevent drift