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.
Files changed (118) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +10 -5
  3. package/docs/_data/pinned.yaml +2 -2
  4. package/docs/agents.md +11 -2
  5. package/docs/architecture.md +5 -4
  6. package/docs/bmad-synergy.md +30 -7
  7. package/docs/campaign.md +172 -0
  8. package/docs/examples.md +7 -3
  9. package/docs/forge-auto.md +90 -0
  10. package/docs/getting-started.md +9 -0
  11. package/docs/how-it-works.md +6 -4
  12. package/docs/index.md +4 -3
  13. package/docs/skill-model.md +1 -1
  14. package/docs/troubleshooting.md +17 -1
  15. package/docs/verifying-a-skill.md +2 -2
  16. package/docs/why-skf.md +1 -1
  17. package/docs/workflows.md +64 -24
  18. package/package.json +2 -2
  19. package/src/module-help.csv +2 -1
  20. package/src/shared/data/language-corpora.json +32 -0
  21. package/src/shared/references/pipeline-contracts.md +6 -3
  22. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  23. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  24. package/src/shared/scripts/skf-detect-docs.py +8 -3
  25. package/src/shared/scripts/skf-detect-language.py +5 -3
  26. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  27. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  28. package/src/shared/scripts/skf-language-corpora.py +100 -0
  29. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  30. package/src/shared/scripts/skf-preapply.py +1 -1
  31. package/src/shared/scripts/skf-shape-detect.py +576 -25
  32. package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
  33. package/src/shared/scripts/skf-validate-pins.py +2 -2
  34. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  35. package/src/skf-analyze-source/SKILL.md +1 -1
  36. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  37. package/src/skf-analyze-source/references/step-auto-scope.md +196 -44
  38. package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
  39. package/src/skf-audit-skill/references/init.md +1 -1
  40. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  41. package/src/skf-brief-skill/references/analyze-target.md +7 -4
  42. package/src/skf-brief-skill/references/confirm-brief.md +0 -1
  43. package/src/skf-brief-skill/references/gather-intent.md +8 -2
  44. package/src/skf-brief-skill/references/scope-definition.md +2 -1
  45. package/src/skf-brief-skill/references/step-auto-brief.md +21 -4
  46. package/src/skf-brief-skill/references/step-auto-validate.md +2 -2
  47. package/src/skf-brief-skill/references/write-brief.md +2 -3
  48. package/src/skf-campaign/SKILL.md +190 -0
  49. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  50. package/src/skf-campaign/customize.toml +73 -0
  51. package/src/skf-campaign/manifest.yaml +11 -0
  52. package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
  53. package/src/skf-campaign/references/health-check.md +35 -0
  54. package/src/skf-campaign/references/step-01-setup.md +122 -0
  55. package/src/skf-campaign/references/step-02-strategy.md +97 -0
  56. package/src/skf-campaign/references/step-03-pins.md +56 -0
  57. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  58. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  59. package/src/skf-campaign/references/step-06-batch.md +87 -0
  60. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  61. package/src/skf-campaign/references/step-08-verify.md +75 -0
  62. package/src/skf-campaign/references/step-09-refine.md +83 -0
  63. package/src/skf-campaign/references/step-10-export.md +106 -0
  64. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  65. package/src/skf-campaign/references/step-resume.md +114 -0
  66. package/src/skf-campaign/scripts/.gitkeep +0 -0
  67. package/src/skf-campaign/scripts/campaign-deps.py +235 -0
  68. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  69. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  70. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  71. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  72. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  73. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  74. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  75. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  76. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  77. package/src/skf-create-skill/SKILL.md +1 -1
  78. package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
  79. package/src/skf-create-skill/references/compile.md +4 -1
  80. package/src/skf-create-skill/references/extract.md +9 -1
  81. package/src/skf-create-skill/references/extraction-patterns.md +3 -1
  82. package/src/skf-create-skill/references/generate-artifacts.md +11 -2
  83. package/src/skf-create-skill/references/health-check.md +2 -2
  84. package/src/skf-create-skill/references/report.md +17 -1
  85. package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
  86. package/src/skf-create-skill/references/step-doc-rot.md +4 -4
  87. package/src/skf-create-skill/references/step-doc-sources.md +11 -2
  88. package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
  89. package/src/skf-create-skill/references/validate.md +12 -3
  90. package/src/skf-drop-skill/SKILL.md +2 -2
  91. package/src/skf-drop-skill/references/execute.md +20 -9
  92. package/src/skf-drop-skill/references/report.md +2 -0
  93. package/src/skf-drop-skill/references/select.md +7 -2
  94. package/src/skf-forger/SKILL.md +12 -7
  95. package/src/skf-refine-architecture/SKILL.md +2 -1
  96. package/src/skf-refine-architecture/references/compile.md +1 -1
  97. package/src/skf-refine-architecture/references/report.md +1 -1
  98. package/src/skf-rename-skill/SKILL.md +2 -2
  99. package/src/skf-rename-skill/references/execute.md +5 -4
  100. package/src/skf-rename-skill/references/rebuild-context.md +2 -2
  101. package/src/skf-rename-skill/references/select.md +3 -3
  102. package/src/skf-setup/SKILL.md +1 -1
  103. package/src/skf-setup/references/auto-index.md +3 -1
  104. package/src/skf-setup/references/detect-and-tier.md +4 -0
  105. package/src/skf-setup/references/report.md +4 -1
  106. package/src/skf-setup/references/tier-rules.md +1 -1
  107. package/src/skf-test-skill/references/coverage-check.md +10 -0
  108. package/src/skf-test-skill/references/init.md +1 -1
  109. package/src/skf-test-skill/references/source-access-protocol.md +13 -3
  110. package/src/skf-test-skill/scripts/compute-score.py +4 -3
  111. package/src/skf-update-skill/customize.toml +0 -9
  112. package/src/skf-update-skill/references/detect-changes.md +33 -3
  113. package/src/skf-update-skill/references/health-check.md +2 -2
  114. package/src/skf-update-skill/references/init.md +1 -0
  115. package/src/skf-update-skill/references/re-extract.md +15 -1
  116. package/src/skf-verify-stack/references/report.md +1 -1
  117. package/tools/cli/lib/ui.js +3 -2
  118. 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 # Set up your forge
