bmad-module-skill-forge 4.0.0 → 6.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 (128) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +3 -3
  3. package/docs/_data/pinned.yaml +19 -13
  4. package/docs/agents.md +1 -1
  5. package/docs/architecture.md +5 -5
  6. package/docs/campaign.md +1 -1
  7. package/docs/concepts.md +4 -4
  8. package/docs/examples.md +32 -25
  9. package/docs/getting-started.md +3 -3
  10. package/docs/how-it-works.md +2 -2
  11. package/docs/index.md +1 -1
  12. package/docs/skill-model.md +41 -39
  13. package/docs/troubleshooting.md +9 -3
  14. package/docs/verifying-a-skill.md +42 -21
  15. package/docs/why-skf.md +1 -1
  16. package/docs/workflows.md +6 -6
  17. package/package.json +1 -1
  18. package/src/knowledge/tool-resolution.md +6 -6
  19. package/src/shared/references/headless-gate-convention.md +4 -1
  20. package/src/shared/references/output-contract-schema.md +3 -3
  21. package/src/shared/references/pipeline-contracts.md +2 -1
  22. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +1 -1
  23. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +11 -3
  24. package/src/shared/scripts/schemas/skill-brief.v1.json +1 -1
  25. package/src/shared/scripts/skf-build-change-manifest.py +1180 -189
  26. package/src/shared/scripts/skf-classify-changed-files.py +15 -10
  27. package/src/shared/scripts/skf-count-tokens.py +54 -10
  28. package/src/shared/scripts/skf-detect-workspaces.py +67 -0
  29. package/src/shared/scripts/skf-emit-result-envelope.py +64 -20
  30. package/src/shared/scripts/skf-extract-public-api.py +472 -120
  31. package/src/shared/scripts/skf-extraction-inventory.py +10 -6
  32. package/src/shared/scripts/skf-extraction-snapshot.py +6 -6
  33. package/src/shared/scripts/skf-parse-gaps.py +136 -26
  34. package/src/shared/scripts/skf-render-metadata-stats.py +1 -1
  35. package/src/shared/scripts/skf-resolve-customization.py +197 -0
  36. package/src/shared/scripts/skf-shard-body.py +239 -41
  37. package/src/shared/scripts/skf-validate-output.py +12 -3
  38. package/src/shared/scripts/skf-verify-provenance-completeness.py +574 -45
  39. package/src/shared/tool-requirements.yaml +8 -2
  40. package/src/skf-analyze-source/SKILL.md +3 -2
  41. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  42. package/src/skf-analyze-source/references/generate-briefs.md +1 -1
  43. package/src/skf-analyze-source/references/headless-contract.md +1 -1
  44. package/src/skf-audit-skill/SKILL.md +4 -3
  45. package/src/skf-audit-skill/references/re-index.md +2 -2
  46. package/src/skf-audit-skill/references/report.md +1 -1
  47. package/src/skf-brief-skill/SKILL.md +3 -2
  48. package/src/skf-brief-skill/assets/skill-brief-schema.md +2 -2
  49. package/src/skf-brief-skill/references/invocation-contract.md +2 -2
  50. package/src/skf-brief-skill/references/scope-definition.md +4 -4
  51. package/src/skf-campaign/SKILL.md +3 -2
  52. package/src/skf-campaign/references/campaign-contracts.md +2 -0
  53. package/src/skf-campaign/references/step-05-skill-loop.md +8 -3
  54. package/src/skf-campaign/references/step-10-export.md +3 -2
  55. package/src/skf-create-skill/SKILL.md +4 -3
  56. package/src/skf-create-skill/assets/skill-sections.md +1 -1
  57. package/src/skf-create-skill/references/ccc-index-check.md +1 -1
  58. package/src/skf-create-skill/references/component-extraction.md +1 -1
  59. package/src/skf-create-skill/references/extract.md +3 -2
  60. package/src/skf-create-skill/references/extraction-patterns.md +3 -3
  61. package/src/skf-create-skill/references/generate-artifacts.md +1 -1
  62. package/src/skf-create-skill/references/report.md +8 -3
  63. package/src/skf-create-skill/references/sub/ccc-discover.md +1 -1
  64. package/src/skf-create-skill/references/validate.md +1 -1
  65. package/src/skf-create-stack-skill/SKILL.md +3 -2
  66. package/src/skf-create-stack-skill/references/generate-output.md +1 -1
  67. package/src/skf-create-stack-skill/references/invocation-contract.md +1 -1
  68. package/src/skf-create-stack-skill/references/parallel-extract.md +2 -2
  69. package/src/skf-create-stack-skill/references/report.md +1 -1
  70. package/src/skf-create-stack-skill/references/validate.md +1 -1
  71. package/src/skf-drop-skill/SKILL.md +3 -2
  72. package/src/skf-drop-skill/references/invocation-contract.md +1 -1
  73. package/src/skf-drop-skill/references/report.md +1 -1
  74. package/src/skf-export-skill/SKILL.md +3 -2
  75. package/src/skf-export-skill/assets/snippet-format.md +5 -5
  76. package/src/skf-export-skill/references/generate-snippet.md +22 -15
  77. package/src/skf-export-skill/references/invocation-contract.md +1 -1
  78. package/src/skf-export-skill/references/load-skill.md +1 -1
  79. package/src/skf-export-skill/references/summary.md +1 -1
  80. package/src/skf-export-skill/references/token-report.md +29 -9
  81. package/src/skf-forger/SKILL.md +3 -2
  82. package/src/skf-forger/references/pipeline-mode.md +2 -2
  83. package/src/skf-quick-skill/SKILL.md +4 -3
  84. package/src/skf-quick-skill/references/finalize.md +1 -1
  85. package/src/skf-quick-skill/references/halt-contract.md +2 -2
  86. package/src/skf-quick-skill/references/write-and-validate.md +1 -1
  87. package/src/skf-refine-architecture/SKILL.md +8 -9
  88. package/src/skf-refine-architecture/customize.toml +1 -1
  89. package/src/skf-refine-architecture/references/report.md +1 -1
  90. package/src/skf-rename-skill/SKILL.md +3 -2
  91. package/src/skf-rename-skill/references/invocation-contract.md +2 -2
  92. package/src/skf-setup/SKILL.md +3 -2
  93. package/src/skf-setup/references/ccc-index.md +1 -1
  94. package/src/skf-setup/references/invocation-contract.md +1 -1
  95. package/src/skf-test-skill/SKILL.md +3 -2
  96. package/src/skf-test-skill/assets/output-section-formats.md +6 -4
  97. package/src/skf-test-skill/references/coherence-check.md +11 -8
  98. package/src/skf-test-skill/references/coverage-check-branches.md +81 -0
  99. package/src/skf-test-skill/references/coverage-check-tiers.md +3 -3
  100. package/src/skf-test-skill/references/coverage-check.md +60 -97
  101. package/src/skf-test-skill/references/init.md +2 -1
  102. package/src/skf-test-skill/references/inventory-contract.md +27 -0
  103. package/src/skf-test-skill/references/invocation-contract.md +3 -3
  104. package/src/skf-test-skill/references/migration-section-rules.md +26 -14
  105. package/src/skf-test-skill/references/report.md +5 -5
  106. package/src/skf-test-skill/references/scoring-rules.md +8 -7
  107. package/src/skf-test-skill/references/signature-comparison-rules.md +72 -0
  108. package/src/skf-test-skill/references/source-access-protocol.md +14 -10
  109. package/src/skf-test-skill/references/step-hard-gate.md +1 -0
  110. package/src/skf-test-skill/scripts/build-result-context.py +20 -3
  111. package/src/skf-test-skill/scripts/gap-ledger.py +456 -176
  112. package/src/skf-test-skill/scripts/load-coverage-inputs.py +516 -79
  113. package/src/skf-test-skill/scripts/locate-export-segments.py +685 -76
  114. package/src/skf-test-skill/scripts/reconcile-coverage.py +192 -39
  115. package/src/skf-test-skill/scripts/score-signatures.py +1205 -65
  116. package/src/skf-test-skill/scripts/stage-helper-payload.py +64 -13
  117. package/src/skf-test-skill/scripts/validate-inventory.py +350 -35
  118. package/src/skf-test-skill/templates/test-report-template.md +1 -0
  119. package/src/skf-update-skill/SKILL.md +3 -2
  120. package/src/skf-update-skill/references/detect-changes.md +1 -1
  121. package/src/skf-update-skill/references/gap-driven.md +54 -33
  122. package/src/skf-update-skill/references/invocation-contract.md +1 -1
  123. package/src/skf-update-skill/references/merge.md +15 -10
  124. package/src/skf-update-skill/references/re-extract.md +3 -1
  125. package/src/skf-update-skill/references/report.md +6 -5
  126. package/src/skf-update-skill/references/write.md +42 -20
  127. package/src/skf-verify-stack/SKILL.md +4 -3
  128. package/tools/cli/lib/tool-check.js +158 -2
