claude-mpm 5.4.36__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 (137) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +489 -177
  3. claude_mpm/agents/base_agent.json +1 -1
  4. claude_mpm/agents/frontmatter_validator.py +2 -2
  5. claude_mpm/cli/commands/configure_agent_display.py +12 -0
  6. claude_mpm/cli/commands/mpm_init/core.py +72 -0
  7. claude_mpm/cli/commands/profile.py +276 -0
  8. claude_mpm/cli/commands/skills.py +14 -18
  9. claude_mpm/cli/executor.py +10 -0
  10. claude_mpm/cli/parsers/base_parser.py +7 -0
  11. claude_mpm/cli/parsers/profile_parser.py +147 -0
  12. claude_mpm/cli/parsers/skills_parser.py +0 -6
  13. claude_mpm/cli/startup.py +433 -147
  14. claude_mpm/commands/mpm-config.md +13 -250
  15. claude_mpm/commands/mpm-doctor.md +9 -22
  16. claude_mpm/commands/mpm-help.md +5 -206
  17. claude_mpm/commands/mpm-init.md +81 -507
  18. claude_mpm/commands/mpm-monitor.md +15 -402
  19. claude_mpm/commands/mpm-organize.md +61 -441
  20. claude_mpm/commands/mpm-postmortem.md +6 -108
  21. claude_mpm/commands/mpm-session-resume.md +12 -363
  22. claude_mpm/commands/mpm-status.md +5 -69
  23. claude_mpm/commands/mpm-ticket-view.md +52 -495
  24. claude_mpm/commands/mpm-version.md +5 -107
  25. claude_mpm/core/optimized_startup.py +61 -0
  26. claude_mpm/core/shared/config_loader.py +3 -1
  27. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  28. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  29. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CWc5urbQ.js → 4TdZjIqw.js} +1 -1
  30. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  31. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  32. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  33. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  34. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  35. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  36. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  37. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  38. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uj46x2Wr.js → BSNlmTZj.js} +1 -1
  39. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  40. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  41. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  42. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DjhvlsAc.js → NqQ1dWOy.js} +1 -1
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{N4qtv3Hx.js → Vzk33B_K.js} +1 -1
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.CAGBuiOw.js → 0.m1gL8KXf.js} +1 -1
  89. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  90. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  91. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  92. claude_mpm/dashboard/static/svelte-build/index.html +10 -10
  93. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  94. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  95. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  96. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  97. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  98. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  99. claude_mpm/init.py +276 -0
  100. claude_mpm/scripts/start_activity_logging.py +0 -0
  101. claude_mpm/services/agents/agent_builder.py +3 -3
  102. claude_mpm/services/agents/deployment/agent_deployment.py +22 -0
  103. claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -1
  104. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  105. claude_mpm/services/agents/deployment/agent_template_builder.py +29 -17
  106. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  107. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  108. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +149 -4
  109. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +47 -26
  110. claude_mpm/services/agents/git_source_manager.py +21 -2
  111. claude_mpm/services/agents/sources/git_source_sync_service.py +116 -5
  112. claude_mpm/services/monitor/management/lifecycle.py +7 -1
  113. claude_mpm/services/pm_skills_deployer.py +711 -0
  114. claude_mpm/services/profile_manager.py +337 -0
  115. claude_mpm/services/skills/git_skill_source_manager.py +148 -11
  116. claude_mpm/services/skills/selective_skill_deployer.py +97 -48
  117. claude_mpm/services/skills_deployer.py +161 -65
  118. claude_mpm/skills/bundled/security-scanning.md +112 -0
  119. claude_mpm/skills/skill_manager.py +98 -3
  120. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  121. {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.59.dist-info}/METADATA +3 -2
  122. {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.59.dist-info}/RECORD +126 -67
  123. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
  124. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
  125. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
  126. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
  127. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
  128. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
  129. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
  130. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
  132. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  133. {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.59.dist-info}/WHEEL +0 -0
  134. {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.59.dist-info}/entry_points.txt +0 -0
  135. {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE +0 -0
  136. {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  137. {claude_mpm-5.4.36.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)
@@ -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}")
claude_mpm/init.py CHANGED
@@ -163,6 +163,15 @@ class ProjectInitializer:
163
163
  f"✓ Found {agent_count} project agent(s) in .claude-mpm/agents/"
164
164
  )
