bmad-module-skill-forge 1.9.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +10 -5
- package/docs/_data/pinned.yaml +2 -2
- package/docs/agents.md +11 -2
- package/docs/architecture.md +5 -4
- package/docs/bmad-synergy.md +30 -7
- package/docs/campaign.md +172 -0
- package/docs/examples.md +7 -3
- package/docs/forge-auto.md +90 -0
- package/docs/getting-started.md +9 -0
- package/docs/how-it-works.md +6 -4
- package/docs/index.md +4 -3
- package/docs/skill-model.md +1 -1
- package/docs/troubleshooting.md +17 -1
- package/docs/verifying-a-skill.md +2 -2
- package/docs/why-skf.md +1 -1
- package/docs/workflows.md +64 -24
- package/package.json +2 -2
- package/src/module-help.csv +2 -1
- package/src/shared/data/language-corpora.json +32 -0
- package/src/shared/references/pipeline-contracts.md +6 -3
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
- package/src/shared/scripts/skf-detect-docs.py +8 -3
- package/src/shared/scripts/skf-detect-language.py +5 -3
- package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
- package/src/shared/scripts/skf-extract-public-api.py +53 -0
- package/src/shared/scripts/skf-language-corpora.py +100 -0
- package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-shape-detect.py +576 -25
- package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
- package/src/shared/scripts/skf-validate-pins.py +2 -2
- package/src/shared/scripts/skf-write-skill-brief.py +21 -5
- package/src/skf-analyze-source/SKILL.md +1 -1
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- package/src/skf-analyze-source/references/step-auto-scope.md +196 -44
- package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
- package/src/skf-audit-skill/references/init.md +1 -1
- package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
- package/src/skf-brief-skill/references/analyze-target.md +7 -4
- package/src/skf-brief-skill/references/confirm-brief.md +0 -1
- package/src/skf-brief-skill/references/gather-intent.md +8 -2
- package/src/skf-brief-skill/references/scope-definition.md +2 -1
- package/src/skf-brief-skill/references/step-auto-brief.md +21 -4
- package/src/skf-brief-skill/references/step-auto-validate.md +2 -2
- package/src/skf-brief-skill/references/write-brief.md +2 -3
- package/src/skf-campaign/SKILL.md +190 -0
- package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
- package/src/skf-campaign/customize.toml +73 -0
- package/src/skf-campaign/manifest.yaml +11 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
- package/src/skf-campaign/references/health-check.md +35 -0
- package/src/skf-campaign/references/step-01-setup.md +122 -0
- package/src/skf-campaign/references/step-02-strategy.md +97 -0
- package/src/skf-campaign/references/step-03-pins.md +56 -0
- package/src/skf-campaign/references/step-04-provenance.md +63 -0
- package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
- package/src/skf-campaign/references/step-06-batch.md +87 -0
- package/src/skf-campaign/references/step-07-capstone.md +63 -0
- package/src/skf-campaign/references/step-08-verify.md +75 -0
- package/src/skf-campaign/references/step-09-refine.md +83 -0
- package/src/skf-campaign/references/step-10-export.md +106 -0
- package/src/skf-campaign/references/step-11-maintenance.md +84 -0
- package/src/skf-campaign/references/step-resume.md +114 -0
- package/src/skf-campaign/scripts/.gitkeep +0 -0
- package/src/skf-campaign/scripts/campaign-deps.py +235 -0
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
- package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
- package/src/skf-campaign/scripts/campaign-report.py +249 -0
- package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
- package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
- package/src/skf-campaign/templates/campaign-report-template.md +54 -0
- package/src/skf-campaign/templates/kickoff-template.md +48 -0
- package/src/skf-create-skill/SKILL.md +1 -1
- package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
- package/src/skf-create-skill/references/compile.md +4 -1
- package/src/skf-create-skill/references/extract.md +9 -1
- package/src/skf-create-skill/references/extraction-patterns.md +3 -1
- package/src/skf-create-skill/references/generate-artifacts.md +11 -2
- package/src/skf-create-skill/references/health-check.md +2 -2
- package/src/skf-create-skill/references/report.md +17 -1
- package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
- package/src/skf-create-skill/references/step-doc-rot.md +4 -4
- package/src/skf-create-skill/references/step-doc-sources.md +11 -2
- package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
- package/src/skf-create-skill/references/validate.md +12 -3
- package/src/skf-drop-skill/SKILL.md +2 -2
- package/src/skf-drop-skill/references/execute.md +20 -9
- package/src/skf-drop-skill/references/report.md +2 -0
- package/src/skf-drop-skill/references/select.md +7 -2
- package/src/skf-forger/SKILL.md +12 -7
- package/src/skf-refine-architecture/SKILL.md +2 -1
- package/src/skf-refine-architecture/references/compile.md +1 -1
- package/src/skf-refine-architecture/references/report.md +1 -1
- package/src/skf-rename-skill/SKILL.md +2 -2
- package/src/skf-rename-skill/references/execute.md +5 -4
- package/src/skf-rename-skill/references/rebuild-context.md +2 -2
- package/src/skf-rename-skill/references/select.md +3 -3
- package/src/skf-setup/SKILL.md +1 -1
- package/src/skf-setup/references/auto-index.md +3 -1
- package/src/skf-setup/references/detect-and-tier.md +4 -0
- package/src/skf-setup/references/report.md +4 -1
- package/src/skf-setup/references/tier-rules.md +1 -1
- package/src/skf-test-skill/references/coverage-check.md +10 -0
- package/src/skf-test-skill/references/init.md +1 -1
- package/src/skf-test-skill/references/source-access-protocol.md +13 -3
- package/src/skf-test-skill/scripts/compute-score.py +4 -3
- package/src/skf-update-skill/customize.toml +0 -9
- package/src/skf-update-skill/references/detect-changes.md +33 -3
- package/src/skf-update-skill/references/health-check.md +2 -2
- package/src/skf-update-skill/references/init.md +1 -0
- package/src/skf-update-skill/references/re-extract.md +15 -1
- package/src/skf-verify-stack/references/report.md +1 -1
- package/tools/cli/lib/ui.js +3 -2
- package/docs/deepwiki.md +0 -89
package/docs/index.md
CHANGED
|
@@ -60,8 +60,9 @@ npx bmad-module-skill-forge install
|
|
|
60
60
|
Then generate your first skill:
|
|
61
61
|
|
|
62
62
|
```
|
|
63
|
-
@Ferris SF
|
|
64
|
-
@Ferris
|
|
63
|
+
@Ferris SF # Set up your forge
|
|
64
|
+
@Ferris forge-auto <repo-or-url> # Zero-ceremony: a repo or doc URL → verified skill
|
|
65
|
+
@Ferris QS <package> # Or a fast skill from a package name in under a minute
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
See [Getting Started](./getting-started/) for platform support, tier selection, and troubleshooting.
|
|
68
|
+
[forge-auto](./forge-auto/) is the recommended starting point — one command, no configuration. See [Getting Started](./getting-started/) for platform support, tier selection, and troubleshooting.
|
package/docs/skill-model.md
CHANGED
|
@@ -92,7 +92,7 @@ This is useful for comparing skill quality across tiers for the same target:
|
|
|
92
92
|
# 4. Reset to tier_override: ~ (auto-detect)
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
Set `tier_override` to `Quick`, `Forge`, `Forge+`, or `Deep`. Set to `~` (null) to return to auto-detection. The override is respected by all tier-aware workflows (CS, SS, US, AS, TS).
|
|
95
|
+
Set `tier_override` to `Quick`, `Forge`, `Forge+`, or `Deep`. Set to `~` (null) to return to auto-detection. The override is respected by all tier-aware workflows (AN, BS, CS, SS, US, AS, TS).
|
|
96
96
|
|
|
97
97
|
---
|
|
98
98
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -27,7 +27,7 @@ If setup reports that ast-grep was not detected, install it to unlock the Forge
|
|
|
27
27
|
|
|
28
28
|
### "No skill brief found"
|
|
29
29
|
|
|
30
|
-
Run `@Ferris BS` first to create a skill brief, or use `@Ferris QS` for brief-less generation. `CS` requires either a brief or
|
|
30
|
+
Run `@Ferris BS` first to create a skill brief, or use `@Ferris QS` for brief-less generation. `CS` always requires a brief — either a `skill-brief.yaml` produced by `BS` (or `AN`), or pass the skill name so it resolves one from your forge-data folder. For brief-less generation use `QS`.
|
|
31
31
|
|
|
32
32
|
### "Ecosystem check: official skill exists"
|
|
33
33
|
|
|
@@ -41,6 +41,22 @@ Quick tier reads source without AST analysis, so signatures are read directly fr
|
|
|
41
41
|
|
|
42
42
|
Install [cocoindex-code](https://github.com/cocoindex-io/cocoindex-code) to unlock the Forge+ tier. CCC indexes your codebase and pre-ranks files by semantic relevance before AST extraction, improving coverage on projects with 500+ files.
|
|
43
43
|
|
|
44
|
+
### `@Ferris deepwiki` shows a deprecation notice
|
|
45
|
+
|
|
46
|
+
The auto pipeline was briefly named `deepwiki`; it's now [`forge-auto`](../forge-auto/) — renamed to avoid confusion with the DeepWiki MCP, since the pipeline compiles a verified skill from source and does **not** call that MCP. `deepwiki` still works (it resolves to `forge-auto`) but prints a one-time notice. Switch your commands to `@Ferris forge-auto <repo-or-doc-url>`.
|
|
47
|
+
|
|
48
|
+
### `@Ferris onboard` returns an error
|
|
49
|
+
|
|
50
|
+
The `onboard` alias was removed. Its replacement is [`forge-auto`](../forge-auto/), which does everything `onboard` did plus auto-scope, auto-brief, and a stricter 90% quality gate. Run `@Ferris forge-auto <repo-or-doc-url>` instead.
|
|
51
|
+
|
|
52
|
+
### forge-auto halted at the Test stage
|
|
53
|
+
|
|
54
|
+
forge-auto runs Test Skill with a stricter **90% quality threshold** (vs the default 80%), so a skill that scores below 90% halts at TS with a gap report rather than exporting a weak skill. Run `@Ferris US` to address the gaps it lists, then `@Ferris TS EX` to re-test and export. If 90% is stricter than you need, run the individual workflows or `forge` instead, which use the default threshold.
|
|
55
|
+
|
|
56
|
+
### My campaign stopped partway — how do I resume?
|
|
57
|
+
|
|
58
|
+
Campaign is designed for exactly this. State lives in `_campaign-state.yaml` on disk, so context death, a session timeout, or a machine restart loses nothing. Run `@Ferris campaign resume` — Ferris validates the state file, skips completed skills, and picks up from the next incomplete skill in dependency order. If the state file is corrupted, Ferris falls back to the `.bak` copy automatically. To re-process one specific skill, use `@Ferris campaign resume --from=<skill>`.
|
|
59
|
+
|
|
44
60
|
---
|
|
45
61
|
|
|
46
62
|
## Still stuck?
|
|
@@ -61,7 +61,7 @@ Every file in the per-skill output carries a specific job. Here's the lookup tab
|
|
|
61
61
|
| Which symbols are documented and where did each come from? | `forge-data/{name}/{version}/provenance-map.json` |
|
|
62
62
|
| What AST patterns were used for extraction? | `forge-data/{name}/{version}/extraction-rules.yaml` |
|
|
63
63
|
| What signatures, types, and examples did the extractor actually capture? | `forge-data/{name}/{version}/evidence-report.md` |
|
|
64
|
-
| How was the skill scored? Show me the math. | `forge-data/{name}/{version}/test-report-{name}.md` |
|
|
64
|
+
| How was the skill scored? Show me the math. | `forge-data/{name}/{version}/test-report-{name}-{run_id}.md` (per-run, run-id-suffixed — newest wins) |
|
|
65
65
|
| How was the skill scoped, and what was deliberately left out? | `forge-data/{name}/skill-brief.yaml` |
|
|
66
66
|
|
|
67
67
|
Everything a reader needs to reconstruct the compilation is in the two sibling directories: `skills/` ships to consumers, `forge-data/` is the audit trail.
|
|
@@ -169,7 +169,7 @@ score >= threshold → PASS → Recommend export-skill
|
|
|
169
169
|
score < threshold → FAIL → Recommend update-skill
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
-
The default threshold is **80%**. Pipeline aliases declare their own defaults: `
|
|
172
|
+
The default threshold is **80%**. Pipeline aliases declare their own defaults: `forge-auto` targets **90%**, `forge` and `forge-quick` target **80%**. You can override any default with a custom threshold (e.g., `TS[min:85]`).
|
|
173
173
|
|
|
174
174
|
When a skill scores between 80% and its target threshold (e.g., 82% against a 90% target), the soft gate falls back to the 80% floor — the skill passes, but an evidence report is written to `forge-data/{skill}/{version}/evidence-report-fallback.md` documenting the quality compromise.
|
|
175
175
|
|
package/docs/why-skf.md
CHANGED
|
@@ -52,7 +52,7 @@ See the [Verifying a Skill](../verifying-a-skill/) page for the full three-step
|
|
|
52
52
|
|
|
53
53
|
### The curious developer
|
|
54
54
|
|
|
55
|
-
Your agent just hallucinated a method that doesn't exist, again. You want this to stop, and you don't want to read a
|
|
55
|
+
Your agent just hallucinated a method that doesn't exist, again. You want this to stop, and you don't want to read a long architecture reference before running your first command.
|
|
56
56
|
→ Start with [Getting Started](../getting-started/).
|
|
57
57
|
|
|
58
58
|
### The BMAD user
|
package/docs/workflows.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Workflows
|
|
3
|
-
description: All
|
|
3
|
+
description: All 15 SKF workflows with commands, steps, and connection diagram
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Trigger workflows by typing commands to [Ferris](../agents/). See [Concepts](../concepts/) for definitions.
|
|
@@ -24,7 +24,10 @@ Trigger workflows by typing commands to [Ferris](../agents/). See [Concepts](../
|
|
|
24
24
|
**Flags:**
|
|
25
25
|
|
|
26
26
|
- `--require-tier=<Quick|Forge|Forge+|Deep>` — fail-fast for CI: if the calculated tier does not satisfy the requested tier (tool-prerequisite check, not a name comparison — Deep does NOT subsume Forge+ because Deep does not require ccc), the workflow halts with a "REQUIRED TIER NOT MET" block and exits without chaining to the health check. Pipelines branch on the JSON envelope's `require_tier_satisfied` field.
|
|
27
|
-
- `--
|
|
27
|
+
- `--orphan-action=<keep|remove>` — resolve the orphan QMD-collection removal gate non-interactively, even outside `--headless`.
|
|
28
|
+
- `--ccc-skip-index` — skip CCC indexing (envelope `ccc_index.status` becomes `"skipped"`) — the fast re-probe lane to refresh the detected tier without paying the full re-index cost.
|
|
29
|
+
- `--quiet` — suppress the human-readable FORGE STATUS banner and emit the envelope only.
|
|
30
|
+
- `--headless` / `-H` — see [Headless Mode](#headless-mode) below. For `/skf-setup` specifically, headless mode emits a single-line `SKF_SETUP_RESULT_JSON: {…}` envelope to stdout (schema-locked, includes `status` — the primary branch field — plus `tier`, `previous_tier`, `tier_changed`, `tools`, `tools_added`/`removed`, `files_written`, `warnings`, `error`) and SUPPRESSES the human-readable banner — the entire payload pipelines need is on one parseable line.
|
|
28
31
|
|
|
29
32
|
**Agent:** Ferris (Architect mode)
|
|
30
33
|
|
|
@@ -80,6 +83,8 @@ Trigger workflows by typing commands to [Ferris](../agents/). See [Concepts](../
|
|
|
80
83
|
|
|
81
84
|
**Purpose:** Brief-less fast skill with package-to-repo resolution.
|
|
82
85
|
|
|
86
|
+
**Note:** QS is **tier-unaware** — it always runs at community tier and does not use ast-grep, CCC, or QMD even when your forge is configured at Forge+/Deep. For tier-aware compilation, use `BS → CS`, `forge`, or `forge-auto`. See [Skill Model](../skill-model/).
|
|
87
|
+
|
|
83
88
|
**When to Use:** When you need a skill quickly — no brief needed. Accepts package names or GitHub URLs. Append `@version` to target a specific version (e.g., `@Ferris QS cognee@1.0.0`).
|
|
84
89
|
|
|
85
90
|
**Key Steps:** Resolve target → Ecosystem check → Quick extract → Compile → Validate → Write
|
|
@@ -159,7 +164,7 @@ Trigger workflows by typing commands to [Ferris](../agents/). See [Concepts](../
|
|
|
159
164
|
|
|
160
165
|
**Key Steps:** Load skill → Detect mode → Coverage check → Coherence check → External validation (skill-check, tessl) → Hard gate → Score → Gap report
|
|
161
166
|
|
|
162
|
-
**Scored Categories:** Export Coverage (36%), Signature Accuracy (22%), Type Coverage (14%), Coherence (18%), External Validation (10%). Default pass threshold: **80%** (per-pipeline defaults:
|
|
167
|
+
**Scored Categories:** Export Coverage (36%), Signature Accuracy (22%), Type Coverage (14%), Coherence (18%), External Validation (10%). Default pass threshold: **80%** (per-pipeline defaults: forge-auto 90%, forge 80%). Pass routes to Export Skill; fail routes to Update Skill with a gap report. See [Completeness Scoring](../verifying-a-skill/#how-the-score-is-computed) for the full formula and tier adjustments.
|
|
163
168
|
|
|
164
169
|
**Agent:** Ferris (Audit mode)
|
|
165
170
|
|
|
@@ -211,6 +216,22 @@ Trigger workflows by typing commands to [Ferris](../agents/). See [Concepts](../
|
|
|
211
216
|
|
|
212
217
|
---
|
|
213
218
|
|
|
219
|
+
## Orchestration Workflows
|
|
220
|
+
|
|
221
|
+
### Campaign Orchestration
|
|
222
|
+
|
|
223
|
+
**Command:** `@Ferris campaign`
|
|
224
|
+
|
|
225
|
+
**Purpose:** Orchestrate multi-library skill production across sessions with dependency tracking and resume.
|
|
226
|
+
|
|
227
|
+
**When to Use:** When you need to produce 15+ coordinated skills with dependency ordering — too many for manual one-at-a-time pipeline runs.
|
|
228
|
+
|
|
229
|
+
**Key Steps:** Setup → Strategy → Pin Validation → Provenance → Skill Loop → Tier B Batch → Capstone → Verify → Refine → Export → Maintenance
|
|
230
|
+
|
|
231
|
+
**Agent:** Ferris (Management mode)
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
214
235
|
## Management Workflows
|
|
215
236
|
|
|
216
237
|
### Rename Skill (RS)
|
|
@@ -280,21 +301,36 @@ flowchart TD
|
|
|
280
301
|
TS --> EX[Export Skill]
|
|
281
302
|
```
|
|
282
303
|
|
|
283
|
-
|
|
304
|
+
**Campaign orchestration path:**
|
|
305
|
+
|
|
306
|
+
```mermaid
|
|
307
|
+
flowchart TD
|
|
308
|
+
CAMPAIGN[Campaign Orchestration] --> SETUP[Setup + Strategy]
|
|
309
|
+
SETUP --> PINS[Pin Validation + Provenance]
|
|
310
|
+
PINS --> LOOP["Skill Loop<br/>(BS → CS → TS → EX per skill)"]
|
|
311
|
+
LOOP --> BATCH[Tier B Batch]
|
|
312
|
+
BATCH --> CAP[Capstone — Stack Skill]
|
|
313
|
+
CAP --> VER[Verify + Refine]
|
|
314
|
+
VER --> EXPORT[Export — write-gate HALT]
|
|
315
|
+
EXPORT --> MAINT[Maintenance + Campaign Report]
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
> **One workflow per session** (unless using pipeline mode or campaign). Each arrow in the standard and compose-mode diagrams represents a new conversation session. Campaign manages its own multi-session orchestration internally — see [Campaign Orchestration](../campaign/). Clear your context between workflows for best results — or use pipeline mode to chain them automatically. See [Pipeline Mode](#pipeline-mode) below.
|
|
284
319
|
|
|
285
320
|
---
|
|
286
321
|
|
|
287
322
|
## Workflow Categories
|
|
288
323
|
|
|
289
|
-
| Category | Workflows
|
|
290
|
-
| ------------------------- |
|
|
291
|
-
| Core | SF, BS, CS, US
|
|
292
|
-
| Feature | QS, SS, AN
|
|
293
|
-
| Quality | AS, TS
|
|
294
|
-
| Architecture Verification | VS, RA
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
|
324
|
+
| Category | Workflows | Description |
|
|
325
|
+
| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
326
|
+
| Core | SF, BS, CS, US | Setup, brief, create, and update skills |
|
|
327
|
+
| Feature | QS, SS, AN | Quick skill, stack skill, and analyze source |
|
|
328
|
+
| Quality | AS, TS | Detect skill drift (AS) and verify skill completeness (TS) |
|
|
329
|
+
| Architecture Verification | VS, RA | Pre-code architecture feasibility and refinement |
|
|
330
|
+
| Orchestration | Campaign | Multi-library skill production with dependency tracking and resume |
|
|
331
|
+
| Management | RS, DS | Rename and drop skill versions with transactional safety |
|
|
332
|
+
| Utility | EX | Package and export for consumption |
|
|
333
|
+
| In-Agent | WS, KI | WS: show lifecycle position, active briefs, and forge tier; KI: list knowledge fragments (both in-agent, no file-based workflow) |
|
|
298
334
|
|
|
299
335
|
---
|
|
300
336
|
|
|
@@ -313,14 +349,13 @@ Instead of running one workflow per session, you can chain multiple workflows in
|
|
|
313
349
|
|
|
314
350
|
### Pipeline Aliases
|
|
315
351
|
|
|
316
|
-
The `
|
|
352
|
+
The `forge-auto` alias is the recommended way to create skills — one command, zero configuration. It chains five workflows with auto-mode flags so you get a verified skill without touching a brief or scope file.
|
|
317
353
|
|
|
318
354
|
| Alias | Expands To | First Workflow | Required Target |
|
|
319
355
|
| ------------- | -------------------------------------- | -------------- | -------------------------------------------- |
|
|
320
|
-
| `
|
|
356
|
+
| `forge-auto` | `AN[auto] BS[auto] CS TS[min:90] EX` | AN | GitHub URL, doc URL, or `--pin <version>` |
|
|
321
357
|
| `forge` | `BS CS TS EX` | BS | GitHub URL or local path **+** skill name |
|
|
322
358
|
| `forge-quick` | `QS TS EX` | QS | GitHub URL **or** package name |
|
|
323
|
-
| `onboard` | `AN CS TS EX` *(deprecated — use deepwiki)* | AN | Project path (defaults to current directory) |
|
|
324
359
|
| `maintain` | `AS US TS EX` | AS | Existing skill name |
|
|
325
360
|
|
|
326
361
|
**The first workflow's input contract defines what arguments the pipeline needs.** A bare package name works for `forge-quick` (QS resolves packages via the registry) but **not** for `forge` — BS requires both an unambiguous target (URL or path) and a skill name.
|
|
@@ -337,14 +372,13 @@ The `deepwiki` alias is the recommended way to create wiki skills — one comman
|
|
|
337
372
|
### Examples
|
|
338
373
|
|
|
339
374
|
```
|
|
340
|
-
@Ferris
|
|
341
|
-
@Ferris
|
|
342
|
-
@Ferris
|
|
375
|
+
@Ferris forge-auto https://github.com/honojs/hono — zero-ceremony skill
|
|
376
|
+
@Ferris forge-auto https://docs.example.com — docs-only skill
|
|
377
|
+
@Ferris forge-auto https://github.com/honojs/hono --pin v4.6.0 — pinned version
|
|
343
378
|
@Ferris forge-quick @tanstack/query — QS + TS + EX for TanStack Query
|
|
344
379
|
@Ferris forge https://github.com/topoteretes/cognee cognee — BS + CS + TS + EX, explicit URL + name
|
|
345
380
|
@Ferris forge https://github.com/topoteretes/cognee cognee "public API only" — with scope hint
|
|
346
381
|
@Ferris maintain cocoindex — AS + US + TS + EX for an existing cocoindex skill
|
|
347
|
-
@Ferris onboard — AN + CS + TS + EX on the current project
|
|
348
382
|
```
|
|
349
383
|
|
|
350
384
|
---
|
|
@@ -364,10 +398,10 @@ You can also set `headless_mode: true` in your forge preferences (`_bmad/_memory
|
|
|
364
398
|
**Exception — `/skf-setup` headless emits a single-line JSON envelope.** Unlike other workflows, headless `/skf-setup` SUPPRESSES the human-readable status banner entirely and emits exactly one prefixed line on stdout:
|
|
365
399
|
|
|
366
400
|
```
|
|
367
|
-
SKF_SETUP_RESULT_JSON: {"skf_setup":{"tier":"Deep","previous_tier":"Forge","tier_changed":true,"tools":{...},"tools_added":[...],"tools_removed":[],"config_path":"...","ccc_index":{...},"files_written":[...],"tier_override_active":false,"tier_override_invalid":false,"require_tier_satisfied":null,"warnings":[],"error":null}}
|
|
401
|
+
SKF_SETUP_RESULT_JSON: {"skf_setup":{"status":"success","tier":"Deep","previous_tier":"Forge","tier_changed":true,"tools":{...},"tools_added":[...],"tools_removed":[],"config_path":"...","ccc_index":{...},"files_written":[...],"tier_override_active":false,"tier_override_invalid":false,"require_tier_satisfied":null,"warnings":[],"error":null}}
|
|
368
402
|
```
|
|
369
403
|
|
|
370
|
-
Parent skills and CI pipelines `grep` one line out of the workflow log to learn the outcome — no ASCII-art parsing, no race against the [`forge-tier.yaml`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-setup/references/write-config.md) writer. The envelope schema is versioned at [`src/shared/scripts/schemas/skf-setup-result-envelope.v1.json`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json) and asserted against on every emit.
|
|
404
|
+
Parent skills and CI pipelines `grep` one line out of the workflow log to learn the outcome — no ASCII-art parsing, no race against the [`forge-tier.yaml`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-setup/references/write-config.md) writer. Branch on the top-level `status` field (`success`, `tier_failure`, `write_failure`, or `blocked`) rather than composing the outcome from `require_tier_satisfied` + `error`. The envelope schema is versioned at [`src/shared/scripts/schemas/skf-setup-result-envelope.v1.json`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json) and asserted against on every emit.
|
|
371
405
|
|
|
372
406
|
**Exception — `/skf-quick-skill` headless emits structured progress + result envelopes.** Headless `/skf-quick-skill` runs are first-class building blocks for batch automators. Three operational contracts beyond per-gate auto-proceed:
|
|
373
407
|
|
|
@@ -389,7 +423,7 @@ Parent skills and CI pipelines `grep` one line out of the workflow log to learn
|
|
|
389
423
|
| 3 | resolution-failure |
|
|
390
424
|
| 4 | write-failure |
|
|
391
425
|
| 5 | overwrite-cancelled |
|
|
392
|
-
| 6 |
|
|
426
|
+
| 6 | user-cancelled |
|
|
393
427
|
| 7 | finalize-blocked |
|
|
394
428
|
|
|
395
429
|
3. **Error-variant result contract on every HARD HALT.** A `SKF_QUICK_SKILL_RESULT_JSON: {…}` envelope is emitted on `stderr` (always) and copied to `{skill_package}/quick-skill-result-latest.json` when the skill package is known (HALTs at step 5 §1 onward). The schema and full population rules live in [`src/skf-quick-skill/SKILL.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-quick-skill/SKILL.md) § "Result Contract on HARD HALT".
|
|
@@ -415,11 +449,17 @@ Parent skills and CI pipelines `grep` one line out of the workflow log to learn
|
|
|
415
449
|
|
|
416
450
|
**Presets (`--preset <name>`).** Loads `{sidecar_path}/brief-presets/{name}.yaml` and merges its keys as defaults at step 1 §8; explicit headless args override preset values. The preset file is YAML containing any subset of the headless args above; unknown fields are ignored with a warning. Useful for repeated patterns — e.g. briefing 5 SaaS SDKs that all share `source_authority=community`, `scope_type=full-library`, `scripts_intent=skip`.
|
|
417
451
|
|
|
452
|
+
**Exception — the management and verification workflows emit structured result envelopes too.** Beyond per-gate auto-proceed, Drop Skill, Rename Skill, and Refine Architecture each emit a single-line `SKF_*_RESULT_JSON: {…}` envelope on every terminal exit (`status: "success"` on the happy path, `status: "error"` with a typed `halt_reason` on any HARD HALT) and exit with a stable code so automators branch on the failure class without grepping message text. All three honour the universal `cancel`/`exit`/`:q` affordance at any prompt (exit `6`, `halt_reason: "user-cancelled"`).
|
|
453
|
+
|
|
454
|
+
- **`/skf-drop-skill` (DS)** — exit `2` `input-missing`/`input-invalid`, `4` `write-failure` (covers manifest-write, context-rebuild, and full-purge `delete-failed`), `5` state-conflict (active-version guard). Schema and `halt_reason` list in [`src/skf-drop-skill/SKILL.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-drop-skill/SKILL.md) § "Exit Codes" / "Result Contract (Headless)".
|
|
455
|
+
- **`/skf-rename-skill` (RS)** — exit `2` `input-missing`/`input-invalid`, `4` `write-failure` (`copy-failed`, `write-failed`, `manifest-write-failed`; the §7 context rebuild is best-effort and never halts), `5` state-conflict (name-collision, source-authority, concurrent-run lock). Schema in [`src/skf-rename-skill/SKILL.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-rename-skill/SKILL.md) § "Exit Codes" / "Result Contract (Headless)".
|
|
456
|
+
- **`/skf-refine-architecture` (RA)** — exit `2` `input-missing`/`input-invalid`, `4` `write-failure`, `7` `inventory-unreliable`, `8` `recovery-failed` (durability state insufficient to reconstruct findings, or the compiled doc is missing its `## Refinement Summary`). Schema in [`src/skf-refine-architecture/SKILL.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-refine-architecture/SKILL.md) § "Exit Codes" / "Result Contract (Headless)".
|
|
457
|
+
|
|
418
458
|
---
|
|
419
459
|
|
|
420
460
|
## Terminal Step: Health Check
|
|
421
461
|
|
|
422
|
-
All
|
|
462
|
+
All 15 workflows above share the same final step — a **health check** defined in [`src/shared/health-check.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/shared/health-check.md). This isn't a workflow you invoke directly; there's no command code and no menu entry. Each workflow ends with a dedicated local `step-NN-health-check.md` whose `nextStepFile` points at the shared file, so the health check fires automatically once the main work is done. After the main work is done, Ferris reflects internally on the execution:
|
|
423
463
|
|
|
424
464
|
- Did any step instruction lead the agent astray or cause unnecessary back-and-forth?
|
|
425
465
|
- Was any step ambiguous, forcing the agent to guess?
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-module-skill-forge",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"description": "BMAD module — Turn code and docs into instructions AI agents can actually follow. Progressive capability tiers (Quick/Forge/Forge+/Deep).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bmad",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"test:cli": "node test/test-cli-integration.js",
|
|
54
54
|
"test:install": "node test/test-installation-components.js",
|
|
55
55
|
"test:knowledge": "node test/test-knowledge-base.js",
|
|
56
|
-
"test:python": "uv run --with pytest --with pyyaml --with jsonschema pytest test/test-compute-score-contract.py test/test-skf-preflight.py test/test-skf-skill-inventory.py test/test-skf-validate-output.py test/test-skf-validate-frontmatter.py test/test-skf-manifest-ops.py test/test-skf-rebuild-managed-sections.py test/test-skf-atomic-write.py test/test-skf-severity-classify.py test/test-skf-structural-diff.py test/test-skf-detect-tools.py test/test-skf-forge-tier-rw.py test/test-skf-emit-result-envelope.py test/test-skf-qmd-classify-collections.py test/test-skf-merge-ccc-exclusions.py test/test-skf-resolve-package.py test/test-skf-extract-public-api.py test/test-skf-render-quick-metadata.py test/test-skf-validate-brief-inputs.py test/test-skf-emit-brief-result-envelope.py test/test-skf-write-skill-brief.py test/test-skf-detect-workspaces.py test/test-skf-recommend-scope-type.py test/test-skf-detect-language.py test/test-skf-description-guard.py test/test-skf-detect-scripts-assets.py test/test-skf-hash-content.py test/test-skf-validate-brief-schema.py test/test-skf-check-workspace-drift.py test/test-skf-update-active-symlink.py test/test-skf-build-change-manifest.py test/test-skf-provenance-gap-dispatch.py test/test-skf-resolve-authoritative-files.py test/test-skf-scan-manifests.py test/test-skf-pair-intersect.py test/test-skf-enumerate-stack-skills.py test/test-skf-compare-file-hashes.py test/test-skf-load-provenance.py test/test-skf-scan-skill-md-structure.py test/test-skf-disqualify-candidates.py test/test-skf-chain-reachability.py test/test-skf-shape-detect.py test/test-skf-detect-docs.py test/test-skf-step-doc-sources.py test/test-skf-step-doc-drift.py test/test-skf-step-hard-gate.py test/test-skf-per-pipeline-thresholds.py test/test-skf-evidence-report-fallback.py test/test-skf-validate-pins.py test/test-skf-auto-shard.py test/test-skf-preapply.py test/test-skf-step-doc-rot.py -v",
|
|
56
|
+
"test:python": "uv run --with pytest --with pyyaml --with jsonschema pytest test/test-compute-score-contract.py test/test-skf-preflight.py test/test-skf-skill-inventory.py test/test-skf-validate-output.py test/test-skf-validate-frontmatter.py test/test-skf-manifest-ops.py test/test-skf-rebuild-managed-sections.py test/test-skf-atomic-write.py test/test-skf-severity-classify.py test/test-skf-structural-diff.py test/test-skf-detect-tools.py test/test-skf-forge-tier-rw.py test/test-skf-emit-result-envelope.py test/test-skf-qmd-classify-collections.py test/test-skf-merge-ccc-exclusions.py test/test-skf-resolve-package.py test/test-skf-extract-public-api.py test/test-skf-render-quick-metadata.py test/test-skf-validate-brief-inputs.py test/test-skf-emit-brief-result-envelope.py test/test-skf-write-skill-brief.py test/test-skf-detect-workspaces.py test/test-skf-recommend-scope-type.py test/test-skf-detect-language.py test/test-skf-language-corpora.py test/test-skf-merge-doc-urls.py test/test-skf-derive-assembly-shape.py test/test-skf-description-guard.py test/test-skf-detect-scripts-assets.py test/test-skf-hash-content.py test/test-skf-validate-brief-schema.py test/test-skf-check-workspace-drift.py test/test-skf-update-active-symlink.py test/test-skf-build-change-manifest.py test/test-skf-provenance-gap-dispatch.py test/test-skf-resolve-authoritative-files.py test/test-skf-scan-manifests.py test/test-skf-pair-intersect.py test/test-skf-enumerate-stack-skills.py test/test-skf-compare-file-hashes.py test/test-skf-load-provenance.py test/test-skf-scan-skill-md-structure.py test/test-skf-disqualify-candidates.py test/test-skf-chain-reachability.py test/test-skf-shape-detect.py test/test-skf-detect-docs.py test/test-skf-step-doc-sources.py test/test-skf-step-doc-drift.py test/test-skf-step-hard-gate.py test/test-skf-per-pipeline-thresholds.py test/test-skf-evidence-report-fallback.py test/test-skf-validate-pins.py test/test-skf-auto-shard.py test/test-skf-preapply.py test/test-skf-step-doc-rot.py test/test-skf-campaign-state.py test/test-skf-campaign-pins.py test/test-skf-campaign-deps.py test/test-skf-campaign-report.py test/test-skf-campaign-stepfiles.py test/test-skf-campaign-validate-state.py test/test-skf-campaign-provenance.py test/test-skf-campaign-parse-manifest.py test/test-skf-campaign-render-kickoff.py -v",
|
|
57
57
|
"test:schemas": "node test/test-agent-schema.js",
|
|
58
58
|
"test:workflow": "node test/test-workflow-state.js",
|
|
59
59
|
"validate:refs": "node tools/validate-file-refs.js --strict",
|
package/src/module-help.csv
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
|
2
2
|
skf,skf-forger,Ferris — Skill Forge Agent,FF,"Skill compilation specialist — the forge master. Invoke to access all SKF workflows via guided menu.",,,anytime,,,false,,
|
|
3
|
-
skf,skf-setup,Setup Forge,SF,"Initialize forge environment — detect tools and set capability tier (Quick/Forge/Forge+/Deep)",,,anytime,,"skf-analyze-source,skf-brief-skill,skf-quick-skill,skf-verify-stack",false,,forge-tier.yaml
|
|
3
|
+
skf,skf-setup,Setup Forge,SF,"Initialize forge environment — detect tools and set capability tier (Quick/Forge/Forge+/Deep)",,,anytime,,"skf-analyze-source,skf-brief-skill,skf-quick-skill,skf-verify-stack,skf-campaign",false,,forge-tier.yaml
|
|
4
|
+
skf,skf-campaign,Campaign,CA,"Orchestrate multi-library skill production across sessions — dependency tracking, file-based state, and resume",,resume [--from=<skill>],anytime,skf-setup,,false,forge_data_folder,_campaign-state.yaml
|
|
4
5
|
skf,skf-analyze-source,Analyze Source,AN,"Discover what to skill in a large repo — produces recommended skill briefs",,,anytime,skf-setup,skf-brief-skill,false,forge_data_folder,skill-brief.yaml
|
|
5
6
|
skf,skf-brief-skill,Brief Skill,BS,"Design a skill scope through guided discovery",,,anytime,"skf-setup,skf-analyze-source",skf-create-skill,false,forge_data_folder,skill-brief.yaml
|
|
6
7
|
skf,skf-create-skill,Create Skill,CS,"Compile a skill from a brief — supports --batch for multiple briefs",,--batch,anytime,skf-brief-skill,"skf-test-skill,skf-create-stack-skill",false,skills_output_folder,SKILL.md
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Canonical companion prose corpora for whole-language skills (issue #427). A language-reference repo (a compiler/interpreter such as rust-lang/rust) carries the language's CODE; its value as a skill comes from the language's PROSE — the guide/Book, the standard/library API docs, and idioms. README detection (skf-detect-docs.py) is the primary source; this registry guarantees the canonical corpora for well-known languages even when the repo's README does not link them. Keyed by lowercase language id (as emitted by skf-detect-language.py / step-auto-scope.md). URLs are version-agnostic, point at maintained latest-stable aliases, and were verified live. Consumed by skf-language-corpora.py, which emits the brief doc_urls contract {url, label, source} with source fixed to 'language-registry' (issue #432).",
|
|
3
|
+
"rust": [
|
|
4
|
+
{ "url": "https://doc.rust-lang.org/book/", "label": "The Rust Programming Language (Book)" },
|
|
5
|
+
{ "url": "https://doc.rust-lang.org/std/", "label": "Rust Standard Library" },
|
|
6
|
+
{ "url": "https://doc.rust-lang.org/reference/", "label": "The Rust Reference" }
|
|
7
|
+
],
|
|
8
|
+
"python": [
|
|
9
|
+
{ "url": "https://docs.python.org/3/tutorial/", "label": "The Python Tutorial" },
|
|
10
|
+
{ "url": "https://docs.python.org/3/library/", "label": "Python Standard Library" },
|
|
11
|
+
{ "url": "https://docs.python.org/3/reference/", "label": "Python Language Reference" },
|
|
12
|
+
{ "url": "https://docs.python.org/3/howto/", "label": "Python HOWTOs (idioms)" }
|
|
13
|
+
],
|
|
14
|
+
"go": [
|
|
15
|
+
{ "url": "https://go.dev/tour/", "label": "A Tour of Go" },
|
|
16
|
+
{ "url": "https://go.dev/doc/effective_go", "label": "Effective Go" },
|
|
17
|
+
{ "url": "https://go.dev/ref/spec", "label": "The Go Language Specification" },
|
|
18
|
+
{ "url": "https://pkg.go.dev/std", "label": "Go Standard Library" }
|
|
19
|
+
],
|
|
20
|
+
"typescript": [
|
|
21
|
+
{ "url": "https://www.typescriptlang.org/docs/handbook/intro.html", "label": "TypeScript Handbook" },
|
|
22
|
+
{ "url": "https://www.typescriptlang.org/docs/", "label": "TypeScript Documentation (Reference)" }
|
|
23
|
+
],
|
|
24
|
+
"javascript": [
|
|
25
|
+
{ "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide", "label": "MDN JavaScript Guide" },
|
|
26
|
+
{ "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference", "label": "MDN JavaScript Reference" }
|
|
27
|
+
],
|
|
28
|
+
"ruby": [
|
|
29
|
+
{ "url": "https://www.ruby-lang.org/en/documentation/quickstart/", "label": "Ruby in Twenty Minutes" },
|
|
30
|
+
{ "url": "https://docs.ruby-lang.org/en/master/", "label": "Ruby Core & Standard Library API" }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -19,12 +19,15 @@ The forger also accepts common pipeline aliases:
|
|
|
19
19
|
|
|
20
20
|
| Alias | Expands To | Description |
|
|
21
21
|
|-------|-----------|-------------|
|
|
22
|
-
| `
|
|
22
|
+
| `forge-auto` | `AN[auto] BS[auto] CS TS[min:90] EX` | Zero-ceremony auto-compile pipeline (bare repo/doc URL → verified skill) |
|
|
23
23
|
| `forge` | `BS CS TS EX` | Full skill creation pipeline (brief through export) |
|
|
24
24
|
| `forge-quick` | `QS TS EX` | Quick skill pipeline |
|
|
25
|
-
| `onboard` | `AN CS TS EX` | Full brownfield onboarding (deprecated — use deepwiki) |
|
|
26
25
|
| `maintain` | `AS US TS EX` | Maintenance cycle (audit → update → test → export) |
|
|
27
26
|
|
|
27
|
+
**Deprecated alias:** `deepwiki` resolves to `forge-auto` (renamed to avoid collision with the DeepWiki MCP — the pipeline auto-forges a verified skill and does not call that MCP). It still works but emits a one-time deprecation notice.
|
|
28
|
+
|
|
29
|
+
**Note:** `campaign` is a standalone workflow invoked via `@Ferris campaign`, not a pipeline alias. It orchestrates its own multi-stage pipeline internally with dependency tracking and resume.
|
|
30
|
+
|
|
28
31
|
## Pipeline Rules
|
|
29
32
|
|
|
30
33
|
1. **Left to right execution** — each workflow completes before the next begins
|
|
@@ -36,7 +39,7 @@ The forger also accepts common pipeline aliases:
|
|
|
36
39
|
|
|
37
40
|
## Pipeline Arguments
|
|
38
41
|
|
|
39
|
-
Pipeline-level arguments (e.g., `--pin <version>`) are passed to the first workflow's data context. The workflow decides how to consume them. For the `
|
|
42
|
+
Pipeline-level arguments (e.g., `--pin <version>`) are passed to the first workflow's data context. The workflow decides how to consume them. For the `forge-auto` pipeline, `--pin` flows to AN, where `step-auto-scope.md §0b` uses it for pin resolution.
|
|
40
43
|
|
|
41
44
|
## Data Flow
|
|
42
45
|
|
|
@@ -51,7 +51,12 @@
|
|
|
51
51
|
"required": ["url"],
|
|
52
52
|
"properties": {
|
|
53
53
|
"url": { "type": "string", "minLength": 1 },
|
|
54
|
-
"label": { "type": "string" }
|
|
54
|
+
"label": { "type": "string" },
|
|
55
|
+
"source": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"enum": ["language-registry", "readme-detection", "homepage", "pages-api", "docs-folder"],
|
|
58
|
+
"description": "Optional per-corpus provenance. `language-registry` marks a registry-guaranteed companion corpus (skf-language-corpora.py); the others mark opportunistically-detected docs by detection method. Absent on legacy/hand-authored entries."
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
},
|
|
57
62
|
"minItems": 1,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = ["pyyaml"]
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Derive Assembly Shape — decide whether a brief is a whole-language reference.
|
|
6
|
+
|
|
7
|
+
create-skill's SKILL.md assembly is mostly LLM-driven prose, but the BRANCH
|
|
8
|
+
SELECTION — does this skill get the whole-language "Language Guide" treatment
|
|
9
|
+
(prose foregrounded, compiler internals demoted) or the standard
|
|
10
|
+
library-export layout? — must be deterministic. This helper computes that one
|
|
11
|
+
decision from the brief so the assembler (and the doc-fetch step) branch on a
|
|
12
|
+
machine value instead of grepping free-text `scope.notes`.
|
|
13
|
+
|
|
14
|
+
The gate is a single, structured, schema-validated condition: the brief's
|
|
15
|
+
`doc_urls` contains at least one entry with `source: "language-registry"`
|
|
16
|
+
(issue #432). Registry-sourced corpora are stamped ONLY by
|
|
17
|
+
skf-language-corpora.py, which is invoked ONLY for a whole-language reference
|
|
18
|
+
(a grammar_file:/tree_triad: signal) in step-auto-scope.md §6b. So the gate
|
|
19
|
+
fires exactly when there is registry-guaranteed prose to foreground, and never
|
|
20
|
+
for an ordinary library, a parser library (pest), a component-library, a
|
|
21
|
+
reference-app, or a docs-only brief — none of which carry a language-registry
|
|
22
|
+
entry. A registry-miss (N==0) whole-language reference carries no such entry
|
|
23
|
+
and is intentionally NOT gated: there is no canonical prose to foreground, so
|
|
24
|
+
it keeps the standard layout (and its scope.notes DEGRADED caveat stands).
|
|
25
|
+
|
|
26
|
+
Gating on the structured `source` field — not the operator-editable
|
|
27
|
+
`scope.notes` substring — means a hand-edited or interactive brief cannot
|
|
28
|
+
silently disable (or misfire) the override.
|
|
29
|
+
|
|
30
|
+
CLI:
|
|
31
|
+
uv run skf-derive-assembly-shape.py <path/to/skill-brief.yaml>
|
|
32
|
+
cat brief.yaml | uv run skf-derive-assembly-shape.py -
|
|
33
|
+
|
|
34
|
+
Output (JSON on stdout):
|
|
35
|
+
{
|
|
36
|
+
"assembly_shape": "whole-language-reference" | "standard",
|
|
37
|
+
"gate_signal": "language-registry" | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Exit codes:
|
|
41
|
+
0 success (shape emitted)
|
|
42
|
+
2 error (missing/unreadable/invalid brief)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import argparse
|
|
48
|
+
import json
|
|
49
|
+
import sys
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
import yaml
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def derive_assembly_shape(brief: dict[str, Any]) -> dict[str, Any]:
|
|
57
|
+
"""Return the assembly-shape decision for a parsed brief."""
|
|
58
|
+
doc_urls = brief.get("doc_urls")
|
|
59
|
+
if isinstance(doc_urls, list):
|
|
60
|
+
for entry in doc_urls:
|
|
61
|
+
if isinstance(entry, dict) and entry.get("source") == "language-registry":
|
|
62
|
+
return {
|
|
63
|
+
"assembly_shape": "whole-language-reference",
|
|
64
|
+
"gate_signal": "language-registry",
|
|
65
|
+
}
|
|
66
|
+
return {"assembly_shape": "standard", "gate_signal": None}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _die(message: str) -> None:
|
|
70
|
+
json.dump({"error": message}, sys.stderr)
|
|
71
|
+
sys.stderr.write("\n")
|
|
72
|
+
sys.exit(2)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main(argv: list[str] | None = None) -> int:
|
|
76
|
+
parser = argparse.ArgumentParser(
|
|
77
|
+
prog="skf-derive-assembly-shape",
|
|
78
|
+
description="Decide whether a skill brief is a whole-language reference.",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"path",
|
|
82
|
+
help="path to skill-brief.yaml; pass `-` to read YAML from stdin",
|
|
83
|
+
)
|
|
84
|
+
args = parser.parse_args(argv)
|
|
85
|
+
|
|
86
|
+
if args.path == "-":
|
|
87
|
+
text = sys.stdin.read()
|
|
88
|
+
else:
|
|
89
|
+
p = Path(args.path)
|
|
90
|
+
if not p.is_file():
|
|
91
|
+
_die(f"Brief not found at {p.as_posix()}")
|
|
92
|
+
text = p.read_text(encoding="utf-8")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
brief = yaml.safe_load(text)
|
|
96
|
+
except yaml.YAMLError as exc:
|
|
97
|
+
_die(f"Brief is not valid YAML: {exc}")
|
|
98
|
+
if not isinstance(brief, dict):
|
|
99
|
+
_die("Brief must be a YAML mapping")
|
|
100
|
+
|
|
101
|
+
json.dump(derive_assembly_shape(brief), sys.stdout)
|
|
102
|
+
sys.stdout.write("\n")
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
raise SystemExit(main())
|
|
@@ -16,7 +16,7 @@ Detection chain (all four methods attempted, results aggregated):
|
|
|
16
16
|
4. docs_folder — markdown files in the repo's docs/ directory
|
|
17
17
|
|
|
18
18
|
CLI:
|
|
19
|
-
uv run
|
|
19
|
+
uv run src/shared/scripts/skf-detect-docs.py \\
|
|
20
20
|
--repo-url <url> [--local-path <path>] [--skip-pages-api]
|
|
21
21
|
|
|
22
22
|
Input:
|
|
@@ -86,13 +86,18 @@ _MD_LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)]+)\)")
|
|
|
86
86
|
_HTML_LINK_RE = re.compile(r'<a\s[^>]*href=["\']([^"\']+)["\']', re.IGNORECASE)
|
|
87
87
|
_BARE_URL_RE = re.compile(r"^(https?://\S+)$", re.MULTILINE)
|
|
88
88
|
|
|
89
|
+
# `docs?\.` (not `docs\.`) so a `doc.` subdomain matches too — language doc
|
|
90
|
+
# sites use the singular form (doc.rust-lang.org, doc.qt.io).
|
|
89
91
|
_DOC_DOMAIN_RE = re.compile(
|
|
90
|
-
r"(?:docs
|
|
92
|
+
r"(?:docs?\.|\.readthedocs\.|wiki\.|documentation\.)",
|
|
91
93
|
re.IGNORECASE,
|
|
92
94
|
)
|
|
93
95
|
|
|
96
|
+
# Language reference/guide path segments (a Book, a std/library API, a tutorial)
|
|
97
|
+
# are doc URLs even on a bare domain (doc.rust-lang.org/book/, .../std/).
|
|
94
98
|
_DOC_PATH_RE = re.compile(
|
|
95
|
-
r"(?:/docs/|/documentation/|/api/|/reference/|/guide/|/wiki/
|
|
99
|
+
r"(?:/docs/|/documentation/|/api/|/reference/|/guide/|/wiki/"
|
|
100
|
+
r"|/book/|/std/|/library/|/tutorial/)",
|
|
96
101
|
re.IGNORECASE,
|
|
97
102
|
)
|
|
98
103
|
|
|
@@ -32,9 +32,10 @@ Detection rules (apply in order, first match wins):
|
|
|
32
32
|
7. build.gradle (Groovy) — check tree:
|
|
33
33
|
src/main/kotlin/ → kotlin (medium)
|
|
34
34
|
else → java (medium)
|
|
35
|
-
8.
|
|
36
|
-
9.
|
|
37
|
-
10.
|
|
35
|
+
8. Package.swift → swift (high)
|
|
36
|
+
9. *.csproj | *.sln → csharp (high)
|
|
37
|
+
10. Gemfile → ruby (high)
|
|
38
|
+
11. Extension-frequency fallback over the full tree.
|
|
38
39
|
dominant extension >= 50% of code files → that language (medium)
|
|
39
40
|
no clear winner → unknown (low)
|
|
40
41
|
|
|
@@ -94,6 +95,7 @@ _MANIFEST_RULES: list[tuple[str, str, str]] = [
|
|
|
94
95
|
("go.mod", "go", "go.mod present"),
|
|
95
96
|
("pom.xml", "java", "pom.xml present"),
|
|
96
97
|
("build.gradle.kts", "kotlin", "build.gradle.kts present"),
|
|
98
|
+
("Package.swift", "swift", "Package.swift present"),
|
|
97
99
|
("Gemfile", "ruby", "Gemfile present"),
|
|
98
100
|
]
|
|
99
101
|
|
|
@@ -54,9 +54,17 @@ Context payload shape (consumed by `emit`):
|
|
|
54
54
|
"ccc_exclusion_warnings": ["string", ...],
|
|
55
55
|
"ccc_registry_stale_removed": ["/path", ...],
|
|
56
56
|
"ccc_indexing_failed_reason": "string|null",
|
|
57
|
+
"orphan_auto_resolution": null|{"action": "keep|remove", "count": int, "source": "headless-default|orphan-action-flag"},
|
|
57
58
|
"error": null|{"phase","path","reason"}
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
When the step-3 orphan-removal gate is resolved non-interactively
|
|
62
|
+
(headless default Keep, or an explicit --orphan-action), pass
|
|
63
|
+
`orphan_auto_resolution` so the audit trail lands in `warnings` —
|
|
64
|
+
most importantly when the destructive `remove` ran headlessly, which a
|
|
65
|
+
pipeline otherwise could not distinguish from a no-op by reading the
|
|
66
|
+
envelope alone.
|
|
67
|
+
|
|
60
68
|
Caller does NOT need to compute warnings, tools_added/removed, or
|
|
61
69
|
tier_changed — the script derives them from the inputs above.
|
|
62
70
|
|
|
@@ -176,6 +184,15 @@ def _assemble_warnings(payload: dict) -> list[str]:
|
|
|
176
184
|
warnings.append(
|
|
177
185
|
f"require_tier_failed: missing {', '.join(missing) if missing else '<none>'}"
|
|
178
186
|
)
|
|
187
|
+
orphan = payload.get("orphan_auto_resolution")
|
|
188
|
+
if isinstance(orphan, dict) and orphan.get("action"):
|
|
189
|
+
action = str(orphan.get("action"))
|
|
190
|
+
count = orphan.get("count", 0)
|
|
191
|
+
source = str(orphan.get("source", "headless-default"))
|
|
192
|
+
warnings.append(
|
|
193
|
+
f"orphan_auto_resolution: {action} {count} orphaned collection(s) "
|
|
194
|
+
f"(non-interactive, {source})"
|
|
195
|
+
)
|
|
179
196
|
return warnings
|
|
180
197
|
|
|
181
198
|
|