@rubytech/create-maxy-code 0.1.187 → 0.1.189

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 (181) hide show
  1. package/dist/index.js +129 -38
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-mcp/dist/index.js +4 -3
  4. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  5. package/payload/platform/lib/graph-mcp/src/index.ts +4 -3
  6. package/payload/platform/lib/graph-search/dist/index.d.ts +12 -0
  7. package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
  8. package/payload/platform/lib/graph-search/dist/index.js +4 -1
  9. package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
  10. package/payload/platform/lib/graph-search/src/index.ts +4 -2
  11. package/payload/platform/package.json +1 -0
  12. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  13. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  14. package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +16 -0
  15. package/payload/platform/plugins/admin/hooks/mcp-tool-missing.sh +94 -0
  16. package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +7 -0
  17. package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +3 -3
  18. package/payload/platform/plugins/admin/skills/stream-log-review/references/analysis-patterns.md +11 -7
  19. package/payload/platform/plugins/docs/references/admin-ui.md +3 -4
  20. package/payload/platform/plugins/docs/references/deployment.md +1 -1
  21. package/payload/platform/plugins/docs/references/internals.md +6 -4
  22. package/payload/platform/plugins/docs/references/plugins-guide.md +3 -3
  23. package/payload/platform/plugins/docs/references/voice-mirror-guide.md +2 -0
  24. package/payload/platform/plugins/graph/.claude-plugin/plugin.json +8 -0
  25. package/payload/platform/plugins/graph/PLUGIN.md +37 -0
  26. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  27. package/payload/platform/plugins/memory/PLUGIN.md +6 -1
  28. package/payload/platform/plugins/memory/mcp/dist/index.js +63 -0
  29. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  30. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.d.ts +2 -0
  31. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.js +135 -0
  33. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.js.map +1 -0
  34. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +1 -1
  35. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +1 -1
  36. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.d.ts +16 -0
  37. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.d.ts.map +1 -0
  38. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.js +56 -0
  39. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.js.map +1 -0
  40. package/payload/platform/plugins/memory/references/graph-primitives.md +36 -27
  41. package/payload/platform/plugins/obsidian-import/PLUGIN.md +1 -1
  42. package/payload/platform/scripts/check-canonical-tool-names.mjs +110 -0
  43. package/payload/platform/scripts/generate-canonical-tool-names.mjs +63 -0
  44. package/payload/platform/scripts/identity-forbidden-token-check.mjs +6 -5
  45. package/payload/platform/scripts/lib/canonical-tool-names.mjs +88 -0
  46. package/payload/platform/scripts/setup-account.sh +12 -1
  47. package/payload/platform/scripts/smoke-boot-services.sh +1 -0
  48. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.d.ts +7 -0
  49. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.d.ts.map +1 -0
  50. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts +5 -0
  51. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -0
  52. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +205 -0
  53. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -0
  54. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.js +23 -0
  55. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.js.map +1 -0
  56. package/payload/platform/services/claude-session-manager/dist/config.d.ts +4 -0
  57. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  58. package/payload/platform/services/claude-session-manager/dist/config.js +6 -1
  59. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  60. package/payload/platform/services/claude-session-manager/dist/index.js +31 -6
  61. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  62. package/payload/platform/services/claude-session-manager/dist/mcp-tools-probe.d.ts +6 -4
  63. package/payload/platform/services/claude-session-manager/dist/mcp-tools-probe.d.ts.map +1 -1
  64. package/payload/platform/services/claude-session-manager/dist/mcp-tools-probe.js.map +1 -1
  65. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +1 -0
  66. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  67. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +50 -26
  68. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  69. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +44 -0
  70. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -0
  71. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +200 -0
  72. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -0
  73. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
  74. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +50 -34
  75. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
  76. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +6 -8
  78. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  79. package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts.map +1 -1
  80. package/payload/platform/services/claude-session-manager/dist/tool-surface.js +23 -13
  81. package/payload/platform/services/claude-session-manager/dist/tool-surface.js.map +1 -1
  82. package/payload/platform/templates/agents/admin/IDENTITY.md +14 -1
  83. package/payload/platform/templates/specialists/agents/content-producer.md +3 -3
  84. package/payload/platform/templates/specialists/agents/database-operator.md +1 -1
  85. package/payload/platform/templates/specialists/agents/librarian.md +3 -2
  86. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  87. package/payload/platform/templates/specialists/agents/project-manager.md +1 -1
  88. package/payload/platform/templates/specialists/agents/research-assistant.md +1 -1
  89. package/payload/premium-plugins/writer-craft/skills/voice-mirror/SKILL.md +6 -0
  90. package/payload/server/{chunk-EFJ2EXZK.js → chunk-WWD4TCJJ.js} +0 -16
  91. package/payload/server/maxy-edge.js +1 -1
  92. package/payload/server/public/assets/Checkbox-g2WZRfCt.js +1 -0
  93. package/payload/server/public/assets/admin-Cst6NkUE.js +216 -0
  94. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-DZoOzmjE.js → architectureDiagram-Q4EWVU46-BVKxPUIS.js} +1 -1
  95. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DG3yufCR.js → blockDiagram-DXYQGD6D-BpD-1G1V.js} +1 -1
  96. package/payload/server/public/assets/brand-BGvv6AYI.js +9 -0
  97. package/payload/server/public/assets/brand-BQW2DYy_.css +1 -0
  98. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-BBx5wXuZ.js → c4Diagram-AHTNJAMY-CFl9Dr6U.js} +1 -1
  99. package/payload/server/public/assets/channel-BTEFjudQ.js +1 -0
  100. package/payload/server/public/assets/{chunk-336JU56O-DwP7g5ns.js → chunk-336JU56O-B2Uc6pEC.js} +2 -2
  101. package/payload/server/public/assets/{chunk-426QAEUC-DXzGdc22.js → chunk-426QAEUC-CcIGcm29.js} +1 -1
  102. package/payload/server/public/assets/{chunk-4TB4RGXK-1LDRjk0E.js → chunk-4TB4RGXK-Dvf5uiFR.js} +1 -1
  103. package/payload/server/public/assets/{chunk-5FUZZQ4R-BHjWhwhn.js → chunk-5FUZZQ4R-DUQR1rF9.js} +1 -1
  104. package/payload/server/public/assets/{chunk-5PVQY5BW-CwPHRVN8.js → chunk-5PVQY5BW-OTvNug7K.js} +1 -1
  105. package/payload/server/public/assets/{chunk-EDXVE4YY-7443hrYZ.js → chunk-EDXVE4YY-CRAsAWbD.js} +1 -1
  106. package/payload/server/public/assets/{chunk-ENJZ2VHE-BTnmmnmX.js → chunk-ENJZ2VHE-BNllMoi_.js} +1 -1
  107. package/payload/server/public/assets/{chunk-ICPOFSXX-CPnMJplL.js → chunk-ICPOFSXX-vTeVS4qd.js} +1 -1
  108. package/payload/server/public/assets/{chunk-OYMX7WX6-DCQ5Q4RS.js → chunk-OYMX7WX6-BdSFcvNa.js} +1 -1
  109. package/payload/server/public/assets/{chunk-U2HBQHQK-BmfZG8e4.js → chunk-U2HBQHQK-BpFXup4D.js} +1 -1
  110. package/payload/server/public/assets/{chunk-X2U36JSP-Da2yJL2u.js → chunk-X2U36JSP-DF-6QHx4.js} +1 -1
  111. package/payload/server/public/assets/{chunk-YZCP3GAM-BdUmMUJ6.js → chunk-YZCP3GAM-CKgDoLRT.js} +1 -1
  112. package/payload/server/public/assets/{chunk-ZZ45TVLE-abkGirrZ.js → chunk-ZZ45TVLE-B0ACvZCZ.js} +1 -1
  113. package/payload/server/public/assets/classDiagram-6PBFFD2Q-ClkdniKF.js +1 -0
  114. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-kjJetfh2.js +1 -0
  115. package/payload/server/public/assets/clone-BllVm2iY.js +1 -0
  116. package/payload/server/public/assets/{dagre-BVVuAEsU.js → dagre-3yl9rfxR.js} +1 -1
  117. package/payload/server/public/assets/{dagre-KV5264BT-VG2kNj19.js → dagre-KV5264BT-D-Ux4uwL.js} +1 -1
  118. package/payload/server/public/assets/data-COwlCdrl.js +1 -0
  119. package/payload/server/public/assets/{diagram-5BDNPKRD-DRiVy69K.js → diagram-5BDNPKRD-D-MOMikl.js} +1 -1
  120. package/payload/server/public/assets/{diagram-G4DWMVQ6-DtpiRTYX.js → diagram-G4DWMVQ6-DKdFcnhf.js} +1 -1
  121. package/payload/server/public/assets/{diagram-MMDJMWI5-Da3t99fY.js → diagram-MMDJMWI5-ajamR766.js} +1 -1
  122. package/payload/server/public/assets/{diagram-TYMM5635-BzQmLTJQ.js → diagram-TYMM5635-D2Pbk2Kk.js} +1 -1
  123. package/payload/server/public/assets/{erDiagram-SMLLAGMA-D3Ll-GDw.js → erDiagram-SMLLAGMA-v3o948pk.js} +1 -1
  124. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-D17bje6d.js → flowDiagram-DWJPFMVM-byC1a6GE.js} +1 -1
  125. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BjeQ-Bun.js → ganttDiagram-T4ZO3ILL-Dybzs-Sw.js} +1 -1
  126. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-LtX4pvZS.js → gitGraphDiagram-UUTBAWPF-DvzXYLuW.js} +1 -1
  127. package/payload/server/public/assets/graph-CkUZEqdW.js +1 -0
  128. package/payload/server/public/assets/graph-labels-Dt1bmq6W.js +1 -0
  129. package/payload/server/public/assets/{graphlib-CzwsfZOw.js → graphlib-DvH_spaA.js} +1 -1
  130. package/payload/server/public/assets/{infoDiagram-42DDH7IO-DOgH4i52.js → infoDiagram-42DDH7IO-C2FeU8nE.js} +1 -1
  131. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DYqy5BFj.js → ishikawaDiagram-UXIWVN3A-CpY5W64P.js} +1 -1
  132. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CsFpIULJ.js → journeyDiagram-VCZTEJTY-ymMAQVdm.js} +1 -1
  133. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-5uZuArw7.js → kanban-definition-6JOO6SKY-_NX9hNK3.js} +1 -1
  134. package/payload/server/public/assets/lib-BEvO-mE0.js +33 -0
  135. package/payload/server/public/assets/{line-BY8PmCSW.js → line-Dz7zKWX-.js} +1 -1
  136. package/payload/server/public/assets/{mermaid-parser.core-CJ_9xqwK.js → mermaid-parser.core-BCWd1en6.js} +1 -1
  137. package/payload/server/public/assets/{mermaid.core-CXznBkX7.js → mermaid.core-gSoFkVoN.js} +3 -3
  138. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DZbHBud5.js → mindmap-definition-QFDTVHPH-QuIyK2bd.js} +1 -1
  139. package/payload/server/public/assets/page-CGXxYYRR.js +1 -0
  140. package/payload/server/public/assets/{page-CfN7nw1J.js → page-DC2hsMPj.js} +2 -2
  141. package/payload/server/public/assets/{pieDiagram-DEJITSTG-QmOi4DRG.js → pieDiagram-DEJITSTG-DHK0yy7U.js} +1 -1
  142. package/payload/server/public/assets/public-BnS9zREj.js +7 -0
  143. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-Cpidq6Xj.js → quadrantDiagram-34T5L4WZ-wK1jwWo5.js} +1 -1
  144. package/payload/server/public/assets/{requirementDiagram-MS252O5E-CBqtfbzR.js → requirementDiagram-MS252O5E-W_mH85_d.js} +1 -1
  145. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-4BiY4dV9.js → sankeyDiagram-XADWPNL6-nGAM-YLQ.js} +1 -1
  146. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DVlL-l2u.js → sequenceDiagram-FGHM5R23-SMfUCwBH.js} +1 -1
  147. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-CAm9dW68.js → stateDiagram-FHFEXIEX-CYKXFirH.js} +1 -1
  148. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B9ZwM8kx.js +1 -0
  149. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-CD-tZc4y.js → timeline-definition-GMOUNBTQ-DethbNFa.js} +1 -1
  150. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-Davcm0TF.js → vennDiagram-DHZGUBPP-BUlJTe9Z.js} +1 -1
  151. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-40LBYw_Y.js → wardleyDiagram-NUSXRM2D-CL1TvZ7j.js} +1 -1
  152. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DEPT-__v.js → xychartDiagram-5P7HB3ND-Cav10l-Z.js} +1 -1
  153. package/payload/server/public/brand/Archive/favicon.ico +0 -0
  154. package/payload/server/public/brand/Archive/maxy-black.png +0 -0
  155. package/payload/server/public/brand/Archive/maxy-horizontal.png +0 -0
  156. package/payload/server/public/brand/Archive/maxy-monochrome.png +0 -0
  157. package/payload/server/public/brand/Archive/maxy-square.png +0 -0
  158. package/payload/server/public/brand/Archive/maxy.png +0 -0
  159. package/payload/server/public/brand/Archive/star.png +0 -0
  160. package/payload/server/public/brand/Archive.zip +0 -0
  161. package/payload/server/public/data.html +5 -5
  162. package/payload/server/public/graph.html +6 -6
  163. package/payload/server/public/index.html +8 -8
  164. package/payload/server/public/public.html +5 -5
  165. package/payload/server/server-init.cjs +12 -2
  166. package/payload/server/server.js +312 -793
  167. package/payload/server/public/assets/ChatInput-CvRaT05l.js +0 -13
  168. package/payload/server/public/assets/ChatInput-DzacFNMk.css +0 -1
  169. package/payload/server/public/assets/Checkbox-BJKNkUYu.js +0 -1
  170. package/payload/server/public/assets/admin-BNy4N2d6.js +0 -217
  171. package/payload/server/public/assets/channel-DdtUtt5g.js +0 -1
  172. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CKk2DFZe.js +0 -1
  173. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E--YvfDQCh.js +0 -1
  174. package/payload/server/public/assets/clone-Cw6UfPGM.js +0 -1
  175. package/payload/server/public/assets/data-uBpwaldK.js +0 -1
  176. package/payload/server/public/assets/graph-labels-Cn9jAE6I.js +0 -1
  177. package/payload/server/public/assets/graph-tvTPvBaz.js +0 -1
  178. package/payload/server/public/assets/lib-p4ylk2XS.js +0 -29
  179. package/payload/server/public/assets/page-B3VYaBcf.js +0 -1
  180. package/payload/server/public/assets/public-Bbew78md.js +0 -7
  181. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B2EzXi50.js +0 -1
