@pcircle/memesh 4.8.2 → 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 (116) 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 +10 -5
  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 +3 -3
  10. package/dist/core/agent-message-inbox.d.ts +2 -2
  11. package/dist/core/agent-message-inbox.d.ts.map +1 -1
  12. package/dist/core/agent-message-inbox.js +10 -5
  13. package/dist/core/agent-message-inbox.js.map +1 -1
  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 +18 -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/doctor.d.ts +8 -0
  30. package/dist/core/doctor.d.ts.map +1 -1
  31. package/dist/core/doctor.js +309 -10
  32. package/dist/core/doctor.js.map +1 -1
  33. package/dist/core/install-channel.d.ts +3 -0
  34. package/dist/core/install-channel.d.ts.map +1 -1
  35. package/dist/core/install-channel.js +79 -2
  36. package/dist/core/install-channel.js.map +1 -1
  37. package/dist/core/install-hooks.d.ts.map +1 -1
  38. package/dist/core/install-hooks.js +10 -6
  39. package/dist/core/install-hooks.js.map +1 -1
  40. package/dist/core/lesson-slug.d.ts.map +1 -1
  41. package/dist/core/lesson-slug.js +6 -4
  42. package/dist/core/lesson-slug.js.map +1 -1
  43. package/dist/core/llm-client.d.ts.map +1 -1
  44. package/dist/core/llm-client.js +4 -1
  45. package/dist/core/llm-client.js.map +1 -1
  46. package/dist/core/llm-validator.d.ts.map +1 -1
  47. package/dist/core/llm-validator.js +6 -15
  48. package/dist/core/llm-validator.js.map +1 -1
  49. package/dist/core/ollama-host.d.ts +6 -0
  50. package/dist/core/ollama-host.d.ts.map +1 -0
  51. package/dist/core/ollama-host.js +30 -0
  52. package/dist/core/ollama-host.js.map +1 -0
  53. package/dist/core/schema-export.d.ts.map +1 -1
  54. package/dist/core/schema-export.js +8 -6
  55. package/dist/core/schema-export.js.map +1 -1
  56. package/dist/core/work-topology.d.ts +3 -0
  57. package/dist/core/work-topology.d.ts.map +1 -1
  58. package/dist/core/work-topology.js +24 -2
  59. package/dist/core/work-topology.js.map +1 -1
  60. package/dist/host-adapters/claude-channel.d.ts +0 -58
  61. package/dist/host-adapters/claude-channel.d.ts.map +1 -1
  62. package/dist/host-adapters/claude-channel.js +0 -242
  63. package/dist/host-adapters/claude-channel.js.map +1 -1
  64. package/dist/host-adapters/codex-cli-queue.d.ts.map +1 -1
  65. package/dist/host-adapters/codex-cli-queue.js +15 -12
  66. package/dist/host-adapters/codex-cli-queue.js.map +1 -1
  67. package/dist/host-runtime/acp.d.ts +2 -0
  68. package/dist/host-runtime/acp.d.ts.map +1 -1
  69. package/dist/host-runtime/acp.js +4 -0
  70. package/dist/host-runtime/acp.js.map +1 -1
  71. package/dist/host-runtime/claude.d.ts +2 -0
  72. package/dist/host-runtime/claude.d.ts.map +1 -1
  73. package/dist/host-runtime/claude.js +6 -5
  74. package/dist/host-runtime/claude.js.map +1 -1
  75. package/dist/host-runtime/codex-session.d.ts +3 -1
  76. package/dist/host-runtime/codex-session.d.ts.map +1 -1
  77. package/dist/host-runtime/codex-session.js +7 -5
  78. package/dist/host-runtime/codex-session.js.map +1 -1
  79. package/dist/host-runtime/codex.d.ts +2 -0
  80. package/dist/host-runtime/codex.d.ts.map +1 -1
  81. package/dist/host-runtime/codex.js +4 -0
  82. package/dist/host-runtime/codex.js.map +1 -1
  83. package/dist/host-runtime/router-client.d.ts +2 -0
  84. package/dist/host-runtime/router-client.d.ts.map +1 -1
  85. package/dist/host-runtime/router-client.js.map +1 -1
  86. package/dist/skills-manifest.json +23 -13
  87. package/dist/storage/graph-repairs.d.ts.map +1 -1
  88. package/dist/storage/graph-repairs.js +70 -22
  89. package/dist/storage/graph-repairs.js.map +1 -1
  90. package/dist/transports/agent-messaging.d.ts +10 -1
  91. package/dist/transports/agent-messaging.d.ts.map +1 -1
  92. package/dist/transports/agent-messaging.js +92 -9
  93. package/dist/transports/agent-messaging.js.map +1 -1
  94. package/dist/transports/cli/cli.d.ts +4 -0
  95. package/dist/transports/cli/cli.d.ts.map +1 -1
  96. package/dist/transports/cli/cli.js +65 -26
  97. package/dist/transports/cli/cli.js.map +1 -1
  98. package/dist/transports/mcp/handlers.d.ts +10 -6
  99. package/dist/transports/mcp/handlers.d.ts.map +1 -1
  100. package/dist/transports/mcp/handlers.js +21 -9
  101. package/dist/transports/mcp/handlers.js.map +1 -1
  102. package/dist/transports/schemas.d.ts +5 -0
  103. package/dist/transports/schemas.d.ts.map +1 -1
  104. package/dist/transports/schemas.js +8 -1
  105. package/dist/transports/schemas.js.map +1 -1
  106. package/docs/platforms/README.md +1 -1
  107. package/docs/platforms/agent-messaging.md +89 -36
  108. package/llms-install.md +29 -6
  109. package/package.json +5 -1
  110. package/scripts/hooks/_generated/agent-message-inbox.js +10 -5
  111. package/scripts/hooks/_generated/citation-rule.js +5 -2
  112. package/scripts/hooks/_generated/work-topology.js +24 -2
  113. package/scripts/hooks/_shared.js +1 -1
  114. package/scripts/hooks/session-start.js +37 -23
  115. package/scripts/upgrade-plugin.sh +531 -81
  116. package/skills/memesh/SKILL.md +20 -5