@@ -29,12 +29,12 @@ Provenance maps live in `forge-data/{skill}/{version}/provenance-map.json` in th
29
29
  ```json
30
30
  {
31
31
  "export_name": "search",
32
- "export_type": "async_function",
32
+ "export_type": "function",
33
33
  "source_library": "cognee",
34
- "params": ["query_text: str", "query_type: SearchType = SearchType.GRAPH_COMPLETION"],
35
- "return_type": "List[SearchResult]",
34
+ "params": ["query_text: str", "query_type: SearchType = SearchType.HYBRID_COMPLETION"],
35
+ "return_type": "list[SearchResult]",
36
36
  "source_file": "cognee/api/v1/search/search.py",
37
- "source_line": 27,
37
+ "source_line": 41,
38
38
  "confidence": "T1",
39
39
  "extraction_method": "ast-grep",
40
40
  "ast_node_type": "function_definition",
@@ -42,15 +42,15 @@ Provenance maps live in `forge-data/{skill}/{version}/provenance-map.json` in th
42
42
  }
43
43
  ```
44
44
 
45
- This is the entry SKF 3.0.0 writes for cognee's `search` when ast-grep matches it, shortened to its first two parameters (`search` takes 20). It points at line 27, where `search` is defined at cognee v1.0.0's commit (`3c048aa4`), and its labels follow the tool that read it: an ast-grep match gives `T1`, `ast-grep`, the node kind its rule matched and an `[AST:]` citation. An export read by eye instead, because ast-grep could not parse its file, an ast-grep call that failed or timed out left its file unread, or no rule matched it, gets `T1-low`, `source-read`, no node kind and an `[SRC:]` citation, at every forge tier.
45
+ This is the published entry for cognee's `search` in oh-my-skills' [`forge-data/oms-cognee/1.6.2/provenance-map.json`](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cognee/1.6.2/provenance-map.json), shortened to its first two parameters (`search` takes 27). It points at line 41, where `async def search(` is defined at cognee v1.6.2's commit (`ba3631f2`), and the skill's `SKILL.md` cites `[AST:cognee/api/v1/search/search.py:L41]`. Its labels follow the tool that read it: an ast-grep match gives `T1`, `ast-grep`, the node kind its rule matched and an `[AST:]` citation. An export read by eye instead, because ast-grep could not parse its file, an ast-grep call that failed or timed out left its file unread, or no rule matched it, gets `T1-low`, `source-read`, no node kind and an `[SRC:]` citation, at every forge tier.
46
46
 