@@ -0,0 +1,56 @@
1
+ import { getSession } from "../lib/neo4j.js";
2
+ import { notTrashed } from "../../../../../lib/graph-trash/dist/index.js";
3
+ import { buildScopeAndSliceClause } from "../../../../../lib/graph-search/dist/index.js";
4
+ const DEFAULT_LIMIT = 25;
5
+ // Stripped from returned properties: the embedding is a large float vector that
6
+ // only bloats agent context, and accountId is the caller's own scope (mirrors
7
+ // memory-search's formatResult hygiene).
8
+ const STRIPPED_PROPERTY_KEYS = new Set(["embedding", "accountId"]);
9
+ export async function memoryLookupByName(params) {
10
+ const { name, accountId, labels, allowedScopes, sliceToken, agentSlug } = params;
11
+ const limit = params.limit ?? DEFAULT_LIMIT;
12
+ if (!name.trim())
13
+ throw new Error("name is required");
14
+ const scope = buildScopeAndSliceClause(allowedScopes, sliceToken, "n");
15
+ const labelClause = labels && labels.length > 0 ? "AND any(l IN labels(n) WHERE l IN $labels)" : "";
16
+ const agentClause = agentSlug
17
+ ? "AND n.agents IS NOT NULL AND $agentSlug IN n.agents"
18
+ : "";
19
+ const cypher = `MATCH (n)
20
+ WHERE n.accountId = $accountId
21
+ AND toLower(n.name) = toLower($name)
22
+ AND ${notTrashed("n")}
23
+ ${labelClause}
24
+ ${scope.clause}
25
+ ${agentClause}
26
+ RETURN elementId(n) AS nodeId, labels(n) AS labels, properties(n) AS properties
27
+ LIMIT toInteger($limit)`;
28
+ const session = getSession();
29
+ try {
30
+ const result = await session.run(cypher, {
31
+ accountId,
32
+ name,
33
+ limit,
34
+ ...(labels && labels.length > 0 ? { labels } : {}),
35
+ ...scope.params,
36
+ ...(agentSlug ? { agentSlug } : {}),
37
+ });
38
+ return result.records.map((r) => {
39
+ const rawProps = r.get("properties");
40
+ const properties = {};
41
+ for (const [k, v] of Object.entries(rawProps)) {
42
+ if (!STRIPPED_PROPERTY_KEYS.has(k))
43
+ properties[k] = v;
44
+ }
45
+ return {
46
+ nodeId: r.get("nodeId"),
47
+ labels: r.get("labels"),
48
+ properties,
49
+ };
50
+ });
51
+ }
52
+ finally {
53
+ await session.close();
54
+ }
55
+ }
56
+ //# sourceMappingURL=memory-lookup-by-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-lookup-by-name.js","sourceRoot":"","sources":["../../src/tools/memory-lookup-by-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAmDzF,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB,gFAAgF;AAChF,8EAA8E;AAC9E,yCAAyC;AACzC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;AAEnE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAA0B;IAE1B,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,aAAa,CAAC;IAE5C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,wBAAwB,CAAC,aAAa,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;IACvE,MAAM,WAAW,GACf,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,MAAM,WAAW,GAAG,SAAS;QAC3B,CAAC,CAAC,qDAAqD;QACvD,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,MAAM,GAAG;;;aAGJ,UAAU,CAAC,GAAG,CAAC;SACnB,WAAW;SACX,KAAK,CAAC,MAAM;SACZ,WAAW;;6BAES,CAAC;IAE5B,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;YACvC,SAAS;YACT,IAAI;YACJ,KAAK;YACL,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,GAAG,KAAK,CAAC,MAAM;YACf,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAA4B,CAAC;YAChE,MAAM,UAAU,GAA4B,EAAE,CAAC;YAC/C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9C,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC;oBAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACxD,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAW;gBACjC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAa;gBACnC,UAAU;aACX,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
@@ -40,31 +40,40 @@ token-limit discipline the upstream server enforces.
40
40
 
