claude-mpm 5.4.21__py3-none-any.whl → 5.4.59__py3-none-any.whl

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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (176) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT.md +164 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  4. claude_mpm/agents/MEMORY.md +1 -1
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +771 -1019
  6. claude_mpm/agents/WORKFLOW.md +5 -254
  7. claude_mpm/agents/agent_loader.py +1 -1
  8. claude_mpm/agents/base_agent.json +31 -0
  9. claude_mpm/agents/frontmatter_validator.py +2 -2
  10. claude_mpm/cli/commands/agent_state_manager.py +10 -10
  11. claude_mpm/cli/commands/agents.py +9 -9
  12. claude_mpm/cli/commands/auto_configure.py +4 -4
  13. claude_mpm/cli/commands/configure.py +1 -1
  14. claude_mpm/cli/commands/configure_agent_display.py +12 -0
  15. claude_mpm/cli/commands/mpm_init/core.py +72 -0
  16. claude_mpm/cli/commands/postmortem.py +1 -1
  17. claude_mpm/cli/commands/profile.py +276 -0
  18. claude_mpm/cli/commands/skills.py +14 -18
  19. claude_mpm/cli/executor.py +10 -0
  20. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  21. claude_mpm/cli/parsers/base_parser.py +7 -0
  22. claude_mpm/cli/parsers/profile_parser.py +147 -0
  23. claude_mpm/cli/parsers/skills_parser.py +0 -6
  24. claude_mpm/cli/startup.py +506 -180
  25. claude_mpm/commands/mpm-config.md +13 -250
  26. claude_mpm/commands/mpm-doctor.md +9 -22
  27. claude_mpm/commands/mpm-help.md +5 -206
  28. claude_mpm/commands/mpm-init.md +81 -507
  29. claude_mpm/commands/mpm-monitor.md +15 -402
  30. claude_mpm/commands/mpm-organize.md +61 -441
  31. claude_mpm/commands/mpm-postmortem.md +6 -108
  32. claude_mpm/commands/mpm-session-resume.md +12 -363
  33. claude_mpm/commands/mpm-status.md +5 -69
  34. claude_mpm/commands/mpm-ticket-view.md +52 -495
  35. claude_mpm/commands/mpm-version.md +5 -107
  36. claude_mpm/core/config.py +2 -4
  37. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  38. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  39. claude_mpm/core/optimized_startup.py +61 -0
  40. claude_mpm/core/shared/config_loader.py +3 -1
  41. claude_mpm/core/unified_agent_registry.py +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  89. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  90. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  91. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  92. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  93. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  94. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
  95. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  96. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  97. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  98. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  99. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  100. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  101. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  102. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  103. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  104. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
  105. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  106. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  107. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  108. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  109. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  110. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  111. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  112. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  113. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  114. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  115. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  116. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  117. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  118. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  119. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  120. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  121. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  122. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  123. claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
  124. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  125. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  126. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  127. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  128. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  129. claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
  130. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  131. claude_mpm/init.py +276 -0
  132. claude_mpm/models/git_repository.py +3 -3
  133. claude_mpm/scripts/start_activity_logging.py +0 -0
  134. claude_mpm/services/agents/agent_builder.py +3 -3
  135. claude_mpm/services/agents/cache_git_manager.py +6 -6
  136. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  137. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -2
  138. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  139. claude_mpm/services/agents/deployment/agent_template_builder.py +31 -19
  140. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  141. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  142. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  143. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
  144. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
  145. claude_mpm/services/agents/git_source_manager.py +23 -4
  146. claude_mpm/services/agents/recommender.py +5 -3
  147. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  148. claude_mpm/services/agents/sources/git_source_sync_service.py +121 -10
  149. claude_mpm/services/agents/startup_sync.py +22 -2
  150. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  151. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  152. claude_mpm/services/git/git_operations_service.py +8 -8
  153. claude_mpm/services/monitor/management/lifecycle.py +7 -1
  154. claude_mpm/services/monitor/server.py +473 -3
  155. claude_mpm/services/pm_skills_deployer.py +711 -0
  156. claude_mpm/services/profile_manager.py +337 -0
  157. claude_mpm/services/skills/git_skill_source_manager.py +148 -11
  158. claude_mpm/services/skills/selective_skill_deployer.py +97 -48
  159. claude_mpm/services/skills_deployer.py +161 -65
  160. claude_mpm/services/socketio/dashboard_server.py +1 -0
  161. claude_mpm/services/socketio/event_normalizer.py +37 -6
  162. claude_mpm/services/socketio/server/core.py +262 -123
  163. claude_mpm/skills/bundled/security-scanning.md +112 -0
  164. claude_mpm/skills/skill_manager.py +98 -3
  165. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  166. claude_mpm/utils/agent_dependency_loader.py +14 -2
  167. claude_mpm/utils/agent_filters.py +1 -1
  168. claude_mpm/utils/migration.py +4 -4
  169. claude_mpm/utils/robust_installer.py +47 -3
  170. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/METADATA +7 -4
  171. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/RECORD +175 -81
  172. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/WHEEL +0 -0
  173. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/entry_points.txt +0 -0
  174. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE +0 -0
  175. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  176. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from fontTools.ttLib import TTFont
