bmad-module-skill-forge 1.8.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 (43) 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/skf-detect-docs.py +412 -0
  12. package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
  13. package/src/shared/scripts/skf-preapply.py +192 -0
  14. package/src/shared/scripts/skf-shape-detect.py +563 -0
  15. package/src/shared/scripts/skf-validate-pins.py +368 -0
  16. package/src/skf-analyze-source/SKILL.md +26 -20
  17. package/src/skf-analyze-source/references/init.md +30 -0
  18. package/src/skf-analyze-source/references/step-auto-scope.md +525 -0
  19. package/src/skf-analyze-source/references/step-shape-detect.md +79 -0
  20. package/src/skf-audit-skill/SKILL.md +1 -0
  21. package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
  22. package/src/skf-audit-skill/references/report.md +4 -0
  23. package/src/skf-audit-skill/references/severity-classify.md +1 -1
  24. package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
  25. package/src/skf-brief-skill/SKILL.md +7 -3
  26. package/src/skf-brief-skill/references/gather-intent.md +14 -0
  27. package/src/skf-brief-skill/references/step-auto-brief.md +171 -0
  28. package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
  29. package/src/skf-create-skill/SKILL.md +3 -0
  30. package/src/skf-create-skill/assets/skill-sections.md +2 -0
  31. package/src/skf-create-skill/references/compile.md +1 -1
  32. package/src/skf-create-skill/references/step-auto-shard.md +110 -0
  33. package/src/skf-create-skill/references/step-doc-rot.md +109 -0
  34. package/src/skf-create-skill/references/step-doc-sources.md +114 -0
  35. package/src/skf-forger/SKILL.md +8 -2
  36. package/src/skf-test-skill/SKILL.md +8 -7
  37. package/src/skf-test-skill/customize.toml +2 -1
  38. package/src/skf-test-skill/references/external-validators.md +1 -1
  39. package/src/skf-test-skill/references/init.md +16 -1
  40. package/src/skf-test-skill/references/report.md +5 -1
  41. package/src/skf-test-skill/references/score.md +95 -6
  42. package/src/skf-test-skill/references/step-hard-gate.md +73 -0
  43. package/src/skf-test-skill/templates/test-report-template.md +1 -0
