bmad-module-skill-forge 1.1.0 → 1.3.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 (45) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +6 -4
  3. package/docs/_data/pinned.yaml +1 -1
  4. package/docs/bmad-synergy.md +2 -2
  5. package/docs/getting-started.md +1 -1
  6. package/docs/skill-model.md +26 -32
  7. package/docs/troubleshooting.md +13 -1
  8. package/docs/why-skf.md +5 -4
  9. package/docs/workflows.md +53 -0
  10. package/package.json +2 -2
  11. package/src/knowledge/ccc-bridge.md +1 -1
  12. package/src/shared/references/output-contract-schema.md +10 -0
  13. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +134 -0
  14. package/src/shared/scripts/skf-detect-tools.py +359 -0
  15. package/src/shared/scripts/skf-emit-result-envelope.py +419 -0
  16. package/src/shared/scripts/skf-extract-public-api.py +505 -0
  17. package/src/shared/scripts/skf-forge-tier-rw.py +413 -0
  18. package/src/shared/scripts/skf-merge-ccc-exclusions.py +324 -0
  19. package/src/shared/scripts/skf-preflight.py +14 -4
  20. package/src/shared/scripts/skf-qmd-classify-collections.py +212 -0
  21. package/src/shared/scripts/skf-render-quick-metadata.py +192 -0
  22. package/src/shared/scripts/skf-resolve-package.py +264 -0
  23. package/src/shared/scripts/skf-validate-frontmatter.py +9 -3
  24. package/src/shared/scripts/skf-validate-output.py +24 -7
  25. package/src/skf-create-skill/steps-c/step-06-validate.md +1 -1
  26. package/src/skf-quick-skill/SKILL.md +178 -10
  27. package/src/skf-quick-skill/assets/skill-template.md +5 -1
  28. package/src/skf-quick-skill/references/registry-resolution.md +2 -0
  29. package/src/skf-quick-skill/steps-c/step-01-resolve-target.md +84 -16
  30. package/src/skf-quick-skill/steps-c/step-02-ecosystem-check.md +3 -3
  31. package/src/skf-quick-skill/steps-c/step-03-quick-extract.md +86 -43
  32. package/src/skf-quick-skill/steps-c/step-04-compile.md +49 -56
  33. package/src/skf-quick-skill/steps-c/step-05-write-and-validate.md +164 -0
  34. package/src/skf-quick-skill/steps-c/{step-06-write.md → step-06-finalize.md} +15 -7
  35. package/src/skf-quick-skill/steps-c/step-07-health-check.md +5 -3
  36. package/src/skf-setup/SKILL.md +25 -10
  37. package/src/skf-setup/references/tier-rules.md +2 -2
  38. package/src/skf-setup/steps-c/step-01-detect-and-tier.md +58 -71
  39. package/src/skf-setup/steps-c/step-01b-ccc-index.md +49 -66
  40. package/src/skf-setup/steps-c/step-02-write-config.md +59 -86
  41. package/src/skf-setup/steps-c/step-03-auto-index.md +73 -91
  42. package/src/skf-setup/steps-c/step-04-report.md +135 -11
  43. package/src/skf-setup/steps-c/step-05-health-check.md +4 -2
  44. package/src/skf-test-skill/steps-c/step-01-init.md +4 -4
  45. package/src/skf-quick-skill/steps-c/step-05-validate.md +0 -193
@@ -23,6 +23,12 @@ These rules apply to every step in this workflow:
23
23
  - Only load one step file at a time — never preload future steps
24
24
  - Always communicate in `{communication_language}`
25
25
  - If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
26
+ - If `{headless_mode}` is true, emit a single-line JSON progress event to **stderr** at each step's entry and exit so pipeline schedulers can stream live progress instead of post-mortem-parsing the result contract:
27
+ - entry: `{"step":N,"name":"<slug>","status":"start"}`
28
+ - exit (just before chaining to nextStepFile): `{"step":N,"name":"<slug>","status":"done"}`
29
+ - on HARD HALT: `{"step":N,"name":"<slug>","status":"halt","exit":<code>}` instead of "done"
30
+
31
+ `N` is the step number (1–7) and `<slug>` is the kebab portion of the filename after the number — `resolve-target`, `ecosystem-check`, `quick-extract`, `compile`, `write-and-validate`, `finalize`, `health-check`. One line per event; do not pretty-print.
26
32
 
27
33
  ## Stages
28
34
 
@@ -32,25 +38,187 @@ These rules apply to every step in this workflow:
32
38
  | 2 | Ecosystem Check | steps-c/step-02-ecosystem-check.md | Yes |
33
39
  | 3 | Quick Extract | steps-c/step-03-quick-extract.md | Yes |
34
40
  | 4 | Compile | steps-c/step-04-compile.md | No (review) |
35
- | 5 | Write & Validate | steps-c/step-05-validate.md | Yes |
36
- | 6 | Finalize | steps-c/step-06-write.md | Yes |
41
+ | 5 | Write & Validate | steps-c/step-05-write-and-validate.md | Yes |
42
+ | 6 | Finalize | steps-c/step-06-finalize.md | Yes |
37
43
  | 7 | Workflow Health Check | steps-c/step-07-health-check.md | Yes |
38
44
 
39
45
  ## Invocation Contract
40
46
 
41
47
  | Aspect | Detail |
42
48
  |--------|--------|