47
- The published entry in oh-my-skills' [`forge-data/oms-cognee/1.0.0/provenance-map.json`](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cognee/1.0.0/provenance-map.json) is a good one to check by hand, because it was compiled before labels followed the tool. It records `search` as `source-read` but labels it `T1`, and it points at line 26, the blank line above the definition, while the skill's `SKILL.md` cites `[AST:cognee/api/v1/search/search.py:L27]`. SKF 3.0.0's [line, label and citation checks](#workflow-time-enforcement) flag all three when Create Skill or Update Skill writes such a map: a source read is `T1-low` with an `[SRC:]` citation, and its line is 27, the `def` line.
47
+ The 1.6.2 map passes the [line, label and citation checks](#workflow-time-enforcement) that SKF 3.0.0 added. SKF 4.0.0's Test Skill first blocked the skill and, after one Update Skill repair, passed it at 99.00%. Strict re-tests then blocked it again. After fixes by hand, one strict test was still blocked on three borderline calls, and a later one on the same documentation, which judged those calls as matches, passed it at 99% (see [the scores](#the-scores-including-the-ones-we-lose)). Test Skill's signature rules now settle such calls instead of leaving them to the judge. The older map next to it, [`forge-data/oms-cognee/1.0.0/provenance-map.json`](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cognee/1.0.0/provenance-map.json), was compiled before labels followed the tool and still shows what those checks catch. It records `search` as `source-read` but labels it `T1`, and it points at line 26, the blank line above the definition, while that version's `SKILL.md` cites `[AST:cognee/api/v1/search/search.py:L27]`. A source read is `T1-low` with an `[SRC:]` citation, and its line is 27, the `def` line at v1.0.0.
48
48
 
49
49
  ### 3. Visit the upstream repo at the pinned commit
50
50
 
51
51
  Open `{source_repo}` at `{source_commit}`, jump to `{source_file}` line `{source_line}`. The signature in `SKILL.md` should match what you see in the source.
52
52
 
53
- If it doesn't, **that's a bug**. For a skill you built, Test Skill (`@Ferris TS`) rates a signature mismatch as a Critical gap, which stops the test at its [hard gate](#hard-gate), and Update Skill (`@Ferris US`) with `--from-test-report` repairs the gaps a test report lists. If you think SKF extracted it wrong, [open an issue](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/new/choose). Falsifiability isn't a feature. It's the whole deal.
53
+ If it doesn't, **that's a bug**. For a skill you built, Test Skill (`@Ferris TS`) rates a signature mismatch as a Critical gap, which stops the test at its [hard gate](#hard-gate). It compares only what a signature writes: a component by its props alone, so `FC<P>` and `(props: P) => JSX.Element` are one signature, and a type or a return type the skill does not write, or type arguments it leaves out (`dict` against `dict[str, Any]`), are not compared. When the skill documents a name in several places, such as a brief usage in `SKILL.md` and a full props list in a reference file, each form that lists parameters or gives a usage is compared, and a `const` holding a component, such as a `forwardRef(...)` result, is compared by the usage or declaration the skill writes for it. Two differences a caller never meets are rated lower and still count as matching: a renamed parameter in a TypeScript, JavaScript, Go, Rust, Java, C or C++ file, whose callers pass arguments by position, is a Low gap, and an optional parameter left out of a signature the skill labels partial (such as `Props (simplified)`) is an Info observation. A renamed Python parameter stays Critical, because Test Skill cannot see which parameters a caller must pass by position, and so does a renamed Vue component prop, which callers set by name. Update Skill (`@Ferris US`) with `--from-test-report` repairs the gaps a test report lists. If you think SKF extracted it wrong, [open an issue](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/new/choose). Falsifiability isn't a feature. It's the whole deal.
54
54
 
55
55
  ### Workflow-time enforcement
56
56
 
@@ -74,7 +74,7 @@ Every file in the per-skill output carries a specific job. Here's the lookup tab
74
74
  | Which symbols are documented and where did each come from? | `forge-data/{name}/{version}/provenance-map.json` |
75
75
  | What AST patterns were used for extraction? | `forge-data/{name}/{version}/extraction-rules.yaml` |
76
76
  | What signatures, types, and examples did the extractor actually capture? | `forge-data/{name}/{version}/evidence-report.md` |
77
- | How was the skill scored? Show me the math. | `forge-data/{name}/{version}/test-report-{name}-{run_id}.md` (one report per run, named with the run ID; the newest one counts. Test Skill gives a report this name only once its checks pass, so a run that halts leaves just its hidden in-progress file, `.skf-test-report-{name}-{run_id}.md`, and the last finished report still counts. Older reports, like the oh-my-skills ones linked below, are named `test-report-{name}.md`) |
77
+ | How was the skill scored? Show me the math. | `forge-data/{name}/{version}/test-report-{name}-{run_id}.md` (one report per run, named with the run ID; the newest one counts. Test Skill gives a report this name only once its checks pass, so a run that halts leaves just its hidden in-progress file, `.skf-test-report-{name}-{run_id}.md`, and the last finished report still counts. Reports from SKF 2.x and earlier are named `test-report-{name}.md`) |
78
78
  | How was the skill scoped, and what was deliberately left out? | `forge-data/{name}/skill-brief.yaml` |
79
79
 
80
80
  Everything a reader needs to reconstruct the compilation is in the two sibling directories: `skills/` ships to consumers, `forge-data/` is the audit trail.
@@ -85,20 +85,41 @@ Everything a reader needs to reconstruct the compilation is in the two sibling d
85
85
 
86
86
  The [scoring formula](#how-the-score-is-computed) is deterministic and the pass threshold is **80%**. Every test report also logs the specific edges where a skill falls short, so the numbers aren't marketing.
87
87
 
88
- Take oh-my-skills' four reference skills as an example. Their scores range from **99.0% to 99.49%**. None is perfect, and every test report names the specific drift it found:
88
+ Take oh-my-skills' four reference skills as an example. Each first failed the [hard gate](#hard-gate) on SKF 4.0.0's stricter checks and was repaired by Update Skill with `--from-test-report`. Three then passed at 99.00% to 100%, and oms-storybook-react-vite at 95.17% (after an earlier 99.93% that was reverted).
89
+
90
+ The oms-cocoindex, oms-cognee and oms-storybook-react-vite passes read the signature rules leniently. A short `SKILL.md` signature that left out parameters its reference lists counted as a match, and so did a prop list missing optional props or `dict` against `dict[str, Any]`. Strict re-tests applied the rule as it was then written, wrote down how they read the cases it did not settle ([#843](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/843), [#845](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/845)), and said which calls they counted. They blocked all four at first. Test Skill's signature rules now settle how a component's props compare, and that a type, a return type or type arguments a signature leaves out (`dict` against `dict[str, Any]`) are not compared. oms-uitripled's strict re-test was blocked only because it compared `cn` with the declaration the plan named, not the one the skill cites; its other 265 signatures matched.
91
+
92
+ Most of the real errors were fixed against the pinned source. Update Skill repaired 22 storybook signatures. The rest were fixed by hand, each in a labeled commit:
93
+ - Two storybook fixes were needed because Update Skill halted on a gap that cited Angular's declaration; repairing from that citation would have copied Angular's signature into a React skill ([#824](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/824), [#830](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/830)).
94
+ - For oms-cocoindex's and oms-cognee's split-body gaps, Update Skill would have copied `SKILL.md`'s errors into reference files that were correct ([#779](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/779)), so their strict-test gaps were all fixed by hand.
95
+ - Other storybook fixes corrected errors no test gap covered, found while reading the source or in a review.
96
+
97
+ Two storybook errors that block the gate, and the errors the reports record without blocking, are still open ([oh-my-skills#17](https://github.com/armelhbobdad/oh-my-skills/issues/17)).
98
+
99
+ Today, on a strict test of the version each one ships, oms-cocoindex passes at **100%** and oms-cognee at **99%**; both passes turn on borderline calls their reports write down, which Test Skill's signature rules now settle. oms-uitripled is blocked by one gap oh-my-skills judges to be a false positive. oms-storybook-react-vite is blocked by two false positives and two older real errors that are not fixed yet.
100
+
101
+ Every report, the failing ones included, is kept: in the skill's `forge-data/` folder, or, for that reverted repair, in oh-my-skills' git history. Even a passing report names the drift it found:
89
102
 
90
103
  | Skill | Score | What the report discloses |
91
104
  |---|---|---|
92
- | [oms-cocoindex](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cocoindex/0.3.37/test-report-oms-cocoindex.md) | **99.0%** | 114/114 provenance entries; 55 public-API denominator from `__init__.py` `__all__`; 20/20 sampled signatures matched. Two denominators (barrel vs. full surface) both disclosed with rationale. |
93
- | [oms-cognee](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cognee/1.0.0/test-report-oms-cognee.md) | **99.0%** | 34/34 exports documented; denominator is the `cognee/__init__.py` barrel (61 lines, 34 public re-exports) at pinned commit `3c048aa4` (v1.0.0). |
94
- | [oms-storybook-react-vite](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-storybook-react-vite/10.3.5/test-report-oms-storybook-react-vite.md) | **99.49%** | 215/216 documented. **GAP-004** logs the one-count gap openly: all 215 exports in the provenance map are documented, and the 216th is a stale count in `metadata.json`, not a missing export. |
95
- | [oms-uitripled](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-uitripled/0.1.0/test-report-oms-uitripled.md) | **99.45%** | 34-entry denominator (not 11, not 25) with the full reconciliation reasoning in the report. |
105
+ | [oms-cocoindex](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cocoindex/1.0.0/test-report-oms-cocoindex-20261103T131524Z-5907fba9.md) | **100%** | 79/79 exports on the brief's `scope.include` union (95 files), 27/27 signatures, 36/36 types. Nine borderline calls are recorded and not counted: bare `*args` and `**kwargs` against `P.args` and `P.kwargs`, unannotated parameters and unstated returns. Test Skill's signature rules now read each of those as a match; one other call, `mount_each` documented with a `process_fn` parameter the source names `fn`, is a renamed Python parameter, so under those rules it is a Critical gap until the reference is corrected. The first strict re-test of the text behind its earlier 100% found seven `SKILL.md` signatures that misnamed parameters or left out `**kwargs` or an optional parameter. Left open: method signatures in the reference files that the report records as warnings without a gap ([oh-my-skills#17](https://github.com/armelhbobdad/oh-my-skills/issues/17)). |
106
+ | [oms-cognee](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cognee/1.6.2/test-report-oms-cognee-20261103T134151Z-12e5ccdf.md) | **99%** | 92/92 exports of the `cognee/__init__.py` barrel at pinned commit `ba3631f2` (v1.6.2), 72/72 signatures, 35/35 types. External Validation is 90%: skill-check finds the `SKILL.md` body over its 5,000-token budget. The pass turns on borderline calls the report writes down: an earlier strict run counted three of them as Critical gaps ([#845](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/845), [#835](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/835)) and was blocked. Test Skill's signature rules now settle them: an overload set is read through its implementation, a bare `*args`, an unannotated parameter, an unstated return and omitted type arguments match, and a documented return type that matches what the function's body returns, against a declared annotation that says otherwise, is an Info observation that blocks nothing, recorded only when Test Skill's scoring script checks the return line the judge names. |
107
+ | [oms-storybook-react-vite](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-storybook-react-vite/10.3.5/test-report-oms-storybook-react-vite-20261103T131516Z-6e2b972a.md) | **blocked** | 4 Critical signature gaps, so no score. Two compare React wrappers with core's functions, false positives ([#781](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/781)). Two are real errors written in April and not fixed yet: the react-vite preset's `viteFinal` typed `PresetProperty<'viteFinal'>`, which resolves to `never` because `StorybookConfigRaw` has no `viteFinal` key, and `useParameter`'s parameters ([oh-my-skills#17](https://github.com/armelhbobdad/oh-my-skills/issues/17)). Signature Accuracy is 82/86 and Type Coverage 98/120. Export Coverage reads 100% only because the stored count then credited every documented name ([#792](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/792)). In the last run that counted them, 204 of the 280 names on the brief's `scope.tier_a_include` were documented, and that list misses names another package also declares ([#830](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/830)). Test Skill now counts only the names on that list, so the next run reads lower and lists the ones the skill does not document. |
108
+ | [oms-uitripled](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-uitripled/0.1.0/test-report-oms-uitripled-20261103T100001Z-981f84e6.md) | **blocked** | One Critical false positive: two exports share the name `cn`, and the test compared the one the skill does not document ([#781](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/781)). Under the strict rule, 265 of 266 signatures and every destructured component props list match. One props type, `NativeHoverCardProps`, writes `size` as required where the source has `size?` (an Info gap, still open). Export Coverage 100% against the stored count of 327, Type Coverage 42/42. |
96
109
 
97
110
  Perfection is suspicious. Visible fallibility is trustworthy. SKF writes down the edges it can't score cleanly, so you can read them and decide for yourself whether the remaining coverage is enough for your use case.
98
111
 
99
- ### GAP-004: a worked example of a logged gap
112
+ ### oms-storybook-react-vite: a worked example of a score that was too high
113
+
114
+ A first round of SKF 4.0.0 repairs of oms-storybook-react-vite, three Update Skill runs, passed at 99.93% by documenting about 800 internal names that its brief's wide `code/core/**` glob pulled in. Those repairs were reverted, and the brief now names the story-authoring entry points in `scope.tier_a_include` (280 names).
115
+
116
+ After the tiered brief, two re-tests were blocked and repaired by Update Skill before a third scored the skill at 95.17%. That number was still too high, for two reasons:
117
+ - Export Coverage read 100% because the stored count then credited every documented name ([#792](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/792)).
118
+ - Its signature judges read the doc-block prop lists leniently, a call no rule settled then. Test Skill's signature rules now say how a props list compares.
119
+
120
+ Removing the stored count made the next run name every undocumented export. It measured 206 of the 280 authored names documented, and it found real errors, such as `definePreview` listed under `storybook/preview-api`. Update Skill repaired 22 signatures from a later report. Two fixes were made by hand because its repair halted: the test had cited Angular's `definePreview`, and Update Skill would have copied that signature into a React skill ([#824](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/824), [#830](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/830)). The other hand fixes, also checked against the pinned source, corrected errors no test gap covered and a later re-test's `DocsContainer` and Canvas gaps.
100
121
 
101
- The [`oms-storybook-react-vite` test report](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-storybook-react-vite/10.3.5/test-report-oms-storybook-react-vite.md) scores Export Coverage at **215/216**, not 216/216. The report logs the gap as **GAP-004**: once duplicate entries are folded, the provenance map holds 215 unique exports, all documented, while `metadata.json` still states a denominator of 216. The report names the gap, shows the math, and leaves the drift visible for the next recompilation pass. Nothing was hidden.
122
+ A strict test of the version that ships ([report](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-storybook-react-vite/10.3.5/test-report-oms-storybook-react-vite-20261103T131516Z-6e2b972a.md)) covers all of those fixes. It is blocked on 4 Critical gaps: two compare the React portable-stories wrappers with core's functions, and two are older real errors no earlier run had flagged, now tracked for a fix. So the skill has no score, and oh-my-skills says so instead of quoting the earlier number. Nothing was hidden.
102
123
 
103
124
  That's the pattern SKF asks you to trust: when scoring can't reach 100%, the report says so, cites the line, and leaves a fingerprint for the next audit.
104
125
 
@@ -115,7 +136,7 @@ The score is a weighted sum of up to five categories. The weights below are the
115
136
  | Category | Weight | What it measures |
116
137
  |---|---|---|
117
138
  | **Export Coverage** | 36% | Percentage of source exports documented in `SKILL.md` |
118
- | **Signature Accuracy** | 22% | Documented function signatures match actual source signatures (parameter names, types, order, return types) |
139
+ | **Signature Accuracy** | 22% | Documented function signatures match actual source signatures (parameter names, types, order, return types). A renamed parameter in a TypeScript, JavaScript, Go, Rust, Java, C or C++ file, whose callers pass every argument by position, still matches, and so does a signature the skill labels partial that leaves out only optional parameters. A component is compared by its props alone, a type or return type a signature does not write is not compared, and each form a name is documented in is compared on its own |
119
140
  | **Type Coverage** | 14% | Types and interfaces referenced in exports are fully documented |
120
141
  | **Coherence** | 18% | Cross-references resolve, integration patterns are complete (contextual mode only) |
121
142
  | **External Validation** | 10% | skill-check quality score (0–100), averaged with the Tessl Review score (0–100) when you opt in to Tessl Review |
@@ -203,11 +224,11 @@ Each step rates every gap it records against the Gap Severity table in Test Skil
203
224
 
204
225
  | Severity | Examples |
205
226
  |---|---|
206
- | **Critical** | A wrong signature, or a fabricated one (a documented export that the source does not define at the line the skill cites); a broken reference in a stack skill (a file, skill, type, or `scripts/` or `assets/` file that does not exist) |
207
- | **High** | An inaccurate reference in a stack skill (the target exists but does not match), or a reference whose real path leads outside the skill and its sources; a split-body mismatch (`SKILL.md` and a `references/` file document one export differently); `metadata.json` stats that count every export as documented while some are missing from the skill; in an individual skill, a missing required section, an unbalanced code fence, or an example that awaits a sync function or skips the `await` of an async one |
227
+ | **Critical** | A wrong signature (any difference but the renamed parameter and the partial signature of the Low and Info rows), or a fabricated one (a documented export that the source does not define at the line the skill cites); a broken reference in a stack skill (a file, skill, type, or `scripts/` or `assets/` file that does not exist) |
228
+ | **High** | An inaccurate reference in a stack skill (the target exists but does not match), or a reference whose real path leads outside the skill and its sources; a split-body mismatch (two files of the skill, `SKILL.md` or a `references/` file, document one export differently); `metadata.json` stats that count every export as documented while some are missing from the skill; in an individual skill, a missing required section, an unbalanced code fence, or an example that awaits a sync function or method or skips the `await` of an async one (a method call the check cannot tie to its class is listed but not checked) |
208
229
  | **Medium** | A missing export or type (a missing export lowers Export Coverage); stale documentation (a documented export outside the coverage surface that the run did not match to one declaration in the source, or that a brief scopes out); an incomplete integration pattern in a stack skill; in an individual skill, a code fence with no language tag, a function no example names, or a table row whose column count differs from its header |
209
- | **Low** | Gaps in metadata, the description or provenance lines, such as missing optional metadata or a provenance line that is not the definition line |
210
- | **Info** | Observations such as style suggestions or a documented extra (a documented name outside the coverage surface that the source still declares); discovery testing not performed |
230
+ | **Low** | Gaps in metadata, the description or provenance lines, such as missing optional metadata or a provenance line that is not the definition line; a renamed parameter in a TypeScript, JavaScript, Go, Rust, Java, C or C++ file, whose callers pass arguments by position |
231
+ | **Info** | Observations such as style suggestions or a documented extra (a documented name outside the coverage surface that the source still declares); a partial signature (an optional parameter left out of a signature the skill labels partial); discovery testing not performed |
211
232
 
212
233
  Only some runs can find a documented extra: the source must be Python or TypeScript/JavaScript, and the run needs the Forge, Forge+ or Deep tier, `analysis_confidence` `full`, `workspaceDrift` not `overridden`, a provenance map and a coverage surface that a complete extraction built. On any other run, every documented export outside the coverage surface is stale documentation.
213
234
 
@@ -265,4 +286,4 @@ Dirty output lists each finding. A stale version in the docs is cited by file an
265
286
 
266
287
  ## Reference output: oh-my-skills
267
288
 
268
- Every example in this page points at [**oh-my-skills**](https://github.com/armelhbobdad/oh-my-skills), the SKF reference portfolio. Four Deep-tier skills (cocoindex, cognee, Storybook v10, uitripled), each shipping its full audit trail alongside the compiled skill. Both the worked example for this page and the continuing proof that the pipeline does what it says. If you want to see what SKF produces when you run it on real libraries, that's the answer.
289
+ Every example in this page points at [**oh-my-skills**](https://github.com/armelhbobdad/oh-my-skills), the SKF reference portfolio. Four Deep-tier skills (cocoindex, cognee, Storybook v10, uitripled), each shipping its full audit trail alongside the compiled skill. Both the worked example for this page and a public record of what the pipeline gets right and where its test still falls short. If you want to see what SKF produces when you run it on real libraries, that's the answer.
package/docs/why-skf.md CHANGED
@@ -11,7 +11,7 @@ Skill Forge compiles AI skills where each claim your agent reads points back to
11
11
 
12
12
  Your AI agents read your codebase through the lens of whatever happened to be in their training data. When that training data is wrong, stale, or incomplete, your agent invents: function names that don't exist, parameter types that don't match, config options removed two versions ago. You catch some of it in review. You ship some of it by accident. Every sprint, your team spends hours untangling code that only compiles in the AI's imagination.
13
13
 
14
- SKF treats this as a citation problem, not a model problem. If a skill claims `cognee.search()` takes `query_text` as its first parameter, SKF points to `cognee/api/v1/search/search.py:L27` at commit `3c048aa4` in the upstream repo. That's the whole pitch: **nothing is made up, and everything is falsifiable in 60 seconds.**
14
+ SKF treats this as a citation problem, not a model problem. If a skill claims `cognee.search()` takes `query_text` as its first parameter, SKF points to `cognee/api/v1/search/search.py:L41` at commit `ba3631f2` in the upstream repo. That's the whole pitch: **nothing is made up, and everything is falsifiable in 60 seconds.**
15
15
 
16
16
  ---
17
17
 
package/docs/workflows.md CHANGED
@@ -95,7 +95,7 @@ Each workflow is also a skill you can run directly, without Ferris: `/skf-setup`
95
95
 
96
96
  **Versions:** An update that writes produces a new version of the skill, in a folder of its own beside the previous version, which stays unchanged. For a skill built from a remote repository, the new version is the source's version when that is higher; otherwise it is the next patch version. `active` then points at the new version, and the next update starts from it, exported or not. Update Skill stops rather than overwrite a version that already exists. A gap-driven run updates the current version in place.
97
97
 
98
- **Modes:** By default, Update Skill compares the skill with its source and rebuilds what changed. To repair the gaps a failed test found, run `@Ferris US <name> --from-test-report`: it reads the newest test report and fixes the skill at its pinned commit. It routes each gap by its category, so a missing export, which Test Skill rates Medium, is still re-extracted from the files its remediation names. A default run first looks for an unconsumed test report: a failed or pass-with-drift report newer than the skill that no repair has applied. It then offers to repair that report's gaps (`[G]`) or to check the source for changes (`[S]`), and a headless run checks the source and adds an `unconsumed-test-report` warning. Stack skills are not updated here. Update Skill sends you to `@Ferris SS` to rebuild the stack.
98
+ **Modes:** By default, Update Skill compares the skill with its source and rebuilds what changed. To repair the gaps a failed test found, run `@Ferris US <name> --from-test-report`: it reads the newest test report and fixes the skill at its pinned commit. It routes each gap by its category, so a missing export, which Test Skill rates Medium, is still re-extracted from the files its remediation names, never from one the skill brief's scope leaves out. A default run first looks for an unconsumed test report: a failed or pass-with-drift report newer than the skill that no repair has applied. It then offers to repair that report's gaps (`[G]`) or to check the source for changes (`[S]`), and a headless run checks the source and adds an `unconsumed-test-report` warning. Stack skills are not updated here. Update Skill sends you to `@Ferris SS` to rebuild the stack.
99
99
 
100
100
  **Preview:** `--detect-only` lists the changes and stops. `--dry-run` also re-extracts and shows what would change. Neither writes anything.
101
101
 
@@ -115,7 +115,7 @@ Each workflow is also a skill you can run directly, without Ferris: `/skf-setup`
115
115
 
116
116
  **Note:** QS ignores your forge tier. Every Quick Skill is built the Quick-tier way (its `metadata.json` records `confidence_tier: "Quick"` and `source_authority: "community"`) and uses none of ast-grep, CCC or QMD, even when your forge is set to Forge+ or Deep. For a skill built at your forge tier, use `BS → CS`, `forge`, or `forge-auto`. See [Skill Model](/docs/skill-model.md).
117
117
 
118
- **When to Use:** When you need a skill quickly, with no brief. Accepts package names, a package's npm, PyPI or crates.io page, or GitHub URLs. Append `@version` to target a specific version (for example `@Ferris QS cognee@1.0.0`).
118
+ **When to Use:** When you need a skill quickly, with no brief. Accepts package names, a package's npm, PyPI or crates.io page, or GitHub URLs. Append `@version` to target a specific version (for example `@Ferris QS cognee@1.6.2`).
119
119
 
120
120
  **Package names:** QS looks a package name up on every registry of npm, PyPI and crates.io; a JavaScript, TypeScript, Python or Rust language hint asks only that language's registry. When more than one registry answers, an interactive run lists every candidate and asks which project to build, or takes another GitHub URL. A headless run keeps the first registry's pick (npm, then PyPI, then crates.io) and records an `also_found_in` warning in its result envelope. To build another candidate, pass its GitHub URL, its registry page URL or a language hint (`language=` on a batch line).
121
121
 
@@ -245,7 +245,7 @@ Default pass threshold: **80%**. Inside a pipeline the default follows the alias
245
245
 
246
246
  **Skill name:** A headless run with no skill name halts `input-missing`; an interactive run lists the skills there are to test and asks which one.
247
247
 
248
- **Verdicts and exit codes (headless):** Each outcome has its exit code: `0` PASS, `2` FAIL, `3` INCONCLUSIVE, `4` pass-with-drift, and `1` for a halt before any verdict. A skill run cannot set the exit status of the agent that runs it (`claude -p` exits `0` whatever the run decided), so automators read `exit_code` from the `SKF_TEST_RESULT_JSON` line, or from `skf-test-skill-result-latest.json`. In headless mode that line, which carries the same verdict and score, is the run's last line. A run the hard gate blocks on a Critical or High gap is a FAIL as well: it still writes the Gap Report and a FAIL result record, runs `on_complete` and the health check, and its result line, printed on stderr instead of stdout, has `exit_code` `2`, `status: "error"`, `halt_reason: "hard-gate-blocked"` and no score.
248
+ **Verdicts and exit codes (headless):** Each outcome has its exit code: `0` PASS, `2` FAIL, `3` INCONCLUSIVE, `4` pass-with-drift, and `1` for a halt before any verdict. A skill run cannot set the exit status of the agent that runs it (`claude -p` exits `0` whatever the run decided), so automators read `exit_code` from the run's own `SKF_TEST_RESULT_JSON` line, which in headless mode is the run's last line. A run that printed no such line has no result to read, whatever `claude -p` exits with: it was cut off, or could not write its result, and `skf-test-skill-result-latest.json` may then hold an earlier run's verdict, so never read it in the line's place. A run the hard gate blocks on a Critical or High gap is a FAIL as well: it still writes the Gap Report and a FAIL result record, runs `on_complete` and the health check, and its result line, printed on stderr instead of stdout, has `exit_code` `2`, `status: "error"`, `halt_reason: "hard-gate-blocked"` and no score.
249
249
 
250
250
  **Agent:** Ferris (Audit mode)
251
251
 
@@ -476,7 +476,7 @@ An alias given without its argument asks for it, and a headless run, such as one
476
476
 
477
477
  - Pipelines **automatically turn on headless mode**: every confirmation gate auto-proceeds with its default action
478
478
  - **Data flows automatically**: once the first workflow completes, its brief path or skill name becomes the input for the next workflows
479
- - **Circuit breakers** stop the pipeline when a step's result is not good enough: AN finds nothing worth making into a skill, CS hits a hard error, AS finds CRITICAL drift, TS settles any verdict but PASS, or VS covers none of the technologies the architecture keeps. TS stops the pipeline on every FAIL (a Critical or High gap, a score below its threshold, or a FAIL that a cap forced although the score clears the threshold), on INCONCLUSIVE and on pass-with-drift. A TS score between 80% and a higher pipeline target (90% for `forge-auto`) passes with a fallback evidence report. VS stops the pipeline only on zero coverage, so a `NOT_FEASIBLE` verdict with coverage goes on to RA, which takes each `Blocked` pair as a critical issue.
479
+ - **Circuit breakers** stop the pipeline when a step's result is not good enough: AN finds nothing worth making into a skill, CS hits a hard error, AS finds CRITICAL drift, TS settles any verdict but PASS, or VS covers none of the technologies the architecture keeps. TS stops the pipeline on every FAIL (a Critical or High gap, a score below its threshold, or a FAIL that a cap forced although the score clears the threshold), on INCONCLUSIVE and on pass-with-drift. A TS score between 80% and a higher pipeline target (90% for `forge-auto`) passes with a fallback evidence report. VS stops the pipeline only on zero coverage, so a `NOT_FEASIBLE` verdict with coverage goes on to RA, which takes each `Blocked` pair as a critical issue. A workflow that printed no result line stops the pipeline with halt reason `no-result` (SF: with the line it displayed, if any): it was cut off, or could not write its result, so there is nothing to check.
480
480
  - **`maintain` skips what it does not need.** When AS finds no drift (CLEAN), Update Skill is skipped, unless the source moved past the ref the skill was built from: then US runs with `--target-ref <upstream_ref>`, the ref the audit found. When AS finds CRITICAL drift, `maintain` stops so you can review the drift report; then run `@Ferris US <name>` yourself.
481
481
  - **Resume:** each pipeline keeps its state in a journal, `pipeline-journal.json`, in its own run folder under `_bmad-output/.skf-run/`, written after every workflow. If one stopped partway, the next time you start Ferris he offers to rerun the step it stopped on with the recorded plan, then the rest. A quality halt gets its repair route instead: after a TS FAIL, `US <name> --from-test-report`, then TS and EX at the recorded threshold. You can also drop the offer, which deletes that run folder. When the chain ends, Ferris writes its result to `pipeline-result-latest.json` in his sidecar folder.
482
482
  - **Anti-pattern warnings**: Ferris warns if you chain workflows in a problematic order (for example, exporting before testing)
@@ -576,7 +576,7 @@ Setup is not the only workflow whose result line is the run's final message: a h
576
576
  - **`/skf-rename-skill` (RS)**: exit `2` `input-missing`/`input-invalid`, `3` resolution-failure (`manifest-corrupt`, `nothing-to-rename`), `4` write-failure (`copy-failed`, `write-failed`, `manifest-write-failed`; the context-file rebuild is best-effort and never halts), `5` state-conflict (`name-collision`, `source-authority-blocked`, `halted-for-concurrent-run` while another rename of the same skill holds its run lock or when this run's own lock went stale while it waited at a prompt, `not-skf-output` for a folder SKF did not generate, or a forge folder that also holds other files or that is a link, is not a folder, or cannot be listed; `flat-layout` for a skill not yet migrated; `verify-failed` when the old name is still in the renamed files, which rolls the rename back with the old skill intact, see [Troubleshooting](/docs/troubleshooting.md)). The run lock is `.skf-rename-<name>.lock` in `forge_data_folder`, and it goes stale 60 minutes after it was taken or renewed: when no other rename of that skill is running, delete the lock file the message names, or wait until the time it gives, then run the rename again. When this run's own lock went stale at a prompt, nothing was changed: run the rename again. Exit codes in [`src/skf-rename-skill/references/exit-codes.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-rename-skill/references/exit-codes.md); the envelope in [`src/skf-rename-skill/references/invocation-contract.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-rename-skill/references/invocation-contract.md) § "Result Contract (Headless)".
577
577
  - **`/skf-refine-architecture` (RA)**: exit `2` `input-missing`/`input-invalid` (`input-invalid` also covers a Verify Stack report with a `schemaVersion` other than `1.0`, a missing or doubled verdict table, or an unknown verdict token), `3` resolution-failure (`output-folder-unconfigured`, `forge-folder-unconfigured`, and `resolution-failure` when the result emitter or `scripts/skf-check-preservation.py` cannot start, when the refinement rules cannot be read, lack one of their six tables or break a tier rule (phase `init:rules`), or when the skill inventory helper (phase `init:inventory`) or the co-mention helper (phase `gap-analysis:comention`) is missing or its call fails, or the Verify Stack report validator (phase `init:feasibility-validator`) is missing), `4` write-failure (`write-failed`, and `preservation-failed` when the draft would lose or change a line of the original: at once in a headless run, or when the promotion still finds it after one rebuild), `5` state-conflict (`insufficient-skills`: no skill SKF generated was found), `7` `inventory-unreliable`, `8` `recovery-failed` (at step 3, the Verify Stack report cannot be read again, Verify Stack rewrote it during the run, or the refinement rules no longer pass step 1's check; the saved state is not enough to rebuild the findings; or a record of the draft's build or promotion is missing at step 6). Exit codes and the envelope in [`src/skf-refine-architecture/references/exit-codes.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-refine-architecture/references/exit-codes.md).
578
578
 
579
- **Reading the result lines.** Check each line's `status` field first (for US, `skf_update.status`). For most workflows it only says whether the run finished or stopped on an error, so read the outcome from the fields below: a TS run whose skill scores below its threshold still reports `status: "success"`, with `verdict: "FAIL"`. AN, CS, SS, AS, TS, EX and VS also give each outcome a stable exit code, which the line carries as `exit_code` (a CS halt has `2` to `6`).
579
+ **Reading the result lines.** Read the run's own line: a run that printed none has no result to read, whatever the process exit code (`claude -p` exits `0` when the agent ends its turn early): it was cut off, or could not write its result. Treat it as a failure, and never read a `-latest.json` result file in its place, since that file may then describe an earlier run. A cut-off run can also leave its run lock until the lock goes stale, 60 minutes after it was taken or renewed: Test Skill's `.test-skill.lock`, Update Skill's `.skf-update.lock` or Rename Skill's `.skf-rename-<name>.lock` ([Troubleshooting](/docs/troubleshooting.md#test-skill-stops-with-another-run-active)). Check each line's `status` field first (for US, `skf_update.status`). For most workflows it only says whether the run finished or stopped on an error, so read the outcome from the fields below: a TS run whose skill scores below its threshold still reports `status: "success"`, with `verdict: "FAIL"`. AN, CS, SS, AS, TS, EX and VS also give each outcome a stable exit code, which the line carries as `exit_code` (a CS halt has `2` to `6`).
580
580
 
581
581
  | Workflow | Result line | Fields worth reading |
582
582
  | --- | --- | --- |
@@ -597,7 +597,7 @@ To run these without questions, pass their inputs as flags. Analyze Source takes
597
597
 
598
598
  Every workflow ships a `customize.toml` with settings you can change. Do not edit that file: updates overwrite it. Put your changes in `_bmad/custom/<skill-name>.toml` (shared with your team) or `_bmad/custom/<skill-name>.user.toml` (just for you), for example `_bmad/custom/skf-test-skill.toml`. Start the file with a `[workflow]` line, as the bundled file does: a setting above it is ignored without a warning. A single value replaces the default; a list adds to it.
599
599
 
600
- These overrides are read by BMAD Method's customization script, which the BMAD Method installer adds. Every workflow, and Ferris, runs that script through `uv` when it starts. When the script cannot run, the run prints one warning, `[activation/warn] customization_resolver_unavailable: <reason>`, and uses the bundled defaults, ignoring `_bmad/custom/`. A workflow also adds a warning naming `customization_resolver_unavailable` and its reason to the run's warnings, in the result envelope and result file of a run that writes them (Campaign logs it in its decision log, a `--quiet` or headless Setup Forge run shows it only in its envelope, and an interactive Export Skill run only prints it). In a project with SKF alone, that script is missing, so every run warns `customization_resolver_unavailable: not found` and uses the bundled defaults.
600
+ These overrides are read by BMAD Method's customization script, which the BMAD Method installer adds. Every workflow, and Ferris, runs that script through `uv` when it starts. When the script cannot run, the run prints one warning, `[activation/warn] customization_resolver_unavailable: <reason>`, and uses the bundled defaults, ignoring `_bmad/custom/`. A workflow also adds a warning naming `customization_resolver_unavailable` and its reason to the run's warnings, in the result envelope and result file of a run that writes them (Campaign logs it in its decision log, a `--quiet` or headless Setup Forge run shows it only in its envelope, and an interactive Export Skill run only prints it). In a project with SKF alone, that script is missing, so every run warns `customization_resolver_unavailable: not found` and uses the bundled defaults. On a BMAD core older than 6.11.0, whose script reads the overrides of the nearest folder holding `_bmad` or `.git`, from the workflow's own folder up, SKF still applies them when that folder is this project, and otherwise warns `customization_resolver_unavailable: BMAD core before 6.11.0: ...`, naming the folder that script would read and the upgrade to 6.11.0 or later.
601
601
 
602
602
  Ferris reads `_bmad/custom/skf-forger.toml` and `_bmad/custom/skf-forger.user.toml` the same way when he starts, under an `[agent]` line, as his bundled file does (a `[workflow]` table there is ignored). An override there can add `activation_steps_prepend`, which runs once his config is loaded, `persistent_facts` (a `!` entry drops each earlier entry it names) and `activation_steps_append`, which runs after his greeting. His name, title and the other agent roster values stay as shipped, and he has no `on_complete`. When the script cannot run, the warning line opens his greeting.
603
603
 
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.0.0",
4
+ "version": "6.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",
@@ -23,12 +23,12 @@ Bridge names (`ast_bridge`, `ccc_bridge`, `qmd_bridge`, `gh_bridge`) and subproc
23
23
 
24
24
  Workflow steps reference "subprocess" execution with numbered patterns. These map to concrete mechanisms per IDE:
25
25
 
26
- | Pattern | Purpose | Claude Code | Cursor | CLI |
27
- |----------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------|-----------------------------------|------------------------------------|
28
- | **Pattern 1** (grep/search) | Search across files for imports, co-imports | Grep tool or Bash with `rg` | Built-in search | `grep` / `rg` |
29
- | **Pattern 2** (per-file deep analysis) | Deep analysis of individual files or units | Agent tool (sequential per unit) | Sequential analysis | Per-unit script |
30
- | **Pattern 3** (data operations) | QMD queries, severity classification, data transforms | Main thread (Read + process) | Main thread | Script |
31
- | **Pattern 4** (parallel execution) | Parallel per-library extraction, parallel diffing | Agent tool with multiple parallel calls or `run_in_background: true` | Parallel requests (IDE-dependent) | `xargs -P` or background processes |
26
+ | Pattern | Purpose | Claude Code | Cursor | CLI |
27
+ |----------------------------------------|-------------------------------------------------------|--------------------------------------------------------------------------|-----------------------------------|-------------------------------------|
28
+ | **Pattern 1** (grep/search) | Search across files for imports, co-imports | Grep tool or Bash with `rg` | Built-in search | `grep` / `rg` |
29
+ | **Pattern 2** (per-file deep analysis) | Deep analysis of individual files or units | Agent tool (foreground, sequential per unit) | Sequential analysis | Per-unit script |
30
+ | **Pattern 3** (data operations) | QMD queries, severity classification, data transforms | Main thread (Read + process) | Main thread | Script |
31
+ | **Pattern 4** (parallel execution) | Parallel per-library extraction, parallel diffing | Several foreground Agent calls in one message, never `run_in_background` | Parallel requests (IDE-dependent) | `xargs -P` (it waits for every job) |
32
32
 
33
33
  ### max_parallel_generation
34
34
 
@@ -81,9 +81,12 @@ A gate appends its decision the moment it decides, and a step appends a warning
81
81
 
82
82
  ```bash
83
83
  uv run {emitEnvelopeHelper} record --workflow <workflow> --run-dir "{run_dir}" --decision < "{run_dir}/decision.json"
84
- uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "<warning>"
84
+ uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "<warning>" [--warning "<another>"]
85
+ uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warnings-file "{run_dir}/<helper output>.json"
85
86
  ```
86
87
 
88
+ `--warning` repeats, one per warning. `--warnings-file` records, in one call, every warning in a helper's saved JSON output: an array of strings, or an object whose `warnings` is one. A blank or non-string warning, or a file with no such list, records nothing and exits 1; an empty list records nothing and exits 0.
89
+
87
90
  With `--workflow`, a decision the workflow's envelope schema rejects fails here, at the gate; without it, the emitter leaves the decision out when the run ends and adds a `headless_decision_invalid` warning. Because the sink is on disk, the trail survives context compaction, and a HARD HALT reports every decision taken before it.
88
91
 
89
92
  When the On Activation customization resolver is missing or fails, the run applies only the skill's own `customize.toml`. Record `customization_resolver_unavailable: <reason>` as a warning, or pass the reason in the envelope payload as `customization_resolver_unavailable`, so a pipeline sees that the team and user overrides under `_bmad/custom/` were not applied. Campaign's run spans several sessions, so it logs `customization_resolver_unavailable: <reason>` as an `event` in its decision log instead, and its result envelope's `decision_log` names that file.
@@ -31,14 +31,14 @@ Each run writes **two files** to `{output_dir}`:
31
31
  - Timestamp is UTC, resolution to seconds, for example `update-skill-result-20260413-145230.json`
32
32
  - When a run of the same workflow already took that second's name, the emitter appends `-2`, `-3` and so on (`update-skill-result-20260413-145230-2.json`), so two runs that end in the same second never share a file
33
33
  - Never overwritten by subsequent runs, so it keeps a durable audit trail across retries, aborts, and re-runs
34
- 2. **Stable latest pointer** (pipeline consumption): `{skill-name}-result-latest.json`
34
+ 2. **Stable latest pointer** (a stable path, not a run's result): `{skill-name}-result-latest.json`
35
35
  - A **copy** (not a symlink) of the per-run record just written
36
- - Always present at a deterministic path so CI / pipelines / the forger can read `summary.*` without enumerating timestamps
36
+ - At a deterministic path, so a reader finds `summary.*` without enumerating timestamps: it holds the last run whose emitter wrote this copy
37
37
  - Overwritten on every successful write
38
38
 
39
39
  Write the per-run record first, then copy it to the `-latest.json` path. If the copy fails, the per-run record still exists — the run is not lost.
40
40
 
41
- **Consumers (forger, CI, chained workflows):** read from `{skill-name}-result-latest.json`. Do not enumerate timestamped files unless inspecting prior-run history.
41
+ **Consumers (forger, CI, chained workflows):** a caller that started a run reads that run's own `SKF_<NAME>_RESULT_JSON` envelope line. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, and `{skill-name}-result-latest.json` may then describe an earlier run, so it is never read in its place. A tool that looks up the last run of a skill rather than one it started (skf-find-test-report.py, forge-status.py) may read the copy. Do not enumerate timestamped files unless inspecting prior-run history.
42
42
 
43
43
  ## Writing the Files
44
44
 
@@ -36,6 +36,7 @@ The forger also accepts common pipeline aliases:
36
36
  4. **Circuit breakers** — if a workflow's output fails its quality check, the pipeline halts with a summary of what completed and what remains
37
37
  5. **Error halts propagate** — if any workflow hard-halts, the pipeline stops immediately
38
38
  6. **Progress reporting** — the forger reports completion of each workflow before starting the next
39
+ 7. **A missing result line halts**: a workflow that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, so the pipeline halts with `no-result` (SF: with the line it did display, or `no-result` when it displayed none), and the forger never reads a `-latest.json` file in its place
39
40
 
40
41
  ## Pipeline Arguments
41
42
 
@@ -117,7 +118,7 @@ Every chained workflow's data context carries `pipeline_mode: true`, which marks
117
118
 
118
119
  ## Pipeline Result
119
120
 
120
- `pipeline-journal.py finish` writes each chain's result from its journal, per `output-contract-schema.md`: the per-run record `{sidecar_path}/pipeline-result-<YYYYMMDD-HHmmss>.json` (UTC; `-2`, `-3` ... when a run already took that second's name) and its copy `{sidecar_path}/pipeline-result-latest.json`, the file consumers read.
121
+ `pipeline-journal.py finish` writes each chain's result from its journal, per `output-contract-schema.md`: the per-run record `{sidecar_path}/pipeline-result-<YYYYMMDD-HHmmss>.json` (UTC; `-2`, `-3` ... when a run already took that second's name) and its copy `{sidecar_path}/pipeline-result-latest.json`, which holds the last chain, or attempt, that reached `finish`. A chain cut off before `finish` writes none, so the file may describe an earlier chain or an earlier attempt of this one: a caller reads the envelope lines the chain's workflows print, never this file in their place.
121
122
 
122
123
  ```json
123
124
  {
@@ -120,7 +120,7 @@
120
120
  "health-check-missing",
121
121
  "hard-gate-blocked"
122
122
  ],
123
- "description": "Null for a scored run. On a halt, the failure class: input-missing (headless with no skill name), input-invalid (a --tier value that names no tier), runtime-missing (python3 or uv is not on PATH), helper-missing (an SKF helper is not installed), helper-failed (a helper or scoring script exited with an error), write-failed (the run folder, the report or the result contract could not be written), target-inaccessible (no version of the skill to test), not-skf-output, frontmatter-invalid, forge-tier-missing, workspace-drift, another-run-active, inventory-invalid (the documented inventory failed its schema check or spot-check twice), indeterminate-surface (zero exports, or an empty stack surface: no score can be attached), step-completeness-violation, report-anchor-missing, health-check-missing; hard-gate-blocked for a run the hard gate blocked (verdict FAIL, exit 2)."
123
+ "description": "Null for a scored run. On a halt, the failure class: input-missing (headless with no skill name), input-invalid (a --tier value that names no tier), runtime-missing (python3 or uv is not on PATH), helper-missing (an SKF helper is not installed), helper-failed (a helper or scoring script exited with an error), write-failed (the run folder, the report or the result contract could not be written), target-inaccessible (no version of the skill to test), not-skf-output, frontmatter-invalid, forge-tier-missing, workspace-drift, another-run-active, inventory-invalid (the documented inventory failed its schema check, spot-check or floor check twice), indeterminate-surface (zero exports, or an empty stack surface: no score can be attached), step-completeness-violation, report-anchor-missing, health-check-missing; hard-gate-blocked for a run the hard gate blocked (verdict FAIL, exit 2)."
124
124
  },
125
125
  "run_id": {
126
126
  "type": ["string", "null"],
@@ -50,7 +50,7 @@
50
50
  "halted-for-concurrent-run",
51
51
  "blocked"
52
52
  ],
53
- "description": "Single-field outcome for pipeline branching. 'success' when the run completed and wrote the updated artifacts. 'no-changes' when detect-changes found nothing to update. 'detect-only' when the workflow was invoked with --detect-only and exited after step 2 without modifying any artifact. 'dry-run' when the workflow was invoked with --dry-run and exited after step 3 (re-extract) without merging or writing: the envelope describes what merge+write WOULD have done. Each 'halted-for-*' value corresponds to a documented halt in the stage prose (workspace-drift from gap-driven.md §3, brief-refinement from detect-changes.md §1c [U] / §2.2 [B], audit from §2.2 [A], remediation-path from gap-driven.md §4a, manual-mismatch from write.md §1, after write.md §6's provenance fixes and after write.md §7's skill-check edits (all error.phase 'write:verify-manual-integrity'), from merge.md §6b's check of the merged SKILL.md before it is published (error.phase 'merge:verify-manual-integrity') and from a headless merge that meets a [MANUAL] conflict (error.phase 'merge:conflict-resolution'), write-failure for a merge.md §6b version-folder or SKILL.md write failure (among them error.phase 'merge:new-version-folder' when the version folder already exists or the next version cannot be computed), a failed write in write.md §2 to §5 (error.phase 'write:artifact-write') and a write.md §8/§8a active-link failure, concurrent-run when init.md §1b's acquire finds another run's fresh run lock (error.phase 'init:concurrency-guard'), or when this run no longer holds its lock at the renewal before merge.md §6b or write.md §2 writes (error.phase 'merge:run-lock' or 'write:run-lock'; error.reason 'run-lock-lost: ...' when the lock lapsed while the run waited), or when write.md §8 finds another flip of the active link holding its lock for 5 s (error.phase 'write:active-symlink', error.reason 'busy: ...'). error.reason names the run that holds a run lock, never a PID). 'blocked' is the catch-all for halts that don't map to a documented status code, among them a headless run given no skill name (error.phase 'init:skill-name', error.reason starting 'input-missing'), a stack skill (error.phase 'init:stack-skill-guard'), init.md §6b (error.phase 'init:source-tree') when no commit to read could be obtained, a helper that failed (each its own '<step>:<section>' phase), and a private source tree that disappeared mid-run (error.phase '<step>:source-tree-missing'). A halt after merge.md §6b's first write, until write.md §10 closes the rollback window, first undoes it: a gap-driven repair's package is restored from the snapshot it took, and a new version's folders are removed, unless the active link already names the new version, which the halt keeps. write.md §8 points the active link at the new version only after §6 and §7, the last checks that can halt, so a [MANUAL] halt never leaves a live version behind. NOTE: detect-only and dry-run are successful exits: pipelines treating non-success as failure must include them in the success set."
53
+ "description": "Single-field outcome for pipeline branching. 'success' when the run completed and wrote the updated artifacts. 'no-changes' when detect-changes found nothing to update. 'detect-only' when the workflow was invoked with --detect-only and exited after step 2 without modifying any artifact. 'dry-run' when the workflow was invoked with --dry-run and exited after step 3 (re-extract) without merging or writing: the envelope describes what merge+write WOULD have done. Each 'halted-for-*' value corresponds to a documented halt in the stage prose (workspace-drift from gap-driven.md §3, brief-refinement from detect-changes.md §1c [U] / §2.2 [B], audit from §2.2 [A], remediation-path from gap-driven.md §4a, manual-mismatch from write.md §1, after write.md §6's provenance fixes and after write.md §7's skill-check edits (all error.phase 'write:verify-manual-integrity'), from merge.md §6b's check of the merged SKILL.md before it is published (error.phase 'merge:verify-manual-integrity') and from a headless merge that meets a [MANUAL] conflict (error.phase 'merge:conflict-resolution'), write-failure for a merge.md §6b version-folder or SKILL.md write failure (among them error.phase 'merge:new-version-folder' when the version folder already exists or the next version cannot be computed), a failed write in write.md §2 to §5 (error.phase 'write:artifact-write') and a write.md §8/§8a active-link failure, concurrent-run when init.md §1b's acquire finds another run's fresh run lock (error.phase 'init:concurrency-guard'), or when this run no longer holds its lock at the renewal before merge.md §6b or write.md §2 writes (error.phase 'merge:run-lock' or 'write:run-lock'; error.reason 'run-lock-lost: ...' when the lock lapsed while the run waited), or when write.md §8 finds another flip of the active link holding its lock for 5 s (error.phase 'write:active-symlink', error.reason 'busy: ...'). error.reason names the run that holds a run lock, never a PID). 'blocked' is the catch-all for halts that don't map to a documented status code, among them a headless run given no skill name (error.phase 'init:skill-name', error.reason starting 'input-missing'), a stack skill (error.phase 'init:stack-skill-guard'), init.md §6b (error.phase 'init:source-tree') when no commit to read could be obtained, a helper that failed (each its own '<step>:<section>' phase), write.md §7's body split (error.phase 'write:body-split': skf-shard-body.py exited 1 or 2 or printed no JSON, or reported a Tier 1 heading pulled or a cross-reference that does not resolve), and a private source tree that disappeared mid-run (error.phase '<step>:source-tree-missing'). A halt after merge.md §6b's first write, until write.md §10 closes the rollback window, first undoes it: a gap-driven repair's package is restored from the snapshot it took, and a new version's folders are removed, unless the active link already names the new version, which the halt keeps. write.md §8 points the active link at the new version only after §6 and §7, the last checks that can halt, so a [MANUAL] halt never leaves a live version behind. NOTE: detect-only and dry-run are successful exits: pipelines treating non-success as failure must include them in the success set."
54
54
  },
55
55
  "skill_name": {
56
56
  "type": "string",
@@ -76,10 +76,18 @@
76
76
  "type": "array",
77
77
  "items": {
78
78
  "type": "string",
79
- "enum": ["SKILL.md", "metadata.json", "provenance-map.json", "evidence-report.md", "context-snippet.md", "active-symlink"]
79
+ "enum": [
80
+ "SKILL.md",
81
+ "references",
82
+ "metadata.json",
83
+ "provenance-map.json",
84
+ "evidence-report.md",
85
+ "context-snippet.md",
86
+ "active-symlink"
87
+ ]
80
88
  },
81
89
  "uniqueItems": true,
82
- "description": "Artifacts actually written or updated this run. Empty array when a halt fired before any write completed."
90
+ "description": "Artifacts actually written or updated this run. 'references' stands for one or more references/*.md files of the skill package written, added or removed (merge.md §6b's merge, at any priority, write.md §6's provenance fixes or write.md §7's body split), one value however many. Empty array when a halt fired before any write completed."
83
91
  },
84
92
  "headless_decisions": {
85
93
  "type": "array",
@@ -86,7 +86,7 @@
86
86
  "include": { "type": "array", "items": { "type": "string" } },
87
87
  "tier_a_include": {
88
88
  "type": "array",
89
- "description": "Optional narrower tier-A include list. When present it becomes the coverage-denominator basis instead of the coarse scope.include, and it exempts the brief from the denominator deflation guard in skf-test-skill references/source-access-protocol.md — so an empty or blank-item list must not be accepted: it would silence that guard while narrowing nothing.",
89
+ "description": "Optional narrower tier-A include list. When present it becomes the coverage-denominator basis instead of the coarse scope.include, and the denominator deflation guard in skf-test-skill references/source-access-protocol.md measures the stored effective_denominator against it, in either direction, instead of against scope.include. An empty or blank-item list must not be accepted: it would narrow nothing.",
90
90
  "minItems": 1,
91
91
  "items": { "type": "string", "minLength": 1 }
92
92
  },