@@ -0,0 +1,147 @@
1
+ ---
2
+ nextStepFile: 'report.md'
3
+ outputFile: '{forge_version}/drift-report-{timestamp}.md'
4
+ ---
5
+
6
+ <!-- Config: communicate in {communication_language}. -->
7
+
8
+ # Step 5a: Documentation Drift
9
+
10
+ ## STEP GOAL:
11
+
12
+ Compare documentation content hashes stored at compile time (in `doc_sources` within metadata.json) against the current upstream state. Produce a drift section that reports which tracked docs have changed, which are unreachable, and which were never hashed. This step is informational — doc drift does not affect the source code drift score.
13
+
14
+ ## Rules
15
+
16
+ - Auto-proceed step — no user interaction
17
+ - Graceful failure — if doc fetching fails for any URL, mark as `fetch_failed`, do not block the audit
18
+ - Do not classify severity — doc drift is informational alongside source drift
19
+ - If no `doc_sources` in metadata, skip with notice and auto-proceed
20
+ - Never abort the audit pipeline on any failure in this step
21
+
22
+ ## MANDATORY SEQUENCE
23
+
24
+ ### 1. Check for doc_sources
25
+
26
+ Check the skill metadata loaded at init (step 1 §3 — Load Skill Artifacts) for a `doc_sources` array.
27
+
28
+ **If `doc_sources` is absent or metadata lacks the field:**
29
+
30
+ Append to {outputFile}:
31
+
32
+ ```markdown
33
+ ## Documentation Drift
34
+
35
+ No doc_sources recorded — skip doc drift check. This skill was compiled before doc tracking was available. Recompile with the current CS pipeline to enable doc drift detection.
36
+ ```
37
+
38
+ Set `doc_drift_summary = { skipped_entirely: true }` in workflow context. Update {outputFile} frontmatter: append `'doc-drift'` to `stepsCompleted`. Auto-proceed to {nextStepFile}.
39
+
40
+ **If `doc_sources` is present but an empty array:**
41
+
42
+ Append to {outputFile}:
43
+
44
+ ```markdown
45
+ ## Documentation Drift
46
+
47
+ No documentation sources tracked. The `doc_sources` array is empty — no drift check to perform.
48
+ ```
49
+
50
+ Set `doc_drift_summary = { total_tracked: 0, skipped_entirely: false }` in workflow context. Update {outputFile} frontmatter: append `'doc-drift'` to `stepsCompleted`. Auto-proceed to {nextStepFile}.
51
+
52
+ **If `doc_sources` is present and non-empty:** Continue to §2.
53
+
54
+ ### 2. Fetch and Hash Each Tracked Doc
55
+
56
+ For each entry in `doc_sources`:
57
+
58
+ 1. Read `url` and `content_hash` from the entry
59
+ 2. **If `content_hash` is `null`:** Skip this entry — there is no baseline to compare against. Record it for the report as a null-hash entry. Do not attempt to fetch the URL.
60
+ 3. **If `content_hash` is non-null:** Attempt HTTP GET of the URL with a reasonable timeout (10s)
61
+ - **On success:** Compute `sha256:{hexdigest}` of the response body bytes (UTF-8 encoding). Compare against the stored `content_hash`. If they differ, record as drifted. If they match, record as unchanged.
62
+ - **On failure (network error, timeout, non-200 status):** Record the entry as `status: "fetch_failed"` with the failure reason. Do not report as drift.
63
+
64
+ If URL fetching is unavailable in the current environment, skip doc drift check entirely with:
65
+
66
+ ```markdown
67
+ ## Documentation Drift
68
+
69
+ Doc drift check skipped — URL fetching unavailable in current environment.
70
+ ```
71
+
72
+ Set `doc_drift_summary = { skipped_entirely: true }` and auto-proceed.
73
+
74
+ ### 3. Build Drift Findings
75
+
76
+ Categorize results:
77
+ - **changed:** entries where `content_hash` differs from newly computed hash
78
+ - **unchanged:** entries where hashes match
79
+ - **fetch_failed:** entries where the URL could not be reached
80
+ - **skipped_null_hash:** entries where `content_hash` was `null`
81
+
82
+ Compute totals:
83
+ - `total_tracked` = length of `doc_sources`
84
+ - `changed` = count of drifted entries
85
+ - `unchanged` = count of matching entries
86
+ - `fetch_failed` = count of fetch failures
87
+ - `skipped_null_hash` = count of null-hash entries
88
+
89
+ ### 4. Append to Drift Report
90
+
91
+ Append the `## Documentation Drift` section to {outputFile}.
92
+
93
+ **When drift detected:**
94
+
95
+ ```markdown
96
+ ## Documentation Drift
97
+
98
+ | URL | Old Hash | New Hash | Detected At |
99
+ |-----|----------|----------|-------------|
100
+ | {url} | `{old_hash}` | `{new_hash}` | {ISO-8601 timestamp} |
101
+
102
+ **{changed} of {total_tracked} tracked documentation source(s) have changed since compile.**
103
+ ```
104
+
105
+ Include rows for ALL entries, in order:
106
+ - Drifted entries: show old and new hash
107
+ - Unchanged entries: omit from table (only drifted entries appear)
108
+ - Fetch-failed entries: `| {url} | \`{old_hash}\` | _(fetch failed: {reason})_ | {timestamp} |`
109
+ - Null-hash entries: `| {url} | _(not recorded)_ | — | — |`
110
+
111
+ Fetch-failed entries are clearly marked and excluded from the drift count.
112
+
113
+ **When no drift detected:**
114
+
115
+ ```markdown
116
+ ## Documentation Drift
117
+
118
+ No documentation drift detected. All {total_tracked} tracked documentation source(s) match their compile-time hashes.
119
+ ```
120
+
121
+ If some entries were fetch_failed or skipped_null_hash, append a note after the main message listing those entries.
122
+
123
+ ### 5. Store Context and Auto-Proceed
124
+
125
+ Store `doc_drift_summary` in workflow context for report.md to reference:
126
+
127
+ ```
128
+ doc_drift_summary = {
129
+ total_tracked: N,
130
+ changed: N,
131
+ unchanged: N,
132
+ fetch_failed: N,
133
+ skipped_null_hash: N,
134
+ skipped_entirely: false
135
+ }
136
+ ```
137
+
138
+ Update {outputFile} frontmatter:
139
+ - Append `'doc-drift'` to `stepsCompleted`
140
+
141
+ Display: "**Documentation drift check complete. {changed} of {total_tracked} source(s) drifted. Proceeding to report generation...**"
142
+
143
+ Load, read the full file, then execute {nextStepFile}.
144
+
145
+ ## CRITICAL STEP COMPLETION NOTE
146
+
147
+ ONLY WHEN the ## Documentation Drift section has been appended to {outputFile} and workflow context updated will you then load and read fully `{nextStepFile}` to begin final report generation.
@@ -73,17 +73,21 @@ These rules apply to every step in this workflow:
73
73
  | # | Step | File | Auto-proceed |
74
74
  |---|------|------|--------------|
75
75
  | 1 | Gather Intent | references/gather-intent.md | No (interactive) |
76
+ | 1a | Auto-Brief Generation (auto mode only) | references/step-auto-brief.md | Yes |
77
+ | 1b | Auto-Brief Validation (auto mode only) | references/step-auto-validate.md | No (interactive gate — headless auto-approves) |
76
78
  | 2 | Analyze Target | references/analyze-target.md | Yes |