43
- | **Inputs** | target (GitHub URL or package name) [required], language_hint [optional], scope_hint [optional] |
44
- | **Gates** | step-01: Input Gate [use args] | step-02: Choice Gate [P] (if match) | step-04: Review Gate [C] |
45
- | **Outputs** | SKILL.md, context-snippet.md, metadata.json, active symlink |
49
+ | **Inputs** | target (GitHub URL or package name) [required for single-target mode], language_hint [optional], scope_hint [optional] |
50
+ | **Overrides** | `--description`, `--exports`, `--skip-snippet`, `--no-active-pointer`, `--batch <file>`, `--fail-fast` see On Activation step 3 |
51
+ | **Gates** | step-01: Input Gate [use args]; step-02: Choice Gate [P] (if match); step-04: Review Gate [C/E/S/Q] |
52
+ | **Outputs** | SKILL.md, context-snippet.md, metadata.json, active pointer, result contract (timestamped + `-latest` copy). Snippet and active pointer can be skipped per overrides. |
46
53
  | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
54
+ | **Exit codes** | See "Exit Codes" below |
55
+
56
+ ## Exit Codes
57
+
58
+ Every HARD HALT in this workflow exits with a stable, documented code so headless automators can branch on the failure class without grepping message text:
59
+
60
+ | Code | Meaning | Raised by |
61
+ | ---- | ---------------------- | ----------------------------------------------------------- |
62
+ | 0 | success | step-07 (terminal) |
63
+ | 3 | resolution-failure | step-01 §2c (prose input), step-01 §3 (registry chain failed) |
64
+ | 4 | write-failure | step-05 §2 (deliverable write failed) |
65
+ | 5 | overwrite-cancelled | step-05 §1 (user selected [N]) |
66
+ | 6 | compile-cancelled | step-04 §6 (user selected [Q]) |
67
+ | 7 | finalize-blocked | step-06 §1 (active-pointer flip refused — non-link in place) |
68
+
69
+ Reserved: `validator-missing` may be promoted from advisory log to fatal exit code in a future revision; consumers should not assume code 8+ is unused.
70
+
71
+ ## Result Contract on HARD HALT
72
+
73
+ In addition to the success-variant result contract written by step-06 §3, every HARD HALT must surface an **error variant** so headless automators don't silently break when `quick-skill-result-latest.json` is missing on failed runs.
74
+
75
+ **Always (every HARD HALT, regardless of phase)** — emit a single line on **stderr**:
76
+
77
+ ```
78
+ SKF_QUICK_SKILL_RESULT_JSON: {"status":"error","exit_code":<N>,"phase":"<slug>","error":{"code":"<class>","message":"<short>"},"outputs":{},"summary":{},"skill_package":"<path-or-null>"}
79
+ ```
80
+
81
+ One line, no pretty-print. Matches the prefix-and-envelope convention used by `skf-emit-result-envelope.py`.
82
+
83
+ **Additionally, when `{skill_package}` is known** (HALT at step-05 §1 onward) — write the same JSON object (without the `SKF_QUICK_SKILL_RESULT_JSON: ` prefix) to disk:
84
+
85
+ ```
86
+ {skill_package}/quick-skill-result-{YYYYMMDD-HHmmss}.json
87
+ {skill_package}/quick-skill-result-latest.json (copy, not symlink)
88
+ ```
89
+
90
+ so consumers that hardcode the `-latest.json` path see a deterministic file even on failed runs. HALTs at step-01/02/03/04 cannot write to disk because `{skill_package}` is computed only in step-05 §1; for those, the stderr envelope plus exit code is the contract.
91
+
92
+ **Schema:**
93
+
94
+ | Field | Type | Notes |
95
+ | --------------- | -------------- | ----------------------------------------------------------------------------------------------------------- |
96
+ | `status` | string | always `"error"` for HARD HALTs |
97
+ | `exit_code` | integer | matches the Exit Codes table |
98
+ | `phase` | string | step slug where the HALT occurred (e.g. `resolve-target`, `compile`) |
99
+ | `error.code` | string | one of: `resolution-failure`, `write-failure`, `overwrite-cancelled`, `compile-cancelled`, `finalize-blocked` |
100
+ | `error.message` | string | the user-facing message that was displayed |
101
+ | `error.details` | any | optional — phase-specific context (e.g. the failed file path) |
102
+ | `outputs` | object | empty `{}` on early HALTs; partial when files were already written |
103
+ | `summary` | object | empty `{}` on early HALTs |
104
+ | `skill_package` | string \| null | absolute path when known, `null` when HALT preceded step-05 §1 |
47
105
 
48
106
  ## On Activation
49
107
 