41
41
  ## When the memory tools are absent
42
42
 
43
- If `mcp__memory__memory-write`, `mcp__memory__memory-search`,
44
- `mcp__memory__memory-reindex`,
45
- `mcp__memory__session-compact`, `mcp__memory__session-compact-status`, or
46
- `mcp__memory__conversation-search` is missing from your tool list, the
47
- memory MCP server failed to start on this device. Reply once with
48
- exactly:
49
-
50
- > The memory MCP server failed to start on this device. Run the admin
43
+ The maxy MCP tool names are bound to the live registry (Task 502): the
44
+ canonical long name `mcp__plugin_memory_memory__<tool>` is generated from the
45
+ memory plugin's manifest, a build gate fails when any instruction names a tool
46
+ the registry does not serve, and a PostToolUse hook turns a missing-tool call
47
+ into a fixed envelope. So a missing memory tool is one of two distinct states,
48
+ not the single "failed to start" the old contract assumed:
49
+
50
+ - **Server dead.** None of the `mcp__plugin_memory_memory__*` tools appear in
51
+ your tool list at all. The per-account memory server did not start; the
52
+ platform's `[mcp-init-error] server=memory` line in the per-conversation
53
+ stream log names it.
54
+ - **Name not registered.** The server is up and other
55
+ `mcp__plugin_memory_memory__*` tools are present, but a specific call returns
56
+ `No such tool available`. The `mcp-tool-missing` hook has already logged
57
+ `[mcp-tool-missing] server=memory tool=<tool>` and returned the deterministic
58
+ envelope you are reading.
59
+
60
+ In either state, reply once with exactly:
61
+
62
+ > The memory MCP server is unavailable on this device. Run the admin
51
63
  > system-status check to diagnose — do not retry by other routes.
