@pcircle/memesh 4.8.1 → 4.8.3

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 (149) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/mcp.json +7 -0
  4. package/.codex-plugin/plugin.json +6 -0
  5. package/AGENTS.md +13 -2
  6. package/README.de.md +11 -10
  7. package/README.md +23 -11
  8. package/README.zh-TW.md +23 -11
  9. package/dashboard/dist/index.html +9 -9
  10. package/dist/core/agent-message-inbox.d.ts +9 -0
  11. package/dist/core/agent-message-inbox.d.ts.map +1 -0
  12. package/dist/core/agent-message-inbox.js +31 -0
  13. package/dist/core/agent-message-inbox.js.map +1 -0
  14. package/dist/core/agent-messaging.d.ts +7 -0
  15. package/dist/core/agent-messaging.d.ts.map +1 -1
  16. package/dist/core/agent-messaging.js +28 -9
  17. package/dist/core/agent-messaging.js.map +1 -1
  18. package/dist/core/agent-router.d.ts +27 -20
  19. package/dist/core/agent-router.d.ts.map +1 -1
  20. package/dist/core/agent-router.js +114 -43
  21. package/dist/core/agent-router.js.map +1 -1
  22. package/dist/core/briefing.d.ts +1 -1
  23. package/dist/core/briefing.d.ts.map +1 -1
  24. package/dist/core/briefing.js +22 -6
  25. package/dist/core/briefing.js.map +1 -1
  26. package/dist/core/citation-rule.d.ts.map +1 -1
  27. package/dist/core/citation-rule.js +5 -2
  28. package/dist/core/citation-rule.js.map +1 -1
  29. package/dist/core/config.d.ts +4 -1
  30. package/dist/core/config.d.ts.map +1 -1
  31. package/dist/core/config.js +12 -3
  32. package/dist/core/config.js.map +1 -1
  33. package/dist/core/doctor.d.ts +8 -0
  34. package/dist/core/doctor.d.ts.map +1 -1
  35. package/dist/core/doctor.js +314 -12
  36. package/dist/core/doctor.js.map +1 -1
  37. package/dist/core/dreamer.d.ts +2 -0
  38. package/dist/core/dreamer.d.ts.map +1 -1
  39. package/dist/core/dreamer.js +7 -1
  40. package/dist/core/dreamer.js.map +1 -1
  41. package/dist/core/install-channel.d.ts +3 -0
  42. package/dist/core/install-channel.d.ts.map +1 -1
  43. package/dist/core/install-channel.js +79 -2
  44. package/dist/core/install-channel.js.map +1 -1
  45. package/dist/core/install-hooks.d.ts.map +1 -1
  46. package/dist/core/install-hooks.js +10 -6
  47. package/dist/core/install-hooks.js.map +1 -1
  48. package/dist/core/lesson-engine.d.ts +2 -1
  49. package/dist/core/lesson-engine.d.ts.map +1 -1
  50. package/dist/core/lesson-engine.js +5 -2
  51. package/dist/core/lesson-engine.js.map +1 -1
  52. package/dist/core/lesson-slug.d.ts +2 -0
  53. package/dist/core/lesson-slug.d.ts.map +1 -0
  54. package/dist/core/lesson-slug.js +14 -0
  55. package/dist/core/lesson-slug.js.map +1 -0
  56. package/dist/core/llm-client.d.ts.map +1 -1
  57. package/dist/core/llm-client.js +4 -1
  58. package/dist/core/llm-client.js.map +1 -1
  59. package/dist/core/llm-validator.d.ts +4 -1
  60. package/dist/core/llm-validator.d.ts.map +1 -1
  61. package/dist/core/llm-validator.js +62 -42
  62. package/dist/core/llm-validator.js.map +1 -1
  63. package/dist/core/ollama-host.d.ts +6 -0
  64. package/dist/core/ollama-host.d.ts.map +1 -0
  65. package/dist/core/ollama-host.js +30 -0
  66. package/dist/core/ollama-host.js.map +1 -0
  67. package/dist/core/operations.d.ts +9 -3
  68. package/dist/core/operations.d.ts.map +1 -1
  69. package/dist/core/operations.js +18 -14
  70. package/dist/core/operations.js.map +1 -1
  71. package/dist/core/paths.d.ts.map +1 -1
  72. package/dist/core/paths.js +2 -3
  73. package/dist/core/paths.js.map +1 -1
  74. package/dist/core/schema-export.d.ts.map +1 -1
  75. package/dist/core/schema-export.js +9 -7
  76. package/dist/core/schema-export.js.map +1 -1
  77. package/dist/core/transcript-extractor.d.ts.map +1 -1
  78. package/dist/core/transcript-extractor.js +2 -2
  79. package/dist/core/transcript-extractor.js.map +1 -1
  80. package/dist/core/work-topology.d.ts +3 -0
  81. package/dist/core/work-topology.d.ts.map +1 -1
  82. package/dist/core/work-topology.js +24 -2
  83. package/dist/core/work-topology.js.map +1 -1
  84. package/dist/db.d.ts.map +1 -1
  85. package/dist/db.js +12 -0
  86. package/dist/db.js.map +1 -1
  87. package/dist/host-adapters/claude-channel.d.ts +0 -58
  88. package/dist/host-adapters/claude-channel.d.ts.map +1 -1
  89. package/dist/host-adapters/claude-channel.js +0 -242
  90. package/dist/host-adapters/claude-channel.js.map +1 -1
  91. package/dist/host-adapters/codex-cli-queue.d.ts.map +1 -1
  92. package/dist/host-adapters/codex-cli-queue.js +15 -12
  93. package/dist/host-adapters/codex-cli-queue.js.map +1 -1
  94. package/dist/host-runtime/acp.d.ts +2 -0
  95. package/dist/host-runtime/acp.d.ts.map +1 -1
  96. package/dist/host-runtime/acp.js +4 -0
  97. package/dist/host-runtime/acp.js.map +1 -1
  98. package/dist/host-runtime/claude.d.ts +2 -0
  99. package/dist/host-runtime/claude.d.ts.map +1 -1
  100. package/dist/host-runtime/claude.js +6 -5
  101. package/dist/host-runtime/claude.js.map +1 -1
  102. package/dist/host-runtime/codex-session.d.ts +3 -1
  103. package/dist/host-runtime/codex-session.d.ts.map +1 -1
  104. package/dist/host-runtime/codex-session.js +7 -5
  105. package/dist/host-runtime/codex-session.js.map +1 -1
  106. package/dist/host-runtime/codex.d.ts +2 -0
  107. package/dist/host-runtime/codex.d.ts.map +1 -1
  108. package/dist/host-runtime/codex.js +4 -0
  109. package/dist/host-runtime/codex.js.map +1 -1
  110. package/dist/host-runtime/router-client.d.ts +2 -0
  111. package/dist/host-runtime/router-client.d.ts.map +1 -1
  112. package/dist/host-runtime/router-client.js.map +1 -1
  113. package/dist/skills-manifest.json +30 -15
  114. package/dist/storage/graph-repairs.d.ts +12 -0
  115. package/dist/storage/graph-repairs.d.ts.map +1 -0
  116. package/dist/storage/graph-repairs.js +242 -0
  117. package/dist/storage/graph-repairs.js.map +1 -0
  118. package/dist/transports/agent-messaging.d.ts +10 -1
  119. package/dist/transports/agent-messaging.d.ts.map +1 -1
  120. package/dist/transports/agent-messaging.js +92 -9
  121. package/dist/transports/agent-messaging.js.map +1 -1
  122. package/dist/transports/cli/cli.d.ts +4 -0
  123. package/dist/transports/cli/cli.d.ts.map +1 -1
  124. package/dist/transports/cli/cli.js +65 -26
  125. package/dist/transports/cli/cli.js.map +1 -1
  126. package/dist/transports/http/server.d.ts.map +1 -1
  127. package/dist/transports/http/server.js +117 -7
  128. package/dist/transports/http/server.js.map +1 -1
  129. package/dist/transports/mcp/handlers.d.ts +10 -6
  130. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  131. package/dist/transports/mcp/handlers.js +21 -9
  132. package/dist/transports/mcp/handlers.js.map +1 -1
  133. package/dist/transports/schemas.d.ts +5 -0
  134. package/dist/transports/schemas.d.ts.map +1 -1
  135. package/dist/transports/schemas.js +8 -1
  136. package/dist/transports/schemas.js.map +1 -1
  137. package/docs/platforms/README.md +1 -1
  138. package/docs/platforms/agent-messaging.md +89 -36
  139. package/llms-install.md +29 -6
  140. package/package.json +17 -3
  141. package/scripts/hooks/_generated/agent-message-inbox.js +38 -0
  142. package/scripts/hooks/_generated/citation-rule.js +5 -2
  143. package/scripts/hooks/_generated/core-paths.js +2 -3
  144. package/scripts/hooks/_generated/work-topology.js +24 -2
  145. package/scripts/hooks/_shared.js +1 -1
  146. package/scripts/hooks/session-start.js +59 -18
  147. package/scripts/hooks/session-summary.js +40 -1
  148. package/scripts/upgrade-plugin.sh +531 -81
  149. package/skills/memesh/SKILL.md +36 -5
