bmad-module-skill-forge 1.8.0 → 2.0.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 (136) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +10 -5
  3. package/docs/_data/pinned.yaml +2 -2
  4. package/docs/agents.md +11 -2
  5. package/docs/architecture.md +5 -4
  6. package/docs/bmad-synergy.md +42 -3
  7. package/docs/campaign.md +172 -0
  8. package/docs/examples.md +7 -3
  9. package/docs/forge-auto.md +90 -0
  10. package/docs/getting-started.md +9 -0
  11. package/docs/how-it-works.md +6 -4
  12. package/docs/index.md +4 -3
  13. package/docs/skill-model.md +1 -1
  14. package/docs/troubleshooting.md +17 -1
  15. package/docs/verifying-a-skill.md +9 -3
  16. package/docs/why-skf.md +1 -1
  17. package/docs/workflows.md +73 -27
  18. package/package.json +2 -2
  19. package/src/module-help.csv +2 -1
  20. package/src/shared/_known-workarounds.yaml +155 -0
  21. package/src/shared/data/language-corpora.json +32 -0
  22. package/src/shared/references/pipeline-contracts.md +14 -2
  23. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -1
  24. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  25. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  26. package/src/shared/scripts/skf-detect-docs.py +417 -0
  27. package/src/shared/scripts/skf-detect-language.py +5 -3
  28. package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
  29. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  30. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  31. package/src/shared/scripts/skf-language-corpora.py +100 -0
  32. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  33. package/src/shared/scripts/skf-preapply.py +192 -0
  34. package/src/shared/scripts/skf-shape-detect.py +1114 -0
  35. package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
  36. package/src/shared/scripts/skf-validate-pins.py +368 -0
  37. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  38. package/src/skf-analyze-source/SKILL.md +26 -20
  39. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  40. package/src/skf-analyze-source/references/init.md +30 -0
  41. package/src/skf-analyze-source/references/step-auto-scope.md +677 -0
  42. package/src/skf-analyze-source/references/step-shape-detect.md +86 -0
  43. package/src/skf-audit-skill/SKILL.md +1 -0
  44. package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
  45. package/src/skf-audit-skill/references/init.md +1 -1
  46. package/src/skf-audit-skill/references/report.md +4 -0
  47. package/src/skf-audit-skill/references/severity-classify.md +1 -1
  48. package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
  49. package/src/skf-brief-skill/SKILL.md +7 -3
  50. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  51. package/src/skf-brief-skill/references/analyze-target.md +7 -4
  52. package/src/skf-brief-skill/references/confirm-brief.md +0 -1
  53. package/src/skf-brief-skill/references/gather-intent.md +22 -2
  54. package/src/skf-brief-skill/references/scope-definition.md +2 -1
  55. package/src/skf-brief-skill/references/step-auto-brief.md +188 -0
  56. package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
  57. package/src/skf-brief-skill/references/write-brief.md +2 -3
  58. package/src/skf-campaign/SKILL.md +190 -0
  59. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  60. package/src/skf-campaign/customize.toml +73 -0
  61. package/src/skf-campaign/manifest.yaml +11 -0
  62. package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
  63. package/src/skf-campaign/references/health-check.md +35 -0
  64. package/src/skf-campaign/references/step-01-setup.md +122 -0
  65. package/src/skf-campaign/references/step-02-strategy.md +97 -0
  66. package/src/skf-campaign/references/step-03-pins.md +56 -0
  67. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  68. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  69. package/src/skf-campaign/references/step-06-batch.md +87 -0
  70. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  71. package/src/skf-campaign/references/step-08-verify.md +75 -0
  72. package/src/skf-campaign/references/step-09-refine.md +83 -0
  73. package/src/skf-campaign/references/step-10-export.md +106 -0
  74. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  75. package/src/skf-campaign/references/step-resume.md +114 -0
  76. package/src/skf-campaign/scripts/.gitkeep +0 -0
  77. package/src/skf-campaign/scripts/campaign-deps.py +235 -0
  78. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  79. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  80. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  81. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  82. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  83. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  84. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  85. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  86. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  87. package/src/skf-create-skill/SKILL.md +4 -1
  88. package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
  89. package/src/skf-create-skill/assets/skill-sections.md +2 -0
  90. package/src/skf-create-skill/references/compile.md +5 -2
  91. package/src/skf-create-skill/references/extract.md +9 -1
  92. package/src/skf-create-skill/references/extraction-patterns.md +3 -1
  93. package/src/skf-create-skill/references/generate-artifacts.md +11 -2
  94. package/src/skf-create-skill/references/health-check.md +2 -2
  95. package/src/skf-create-skill/references/report.md +17 -1
  96. package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
  97. package/src/skf-create-skill/references/step-auto-shard.md +110 -0
  98. package/src/skf-create-skill/references/step-doc-rot.md +109 -0
  99. package/src/skf-create-skill/references/step-doc-sources.md +123 -0
  100. package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
  101. package/src/skf-create-skill/references/validate.md +12 -3
  102. package/src/skf-drop-skill/SKILL.md +2 -2
  103. package/src/skf-drop-skill/references/execute.md +20 -9
  104. package/src/skf-drop-skill/references/report.md +2 -0
  105. package/src/skf-drop-skill/references/select.md +7 -2
  106. package/src/skf-forger/SKILL.md +15 -4
  107. package/src/skf-refine-architecture/SKILL.md +2 -1
  108. package/src/skf-refine-architecture/references/compile.md +1 -1
  109. package/src/skf-refine-architecture/references/report.md +1 -1
  110. package/src/skf-rename-skill/SKILL.md +2 -2
  111. package/src/skf-rename-skill/references/execute.md +5 -4
  112. package/src/skf-rename-skill/references/rebuild-context.md +2 -2
  113. package/src/skf-rename-skill/references/select.md +3 -3
  114. package/src/skf-setup/SKILL.md +1 -1
  115. package/src/skf-setup/references/auto-index.md +3 -1
  116. package/src/skf-setup/references/detect-and-tier.md +4 -0
  117. package/src/skf-setup/references/report.md +4 -1
  118. package/src/skf-setup/references/tier-rules.md +1 -1
  119. package/src/skf-test-skill/SKILL.md +8 -7
  120. package/src/skf-test-skill/customize.toml +2 -1
  121. package/src/skf-test-skill/references/coverage-check.md +10 -0
  122. package/src/skf-test-skill/references/external-validators.md +1 -1
  123. package/src/skf-test-skill/references/init.md +16 -1
  124. package/src/skf-test-skill/references/report.md +5 -1
  125. package/src/skf-test-skill/references/score.md +95 -6
  126. package/src/skf-test-skill/references/source-access-protocol.md +13 -3
  127. package/src/skf-test-skill/references/step-hard-gate.md +73 -0
  128. package/src/skf-test-skill/scripts/compute-score.py +4 -3
  129. package/src/skf-test-skill/templates/test-report-template.md +1 -0
  130. package/src/skf-update-skill/customize.toml +0 -9
  131. package/src/skf-update-skill/references/detect-changes.md +33 -3
  132. package/src/skf-update-skill/references/health-check.md +2 -2
  133. package/src/skf-update-skill/references/init.md +1 -0
  134. package/src/skf-update-skill/references/re-extract.md +15 -1
  135. package/src/skf-verify-stack/references/report.md +1 -1
  136. package/tools/cli/lib/ui.js +3 -2