50
- 1. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve:
51
- - `project_name`, `output_folder`, `user_name`, `communication_language`, `document_output_language`
52
- - `skills_output_folder`, `forge_data_folder`
108
+ 1. Read `{project-root}/_bmad/skf/config.yaml` and `{forger_root}/preferences.yaml` in parallel (one batched tool-call message — they are independent files), then resolve:
109
+ - From config: `project_name`, `output_folder`, `user_name`, `communication_language`, `document_output_language`, `skills_output_folder`, `forge_data_folder`
110
+ - From preferences: `headless_mode` (default false)
111
+
112
+ 2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `preferences.yaml`. Default: false.
113
+
114
+ 3. **Parse CLI overrides** — capture optional override flags into the workflow context as `{overrides}`. Each override is opt-in; when omitted, the workflow runs as today.
115
+
116
+ | Flag | Effect |
117
+ | --- | --- |
118
+ | `--description "<string>"` | Override the LLM-derived description in step-04 §2 (used in SKILL.md frontmatter and metadata.json). Subject to the same agentskills.io length (1–1024 chars) and voice (third-person) checks as extracted descriptions. |
119
+ | `--exports "<name1,name2,...>"` | Override the extracted export list. Parse as comma-separated; trim whitespace per item; skip empty items. Used in step-04 §2 Key Exports and the count-derived metadata stats. |
120
+ | `--skip-snippet` | Skip context-snippet.md generation in step-04 §3 and its write in step-05 §2. Artifact omitted from `outputs`; step-05 §5 advisory snippet validation reports a "skipped" entry. |
121
+ | `--no-active-pointer` | Skip the active-pointer flip in step-06 §1. Deliverables still land in `{skill_package}` but `{skill_group}/active` is not updated. Useful for batch automators that flip pointers in a separate stage. |
122
+ | `--batch <file>` | Run the workflow against a list of targets from a text file rather than a single argument. Implies `--headless` (gates cannot be human-driven across N targets). See "Batch Mode" below for input format and summary contract. Single-target overrides above apply globally to every target in the batch. |
123
+ | `--fail-fast` | Only meaningful with `--batch`. Abort the whole batch on the first per-target failure instead of recording the failure in the summary and proceeding to the next target. |
124
+
125
+ 4. **If `--batch` is set**, force `{headless_mode} = true` (log "headless: coerced by --batch" if it was false), read the batch file, and parse the target list per "Batch Mode" below. Continue at step 5; the batch loop documented in "Batch Mode" wraps the step-01 → step-07 pipeline that follows.
126
+
127
+ 5. Load, read the full file, and then execute `./steps-c/step-01-resolve-target.md` to begin the workflow. (In batch mode, control returns here for each subsequent target after step-07 completes; see "Batch Mode" below.)
128
+
129
+ ## Batch Mode
130
+
131
+ When `--batch <file>` is supplied, quick-skill processes a list of targets from a text file in sequence rather than a single target from arguments. Designed for unattended bulk runs (CI pipelines, mass-rebuilds, the skf-batch-skills meta-workflow when it lands).
132
+
133
+ ### Input format
134
+
135
+ One target per line. Empty lines and lines starting with `#` (after optional leading whitespace) are ignored. Each non-empty line has the same shape as the single-target `target` argument, with optional space-separated per-line modifiers:
136
+
137
+ ```
138
+ # A batch input file.
139
+ lodash
140
+ @vercel/og
141
+ cognee@0.5.0
142
+ https://github.com/foo/bar
143
+ https://github.com/foo/bar@2.1.0-beta
144
+
145
+ # Per-line modifiers — overrides for THIS target only:
146
+ lodash language=javascript scope=src/
147
+ cognee@0.5.0 language=python scope=cognee/api/
148
+ ```
149
+
150
+ Recognised per-line modifiers:
151
+
152
+ | Modifier | Effect (this target only) |
153
+ | --- | --- |
154
+ | `language=<lang>` | Sets `language_hint` for this target — same effect as the optional `language_hint` input on a single-target run. |
155
+ | `scope=<path>` | Sets `scope_hint` for this target — same effect as the optional `scope_hint` input on a single-target run. |
156
+
157
+ Per-line modifiers shadow the global `--description` / `--exports` / `--skip-snippet` / `--no-active-pointer` overrides only when those override fields are not set. Global overrides apply to every target unless a future modifier extends per-line override syntax.
158
+
159
+ ### Execution
160
+
161
+ `--batch` implies `--headless`. The batch loop runs the full quick-skill pipeline (steps 1–7) for each target in file order:
162
+
163
+ 1. Set `target`, `target_version`, `language_hint`, `scope_hint` from the batch line into the workflow context.
164
+ 2. Execute steps 1–7 per the normal pipeline.
165
+ 3. After step-07 completes (success or HARD HALT), record the per-target outcome (target, status, exit_code, skill_package, error.code) into the batch result list.
166
+ 4. If `--fail-fast` is set and the target failed, exit the batch loop immediately. Otherwise continue with the next target.
167
+
168
+ Per-target output lands in `{skill_package}/` as today, with the per-target result contract at `{skill_package}/quick-skill-result-latest.json` (success or error variant per "Result Contract on HARD HALT" above).
169
+
170
+ ### Batch summary contract
171
+
172
+ After the last target completes (or `--fail-fast` triggers an early exit), write the batch summary at:
173
+
174
+ ```
175
+ {skills_output_folder}/_batch/quick-skill-batch-{YYYYMMDD-HHmmss}.json
176
+ {skills_output_folder}/_batch/quick-skill-batch-latest.json (copy, not symlink)
177
+ ```
178
+
179
+ Schema:
180
+
181
+ ```json
182
+ {
183
+ "skill": "skf-quick-skill",
184
+ "mode": "batch",
185
+ "status": "success | partial | failed",
186
+ "timestamp": "<ISO 8601 UTC>",
187
+ "input_file": "<path passed to --batch>",
188
+ "targets_total": 0,
189
+ "succeeded": 0,
190
+ "failed": 0,
191
+ "fail_fast_triggered": false,
192
+ "results": [
193
+ {
194
+ "target": "<line from batch file>",
195
+ "status": "success | error",
196
+ "exit_code": 0,
197
+ "skill_package": "<absolute path or null>",
198
+ "error_code": null
199
+ }
200
+ ]
201
+ }
202
+ ```
203
+
204
+ `status` resolves as: `"success"` when `failed == 0`; `"partial"` when `failed > 0 && succeeded > 0`; `"failed"` when `succeeded == 0`. `fail_fast_triggered` is `true` only when `--fail-fast` aborted the loop early — `targets_total` then reflects the count actually attempted, not the file's line count.
205
+
206
+ ### Headless events
207
+
208
+ Batch mode emits per-target boundary events on stderr in addition to the per-step events documented in Workflow Rules:
209
+
210
+ ```
211
+ {"batch":<n>,"target":"<target>","status":"start"}
212
+ {"batch":<n>,"target":"<target>","status":"done","exit":<code>}
213
+ {"batch":<n>,"target":"<target>","status":"fail","exit":<code>,"error_code":"<class>"}
214
+ ```
215
+
216
+ `<n>` is the 1-based index of the target in the parsed list. After the loop ends, emit one final batch-summary event:
217
+
218
+ ```
219
+ {"batch_summary":true,"targets_total":N,"succeeded":K,"failed":M,"status":"<...>","fail_fast_triggered":<bool>}
220
+ ```
53
221
 
54
- 2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false.
222
+ ### Exit code
55
223
 