52
64
 
53
- Then stop. Do not search for a similarly-named tool via `ToolSearch`, do
54
- not improvise via `maxy-graph-read_neo4j_cypher` (it is read-only — every
55
- write goes through the schema-aware memory tools), do not paraphrase.
56
- A missing memory tool is a deterministic failure of the per-account
57
- memory server: the platform's `[mcp-init-error] server=memory` line in
58
- the per-conversation stream log already names it; the user-facing reply
59
- exists so the operator knows the session cannot proceed without an
60
- operator-side fix (the loud-fail contract applies equally
61
- to graph and memory tool initialisation).
65
+ Then stop. Do not search for a similarly-named tool via `ToolSearch`, do not
66
+ improvise via `mcp__plugin_graph_graph__maxy-graph-read_neo4j_cypher` (it is
67
+ read-only — every write goes through the schema-aware memory tools), do not
68
+ paraphrase, do not describe the server as "warming up". The reply exists so the
69
+ operator knows the session cannot proceed without an operator-side fix (the
70
+ loud-fail contract applies equally to graph and memory tool initialisation).
62
71
 
63
72
  ## Mutation intents
64
73
 
65
74
  The graph tools above are read-only. Every write intent has a schema-aware
66
75
  tool that already exists — match the user's prose to a tool in this table
67
- rather than improvising. `mcp__admin__admin-remove` is in your initial
76
+ rather than improvising. `mcp__plugin_admin_admin__admin-remove` is in your initial
68
77
  tool list; the other tools below are deferred (they do not appear until
69
78
  you invoke `ToolSearch` by the exact name you pick from the table).
70
79
  Do not use `ToolSearch` to *discover* the tool by keyword — identify it
@@ -78,14 +87,14 @@ graph as a single action.
78
87
 
79
88
  | User intent | Tool | When to pick this, not the next row |
80
89
  |---|---|---|