@@ -15,6 +15,7 @@ import {
15
15
  importFromPluginRoot,
16
16
  assembleTopologyBlock,
17
17
  DEFAULT_TOPOLOGY_BUDGET,
18
+ GLOBAL_TOPOLOGY_LIMIT,
18
19
  SNIPPET_FETCH_CHARS,
19
20
  TOPOLOGY_CANDIDATE_CAP,
20
21
  isTrustedForAutoContext,
@@ -36,6 +37,7 @@ import {
36
37
  writePrivateJson,
37
38
  } from './_shared.js';
38
39
  import { MemeshDatabase } from './_generated/sqlite.js';
40
+ import { unreadDeliveryCount, unreadInboxLines } from './_generated/agent-message-inbox.js';
39
41
 
40
42
  const require = createRequire(import.meta.url);
41
43
 
@@ -785,9 +787,12 @@ process.stdin.on('end', async () => {
785
787
  };
786
788
 
787
789
  const projectTag = `project:${projectName}`;
790
+ const notGlobal = colNames.has('namespace')
791
+ ? "AND (e.namespace IS NULL OR e.namespace <> 'global')"
792
+ : '';
788
793
  const projectQuery = buildScoringQuery(
789
794
  `JOIN tags t ON t.entity_id = e.id`,
790
- `WHERE t.tag = ? ${statusFilter}`,
795
+ `WHERE t.tag = ? ${notGlobal} ${statusFilter}`,
791
796
  );
792
797
  // Over-fetch WIDE, then filter. The window used to be `sessionLimit * 3`
793
798
  // and the trust filter ran after it — so a class of entity that ranks
@@ -801,14 +806,36 @@ process.stdin.on('end', async () => {
801
806
  // Shared with the briefing surface via the leaf, so the two sides'
802
807
  // candidate windows cannot drift apart.
803
808
  const CANDIDATE_CAP = TOPOLOGY_CANDIDATE_CAP;
804
- const projectEntities = db.prepare(projectQuery).all(projectTag, CANDIDATE_CAP)
805
- .filter(entity => isTrustedForAutoContext(entity.metadata))
806
- .slice(0, sessionLimit);
809
+ const projectOnly = db.prepare(projectQuery).all(projectTag, CANDIDATE_CAP)
810
+ .filter(entity => isTrustedForAutoContext(entity.metadata));
811
+
812
+ // The `global` namespace is the documented way to store something that
813
+ // is not tied to one project — and the injection selected purely by
814
+ // `project:` tag, so a global memory with no project tag was reachable
815
+ // by nobody (#242): 1814 entities, 2 global, one of them structurally
816
+ // uninjectable. A standing behaviour rule sat in that state for months.
817
+ //
818
+ // Global rows ride in a SEPARATE, small window and the shared assembler
819
+ // gives them a separate render budget. The project keeps `sessionLimit`
820
+ // slots and its full character budget. The column is absent on
821
+ // pre-namespace schemas; then this branch is simply empty.
822
+ let globalEntities = [];
823
+ if (colNames.has('namespace')) {
824
+ const globalQuery = buildScoringQuery('', `WHERE e.namespace = 'global' ${statusFilter}`);
825
+ globalEntities = db.prepare(globalQuery).all(CANDIDATE_CAP)
826
+ .filter(entity => isTrustedForAutoContext(entity.metadata))
827
+ .slice(0, GLOBAL_TOPOLOGY_LIMIT);
828
+ }
829
+ const projectEntities = projectOnly.slice(0, sessionLimit);
807
830
 
808
831
  // recentStatusFilter is "WHERE status = 'active'" or "" — the bare-column
809
832
  // form is fine when there's no JOIN, but we now alias the table as `e`,
810
833
  // so rewrite to e.status for consistency.
811
- const recentWhere = hasStatus ? "WHERE e.status = 'active'" : '';
834
+ const recentConditions = [
835
+ hasStatus ? "e.status = 'active'" : '',
836
+ colNames.has('namespace') ? "(e.namespace IS NULL OR e.namespace <> 'global')" : '',
837
+ ].filter(Boolean);
838
+ const recentWhere = recentConditions.length > 0 ? `WHERE ${recentConditions.join(' AND ')}` : '';
812
839
  const recentQuery = buildScoringQuery('', recentWhere);
813
840
  const recentEntities = db.prepare(recentQuery).all(CANDIDATE_CAP)
814
841
  .filter(entity => isTrustedForAutoContext(entity.metadata))
@@ -828,6 +855,7 @@ process.stdin.on('end', async () => {
828
855
  JOIN tags t ON t.entity_id = e.id
829
856
  WHERE e.type = 'lesson_learned'
830
857
  ${hasStatus ? "AND e.status = 'active'" : ''}
858
+ ${colNames.has('namespace') ? "AND (e.namespace IS NULL OR e.namespace <> 'global')" : ''}
831
859
  AND t.tag = ?
832
860
  LIMIT 50
833
861
  `).all(projectTag).filter(entity => isTrustedForAutoContext(entity.metadata));
@@ -844,9 +872,11 @@ process.stdin.on('end', async () => {
844
872
  // SessionStart system message takes one row in the Claude Code
845
873
  // log instead of four. Counts are count-only — no entity bullets.
846
874
  const projectCount = projectEntities.length;
875
+ const globalCount = globalEntities.length;
847
876
  const recentCount = recentEntities.length;
848
877
  const memoryFragments = [];
849
878
  if (projectCount > 0) memoryFragments.push(`${projectCount} project`);
879
+ if (globalCount > 0) memoryFragments.push(`${globalCount} global`);
850
880
  if (recentCount > 0) memoryFragments.push(`${recentCount} recent`);
851
881
 
852
882
  let summary;
@@ -893,6 +923,7 @@ process.stdin.on('end', async () => {
893
923
  const rankedIds = [
894
924
  ...topLessons.map(e => e.id),
895
925
  ...projectEntities.map(e => e.id),
926
+ ...globalEntities.map(e => e.id),
896
927
  ...recentEntities.map(e => e.id),
897
928
  ];
898
929
  const uniqueIds = [...new Set(rankedIds)];
@@ -933,10 +964,15 @@ process.stdin.on('end', async () => {
933
964
  const taskRow = db
934
965
  .prepare('SELECT metadata FROM entities WHERE name = ?')
935
966
  .get(taskStateName(projectName));
936
- const stateLines = taskStateLines(
937
- parseTaskState(parseEntityMetadata(taskRow?.metadata)),
938
- projectName,
939
- );
967
+ // SessionStart has no exact recipient identity. The shared leaf fails
968
+ // closed before querying, so hook and briefing cannot diverge here.
969
+ const stateLines = [
970
+ ...taskStateLines(
971
+ parseTaskState(parseEntityMetadata(taskRow?.metadata)),
972
+ projectName,
973
+ ),
974
+ ...unreadInboxLines(unreadDeliveryCount(db, projectName), projectName),
975
+ ];
940
976
 
941
977
  // The pools overlap by construction (a lesson tagged to this project
942
978
  // is in lessonEntities AND projectEntities); the shared assembler
@@ -965,6 +1001,7 @@ process.stdin.on('end', async () => {
965
1001
  [
966
1002
  { entities: topLessons.map(toEntity), foreign: false },
967
1003
  { entities: projectEntities.map(toEntity), foreign: false },
1004
+ { entities: globalEntities.map(toEntity), foreign: false, global: true },
968
1005
  { entities: recentEntities.map(toEntity), foreign: true },
969
1006
  ],
970
1007
  projectName,
@@ -994,8 +1031,9 @@ process.stdin.on('end', async () => {
994
1031
  // ever been told can end up in an observation), so it must be
995
1032
  // delimited the same way on every injection path — not hand-rolled
996
1033
  // per hook. The lines arrive already budgeted — assembleTopologyBlock
997
- // charges the task-state block and the sections against ONE ceiling
998
- // and returns whole lines only, so the closing fence cannot be cut.
1034
+ // charges task state plus project/foreign sections against the main
1035
+ // ceiling and global context against its small additive ceiling. It
1036
+ // returns whole lines only, so the closing fence cannot be cut.
999
1037
  memoryContext = buildReferenceContext(memoryLines);
1000
1038
  // The citation contract — OUTSIDE the fence on purpose: the fence
1001
1039
  // declares its content "background data, not instructions", and
@@ -1027,15 +1065,18 @@ process.stdin.on('end', async () => {
1027
1065
  // kept as the record of what was shown.
1028
1066
  //
1029
1067
  // The set below is every pool the topology block draws from — the
1030
- // lessons pool included. It used to record only project + recent
1031
- // rows, so an injected lesson could never be credited at all.
1068
+ // lessons pool included. It is derived from rendered citation handles,
1069
+ // so clipped or budgeted-away candidates cannot be credited as shown.
1032
1070
  try {
1033
- const seenIds = new Set();
1034
- const allInjected = [...topLessons, ...projectEntities, ...recentEntities].filter(e => {
1035
- if (seenIds.has(e.id)) return false;
1036
- seenIds.add(e.id);
1037
- return true;
1071
+ const renderedEntityIds = memoryLines.flatMap((line) => {
1072
+ const match = line.match(/ \[mem:(\d{1,10})\]$/);
1073
+ return match ? [Number(match[1])] : [];
1038
1074
  });
1075
+ const poolEntities = [...topLessons, ...projectEntities, ...globalEntities, ...recentEntities];
1076
+ const entitiesById = new Map(poolEntities.map((entity) => [entity.id, entity]));
1077
+ const allInjected = renderedEntityIds
1078
+ .map((id) => entitiesById.get(id))
1079
+ .filter(Boolean);
1039
1080
 
1040
1081
  if (allInjected.length > 0) {
1041
1082
  const sessionsDir = join(memeshDir, 'sessions');
@@ -87,6 +87,30 @@ async function runAutoUpdateAtStop() {
87
87
  // Handles the current Claude Code transcript format where tool_use/tool_result
88
88
  // are nested inside assistant/user message entries, not top-level entries.
89
89
  // Defensive: never throws — malformed lines are silently skipped.
90
+ /**
91
+ * File paths a shell command writes in place. Recognises the shapes an
92
+ * agent actually uses to edit without Write/Edit: heredoc redirection
93
+ * (`> path <<'EOF'`, `cat > path`), `sed -i`, `tee`, and `pathlib.Path('x')
94
+ * .write_text(` / `fs.writeFileSync('x'` inside an inline script. Returns
95
+ * basenames' sources; the caller keeps basename() as the stored form.
96
+ */
97
+ function bashEditedPaths(cmd) {
98
+ if (typeof cmd !== 'string') return [];
99
+ const found = new Set();
100
+ const add = (m) => { if (m && m[1] && !m[1].startsWith('/dev/') && !m[1].startsWith('/tmp/')) found.add(m[1]); };
101
+ for (const re of [
102
+ /(?:^|[^<])>\s*"?([^\s"'>|&;]+)"?\s*<<\s*['"]?\w+['"]?/g, // > file <<'EOF'
103
+ /\bcat\s*>\s*"?([^\s"'>|&;]+)"?/g, // cat > file
104
+ /\btee\s+(?:-a\s+)?"?([^\s"'>|&;]+)"?/g, // tee file
105
+ /\bsed\s+-i(?:\s+'')?\s+(?:'[^']*'|"[^"]*")\s+"?([^\s"'>|&;]+)"?/g, // sed -i '...' file
106
+ /Path\(\s*['"]([^'"]+)['"]\s*\)\s*\.write_text\(/g, // pathlib write_text
107
+ /writeFileSync\(\s*['"]([^'"]+)['"]/g, // fs.writeFileSync
108
+ ]) {
109
+ let m; while ((m = re.exec(cmd)) !== null) add(m);
110
+ }
111
+ return [...found];
112
+ }
113
+
90
114
  function parseTranscript(transcriptPath) {
91
115
  const filesEdited = new Set();
92
116
  const bashCommands = [];
@@ -118,6 +142,15 @@ function parseTranscript(transcriptPath) {
118
142
  }
119
143
  if (block.name === 'Bash') {
120
144
  const cmd = block.input?.command ?? '';
145
+ // A session that edits through Bash — heredocs, sed -i, a
146
+ // short python script — never produces a Write/Edit block, so
147
+ // `filesEdited` stayed empty and the summary asserted "0 files
148
+ // edited" for a session that edited a dozen. The count also
149
+ // drives the re-capture guard below, so the same gap made the
150
+ // -summary entity re-append on every Stop (#240). Recognise the
151
+ // common in-place write shapes; anything not matched is simply
152
+ // uncounted, which is honest — it is not asserted as zero.
153
+ for (const fp of bashEditedPaths(cmd)) filesEdited.add(basename(fp));
121
154
  if (typeof cmd === 'string' && cmd.length > 10 && !cmd.startsWith('ls') && !cmd.startsWith('cd')) {
122
155
  // Redact BEFORE truncating. A bash command line is the single
123
156
  // most likely place a credential appears in a transcript
@@ -347,7 +380,13 @@ process.stdin.on('end', async () => {
347
380
  // A dedup bail is a SUCCESSFUL run — the loop executed and correctly
348
381
  // decided there was nothing to do — so it stamps the heartbeat like
349
382
  // the capture path below does. Only a throw leaves no stamp.
350
- const alreadyCaptured = db.prepare("SELECT id FROM entities WHERE name = ?").get(`session-${sessionId}-files`);
383
+ // Guard on ANY of this session's three entities, not only `-files`.
384
+ // A Bash-only session created no `-files` row, so the guard never
385
+ // tripped and `-summary` was re-appended on every Stop — measured: 56
386
+ // observations, 16 unique, three commands stored fourteen times each.
387
+ const alreadyCaptured = db.prepare(
388
+ "SELECT id FROM entities WHERE name IN (?, ?, ?) LIMIT 1",
389
+ ).get(`session-${sessionId}-files`, `session-${sessionId}-fixes`, `session-${sessionId}-summary`);
351
390
  if (alreadyCaptured) {
352
391
  recordHookRun(db, 'session-summary');
353
392
  return exit0();