56
- 3. Load, read the full file, and then execute `./steps-c/step-01-resolve-target.md` to begin the workflow.
224
+ The batch process exits with code `0` when `failed == 0`, otherwise with the exit code of the first failed target (so automators that already branch on the single-target exit-code map continue to work without batch-specific handling). When `--fail-fast` triggers, the exit code is the failing target's code.
@@ -101,6 +101,10 @@ Indexed format targeting ~80-120 tokens per skill:
101
101
  "assets_count": 0
102
102
  },
103
103
  "dependencies": [],
104
- "compatibility": "{semver-range}"
104
+ "compatibility": "{semver-range}",
105
+ "provenance": {
106
+ "language_hint": "{language_hint or null}",
107
+ "scope_hint": "{scope_hint or null}"
108
+ }
105
109
  }
106
110
  ```
@@ -13,6 +13,8 @@ When the user provides a package name instead of a GitHub URL, use this fallback
13
13
 
14
14
  Try each registry in order. Stop at first success.
15
15
 
16
+ **Per-call timeout:** apply a 10s timeout to each registry HTTP call (15s for the web-search fallback) so a single hung registry cannot stall the workflow under hostile network conditions. Treat a timeout as a soft failure and fall through to the next entry in the chain.
17
+
16
18
  #### 1. npm Registry (JavaScript/TypeScript)
17
19
 
18
20
  ```
@@ -1,10 +1,15 @@
1
1
  ---
2
2
  nextStepFile: './step-02-ecosystem-check.md'
3
3
  registryResolutionData: 'references/registry-resolution.md'
4
+ packageResolverProbeOrder:
5
+ - '{project-root}/_bmad/skf/shared/scripts/skf-resolve-package.py'
6
+ - '{project-root}/src/shared/scripts/skf-resolve-package.py'
4
7
  ---
5
8
 
6
9
  # Step 1: Resolve Target
7
10
 
11
+ Communicate with the user in `{communication_language}`.
12
+
8
13
  ## STEP GOAL:
9
14
 
10
15
  To accept a GitHub URL or package name from the user, resolve it to a GitHub repository, detect the primary language, and prepare state for source extraction.
@@ -16,10 +21,12 @@ To accept a GitHub URL or package name from the user, resolve it to a GitHub rep
16
21
 
17
22
  ## MANDATORY SEQUENCE
18
23
 
19
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise unless user explicitly requests a change.
20
-
21
24
  ### 1. Accept User Input
22
25
 
26
+ **Batch mode:** if `--batch` is active (see SKILL.md "Batch Mode"), the current target was already resolved by On Activation step 4 from the next batch line and placed into the workflow context as `target`, with optional `language_hint` and `scope_hint` per-line modifiers. Skip the prompt below — emit `{"batch":<n>,"target":"<target>","status":"start"}` to stderr and proceed directly to §1b with the batch-supplied values.
27
+
28
+ **Single-target mode** (default):
29
+
23
30
  "**Quick Skill — fastest path to a skill.**
24
31
 
25
32
  Provide a **GitHub URL** or **package name** and I'll resolve it to source and compile a best-effort SKILL.md.
@@ -50,41 +57,88 @@ If no `@version` suffix is present, proceed as today — version will be auto-de
50
57
  - Extract org/repo from URL
51
58
  - Set `resolved_url` to the GitHub URL
52
59
  - Set `repo_name` to the repo name (last path segment)
53
- - Skip to step 4 (Detect Language)
60
+ - Skip to step 3a (Verify Target Version Tag), then step 4 (Detect Language)
54
61
 
55
- **If input is a package name:**
62
+ **If input is a package-name-like token** (no whitespace, matches `[@a-zA-Z0-9._/-]+(@<semver>)?`, e.g. `lodash`, `@scope/name`, `requests==2.31`, `cognee@0.5.0`):
56
63
  - Proceed to step 3 (Registry Resolution)
57
64
 
65
+ **Otherwise — input looks like free-form prose, not a target:**
66
+
67
+ The user typed something like "I want a skill that helps with onboarding" or "build me a brainstorming workflow" — quick-skill cannot resolve that to a GitHub repository. Instead of falling through to a registry-failure HARD HALT, redirect with a sibling-skill suggestion:
68
+
69
+ "**This input looks like a description, not a package or URL.** Quick Skill needs a package name (e.g. `lodash`, `@vercel/og`, `requests`) or a GitHub URL (e.g. `https://github.com/lodash/lodash`).
70
+
71
+ If you are describing a skill you want to **create from scratch** rather than compile from existing source:
72
+
73
+ - Run `/skf-create-skill` with a skill brief — full pipeline with provenance tracking and AST-verified exports
74
+ - Or use `bmad-agent-builder` for an interactive skill design session
75
+
76
+ Otherwise, paste the package name or GitHub URL of the library you want to wrap, and quick-skill will resolve it."
77
+
78
+ **GATE [default: HALT]** — In headless mode, emit the same redirect message and HALT with **exit code 3 (resolution-failure)** per the SKILL.md exit-code map. Before exiting, emit the error result contract per SKILL.md "Result Contract on HARD HALT" (`phase: "resolve-target"`, `error.code: "resolution-failure"`, `skill_package: null`). Do not attempt registry lookups against prose input; that wastes ~3-4 round trips and produces a less actionable error message than the redirect above.
79
+
58
80
  ### 3. Registry Resolution
59
81
 
60
- Load {registryResolutionData} for resolution patterns.
82
+ Run the shared resolver against the deterministic registries (npm → PyPI → crates.io). The resolver does the HTTP+JSON+GitHub-URL-extraction work; the LLM only handles the web-search fallback below when needed.
83
+
84
+ **Resolve `{packageResolver}`** from `{packageResolverProbeOrder}`; first existing path wins. If no candidate exists, fall back to the LLM walk of {registryResolutionData} for the full chain.
61
85
 