81
- | "remove admin X", "take away their admin access", "revoke admin" | `mcp__admin__admin-remove` | Device-level admin offboarding — detaches the `(AdminUser)-[:ADMIN_OF]->(LocalBusiness)` edge and updates `platform/config/users.json`. Not for scrubbing personal data — use the GDPR contact-erase tools for that. |
82
- | "delete this specific node", "remove this entry by its id", "get rid of node X", "remove orphans", "prune", "clean up" | `mcp__memory__memory-delete` | Soft-deletes a single node by `elementId` (adds `:Trashed`, preserves relationships, snapshots unique keys, invisible to `memory-search` and any `notTrashed`-filtered read). For KnowledgeDocument, cascades to Sections and Chunks. Enumerate candidates first with a Cypher read, confirm with the user, then delete each by `elementId`. There is no bulk "delete everything without relationships" tool. Per the write doctrine (`.docs/neo4j.md` § Write doctrine), **new** orphans should be impossible — every writer now rejects zero-edge calls at the MCP tool-surface. Existing orphans from legacy data are legitimate backfill candidates but are not produced by any live writer; if you see a freshly-created orphan, it is a bug in a writer that bypassed `writeNodeWithEdges`, not expected state. |
83
- | "undo that delete", "I deleted X by mistake, bring it back", "restore the contact I just trashed" | `mcp__memory__memory-restore` | Removes `:Trashed`, restores snapshotted unique-key properties. Fails loudly when an active node already holds the unique slot (e.g. a fresh `LocalBusiness` was created while the old one was in trash) — the error names the conflicting `elementId` so you can ask the user how to resolve. |
84
- | "empty the trash", "purge old soft-deletes", "what's been sitting in trash and ready to clear" | `mcp__memory__memory-empty-trash` | Hard-deletes trashed nodes whose `trashedAt` is older than `graceDays` (default 30); `dryRun=true` lists candidates without mutating. Use `dryRun` first, show the list to the user, then run for real. For KnowledgeDocument candidates, the on-disk attachment directory is removed too. |
85
- | "scrub this conversation's memory", "remove this claim the bot picked up", "forget what we said in that thread" | `mcp__memory__conversation-memory-expunge` | Removes deny-listable content from `:Memory` nodes scoped to one conversation. Requires an explicit pattern and sessionId. Not for deleting individual nodes — use `memory-delete`. |
86
- | "update a property", "fix a typo in a node", "change the role field on X", "null this poisonous string" | `mcp__memory__memory-update` | Targeted property edit on an existing node, schema-validated. Use to null individual string properties matching a deny-list entry once you've identified the offender via a Cypher read. Not for creating new nodes — use `memory-write`. |
87
- | "update X by name, search didn't return it", "wire this Org but search came back empty", "I know it exists but I don't have the elementId" | `mcp__memory__memory-update-by-name` | Repair surface for the case where `memory-search` cannot return a row you know exists by name. Resolves the unique `(label, name, accountId)` tuple, then delegates to `memory-update`. Rejects loudly on zero or ambiguous matches (the rejection lists candidate elementIds so you can pick one). Trashed nodes excluded. Use this only after `memory-search` failed — for normal updates, get the elementId from search and call `memory-update` directly. |
88
- | "don't surface these substrings in future memory results", "watch for this string" | `mcp__memory__graph-prune-denylist-add` | Records a substring as operator-curated signal. Nothing auto-applies it — when a subsequent memory-search or cypher read surfaces a match, use `memory-update` or `memory-delete` with discretion to scrub it. |
90
+ | "remove admin X", "take away their admin access", "revoke admin" | `mcp__plugin_admin_admin__admin-remove` | Device-level admin offboarding — detaches the `(AdminUser)-[:ADMIN_OF]->(LocalBusiness)` edge and updates `platform/config/users.json`. Not for scrubbing personal data — use the GDPR contact-erase tools for that. |
91
+ | "delete this specific node", "remove this entry by its id", "get rid of node X", "remove orphans", "prune", "clean up" | `mcp__plugin_memory_memory__memory-delete` | Soft-deletes a single node by `elementId` (adds `:Trashed`, preserves relationships, snapshots unique keys, invisible to `memory-search` and any `notTrashed`-filtered read). For KnowledgeDocument, cascades to Sections and Chunks. Enumerate candidates first with a Cypher read, confirm with the user, then delete each by `elementId`. There is no bulk "delete everything without relationships" tool. Per the write doctrine (`.docs/neo4j.md` § Write doctrine), **new** orphans should be impossible — every writer now rejects zero-edge calls at the MCP tool-surface. Existing orphans from legacy data are legitimate backfill candidates but are not produced by any live writer; if you see a freshly-created orphan, it is a bug in a writer that bypassed `writeNodeWithEdges`, not expected state. |
92
+ | "undo that delete", "I deleted X by mistake, bring it back", "restore the contact I just trashed" | `mcp__plugin_memory_memory__memory-restore` | Removes `:Trashed`, restores snapshotted unique-key properties. Fails loudly when an active node already holds the unique slot (e.g. a fresh `LocalBusiness` was created while the old one was in trash) — the error names the conflicting `elementId` so you can ask the user how to resolve. |
93
+ | "empty the trash", "purge old soft-deletes", "what's been sitting in trash and ready to clear" | `mcp__plugin_memory_memory__memory-empty-trash` | Hard-deletes trashed nodes whose `trashedAt` is older than `graceDays` (default 30); `dryRun=true` lists candidates without mutating. Use `dryRun` first, show the list to the user, then run for real. For KnowledgeDocument candidates, the on-disk attachment directory is removed too. |
94
+ | "scrub this conversation's memory", "remove this claim the bot picked up", "forget what we said in that thread" | `mcp__plugin_memory_memory__conversation-memory-expunge` | Removes deny-listable content from `:Memory` nodes scoped to one conversation. Requires an explicit pattern and sessionId. Not for deleting individual nodes — use `memory-delete`. |
95
+ | "update a property", "fix a typo in a node", "change the role field on X", "null this poisonous string" | `mcp__plugin_memory_memory__memory-update` | Targeted property edit on an existing node, schema-validated. Use to null individual string properties matching a deny-list entry once you've identified the offender via a Cypher read. Not for creating new nodes — use `memory-write`. |
96
+ | "update X by name, search didn't return it", "wire this Org but search came back empty", "I know it exists but I don't have the elementId" | `mcp__plugin_memory_memory__memory-update-by-name` | Repair surface for the case where `memory-search` cannot return a row you know exists by name. Resolves the unique `(label, name, accountId)` tuple, then delegates to `memory-update`. Rejects loudly on zero or ambiguous matches (the rejection lists candidate elementIds so you can pick one). Trashed nodes excluded. Use this only after `memory-search` failed — for normal updates, get the elementId from search and call `memory-update` directly. |
97
+ | "don't surface these substrings in future memory results", "watch for this string" | `mcp__plugin_memory_memory__graph-prune-denylist-add` | Records a substring as operator-curated signal. Nothing auto-applies it — when a subsequent memory-search or cypher read surfaces a match, use `memory-update` or `memory-delete` with discretion to scrub it. |
89
98
 
90
99
  **Never run `cypher-shell` via `Bash` to work around the read-only tool.**
91
100
  The admin session's `Bash` surface has no path to the per-brand Neo4j
