bmad-module-skill-forge 1.7.0 → 1.9.0

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 (53) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/_data/pinned.yaml +1 -1
  3. package/docs/bmad-synergy.md +16 -0
  4. package/docs/deepwiki.md +89 -0
  5. package/docs/verifying-a-skill.md +8 -2
  6. package/docs/workflows.md +17 -11
  7. package/package.json +2 -2
  8. package/src/shared/_known-workarounds.yaml +155 -0
  9. package/src/shared/references/pipeline-contracts.md +11 -2
  10. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -1
  11. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  12. package/src/shared/scripts/skf-detect-docs.py +412 -0
  13. package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
  14. package/src/shared/scripts/skf-forge-tier-rw.py +73 -0
  15. package/src/shared/scripts/skf-preapply.py +192 -0
  16. package/src/shared/scripts/skf-shape-detect.py +563 -0
  17. package/src/shared/scripts/skf-validate-frontmatter.py +56 -5
  18. package/src/shared/scripts/skf-validate-pins.py +368 -0
  19. package/src/skf-analyze-source/SKILL.md +26 -20
  20. package/src/skf-analyze-source/assets/skill-brief-schema.md +2 -1
  21. package/src/skf-analyze-source/references/identify-units.md +41 -4
  22. package/src/skf-analyze-source/references/init.md +36 -0
  23. package/src/skf-analyze-source/references/scan-project.md +5 -2
  24. package/src/skf-analyze-source/references/step-auto-scope.md +525 -0
  25. package/src/skf-analyze-source/references/step-shape-detect.md +79 -0
  26. package/src/skf-analyze-source/references/unit-detection-heuristics.md +16 -0
  27. package/src/skf-audit-skill/SKILL.md +1 -0
  28. package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
  29. package/src/skf-audit-skill/references/report.md +4 -0
  30. package/src/skf-audit-skill/references/severity-classify.md +1 -1
  31. package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
  32. package/src/skf-brief-skill/SKILL.md +7 -3
  33. package/src/skf-brief-skill/references/gather-intent.md +14 -0
  34. package/src/skf-brief-skill/references/step-auto-brief.md +171 -0
  35. package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
  36. package/src/skf-create-skill/SKILL.md +3 -0
  37. package/src/skf-create-skill/assets/skill-sections.md +2 -0
  38. package/src/skf-create-skill/references/compile.md +1 -1
  39. package/src/skf-create-skill/references/generate-artifacts.md +5 -10
  40. package/src/skf-create-skill/references/step-auto-shard.md +110 -0
  41. package/src/skf-create-skill/references/step-doc-rot.md +109 -0
  42. package/src/skf-create-skill/references/step-doc-sources.md +114 -0
  43. package/src/skf-create-stack-skill/references/generate-output.md +5 -5
  44. package/src/skf-forger/SKILL.md +8 -2
  45. package/src/skf-test-skill/SKILL.md +8 -7
  46. package/src/skf-test-skill/customize.toml +2 -1
  47. package/src/skf-test-skill/references/external-validators.md +1 -1
  48. package/src/skf-test-skill/references/init.md +16 -1
  49. package/src/skf-test-skill/references/report.md +5 -1
  50. package/src/skf-test-skill/references/score.md +95 -6
  51. package/src/skf-test-skill/references/source-access-protocol.md +14 -0
  52. package/src/skf-test-skill/references/step-hard-gate.md +73 -0
  53. package/src/skf-test-skill/templates/test-report-template.md +1 -0