62
- **Execute the fallback chain in order — stop at first success:**
86
+ ```bash
87
+ python3 {packageResolver} {package_name} --timeout 10
88
+ ```
63
89
 
64
- 1. **npm registry:** Fetch `https://registry.npmjs.org/{package_name}` extract `repository.url`
65
- 2. **PyPI registry:** Fetch `https://pypi.org/pypi/{package_name}/json` — extract `info.project_urls.Source` or `info.home_page`
66
- 3. **crates.io registry:** Fetch `https://crates.io/api/v1/crates/{package_name}` — extract `crate.repository`
67
- 4. **Web search fallback:** Search `"{package_name} github repository"` — look for GitHub URL
90
+ The resolver emits JSON with `status` (`"ok"` or `"fallthrough"`), `resolved_url`, `repo_owner`, `repo_name`, `registry_used`, `registries_tried`, and a per-registry `registry_outcomes` map. Exit 0 means ok; exit 1 means fallthrough.
68
91
 
69
- **If all methods fail HARD HALT:**
92
+ - **On `status: "ok"`**capture `resolved_url`, `repo_name`, and `registry_used` from the JSON. Proceed to §3a.
93
+ - **On `status: "fallthrough"`** — the deterministic chain returned no GitHub URL (every registry replied with 404 / no-github-link / timeout). Fall back to the web-search step from {registryResolutionData} §4: search `"{package_name} github repository"` with a 15s timeout and look for a GitHub URL in the top results. If found, set `resolved_url` and proceed. If web search also returns nothing, HARD HALT below.
94
+
95
+ **If all methods fail — HARD HALT (exit code 3, resolution-failure):**
70
96
 
71
97
  "**Resolution failed.** Could not resolve `{package_name}` to a GitHub repository.
72
98
 
73
- Please check:
99
+ Check:
74
100
  - Is the package name spelled correctly?
75
101
  - Is it a private package?
76
102
  - Is the source hosted on a non-GitHub platform?
77
103
 
78
104
  **Provide the GitHub URL directly to continue.**"
79
105
 
80
- Wait for corrected input. Loop back to step 2.
106
+ In interactive mode, wait for corrected input and loop back to step 2. In headless mode, emit the error result contract per SKILL.md "Result Contract on HARD HALT" (`phase: "resolve-target"`, `error.code: "resolution-failure"`, `skill_package: null`) and exit 3.
107
+
108
+ ### 3a. Verify Target Version Tag (when applicable)
109
+
110
+ Skip this section if `target_version` is null (auto-detect path — version comes from manifest read in step-03).
111
+
112
+ When the user explicitly supplied `@version` in §1b, verify the tag exists in the resolved repo before extraction. Otherwise step-03 silently reads from the default branch while metadata records the requested version — a quiet provenance bug where the SKILL.md claims version 0.5.0 but the exports actually came from main.
113
+
114
+ Probe both with-and-without v-prefix (the v-prefix is conventional but not universal across ecosystems):
115
+
116
+ ```bash
117
+ gh api repos/{owner}/{repo}/git/ref/tags/{target_version} --silent \
118
+ || gh api repos/{owner}/{repo}/git/ref/tags/v{target_version} --silent
119
+ ```
120
+
121
+ **If a matching tag is found** — set `source_ref` to the matching ref (with v-prefix when that variant matched). Step-03's ref-aware source reading uses this value to fetch from the tagged commit. Proceed to §4.
122
+
123
+ **If no tag matches** — HARD HALT with **exit code 3 (resolution-failure)**:
124
+
125
+ "**Tag `{target_version}` not found in `{owner}/{repo}`.**
126
+
127
+ The version was parsed from your `@version` suffix but does not exist as a tag in the resolved repository. Quick-skill cannot extract from a version with no commit pointer — the result would be sourced from the default branch but labelled `{target_version}` in metadata.
128
+
129
+ Recent tags in this repo:
130
+ {list top 5 from `gh api repos/{owner}/{repo}/tags --paginate=false`, or "(none — repo has no tags; omit @version to auto-detect from default branch)"}
131
+
132
+ Re-run with one of these tags, or omit the `@version` suffix to auto-detect from the default branch."
133
+
134
+ Before exiting, emit the error result contract per SKILL.md "Result Contract on HARD HALT" (`phase: "resolve-target"`, `error.code: "resolution-failure"`, `error.details: {requested_version: "{target_version}", available_tags: [...top 5]}`, `skill_package: null`). In headless mode, exit immediately; do not loop.
81
135
 
82
136
  ### 4. Detect Language
83
137
 
84
138
  Determine primary language from:
85
139
 
86
- 1. **User-provided language hint** (overrides detection)
87
- 2. **Manifest file presence** (check via GitHub API or web browsing):
140
+ 1. **User-provided language hint** (overrides detection — skip the ambiguity gate below).
141
+ 2. **Manifest-presence scan** check the repo root for ALL of these (priority order = first hit wins on auto-pick):
88
142
  - `package.json` → JavaScript/TypeScript
89
143
  - `pyproject.toml` or `setup.py` → Python
90
144
  - `Cargo.toml` → Rust
@@ -92,7 +146,21 @@ Determine primary language from:
92
146
  - `pom.xml` → Java (or Kotlin if `src/main/kotlin/` is present)
93
147
  - `build.gradle.kts` or `build.gradle` → Kotlin (or Java if only `src/main/java/` is present)
94
148
 