@@ -0,0 +1,174 @@
1
+ # /// script
2
+ # requires-python = ">=3.9"
3
+ # dependencies = ["pyyaml"]
4
+ # ///
5
+ """Campaign Validate Pins — validate all version pins in a campaign state file.
6
+
7
+ Campaign-specific wrapper around the shared skf-validate-pins.py module.
8
+ Reads the campaign state and brief, validates every skill's pin against
9
+ real GitHub releases/tags, and outputs consolidated JSON.
10
+
11
+ CLI:
12
+ uv run src/skf-campaign/scripts/campaign-validate-pins.py \
13
+ --state-file <path> --brief-file <path>
14
+
15
+ Input:
16
+ --state-file Path to _campaign-state.yaml
17
+ --brief-file Path to campaign-brief.yaml
18
+
19
+ Output (JSON on stdout):
20
+ {
21
+ "results": [
22
+ {
23
+ "name": "skill-name",
24
+ "status": "valid|invalid|resolved",
25
+ "pin": "input-pin-or-null",
26
+ "resolved_ref": "actual-tag-or-branch",
27
+ "ref_type": "tag|branch|null",
28
+ "version": "semver-or-null",
29
+ "suggestions": []
30
+ }
31
+ ],
32
+ "all_valid": true,
33
+ "invalid_count": 0,
34
+ "resolved_count": 0
35
+ }
36
+
37
+ Exit codes:
38
+ 0 all valid/resolved
39
+ 1 one or more invalid pins
40
+ 2 error (missing files, bad YAML, gh unavailable)
41
+ """
42
+
43
+ from __future__ import annotations
44
+
45
+ import argparse
46
+ import importlib.util
47
+ import json
48
+ import sys
49
+ from pathlib import Path
50
+ from typing import Any, Dict, List
51
+
52
+ import yaml
53
+
54
+ SHARED_SCRIPTS = Path(__file__).parent.parent.parent / "shared" / "scripts"
55
+ VALIDATE_PINS_PATH = SHARED_SCRIPTS / "skf-validate-pins.py"
56
+
57
+
58
+ def _load_validate_pin():
59
+ spec = importlib.util.spec_from_file_location("skf_validate_pins", VALIDATE_PINS_PATH)
60
+ mod = importlib.util.module_from_spec(spec)
61
+ spec.loader.exec_module(mod)
62
+ return mod.validate_pin
63
+
64
+
65
+ def _emit_error(message: str, code: str) -> None:
66
+ json.dump({"error": message, "code": code}, sys.stderr)
67
+ sys.stderr.write("\n")
68
+
69
+
70
+ def _load_yaml(path: Path) -> Any:
71
+ with open(path, encoding="utf-8") as f:
72
+ return yaml.safe_load(f)
73
+
74
+
75
+ def run(state_file: str, brief_file: str) -> int:
76
+ state_path = Path(state_file)
77
+ brief_path = Path(brief_file)
78
+
79
+ if not state_path.is_file():
80
+ _emit_error(f"State file not found: {state_file}", "STATE_NOT_FOUND")
81
+ return 2
82
+
83
+ if not brief_path.is_file():
84
+ _emit_error(f"Brief file not found: {brief_file}", "BRIEF_NOT_FOUND")
85
+ return 2
86
+
87
+ try:
88
+ state = _load_yaml(state_path)
89
+ except Exception as exc:
90
+ _emit_error(f"Failed to parse state file: {exc}", "STATE_PARSE_ERROR")
91
+ return 2
92
+
93
+ try:
94
+ brief = _load_yaml(brief_path)
95
+ except Exception as exc:
96
+ _emit_error(f"Failed to parse brief file: {exc}", "BRIEF_PARSE_ERROR")
97
+ return 2
98
+
99
+ skills = state.get("skills", [])
100
+ if not isinstance(skills, list):
101
+ _emit_error("State file 'skills' is not an array", "INVALID_STATE")
102
+ return 2
103
+
104
+ targets = brief.get("targets", [])
105
+ if not isinstance(targets, list):
106
+ _emit_error("Brief file 'targets' is not an array", "INVALID_BRIEF")
107
+ return 2
108
+
109
+ name_to_repo: Dict[str, str] = {}
110
+ for target in targets:
111
+ name_to_repo[target["name"]] = target["repo_url"]
112
+
113
+ if not VALIDATE_PINS_PATH.is_file():
114
+ _emit_error(
115
+ f"Shared module not found: {VALIDATE_PINS_PATH.as_posix()}",
116
+ "SHARED_MODULE_NOT_FOUND",
117
+ )
118
+ return 2
119
+
120
+ validate_pin = _load_validate_pin()
121
+
122
+ results: List[Dict[str, Any]] = []
123
+ invalid_count = 0
124
+ resolved_count = 0
125
+
126
+ for skill in skills:
127
+ skill_name = skill["name"]
128
+ repo_url = name_to_repo.get(skill_name)
129
+ if repo_url is None:
130
+ _emit_error(
131
+ f"Skill '{skill_name}' not found in brief targets",
132
+ "SKILL_NOT_IN_BRIEF",
133
+ )
134
+ return 2
135
+
136
+ pin = skill.get("pin")
137
+ result = validate_pin(repo_url, pin=pin)
138
+ result["name"] = skill_name
139
+
140
+ if result["status"] == "invalid":
141
+ invalid_count += 1
142
+ elif result["status"] == "resolved":
143
+ resolved_count += 1
144
+
145
+ results.append(result)
146
+
147
+ all_valid = invalid_count == 0
148
+ output = {
149
+ "results": results,
150
+ "all_valid": all_valid,
151
+ "invalid_count": invalid_count,
152
+ "resolved_count": resolved_count,
153
+ }
154
+
155
+ json.dump(output, sys.stdout, separators=(",", ":"))
156
+ sys.stdout.write("\n")
157
+
158
+ if not all_valid:
159
+ return 1
160
+ return 0
161
+
162
+
163
+ def main() -> int:
164
+ parser = argparse.ArgumentParser(
165
+ description="Validate all version pins in a campaign state file.",
166
+ )
167
+ parser.add_argument("--state-file", required=True, help="Path to _campaign-state.yaml")
168
+ parser.add_argument("--brief-file", required=True, help="Path to campaign-brief.yaml")
169
+ args = parser.parse_args()
170
+ return run(args.state_file, args.brief_file)
171
+
172
+
173
+ if __name__ == "__main__":
174
+ raise SystemExit(main())
@@ -0,0 +1,207 @@
1
+ # /// script
2
+ # requires-python = ">=3.9"
3
+ # dependencies = ["pyyaml", "jsonschema>=4.0"]
4
+ # ///
5
+ """Campaign Validate State — schema check for _campaign-state.yaml on disk.
6
+
7
+ Replaces the per-step "mentally validate the loaded state against the schema"
8
+ prose with a deterministic check. Every campaign step that loads state runs
9
+ this once on entry instead of asking the LLM to validate a draft-07 schema
10
+ (nested objects, enums, additionalProperties:false, date-time) by hand — the
11
+ exact check an LLM does unreliably and which, when wrong, silently corrupts a
12
+ multi-session campaign.
13
+
14
+ Loads the campaign state YAML, validates it against
15
+ `assets/campaign-state-schema.json` (resolved relative to this script unless
16
+ `--schema-file` overrides), and emits skill-friendly error records the calling
17
+ step can forward verbatim.
18
+
19
+ CLI:
20
+ uv run campaign-validate-state.py --state-file <path>
21
+ uv run campaign-validate-state.py --state-file <path> --schema-file <path>
22
+
23
+ Output (JSON on stdout):
24
+ {
25
+ "valid": bool,
26
+ "errors": [{"field": "campaign.current_stage", "message": "..."}, ...],
27
+ "halt_reason": "state-missing" | "state-malformed" | "state-invalid" | null
28
+ }
29
+
30
+ Exit codes:
31
+ 0 valid (errors empty)
32
+ 1 invalid (schema violations) OR file/yaml load failed
33
+ 2 configuration error (schema file missing or unreadable)
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import argparse
39
+ import datetime
40
+ import json
41
+ import sys
42
+ from pathlib import Path
43
+ from typing import Any
44
+
45
+ import yaml
46
+ from jsonschema import Draft7Validator
47
+
48
+ DEFAULT_SCHEMA_PATH = Path(__file__).resolve().parent.parent / "assets" / "campaign-state-schema.json"
49
+
50
+
51
+ def _emit(envelope: dict) -> None:
52
+ json.dump(envelope, sys.stdout, separators=(",", ":"), default=str)
53
+ sys.stdout.write("\n")
54
+
55
+
56
+ def _load_yaml_text(text: str) -> tuple[Any, str | None]:
57
+ try:
58
+ data = yaml.safe_load(text)
59
+ except yaml.YAMLError as exc:
60
+ return None, f"State is not valid YAML: {exc}"
61
+ if data is None:
62
+ return None, "State file is empty"
63
+ if not isinstance(data, dict):
64
+ return None, f"State root must be a YAML mapping; got {type(data).__name__}"
65
+ return data, None
66
+
67
+
68
+ def _field_path(error_path) -> str:
69
+ parts: list[str] = []
70
+ for p in error_path:
71
+ if isinstance(p, int):
72
+ parts.append(f"[{p}]")
73
+ else:
74
+ parts.append(f".{p}" if parts else str(p))
75
+ return "".join(parts) or "(root)"
76
+
77
+
78
+ def _translate(err) -> dict:
79
+ field = _field_path(err.absolute_path)
80
+ validator = err.validator
81
+ inst = err.instance
82
+
83
+ if validator == "required":
84
+ missing = err.message.split("'")[1] if "'" in err.message else "(unknown)"
85
+ return {
86
+ "field": missing,
87
+ "message": f"State validation failed: missing required field `{missing}`.",
88
+ }
89
+ if validator == "enum":
90
+ return {
91
+ "field": field,
92
+ "message": (
93
+ f"State validation failed: `{field}` value `{inst}` is not one of "
94
+ f"{err.validator_value}."
95
+ ),
96
+ }
97
+ if validator == "type":
98
+ if isinstance(inst, datetime.date) and "string" in str(err.validator_value):
99
+ return {
100
+ "field": field,
101
+ "message": (
102
+ f"State validation failed: `{field}` parsed as a YAML date, not a "
103
+ f"string. Quote it (e.g. `'2026-05-01T00:00:00Z'`)."
104
+ ),
105
+ }
106
+ return {
107
+ "field": field,
108
+ "message": (
109
+ f"State validation failed: `{field}` has type `{type(inst).__name__}`, "
110
+ f"expected `{err.validator_value}`."
111
+ ),
112
+ }
113
+ if validator == "additionalProperties":
114
+ return {
115
+ "field": field,
116
+ "message": f"State validation failed: `{field}` — {err.message}",
117
+ }
118
+ if validator in ("minimum", "maximum"):
119
+ return {
120
+ "field": field,
121
+ "message": f"State validation failed: `{field}` value `{inst}` violates {validator} `{err.validator_value}`.",
122
+ }
123
+ return {
124
+ "field": field,
125
+ "message": f"State validation failed: `{field}` — {err.message}",
126
+ }
127
+
128
+
129
+ def validate_state(state: dict, schema: dict) -> dict:
130
+ validator = Draft7Validator(schema)
131
+ errors = [
132
+ _translate(err)
133
+ for err in sorted(validator.iter_errors(state), key=lambda e: list(e.absolute_path))
134
+ ]
135
+ return {"valid": not errors, "errors": errors}
136
+
137
+
138
+ def run(state_file: str, schema_file: str | None = None) -> int:
139
+ schema_path = Path(schema_file) if schema_file else DEFAULT_SCHEMA_PATH
140
+ if not schema_path.is_file():
141
+ _emit(
142
+ {
143
+ "valid": False,
144
+ "errors": [{"field": "(schema)", "message": f"Schema not found at `{schema_path}`."}],
145
+ "halt_reason": "state-invalid",
146
+ }
147
+ )
148
+ return 2
149
+ try:
150
+ schema = json.loads(schema_path.read_text(encoding="utf-8"))
151
+ except (OSError, ValueError) as exc:
152
+ _emit(
153
+ {
154
+ "valid": False,
155
+ "errors": [{"field": "(schema)", "message": f"Schema unreadable: {exc}"}],
156
+ "halt_reason": "state-invalid",
157
+ }
158
+ )
159
+ return 2
160
+
161
+ state_path = Path(state_file)
162
+ if not state_path.is_file():
163
+ _emit(
164
+ {
165
+ "valid": False,
166
+ "errors": [{"field": "(file)", "message": f"State not found at `{state_path}`."}],
167
+ "halt_reason": "state-missing",
168
+ }
169
+ )
170
+ return 1
171
+
172
+ state, load_err = _load_yaml_text(state_path.read_text(encoding="utf-8"))
173
+ if load_err is not None:
174
+ _emit(
175
+ {
176
+ "valid": False,
177
+ "errors": [{"field": "(file)", "message": load_err}],
178
+ "halt_reason": "state-malformed",
179
+ }
180
+ )
181
+ return 1
182
+
183
+ result = validate_state(state, schema)
184
+ if not result["valid"]:
185
+ _emit({**result, "halt_reason": "state-invalid"})
186
+ return 1
187
+
188
+ _emit({**result, "halt_reason": None})
189
+ return 0
190
+
191
+
192
+ def main(argv: list[str] | None = None) -> int:
193
+ parser = argparse.ArgumentParser(
194
+ prog="campaign-validate-state",
195
+ description="Validate _campaign-state.yaml against the campaign state schema.",
196
+ )
197
+ parser.add_argument("--state-file", required=True, help="Path to _campaign-state.yaml")
198
+ parser.add_argument(
199
+ "--schema-file",
200
+ help="Path to campaign-state-schema.json (defaults to the bundled schema)",
201
+ )
202
+ args = parser.parse_args(argv)
203
+ return run(args.state_file, args.schema_file)
204
+
205
+
206
+ if __name__ == "__main__":
207
+ raise SystemExit(main())
@@ -0,0 +1,34 @@
1
+ # Machine-generated campaign brief — resume context for fresh sessions (FR-35)
2
+ # Populated by step-01-setup.md from operator inputs
3
+
4
+ # Campaign identifier
5
+ campaign_name: ""
6
+
7
+ # ISO-8601 timestamp of campaign creation
8
+ created_at: ""
9
+
10
+ # Target skills for this campaign run
11
+ targets: []
12
+ # Each target entry:
13
+ # name: ""
14
+ # repo_url: ""
15
+ # tier: "A" # A (full pipeline) or B (batch)
16
+ # pin: null # version pin, or null for latest
17
+ # depends_on: [] # list of skill names this target depends on
18
+
19
+ # Quality thresholds applied to all skills in this campaign
20
+ quality_gate:
21
+ hard: "zero-critical-high"
22
+ soft_target: 90
23
+ soft_fallback: 80
24
+
25
+ # Where health findings are routed: "local" (project-local) or "improvement" (shared queue)
26
+ health_findings_queue: "local"
27
+
28
+ # Optional path to the architecture document the verify (Stage 7) and refine
29
+ # (Stage 8) stages consume. Empty string means those stages discover it at
30
+ # runtime (docs/architecture.md, then _bmad-output/planning-artifacts/architecture.md).
31
+ architecture_doc_path: ""
32
+
33
+ # Operator-provided context or directives for this campaign
34
+ notes: ""
@@ -0,0 +1,54 @@
1
+ # Campaign Report — {{campaign_name}}
2
+
3
+ ## Campaign Summary
4
+
5
+ | Field | Value |
6
+ |-------|-------|
7
+ | **Campaign** | {{campaign_name}} |
8
+ | **Started** | {{started_at}} |
9
+ | **Completed** | {{completed_at}} |
10
+ | **Duration** | {{duration}} |
11
+ | **Quality Gate (Hard)** | {{quality_gate_hard}} |
12
+ | **Quality Gate (Soft Target)** | {{quality_gate_soft_target}} |
13
+ | **Quality Gate (Soft Fallback)** | {{quality_gate_soft_fallback}} |
14
+ | **Skills Completed** | {{skills_completed}} |
15
+ | **Skills Failed** | {{skills_failed}} |
16
+ | **Skills Skipped** | {{skills_skipped}} |
17
+
18
+ ## Skills Overview
19
+
20
+ | Name | Tier | Status | Quality Score | Pin | Workarounds |
21
+ |------|------|--------|---------------|-----|-------------|
22
+ {{skills_table}}
23
+
24
+ ## Quality Scores
25
+
26
+ | Metric | Value |
27
+ |--------|-------|
28
+ | **Minimum** | {{quality_min}} |
29
+ | **Maximum** | {{quality_max}} |
30
+ | **Average** | {{quality_avg}} |
31
+
32
+ ### Per-Skill Breakdown
33
+
34
+ {{quality_breakdown}}
35
+
36
+ ## Findings Summary
37
+
38
+ - **Total workarounds applied:** {{total_workarounds}}
39
+ - **Skills with workarounds:** {{skills_with_workarounds}}
40
+ - **Doc-rot corrections:** tracked per-skill in health-check findings (not aggregated in campaign state)
41
+
42
+ ## Workarounds Applied
43
+
44
+ {{workarounds_list}}
45
+
46
+ ## Duration Breakdown
47
+
48
+ | Skill | Started | Completed | Duration |
49
+ |-------|---------|-----------|----------|
50
+ {{duration_table}}
51
+
52
+ ## Failed / Skipped Skills
53
+
54
+ {{failed_skipped_section}}
@@ -0,0 +1,48 @@
1
+ # Skill Kickoff — {{skill_name}}
2
+
3
+ ## Campaign Context
4
+
5
+ - **Campaign:** {{campaign_name}}
6
+ - **Current Stage:** {{current_stage}}
7
+ - **Quality Gate:** {{quality_gate_summary}}
8
+
9
+ ## Skill Identity
10
+
11
+ - **Skill:** {{skill_name}}
12
+ - **Tier:** {{skill_tier}}
13
+ - **Repository:** {{repo_url}}
14
+ - **Pin:** {{pin}}
15
+ - **Commit:** {{commit_sha}}
16
+
17
+ ## Brief Summary
18
+
19
+ {{brief_summary}}
20
+
21
+ ## Campaign Facts
22
+
23
+ {{persistent_facts}}
24
+
25
+ ## Dependency State
26
+
27
+ {{dependency_status_table}}
28
+
29
+ ## Standing Directive
30
+
31
+ {{directive_content}}
32
+
33
+ ## Workarounds Applied
34
+
35
+ {{workarounds_list}}
36
+
37
+ ## Pipeline Instructions
38
+
39
+ Execute the standard forge pipeline for **{{skill_name}}**:
40
+
41
+ 1. **AN** (Analyze) — scope and source intelligence
42
+ 2. **BS** (Brief Synthesis) — generate or validate the skill brief
43
+ 3. **CS** (Compile Skill) — compile the SKILL.md artifact
44
+ 4. **TS** (Test Skill) — run health-check validation
45
+
46
+ **Parameters:**
47
+ - Pin: {{pin}}
48
+ - Quality target: {{quality_gate_summary}}
@@ -7,7 +7,7 @@ description: Compile a skill from a brief. Supports --batch for multiple briefs.
7
7
 
8
8
  ## Overview
9
9
 
10
- Compiles a verified agent skill from a skill-brief.yaml and source code, producing an agentskills.io-compliant SKILL.md with provenance map, evidence report, and progressive disclosure references. The workflow is mostly autonomous with three interaction points — after ecosystem check (if match found), after source extraction (to confirm findings), and after content quality review (when tessl produces suggestions). Steps adapt behavior based on forge tier (Quick/Forge/Forge+/Deep). Zero hallucination tolerance: every instruction in the output must trace to source code with a confidence tier citation.
10
+ Compiles a verified agent skill from a skill-brief.yaml and source code, producing an agentskills.io-compliant SKILL.md with provenance map, evidence report, and progressive disclosure references. The workflow is mostly autonomous with three interaction points — after ecosystem check (if match found), after source extraction (to confirm findings), and after content quality review (when tessl produces suggestions). Steps adapt behavior based on forge tier (Quick/Forge/Forge+/Deep). Zero hallucination tolerance: every instruction in the output must trace to source code with a confidence tier citation. A single run is not resumable — if it is interrupted mid-compile, re-run from the brief (only `--batch` checkpoints progress across briefs).
11
11
 
12
12
  ## Conventions
13
13
 
@@ -43,6 +43,9 @@ These rules apply to every step in this workflow:
43
43
  | 3d | Component Extraction | references/component-extraction.md | Conditional |
44
44
  | 4 | Enrich | references/enrich.md | Yes |
45
45
  | 5 | Compile | references/compile.md | Yes |
46
+ | 5a | Doc Sources | references/step-doc-sources.md | Yes |
47
+ | 5b | Auto-Shard | references/step-auto-shard.md | Yes |
48
+ | 5c | Doc-Rot | references/step-doc-rot.md | Yes |
46
49
  | 6 | Validate | references/validate.md | Conditional |
47
50
  | 7 | Generate Artifacts | references/generate-artifacts.md | Yes |
48
51
  | 8 | Report | references/report.md | Yes |
@@ -290,6 +290,28 @@ Replace per-function subsections with `references/pattern-*.md` groupings: one r
290
290
 
291
291
  **When this clause does NOT apply:** `full-library`, `specific-modules`, `public-api`, `component-library`, or `docs-only`. Those scope types have their own assembly semantics and export-count conventions — do not mix.
292
292
 
293
+ ### Whole-Language Reference Assembly Overrides
294
+
295
+ A **whole-language reference** is a compiler/interpreter repo (rustc, CPython, the Go toolchain, TypeScript) enriched with the language's canonical prose — the guide/Book and the standard/library docs. It maps to `scope.type: full-library`, but its value to a skill consumer is the **language**, not the compiler's internal exports. The standard library-export layout above would foreground compiler-internal signatures and bury the prose; these overrides invert that.
296
+
297
+ **GATE — apply ONLY when this is a whole-language reference.** The brief carries ≥1 `doc_urls` entry with `source: language-registry` (equivalently, `skf-derive-assembly-shape.py` returns `assembly_shape: "whole-language-reference"`). This is a structured, schema-validated signal — NOT a `scope.notes` substring — so an ordinary `full-library` library, a parser *library* (pest/lalrpop — its code IS the product, so §6b seeds no corpora), a component-library, a reference-app (including the language/spec-reference DSL sub-shape, which stays `scope.type: reference-app` and is handled by the reference-app override above), and a docs-only brief all fail the gate. When the gate does NOT fire, **skip this entire section** — the standard Tier 1 (sections 1–8) and Tier 2 (9–11) layout and the signature-fidelity rule run unchanged, so non-whole-language skills assemble byte-identically.
298
+
299
+ When the gate fires, the assembler has a `language_guide[]` artifact from step 3c §4a (the retained corpora prose, carved out of the T3-vs-T1 conflict rule). Apply these overrides:
300
+
301
+ **Empty-guide guard (check first):** If `language_guide[]` is absent or every entry's `prose` is null (all registry corpora failed to fetch), do NOT emit a thin Language Guide above a full internals section — fall back to the standard layout and record a warning in `evidence-report.md`: "Whole-language reference gated but no Language-Guide prose was retained — assembled as a standard library skill." This prevents the inverse-value outcome (prose section empty, compiler internals dominant).
302
+
303
+ **New Section 1b — Language Guide (Tier 1, foregrounded):** Immediately after Section 1 (Overview) and BEFORE Quick Start, emit the skill's primary content from `language_guide[]`. One subsection per corpus (`{label}`), carrying the retained prose — language concepts, idioms, and usage examples — each block cited `[EXT:{url}]`. This is the section an agent reads to learn the language; it survives split-body as Tier 1.
304
+
305
+ **Section 2 (Quick Start) — language-usage override:** Build the runnable examples from the Language-Guide prose (writing and running code *in* the language — a minimal program, a common idiom), cited `[EXT:{url}]`, NOT from compiler-internal export call chains. Fall back to the standard signature-only Quick Start only if the guide yields no examples.
306
+
307
+ **Section 3 (Common Workflows) — "Writing {language}" override:** Replace function-call-chain workflows over compiler exports with common language tasks (define a type, handle errors, organize a module), each a short idiom from the guide.
308
+
309
+ **Section 4 (Key API Summary) — demote compiler internals:** Replace the top-of-body compiler-export function table with a one-paragraph "Standard Library & Language Surface" pointer into the Language Guide. Move the AST/compiler-internal exports into a late-ordered Tier 2 subsection `### Compiler Internals (reference only)` with a one-line note: "Internal implementation surface of the {language} toolchain — most consumers want the Language Guide above, not these."
310
+
311
+ **Signature fidelity is preserved (NOT relaxed):** this is an ordering/prominence change only. Any compiler signatures that DO appear (in Compiler Internals) keep their T1/AST-authoritative params and return types per Section 1b of `compile.md` and rule 7 below. The override never substitutes prose-derived signatures for AST-extracted ones; it changes which content leads, not which tier wins a per-export signature conflict.
312
+
313
+ **metadata.json:** the skill stays `scope.type: full-library`; export-count stats are still emitted. (A whole-language skill's low public-API coverage versus the full compiler surface is expected — coverage-gate semantics for this shape are tracked separately and out of scope here.)
314
+
293
315
  ### Assembly Rules
294
316
 
295
317
  1. Assemble all Tier 1 sections first — these form the essential standalone body
@@ -230,6 +230,8 @@ Indexed pipe-delimited format for CLAUDE.md managed section (~80-120 tokens per
230
230
  // scripts[] and assets[] — include ONLY when inventories are non-empty; omit entirely otherwise
231
231
  // "scripts": [{ "file": "scripts/{name}", "purpose": "{description}", "source_file": "{source-path}", "confidence": "T1-low" }],
232
232
  // "assets": [{ "file": "assets/{name}", "purpose": "{description}", "source_file": "{source-path}", "confidence": "T1-low" }],
233
+ // doc_sources[] — include ONLY when doc detection ran; omit if detection was skipped entirely
234
+ // "doc_sources": [{ "url": "https://...", "detected_via": "homepageUrl|readme_link|pages_api|docs_folder|readme_always", "content_hash": "sha256:{hex}|null", "recorded_at": "ISO-8601" }],
233
235
  "generated_by": "{quick-skill|create-skill}",
234
236
  "dependencies": [],
235
237
  "compatibility": "{semver-range}"
@@ -1,5 +1,5 @@
1
1
  ---
2
- nextStepFile: 'validate.md'
2
+ nextStepFile: 'step-doc-sources.md'
3
3
  skillSectionsData: 'assets/skill-sections.md'
4
4
  assemblyRulesData: 'assets/compile-assembly-rules.md'
5
5
  ---
@@ -54,6 +54,8 @@ This rule applies to ALL sections including Tier 1 Key API Summary, Tier 2 Full
54
54
 
55
55
  Assemble each section in order using the assembly rules data file (`{assemblyRulesData}`). The data file specifies frontmatter format, Tier 1 section details (Sections 1-8, including conditional Section 7b for scripts/assets), Tier 2 section details (Sections 9-11), and assembly ordering rules. Follow it exactly. Assemble Section 7b (Scripts & Assets) only if `scripts_inventory` or `assets_inventory` is non-empty.
56
56
 
57
+ **Shape-specific overrides:** the assembly-rules file defines gated override blocks for `component-library`, `reference-app`, and **whole-language reference** skills. Apply the override whose gate matches this brief. A whole-language reference is gated on `assembly_shape: "whole-language-reference"` (a `doc_urls` entry with `source: language-registry`, as step 3c §1 determined and as `skf-derive-assembly-shape.py` reports) and uses the `language_guide[]` artifact step 3c §4a retained — it foregrounds the Language Guide and demotes compiler internals. When no override gate matches, assemble the standard library-export layout unchanged.
58
+
57
59
  ### 2a. Description Sanitization Pass
58
60
 
59
61
  **Before writing SKILL.md frontmatter to disk**, sanitize the assembled `description` string by replacing every `<` with `{` and every `>` with `}`. Apply this pass unconditionally to the final assembled description in context, then write the result to `SKILL.md`.
@@ -115,7 +117,8 @@ Following the structure from the skill-sections data file:
115
117
 
116
118
  Otherwise omit the field entirely — when absent, `skf-test-skill` falls back to `exports_public_api`. See `skf-test-skill` `references/source-access-protocol.md` §Source API Surface Definition ("Stratified-scope monorepo packages") for the test-side consumption rules.
117
119
 
118
- **Reference-app carve-out:** never emit `effective_denominator` for `scope.type: "reference-app"`, even when the three conditions above are literally satisfied (a reference-app-in-monorepo matches all of them). A reference app's coverage basis is `pattern_surfaces_documented`, not library exports — see the Reference-App stats semantics in `{compileAssemblyRules}`.
120
+ **Reference-app carve-out:** never emit `effective_denominator` for `scope.type: "reference-app"`, even when the three conditions above are literally satisfied (a reference-app-in-monorepo matches all of them). A reference app's coverage basis is `pattern_surfaces_documented`, not library exports — see the Reference-App stats semantics in `assets/compile-assembly-rules.md`.
121
+ - Set `confidence_distribution` by binning **each documented export exactly once** by its provenance-map `signature_source` tier (`T1` → `t1`, `T1-low` → `t1_low`, `T2` → `t2`, `T3` → `t3`) — the four bins MUST sum to `stats.exports_documented`. Do NOT add T2 annotation counts or T3 doc-item counts on top of the per-export bins; those are enrichment totals and belong in the evidence report's Extraction Summary, not in the distribution. `skf-test-skill` coverage-check §4b enforces this sum as an internal-consistency defect. Carve-outs (mirroring that consumer): a **stack** skill bins the cited constituents (sums to the constituent count, not `exports_documented`); a **reference-app** bins per-citation provenance entries (sums to the citation count, not `pattern_surfaces_documented`)
119
122
  - Set `description` from the SKILL.md frontmatter `description` field (already assembled in section 2)
120
123
  - Set `language` from source analysis (e.g., `"typescript"`, `"python"`) — use the primary language of the entry point file
121
124
  - Set `ast_node_count` from extraction stats if ast-grep was used (Forge/Deep tier), otherwise omit
@@ -55,7 +55,7 @@ Load `{extractionPatternsData}` completely. Identify the strategy for the curren
55
55
 
56
56
  From the brief, apply scope and pattern filters:
57
57
 
58
- - `scope.type` — determines what to extract (e.g., `full-library`, `specific-modules`, `public-api`, `component-library`, `reference-app`, `docs-only`). Use `reference-app` when the source is a whole app and the skill's value is wiring patterns rather than public exports (embedded-sidecar reference apps, CLI-demo repos, integration-pattern demonstrators). `reference-app` triggers the compile-assembly overrides in `{compileAssemblyRules}` that replace "Key API Summary" with a "Pattern Surface" section and make `stats.exports_documented` semantics pattern-oriented. Do NOT pick `full-library` for reference apps — downstream assembly will remap wiring onto export slots, producing fuzzy counts and an awkward SKILL.md.
58
+ - `scope.type` — determines what to extract (e.g., `full-library`, `specific-modules`, `public-api`, `component-library`, `reference-app`, `docs-only`). Use `reference-app` when the source is a whole app and the skill's value is wiring patterns rather than public exports (embedded-sidecar reference apps, CLI-demo repos, integration-pattern demonstrators). `reference-app` triggers the compile-assembly overrides in `assets/compile-assembly-rules.md` that replace "Key API Summary" with a "Pattern Surface" section and make `stats.exports_documented` semantics pattern-oriented. Do NOT pick `full-library` for reference apps — downstream assembly will remap wiring onto export slots, producing fuzzy counts and an awkward SKILL.md.
59
59
  - `scope.include` — file globs to include
60
60
  - `scope.exclude` — file globs to exclude
61
61
 
@@ -274,6 +274,14 @@ Compile all extracted data into a structured inventory:
274
274
 
275
275
  **Docs-only note:** If `docs_only_mode` is active (`extraction_mode: "docs-only"`), display a brief note explaining that T3 content will be added by the doc-fetcher step (step 3c), then auto-proceed past this gate. Example: "Docs-only mode: extraction inventory is empty. Documentation content will be fetched from `doc_urls` in step 3c. Auto-proceeding."
276
276
 
277
+ **Zero-export sanity check (source mode):** If `extraction_mode != "docs-only"` AND `export_count == 0` AND the brief declares no `doc_urls`, an empty extraction is almost always an error — a wrong branch/tag, an over-narrow `scope.include`, or a failed AST run — not a valid empty surface. Do NOT let this sail through to a green report. Surface a distinct warning at the gate:
278
+
279
+ "**⚠️ Zero public exports extracted.** A source-type brief produced no documented surface and declares no `doc_urls`. This usually means a scope/branch/tag mismatch (wrong `target_version`, over-narrow `scope.include`) or a failed AST run — the compiled skill would document nothing. Verify the brief's source ref and scope before continuing.
280
+
281
+ **[C] Continue anyway** — compile an empty surface (default)"
282
+
283
+ Under `{headless_mode}`, do NOT auto-pass silently: set `status: "partial"` and `summary.warning: "zero-exports"` on the result contract (carried to step 8's record), log `"headless: zero public exports extracted — likely scope/branch/tag mismatch, continuing"`, append a `headless_decisions[]` entry `{step: "extract", gate: "zero-exports", decision: "C", rationale: "headless mode — zero exports, no human to confirm scope/ref", timestamp: {ISO}}`, and proceed. The distinct warning string surfaces the worst kind of failure (looks green, isn't) where a human or automator can act on it.
284
+
277
285
  Display the extraction findings for user confirmation:
278
286
 
279
287
  "**Extraction complete.**
@@ -287,7 +287,7 @@ rule:
287
287
  pattern: 'export class $NAME { $$$ }'
288
288
  ```
289
289
 
290
- > **Important:** The body (`{ $$$ }`) is required on ast-grep 0.42.x. The bare `export class $NAME` pattern returns zero matches — and emits a `Pattern contains an ERROR node` warning — through **both** `find_code()` and the CLI, because an incomplete class declaration does not parse as a complete statement (see Known Limitation #9). With the body present, the simple `find_code()` pattern detects class exports reliably; `find_code_by_rule` would additionally require an explicit AST `kind` rule.
290
+ > **Important:** The body (`{ $$$ }`) is required on ast-grep 0.42.x. The bare `export class $NAME` pattern returns zero matches — and emits a `Pattern contains an ERROR node` warning — through **both** `find_code()` and the CLI, because an incomplete class declaration does not parse as a complete statement (see Known Limitation #9). With the body present, the simple `find_code()` pattern detects class exports reliably — it matches non-generic classes only; generic (`export class $NAME<T>`) and generic-extends (`export class $NAME<T> extends $BASE`) forms are skipped, so source-read them via `^export (abstract )?class` and merge by name+file (see Known Limitation #10). `find_code_by_rule` would additionally require an explicit AST `kind` rule.
291
291
 
292
292
  **JavaScript/TypeScript — re-export detection (use `find_code`):**
293
293
 
@@ -411,6 +411,8 @@ When using ast-grep for extraction, be aware of these documented limitations:
411
411
 
412
412
  Never silently accept zero results for a declaration form the source language commonly uses.
413
413
 
414
+ 10. **Generic class declarations do not match non-generic class patterns (ast-grep 0.42.x):** The non-generic patterns `export class $NAME { $$$ }` and `export class $NAME extends $BASE { $$$ }` silently skip every generic form on ast-grep 0.42.2 — verified via both the CLI (`ast-grep run -p ... -l typescript`) and `find_code()`, the first matched only a plain `Plain` and the second only a plain `PlainExtends` on a fixture of `Plain` / `Generic<T>` / `GenericExtends<T> extends Base<T>` / `abstract AbstractGeneric<T>` / `PlainExtends`. The per-shape generic patterns `export class $NAME<$$$P> { $$$ }`, `export class $NAME<$$$P> extends $BASE { $$$ }`, and `export abstract class $NAME<$$$P> { $$$ }` each match exactly **one** shape — no single pattern covers all of them. **Workaround:** Always run a source-read fallback `^export (abstract )?class` over the in-scope `.ts` sources (T1-low) and merge by name+file with the AST results, mirroring the `export function` guidance in #9. The bare `class $NAME` pattern catches every form but over-captures non-exported classes, so it still needs the source-read pass to re-impose the exported-only filter — this is the opposite trade from the Python #7 `^[^_]` re-filter, since TS has no name-prefix convention for exports. Never accept a class inventory that omits generic classes when the source uses them.
415
+
414
416
  ### Component Library Demo/Example Auto-Exclusion
415
417
 
416
418
  When `scope.type: "component-library"`, auto-detect and propose demo/example exclusions before extraction begins. **User confirmation is required before applying** — some `examples/` directories contain API-level code.