4
+ import sys
5
+ import json
6
+
7
+ # map of characters to extract
8
+ metrics_to_extract = {
9
+ # Font name
10
+ "AMS-Regular": {
11
+ u"\u21e2": None, # \dashrightarrow
12
+ u"\u21e0": None, # \dashleftarrow
13
+ },
14
+ "Main-Regular": {
15
+ # Skew and italic metrics can't be easily parsed from the TTF. Instead,
16
+ # we map each character to a "base character", which is a character
17
+ # from the same font with correct italic and skew metrics. A character
18
+ # maps to None if it doesn't have a base.
19
+
20
+ #u"\u2209": None, # \notin
21
+ #u"\u2260": None, # \neq
22
+ u"\u2245": None, # \cong
23
+ u"\u2026": None, # \ldots
24
+ u"\u22ef": None, # \cdots
25
+ u"\u22f1": None, # \ddots
26
+ u"\u22ee": None, # \vdots
27
+ u"\u22ee": None, # \vdots
28
+ u"\u22a8": None, # \models
29
+ u"\u22c8": None, # \bowtie
30
+ u"\u2250": None, # \doteq
31
+ u"\u23b0": None, # \lmoustache
32
+ u"\u23b1": None, # \rmoustache
33
+ u"\u27ee": None, # \lgroup
34
+ u"\u27ef": None, # \rgroup
35
+ u"\u27f5": None, # \longleftarrow
36
+ u"\u27f8": None, # \Longleftarrow
37
+ u"\u27f6": None, # \longrightarrow
38
+ u"\u27f9": None, # \Longrightarrow
39
+ u"\u27f7": None, # \longleftrightarrow
40
+ u"\u27fa": None, # \Longleftrightarrow
41
+ u"\u21a6": None, # \mapsto
42
+ u"\u27fc": None, # \longmapsto
43
+ u"\u21a9": None, # \hookleftarrow
44
+ u"\u21aa": None, # \hookrightarrow
45
+ u"\u21cc": None, # \rightleftharpoons
46
+ },
47
+ "Main-Bold": {
48
+ u"\u2245": None, # \cong
49
+ },
50
+ "Size1-Regular": {
51
+ u"\u222c": u"\u222b", # \iint, based on \int
52
+ u"\u222d": u"\u222b", # \iiint, based on \int
53
+ },
54
+ "Size2-Regular": {
55
+ u"\u222c": u"\u222b", # \iint, based on \int
56
+ u"\u222d": u"\u222b", # \iiint, based on \int
57
+ },
58
+ }
59
+
60
+
61
+ def main():
62
+ start_json = json.load(sys.stdin)
63
+
64
+ for font in start_json:
65
+ fontInfo = TTFont("../../fonts/KaTeX_" + font + ".ttf")
66
+ glyf = fontInfo["glyf"]
67
+ widths = fontInfo.getGlyphSet()
68
+ unitsPerEm = float(fontInfo["head"].unitsPerEm)
69
+
70
+ # We keep ALL Unicode cmaps, not just fontInfo["cmap"].getcmap(3, 1).
71
+ # This is playing it extra safe, since it reports inconsistencies.
72
+ # Platform 0 is Unicode, platform 3 is Windows. For platform 3,
73
+ # encoding 1 is UCS-2 and encoding 10 is UCS-4.
74
+ cmap = [t.cmap for t in fontInfo["cmap"].tables
75
+ if (t.platformID == 0)
76
+ or (t.platformID == 3 and t.platEncID in (1, 10))]
77
+
78
+ chars = metrics_to_extract.get(font, {})
79
+ chars[u"\u0020"] = None # space
80
+ chars[u"\u00a0"] = None # nbsp
81
+
82
+ for char, base_char in chars.items():
83
+ code = ord(char)
84
+ names = set(t.get(code) for t in cmap)
85
+ if not names:
86
+ sys.stderr.write(
87
+ "Codepoint {} of font {} maps to no name\n"
88
+ .format(code, font))
89
+ continue
90
+ if len(names) != 1:
91
+ sys.stderr.write(
92
+ "Codepoint {} of font {} maps to multiple names: {}\n"
93
+ .format(code, font, ", ".join(sorted(names))))
94
+ continue
95
+ name = names.pop()
96
+
97
+ height = depth = italic = skew = width = 0
98
+ glyph = glyf[name]
99
+ if glyph.numberOfContours:
100
+ height = glyph.yMax / unitsPerEm
101
+ depth = -glyph.yMin / unitsPerEm
102
+ width = widths[name].width / unitsPerEm
103
+ if base_char:
104
+ base_char_str = str(ord(base_char))
105
+ base_metrics = start_json[font][base_char_str]
106
+ italic = base_metrics["italic"]
107
+ skew = base_metrics["skew"]
108
+ width = base_metrics["width"]
109
+
110
+ start_json[font][str(code)] = {
111
+ "height": height,
112
+ "depth": depth,
113
+ "italic": italic,
114
+ "skew": skew,
115
+ "width": width
116
+ }
117
+
118
+ sys.stdout.write(
119
+ json.dumps(start_json, separators=(',', ':'), sort_keys=True))
120
+
121
+ if __name__ == "__main__":
122
+ main()
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import json
5
+
6
+ props = ['depth', 'height', 'italic', 'skew']
7
+
8
+ if len(sys.argv) > 1:
9
+ if sys.argv[1] == '--width':
10
+ props.append('width')
11
+
12
+ data = json.load(sys.stdin)
13
+ sys.stdout.write(
14
+ "// This file is GENERATED by buildMetrics.sh. DO NOT MODIFY.\n")
15
+ sep = "export default {\n "
16
+ for font in sorted(data):
17
+ sys.stdout.write(sep + json.dumps(font))
18
+ sep = ": {\n "
19
+ for glyph in sorted(data[font], key=int):
20
+ sys.stdout.write(sep + json.dumps(glyph) + ": ")
21
+
22
+ values = [value if value != 0.0 else 0 for value in
23
+ [data[font][glyph][key] for key in props]]
24
+
25
+ sys.stdout.write(json.dumps(values))
26
+ sep = ",\n "
27
+ sep = ",\n },\n "
28
+ sys.stdout.write(",\n },\n};\n")
@@ -0,0 +1,211 @@
1
+ class CharInfoWord(object):
2
+ def __init__(self, word):
3
+ b1, b2, b3, b4 = (word >> 24,
4
+ (word & 0xff0000) >> 16,
5
+ (word & 0xff00) >> 8,
6
+ word & 0xff)
7
+
8
+ self.width_index = b1
9
+ self.height_index = b2 >> 4
10
+ self.depth_index = b2 & 0x0f
11
+ self.italic_index = (b3 & 0b11111100) >> 2
12
+ self.tag = b3 & 0b11
13
+ self.remainder = b4
14
+
15
+ def has_ligkern(self):
16
+ return self.tag == 1
17
+
18
+ def ligkern_start(self):
19
+ return self.remainder
20
+
21
+
22
+ class LigKernProgram(object):
23
+ def __init__(self, program):
24
+ self.program = program
25
+
26
+ def execute(self, start, next_char):
27
+ curr_instruction = start
28
+ while True:
29
+ instruction = self.program[curr_instruction]
30
+ (skip, inst_next_char, op, remainder) = instruction
31
+
32
+ if inst_next_char == next_char:
33
+ if op < 128:
34
+ # Don't worry about ligatures for now, we only need kerns
35
+ return None
36
+ else:
37
+ return 256 * (op - 128) + remainder
38
+ elif skip >= 128:
39
+ return None
40
+ else:
41
+ curr_instruction += 1 + skip
42
+
43
+
44
+ class TfmCharMetrics(object):
45
+ def __init__(self, width, height, depth, italic, kern_table):
46
+ self.width = width
47
+ self.height = height
48
+ self.depth = depth
49
+ self.italic_correction = italic
50
+ self.kern_table = kern_table
51
+
52
+
53
+ class TfmFile(object):
54
+ def __init__(self, start_char, end_char, char_info, width_table,
55
+ height_table, depth_table, italic_table, ligkern_table,
56
+ kern_table):
57
+ self.start_char = start_char
58
+ self.end_char = end_char
59
+ self.char_info = char_info
60
+ self.width_table = width_table
61
+ self.height_table = height_table
62
+ self.depth_table = depth_table
63
+ self.italic_table = italic_table
64
+ self.ligkern_program = LigKernProgram(ligkern_table)
65
+ self.kern_table = kern_table
66
+
67
+ def get_char_metrics(self, char_num, fix_rsfs=False):
68
+ """Return glyph metrics for a unicode code point.
69
+
70
+ Arguments:
71
+ char_num: a unicode code point
72
+ fix_rsfs: adjust for rsfs10.tfm's different indexing system
73
+ """
74
+ if char_num < self.start_char or char_num > self.end_char:
75
+ raise RuntimeError("Invalid character number")
76
+
77
+ if fix_rsfs:
78
+ # all of the char_nums contained start from zero in rsfs10.tfm
79
+ info = self.char_info[char_num - self.start_char]
80
+ else:
81
+ info = self.char_info[char_num + self.start_char]
82
+
83
+ char_kern_table = {}
84
+ if info.has_ligkern():
85
+ for char in range(self.start_char, self.end_char + 1):
86
+ kern = self.ligkern_program.execute(info.ligkern_start(), char)
87
+ if kern:
88
+ char_kern_table[char] = self.kern_table[kern]
89
+
90
+ return TfmCharMetrics(
91
+ self.width_table[info.width_index],
92
+ self.height_table[info.height_index],
93
+ self.depth_table[info.depth_index],
94
+ self.italic_table[info.italic_index],
95
+ char_kern_table)
96
+
97
+
98
+ class TfmReader(object):
99
+ def __init__(self, f):
100
+ self.f = f
101
+
102
+ def read_byte(self):
103
+ return ord(self.f.read(1))
104
+
105
+ def read_halfword(self):
106
+ b1 = self.read_byte()
107
+ b2 = self.read_byte()
108
+ return (b1 << 8) | b2
109
+
110
+ def read_word(self):
111
+ b1 = self.read_byte()
112
+ b2 = self.read_byte()
113
+ b3 = self.read_byte()
114
+ b4 = self.read_byte()
115
+ return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
116
+
117
+ def read_fixword(self):
118
+ word = self.read_word()
119
+
120
+ neg = False
121
+ if word & 0x80000000:
122
+ neg = True
123
+ word = (-word & 0xffffffff)
124
+
125
+ return (-1 if neg else 1) * word / float(1 << 20)
126
+
127
+ def read_bcpl(self, length):
128
+ str_length = self.read_byte()
129
+ data = self.f.read(length - 1)
130
+ return data[:str_length]
131
+
132
+
133
+ def read_tfm_file(file_name):
134
+ with open(file_name, 'rb') as f:
135
+ reader = TfmReader(f)
136
+
137
+ # file_size
138
+ reader.read_halfword()
139
+ header_size = reader.read_halfword()
140
+
141
+ start_char = reader.read_halfword()
142
+ end_char = reader.read_halfword()
143
+
144
+ width_table_size = reader.read_halfword()
145
+ height_table_size = reader.read_halfword()
146
+ depth_table_size = reader.read_halfword()
147
+ italic_table_size = reader.read_halfword()
148
+
149
+ ligkern_table_size = reader.read_halfword()
150
+ kern_table_size = reader.read_halfword()
151
+
152
+ # extensible_table_size
153
+ reader.read_halfword()
154
+ # parameter_table_size
155
+ reader.read_halfword()
156
+
157
+ # checksum
158
+ reader.read_word()
159
+ # design_size
160
+ reader.read_fixword()
161
+
162
+ if header_size > 2:
163
+ # coding_scheme
164
+ reader.read_bcpl(40)
165
+
166
+ if header_size > 12:
167
+ # font_family
168
+ reader.read_bcpl(20)
169
+
170
+ for i in range(header_size - 17):
171
+ reader.read_word()
172
+
173
+ char_info = []
174
+ for i in range(start_char, end_char + 1):
175
+ char_info.append(CharInfoWord(reader.read_word()))
176
+
177
+ width_table = []
178
+ for i in range(width_table_size):
179
+ width_table.append(reader.read_fixword())
180
+
181
+ height_table = []
182
+ for i in range(height_table_size):
183
+ height_table.append(reader.read_fixword())
184
+
185
+ depth_table = []
186
+ for i in range(depth_table_size):
187
+ depth_table.append(reader.read_fixword())
188
+
189
+ italic_table = []
190
+ for i in range(italic_table_size):
191
+ italic_table.append(reader.read_fixword())
192
+
193
+ ligkern_table = []
194
+ for i in range(ligkern_table_size):
195
+ skip = reader.read_byte()
196
+ next_char = reader.read_byte()
197
+ op = reader.read_byte()
198
+ remainder = reader.read_byte()
199
+
200
+ ligkern_table.append((skip, next_char, op, remainder))
201
+
202
+ kern_table = []
203
+ for i in range(kern_table_size):
204
+ kern_table.append(reader.read_fixword())
205
+
206
+ # There is more information, like the ligkern, kern, extensible, and
207
+ # param table, but we don't need these for now
208
+
209
+ return TfmFile(start_char, end_char, char_info, width_table,
210
+ height_table, depth_table, italic_table,
211
+ ligkern_table, kern_table)
@@ -394,6 +394,8 @@ class ClaudeHookHandler:
394
394
  Returns:
395
395
  Modified input for PreToolUse events (v2.0.30+), None otherwise
396
396
  """
397
+ import time
398
+
397
399
  # Try multiple field names for compatibility
398
400
  hook_type = (
399
401
  event.get("hook_event_name")
@@ -425,15 +427,40 @@ class ClaudeHookHandler:
425
427
  # Call appropriate handler if exists
426
428
  handler = event_handlers.get(hook_type)
427
429
  if handler:
430
+ # Track execution timing for hook emission
431
+ start_time = time.time()
432
+ success = False
433
+ error_message = None
434
+ result = None
435
+
428
436
  try:
429
437
  # Handlers can optionally return modified input
430
438
  result = handler(event)
439
+ success = True
431
440
  # Only PreToolUse handlers should return modified input
432
441
  if hook_type == "PreToolUse" and result is not None:
433
- return result
442
+ return_value = result
443
+ else:
444
+ return_value = None
434
445
  except Exception as e:
446
+ error_message = str(e)
447
+ return_value = None
435
448
  if DEBUG:
436
449
  print(f"Error handling {hook_type}: {e}", file=sys.stderr)
450
+ finally:
451
+ # Calculate duration
452
+ duration_ms = int((time.time() - start_time) * 1000)
453
+
454
+ # Emit hook execution event
455
+ self._emit_hook_execution_event(
456
+ hook_type=hook_type,
457
+ event=event,
458
+ success=success,
459
+ duration_ms=duration_ms,
460
+ error_message=error_message,
461
+ )
462
+
463
+ return return_value
437
464
 
438
465
  return None
439
466
 
@@ -478,6 +505,127 @@ class ClaudeHookHandler:
478
505
  """Generate event key through duplicate detector (backward compatibility)."""
479
506
  return self.duplicate_detector.generate_event_key(event)
480
507
 
508
+ def _emit_hook_execution_event(
509
+ self,
510
+ hook_type: str,
511
+ event: dict,
512
+ success: bool,
513
+ duration_ms: int,
514
+ error_message: Optional[str] = None,
515
+ ):
516
+ """Emit a structured JSON event for hook execution.
517
+
518
+ This emits a normalized event following the claude_event schema to provide
519
+ visibility into hook processing, timing, and success/failure status.
520
+
521
+ Args:
522
+ hook_type: The type of hook that executed (e.g., "UserPromptSubmit", "PreToolUse")
523
+ event: The original hook event data
524
+ success: Whether the hook executed successfully
525
+ duration_ms: How long the hook took to execute in milliseconds
526
+ error_message: Optional error message if the hook failed
527
+ """
528
+ # Generate a human-readable summary based on hook type
529
+ summary = self._generate_hook_summary(hook_type, event, success)
530
+
531
+ # Extract common fields
532
+ session_id = event.get("session_id", "")
533
+ working_dir = event.get("cwd", "")
534
+
535
+ # Build hook execution data
536
+ hook_data = {
537
+ "hook_name": hook_type,
538
+ "hook_type": hook_type,
539
+ "session_id": session_id,
540
+ "working_directory": working_dir,
541
+ "success": success,
542
+ "duration_ms": duration_ms,
543
+ "result_summary": summary,
544
+ "timestamp": datetime.now(timezone.utc).isoformat(),
545
+ }
546
+
547
+ # Add error information if present
548
+ if error_message:
549
+ hook_data["error_message"] = error_message
550
+
551
+ # Add hook-specific context
552
+ if hook_type == "PreToolUse":
553
+ hook_data["tool_name"] = event.get("tool_name", "")
554
+ elif hook_type == "PostToolUse":
555
+ hook_data["tool_name"] = event.get("tool_name", "")
556
+ hook_data["exit_code"] = event.get("exit_code", 0)
557
+ elif hook_type == "UserPromptSubmit":
558
+ prompt = event.get("prompt", "")
559
+ hook_data["prompt_preview"] = prompt[:100] if len(prompt) > 100 else prompt
560
+ hook_data["prompt_length"] = len(prompt)
561
+ elif hook_type == "SubagentStop":
562
+ hook_data["agent_type"] = event.get("agent_type", "unknown")
563
+ hook_data["reason"] = event.get("reason", "unknown")
564
+
565
+ # Emit through connection manager with proper structure
566
+ # This uses the existing event infrastructure
567
+ self._emit_socketio_event("", "hook_execution", hook_data)
568
+
569
+ if DEBUG:
570
+ print(
571
+ f"📊 Hook execution event: {hook_type} - {duration_ms}ms - {'✅' if success else '❌'}",
572
+ file=sys.stderr,
573
+ )
574
+
575
+ def _generate_hook_summary(self, hook_type: str, event: dict, success: bool) -> str:
576
+ """Generate a human-readable summary of what the hook did.
577
+
578
+ Args:
579
+ hook_type: The type of hook
580
+ event: The hook event data
581
+ success: Whether the hook executed successfully
582
+
583
+ Returns:
584
+ A brief description of what happened
585
+ """
586
+ if not success:
587
+ return f"Hook {hook_type} failed during processing"
588
+
589
+ # Generate hook-specific summaries
590
+ if hook_type == "UserPromptSubmit":
591
+ prompt = event.get("prompt", "")
592
+ if prompt.startswith("/"):
593
+ return f"Processed command: {prompt.split()[0]}"
594
+ return f"Processed user prompt ({len(prompt)} chars)"
595
+
596
+ if hook_type == "PreToolUse":
597
+ tool_name = event.get("tool_name", "unknown")
598
+ return f"Pre-processing tool call: {tool_name}"
599
+
600
+ if hook_type == "PostToolUse":
601
+ tool_name = event.get("tool_name", "unknown")
602
+ exit_code = event.get("exit_code", 0)
603
+ status = "success" if exit_code == 0 else "failed"
604
+ return f"Completed tool call: {tool_name} ({status})"
605
+
606
+ if hook_type == "SubagentStop":
607
+ agent_type = event.get("agent_type", "unknown")
608
+ reason = event.get("reason", "unknown")
609
+ return f"Subagent {agent_type} stopped: {reason}"
610
+
611
+ if hook_type == "SessionStart":
612
+ return "New session started"
613
+
614
+ if hook_type == "Stop":
615
+ reason = event.get("reason", "unknown")
616
+ return f"Session stopped: {reason}"
617
+
618
+ if hook_type == "Notification":
619
+ notification_type = event.get("notification_type", "unknown")
620
+ return f"Notification received: {notification_type}"
621
+
622
+ if hook_type == "AssistantResponse":
623
+ response_len = len(event.get("response", ""))
624
+ return f"Assistant response generated ({response_len} chars)"
625
+
626
+ # Default summary
627
+ return f"Hook {hook_type} processed successfully"
628
+
481
629
  def __del__(self):
482
630
  """Cleanup on handler destruction."""
483
631
  # Clean up connection manager if it exists
@@ -58,7 +58,7 @@ except ImportError:
58
58
  (),
59
59
  {
60
60
  "to_dict": lambda: {
61
- "event": "claude_event",
61
+ "event": "mpm_event",
62
62
  "type": event_data.get("type", "unknown"),
63
63
  "subtype": event_data.get("subtype", "generic"),
64
64
  "timestamp": event_data.get(
@@ -119,13 +119,33 @@ class ConnectionManagerService:
119
119
  tool_call_id = data.get("tool_call_id")
120
120
 
121
121
  # Create event data for normalization
122
+ # Extract session_id (try both camelCase and snake_case)
123
+ session_id = data.get("session_id") or data.get("sessionId")
124
+
125
+ # Extract working directory for project identification
126
+ # Try multiple field names for maximum compatibility
127
+ cwd = (
128
+ data.get("cwd")
129
+ or data.get("working_directory")
130
+ or data.get("workingDirectory")
131
+ )
132
+
133
+ # For hook_execution events, extract the actual hook type from data
134
+ # Otherwise use "hook" as the type
135
+ if event == "hook_execution":
136
+ hook_type = data.get("hook_type", "unknown")
137
+ event_type = hook_type
138
+ else:
139
+ event_type = "hook"
140
+
122
141
  raw_event = {
123
- "type": "hook",
124
- "subtype": event, # e.g., "user_prompt", "pre_tool", "subagent_stop"
142
+ "type": event_type, # Use actual hook type for hook_execution, "hook" otherwise
143
+ "subtype": event, # e.g., "user_prompt", "pre_tool", "subagent_stop", "execution"
125
144
  "timestamp": datetime.now(timezone.utc).isoformat(),
126
145
  "data": data,
127
- "source": "claude_hooks", # Identify the source
128
- "session_id": data.get("sessionId"), # Include session if available
146
+ "source": "mpm_hook", # Identify the source as mpm_hook
147
+ "session_id": session_id, # Include session if available (supports both naming conventions)
148
+ "cwd": cwd, # Add working directory at top level for easy frontend access
129
149
  "correlation_id": tool_call_id, # Set from tool_call_id for event correlation
130
150
  }
131
151
 
@@ -154,7 +174,7 @@ class ConnectionManagerService:
154
174
  if self.connection_pool:
155
175
  try:
156
176
  # Emit to Socket.IO server directly
157
- self.connection_pool.emit("claude_event", claude_event_data)
177
+ self.connection_pool.emit("mpm_event", claude_event_data)
158
178
  if DEBUG:
159
179
  print(f"✅ Emitted via connection pool: {event}", file=sys.stderr)
160
180
  return # Success - no need for fallback
@@ -13,9 +13,9 @@ for structured memory storage with semantic search capabilities.
13
13
  DESIGN DECISIONS:
14
14
  - Priority 10 for early execution to enrich prompts before other hooks
15
15
  - Uses subprocess to call kuzu-memory directly for maximum compatibility
16
- - Graceful degradation if kuzu-memory is not in PATH (though it's now required)
16
+ - Graceful degradation if kuzu-memory is not installed
17
17
  - Automatic extraction and storage of important information
18
- - kuzu-memory>=1.1.5 is now a REQUIRED dependency (moved from optional in v4.8.6)
18
+ - kuzu-memory is an OPTIONAL dependency (install with: pip install claude-mpm[memory])
19
19
  """
20
20
 
21
21
  import json
@@ -51,9 +51,9 @@ class KuzuMemoryHook(SubmitHook):
51
51
  self.enabled = self.kuzu_memory_cmd is not None
52
52
 
53
53
  if not self.enabled:
54
- logger.warning(
55
- "Kuzu-memory not found in PATH. As of v4.8.6, it's a required dependency. "
56
- "Install with: pip install kuzu-memory>=1.1.5 or pipx install kuzu-memory"
54
+ logger.debug(
55
+ "Kuzu-memory not found. Graph-based memory disabled. "
56
+ "To enable: pip install claude-mpm[memory] (requires cmake)"
57
57
  )
58
58
  else:
59
59
  logger.info(f"Kuzu-memory integration enabled: {self.kuzu_memory_cmd}")