95
- Set `language` to detected language.
149
+ Collect every match into `detected_languages`.
150
+
151
+ 3. **Single-language case** (`len(detected_languages) <= 1`) — set `language` to the detected value (or HALT in step-01 §3 if zero matches).
152
+
153
+ 4. **Multi-language case** (`len(detected_languages) > 1`) — surface the choice rather than silently picking the first match. Multi-language repos (Python + JS bindings, or monorepos with mixed manifests) otherwise produce a skill for whichever manifest probe hits first, with no signal that the user might have wanted the other one.
154
+
155
+ "**`{repo_name}` has manifests for multiple languages:** {detected_languages}.
156
+
157
+ Primary guess: **{first_match}** (manifest-priority order). If you wanted a different language, abort and re-run with `--language-hint <lang>` or with the optional language hint at step-01 §1.
158
+
159
+ Select: [C] Continue with `{first_match}` · [A] Abort"
160
+
161
+ - **IF C** — log "user accepted multi-manifest pick: `{first_match}`" and set `language` to the first match.
162
+ - **IF A** — HARD HALT with **exit code 3 (resolution-failure)**: "Aborted to disambiguate language. Re-run with a `language_hint`." Before exiting, emit the error result contract per SKILL.md "Result Contract on HARD HALT" (`phase: "resolve-target"`, `error.code: "resolution-failure"`, `error.details: {detected_languages: [...], auto_pick: "{first_match}"}`, `skill_package: null`).
163
+ - **GATE [default: C]** — Headless mode auto-proceeds with the manifest-priority pick; record `detected_languages` and `language_resolution: "auto-picked-first"` in the extraction context so the result contract surfaces the ambiguity downstream.
96
164
 
97
165
  ### 5. Confirm Resolution
98
166
 
@@ -4,6 +4,8 @@ nextStepFile: './step-03-quick-extract.md'
4
4
 
5
5
  # Step 2: Ecosystem Check
6
6
 
7
+ Communicate with the user in `{communication_language}`.
8
+
7
9
  ## STEP GOAL:
8
10
 
9
11
  To query the agentskills.io ecosystem for an existing official skill matching the resolved target, preventing unnecessary duplication. This is an advisory gate — it never blocks the workflow on failure.
@@ -16,8 +18,6 @@ To query the agentskills.io ecosystem for an existing official skill matching th
16
18
 
17
19
  ## MANDATORY SEQUENCE
18
20
 
19
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise unless user explicitly requests a change.
20
-
21
21
  ### 1. Query Ecosystem
22
22
 
23
23
  Search for an existing official skill matching `{repo_name}` in the agentskills.io ecosystem.
@@ -83,5 +83,5 @@ An official skill already exists. You can:
83
83
 
84
84
  ## CRITICAL STEP COMPLETION NOTE
85
85
 
86
- ONLY WHEN ecosystem check completes (match with user choice, no-match, or skip) will you load and read fully `{nextStepFile}` to execute source extraction.
86
+ ONLY WHEN ecosystem check completes (match with user choice, no-match, or skip) will you load and read fully `{nextStepFile}` to proceed to source extraction.
87
87
 
@@ -1,9 +1,14 @@
1
1
  ---
2
2
  nextStepFile: './step-04-compile.md'
3
+ publicApiExtractorProbeOrder:
4
+ - '{project-root}/_bmad/skf/shared/scripts/skf-extract-public-api.py'
5
+ - '{project-root}/src/shared/scripts/skf-extract-public-api.py'
3
6
  ---
4
7
 
5
8
  # Step 3: Quick Extract
6
9
 
10
+ Communicate with the user in `{communication_language}`.
11
+
7
12
  ## STEP GOAL:
8
13
 
9
14
  To read the resolved GitHub repository source and extract the public API surface using surface-level source reading (no AST). Produces an extraction inventory of exports, descriptions, and manifest data for compilation.
@@ -16,10 +21,10 @@ To read the resolved GitHub repository source and extract the public API surface
16
21
 
17
22
  ## MANDATORY SEQUENCE
18
23
 
19
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise unless user explicitly requests a change.
20
-
21
24
  **Ref-aware source reading:** When `source_ref` is set from tag resolution (see step-01), append `?ref={source_ref}` to all GitHub API content and tree requests (e.g., `gh api repos/{owner}/{repo}/contents/{path}?ref={source_ref}`) to read from the tagged version. When using web browsing, use the tagged URL format (e.g., `github.com/{owner}/{repo}/blob/{source_ref}/{path}`). This ensures extraction reads from the same source version resolved during tag resolution.
22
25
 
26
+ **Parallel-fetch directive:** §1 (README), §2 (manifest), and §3 (entry-point exports) read independent files from the same `?ref={source_ref}` and are safe to issue as one batched tool-call message rather than three sequential round trips. For multi-module Maven (`<modules>`) and multi-project Gradle (`include(...)`) builds, also fetch all submodule `pom.xml` / `build.gradle[.kts]` files in parallel rather than serially per module — N module fetches collapse to O(1) wall-clock time.
27
+
23
28
  ### 1. Read README
24
29
 
25
30
  Read `README.md` from the repository root via web browsing.
@@ -32,59 +37,74 @@ Extract:
32
37
 
33
38
  If README is unavailable, note and continue.
34
39
 
35
- ### 2. Read Manifest File
40
+ ### 1.5. Repo-Shape Sniff
36
41
 
37
- Based on detected language, read the primary manifest file:
42
+ After the README has loaded, classify the repo shape from the available signals before committing further effort to extraction. Quick-skill is designed to wrap a library; non-library repos sail through silently today and produce low-quality skills the user only notices via the description field after compilation.
38
43
 
39
- - **JavaScript/TypeScript:** `package.json` extract name, version, description, main, exports, dependencies
40
- - **Python:** `pyproject.toml` or `setup.py` — extract project name, version, description, dependencies
41
- - **Rust:** `Cargo.toml` — extract package name, version, description, dependencies
42
- - **Go:** `go.mod` — extract module path, require list
43
- - **Java (Maven):** `pom.xml` — extract `<groupId>`, `<artifactId>`, `<version>`, `<description>`, direct `<dependencies>`. For multi-module projects, also enumerate `<modules><module>` entries and read each submodule's `pom.xml` (treat each as a logical unit in the extraction inventory).
44
- - **Kotlin / Java (Gradle):** `build.gradle.kts` or `build.gradle` — extract `group`, `version`, `description` (when declared), and top-level `dependencies { }` block. For multi-project builds, read `settings.gradle[.kts]` for `include(...)` entries and repeat per subproject.
44
+ **Classify as one of:**
45
45
 
