ma-agents 3.13.2 → 3.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +3 -3
  2. package/lib/agents.js +9 -2
  3. package/lib/bmad-cache/bmb/.claude-plugin/marketplace.json +3 -3
  4. package/lib/bmad-cache/bmb/_git_preserved/index +0 -0
  5. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-b388526edf307cf56f8e65673466cff074105abf.idx +0 -0
  6. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/{pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.pack → pack-b388526edf307cf56f8e65673466cff074105abf.pack} +0 -0
  7. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-b388526edf307cf56f8e65673466cff074105abf.rev +0 -0
  8. package/lib/bmad-cache/bmb/_git_preserved/packed-refs +1 -1
  9. package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
  10. package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
  11. package/lib/bmad-cache/bmb/package-lock.json +2 -2
  12. package/lib/bmad-cache/bmb/package.json +1 -1
  13. package/lib/bmad-cache/bmb/samples/bmad-agent-code-coach/scripts/wake.py +78 -0
  14. package/lib/bmad-cache/bmb/samples/bmad-agent-creative-muse/scripts/wake.py +78 -0
  15. package/lib/bmad-cache/bmb/samples/bmad-agent-sentinel/scripts/wake.py +78 -0
  16. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/customize-template.toml +3 -0
  17. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/init-sanctum-template.py +11 -5
  18. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/report-shell.html +1073 -0
  19. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/wake-template.py +78 -0
  20. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/customize.toml +48 -0
  21. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/count_tokens.py +78 -0
  22. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass.py +258 -0
  23. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/process-template.py +20 -0
  24. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/render_report.py +387 -0
  25. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/assets/adapter-claude-code.json +9 -0
  26. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/aggregate_benchmark.py +236 -0
  27. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/memlog.py +197 -0
  28. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_evals.py +513 -390
  29. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_triggers.py +347 -251
  30. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/tests/test_env_isolation.py +79 -0
  31. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/tests/test_trigger_detection.py +123 -0
  32. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-validate-module.py +82 -4
  33. package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/validate-module.py +55 -11
  34. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/customize-template.toml +3 -5
  35. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/report-shell.html +860 -0
  36. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/customize.toml +59 -0
  37. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/count_tokens.py +77 -0
  38. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/init_skill.py +132 -0
  39. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-prompt-metrics.py +186 -221
  40. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/quick_validate.py +117 -0
  41. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/render_report.py +387 -0
  42. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_canon_sync.py +46 -0
  43. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_count_tokens.py +180 -0
  44. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_render_report.py +171 -0
  45. package/lib/bmad-cache/cache-manifest.json +4 -4
  46. package/lib/bmad-customize/bmm-qa.customize.yaml +3 -3
  47. package/lib/bmad-extension/.claude-plugin/marketplace.json.template +3 -2
  48. package/lib/bmad-extension/skills/bmad-dev-epic/SKILL.md +101 -0
  49. package/lib/bmad-extension/skills/bmad-dev-epic/bmad-skill-manifest.yaml +3 -0
  50. package/lib/bmad-extension/skills/bmad-dev-epic/checklist.md +44 -0
  51. package/lib/bmad-extension/skills/bmad-dev-epic/customize.toml +54 -0
  52. package/lib/bmad-extension/skills/bmad-dev-epic/references/story-pipeline.md +112 -0
  53. package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-01-intake.md +59 -0
  54. package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-02-plan-waves.md +56 -0
  55. package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-03-epic-branch.md +35 -0
  56. package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-04-run-waves.md +49 -0
  57. package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-05-epic-adversarial.md +31 -0
  58. package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-06-release.md +57 -0
  59. package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-07-report.md +40 -0
  60. package/lib/bmad-extension/skills/ma-agent-sqa/SKILL.md +3 -3
  61. package/lib/bmad-extension/skills/ma-agent-sqa/bmad-skill-manifest.yaml +1 -1
  62. package/lib/bmad-extension/skills/module-help.csv +2 -1
  63. package/lib/bmad-extension/skills/module.yaml +1 -1
  64. package/lib/bmad-extension/skills/sqa-audit/SKILL.md +2 -2
  65. package/lib/bmad-extension/skills/sqa-ieee12207/SKILL.md +3 -3
  66. package/lib/bmad-extension/skills/sqa-requirements-quality/SKILL.md +2 -2
  67. package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +4 -3
  68. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/SKILL.md +101 -0
  69. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/bmad-skill-manifest.yaml +3 -0
  70. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/checklist.md +44 -0
  71. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/customize.toml +54 -0
  72. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/references/story-pipeline.md +112 -0
  73. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-01-intake.md +59 -0
  74. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-02-plan-waves.md +56 -0
  75. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-03-epic-branch.md +35 -0
  76. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-04-run-waves.md +49 -0
  77. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-05-epic-adversarial.md +31 -0
  78. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-06-release.md +57 -0
  79. package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-07-report.md +40 -0
  80. package/lib/bmad-extension-plugin/skills/ma-agent-sqa/SKILL.md +3 -3
  81. package/lib/bmad-extension-plugin/skills/ma-agent-sqa/bmad-skill-manifest.yaml +1 -1
  82. package/lib/bmad-extension-plugin/skills/module-help.csv +2 -1
  83. package/lib/bmad-extension-plugin/skills/module.yaml +1 -1
  84. package/lib/bmad-extension-plugin/skills/sqa-audit/SKILL.md +2 -2
  85. package/lib/bmad-extension-plugin/skills/sqa-ieee12207/SKILL.md +3 -3
  86. package/lib/bmad-extension-plugin/skills/sqa-requirements-quality/SKILL.md +2 -2
  87. package/lib/bmad.js +6 -1
  88. package/package.json +3 -3
  89. package/lib/.bmad-extension-plugin.build-22612-1782454014750/.claude-plugin/marketplace.json +0 -109
  90. package/lib/.bmad-extension-plugin.build-22612-1782454014750/skills/module-help.csv +0 -62
  91. package/lib/.bmad-extension-plugin.build-22612-1782454014750/skills/module.yaml +0 -20
  92. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.idx +0 -0
  93. package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.rev +0 -0
  94. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/references/sample-init-sanctum.py +0 -274
  95. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/generate-html-report.py +0 -534
  96. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-execution-deps.py +0 -337
  97. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-prompt-metrics.py +0 -425
  98. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-sanctum-architecture.py +0 -385
  99. package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-structure-capabilities.py +0 -482
  100. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/assets/Dockerfile +0 -29
  101. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/docker_setup.py +0 -115
  102. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/generate_report.py +0 -184
  103. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/pty_runner.py +0 -171
  104. package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/utils.py +0 -260
  105. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/extract-report-json.py +0 -287
  106. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-html-report.py +0 -588
  107. package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-execution-deps.py +0 -288
