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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT.md +164 -0
- claude_mpm/agents/BASE_ENGINEER.md +658 -0
- claude_mpm/agents/MEMORY.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +771 -1019
- claude_mpm/agents/WORKFLOW.md +5 -254
- claude_mpm/agents/agent_loader.py +1 -1
- claude_mpm/agents/base_agent.json +31 -0
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/cli/commands/agent_state_manager.py +10 -10
- claude_mpm/cli/commands/agents.py +9 -9
- claude_mpm/cli/commands/auto_configure.py +4 -4
- claude_mpm/cli/commands/configure.py +1 -1
- claude_mpm/cli/commands/configure_agent_display.py +12 -0
- claude_mpm/cli/commands/mpm_init/core.py +72 -0
- claude_mpm/cli/commands/postmortem.py +1 -1
- claude_mpm/cli/commands/profile.py +276 -0
- claude_mpm/cli/commands/skills.py +14 -18
- claude_mpm/cli/executor.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +2 -2
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/profile_parser.py +147 -0
- claude_mpm/cli/parsers/skills_parser.py +0 -6
- claude_mpm/cli/startup.py +506 -180
- claude_mpm/commands/mpm-config.md +13 -250
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -206
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +61 -441
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/core/config.py +2 -4
- claude_mpm/core/framework/loaders/agent_loader.py +1 -1
- claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
- claude_mpm/core/optimized_startup.py +61 -0
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
- claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
- claude_mpm/dashboard/static/svelte-build/index.html +36 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/init.py +276 -0
- claude_mpm/models/git_repository.py +3 -3
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/cache_git_manager.py +6 -6
- claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -2
- claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +31 -19
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
- claude_mpm/services/agents/git_source_manager.py +23 -4
- claude_mpm/services/agents/recommender.py +5 -3
- claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +121 -10
- claude_mpm/services/agents/startup_sync.py +22 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/monitor/management/lifecycle.py +7 -1
- claude_mpm/services/monitor/server.py +473 -3
- claude_mpm/services/pm_skills_deployer.py +711 -0
- claude_mpm/services/profile_manager.py +337 -0
- claude_mpm/services/skills/git_skill_source_manager.py +148 -11
- claude_mpm/services/skills/selective_skill_deployer.py +97 -48
- claude_mpm/services/skills_deployer.py +161 -65
- claude_mpm/services/socketio/dashboard_server.py +1 -0
- claude_mpm/services/socketio/event_normalizer.py +37 -6
- claude_mpm/services/socketio/server/core.py +262 -123
- claude_mpm/skills/bundled/security-scanning.md +112 -0
- claude_mpm/skills/skill_manager.py +98 -3
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- claude_mpm/utils/agent_dependency_loader.py +14 -2
- claude_mpm/utils/agent_filters.py +1 -1
- claude_mpm/utils/migration.py +4 -4
- claude_mpm/utils/robust_installer.py +47 -3
- {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/METADATA +7 -4
- {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/RECORD +175 -81
- {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {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)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -58,7 +58,7 @@ except ImportError:
|
|
|
58
58
|
(),
|
|
59
59
|
{
|
|
60
60
|
"to_dict": lambda: {
|
|
61
|
-
"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": "
|
|
128
|
-
"session_id":
|
|
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("
|
|
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
|
|
16
|
+
- Graceful degradation if kuzu-memory is not installed
|
|
17
17
|
- Automatic extraction and storage of important information
|
|
18
|
-
- kuzu-memory
|
|
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.
|
|
55
|
-
"Kuzu-memory not found
|
|
56
|
-
"
|
|
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}")
|