165
165
 
166
+ # Verify and deploy PM skills (non-blocking)
167
+ self._verify_and_deploy_pm_skills(project_root, is_mcp_mode)
168
+
169
+ # Setup security hooks (auto-install pre-commit, detect-secrets)
170
+ self._setup_security_hooks(project_root, is_mcp_mode)
171
+
172
+ # Perform security checks (non-blocking)
173
+ self._check_security_risks(project_root, is_mcp_mode)
174
+
166
175
  return True
167
176
 
168
177
  except Exception as e:
@@ -170,6 +179,68 @@ class ProjectInitializer:
170
179
  print(f"✗ Failed to create .claude-mpm/ directory: {e}")
171
180
  return False
172
181
 
182
+ def _verify_and_deploy_pm_skills(
183
+ self, project_root: Path, is_mcp_mode: bool = False
184
+ ) -> None:
185
+ """Verify PM skills are deployed and auto-deploy if missing.
186
+
187
+ Non-blocking operation that gracefully handles errors.
188
+
189
+ Args:
190
+ project_root: Project root directory
191
+ is_mcp_mode: Whether running in MCP mode (suppress console output)
192
+ """
193
+ try:
194
+ from claude_mpm.services.pm_skills_deployer import PMSkillsDeployerService
195
+
196
+ deployer = PMSkillsDeployerService()
197
+ result = deployer.verify_pm_skills(project_root)
198
+
199
+ if not result.verified:
200
+ # Log warnings
201
+ for warning in result.warnings:
202
+ self.logger.warning(warning)
203
+
204
+ # Auto-deploy PM skills
205
+ self.logger.info("Auto-deploying PM skills...")
206
+ deploy_result = deployer.deploy_pm_skills(project_root)
207
+
208
+ if deploy_result.success:
209
+ self.logger.info(
210
+ f"PM skills deployed: {len(deploy_result.deployed)} deployed, "
211
+ f"{len(deploy_result.skipped)} skipped"
212
+ )
213
+
214
+ # Print to console if not in MCP mode
215
+ if not is_mcp_mode:
216
+ if deploy_result.deployed:
217
+ print(
218
+ f"✓ Deployed {len(deploy_result.deployed)} PM skill(s) "
219
+ f"to .claude-mpm/skills/pm/"
220
+ )
221
+ else:
222
+ self.logger.warning(
223
+ f"PM skills deployment had errors: {len(deploy_result.errors)}"
224
+ )
225
+ if not is_mcp_mode and deploy_result.errors:
226
+ print(
227
+ f"⚠ PM skills deployment had {len(deploy_result.errors)} error(s)"
228
+ )
229
+ else:
230
+ # Skills verified successfully
231
+ registry = deployer._load_registry(project_root)
232
+ skill_count = len(registry.get("skills", []))
233
+ self.logger.debug(f"PM skills verified: {skill_count} skills")
234
+
235
+ if not is_mcp_mode and skill_count > 0:
236
+ print(f"✓ Verified {skill_count} PM skill(s)")
237
+
238
+ except ImportError:
239
+ self.logger.debug("PM skills deployer not available")
240
+ except Exception as e:
241
+ self.logger.warning(f"PM skills verification failed: {e}")
242
+ # Don't print to console - this is a non-critical failure
243
+
173
244
  def _migrate_project_agents(self):