64
- @Ferris QS <package> # Generate a skill in under a minute
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.
@@ -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
 
@@ -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 a direct invocation with scope arguments.
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: `deepwiki` targets **90%**, `forge` and `forge-quick` target **80%**. You can override any default with a custom threshold (e.g., `TS[min:85]`).
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 569-line architecture page before running your first command.
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 14 SKF workflows with commands, steps, and connection diagram
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
- - `--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 `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.
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: deepwiki 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.
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
- > **One workflow per session** (unless using pipeline mode). Each arrow in the diagrams above represents a new conversation session. Clear your context between workflows for best results — or use pipeline mode to chain them automatically. See [Pipeline Mode](#pipeline-mode) below.
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 | Description |
290
- | ------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
291
- | Core | SF, BS, CS, US | Setup, brief, create, and update skills |
292
- | Feature | QS, SS, AN | Quick skill, stack skill, and analyze source — the [`deepwiki`](#pipeline-aliases) alias chains AN + BS + CS + TS + EX for zero-ceremony wiki-skill creation |
293
- | Quality | AS, TS | Detect skill drift (AS) and verify skill completeness (TS) |
294
- | Architecture Verification | VS, RA | Pre-code architecture feasibility and refinement |
295
- | Management | RS, DS | Rename and drop skill versions with transactional safety |
296
- | Utility | EX | Package and export for consumption |
297
- | In-Agent | WS, KI | WS: show lifecycle position, active briefs, and forge tier; KI: list knowledge fragments (both in-agent, no file-based workflow) |
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 `deepwiki` alias is the recommended way to create wiki 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.
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
- | `deepwiki` | `AN[auto] BS[auto] CS TS[min:90] EX` | AN | GitHub URL, doc URL, or `--pin <version>` |
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 deepwiki https://github.com/honojs/hono — zero-ceremony wiki skill
341
- @Ferris deepwiki https://docs.example.com — docs-only wiki skill
342
- @Ferris deepwiki https://github.com/honojs/hono --pin v4.6.0 — pinned version
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 | compile-cancelled |
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 14 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:
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": "1.9.0",
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",
@@ -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
- | `deepwiki` | `AN[auto] BS[auto] CS TS[min:90] EX` | Full zero-ceremony wiki-skill pipeline |
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 `deepwiki` pipeline, `--pin` flows to AN, where `step-auto-scope.md §0b` uses it for pin resolution.
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 python src/shared/scripts/skf-detect-docs.py \\
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\.|\.readthedocs\.|wiki\.|documentation\.)",
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. *.csproj | *.sln csharp (high)
36
- 9. Gemfile ruby (high)
37
- 10. Extension-frequency fallback over the full tree.
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