@@ -0,0 +1,192 @@
1
+ # /// script
2
+ # requires-python = ">=3.9"
3
+ # dependencies = ["pyyaml"]
4
+ # ///
5
+ """SKF Pre-Apply — apply known workarounds before pipeline iteration.
6
+
7
+ Loads a shared seed registry of known workarounds and optionally a project-local
8
+ registry, then scans target directory files for fingerprint matches and applies
9
+ the corresponding fixes.
10
+
11
+ CLI:
12
+ uv run python src/shared/scripts/skf-preapply.py \
13
+ --target-dir <path> [--registry <path>] [--local-registry <path>] [--log-dir <path>]
14
+
15
+ Input:
16
+ --target-dir directory to scan for fingerprint matches (required)
17
+ --registry path to shared seed registry YAML (optional, defaults to
18
+ src/shared/_known-workarounds.yaml relative to script)
19
+ --local-registry path to project-local override registry YAML (optional)
20
+ --log-dir directory to write preapply-log.json (optional)
21
+
22
+ Output (JSON on stdout):
23
+ {
24
+ "applied": [{"fingerprint": "...", "fix": "...", "file": "...", "severity": "..."}],
25
+ "skipped_count": 0,
26
+ "registry_version": 1
27
+ }
28
+
29
+ Exit codes:
30
+ 0 success (applied >= 0 fixes without errors)
31
+ 2 error (invalid args, missing registry, YAML parse error)
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import argparse
37
+ import json
38
+ import os
39
+ import re
40
+ import sys
41
+ from pathlib import Path
42
+ from typing import Any, Dict, List, NoReturn
43
+
44
+ import yaml
45
+
46
+
47
+ def _err(message: str, code: str) -> NoReturn:
48
+ json.dump({"error": message, "code": code}, sys.stderr)
49
+ sys.stderr.write("\n")
50
+ sys.exit(2)
51
+
52
+
53
+ def _load_registry(path: Path) -> Dict[str, Any]:
54
+ if not path.is_file():
55
+ _err(f"Registry not found: {path}", "REGISTRY_NOT_FOUND")
56
+ try:
57
+ text = path.read_text(encoding="utf-8")
58
+ data = yaml.safe_load(text)
59
+ except yaml.YAMLError as exc:
60
+ _err(f"YAML parse error in {path}: {exc}", "YAML_PARSE_ERROR")
61
+ if not isinstance(data, dict) or "workarounds" not in data:
62
+ _err(f"Invalid registry format in {path}", "INVALID_REGISTRY")
63
+ return data
64
+
65
+
66
+ def _merge_registries(
67
+ shared: Dict[str, Any], local: Dict[str, Any] | None
68
+ ) -> tuple[List[Dict[str, Any]], int]:
69
+ version = shared.get("version", 1)
70
+ by_fp: Dict[str, Dict[str, Any]] = {}
71
+ for entry in shared.get("workarounds", []):
72
+ by_fp[entry["fingerprint"]] = entry
73
+ if local is not None:
74
+ for entry in local.get("workarounds", []):
75
+ by_fp[entry["fingerprint"]] = entry
76
+ return list(by_fp.values()), version
77
+
78
+
79
+ def _match_and_apply(
80
+ workarounds: List[Dict[str, Any]], target_dir: Path
81
+ ) -> tuple[List[Dict[str, Any]], int]:
82
+ applied: List[Dict[str, Any]] = []
83
+ skipped = 0
84
+
85
+ md_files = sorted(target_dir.rglob("*.md"))
86
+
87
+ for wa in workarounds:
88
+ fp = wa["fingerprint"]
89
+ fix = wa["fix"]
90
+ use_regex = wa.get("regex", False)
91
+ matched = False
92
+
93
+ for md_file in md_files:
94
+ content = md_file.read_text(encoding="utf-8")
95
+ if use_regex:
96
+ try:
97
+ pattern = re.compile(fp)
98
+ except re.error as exc:
99
+ _err(
100
+ f"Invalid regex in workaround fingerprint: {exc}",
101
+ "INVALID_REGEX",
102
+ )
103
+ if pattern.search(content):
104
+ new_content = pattern.sub(fix, content)
105
+ md_file.write_text(new_content, encoding="utf-8")
106
+ applied.append({
107
+ "fingerprint": fp,
108
+ "fix": fix,
109
+ "file": md_file.relative_to(target_dir).as_posix(),
110
+ "severity": wa.get("severity", "low"),
111
+ })
112
+ matched = True
113
+ else:
114
+ if fp in content:
115
+ new_content = content.replace(fp, fix)
116
+ md_file.write_text(new_content, encoding="utf-8")
117
+ applied.append({
118
+ "fingerprint": fp,
119
+ "fix": fix,
120
+ "file": md_file.relative_to(target_dir).as_posix(),
121
+ "severity": wa.get("severity", "low"),
122
+ })
123
+ matched = True
124
+
125
+ if not matched:
126
+ skipped += 1
127
+
128
+ return applied, skipped
129
+
130
+
131
+ def main(argv: List[str] | None = None) -> None:
132
+ parser = argparse.ArgumentParser(
133
+ description="Apply known workarounds before pipeline iteration."
134
+ )
135
+ parser.add_argument(
136
+ "--target-dir", required=True, type=Path,
137
+ help="Directory to scan for fingerprint matches",
138
+ )
139
+ default_registry = (
140
+ Path(__file__).resolve().parent.parent / "_known-workarounds.yaml"
141
+ )
142
+ parser.add_argument(
143
+ "--registry", type=Path, default=default_registry,
144
+ help="Path to shared seed registry YAML",
145
+ )
146
+ parser.add_argument(
147
+ "--local-registry", type=Path, default=None,
148
+ help="Path to project-local override registry YAML",
149
+ )
150
+ parser.add_argument(
151
+ "--log-dir", type=Path, default=None,
152
+ help="Directory to write preapply-log.json",
153
+ )
154
+
155
+ args = parser.parse_args(argv)
156
+
157
+ if not args.target_dir.is_dir():
158
+ _err(
159
+ f"Target directory not found: {args.target_dir}",
160
+ "TARGET_NOT_FOUND",
161
+ )
162
+
163
+ shared = _load_registry(args.registry)
164
+
165
+ local = None
166
+ if args.local_registry is not None:
167
+ local = _load_registry(args.local_registry)
168
+
169
+ workarounds, version = _merge_registries(shared, local)
170
+ applied, skipped = _match_and_apply(workarounds, args.target_dir)
171
+
172
+ result = {
173
+ "applied": applied,
174
+ "skipped_count": skipped,
175
+ "registry_version": version,
176
+ }
177
+
178
+ json.dump(result, sys.stdout)
179
+ sys.stdout.write("\n")
180
+
181
+ if args.log_dir is not None:
182
+ args.log_dir.mkdir(parents=True, exist_ok=True)
183
+ log_path = args.log_dir / "preapply-log.json"
184
+ with open(
185
+ log_path, "w", encoding="utf-8", newline="\n"
186
+ ) as f:
187
+ json.dump(result, f, indent=2)
188
+ f.write("\n")
189
+
190
+
191
+ if __name__ == "__main__":
192
+ main()