@pcircle/memesh 4.6.0 → 4.6.2

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 (92) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +21 -0
  4. package/README.de.md +12 -13
  5. package/README.md +12 -14
  6. package/README.zh-TW.md +12 -13
  7. package/dashboard/dist/index.html +14 -13
  8. package/dist/core/analytics.d.ts +9 -0
  9. package/dist/core/analytics.d.ts.map +1 -1
  10. package/dist/core/analytics.js +36 -18
  11. package/dist/core/analytics.js.map +1 -1
  12. package/dist/core/briefing.d.ts.map +1 -1
  13. package/dist/core/briefing.js +1 -0
  14. package/dist/core/briefing.js.map +1 -1
  15. package/dist/core/config.d.ts +0 -1
  16. package/dist/core/config.d.ts.map +1 -1
  17. package/dist/core/config.js.map +1 -1
  18. package/dist/core/demo.d.ts.map +1 -1
  19. package/dist/core/demo.js +1 -1
  20. package/dist/core/demo.js.map +1 -1
  21. package/dist/core/doctor.d.ts.map +1 -1
  22. package/dist/core/doctor.js +25 -6
  23. package/dist/core/doctor.js.map +1 -1
  24. package/dist/core/dreamer.d.ts +2 -2
  25. package/dist/core/dreamer.d.ts.map +1 -1
  26. package/dist/core/dreamer.js +221 -0
  27. package/dist/core/dreamer.js.map +1 -1
  28. package/dist/core/embedder.d.ts +4 -1
  29. package/dist/core/embedder.d.ts.map +1 -1
  30. package/dist/core/embedder.js +67 -17
  31. package/dist/core/embedder.js.map +1 -1
  32. package/dist/core/graph.d.ts +12 -0
  33. package/dist/core/graph.d.ts.map +1 -1
  34. package/dist/core/graph.js +56 -1
  35. package/dist/core/graph.js.map +1 -1
  36. package/dist/core/guards.d.ts +20 -0
  37. package/dist/core/guards.d.ts.map +1 -0
  38. package/dist/core/guards.js +103 -0
  39. package/dist/core/guards.js.map +1 -0
  40. package/dist/core/kg-backfill.d.ts +5 -1
  41. package/dist/core/kg-backfill.d.ts.map +1 -1
  42. package/dist/core/kg-backfill.js +155 -2
  43. package/dist/core/kg-backfill.js.map +1 -1
  44. package/dist/core/operations.d.ts +13 -2
  45. package/dist/core/operations.d.ts.map +1 -1
  46. package/dist/core/operations.js +93 -15
  47. package/dist/core/operations.js.map +1 -1
  48. package/dist/core/serializer.d.ts.map +1 -1
  49. package/dist/core/serializer.js +8 -0
  50. package/dist/core/serializer.js.map +1 -1
  51. package/dist/core/types.d.ts +1 -0
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/core/why.d.ts +54 -0
  54. package/dist/core/why.d.ts.map +1 -0
  55. package/dist/core/why.js +168 -0
  56. package/dist/core/why.js.map +1 -0
  57. package/dist/core/work-topology.d.ts +3 -0
  58. package/dist/core/work-topology.d.ts.map +1 -1
  59. package/dist/core/work-topology.js +11 -2
  60. package/dist/core/work-topology.js.map +1 -1
  61. package/dist/db.d.ts +31 -4
  62. package/dist/db.d.ts.map +1 -1
  63. package/dist/db.js +163 -31
  64. package/dist/db.js.map +1 -1
  65. package/dist/skills-manifest.json +29 -19
  66. package/dist/storage/sqlite.d.ts.map +1 -1
  67. package/dist/storage/sqlite.js +1 -1
  68. package/dist/storage/sqlite.js.map +1 -1
  69. package/dist/transports/cli/cli.js +198 -61
  70. package/dist/transports/cli/cli.js.map +1 -1
  71. package/dist/transports/http/server.d.ts.map +1 -1
  72. package/dist/transports/http/server.js +43 -5
  73. package/dist/transports/http/server.js.map +1 -1
  74. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  75. package/dist/transports/mcp/handlers.js +2 -2
  76. package/dist/transports/mcp/handlers.js.map +1 -1
  77. package/dist/transports/schemas.d.ts +8 -0
  78. package/dist/transports/schemas.d.ts.map +1 -1
  79. package/dist/transports/schemas.js +7 -0
  80. package/dist/transports/schemas.js.map +1 -1
  81. package/hooks/hooks.json +10 -0
  82. package/package.json +3 -2
  83. package/scripts/hooks/_generated/guards.js +110 -0
  84. package/scripts/hooks/_generated/sqlite.js +1 -1
  85. package/scripts/hooks/_generated/work-topology.js +11 -2
  86. package/scripts/hooks/_shared.js +77 -4
  87. package/scripts/hooks/guard-check.js +76 -0
  88. package/scripts/hooks/post-commit.js +27 -0
  89. package/scripts/hooks/pre-edit-recall.js +157 -121
  90. package/scripts/hooks/session-start.js +34 -23
  91. package/scripts/hooks/session-summary.js +43 -90
  92. package/skills/memesh/SKILL.md +11 -0