@@ -107,7 +116,7 @@ MATCH (a:AdminUser) WHERE NOT (a)--() RETURN elementId(a), a.userId, a.createdAt
107
116
  ```
108
117
 
109
118
  Show the list to the user. For each confirmed entry, call
110
- `mcp__memory__memory-delete` with the `elementId`. Do not batch-delete
119
+ `mcp__plugin_memory_memory__memory-delete` with the `elementId`. Do not batch-delete
111
120
  by pattern — on 2026-04-20 an autonomous orphan-delete rule wiped 19
112
121
  nodes in one call, with no property recovery possible. Structural mass-
113
122
  deletion by shape is banned; discretion per node is the only sanctioned
@@ -18,7 +18,7 @@ The admin agent delegates to `specialists:database-operator` when the operator d
18
18
 
19
19
  ## How parsing happens
20
20
 
21
- The skill does not parse markdown. Vault traversal, frontmatter YAML, wikilink regex, tag scanning, daily-notes detection, and attachment resolution all happen server-side inside `mcp__memory__obsidian-vault-import` (memory plugin). The skill is the operator-facing conversation surface only: confirm owner → select filters → invoke the tool with `mode='dry-run'` → present ambiguities → invoke with `mode='commit'` and the operator's resolutions.
21
+ The skill does not parse markdown. Vault traversal, frontmatter YAML, wikilink regex, tag scanning, daily-notes detection, and attachment resolution all happen server-side inside `mcp__plugin_memory_memory__obsidian-vault-import` (memory plugin). The skill is the operator-facing conversation surface only: confirm owner → select filters → invoke the tool with `mode='dry-run'` → present ambiguities → invoke with `mode='commit'` and the operator's resolutions.
22
22
 
23
23
  ## Intra-plugin growth
24
24
 
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ // Strong build gate for canonical maxy MCP tool names (Task 502).
3
+ //
4
+ // Two checks, both must pass:
5
+ // 1. Regenerate-and-diff: the committed generated artifact
6
+ // (canonical-tool-names.generated.ts) must byte-match a fresh generation
7
+ // from platform/plugins/*/PLUGIN.md. A stale artifact fails the build —
8
+ // this is what stops a PLUGIN.md tool add/remove from drifting away from
9
+ // the names instructions are bound to.
10
+ // 2. Instruction-name validation: every `mcp__...` tool name in an
11
+ // instruction file (specialist templates, agent prompts, plugin
12
+ // references/skills, PLUGIN.md prose) that targets a MAXY plugin must be
13
+ // the canonical long prefix AND must exist in the live set. A short-prefix
14
+ // maxy name (mcp__<plugin>__<tool>) fails — it must be long. An unknown
15
+ // long-prefix maxy name fails — typo or stale. Non-maxy bridge namespaces
16
+ // (playwright, etc.) are ignored: their tool names are upstream-owned.
17
+
18
+ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
19
+ import { join } from 'node:path'
20
+ import { computeCanonicalToolNames, MAXY_CODE_ROOT } from './lib/canonical-tool-names.mjs'
21
+ import { renderArtifact, renderPluginsList } from './generate-canonical-tool-names.mjs'
22
+
23
+ const GENERATED = join(
24
+ MAXY_CODE_ROOT, 'platform', 'services', 'claude-session-manager', 'src',
25
+ 'canonical-tool-names.generated.ts',
26
+ )
27
+ const PLUGINS_TXT = join(
28
+ MAXY_CODE_ROOT, 'platform', 'plugins', 'admin', 'hooks', 'lib',
29
+ 'maxy-mcp-plugins.txt',
30
+ )
31
+
32
+ const SCAN_DIRS = [
33
+ join(MAXY_CODE_ROOT, 'platform', 'templates', 'specialists', 'agents'),
34
+ join(MAXY_CODE_ROOT, 'platform', 'plugins'),
35
+ ]
36
+ const SCAN_EXT = new Set(['.md'])
37
+ const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'payload', '__tests__'])
38
+ // mcp__<seg>__<tool> — seg may itself contain `_` (long prefix plugin_a_b).
39
+ const TOOL_RE = /mcp__([A-Za-z0-9-]+(?:_[A-Za-z0-9-]+)*)__([A-Za-z0-9_-]+)/g
40
+
41
+ function* walk(dir) {
42
+ let entries
43
+ try { entries = readdirSync(dir) } catch { return }
44
+ for (const e of entries.sort()) {
45
+ if (e.startsWith('.') || SKIP_DIRS.has(e)) continue
46
+ const p = join(dir, e)
47
+ let st
48
+ try { st = statSync(p) } catch { continue }
49
+ if (st.isDirectory()) { yield* walk(p); continue }
50
+ const ext = e.includes('.') ? '.' + e.split('.').pop() : ''
51
+ if (SCAN_EXT.has(ext)) yield p
52
+ }
53
+ }
54
+
55
+ function main() {
56
+ const { plugins, names } = computeCanonicalToolNames()
57
+ const maxyPlugins = new Set(plugins)
58
+ const canonical = new Set(names)
59
+
60
+ // Check 1 — regenerate-and-diff both generated outputs.
61
+ let driftError = null
62
+ const staleOutputs = []
63
+ if ((existsSync(GENERATED) ? readFileSync(GENERATED, 'utf8') : '') !== renderArtifact()) staleOutputs.push(GENERATED)
64
+ if ((existsSync(PLUGINS_TXT) ? readFileSync(PLUGINS_TXT, 'utf8') : '') !== renderPluginsList()) staleOutputs.push(PLUGINS_TXT)
65
+ if (staleOutputs.length > 0) {
66
+ driftError =
67
+ `[gate] canonical-tool-names: GENERATED ARTIFACT STALE.\n` +
68
+ staleOutputs.map((p) => ` ${p}`).join('\n') + '\n' +
69
+ ` Regenerate: cd platform && npm run gen:canonical-tools`
70
+ }
71
+
72
+ // Check 2 — instruction-name validation.
73
+ const violations = []
74
+ for (const root of SCAN_DIRS) {
75
+ for (const file of walk(root)) {
76
+ const text = readFileSync(file, 'utf8')
77
+ for (const m of text.matchAll(TOOL_RE)) {
78
+ const seg = m[1]
79
+ const full = m[0]
80
+ // Skip glob patterns in prose (`mcp__plugin_x_x__*`): the `*` is not a
81
+ // tool char so the match captured a partial name, not a real reference.
82
+ if (text[m.index + full.length] === '*') continue
83
+ if (seg.startsWith('plugin_')) {
84
+ // Long prefix mcp__plugin_<plugin>_<server>__<tool>.
85
+ const inner = seg.slice('plugin_'.length)
86
+ // plugin === server for maxy; the long prefix is `<plugin>_<plugin>`.
87
+ const plugin = [...maxyPlugins].find((p) => inner === `${p}_${p}`)
88
+ if (!plugin) continue // non-maxy bridge (playwright) — upstream-owned.
89
+ if (!canonical.has(full)) {
90
+ violations.push({ file, name: full, why: 'unknown maxy tool (typo or stale)' })
91
+ }
92
+ } else if (maxyPlugins.has(seg)) {
93
+ // Short prefix mcp__<maxyplugin>__<tool> — must be long now.
94
+ violations.push({ file, name: full, why: `short prefix for maxy plugin '${seg}' — use canonical long name` })
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ const failed = driftError || violations.length > 0
101
+ if (driftError) console.error(driftError)
102
+ if (violations.length > 0) {
103
+ console.error('[gate] canonical-tool-names: instruction files name tools outside the live set:')
104
+ for (const v of violations) console.error(` ${v.file.replace(MAXY_CODE_ROOT + '/', '')}: ${v.name} — ${v.why}`)
105
+ }
106
+ if (failed) process.exit(1)
107
+ console.log(`[gate] check-canonical-tool-names: PASS (${names.length} canonical names, scanned instruction files)`)
108
+ }
109
+
110
+ main()
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ // Generator: writes the committed canonical maxy MCP tool-name artifact from
3
+ // every platform/plugins/<dir>/PLUGIN.md. Run via `npm run gen:canonical-tools`
4
+ // (platform). The strong build gate (check-canonical-tool-names.mjs) regenerates
5
+ // in-memory and fails the build if the committed artifact has drifted.
6
+ import { writeFileSync } from 'node:fs'
7
+ import { join } from 'node:path'
8
+ import { computeCanonicalToolNames, MAXY_CODE_ROOT } from './lib/canonical-tool-names.mjs'
9
+
10
+ const OUT = join(
11
+ MAXY_CODE_ROOT, 'platform', 'services', 'claude-session-manager', 'src',
12
+ 'canonical-tool-names.generated.ts',
13
+ )
14
+
15
+ // Plain newline-delimited maxy plugin list the deterministic missing-tool hook
16
+ // (bash) reads to tell a maxy server (mcp__plugin_<p>_<p>__) from a third-party
17
+ // bridge that shares the same long-prefix shape (Playwright etc.).
18
+ const PLUGINS_TXT = join(
19
+ MAXY_CODE_ROOT, 'platform', 'plugins', 'admin', 'hooks', 'lib',
20
+ 'maxy-mcp-plugins.txt',
21
+ )
22
+
23
+ export function renderPluginsList() {
24
+ const { plugins } = computeCanonicalToolNames()
25
+ return plugins.join('\n') + '\n'
26
+ }
27
+
28
+ export function renderArtifact() {
29
+ const { plugins, names } = computeCanonicalToolNames()
30
+ const body = [
31
+ '// GENERATED — do not edit by hand.',
32
+ '// Source: platform/plugins/*/PLUGIN.md (tools: + hidden: blocks).',
33
+ '// Regenerate: cd platform && npm run gen:canonical-tools',
34
+ '// Gate: platform/scripts/check-canonical-tool-names.mjs fails the build on drift.',
35
+ '//',
36
+ '// Canonical long-prefix names every maxy MCP tool is served under by the',
37
+ '// installed-plugin channel: mcp__plugin_<dir>_<dir>__<tool>.',
38
+ '',
39
+ '/** Directory basenames of maxy platform plugins that expose a tool surface. */',
40
+ 'export const MAXY_MCP_PLUGINS: readonly string[] = [',
41
+ ...plugins.map((p) => ` ${JSON.stringify(p)},`),
42
+ ']',
43
+ '',
44
+ '/** Every canonical maxy MCP tool name (long prefix), sorted. */',
45
+ 'export const CANONICAL_MAXY_TOOL_NAMES: readonly string[] = [',
46
+ ...names.map((n) => ` ${JSON.stringify(n)},`),
47
+ ']',
48
+ '',
49
+ ].join('\n')
50
+ return body
51
+ }
52
+
53
+ function main() {
54
+ writeFileSync(OUT, renderArtifact(), 'utf8')
55
+ writeFileSync(PLUGINS_TXT, renderPluginsList(), 'utf8')
56
+ const { plugins, names } = computeCanonicalToolNames()
57
+ console.log(`[gen] canonical-tool-names: ${plugins.length} plugins, ${names.length} tools -> ${OUT}`)
58
+ console.log(`[gen] maxy-mcp-plugins.txt -> ${PLUGINS_TXT}`)
59
+ }
60
+
61
+ export { OUT, PLUGINS_TXT }
62
+
63
+ if (import.meta.url === `file://${process.argv[1]}`) main()
@@ -43,14 +43,15 @@ const FORBIDDEN_HOOK_NAMES = [
43
43
  ];