@@ -1,385 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Deterministic pre-pass for sanctum architecture scanner.
3
-
4
- Extracts structural metadata from a memory agent's sanctum architecture
5
- that the LLM scanner can use instead of reading all files itself. Covers:
6
- - SKILL.md content line count (non-blank, non-frontmatter)
7
- - Template file inventory (which of the 6 standard templates exist)
8
- - CREED template section inventory
9
- - BOND template section inventory
10
- - Capability reference frontmatter fields
11
- - Init script parameter extraction (SKILL_NAME, TEMPLATE_FILES, EVOLVABLE)
12
- - First-breath.md section inventory
13
- - PULSE template presence and sections
14
-
15
- Only runs for memory agents (agents with assets/ containing template files).
16
- """
17
-
18
- # /// script
19
- # requires-python = ">=3.9"
20
- # dependencies = []
21
- # ///
22
-
23
- from __future__ import annotations
24
-
25
- import argparse
26
- import json
27
- import re
28
- import sys
29
- from datetime import datetime, timezone
30
- from pathlib import Path
31
-
32
-
33
- STANDARD_TEMPLATES = [
34
- "INDEX-template.md",
35
- "PERSONA-template.md",
36
- "CREED-template.md",
37
- "BOND-template.md",
38
- "MEMORY-template.md",
39
- "CAPABILITIES-template.md",
40
- ]
41
-
42
- OPTIONAL_TEMPLATES = [
43
- "PULSE-template.md",
44
- ]
45
-
46
- CREED_REQUIRED_SECTIONS = [
47
- "The Sacred Truth",
48
- "Mission",
49
- "Core Values",
50
- "Standing Orders",
51
- "Philosophy",
52
- "Boundaries",
53
- "Anti-Patterns",
54
- "Dominion",
55
- ]
56
-
57
- FIRST_BREATH_CALIBRATION_SECTIONS = [
58
- "Save As You Go",
59
- "Pacing",
60
- "Chase What Catches",
61
- "Absorb Their Voice",
62
- "Show Your Work",
63
- "Hear the Silence",
64
- "The Territories",
65
- "Wrapping Up",
66
- ]
67
-
68
- FIRST_BREATH_CONFIG_SECTIONS = [
69
- "Save As You Go",
70
- "Discovery",
71
- "Urgency",
72
- "Wrapping Up",
73
- ]
74
-
75
-
76
- def count_content_lines(file_path: Path) -> int:
77
- """Count non-blank, non-frontmatter lines in a markdown file."""
78
- content = file_path.read_text()
79
-
80
- # Strip frontmatter
81
- stripped = re.sub(r"^---\s*\n.*?\n---\s*\n", "", content, count=1, flags=re.DOTALL)
82
-
83
- lines = [line for line in stripped.split("\n") if line.strip()]
84
- return len(lines)
85
-
86
-
87
- def extract_h2_h3_sections(file_path: Path) -> list[str]:
88
- """Extract H2 and H3 headings from a markdown file."""
89
- sections = []
90
- if not file_path.exists():
91
- return sections
92
- for line in file_path.read_text().split("\n"):
93
- match = re.match(r"^#{2,3}\s+(.+)", line)
94
- if match:
95
- sections.append(match.group(1).strip())
96
- return sections
97
-
98
-
99
- def parse_frontmatter(file_path: Path) -> dict:
100
- """Extract YAML frontmatter from a markdown file."""
101
- meta = {}
102
- content = file_path.read_text()
103
- match = re.match(r"^---\s*\n(.*?)\n---", content, re.DOTALL)
104
- if not match:
105
- return meta
106
- for line in match.group(1).strip().split("\n"):
107
- if ":" in line:
108
- key, _, value = line.partition(":")
109
- meta[key.strip()] = value.strip().strip("'\"")
110
- return meta
111
-
112
-
113
- def extract_init_script_params(script_path: Path) -> dict:
114
- """Extract agent-specific configuration from init-sanctum.py."""
115
- params = {
116
- "exists": script_path.exists(),
117
- "skill_name": None,
118
- "template_files": [],
119
- "skill_only_files": [],
120
- "evolvable": None,
121
- }
122
- if not script_path.exists():
123
- return params
124
-
125
- content = script_path.read_text()
126
-
127
- # SKILL_NAME
128
- match = re.search(r'SKILL_NAME\s*=\s*["\']([^"\']+)["\']', content)
129
- if match:
130
- params["skill_name"] = match.group(1)
131
-
132
- # TEMPLATE_FILES
133
- tmpl_match = re.search(
134
- r"TEMPLATE_FILES\s*=\s*\[(.*?)\]", content, re.DOTALL
135
- )
136
- if tmpl_match:
137
- params["template_files"] = re.findall(r'["\']([^"\']+)["\']', tmpl_match.group(1))
138
-
139
- # SKILL_ONLY_FILES
140
- only_match = re.search(
141
- r"SKILL_ONLY_FILES\s*=\s*\{(.*?)\}", content, re.DOTALL
142
- )
143
- if only_match:
144
- params["skill_only_files"] = re.findall(r'["\']([^"\']+)["\']', only_match.group(1))
145
-
146
- # EVOLVABLE
147
- ev_match = re.search(r"EVOLVABLE\s*=\s*(True|False)", content)
148
- if ev_match:
149
- params["evolvable"] = ev_match.group(1) == "True"
150
-
151
- return params
152
-
153
-
154
- def check_section_present(sections: list[str], keyword: str) -> bool:
155
- """Check if any section heading contains the keyword (case-insensitive)."""
156
- keyword_lower = keyword.lower()
157
- return any(keyword_lower in s.lower() for s in sections)
158
-
159
-
160
- def main():
161
- parser = argparse.ArgumentParser(
162
- description="Pre-pass for sanctum architecture scanner"
163
- )
164
- parser.add_argument("skill_path", help="Path to the agent skill directory")
165
- parser.add_argument(
166
- "-o", "--output", help="Output JSON file path (default: stdout)"
167
- )
168
- args = parser.parse_args()
169
-
170
- skill_path = Path(args.skill_path).resolve()
171
- if not skill_path.is_dir():
172
- print(f"Error: {skill_path} is not a directory", file=sys.stderr)
173
- sys.exit(2)
174
-
175
- assets_dir = skill_path / "assets"
176
- references_dir = skill_path / "references"
177
- scripts_dir = skill_path / "scripts"
178
- skill_md = skill_path / "SKILL.md"
179
-
180
- # Check if this is a memory agent (has template files in assets/)
181
- is_memory_agent = assets_dir.exists() and any(
182
- f.name.endswith("-template.md") for f in assets_dir.iterdir() if f.is_file()
183
- )
184
-
185
- if not is_memory_agent:
186
- result = {
187
- "timestamp": datetime.now(timezone.utc).isoformat(),
188
- "skill_path": str(skill_path),
189
- "is_memory_agent": False,
190
- "message": "Not a memory agent — no sanctum templates found in assets/",
191
- }
192
- output_json(result, args.output)
193
- return
194
-
195
- # SKILL.md analysis
196
- skill_analysis = {
197
- "exists": skill_md.exists(),
198
- "content_lines": count_content_lines(skill_md) if skill_md.exists() else 0,
199
- "sections": extract_h2_h3_sections(skill_md) if skill_md.exists() else [],
200
- }
201
-
202
- # Template inventory
203
- template_inventory = {}
204
- for tmpl in STANDARD_TEMPLATES:
205
- tmpl_path = assets_dir / tmpl
206
- template_inventory[tmpl] = {
207
- "exists": tmpl_path.exists(),
208
- "sections": extract_h2_h3_sections(tmpl_path) if tmpl_path.exists() else [],
209
- "content_lines": count_content_lines(tmpl_path) if tmpl_path.exists() else 0,
210
- }
211
-
212
- for tmpl in OPTIONAL_TEMPLATES:
213
- tmpl_path = assets_dir / tmpl
214
- template_inventory[tmpl] = {
215
- "exists": tmpl_path.exists(),
216
- "optional": True,
217
- "sections": extract_h2_h3_sections(tmpl_path) if tmpl_path.exists() else [],
218
- "content_lines": count_content_lines(tmpl_path) if tmpl_path.exists() else 0,
219
- }
220
-
221
- # CREED section check
222
- creed_path = assets_dir / "CREED-template.md"
223
- creed_sections = extract_h2_h3_sections(creed_path) if creed_path.exists() else []
224
- creed_check = {}
225
- for section in CREED_REQUIRED_SECTIONS:
226
- creed_check[section] = check_section_present(creed_sections, section)
227
-
228
- # First-breath analysis
229
- first_breath_path = references_dir / "first-breath.md"
230
- fb_sections = extract_h2_h3_sections(first_breath_path) if first_breath_path.exists() else []
231
-
232
- # Detect style: calibration has "Absorb Their Voice", configuration has "Discovery"
233
- is_calibration = check_section_present(fb_sections, "Absorb")
234
- is_configuration = check_section_present(fb_sections, "Discovery") and not is_calibration
235
- fb_style = "calibration" if is_calibration else ("configuration" if is_configuration else "unknown")
236
-
237
- expected_sections = (
238
- FIRST_BREATH_CALIBRATION_SECTIONS if is_calibration else FIRST_BREATH_CONFIG_SECTIONS
239
- )
240
- fb_check = {}
241
- for section in expected_sections:
242
- fb_check[section] = check_section_present(fb_sections, section)
243
-
244
- first_breath_analysis = {
245
- "exists": first_breath_path.exists(),
246
- "style": fb_style,
247
- "sections": fb_sections,
248
- "section_checks": fb_check,
249
- }
250
-
251
- # Capability frontmatter scan
252
- capabilities = []
253
- if references_dir.exists():
254
- for md_file in sorted(references_dir.glob("*.md")):
255
- if md_file.name == "first-breath.md":
256
- continue
257
- meta = parse_frontmatter(md_file)
258
- if meta:
259
- cap_info = {
260
- "file": md_file.name,
261
- "has_name": "name" in meta,
262
- "has_code": "code" in meta,
263
- "has_description": "description" in meta,
264
- "sections": extract_h2_h3_sections(md_file),
265
- }
266
- # Check for memory agent patterns
267
- cap_info["has_memory_integration"] = check_section_present(
268
- cap_info["sections"], "Memory Integration"
269
- )
270
- cap_info["has_after_session"] = check_section_present(
271
- cap_info["sections"], "After"
272
- )
273
- cap_info["has_success"] = check_section_present(
274
- cap_info["sections"], "Success"
275
- )
276
- capabilities.append(cap_info)
277
-
278
- # Init script analysis
279
- init_script_path = scripts_dir / "init-sanctum.py"
280
- init_params = extract_init_script_params(init_script_path)
281
-
282
- # Cross-check: init TEMPLATE_FILES vs actual templates
283
- actual_templates = [f.name for f in assets_dir.iterdir() if f.name.endswith("-template.md")] if assets_dir.exists() else []
284
- init_template_match = set(init_params.get("template_files", [])) == set(actual_templates) if init_params["exists"] else None
285
-
286
- # Cross-check: init SKILL_NAME vs folder name
287
- skill_name_match = init_params.get("skill_name") == skill_path.name if init_params["exists"] else None
288
-
289
- # Findings
290
- findings = []
291
-
292
- if skill_analysis["content_lines"] > 40:
293
- findings.append({
294
- "severity": "high",
295
- "file": "SKILL.md",
296
- "message": f"Bootloader has {skill_analysis['content_lines']} content lines (target: ~30, max: 40)",
297
- })
298
-
299
- for tmpl in STANDARD_TEMPLATES:
300
- if not template_inventory[tmpl]["exists"]:
301
- findings.append({
302
- "severity": "critical",
303
- "file": f"assets/{tmpl}",
304
- "message": f"Missing standard template: {tmpl}",
305
- })
306
-
307
- for section, present in creed_check.items():
308
- if not present:
309
- findings.append({
310
- "severity": "high",
311
- "file": "assets/CREED-template.md",
312
- "message": f"Missing required CREED section: {section}",
313
- })
314
-
315
- if not first_breath_analysis["exists"]:
316
- findings.append({
317
- "severity": "critical",
318
- "file": "references/first-breath.md",
319
- "message": "Missing first-breath.md",
320
- })
321
- else:
322
- for section, present in first_breath_analysis["section_checks"].items():
323
- if not present:
324
- findings.append({
325
- "severity": "high",
326
- "file": "references/first-breath.md",
327
- "message": f"Missing First Breath section: {section}",
328
- })
329
-
330
- if not init_params["exists"]:
331
- findings.append({
332
- "severity": "critical",
333
- "file": "scripts/init-sanctum.py",
334
- "message": "Missing init-sanctum.py",
335
- })
336
- else:
337
- if skill_name_match is False:
338
- findings.append({
339
- "severity": "critical",
340
- "file": "scripts/init-sanctum.py",
341
- "message": f"SKILL_NAME mismatch: script has '{init_params['skill_name']}', folder is '{skill_path.name}'",
342
- })
343
- if init_template_match is False:
344
- findings.append({
345
- "severity": "high",
346
- "file": "scripts/init-sanctum.py",
347
- "message": "TEMPLATE_FILES does not match actual templates in assets/",
348
- })
349
-
350
- result = {
351
- "timestamp": datetime.now(timezone.utc).isoformat(),
352
- "skill_path": str(skill_path),
353
- "is_memory_agent": True,
354
- "skill_md": skill_analysis,
355
- "template_inventory": template_inventory,
356
- "creed_sections": creed_check,
357
- "first_breath": first_breath_analysis,
358
- "capabilities": capabilities,
359
- "init_script": init_params,
360
- "cross_checks": {
361
- "skill_name_match": skill_name_match,
362
- "template_files_match": init_template_match,
363
- },
364
- "findings": findings,
365
- "finding_count": len(findings),
366
- "critical_count": sum(1 for f in findings if f["severity"] == "critical"),
367
- "high_count": sum(1 for f in findings if f["severity"] == "high"),
368
- }
369
-
370
- output_json(result, args.output)
371
-
372
-
373
- def output_json(data: dict, output_path: str | None) -> None:
374
- """Write JSON to file or stdout."""
375
- json_str = json.dumps(data, indent=2)
376
- if output_path:
377
- Path(output_path).parent.mkdir(parents=True, exist_ok=True)
378
- Path(output_path).write_text(json_str + "\n")
379
- print(f"Wrote: {output_path}", file=sys.stderr)
380
- else:
381
- print(json_str)
382
-
383
-
384
- if __name__ == "__main__":
385
- main()