@@ -29,6 +29,7 @@ import {
29
29
  AUTO_CAPTURE_TAG,
30
30
  captureEntity,
31
31
  decideAutoUpdateHook,
32
+ extractCitedMemoryIds,
32
33
  getMemeshDirFromDbPath,
33
34
  getProjectName,
34
35
  importFromPluginRoot,
@@ -475,56 +476,58 @@ process.stdin.on('end', async () => {
475
476
  }
476
477
 
477
478
  if (injectedData) {
478
- const { entityIds, entityNames } = injectedData;
479
+ const { entityIds } = injectedData;
479
480
 
480
481
  if (entityIds && entityIds.length > 0) {
481
482
  // Check if recall_hits column exists (v4.0+ migration)
482
483
  const colCheck = db.prepare("PRAGMA table_info(entities)").all();
483
484
  if (colCheck.some(c => c.name === 'recall_hits')) {
484
485
  // Drop the records Claude Code created FROM our own hook
485
- // output before matching. One SessionStart injection lands in
486
- // the transcript 2+ times (hook_success + hook_additional_context),
487
- // so any count-based discount depends on guessing an
488
- // undocumented internal get it wrong and every entity scores
489
- // a hit instead of a miss. Structural removal is copy-count
490
- // and encoding independent.
491
- // Reuse the raw text parseTranscript already read — a second
492
- // readFileSync doubles the Stop hook's I/O on 47MB transcripts.
493
- const sessionText = stripHookEchoes(transcriptRawText).toLowerCase();
494
-
495
- // Hit/miss decision lives in `isRecallHit` (exported, unit-tested).
496
-
486
+ // output before scanning: the injected block itself prints a
487
+ // `[mem:id]` handle on every line, and counting those would
488
+ // score every injection as a hit. Structural removal is
489
+ // copy-count and encoding independent. Reuse the raw text
490
+ // parseTranscript already read a second readFileSync
491
+ // doubles the Stop hook's I/O on 47MB transcripts.
492
+ const sessionText = stripHookEchoes(transcriptRawText);
493
+
494
+ // Citation accounting. A hit is an EXPLICIT `[mem:id]` marker
495
+ // the agent wrote for an id this session injected — the
496
+ // instruction line session-start appends after the fenced
497
+ // block. Literal-content matching (the previous accounting)
498
+ // was retired after measuring 0% signal across ten real
499
+ // sessions and three matching strategies: every injected
500
+ // memory drifted toward an unearned recall_miss, and misses
501
+ // feed the impact factor in core ranking.
502
+ //
503
+ // Markers are self-reported: an agent that used a memory
504
+ // silently earns it nothing, so the signal UNDERCOUNTS and
505
+ // never overcounts. That asymmetry is why misses are FROZEN —
506
+ // recall_misses stays untouched until measured marker
507
+ // compliance (the counters below) justifies reading silence
508
+ // as non-use. The mode stamp keeps the two eras of numbers
509
+ // apart.
510
+ const cited = extractCitedMemoryIds(sessionText);
497
511
  const updateHit = db.prepare(
498
512
  'UPDATE entities SET recall_hits = COALESCE(recall_hits, 0) + 1 WHERE id = ?'
499
513
  );
500
- const updateMiss = db.prepare(
501
- 'UPDATE entities SET recall_misses = COALESCE(recall_misses, 0) + 1 WHERE id = ?'
502
- );
503
-
504
- // The injected block shows an entity's TITLE, not its name
505
- // (A1 — a machine key like `commit-a1b2c3d` cost tokens and
506
- // taught the model nothing). Matching on the name alone would
507
- // therefore score a miss against a string the session was never
508
- // shown, and a miss is not inert: it lowers the entity's impact
509
- // factor in core ranking. Match either.
510
- const titleStmt = db.prepare('SELECT title FROM entities WHERE id = ?');
511
- for (let i = 0; i < entityIds.length; i++) {
512
- const name = (entityNames[i] || '').toLowerCase();
513
- // Skip names that carry no recall signal: too short, or a
514
- // machine identifier (auto-capture entities) that can never
515
- // substring-match prose. Scoring those would be a guaranteed
516
- // unearned miss — see isMeasurableRecallName.
517
- let title = null;
518
- try { title = titleStmt.get(entityIds[i])?.title ?? null; } catch { /* pre-title schema */ }
519
- // A row is measurable if EITHER string could plausibly appear
520
- // in prose; a machine-named row with a human title now can.
521
- if (!isMeasurableRecallName(name) && !isMeasurableRecallName(title)) continue;
522
- if (isRecallHit(sessionText, name) || isRecallHit(sessionText, title)) {
523
- updateHit.run(entityIds[i]);
524
- } else {
525
- updateMiss.run(entityIds[i]);
526
- }
514
+ for (const id of entityIds) {
515
+ if (cited.has(id)) updateHit.run(id);
527
516
  }
517
+
518
+ // Accounting-mode stamp (constant value, rewritten every
519
+ // session so it survives DB restores from either era) plus
520
+ // the compliance denominators: sessions that HAD an injection
521
+ // vs sessions whose transcript carried any citation marker.
522
+ db.prepare(
523
+ 'INSERT OR REPLACE INTO memesh_metadata (key, value) VALUES (?, ?)'
524
+ ).run('recall_accounting_mode', 'citation-v1 since 2026-08-16');
525
+ const bump = db.prepare(
526
+ `INSERT INTO memesh_metadata (key, value) VALUES (?, '1')
527
+ ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT)`
528
+ );
529
+ bump.run('citation_sessions_total');
530
+ if (cited.size > 0) bump.run('citation_sessions_cited');
528
531
  }
529
532
  }
530
533
  }
@@ -837,56 +840,6 @@ export function stripHookEchoes(rawTranscript) {
837
840
  return kept.join('\n');
838
841
  }
839
842
 
840
- /**
841
- * Did the session actually USE the memory named `name`, or does the name only
842
- * appear because memesh injected it at session start?
843
- *
844
- * The caller passes `sessionText` with memesh's own SessionStart injection
845
- * already stripped structurally (see `stripHookEchoes` — matches on
846
- * `attachment.type`, so it is independent of JSON escaping and of how many
847
- * times Claude Code echoes one injection). That removal is what stops an
848
- * injected name from scoring a false hit; once the echo is gone, a plain
849
- * substring match is the whole test.
850
- *
851
- * (This replaced an earlier `transcript.replace(injectedBlob, '')` + match,
852
- * which silently failed on JSON-encoded transcripts and scored every entity a
853
- * hit — see the callsite comment.)
854
- *
855
- * Ignores names shorter than 4 chars (too generic to match reliably).
856
- *
857
- * CONTRACT: `sessionText` must already be lowercased. The haystack is a
858
- * multi-megabyte transcript and this runs twice per injected entity (name +
859
- * title) — re-lowercasing it inside the function copied the whole transcript
860
- * on every call, hundreds of MB of transient allocation in the Stop hook.
861
- * The caller lowercases once; only the needle is normalized here.
862
- */
863
- export function isRecallHit(sessionText, name) {
864
- if (!name || name.length < 4) return false;
865
- return String(sessionText ?? '').includes(String(name).toLowerCase());
866
- }
867
-
868
- /**
869
- * Whether an injected entity's NAME can serve as a recall-effectiveness signal.
870
- *
871
- * Recall-effectiveness decides "was this injected memory used?" by substring-
872
- * matching the entity NAME in the session transcript (isRecallHit). That only
873
- * works for names a human might type. Auto-capture entities are named with
874
- * machine identifiers — `session-<pid>-<ts>-files`, `commit-<hash>`,
875
- * `pre-compact-<id>` — which never appear verbatim in conversation prose, so
876
- * they take a `recall_miss` they didn't earn on every injection. Over repeated
877
- * sessions that drags their Laplace-smoothed impact factor (scoring.ts, 10%
878
- * weight) down and quietly suppresses auto-captured memories from future recall.
879
- *
880
- * We can't measure their usefulness by name, so we don't count them either way —
881
- * they keep the neutral 0.5 impact. The prefix set is coupled to the auto-capture
882
- * producers' `<kind>-<id>` naming (post-commit / session-summary / pre-compact);
883
- * a new auto-capture producer should add its prefix here.
884
- */
885
- export function isMeasurableRecallName(name) {
886
- if (!name || name.length < 4) return false;
887
- return !/^(session-|commit-|pre-compact-)/i.test(name);
888
- }
889
-
890
843
  export function maybeTriggerDream(projectName, config, pluginRoot) {
891
844
  dreamTrigTrace('enter', { projectName, hasLlm: Boolean(config?.llm) });
892
845
  if (!projectName || projectName === 'unknown') {
@@ -56,6 +56,16 @@ clear it.
56
56
  Run `memesh briefing` (or `--project <name>`) and answer from it. For specific
57
57
  follow-up questions, use `recall`.
58
58
 
59
+ **MEMESH UNAVAILABLE or RECALL EMPTY → say so, never invent.** Report that
60
+ memory is unavailable (or found nothing) and continue without it. Never
61
+ fabricate a memory or cite a `[mem:id]` that was not actually returned.
62
+ Recall is bounded by `limit` — a small hit count is not a graph-wide count,
63
+ and an empty result is not proof nothing was stored: vary the wording or
64
+ narrow by tag before concluding. Every recall answer includes a `retrieval`
65
+ block — `truncated: true` means the window filled (more may exist);
66
+ `degraded: true` means semantic search could not run and these are
67
+ keyword-only results right now (`memesh doctor` explains why).
68
+
59
69
  ## What's Already Automatic (Claude Code Plugin Hooks)
60
70
 
61
71
  If MeMesh is installed as a Claude Code plugin, these happen **without any action from you**:
@@ -68,6 +78,7 @@ If MeMesh is installed as a Claude Code plugin, these happen **without any actio
68
78
  | **PostToolUse (Bash)** | After `git commit` | Auto-tracks the commit with diff stats as a memory entity |
69
79
  | **Stop** | Session ends | Auto-captures session knowledge + runs LLM failure analysis → lessons |
70
80
  | **PreCompact** | Before context compaction | Saves important knowledge before history is compressed |
81
+ | **PreToolUse (Bash)** | Before a command runs | Fires accepted lesson-guards — warns when a recorded mistake is about to repeat |
71
82
 
72
83
  Because of the SessionStart hook: **in Claude Code, do NOT call `briefing` at
73
84
  session start — it is already in your context.** Call it only mid-session