44
44
 
45
45
  function stripVoiceSection(content) {
46
+ // Scope past the "## How you sound" section through end of file. Everything
47
+ // from that heading onward is voice/output/access prose that legitimately
48
+ // names forbidden classes and skill-load invocations (the plain-English and
49
+ // operator-voice passes) without being instances of them; only the
50
+ // behavioural prose above it is scanned.
46
51
  const lines = content.split("\n");
47
52
  const start = lines.findIndex((l) => l.trim() === "## How you sound");
48
53
  if (start === -1) return content;
49
- let end = lines.length;
50
- for (let i = start + 1; i < lines.length; i++) {
51
- if (lines[i].startsWith("## ")) { end = i; break; }
52
- }
53
- return [...lines.slice(0, start), ...lines.slice(end)].join("\n");
54
+ return lines.slice(0, start).join("\n");
54
55
  }
55
56
 
56
57
  export function runIdentityForbiddenTokenCheck(identityContent) {
@@ -0,0 +1,88 @@
1
+ // Shared source of truth for canonical (long-prefix) maxy MCP tool names.
2
+ //
3
+ // Claude Code's installed-plugin channel serves every plugin MCP tool under
4
+ // the long prefix `mcp__plugin_<plugin>_<server>__<tool>`. For every platform
5
+ // plugin the marketplace name, the MCP server key, and the directory basename
6
+ // are identical, so the canonical name is deterministically
7
+ // `mcp__plugin_<dir>_<dir>__<tool>`. This module is the single place that
8
+ // derives those names from each `platform/plugins/<dir>/PLUGIN.md`; the
9
+ // generator writes the committed artifact from it, the build gate diffs
10
+ // against it, and the session-manager cross-checks its tool surface against it.
11
+ //
12
+ // Scope: platform plugins only. Third-party bridge servers (playwright, etc.)
13
+ // also render under `mcp__plugin_*` but their tool names are upstream-owned and
14
+ // are NOT part of the maxy canonical set — the drift check treats a
15
+ // `mcp__plugin_*` name whose plugin segment is not a maxy plugin as a bridge
16
+ // pass, never validating it against this set.
17
+
18
+ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
19
+ import { dirname, join, resolve } from 'node:path'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ const __dirname = dirname(fileURLToPath(import.meta.url))
23
+ export const MAXY_CODE_ROOT = resolve(__dirname, '..', '..', '..')
24
+ const PLATFORM_PLUGINS_ROOT = join(MAXY_CODE_ROOT, 'platform', 'plugins')
25
+
26
+ /** Long-prefix canonical name for a platform plugin tool. plugin === server
27
+ * === directory basename for every platform MCP plugin. */
28
+ export function canonicalToolName(plugin, tool) {
29
+ return `mcp__plugin_${plugin}_${plugin}__${tool}`
30
+ }
31
+
32
+ function extractFrontmatter(md) {
33
+ if (!md.startsWith('---')) return null
34
+ const end = md.indexOf('\n---', 3)
35
+ if (end < 0) return null
36
+ return md.slice(3, end)
37
+ }
38
+
39
+ // Collect `- name: <x>` entries under a bare `tools:` block and `- <x>` entries
40
+ // under a bare `hidden:` block. Both contribute names a specialist may list in
41
+ // its `tools:` frontmatter, so both belong to the live set.
42
+ function parseToolNames(frontmatter) {
43
+ const lines = frontmatter.split('\n')
44
+ const names = []
45
+ let mode = null // 'tools' | 'hidden' | null
46
+ for (const line of lines) {
47
+ if (/^tools\s*:/.test(line)) {
48
+ mode = /^tools\s*:\s*$/.test(line) ? 'tools' : null
49
+ continue
50
+ }
51
+ if (/^hidden\s*:/.test(line)) {
52
+ mode = /^hidden\s*:\s*$/.test(line) ? 'hidden' : null
53
+ continue
54
+ }
55
+ if (/^\S/.test(line) && line.trim() !== '') { mode = null; continue }
56
+ if (mode === 'tools') {
57
+ const m = line.match(/^\s+-\s+name:\s*(.+?)\s*$/)
58
+ if (m) names.push(m[1])
59
+ } else if (mode === 'hidden') {
60
+ const m = line.match(/^\s+-\s+(.+?)\s*$/)
61
+ if (m) names.push(m[1])
62
+ }
63
+ }
64
+ return names
65
+ }
66
+
67
+ /** Returns { plugins, names } — `plugins` is the sorted list of maxy plugin
68
+ * directory basenames that declare a tool surface; `names` is the sorted list
69
+ * of canonical long-prefix tool names across all of them. */
70
+ export function computeCanonicalToolNames(pluginsRoot = PLATFORM_PLUGINS_ROOT) {
71
+ const plugins = []
72
+ const names = new Set()
73
+ if (!existsSync(pluginsRoot)) return { plugins, names: [] }
74
+ for (const entry of readdirSync(pluginsRoot).sort()) {
75
+ if (entry.startsWith('.')) continue
76
+ const dir = join(pluginsRoot, entry)
77
+ try { if (!statSync(dir).isDirectory()) continue } catch { continue }
78
+ const manifest = join(dir, 'PLUGIN.md')
79
+ if (!existsSync(manifest)) continue
80
+ const fm = extractFrontmatter(readFileSync(manifest, 'utf-8'))
81
+ if (fm === null) continue
82
+ const toolNames = parseToolNames(fm)
83
+ if (toolNames.length === 0) continue
84
+ plugins.push(entry)
85
+ for (const t of toolNames) names.add(canonicalToolName(entry, t))
86
+ }
87
+ return { plugins: plugins.sort(), names: Array.from(names).sort() }
88
+ }
@@ -127,6 +127,12 @@ cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
127
127
  { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
128
128
  ]
129
129
  },
