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.
- package/README.md +3 -3
- package/lib/agents.js +9 -2
- package/lib/bmad-cache/bmb/.claude-plugin/marketplace.json +3 -3
- package/lib/bmad-cache/bmb/_git_preserved/index +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-b388526edf307cf56f8e65673466cff074105abf.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/{pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.pack → pack-b388526edf307cf56f8e65673466cff074105abf.pack} +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-b388526edf307cf56f8e65673466cff074105abf.rev +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/packed-refs +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/refs/heads/main +1 -1
- package/lib/bmad-cache/bmb/_git_preserved/shallow +1 -1
- package/lib/bmad-cache/bmb/package-lock.json +2 -2
- package/lib/bmad-cache/bmb/package.json +1 -1
- package/lib/bmad-cache/bmb/samples/bmad-agent-code-coach/scripts/wake.py +78 -0
- package/lib/bmad-cache/bmb/samples/bmad-agent-creative-muse/scripts/wake.py +78 -0
- package/lib/bmad-cache/bmb/samples/bmad-agent-sentinel/scripts/wake.py +78 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/customize-template.toml +3 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/init-sanctum-template.py +11 -5
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/report-shell.html +1073 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/assets/wake-template.py +78 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/customize.toml +48 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/count_tokens.py +78 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass.py +258 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/process-template.py +20 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/render_report.py +387 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/assets/adapter-claude-code.json +9 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/aggregate_benchmark.py +236 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/memlog.py +197 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_evals.py +513 -390
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/run_triggers.py +347 -251
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/tests/test_env_isolation.py +79 -0
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/tests/test_trigger_detection.py +123 -0
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/tests/test-validate-module.py +82 -4
- package/lib/bmad-cache/bmb/skills/bmad-module-builder/scripts/validate-module.py +55 -11
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/customize-template.toml +3 -5
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/assets/report-shell.html +860 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/customize.toml +59 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/count_tokens.py +77 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/init_skill.py +132 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-prompt-metrics.py +186 -221
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/quick_validate.py +117 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/render_report.py +387 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_canon_sync.py +46 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_count_tokens.py +180 -0
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/tests/test_render_report.py +171 -0
- package/lib/bmad-cache/cache-manifest.json +4 -4
- package/lib/bmad-customize/bmm-qa.customize.yaml +3 -3
- package/lib/bmad-extension/.claude-plugin/marketplace.json.template +3 -2
- package/lib/bmad-extension/skills/bmad-dev-epic/SKILL.md +101 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/checklist.md +44 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/customize.toml +54 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/references/story-pipeline.md +112 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-01-intake.md +59 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-02-plan-waves.md +56 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-03-epic-branch.md +35 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-04-run-waves.md +49 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-05-epic-adversarial.md +31 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-06-release.md +57 -0
- package/lib/bmad-extension/skills/bmad-dev-epic/steps/step-07-report.md +40 -0
- package/lib/bmad-extension/skills/ma-agent-sqa/SKILL.md +3 -3
- package/lib/bmad-extension/skills/ma-agent-sqa/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-extension/skills/module-help.csv +2 -1
- package/lib/bmad-extension/skills/module.yaml +1 -1
- package/lib/bmad-extension/skills/sqa-audit/SKILL.md +2 -2
- package/lib/bmad-extension/skills/sqa-ieee12207/SKILL.md +3 -3
- package/lib/bmad-extension/skills/sqa-requirements-quality/SKILL.md +2 -2
- package/lib/bmad-extension-plugin/.claude-plugin/marketplace.json +4 -3
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/SKILL.md +101 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/bmad-skill-manifest.yaml +3 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/checklist.md +44 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/customize.toml +54 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/references/story-pipeline.md +112 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-01-intake.md +59 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-02-plan-waves.md +56 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-03-epic-branch.md +35 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-04-run-waves.md +49 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-05-epic-adversarial.md +31 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-06-release.md +57 -0
- package/lib/bmad-extension-plugin/skills/bmad-dev-epic/steps/step-07-report.md +40 -0
- package/lib/bmad-extension-plugin/skills/ma-agent-sqa/SKILL.md +3 -3
- package/lib/bmad-extension-plugin/skills/ma-agent-sqa/bmad-skill-manifest.yaml +1 -1
- package/lib/bmad-extension-plugin/skills/module-help.csv +2 -1
- package/lib/bmad-extension-plugin/skills/module.yaml +1 -1
- package/lib/bmad-extension-plugin/skills/sqa-audit/SKILL.md +2 -2
- package/lib/bmad-extension-plugin/skills/sqa-ieee12207/SKILL.md +3 -3
- package/lib/bmad-extension-plugin/skills/sqa-requirements-quality/SKILL.md +2 -2
- package/lib/bmad.js +6 -1
- package/package.json +3 -3
- package/lib/.bmad-extension-plugin.build-22612-1782454014750/.claude-plugin/marketplace.json +0 -109
- package/lib/.bmad-extension-plugin.build-22612-1782454014750/skills/module-help.csv +0 -62
- package/lib/.bmad-extension-plugin.build-22612-1782454014750/skills/module.yaml +0 -20
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.idx +0 -0
- package/lib/bmad-cache/bmb/_git_preserved/objects/pack/pack-8f8b045fef5af6911495cf3b2a89f1ed75e120f7.rev +0 -0
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/references/sample-init-sanctum.py +0 -274
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/generate-html-report.py +0 -534
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-execution-deps.py +0 -337
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-prompt-metrics.py +0 -425
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-sanctum-architecture.py +0 -385
- package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-structure-capabilities.py +0 -482
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/assets/Dockerfile +0 -29
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/docker_setup.py +0 -115
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/generate_report.py +0 -184
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/pty_runner.py +0 -171
- package/lib/bmad-cache/bmb/skills/bmad-eval-runner/scripts/utils.py +0 -260
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/extract-report-json.py +0 -287
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/generate-html-report.py +0 -588
- package/lib/bmad-cache/bmb/skills/bmad-workflow-builder/scripts/prepass-execution-deps.py +0 -288
package/lib/bmad-cache/bmb/skills/bmad-agent-builder/scripts/prepass-sanctum-architecture.py
DELETED
|
@@ -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()
|