@@ -6,18 +6,21 @@
6
6
  // always-on capture path survives a missing or stale dist/ while staying
7
7
  // byte-locked to core — eliminating the hand-mirror drift behind the P0 FTS bug.
8
8
  // ============================================================================
9
- export function unreadDeliveryCount(db, project) {
9
+ export function unreadDeliveryCount(db, project, recipient) {
10
+ if (!recipient)
11
+ return 0;
10
12
  try {
11
13
  const row = db.prepare(`SELECT COUNT(*) AS n
12
14
  FROM agent_message_deliveries d
13
15
  WHERE d.project = ?
16
+ AND d.recipient = ?
14
17
  AND NOT EXISTS (
15
18
  SELECT 1 FROM agent_message_receipts r
16
19
  WHERE r.project = d.project
17
20
  AND r.recipient = d.recipient
18
21
  AND r.message_id = d.message_id
19
22
  AND r.receipt_kind = 'intake'
20
- )`).get(project);
23
+ )`).get(project, recipient);
21
24
  const n = row?.n;
22
25
  return typeof n === 'number' && n > 0 ? n : 0;
23
26
  }
@@ -25,9 +28,11 @@ export function unreadDeliveryCount(db, project) {
25
28
  return 0;
26
29
  }
27
30
  }
28
- export function unreadInboxLines(count, project) {
29
- if (count <= 0)
31
+ export function unreadInboxLines(count, project, recipient) {
32
+ if (count <= 0 || !recipient)
30
33
  return [];
31
34
  const noun = count === 1 ? 'message' : 'messages';
32
- return [`${count} ${noun} waiting for "${project}" — fetch them with the message tool; fetching does not acknowledge.`];
35
+ const displayProject = JSON.stringify(project);
36
+ const displayRecipient = JSON.stringify(recipient);
37
+ return [`${count} ${noun} waiting for ${displayRecipient} in project ${displayProject} — poll the message tool with project ${displayProject} and recipient ${displayRecipient}, then fetch each message_id; fetching does not acknowledge.`];
33
38
  }
@@ -25,8 +25,11 @@ memory is recorded as unused: that is how MeMesh learns which memories are
25
25
  worth the tokens they cost you.
26
26
  `;
27
27
  export function citationRuleDir(scope, home, cwd) {
28
- const base = scope === 'user' ? home : cwd;
29
- return path.join(base, '.claude', 'rules');
28
+ if (scope === 'project')
29
+ return path.join(cwd, '.claude', 'rules');
30
+ const relocated = process.env.CLAUDE_CONFIG_DIR;
31
+ const configRoot = relocated ? path.resolve(relocated) : path.join(home, '.claude');
32
+ return path.join(configRoot, 'rules');
30
33
  }
31
34
  export function citationRulePath(scope, home, cwd) {
32
35
  return path.join(citationRuleDir(scope, home, cwd), CITATION_RULE_FILENAME);
@@ -82,10 +82,15 @@ export function groupTopology(entities, projectName) {
82
82
  const lessons = [];
83
83
  const knowledge = [];
84
84
  const evidence = [];
85
+ const global = [];
85
86
  const foreign = [];
86
87
  for (const e of entities) {
87
88
  if (e.type === 'task-state')
88
89
  continue;
90
+ if (e.global) {
91
+ global.push(e);
92
+ continue;
93
+ }
89
94
  if (e.foreign) {
90
95
  foreign.push(e);
91
96
  continue;
@@ -104,7 +109,7 @@ export function groupTopology(entities, projectName) {
104
109
  else
105
110
  decisions.push(e);
106
111
  }
107
- for (const list of [decisions, lessons, knowledge, evidence, foreign])
112
+ for (const list of [decisions, lessons, knowledge, evidence, global, foreign])
108
113
  list.sort(bySignal);
109
114
  const sections = [];
110
115
  if (decisions.length)
@@ -115,6 +120,8 @@ export function groupTopology(entities, projectName) {
115
120
  sections.push({ heading: `What is known about "${projectName}":`, entities: knowledge });
116
121
  if (evidence.length)
117
122
  sections.push({ heading: `Recent activity in "${projectName}":`, entities: evidence });
123
+ if (global.length)
124
+ sections.push({ heading: 'Global memory — applies across projects:', entities: global });
118
125
  if (foreign.length)
119
126
  sections.push({ heading: 'From your other projects (may or may not apply here):', entities: foreign });
120
127
  return sections;
@@ -124,6 +131,11 @@ export const DEFAULT_TOPOLOGY_BUDGET = {
124
131
  maxChars: 4000,
125
132
  maxLineChars: 160,
126
133
  };
134
+ export const GLOBAL_TOPOLOGY_LIMIT = 3;
135
+ const GLOBAL_TOPOLOGY_BUDGET = {
136
+ maxChars: 640,
137
+ maxLineChars: DEFAULT_TOPOLOGY_BUDGET.maxLineChars,
138
+ };
127
139
  export const TOPOLOGY_CANDIDATE_CAP = 400;
128
140
  export const SNIPPET_FETCH_CHARS = DEFAULT_TOPOLOGY_BUDGET.maxLineChars * 4;
129
141
  export function buildTopologyLines(entities, projectName, budget) {
@@ -155,12 +167,18 @@ export function buildTopologyLines(entities, projectName, budget) {
155
167
  export function assembleTopologyBlock(stateLines, pools, projectName, budget = DEFAULT_TOPOLOGY_BUDGET) {
156
168
  const seen = new Set();
157
169
  const candidates = [];
170
+ const globalCandidates = [];
158
171
  for (const pool of pools) {
159
172
  for (const e of pool.entities) {
160
173
  if (seen.has(e.name))
161
174
  continue;
162
175
  seen.add(e.name);
163
- candidates.push(pool.foreign && !e.foreign ? { ...e, foreign: true } : e);
176
+ if (pool.global) {
177
+ globalCandidates.push(e.global ? e : { ...e, global: true });
178
+ }
179
+ else {
180
+ candidates.push(pool.foreign && !e.foreign ? { ...e, foreign: true } : e);
181
+ }
164
182
  }
165
183
  }
166
184
  const lines = [];
@@ -173,9 +191,13 @@ export function assembleTopologyBlock(stateLines, pools, projectName, budget = D
173
191
  const topologyLines = remaining > 0
174
192
  ? buildTopologyLines(candidates, projectName, { ...budget, maxChars: remaining })
175
193
  : [];
194
+ const globalLines = buildTopologyLines(globalCandidates, projectName, GLOBAL_TOPOLOGY_BUDGET);
176
195
  if (lines.length > 0 && topologyLines.length > 0)
177
196
  lines.push('');
178
197
  lines.push(...topologyLines);
198
+ if (lines.length > 0 && globalLines.length > 0)
199
+ lines.push('');
200
+ lines.push(...globalLines);
179
201
  return lines;
180
202
  }
181
203
  export function buildReferenceContext(memoryLines) {
@@ -34,7 +34,7 @@ import {
34
34
  slugFromRemoteUrl,
35
35
  } from './_generated/core-paths.js';
36
36
  import { autoCaptureDecision } from './_generated/capture-flag.js';
37
- export { assembleTopologyBlock, buildReferenceContext, extractCitedMemoryIds, DEFAULT_TOPOLOGY_BUDGET, SNIPPET_FETCH_CHARS, TOPOLOGY_CANDIDATE_CAP } from './_generated/work-topology.js';
37
+ export { assembleTopologyBlock, buildReferenceContext, extractCitedMemoryIds, DEFAULT_TOPOLOGY_BUDGET, GLOBAL_TOPOLOGY_LIMIT, SNIPPET_FETCH_CHARS, TOPOLOGY_CANDIDATE_CAP } from './_generated/work-topology.js';
38
38
  export { readRepoState, repoStateLines } from './_generated/repo-state.js';
39
39
  export { matchingGuards, guardFromMetadata } from './_generated/guards.js';
40
40
  export { writeCitationRule, citationRulePath, CITATION_RULE_BODY } from './_generated/citation-rule.js';
@@ -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,
@@ -786,9 +787,12 @@ process.stdin.on('end', async () => {
786
787
  };
787
788
 
788
789
  const projectTag = `project:${projectName}`;
790
+ const notGlobal = colNames.has('namespace')
791
+ ? "AND (e.namespace IS NULL OR e.namespace <> 'global')"
792
+ : '';
789
793
  const projectQuery = buildScoringQuery(
790
794
  `JOIN tags t ON t.entity_id = e.id`,
791
- `WHERE t.tag = ? ${statusFilter}`,
795
+ `WHERE t.tag = ? ${notGlobal} ${statusFilter}`,
792
796
  );
793
797
  // Over-fetch WIDE, then filter. The window used to be `sessionLimit * 3`
794
798
  // and the trust filter ran after it — so a class of entity that ranks
@@ -811,26 +815,27 @@ process.stdin.on('end', async () => {
811
815
  // by nobody (#242): 1814 entities, 2 global, one of them structurally
812
816
  // uninjectable. A standing behaviour rule sat in that state for months.
813
817
  //
814
- // Global rows ride in a SEPARATE, small window so they cannot displace
815
- // the project's own memories: the project keeps `sessionLimit` slots and
816
- // global adds at most GLOBAL_SLOTS after them. Same trust filter, same
817
- // scoring. The column is absent on pre-namespace schemas; then this
818
- // branch is simply empty, which is the old behaviour.
819
- const GLOBAL_SLOTS = 3;
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.
820
822
  let globalEntities = [];
821
823
  if (colNames.has('namespace')) {
822
824
  const globalQuery = buildScoringQuery('', `WHERE e.namespace = 'global' ${statusFilter}`);
823
- const projectIds = new Set(projectOnly.map(e => e.id));
824
825
  globalEntities = db.prepare(globalQuery).all(CANDIDATE_CAP)
825
- .filter(entity => isTrustedForAutoContext(entity.metadata) && !projectIds.has(entity.id))
826
- .slice(0, GLOBAL_SLOTS);
826
+ .filter(entity => isTrustedForAutoContext(entity.metadata))
827
+ .slice(0, GLOBAL_TOPOLOGY_LIMIT);
827
828
  }
828
- const projectEntities = [...projectOnly.slice(0, sessionLimit), ...globalEntities];
829
+ const projectEntities = projectOnly.slice(0, sessionLimit);
829
830
 
830
831
  // recentStatusFilter is "WHERE status = 'active'" or "" — the bare-column
831
832
  // form is fine when there's no JOIN, but we now alias the table as `e`,
832
833
  // so rewrite to e.status for consistency.
833
- 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 ')}` : '';
834
839
  const recentQuery = buildScoringQuery('', recentWhere);
835
840
  const recentEntities = db.prepare(recentQuery).all(CANDIDATE_CAP)
836
841
  .filter(entity => isTrustedForAutoContext(entity.metadata))
@@ -850,6 +855,7 @@ process.stdin.on('end', async () => {
850
855
  JOIN tags t ON t.entity_id = e.id
851
856
  WHERE e.type = 'lesson_learned'
852
857
  ${hasStatus ? "AND e.status = 'active'" : ''}
858
+ ${colNames.has('namespace') ? "AND (e.namespace IS NULL OR e.namespace <> 'global')" : ''}
853
859
  AND t.tag = ?
854
860
  LIMIT 50
855
861
  `).all(projectTag).filter(entity => isTrustedForAutoContext(entity.metadata));
@@ -866,9 +872,11 @@ process.stdin.on('end', async () => {
866
872
  // SessionStart system message takes one row in the Claude Code
867
873
  // log instead of four. Counts are count-only — no entity bullets.
868
874
  const projectCount = projectEntities.length;
875
+ const globalCount = globalEntities.length;
869
876
  const recentCount = recentEntities.length;
870
877
  const memoryFragments = [];
871
878
  if (projectCount > 0) memoryFragments.push(`${projectCount} project`);
879
+ if (globalCount > 0) memoryFragments.push(`${globalCount} global`);
872
880
  if (recentCount > 0) memoryFragments.push(`${recentCount} recent`);
873
881
 
874
882
  let summary;
@@ -915,6 +923,7 @@ process.stdin.on('end', async () => {
915
923
  const rankedIds = [
916
924
  ...topLessons.map(e => e.id),
917
925
  ...projectEntities.map(e => e.id),
926
+ ...globalEntities.map(e => e.id),
918
927
  ...recentEntities.map(e => e.id),
919
928
  ];
920
929
  const uniqueIds = [...new Set(rankedIds)];
@@ -955,8 +964,8 @@ process.stdin.on('end', async () => {
955
964
  const taskRow = db
956
965
  .prepare('SELECT metadata FROM entities WHERE name = ?')
957
966
  .get(taskStateName(projectName));
958
- // Inbox line beside the stated lines the same leaf briefing uses,
959
- // so the two surfaces cannot disagree on what "unread" means.
967
+ // SessionStart has no exact recipient identity. The shared leaf fails
968
+ // closed before querying, so hook and briefing cannot diverge here.
960
969
  const stateLines = [
961
970
  ...taskStateLines(
962
971
  parseTaskState(parseEntityMetadata(taskRow?.metadata)),
@@ -992,6 +1001,7 @@ process.stdin.on('end', async () => {
992
1001
  [
993
1002
  { entities: topLessons.map(toEntity), foreign: false },
994
1003
  { entities: projectEntities.map(toEntity), foreign: false },
1004
+ { entities: globalEntities.map(toEntity), foreign: false, global: true },
995
1005
  { entities: recentEntities.map(toEntity), foreign: true },
996
1006
  ],
997
1007
  projectName,
@@ -1021,8 +1031,9 @@ process.stdin.on('end', async () => {
1021
1031
  // ever been told can end up in an observation), so it must be
1022
1032
  // delimited the same way on every injection path — not hand-rolled
1023
1033
  // per hook. The lines arrive already budgeted — assembleTopologyBlock
1024
- // charges the task-state block and the sections against ONE ceiling
1025
- // 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.
1026
1037
  memoryContext = buildReferenceContext(memoryLines);
1027
1038
  // The citation contract — OUTSIDE the fence on purpose: the fence
1028
1039
  // declares its content "background data, not instructions", and
@@ -1054,15 +1065,18 @@ process.stdin.on('end', async () => {
1054
1065
  // kept as the record of what was shown.
1055
1066
  //
1056
1067
  // The set below is every pool the topology block draws from — the
1057
- // lessons pool included. It used to record only project + recent
1058
- // 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.
1059
1070
  try {
1060
- const seenIds = new Set();
1061
- const allInjected = [...topLessons, ...projectEntities, ...recentEntities].filter(e => {
1062
- if (seenIds.has(e.id)) return false;
1063
- seenIds.add(e.id);
1064
- return true;
1071
+ const renderedEntityIds = memoryLines.flatMap((line) => {
1072
+ const match = line.match(/ \[mem:(\d{1,10})\]$/);
1073
+ return match ? [Number(match[1])] : [];
1065
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);
1066
1080
 
1067
1081
  if (allInjected.length > 0) {
1068
1082
  const sessionsDir = join(memeshDir, 'sessions');