130
+ {
131
+ "matcher": "mcp__.*",
132
+ "hooks": [
133
+ { "type": "command", "command": "bash $HOOKS_PATH/mcp-tool-missing.sh" }
134
+ ]
135
+ },
130
136
  {
131
137
  "matcher": "Write",
132
138
  "hooks": [
@@ -202,7 +208,12 @@ done
202
208
  rm -f "$ACCOUNT_DIR/.claude/agents/"*.md 2>/dev/null || true
203
209
 
204
210
  # SOUL.md — user-controlled personalisation. Only create if missing. Never overwrite.
205
- [ -f "$ACCOUNT_DIR/agents/admin/SOUL.md" ] || cp "$TEMPLATES_DIR/agents/admin/SOUL.md" "$ACCOUNT_DIR/agents/admin/SOUL.md"
211
+ if [ -f "$ACCOUNT_DIR/agents/admin/SOUL.md" ]; then
212
+ echo " agents/admin/SOUL.md preserved ($(wc -c < "$ACCOUNT_DIR/agents/admin/SOUL.md" | tr -d ' ') bytes)"
213
+ else
214
+ cp "$TEMPLATES_DIR/agents/admin/SOUL.md" "$ACCOUNT_DIR/agents/admin/SOUL.md"
215
+ echo " agents/admin/SOUL.md created ($(wc -c < "$ACCOUNT_DIR/agents/admin/SOUL.md" | tr -d ' ') bytes)"
216
+ fi
206
217
 
207
218
  # Public agent SOUL.md + config.json — create if missing, never overwrite.
208
219
  # IDENTITY.md is handled in the Rubytech-controlled block above.
@@ -92,6 +92,7 @@ fixture_claude_session_manager() {
92
92
  cat > "$platform_root/config/brand.json" <<'BRAND'
93
93
  {
94
94
  "hostname": "smoke",
95
+ "productName": "Smoke",
95
96
  "domain": null
96
97
  }
97
98
  BRAND
@@ -0,0 +1,7 @@
1
+ /** Long-prefix name served by the installed-plugin channel (admin + specialist
2
+ * spawns). plugin === server === plugin directory basename. */
3
+ export declare function canonicalToolName(plugin: string, tool: string): string;
4
+ /** Short-prefix name served by the per-spawn `--mcp-config` channel, retained
5
+ * only for public-agent spawns. */
6
+ export declare function qualifyShortToolName(plugin: string, tool: string): string;
7
+ //# sourceMappingURL=canonical-tool-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-tool-names.d.ts","sourceRoot":"","sources":["../src/canonical-tool-names.ts"],"names":[],"mappings":"AAaA;gEACgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;oCACoC;AACpC,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzE"}
@@ -0,0 +1,5 @@
1
+ /** Directory basenames of maxy platform plugins that expose a tool surface. */
2
+ export declare const MAXY_MCP_PLUGINS: readonly string[];
3
+ /** Every canonical maxy MCP tool name (long prefix), sorted. */
4
+ export declare const CANONICAL_MAXY_TOOL_NAMES: readonly string[];
5
+ //# sourceMappingURL=canonical-tool-names.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-tool-names.generated.d.ts","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"AAQA,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAiB7C,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAgLtD,CAAA"}