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.
- package/.claude-plugin/marketplace.json +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/docs/bmad-synergy.md +16 -0
- package/docs/deepwiki.md +89 -0
- package/docs/verifying-a-skill.md +8 -2
- package/docs/workflows.md +17 -11
- package/package.json +2 -2
- package/src/shared/_known-workarounds.yaml +155 -0
- package/src/shared/references/pipeline-contracts.md +11 -2
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -1
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- package/src/shared/scripts/skf-detect-docs.py +412 -0
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
- package/src/shared/scripts/skf-forge-tier-rw.py +73 -0
- package/src/shared/scripts/skf-preapply.py +192 -0
- package/src/shared/scripts/skf-shape-detect.py +563 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +56 -5
- package/src/shared/scripts/skf-validate-pins.py +368 -0
- package/src/skf-analyze-source/SKILL.md +26 -20
- package/src/skf-analyze-source/assets/skill-brief-schema.md +2 -1
- package/src/skf-analyze-source/references/identify-units.md +41 -4
- package/src/skf-analyze-source/references/init.md +36 -0
- package/src/skf-analyze-source/references/scan-project.md +5 -2
- package/src/skf-analyze-source/references/step-auto-scope.md +525 -0
- package/src/skf-analyze-source/references/step-shape-detect.md +79 -0
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +16 -0
- package/src/skf-audit-skill/SKILL.md +1 -0
- package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
- package/src/skf-audit-skill/references/report.md +4 -0
- package/src/skf-audit-skill/references/severity-classify.md +1 -1
- package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
- package/src/skf-brief-skill/SKILL.md +7 -3
- package/src/skf-brief-skill/references/gather-intent.md +14 -0
- package/src/skf-brief-skill/references/step-auto-brief.md +171 -0
- package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
- package/src/skf-create-skill/SKILL.md +3 -0
- package/src/skf-create-skill/assets/skill-sections.md +2 -0
- package/src/skf-create-skill/references/compile.md +1 -1
- package/src/skf-create-skill/references/generate-artifacts.md +5 -10
- package/src/skf-create-skill/references/step-auto-shard.md +110 -0
- package/src/skf-create-skill/references/step-doc-rot.md +109 -0
- package/src/skf-create-skill/references/step-doc-sources.md +114 -0
- package/src/skf-create-stack-skill/references/generate-output.md +5 -5
- package/src/skf-forger/SKILL.md +8 -2
- package/src/skf-test-skill/SKILL.md +8 -7
- package/src/skf-test-skill/customize.toml +2 -1
- package/src/skf-test-skill/references/external-validators.md +1 -1
- package/src/skf-test-skill/references/init.md +16 -1
- package/src/skf-test-skill/references/report.md +5 -1
- package/src/skf-test-skill/references/score.md +95 -6
- package/src/skf-test-skill/references/source-access-protocol.md +14 -0
- package/src/skf-test-skill/references/step-hard-gate.md +73 -0
- 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()
|