46
- Extract:
47
- - **Package metadata:** name, version, description
48
- - **Entry points:** main, exports, module fields
49
- - **Key dependencies:** direct dependencies list
46
+ - **library** (default) — README has installation / usage / API content; manifest at root with publishable metadata. Proceed normally.
47
+ - **awesome-list** README H1 contains "awesome" (case-insensitive) or `awesome-` is in the repo name; README body is dominated by curated bullet links of the form `- [name](url) — desc`; no manifest at root.
48
+ - **docs-site / website** — README is short (under ~50 non-empty lines) and primarily points elsewhere ("See https://… for docs"); root has no manifest, or only a docs-framework manifest (e.g. `docusaurus.config.js`, `astro.config.mjs`, `mkdocs.yml`).
49
+ - **examples-only / tutorial** README explicitly labels the repo as examples or a tutorial ("Code examples for…", "Tutorial: …", "Learn X by building Y"); typically no published package; many small standalone files instead of a single API surface.
50
+
51
+ **If a non-library shape is detected** — soft-warn and gate before continuing:
52
+
53
+ "**Heads up — `{repo_name}` looks like a `{shape}` repo, not a library.**
54
+
55
+ Quick-skill is designed to wrap a library's public API. The compiled SKILL.md will likely have a thin Description and an empty Key Exports list. You can continue anyway, or abort and pick a target library.
56
+
57
+ Select: [C] Continue anyway · [A] Abort"
58
+
59
+ - **IF C** — log "user accepted `{shape}` shape" and proceed to §2. Set `extraction_inventory.repo_shape` to the detected shape so the result contract carries the signal for automators.
60
+ - **IF A** — HARD HALT with **exit code 3 (resolution-failure)** per the SKILL.md exit-code map: "Aborted. `{shape}` repos are best wrapped manually with `/skf-create-skill` from a brief, not auto-extracted." Before exiting, emit the error result contract per SKILL.md "Result Contract on HARD HALT" (`phase: "quick-extract"`, `error.code: "resolution-failure"`, `error.details: {repo_shape: "{shape}"}`, `skill_package: null`).
61
+
62
+ **GATE [default: C]** — In headless mode, log "headless: detected `{shape}` repo, continuing anyway" and proceed; the result contract's `summary.repo_shape` carries the signal so automators can flag low-quality outputs without re-parsing logs.
63
+
64
+ ### 2. Fetch Source Files
65
+
66
+ Fetch the manifest file and the top-level entry-point file(s) for the detected language. The helper invoked in §3 does pure parsing — no I/O — so this step does the fetch work using `gh api` (preferred when source_ref is set) or web browsing.
67
+
68
+ | Language | Manifest | Entry-point files (quick mode) |
69
+ | --- | --- | --- |
70
+ | JavaScript / TypeScript | `package.json` | `index.{js,ts}`, `src/index.{ts,js}`, or the file pointed to by the `main` field |
71
+ | Python | `pyproject.toml` or `setup.py` | `__init__.py`, `src/{package}/__init__.py` |
72
+ | Rust | `Cargo.toml` | `src/lib.rs` |
73
+ | Go | `go.mod` | top-level `*.go` files (3–5 best-effort) |
74
+ | Java (Maven) | `pom.xml` | top-level `*.java` files under `src/main/java/<groupId-as-path>/` (3–5 best-effort) |
75
+ | Kotlin (Gradle) | `build.gradle.kts` or `build.gradle` | top-level `*.kt` files under `src/main/kotlin/` (3–5 best-effort); also fetch `settings.gradle[.kts]` for `include(...)` entries when present |
50
76
 
51
- ### 3. Scan Top-Level Exports
77
+ **If `scope_hint` provided:** focus the entry-point fetch on the specified directories instead of repo root.
52
78
 
53
- Based on language and entry points from manifest, read the primary export files:
79
+ For multi-module Maven (`<modules>`) and multi-project Gradle (`include(...)`) builds, fetch the parent manifest first, then loop §2+§3 per module. Sub-module fetches are safe to issue as one batched tool-call message — N module fetches collapse to O(1) wall-clock time.
54
80
 
55
- **JavaScript/TypeScript:**
56
- - Read `index.js`, `index.ts`, `src/index.ts`, or `main` field from package.json
57
- - Extract: `export` statements, `module.exports` assignments
58
- - Pattern: lines matching `export (const|function|class|default|type|interface)`
81
+ ### 3. Parse Manifest and Scan Exports
59
82
 
60
- **Python:**
61
- - Read `__init__.py` or `src/{package}/__init__.py`
62
- - Extract: `__all__` list, top-level function/class definitions
63
- - Pattern: lines matching `def |class |__all__`
83
+ Run the shared extractor against the contents fetched in §2. The helper does manifest parse + export scan in one invocation and emits a structured envelope ready to feed §4's inventory.
64
84
 
65
- **Rust:**
66
- - Read `src/lib.rs`
67
- - Extract: `pub fn`, `pub struct`, `pub enum`, `pub trait` declarations
68
- - Pattern: lines matching `pub (fn|struct|enum|trait|mod)`
85
+ **Resolve `{publicApiExtractor}`** from `{publicApiExtractorProbeOrder}`; first existing path wins. If no candidate exists, fall back to in-prompt parsing (the legacy per-language regex tables that this section replaces).
69
86
 