77
79
  | 3 | Scope Definition | references/scope-definition.md | No (interactive) |
78
80
  | 4 | Confirm Brief | references/confirm-brief.md | No (confirm) |
79
81
  | 5 | Write Brief | references/write-brief.md | Yes |
80
82
  | 6 | Workflow Health Check (terminal) | references/health-check.md | Yes |
81
83
 
84
+ Stages 1a-1b are conditional — they replace stages 2-5 when BS is invoked with the `[auto]` flag via pipeline context. The routing decision is made in stage 1 (gather-intent.md §1b). In auto mode, the chain is: gather-intent.md §1 (forge tier) → §1b (auto check) → step-auto-brief.md → step-auto-validate.md → health-check.md (on [A]pprove or [E]dit) or → confirm-brief.md → write-brief.md → health-check.md (on [R]eject).
85
+
82
86
  ## Invocation Contract
83
87
 
84
88
  | Aspect | Detail |
85
89
  |--------|--------|
86
- | **Inputs** | `target_repo` [required], `skill_name` [required], `scope_hint` [optional], `language_hint` [optional], `target_version` [optional], `source_authority` [optional: official/community/internal, default community], `source_type` [optional: source/docs-only, default source], `doc_urls` [optional: list of `url[,label]` for source_type=docs-only or supplemental], `scope_type` [optional: full-library/specific-modules/public-api/component-library/reference-app/docs-only], `include` [optional: comma-separated globs], `exclude` [optional: comma-separated globs], `scripts_intent` [optional: detect/none/free-text, default detect], `assets_intent` [optional: detect/none/free-text, default detect], `intent` [optional: free-text used to derive description], `force` [optional: overwrite existing brief without prompting], `from_brief` [optional: path to a pre-authored `skill-brief.yaml` to *ratify* — when supplied it is the source of truth, `target_repo`/`skill_name` become optional/derived-from-brief, and the run mirrors the interactive §3.1a ratify path: schema-validate, skip analyze-target/scope-definition, write through the canonical writer in place] |
90
+ | **Inputs** | `target_repo` [required], `skill_name` [required], `scope_hint` [optional], `language_hint` [optional], `target_version` [optional], `source_authority` [optional: official/community/internal, default community], `source_type` [optional: source/docs-only, default source], `doc_urls` [optional: list of `url[,label]` for source_type=docs-only or supplemental], `scope_type` [optional: full-library/specific-modules/public-api/component-library/reference-app/docs-only], `include` [optional: comma-separated globs], `exclude` [optional: comma-separated globs], `scripts_intent` [optional: detect/none/free-text, default detect], `assets_intent` [optional: detect/none/free-text, default detect], `intent` [optional: free-text used to derive description], `force` [optional: overwrite existing brief without prompting], `from_brief` [optional: path to a pre-authored `skill-brief.yaml` to *ratify* — when supplied it is the source of truth, `target_repo`/`skill_name` become optional/derived-from-brief, and the run mirrors the interactive §3.1a ratify path: schema-validate, skip analyze-target/scope-definition, write through the canonical writer in place], `[auto]` [optional: bracket modifier passed via pipeline context — when present, BS loads the upstream brief from `brief_path` in pipeline data, enriches it with doc detection, and writes through the canonical writer; requires `brief_path` from AN's `SKF_ANALYZE_RESULT_JSON`] |
87
91
  | **Gates** | step 1: Input Gate [use args] | step 3: Confirm Gate [C] | step 4: Confirm Gate [C] |
88
92
  | **Outputs** | `skill-brief.yaml` at `{forge_data_folder}/{skill-name}/skill-brief.yaml`; final `SKF_BRIEF_RESULT_JSON` line on stdout when `{headless_mode}` is true |
89
93
  | **Headless** | All gates auto-resolve with heuristic-driven or default action when `{headless_mode}` is true; pre-supplied inputs consumed at the gates that would otherwise prompt; absent `source_authority` and `scope_type` are resolved by signal-driven detection (see `references/headless-args.md`); existing briefs are preserved unless `--force` was supplied (HALT with `overwrite-cancelled` otherwise); supplying `from_brief <path>` instead routes the step 1 GATE to a ratify path that schema-validates the pre-authored brief, skips analyze-target/scope-definition, and writes through the canonical writer (overwriting in place, no `--force` needed) rather than deriving a new brief |
@@ -108,7 +112,7 @@ Every HARD HALT in this workflow exits with a stable code so headless automators
108
112
  When `{headless_mode}` is true, step 5 emits a single-line JSON envelope on **stdout** before chaining to step 6, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
109
113
 
110
114
  ```
111
- SKF_BRIEF_RESULT_JSON: {"status":"success|error","brief_path":"…|null","skill_name":"…","version":"…|null","language":"…|null","scope_type":"…|null","exit_code":0,"halt_reason":null}
115
+ SKF_BRIEF_RESULT_JSON: {"status":"success|error","brief_path":"…|null","skill_name":"…","version":"…|null","language":"…|null","scope_type":"…|null","exit_code":0,"halt_reason":null,"mode":"auto|null"}
112
116
  ```
113
117
 
114
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"forge-tier-missing"`, `"target-inaccessible"`, `"gh-auth-failed"`, `"write-failed"`, `"overwrite-cancelled"`, `"user-cancelled"`. `exit_code` matches the table above.
118
+ `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"forge-tier-missing"`, `"target-inaccessible"`, `"gh-auth-failed"`, `"write-failed"`, `"overwrite-cancelled"`, `"user-cancelled"`. `exit_code` matches the table above. `mode` is `"auto"` when BS was invoked with the `[auto]` flag (pipeline auto-brief generation), `null` otherwise (interactive or headless-without-auto).
@@ -63,6 +63,20 @@ Attempt to load `{forgeTierFile}`:
63
63
  - "**Cannot proceed.** forge-tier.yaml not found at `{forgeTierFile}`. Run the **setup** workflow first to configure your forge tier (Quick/Forge/Forge+/Deep)."
64
64
  - HALT (exit code 3, `halt_reason: "forge-tier-missing"`) — do not proceed.
65
65
 
66
+ ### 1b. Auto Mode Check
67
+
68
+ **Check for `[auto]` flag:** If `[auto]` was passed as a bracket modifier in the pipeline context (e.g., `BS[auto]`), set `{auto_mode}` = true.
69
+
70
+ **IF `{auto_mode}` is true:**
71
+
72
+ 1. **Load upstream brief path:** Read `brief_path` from the pipeline data context (passed by the forger from AN's `SKF_ANALYZE_RESULT_JSON` `brief_paths[]`). If `brief_path` is not available, HARD HALT with exit code 2 (`input-missing`): "**Auto mode requires `brief_path` in pipeline context — AN must run before BS[auto].**"
73
+ 2. **Load source repo:** Read `source_repo` from the pipeline data context (the target repo URL or path, forwarded by the forger). If not available, attempt to extract it from the upstream brief at `brief_path`.
74
+ 3. "**Auto mode activated — bypassing interactive brief workflow.**"
75
+ 4. **Route to auto-brief:** Load, read fully, then execute `references/step-auto-brief.md`. **STOP HERE** — do not continue to §2 or any subsequent section.
76
+
77
+ **IF `{auto_mode}` is NOT true:**
78
+ Continue to §2 as normal — the entire interactive flow below is unchanged.
79
+
66
80
  ### 2. Welcome and Explain
67
81
 
68
82
  "**Welcome to Brief Skill — the skill scoping workflow.**
@@ -0,0 +1,171 @@
1
+ ---
2
+ nextStepFile: 'step-auto-validate.md'
3
+ validateBriefSchemaProbeOrder:
4
+ - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
5
+ - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
6
+ writeSkillBriefProbeOrder:
7
+ - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
8
+ - '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
9
+ emitBriefEnvelopeProbeOrder:
10
+ - '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
11
+ - '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
12
+ detectDocsScript: 'src/shared/scripts/skf-detect-docs.py'
13
+ ---
14
+
15
+ <!-- Config: communicate in {communication_language}. -->
16
+
17
+ # Step 1a: Auto-Brief Generation
18
+
19
+ ## STEP GOAL:
20
+
21
+ To enrich an upstream skill brief (produced by AN auto-scope) with documentation URLs discovered via `skf-detect-docs.py`, validate the enriched brief, and write it through the canonical writer. Envelope emission is deferred to step-auto-validate.md, which presents the brief for user approval before continuing. This step replaces the interactive gather-intent → analyze-target → scope-definition → confirm-brief → write-brief chain when `[auto]` mode is active.
22
+
23
+ ## Rules
24
+
25
+ - Auto-proceed step — no user interaction required
26
+ - This step is conditional — only loaded when `[auto]` flag is present in the pipeline context
27
+ - Must produce the same output artifact as the interactive chain: a validated `skill-brief.yaml`
28
+ - Doc detection is best-effort — failures do not halt the pipeline
29
+ - Do NOT re-derive scope fields from the upstream brief — AN already set them correctly
30
+ - Do NOT render YAML or JSON envelopes in the LLM — delegate to deterministic scripts
31
+
32
+ ## MANDATORY SEQUENCE
33
+
34
+ ### 1. Load Upstream Brief
35
+
36
+ Read the upstream brief path from `{brief_path}` (passed by the forger from AN's `SKF_ANALYZE_RESULT_JSON` `brief_paths[]`).
37
+
38
+ **IF `{brief_path}` is not set or the file does not exist:**
39
+ - HARD HALT with exit code 2 (`input-missing`): "**Auto-brief requires an upstream brief — `brief_path` is missing or the file does not exist at `{brief_path}`.**"
40
+ - Emit error envelope per §6 with `halt_reason: "input-missing"`.
41
+
42
+ **Resolve `{validateBriefSchemaHelper}`** from `{validateBriefSchemaProbeOrder}`; first existing path wins. HALT if no candidate exists.
43
+
44
+ Validate the upstream brief against the schema:
45
+
46
+ ```bash
47
+ uv run {validateBriefSchemaHelper} {brief_path}
48
+ ```
49
+
50
+ The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}`.
51
+
52
+ - **`valid: false`** — the upstream brief is malformed. HARD HALT with exit code 2 (`input-invalid`): "**Upstream brief at `{brief_path}` is invalid: {first error message}.**" Emit error envelope per §6 with `halt_reason: "input-invalid"`.
53
+ - **`valid: true`** — proceed with the parsed `brief` payload. Surface any non-empty `warnings[]` to the log.
54
+
55
+ Extract from the parsed brief:
56
+ - `skill_name` ← `brief.name`
57
+ - `version` ← `brief.version`
58
+ - `source_repo` ← `brief.source_repo`
59
+ - `language` ← `brief.language`
60
+ - `scope_type` ← `brief.scope.type`
61
+ - `forge_tier` ← `brief.forge_tier`
62
+ - `description` ← `brief.description`
63
+ - `created` ← `brief.created`
64
+ - `created_by` ← `brief.created_by`
65
+ - All scope fields: `scope.include`, `scope.exclude`, `scope.notes`, `scope.rationale`, `scope.amendments`, `scope.tier_a_include`
66
+ - Optional fields: `source_type`, `source_authority`, `doc_urls`, `target_version`, `target_ref`, `source_ref`, `scripts_intent`, `assets_intent`
67
+
68
+ **Docs-only check:** If `source_type` is `docs-only` in the parsed brief, skip §2 (Run Doc Detection) and §3 (Enrich Brief with Detected Docs) — the doc URL is already in the brief's `doc_urls`. Log: "Docs-only brief — skipping repo-based doc detection. Doc URLs provided by upstream." Proceed directly to §4 (Validate Enriched Brief). All brief fields (`source_type`, `source_authority`, `doc_urls`, `scope_type`) must pass through unmodified.
69
+
70
+ ### 2. Run Doc Detection
71
+
72
+ Invoke doc detection to discover documentation URLs for the source repo:
73
+
74
+ ```bash
75
+ uv run python {project-root}/{detectDocsScript} --repo-url {source_repo}
76
+ ```
77
+
78
+ `--repo-url` is always required (the script uses it for GitHub API calls). If a local clone is also available at `{local_clone_path}`, add `--local-path {local_clone_path}` to enable docs-folder scanning in addition to API-based detection.
79
+
80
+ **Handle exit codes:**
81
+
82
+ - **Exit 0 (found docs):** Parse the JSON output array. Each entry has `{url, detected_via, content_hash, content_type}`. Proceed to §3 with the detected docs.
83
+ - **Exit 1 (none found):** Log: "No external documentation found — brief generated from source analysis only." Proceed to §4 with no doc enrichment.
84
+ - **Exit 2 (error):** Log warning: "Doc detection failed — proceeding without doc enrichment." Do NOT halt — doc enrichment is best-effort. Proceed to §4 with no doc enrichment.
85
+
86
+ ### 3. Enrich Brief with Detected Docs
87
+
88
+ For each detected doc entry, create a brief `doc_urls` entry:
89
+
90
+ - `url` ← `url` (direct copy)
91
+ - `label` ← derive from `content_type` if available:
92
+ - `"api-docs"` → `"API Documentation"`
93
+ - `"guide"` → `"Guide"`
94
+ - `"reference"` → `"Reference"`
95
+ - Otherwise derive from `detected_via`:
96
+ - `"homepageUrl"` → `"Homepage"`
97
+ - `"readme_link"` → `"README Link"`
98
+ - `"pages_api"` → `"GitHub Pages"`
99
+ - `"docs_folder"` → `"Docs Folder"`
100
+
101
+ If the upstream brief already has `doc_urls`, merge the detected docs with the existing entries (deduplicate by URL — existing entries take precedence).
102
+
103
+ ### 4. Validate Enriched Brief
104
+
105
+ Assemble the enriched brief context as a flat JSON object following the write-brief §3 contract:
106
+
107
+ ```json
108
+ {
109
+ "name": "{skill_name}",
110
+ "target_version": "{target_version or null}",
111
+ "detected_version": null,
112
+ "source_type": "{source_type or 'source'}",
113
+ "source_repo": "{source_repo}",
114
+ "language": "{language}",
115
+ "description": "{description}",
116
+ "forge_tier": "{forge_tier}",
117
+ "created": "{created}",
118
+ "created_by": "{created_by}",
119
+ "scope_type": "{scope_type}",
120
+ "scope_include": ["{scope.include patterns}"],
121
+ "scope_exclude": ["{scope.exclude patterns}"],
122
+ "scope_notes": "{scope.notes or ''}",
123
+ "scope_rationale": null,
124
+ "scope_tier_a_include": null,
125
+ "scope_amendments": null,
126
+ "doc_urls": [{"url": "...", "label": "..."}],
127
+ "scripts_intent": "{scripts_intent or null}",
128
+ "assets_intent": "{assets_intent or null}",
129
+ "source_authority": "{source_authority or null}",
130
+ "target_ref": "{target_ref or null}",
131
+ "source_ref": "{source_ref or null}",
132
+ "version_resolved": "{version}"
133
+ }
134
+ ```
135
+
136
+ The `version_resolved` key pins the output to the upstream brief's version — without it, the writer's precedence logic falls through to `1.0.0` since `target_version` and `detected_version` are both null on the auto path.
137
+
138
+ ### 5. Write Enriched Brief
139
+
140
+ **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins. HALT if no candidate exists.
141
+
142
+ Write the enriched brief through the canonical writer:
143
+
144
+ ```bash
145
+ echo '<context-json>' | uv run {writeSkillBriefHelper} write --target {forge_data_folder}/{skill_name}/skill-brief.yaml --from-flat
146
+ ```
147
+
148
+ **On script failure (non-zero exit):**
149
+ - Exit 1 (validation/invariant): Emit error envelope per §6 with `halt_reason: "input-invalid"`, then HARD HALT.
150
+ - Exit 2 (I/O failure): Emit error envelope per §6 with `halt_reason: "write-failed"`, then HARD HALT.
151
+
152
+ **On success:** Capture `brief_path` and `version` from the response envelope for step-auto-validate's envelope emission.
153
+
154
+ ### 6. Error Envelope (Canonical)
155
+
156
+ Every HARD HALT in this step emits the error envelope on stderr:
157
+
158
+ **Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins. HALT if no candidate exists.
159
+
160
+ ```bash
161
+ echo '{"status":"error","skill_name":"{skill_name or unknown}","halt_reason":"{reason}","mode":"auto"}' | \
162
+ uv run {emitBriefEnvelopeHelper} emit --target stderr
163
+ ```
164
+
165
+ ### 7. Chain to Auto-Validate
166
+
167
+ Load, read fully, then execute {nextStepFile} to present the auto-brief validation gate, where the user can approve, edit, or reject the brief before the pipeline continues.
168
+
169
+ ## CRITICAL STEP COMPLETION NOTE
170
+
171
+ ONLY WHEN the enriched brief has been written and validated will you load and read fully {nextStepFile} to begin the auto-brief validation gate.
@@ -0,0 +1,209 @@
1
+ ---
2
+ nextStepFile: 'health-check.md'
3
+ rejectTargetFile: 'confirm-brief.md'
4
+ validateBriefSchemaProbeOrder:
5
+ - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
6
+ - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
7
+ writeSkillBriefProbeOrder:
8
+ - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
9
+ - '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
10
+ emitBriefEnvelopeProbeOrder:
11
+ - '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
12
+ - '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
13
+ ---
14
+
15
+ <!-- Config: communicate in {communication_language}. -->
16
+
17
+ # Step 1b: Auto-Brief Validation
18
+
19
+ ## STEP GOAL:
20
+
21
+ To present the user with a concise summary of the auto-generated brief and offer three actions — approve, edit, or reject — before the pipeline continues. On approve or edit, the result envelope is emitted and the pipeline chains to the health check. On reject, the pipeline falls back to the interactive brief review cycle with pre-populated fields.
22
+
23
+ ## Rules
24
+
25
+ - This step is conditional — only loaded from step-auto-brief.md when `[auto]` mode is active
26
+ - The brief MUST already exist on disk (written by step-auto-brief §5) before this step runs
27
+ - Do NOT render YAML or JSON envelopes in the LLM — delegate to deterministic scripts
28
+ - Do NOT modify confirm-brief.md or write-brief.md — the [R]eject path reuses them as-is
29
+ - The 10-line summary is always displayed, even in headless mode, for logging transparency
30
+
31
+ ## MANDATORY SEQUENCE
32
+
33
+ ### 1. Load Auto-Brief
34
+
35
+ Read the brief from `{forge_data_folder}/{skill_name}/skill-brief.yaml` (written by step-auto-brief §5).
36
+
37
+ **Resolve `{validateBriefSchemaHelper}`** from `{validateBriefSchemaProbeOrder}`; first existing path wins. HALT if no candidate exists.
38
+
39
+ Validate the brief against the schema:
40
+
41
+ ```bash
42
+ uv run {validateBriefSchemaHelper} {forge_data_folder}/{skill_name}/skill-brief.yaml
43
+ ```
44
+
45
+ The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}`.
46
+
47
+ - **`valid: false`** — HARD HALT with exit code 2 (`input-invalid`): "**Auto-brief at `{forge_data_folder}/{skill_name}/skill-brief.yaml` is invalid: {first error message}.**" Emit error envelope per §7 with `halt_reason: "input-invalid"`.
48
+ - **`valid: true`** — proceed with the parsed `brief` payload. Surface any non-empty `warnings[]` to the log.
49
+
50
+ **IF the file does not exist:**
51
+ - HARD HALT with exit code 2 (`input-missing`): "**Auto-brief not found at `{forge_data_folder}/{skill_name}/skill-brief.yaml` — step-auto-brief must write the brief before this step runs.**" Emit error envelope per §7 with `halt_reason: "input-missing"`.
52
+
53
+ Extract from the parsed brief:
54
+ - `skill_name` ← `brief.name`
55
+ - `version` ← `brief.version`
56
+ - `source_repo` ← `brief.source_repo`
57
+ - `language` ← `brief.language`
58
+ - `scope_type` ← `brief.scope.type`
59
+ - `scope_include` ← `brief.scope.include`
60
+ - `scope_exclude` ← `brief.scope.exclude`
61
+ - `forge_tier` ← `brief.forge_tier`
62
+ - `description` ← `brief.description`
63
+ - `doc_urls` ← `brief.doc_urls`
64
+
65
+ ### 2. Present 10-Line Summary
66
+
67
+ Render a concise summary from the brief fields for rapid scanning:
68
+
69
+ ```
70
+ Auto-Brief Summary: {skill_name}
71
+ ─────────────────────────────────
72
+ Source: {source_repo}
73
+ Language: {language}
74
+ Scope: {scope_type} ({N} include, {M} exclude patterns)
75
+ Docs: {doc_urls count} sources detected | "None detected"
76
+ Version: {version}
77
+ Forge Tier: {forge_tier}
78
+ Quality: 90% target (deepwiki pipeline)
79
+ Description: "{description}"
80
+ ```
81
+
82
+ Where `{N}` is the count of `scope_include` patterns and `{M}` is the count of `scope_exclude` patterns. If `doc_urls` is null or empty, display "None detected".
83
+
84
+ ### 3. Validation Gate
85
+
86
+ **GATE: [A]pprove** — Present `[A]pprove` / `[E]dit` / `[R]eject` to user.
87
+
88
+ "**Review the auto-generated brief above.**
89
+
90
+ Select an action:
91
+ [A] Approve — accept the brief as-is and continue the pipeline
92
+ [E] Edit — modify specific fields before continuing
93
+ [R] Reject — fall back to interactive brief review with pre-populated fields"
94
+
95
+ If `{headless_mode}`: auto-proceed with `[A]pprove`, log: "headless: auto-approve auto-brief".
96
+
97
+ Wait for user response. Branch on the response:
98
+
99
+ - `[A]` or `approve` → §4 ([A]pprove path)
100
+ - `[E]` or `edit` → §5 ([E]dit path)
101
+ - `[R]` or `reject` → §6 ([R]eject path)
102
+ - Any other input → answer briefly, re-display the menu
103
+
104
+ ### 4. [A]pprove Path
105
+
106
+ **Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins. HALT if no candidate exists.
107
+
108
+ Emit the `SKF_BRIEF_RESULT_JSON` envelope with `mode: "auto"`:
109
+
110
+ ```bash
111
+ echo '{"status":"success","brief_path":"{brief_path}","skill_name":"{skill_name}","version":"{version}","language":"{language}","scope_type":"{scope_type}","halt_reason":null,"mode":"auto"}' | \
112
+ uv run {emitBriefEnvelopeHelper} emit
113
+ ```
114
+
115
+ Where `{brief_path}` is `{forge_data_folder}/{skill_name}/skill-brief.yaml`.
116
+
117
+ Chain to {nextStepFile} (health-check.md) — load, read fully, then execute.
118
+
119
+ ### 5. [E]dit Path
120
+
121
+ Present each brief field with its current value and accept natural language modification requests.
122
+
123
+ "**Editable fields:**
124
+
125
+ 1. **Name:** {skill_name}
126
+ 2. **Source:** {source_repo}
127
+ 3. **Language:** {language}
128
+ 4. **Scope type:** {scope_type}
129
+ 5. **Include patterns:** {scope_include}
130
+ 6. **Exclude patterns:** {scope_exclude}
131
+ 7. **Description:** {description}
132
+ 8. **Doc URLs:** {doc_urls or "None"}
133
+ 9. **Version:** {version}
134
+ 10. **Forge tier:** {forge_tier}
135
+
136
+ Tell me what to change (e.g. 'change scope type to public-api', 'add doc URL https://...')."
137
+
138
+ Wait for user response. Apply changes to the brief context.
139
+
140
+ **Re-write the modified brief** through the canonical writer:
141
+
142
+ **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins.
143
+
144
+ Assemble the modified brief context as a flat JSON object (same format as step-auto-brief §4):
145
+
146
+ ```bash
147
+ echo '<modified-flat-json>' | uv run {writeSkillBriefHelper} write --target {forge_data_folder}/{skill_name}/skill-brief.yaml --from-flat
148
+ ```
149
+
150
+ The canonical writer validates the brief internally — on non-zero exit, surface the error and re-prompt for corrections. The edit loop allows multiple modifications — each write re-validates before accepting.
151
+
152
+ **Re-validate the written brief** against the schema to confirm correctness:
153
+
154
+ **Resolve `{validateBriefSchemaHelper}`** from `{validateBriefSchemaProbeOrder}`; first existing path wins.
155
+
156
+ ```bash
157
+ uv run {validateBriefSchemaHelper} {forge_data_folder}/{skill_name}/skill-brief.yaml
158
+ ```
159
+
160
+ If validation fails (the writer missed a constraint), surface the error and re-prompt for corrections.
161
+
162
+ Re-present the 10-line summary (§2 format) with updated values so the user can verify the change.
163
+
164
+ "Updated brief written. **Select:** [A] Approve and continue · [E] Edit more · [R] Reject"
165
+
166
+ - `[A]` → emit envelope per §4, chain to {nextStepFile}
167
+ - `[E]` → repeat §5 edit loop
168
+ - `[R]` → §6 ([R]eject path)
169
+
170
+ ### 6. [R]eject Path
171
+
172
+ "**Falling back to interactive brief — fields pre-populated from auto-detection.**"
173
+
174
+ Hydrate brief context variables from the auto-brief on disk, using the same field mapping as the ratify path in gather-intent §3.1a:
175
+
176
+ - `name` ← `brief.name`; `version` ← `brief.version`; `target_version` ← `brief.target_version`
177
+ - `target_ref` ← `brief.target_ref`; `source_ref` ← `brief.source_ref` (optional git refs; preserve when present)
178
+ - `source_repo` ← `brief.source_repo`; `source_type` ← `brief.source_type`; `source_authority` ← `brief.source_authority`; `doc_urls` ← `brief.doc_urls`
179
+ - `language` ← `brief.language`; `description` ← `brief.description`; `forge_tier` ← `brief.forge_tier`
180
+ - `created` ← `brief.created`; `created_by` ← `brief.created_by`
181
+ - `scope.type` / `scope.include` / `scope.exclude` / `scope.tier_a_include` / `scope.notes` / `scope.rationale` / `scope.amendments` ← `brief.scope.*` (preserve `tier_a_include` and the `amendments` log verbatim — do not re-derive or drop them)
182
+ - `scripts_intent` ← `brief.scripts_intent`; `assets_intent` ← `brief.assets_intent`
183
+
184
+ Set `ratify_mode: true` and `ratify_source_path: {forge_data_folder}/{skill_name}/skill-brief.yaml` in workflow context.
185
+
186
+ Chain to {rejectTargetFile} (confirm-brief.md) — load, read fully, then execute. The user gets the full interactive review experience: view, adjust fields inline, revise scope via [R], or approve via [C] → write-brief.md → health-check.md.
187
+
188
+ The interactive chain's write-brief.md handles its own envelope emission with `mode: null` (interactive), which is correct since the user explicitly chose to leave auto mode.
189
+
190
+ ### 7. Error Envelope (Canonical)
191
+
192
+ Every HARD HALT in this step emits the error envelope on stderr:
193
+
194
+ **Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins.
195
+
196
+ ```bash
197
+ echo '{"status":"error","skill_name":"{skill_name or unknown}","halt_reason":"{reason}","mode":"auto"}' | \
198
+ uv run {emitBriefEnvelopeHelper} emit --target stderr
199
+ ```
200
+
201
+ ### 8. Chain
202
+
203
+ Load, read fully, then execute the appropriate next step file based on the user's choice:
204
+ - [A]pprove or [E]dit (after final approve): {nextStepFile} (health-check.md)
205
+ - [R]eject: {rejectTargetFile} (confirm-brief.md)
206
+
207
+ ## CRITICAL STEP COMPLETION NOTE
208
+
209
+ ONLY WHEN the user has made their choice (approve, edit-then-approve, or reject) and the appropriate action has been taken (envelope emitted or context hydrated) will you load and read fully the next step file.
@@ -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 |
@@ -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
  ---