174
245
  """Migrate agents from old subdirectory structure to direct agents directory.
175
246
 
@@ -319,6 +390,211 @@ class ProjectInitializer:
319
390
  if not dst_file.exists():
320
391
  shutil.copy2(template_file, dst_file)
321
392
 
393
+ def _setup_security_hooks(
394
+ self, project_root: Path, is_mcp_mode: bool = False
395
+ ) -> None:
396
+ """Automatically install pre-commit hooks for secret scanning.
397
+
398
+ This method:
399
+ 1. Installs pre-commit and detect-secrets if missing
400
+ 2. Copies .pre-commit-config.yaml to project root
401
+ 3. Runs pre-commit install to set up git hooks
402
+ 4. Creates .secrets.baseline for detect-secrets
403
+
404
+ Args:
405
+ project_root: Project root directory
406
+ is_mcp_mode: Whether running in MCP mode (suppress console output)
407
+ """
408
+ try:
409
+ import subprocess
410
+
411
+ # Only set up hooks if this is a git repository
412
+ if not (project_root / ".git").exists():
413
+ self.logger.debug("Not a git repository, skipping security hooks setup")
414
+ return
415
+
416
+ # Check/install pre-commit
417
+ try:
418
+ subprocess.run(
419
+ ["pre-commit", "--version"],
420
+ capture_output=True,
421
+ text=True,
422
+ timeout=2,
423
+ check=True,
424
+ )
425
+ except (
426
+ subprocess.CalledProcessError,
427
+ subprocess.TimeoutExpired,
428
+ FileNotFoundError,
429
+ ):
430
+ self.logger.info("Installing pre-commit...")
431
+ try:
432
+ subprocess.run(
433
+ [sys.executable, "-m", "pip", "install", "pre-commit"],
434
+ capture_output=True,
435
+ text=True,
436
+ timeout=60,
437
+ check=True,
438
+ )
439
+ self.logger.info("pre-commit installed successfully")
440
+ except subprocess.CalledProcessError as e:
441
+ self.logger.warning(f"Failed to install pre-commit: {e}")
442
+ return
443
+
444
+ # Check/install detect-secrets
445
+ try:
446
+ subprocess.run(
447
+ ["detect-secrets", "--version"],
448
+ capture_output=True,
449
+ text=True,
450
+ timeout=2,
451
+ check=True,
452
+ )
453
+ except (
454
+ subprocess.CalledProcessError,
455
+ subprocess.TimeoutExpired,
456
+ FileNotFoundError,
457
+ ):
458
+ self.logger.info("Installing detect-secrets...")
459
+ try:
460
+ subprocess.run(
461
+ [sys.executable, "-m", "pip", "install", "detect-secrets"],
462
+ capture_output=True,
463
+ text=True,
464
+ timeout=60,
465
+ check=True,
466
+ )
467
+ self.logger.info("detect-secrets installed successfully")
468
+ except subprocess.CalledProcessError as e:
469
+ self.logger.warning(f"Failed to install detect-secrets: {e}")
470
+ return
471
+
472
+ # Copy .pre-commit-config.yaml to project root if it doesn't exist
473
+ precommit_config = project_root / ".pre-commit-config.yaml"
474
+ if not precommit_config.exists():
475
+ template_dir = Path(__file__).parent / "templates"
476
+ template_config = template_dir / ".pre-commit-config.yaml"
477
+
478
+ if template_config.exists():
479
+ shutil.copy2(template_config, precommit_config)
480
+ self.logger.info("Copied .pre-commit-config.yaml to project root")
481
+ else:
482
+ self.logger.warning("Template .pre-commit-config.yaml not found")
483
+ return
484
+
485
+ # Create .secrets.baseline if it doesn't exist
486
+ secrets_baseline = project_root / ".secrets.baseline"
487
+ if not secrets_baseline.exists():
488
+ try:
489
+ subprocess.run(
490
+ ["detect-secrets", "scan", "--baseline", ".secrets.baseline"],
491
+ cwd=str(project_root),
492
+ capture_output=True,
493
+ text=True,
494
+ timeout=30,
495
+ check=True,
496
+ )
497
+ self.logger.info("Created .secrets.baseline")
498
+ except subprocess.CalledProcessError as e:
499
+ self.logger.warning(f"Failed to create .secrets.baseline: {e}")
500
+
501
+ # Install git hooks
502
+ try:
503
+ subprocess.run(
504
+ ["pre-commit", "install"],
505
+ cwd=str(project_root),
506
+ capture_output=True,
507
+ text=True,
508
+ timeout=30,
509
+ check=True,
510
+ )
511
+ self.logger.info("Pre-commit hooks installed in git repository")
512
+
513
+ if not is_mcp_mode:
514
+ print("✓ Security hooks installed (pre-commit + detect-secrets)")
515
+
516
+ except subprocess.CalledProcessError as e:
517
+ self.logger.warning(f"Failed to install pre-commit hooks: {e}")
518
+
519
+ except Exception as e:
520
+ self.logger.debug(f"Security hooks setup failed: {e}")
521
+ # Don't print to console - this is a non-critical failure
522
+
523
+ def _check_security_risks(
524
+ self, project_root: Path, is_mcp_mode: bool = False
525
+ ) -> None:
526
+ """Check for potential security risks like exposed config files.
527
+
528
+ Non-blocking operation that warns about security issues.
529
+
530
+ Args:
531
+ project_root: Project root directory
532
+ is_mcp_mode: Whether running in MCP mode (suppress console output)
533
+ """
534
+ try:
535
+ import subprocess
536
+
537
+ security_issues = []
538
+
539
+ # Common secret file patterns to check
540
+ secret_patterns = [
541
+ ".mcp-vector-search/config.json",
542
+ ".mcp/config.json",
543
+ "openrouter.json",
544
+ "anthropic-config.json",
545
+ "credentials.json",
546
+ "secrets.json",
547
+ "api-keys.json",
548
+ ]
549
+
550
+ for pattern in secret_patterns:
551
+ file_path = project_root / pattern
552
+ if file_path.exists():
553
+ # Check if file is tracked by git
554
+ try:
555
+ result = subprocess.run(
556
+ ["git", "ls-files", str(file_path)],
557
+ check=False,
558
+ cwd=str(project_root),
559
+ capture_output=True,
560
+ text=True,
561
+ timeout=2,
562
+ )
563
+ if result.stdout.strip():
564
+ security_issues.append(
565
+ f"⚠️ SECURITY: {pattern} is tracked by git (may contain secrets)"
566
+ )
567
+ except (subprocess.TimeoutExpired, FileNotFoundError):
568
+ pass
569
+
570
+ # Check if file is ignored by .gitignore
571
+ try:
572
+ result = subprocess.run(
573
+ ["git", "check-ignore", str(file_path)],
574
+ check=False,
575
+ cwd=str(project_root),
576
+ capture_output=True,
577
+ text=True,
578
+ timeout=2,
579
+ )
580
+ if result.returncode != 0: # File NOT ignored
581
+ security_issues.append(
582
+ f"⚠️ WARNING: {pattern} exists but not in .gitignore"
583
+ )
584
+ except (subprocess.TimeoutExpired, FileNotFoundError):
585
+ pass
586
+
587
+ # Print security warnings if not in MCP mode
588
+ if security_issues and not is_mcp_mode:
589
+ print("\n🔒 Security Check:")
590
+ for issue in security_issues:
591
+ print(f" {issue}")
592
+ print()
593
+
594
+ except Exception as e:
595
+ self.logger.debug(f"Security check failed: {e}")
596
+ # Don't print to console - this is a non-critical failure
597
+
322
598
  def validate_dependencies(self) -> Dict[str, bool]:
323
599
  """Validate that all required dependencies are available."""
324
600
  dependencies = {}
File without changes