70
- **Go:**
71
- - Read exported functions from top-level `.go` files
72
- - Extract: capitalized function names (Go export convention)
73
- - Pattern: lines matching `func [A-Z]`
87
+ Build the input payload from §2's fetched files and pipe it to the helper:
74
88
 
75
- **Java:**
76
- - Read `src/main/java/**/*.java` (focus on top-level packages declared in the manifest's `groupId`)
77
- - Extract: public classes, public methods, and framework annotations that mark API surfaces (Spring, Jakarta EE, CDI)
78
- - Pattern: lines matching `@(RestController|Service|Component|Configuration|Controller|Repository|Bean)|public (class|interface|enum|record) |public .* \(`
79
- - **Multi-module Maven:** iterate the `<module>` entries discovered in §2 and repeat the scan per module, reading each `{module}/src/main/java/**/*.java`
89
+ ```bash
90
+ echo '{"language":"<lang>","manifest":{"path":"<rel>","content":"<...>"},"entries":[{"path":"<rel>","content":"<...>"},...],"mode":"quick"}' \
91
+ | python3 {publicApiExtractor} --mode quick
92
+ ```
93
+
94
+ Where `<lang>` is one of `js`, `ts`, `javascript`, `typescript`, `python`, `rust`, `go`, `java`, `kotlin`. The helper accepts arbitrarily many `entries` items and aggregates exports across them.
95
+
96
+ The helper emits JSON on stdout with:
97
+
98
+ - `package_name`, `version`, `description` — parsed from the manifest
99
+ - `exports[]` — `{name, type, source_file}` per discovered top-level public symbol
100
+ - `dependencies[]` — declared direct dependencies
101
+ - `modules[]` — for Maven `<modules>` and Gradle `include(...)`, the names of sub-modules to iterate (loop §2+§3 per entry)
102
+ - `extra` — language-specific extras (e.g. `group_id` for Maven)
103
+ - `warnings[]` — manifest parse failures or scanner errors (advisory only; the envelope is still valid)
80
104
 
81
- **Kotlin:**
82
- - Read `src/main/kotlin/**/*.kt` (Kotlin defaults to `public` visibility — omit `internal`/`private` declarations)
83
- - Extract: top-level `fun`, `class`, `object`, `interface` declarations
84
- - Pattern: lines matching `^(fun |class |object |interface |data class |sealed class |@(RestController|Service|Component|Configuration|Controller))`
85
- - **Multi-project Gradle:** iterate the `include(...)` entries discovered in §2 and repeat the scan per subproject
105
+ Capture the helper's output into the extraction context. The shape of the envelope is the same for every language; §4 builds the inventory from it without per-language branching.
86
106
 
87
- **If scope_hint provided:** Focus reading on the specified directories instead of root.
107
+ **Multi-module loop:** when `modules[]` is non-empty, fetch each sub-module's manifest + entry-point files (§2) and re-invoke the helper per module (§3), aggregating `exports[]` across all module envelopes. The aggregated `exports[]`, `dependencies[]`, and a single resolved `package_name` (from the parent manifest) feed §4.
88
108
 
89
109
  ### 4. Build Extraction Inventory
90
110
 
@@ -107,6 +127,29 @@ extraction_inventory:
107
127
  - Use README description and features as fallback content
108
128
  - Note: "No exports detected — SKILL.md will be based on README content only"
109
129
 
130
+ ### 4.5. Zero-Exports Soft Gate (rescue mode)
131
+
132
+ Run this gate **only when** `extraction_inventory.exports.length == 0` AND `extraction_inventory.description` is empty (no usable README content either). When either is non-empty, the README-fallback in §4 produces a usable skill and this section is skipped.
133
+
134
+ When both are empty, the compiled SKILL.md would be effectively empty — no API surface to document and no description to fall back on. Offer the user a chance to retry with hints before producing a degenerate output:
135
+
136
+ "**Extraction yielded zero exports and no README description.**
137
+
138
+ The compiled SKILL.md would be effectively empty — no API surface to document and no description to fall back on.
139
+
140
+ Common causes:
141
+ - Wrong scope (extraction read the repo root, but the public API lives in a subdir)
142
+ - Wrong language (manifest probe picked the test/build language, not the lib language)
143
+ - Repo lays out exports unconventionally (e.g., not in `src/index.*` or `lib.rs`)
144
+
145
+ Select: [R] Retry with new hints · [P] Proceed anyway (low-confidence skill) · [A] Abort"
146
+
147
+ - **IF R** — prompt for new `scope_hint` ("New scope hint (e.g. `src/server/`):") and optional new `language_hint` ("New language hint (or empty to keep `{language}`):"). Update the extraction context with the new hints, then **re-execute step-03 from §1** with the new values. Discards the prior empty inventory.
148
+ - **IF P** — log "user accepted zero-exports outcome" and proceed to §5. The compiled skill will be README-content-only with confidence `low`. Record `zero_exports_rescue: "user-accepted"` in the inventory so the result contract summary surfaces it.
149
+ - **IF A** — HARD HALT with **exit code 3 (resolution-failure)**: "Aborted. Run `/skf-create-skill` from a brief if you want a guided extraction with provenance tracking." Before exiting, emit the error result contract per SKILL.md "Result Contract on HARD HALT" (`phase: "quick-extract"`, `error.code: "resolution-failure"`, `error.details: {exports_found: 0, description_empty: true, language: "{language}", scope: "{scope_hint or 'entire repo'}"}`, `skill_package: null`).
150
+
151
+ **GATE [default: P]** — In headless mode, log "headless: zero exports + empty description, proceeding with low-confidence skill" and proceed; record `zero_exports_rescue: "auto-proceeded"` in the result contract summary so batch automators can re-queue these targets with stricter hints downstream. [P] preserves the pre-rescue behaviour for unattended pipelines.
152
+
110
153
  ### 5. Report Extraction Summary
111
154
 
112
155
  "**Extraction complete:**