bmad-module-skill-forge 3.0.0 → 5.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 (141) 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/architecture.md +2 -2
  5. package/docs/concepts.md +4 -4
  6. package/docs/examples.md +33 -26
  7. package/docs/getting-started.md +3 -3
  8. package/docs/how-it-works.md +2 -2
  9. package/docs/index.md +1 -1
  10. package/docs/skill-model.md +41 -39
  11. package/docs/troubleshooting.md +8 -0
  12. package/docs/verifying-a-skill.md +46 -21
  13. package/docs/why-skf.md +1 -1
  14. package/docs/workflows.md +5 -5
  15. package/package.json +1 -1
  16. package/src/knowledge/qmd-registry.md +1 -1
  17. package/src/knowledge/version-paths.md +1 -1
  18. package/src/shared/health-check.md +2 -50
  19. package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +2 -2
  20. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +12 -4
  21. package/src/shared/scripts/schemas/skill-brief.v1.json +1 -1
  22. package/src/shared/scripts/skf-atomic-write.py +212 -59
  23. package/src/shared/scripts/skf-build-change-manifest.py +1437 -178
  24. package/src/shared/scripts/skf-classify-changed-files.py +78 -0
  25. package/src/shared/scripts/skf-compare-file-hashes.py +266 -104
  26. package/src/shared/scripts/skf-count-tokens.py +54 -10
  27. package/src/shared/scripts/skf-derive-assembly-shape.py +18 -1
  28. package/src/shared/scripts/skf-detect-scripts-assets.py +196 -54
  29. package/src/shared/scripts/skf-detect-workspaces.py +67 -0
  30. package/src/shared/scripts/skf-emit-result-envelope.py +6 -5
  31. package/src/shared/scripts/skf-extract-public-api.py +1673 -189
  32. package/src/shared/scripts/skf-extraction-inventory.py +584 -46
  33. package/src/shared/scripts/skf-extraction-snapshot.py +158 -33
  34. package/src/shared/scripts/skf-forge-tier-rw.py +26 -3
  35. package/src/shared/scripts/skf-github-probe.py +47 -8
  36. package/src/shared/scripts/skf-hash-content.py +14 -8
  37. package/src/shared/scripts/skf-load-provenance.py +3 -1
  38. package/src/shared/scripts/skf-merge-doc-urls.py +142 -19
  39. package/src/shared/scripts/skf-parse-gaps.py +135 -26
  40. package/src/shared/scripts/skf-preflight.py +15 -1
  41. package/src/shared/scripts/skf-provenance-gap-dispatch.py +25 -1
  42. package/src/shared/scripts/skf-render-metadata-stats.py +1 -1
  43. package/src/shared/scripts/skf-resolve-authoritative-files.py +29 -3
  44. package/src/shared/scripts/skf-resolve-customization.py +197 -0
  45. package/src/shared/scripts/skf-shard-body.py +239 -41
  46. package/src/shared/scripts/skf-structural-diff.py +133 -18
  47. package/src/shared/scripts/skf-update-active-symlink.py +115 -62
  48. package/src/shared/scripts/skf-validate-brief-schema.py +19 -1
  49. package/src/shared/scripts/skf-validate-output.py +12 -3
  50. package/src/shared/scripts/skf-verify-provenance-completeness.py +1439 -79
  51. package/src/shared/scripts/skf-write-skill-brief.py +41 -8
  52. package/src/shared/tool-requirements.yaml +8 -2
  53. package/src/skf-analyze-source/SKILL.md +2 -2
  54. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  55. package/src/skf-analyze-source/references/map-unit-exports.md +1 -1
  56. package/src/skf-audit-skill/SKILL.md +2 -2
  57. package/src/skf-audit-skill/assets/drift-report-template.md +1 -0
  58. package/src/skf-audit-skill/references/headless-contract.md +1 -1
  59. package/src/skf-audit-skill/references/health-check.md +2 -0
  60. package/src/skf-audit-skill/references/init.md +2 -1
  61. package/src/skf-audit-skill/references/re-index.md +14 -9
  62. package/src/skf-audit-skill/references/report.md +2 -2
  63. package/src/skf-audit-skill/references/semantic-diff.md +15 -15
  64. package/src/skf-audit-skill/references/structural-diff.md +10 -5
  65. package/src/skf-audit-skill/scripts/render-drift-tables.py +29 -2
  66. package/src/skf-brief-skill/SKILL.md +3 -3
  67. package/src/skf-brief-skill/assets/scope-templates.md +1 -1
  68. package/src/skf-brief-skill/assets/skill-brief-schema.md +4 -11
  69. package/src/skf-brief-skill/references/analyze-target.md +6 -6
  70. package/src/skf-brief-skill/references/confirm-brief.md +1 -1
  71. package/src/skf-brief-skill/references/scope-definition.md +4 -4
  72. package/src/skf-brief-skill/references/step-auto-brief.md +11 -29
  73. package/src/skf-brief-skill/references/version-resolution.md +5 -9
  74. package/src/skf-campaign/SKILL.md +2 -2
  75. package/src/skf-campaign/references/step-05-skill-loop.md +6 -1
  76. package/src/skf-create-skill/SKILL.md +2 -2
  77. package/src/skf-create-skill/assets/compile-assembly-rules.md +5 -3
  78. package/src/skf-create-skill/assets/skill-sections.md +7 -7
  79. package/src/skf-create-skill/references/authoritative-files-protocol.md +2 -23
  80. package/src/skf-create-skill/references/compile.md +31 -60
  81. package/src/skf-create-skill/references/component-extraction.md +3 -3
  82. package/src/skf-create-skill/references/enrich.md +2 -2
  83. package/src/skf-create-skill/references/entry-points-by-hand.md +24 -7
  84. package/src/skf-create-skill/references/extract.md +23 -19
  85. package/src/skf-create-skill/references/extraction-patterns-tracing.md +9 -9
  86. package/src/skf-create-skill/references/extraction-patterns.md +10 -11
  87. package/src/skf-create-skill/references/generate-artifacts.md +1 -1
  88. package/src/skf-create-skill/references/report.md +21 -5
  89. package/src/skf-create-skill/references/source-resolution-protocols.md +12 -17
  90. package/src/skf-create-skill/references/sub/fetch-docs.md +1 -1
  91. package/src/skf-create-stack-skill/SKILL.md +2 -2
  92. package/src/skf-create-stack-skill/references/validate.md +1 -1
  93. package/src/skf-drop-skill/SKILL.md +2 -2
  94. package/src/skf-drop-skill/references/execute.md +5 -1
  95. package/src/skf-drop-skill/references/health-check.md +2 -0
  96. package/src/skf-drop-skill/references/invocation-contract.md +1 -1
  97. package/src/skf-drop-skill/references/report.md +2 -2
  98. package/src/skf-drop-skill/references/select.md +7 -3
  99. package/src/skf-export-skill/SKILL.md +2 -2
  100. package/src/skf-export-skill/assets/snippet-format.md +5 -5
  101. package/src/skf-export-skill/references/generate-snippet.md +22 -15
  102. package/src/skf-export-skill/references/load-skill.md +1 -1
  103. package/src/skf-export-skill/references/token-report.md +29 -9
  104. package/src/skf-forger/SKILL.md +2 -2
  105. package/src/skf-quick-skill/SKILL.md +2 -2
  106. package/src/skf-refine-architecture/SKILL.md +6 -8
  107. package/src/skf-rename-skill/SKILL.md +2 -2
  108. package/src/skf-setup/SKILL.md +2 -2
  109. package/src/skf-test-skill/SKILL.md +2 -2
  110. package/src/skf-test-skill/assets/output-section-formats.md +6 -4
  111. package/src/skf-test-skill/references/coherence-check.md +5 -4
  112. package/src/skf-test-skill/references/coverage-check-branches.md +61 -0
  113. package/src/skf-test-skill/references/coverage-check-tiers.md +5 -5
  114. package/src/skf-test-skill/references/coverage-check.md +69 -111
  115. package/src/skf-test-skill/references/init.md +5 -6
  116. package/src/skf-test-skill/references/migration-section-rules.md +26 -14
  117. package/src/skf-test-skill/references/report.md +11 -9
  118. package/src/skf-test-skill/references/scoring-rules.md +8 -6
  119. package/src/skf-test-skill/references/source-access-protocol.md +15 -18
  120. package/src/skf-test-skill/scripts/build-result-context.py +114 -21
  121. package/src/skf-test-skill/scripts/gap-ledger.py +580 -80
  122. package/src/skf-test-skill/scripts/load-coverage-inputs.py +646 -107
  123. package/src/skf-test-skill/scripts/locate-export-segments.py +101 -20
  124. package/src/skf-test-skill/scripts/reconcile-coverage.py +450 -23
  125. package/src/skf-test-skill/scripts/score-signatures.py +356 -61
  126. package/src/skf-test-skill/scripts/stage-helper-payload.py +64 -13
  127. package/src/skf-test-skill/scripts/validate-inventory.py +108 -9
  128. package/src/skf-test-skill/scripts/verify-declared-numerator.py +7 -5
  129. package/src/skf-test-skill/templates/test-report-template.md +1 -0
  130. package/src/skf-update-skill/SKILL.md +2 -2
  131. package/src/skf-update-skill/references/detect-changes.md +32 -13
  132. package/src/skf-update-skill/references/gap-driven.md +37 -22
  133. package/src/skf-update-skill/references/init.md +6 -2
  134. package/src/skf-update-skill/references/invocation-contract.md +1 -1
  135. package/src/skf-update-skill/references/merge.md +17 -12
  136. package/src/skf-update-skill/references/re-extract.md +4 -2
  137. package/src/skf-update-skill/references/report.md +7 -4
  138. package/src/skf-update-skill/references/write.md +77 -36
  139. package/src/skf-update-skill/scripts/skf-new-file-diff.py +157 -42
  140. package/src/skf-verify-stack/SKILL.md +2 -2
  141. 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 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)). 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). 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 apply the written rule, write down how they read the cases it does not settle ([#827](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/827), [#835](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/835), [#843](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/843), [#845](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/845)), and say which calls they counted. They blocked all four at first. 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. 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; counted, they would be nine Critical signature gaps (18/27), and the hard gate would block the run. 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. |
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
100
113
 
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.
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 settles ([#827](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/827)).
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.
121
+
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 |
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,15 @@ 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) |
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) |
207
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 (`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 |
208
- | **Medium** | A missing export or type (a missing export lowers Export Coverage); stale documentation (a documented export the source no longer exports); 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; discovery testing not performed |
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 |
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 |
232
+
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.
234
+
235
+ A declaration the run finds only in an `example`, `demo`, `demos`, `sample`, `samples`, `scripts`, `playground`, `bench`, `benchmark` or `migrations` folder right below a package's root, or in an `e2e` or `__fixtures__` folder anywhere, does not count, so a name declared only in `scripts/foo.py` stays stale documentation unless the provenance map cites a file in that folder. Nor does a module named after the name count (such as `client.py` for a documented `client()`) when the skill documents the name as a function, hook, class, interface, enum or type, unless the provenance map records the name as a module or package, or the module is a TypeScript or JavaScript file that is itself the export (`export default`, `module.exports =`).
211
236
 
212
237
  Discovery testing runs after the hard gate, so a discovery gap (Info, Medium or High) is counted in the Gap Report and blocks nothing.
213
238
 
@@ -261,4 +286,4 @@ Dirty output lists each finding. A stale version in the docs is cited by file an
261
286
 
262
287
  ## Reference output: oh-my-skills
263
288
 
264
- 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
@@ -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
 
@@ -208,7 +208,7 @@ Each workflow is also a skill you can run directly, without Ferris: `/skf-setup`
208
208
 
209
209
  **Baseline:** An audit compares the skill with the baseline it recorded when it was built. An interactive audit shows its baseline summary and analysis plan and goes straight on. It stops to ask only when this run's forge tier is below the tier the skill was compiled at (so the diff can report as drift what only the tools changed; a compose-mode stack or a docs-only skill re-reads no source and never stops for this), or when the provenance map is more than 90 days old. At that stop, `[C]` continues, and `[X]` stops the run with exit `6` (`user-cancelled`) and keeps the drift report, which then holds the baseline only. A headless run continues. A skill with no provenance map, such as a Quick Skill, stops with `no-baseline` (exit `3`) in every mode, and the message points to `@Ferris TS` (test it against its source) or `@Ferris CS` (create it again so it can be audited). A docs-only skill is audited by its documents: each tracked document's hash is compared with the one recorded at compile time, and a changed document is graded HIGH, so the score is SIGNIFICANT, with `next_workflow` `update-skill`, or CLEAN when nothing changed. A docs-only skill that records no hash stops with `no-baseline`, one with no document that could be compared stops with `source-unreadable` (exit `3`), and a partial comparison adds a `doc_fetch_failed` or `doc_not_hashed` warning. The audit checks `uv` and its helpers before its first question and stops with `helper-missing` (exit `3`) when one is missing. The `degraded` input and the `severity_rules_path` setting are gone.
210
210
 
211
- **Provenance labels:** An export's labels, its confidence tier and its extraction method, follow the tool that read it, so a later read can change them, such as T1 to T1-low when ast-grep could not parse the file this time, or back. The drift report lists such changes in a **Provenance label differences (not drift)** table: they are not drift, are not counted in Total Drift Items and move no score.
211
+ **Provenance labels:** An export's labels, its confidence tier and its extraction method, follow the tool that read it, so a later read can change them, such as T1 to T1-low when ast-grep could not parse the file this time, or back. The drift report lists such changes in a **Provenance label differences (not drift)** table: they are not drift, are not counted in Total Drift Items and move no score. For a `public-api` skill, the new exports off the public surface Create Skill and Update Skill keep the provenance map to are listed apart, named in an **Off the public surface** note, and not counted in Total Drift Items; when the audit cannot read the surface (at Quick tier, for example), every new export is reported as added and the run warns `public_surface_not_applied`.
212
212
 
213
213
  **Doc drift:** A skill compiled before SKF 3.0.0 recorded its README as a `blob/main` page, or as a URL with no scheme, so doc drift keeps flagging its README until Create Skill compiles it again and records the raw README at the ref the skill was built from.
214
214
 
@@ -368,7 +368,7 @@ Default pass threshold: **80%**. Inside a pipeline the default follows the alias
368
368
 
369
369
  **Key Steps:** Select skill → Select version(s) + mode → Update manifest → Rebuild context files → Delete files (if purge)
370
370
 
371
- **Safety:** Active version guard: you cannot drop the currently active version while other non-deprecated versions exist. To drop it and keep another version, make the version you keep active first: point the skill's `active` link at it (`ln -sfn <version to keep> <skills_output_folder>/<name>/active`), run `@Ferris EX` on the skill, which exports that version and archives the old one, then drop the old one. To drop the whole skill instead, choose the All versions option. Soft drop is reversible by editing the manifest. Drop offers a skill folder SKF did not generate only when the manifest lists it, and then only for deprecate. A purge deletes only SKF output: a whole-skill purge of a folder that also holds other files is refused, and so is a purge through a link. The same goes for the skill's folder in `forge_data_folder`, except that a folder there SKF did not generate (another tool's folder of the same name, or a link) is left in place and named in the report. A headless drop of a skill with no manifest entry needs `--mode purge`. A headless drop of a skill in the export manifest needs `version=all` or the one version to drop: without it, the run stops with exit `2` (`input-missing`) before it changes anything. A specific version for a draft skill (one with no manifest entry) stops with exit `2` (`input-invalid`) in every mode, since a draft is dropped whole.
371
+ **Safety:** Active version guard: you cannot drop the currently active version while other non-deprecated versions exist. To drop it and keep another version, make the version you keep active first: point the skill's `active` link at it (`uv run _bmad/skf/shared/scripts/skf-atomic-write.py flip-link --link <skills_output_folder>/<name>/active --target <version to keep>`, which works in Git Bash, PowerShell and WSL2), run `@Ferris EX` on the skill, which exports that version and archives the old one, then drop the old one. To drop the whole skill instead, choose the All versions option. Soft drop is reversible by editing the manifest. Drop offers a skill folder SKF did not generate only when the manifest lists it, and then only for deprecate. A purge deletes only SKF output: a whole-skill purge of a folder that also holds other files is refused, and so is a purge through a link. The same goes for the skill's folder in `forge_data_folder`, except that a folder there SKF did not generate (another tool's folder of the same name, or a link) is left in place and named in the report. A headless drop of a skill with no manifest entry needs `--mode purge`. A headless drop of a skill in the export manifest needs `version=all` or the one version to drop: without it, the run stops with exit `2` (`input-missing`) before it changes anything. A specific version for a draft skill (one with no manifest entry) stops with exit `2` (`input-invalid`) in every mode, since a draft is dropped whole.
372
372
 
373
373
  **Preview first:** Add `--dry-run` to `@Ferris RS` or `@Ferris DS` to see which folders, manifest entries and context files would change. Nothing is written or deleted.
374
374
 
@@ -517,7 +517,7 @@ SKF_SETUP_RESULT_JSON: {"skf_setup":{"status":"success","tier":"Deep","previous_
517
517
 
518
518
  Parent skills and CI pipelines `grep` one line out of the workflow log to learn the outcome, with no ASCII-art parsing and 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`, or `blocked`) rather than composing the outcome from `require_tier_satisfied` + `error`. A write failure arrives as `blocked`, with an `error.phase` of `step 2:write-tools` or `step 2:forge-data-dir`. 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.
519
519
 
520
- Setup is not the only workflow whose result line is the run's final message: a headless Test Skill, Quick Skill (one target, not `--batch`), Brief Skill (also under `[auto]`), Update Skill, Export Skill (a `--dry-run` too) or campaign run hands its `SKF_TEST_RESULT_JSON`, `SKF_QUICK_SKILL_RESULT_JSON`, `SKF_BRIEF_RESULT_JSON`, `SKF_UPDATE_RESULT_JSON`, `SKF_EXPORT_RESULT_JSON` or `SKF_CAMPAIGN_RESULT_JSON` line to the shared health check, which displays it last, after everything else it shows (under `--no-health-check`, Test Skill displays it itself as its last line).
520
+ Setup is not the only workflow whose result line is the run's final message: a headless Test Skill, Quick Skill (one target, not `--batch`), Brief Skill (also under `[auto]`), Update Skill, Audit Skill, Drop Skill, Export Skill (a `--dry-run` too) or campaign run hands its `SKF_TEST_RESULT_JSON`, `SKF_QUICK_SKILL_RESULT_JSON`, `SKF_BRIEF_RESULT_JSON`, `SKF_UPDATE_RESULT_JSON`, `SKF_AUDIT_RESULT_JSON`, `SKF_DROP_SKILL_RESULT_JSON`, `SKF_EXPORT_RESULT_JSON` or `SKF_CAMPAIGN_RESULT_JSON` line to the shared health check, which displays it last, after everything else it shows (under `--no-health-check`, Test Skill displays it itself as its last line). A Drop Skill `--dry-run` stops at its confirmation gate and displays its line itself, last.
521
521
 
522
522
  **Exception: `/skf-quick-skill` headless emits structured progress and result envelopes.** Headless `/skf-quick-skill` runs are first-class building blocks for batch automators. Four operational contracts go beyond per-gate auto-proceed:
523
523
 
@@ -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": "3.0.0",
4
+ "version": "5.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",
@@ -137,7 +137,7 @@ Then append/replace the registry entry in forge-tier.yaml. Failures never block
137
137
 
138
138
  **Context:** Audit-skill's Deep-tier semantic diff retrieves what the skill documents about each export, then checks each claim against the current source.
139
139
 
140
- **Implementation:** Read `qmd_collections` from forge-tier.yaml. Find entry where `skill_name` matches AND `type` is `"extraction"`. If found, query via qmd_bridge. If not found, log and continue without T2 enrichment — not an error.
140
+ **Implementation:** Read `qmd_collections` from forge-tier.yaml. Find entry where `skill_name` matches AND `type` is `"extraction"`. If found, query via qmd_bridge. If not found, log it and read the skill's own files instead (the direct-content fallback, labeled T1-low-fallback): not an error.
141
141
 
142
142
  ### Example 3: Janitor Hygiene (setup)
143
143
 
@@ -266,7 +266,7 @@ Drops a specific version or the entire skill with two modes:
266
266
 
267
267
  **Hard drop (`--purge`):** Same as soft drop, plus deletes the version directory (`{skill_package}`) and forge data directory (`{forge_version}`, only when SKF generated it). Irreversible.
268
268
 
269
- **Active version guard:** Cannot drop the active version while other non-deprecated versions exist. The user must either make the version they keep active first (point the `active` link at it with `ln -sfn <version to keep> {skill_group}/active`, then run export-skill: the Manifest-lag guard makes it export that version, and its manifest update archives the old one), or drop all versions at once.
269
+ **Active version guard:** Cannot drop the active version while other non-deprecated versions exist. The user must either make the version they keep active first (point the `active` link at it with `uv run _bmad/skf/shared/scripts/skf-atomic-write.py flip-link --link {skill_group}/active --target <version to keep>`, then run export-skill: the Manifest-lag guard makes it export that version, and its manifest update archives the old one), or drop all versions at once.
270
270
 
271
271
  **Skill-level drop:** Removes the entire `{skill_group}` from the manifest. If purge, also deletes `{skill_group}` and `{forge_group}` directories (the forge one only when SKF generated it).
272
272
 
@@ -24,17 +24,8 @@ Reflect on the workflow that just completed. If real friction, bugs, or gaps wer
24
24
  - Read the complete step file before taking any action
25
25
  - Speak in `{communication_language}`
26
26
 
27
- ### Role Reinforcement:
28
- - You are a **self-improvement auditor** — honest, precise, evidence-based
29
- - You report ONLY what you actually experienced during THIS session
30
- - You are NOT a creative writer looking for things to say
31
-
32
- ### Anti-Hallucination Rules:
33
- - **DO NOT FABRICATE ISSUES.** If the workflow ran smoothly, say so and exit. Inventing issues to appear thorough is a SYSTEM FAILURE.
34
- - Only report issues you **ACTUALLY encountered** during THIS workflow execution
35
- - Every finding MUST cite the **specific step file path and section** where the issue occurred
36
- - If you are unsure whether something was a real issue or your own confusion, DO NOT report it
37
- - Reporting zero issues is the EXPECTED outcome for a well-designed workflow
27
+ ### Findings Rule:
28
+ Zero findings is the expected outcome of a good run. Report only a bug, friction or gap you hit in this session, each with the step file and section where it happened, and leave out anything you cannot tell apart from your own confusion: a finding you invent can become a real GitHub issue filed against the module.
38
29
 
39
30
  ---
40
31
 
@@ -264,7 +255,6 @@ After the issue is created, write the fingerprint → issue-url mapping to the s
264
255
  - **Respect the length budgets.** Finding, Expected, Actual, Impact are each ONE sentence. Evidence is 2-5 bullets, not prose. Suggested Fix is 1-3 sentences with ONE recommendation — multiple options go in the `Alternatives considered` collapsible or (better) not at all.
265
256
  - **Quote, don't paraphrase.** In Evidence, cite the exact `file:line` and put the quoted text in quotes. Link the convention to the instruction that caused it.
266
257
  - **Never narrate the session.** The reader wants the defect, not the story. If a sentence starts with "During my run..." or "I was trying to...", delete it.
267
- - **If unsure whether it's a real issue, do not submit it.** Reporting zero findings is a healthy outcome.
268
258
 
269
259
  **Issue body format:**
270
260
  ```markdown
@@ -389,41 +379,3 @@ Or open them manually at: <https://github.com/{healthCheckRepo}/issues/new/choos
389
379
  Workflow complete."
390
380
 
391
381
  When `{result_envelope_line}` is bound (§0), display it verbatim after that block, as the last line.
392
-
393
- ---
394
-
395
- ## CRITICAL STEP COMPLETION NOTE
396
-
397
- This is the TERMINAL step — shared across all SKF workflows. After the health check completes (clean run, or findings submitted, queued or discarded), the workflow is fully done. No further steps to load.
398
-
399
- ---
400
-
401
- ## SYSTEM SUCCESS/FAILURE METRICS
402
-
403
- ### SUCCESS:
404
-
405
- - Clean runs exit immediately with no findings (most common outcome)
406
- - Findings cite specific step files and sections with real evidence
407
- - User review gate presented before any submission
408
- - Severity gate respected: only `bug` submits live by default; `friction`/`gap` require explicit opt-in
409
- - Fingerprint computed deterministically and applied to both title prefix and `fp-*` label
410
- - `fp-*` label is ensured idempotently (`gh label create ... || true`) before `gh issue create`, so a first reporter of a defect never hard-fails
411
- - Remote dedup search performed before every live submission; existing issues get reactions/delta-comments rather than duplicates
412
- - Seen-cache at `{seenCachePath}` updated after every submission/reaction and consulted before every search
413
- - Local fallback files written with clear manual submission instructions (when `gh` unavailable)
414
- - Workflow ends cleanly
415
-
416
- ### SYSTEM FAILURE:
417
-
418
- - Fabricating issues that were not actually encountered during the session
419
- - Reporting vague issues without step file citations ("the workflow was confusing")
420
- - Skipping the user review gate (resolving it to its listed headless default **\[Q]** is not skipping it: nothing is submitted)
421
- - Creating issues without user confirmation
422
- - Creating a new issue when a matching `fp-*` open issue already exists (without explicit user \[N] override)
423
- - Submitting `friction` or `gap` findings live without the explicit severity-gate opt-in
424
- - Using LLM-judged "similarity" in place of the deterministic fingerprint
425
- - Not updating the seen-cache, causing the same user to re-report identical fingerprints
426
- - Not providing the local fallback when `gh` is unavailable
427
- - Continuing to load steps after this one (this is terminal)
428
-
429
- **Master Rule:** Honesty is the only policy. Zero findings is the expected, healthy outcome. Fabricating issues to appear thorough undermines the entire self-improvement system and constitutes SYSTEM FAILURE.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/armelhbobdad/bmad-module-skill-forge/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json",
4
4
  "title": "SKF_AUDIT_RESULT_JSON envelope (v1)",
5
- "description": "Single-line JSON document skf-audit-skill emits when {headless_mode} is true, prefixed with the literal 'SKF_AUDIT_RESULT_JSON: ': once on stdout when step 6 (report) finishes, before it chains to the health check, and once on stderr at every HARD HALT (status error). The shared emitter, skf-emit-result-envelope.py, builds every line from the payload a stage stages in the run folder, folds in the auto-decisions and warnings the run recorded there, derives exit_code from halt_reason, stamps run_id and result_path, writes the result files on success, and checks the line against this schema, so the model never types it. The skill's references/headless-contract.md holds the exit-code table and its stage files show the halt command.",
5
+ "description": "Single-line JSON document skf-audit-skill emits when {headless_mode} is true, prefixed with the literal 'SKF_AUDIT_RESULT_JSON: ': once on stdout when step 6 (report) finishes, which the health check displays as the run's last line, and once on stderr at every HARD HALT (status error). The shared emitter, skf-emit-result-envelope.py, builds every line from the payload a stage stages in the run folder, folds in the auto-decisions and warnings the run recorded there, derives exit_code from halt_reason, stamps run_id and result_path, writes the result files on success, and checks the line against this schema, so the model never types it. The skill's references/headless-contract.md holds the exit-code table and its stage files show the halt command.",
6
6
  "$defs": {
7
7
  "skf-envelope": {
8
8
  "description": "The shared emitter's settings for this workflow, read by skf-emit-result-envelope.py: the workflow, the line's prefix, the wrapper, the status a halt carries, the exit code of each halt_reason and the result file stem. Nothing in the schema references this entry, so it constrains no envelope.",
@@ -164,7 +164,7 @@
164
164
  "warnings": {
165
165
  "type": "array",
166
166
  "items": { "type": "string", "minLength": 1 },
167
- "description": "Optional. Non-fatal anomalies the run recorded in its sink or the emitter raised, one string each, starting with its code: upstream_tree_unavailable when [C] could not read the upstream ref into a private tree and the audit stayed on the baseline, source_tree_not_removed when the private tree could not be removed, doc_fetch_failed and doc_not_hashed (each with its count) when a docs-only skill's score leaves out documents that could not be fetched or have no recorded hash, customization_resolver_unavailable when the customization resolver failed and only the bundled customize.toml applied, and result_file_write_failed when a result file could not be written. Present only when there is at least one."
167
+ "description": "Optional. Non-fatal anomalies the run recorded in its sink or the emitter raised, one string each, starting with its code: upstream_tree_unavailable when [C] could not read the upstream ref into a private tree and the audit stayed on the baseline, source_tree_not_removed when the private tree could not be removed, recipe_runner_failed (with the recipe runner's error line) when the runner left no JSON and named an error on stderr, so re-index extracted without it, public_surface_not_applied (with the rule's problem) when re-index could read no public surface for a public-api skill (Quick tier, a runner that could not run, an incomplete run, one with errors, or an entry point it could not trace), so structural diff reported every export the provenance map does not hold as added, doc_fetch_failed and doc_not_hashed (each with its count) when a docs-only skill's score leaves out documents that could not be fetched or have no recorded hash, new_files_not_checked (with the reason) when the Script/Asset Drift check looked for no new script, asset or document because the skill has no brief it could read or a helper it needs could not be loaded, customization_resolver_unavailable when the customization resolver failed and only the bundled customize.toml applied, and result_file_write_failed when a result file could not be written. Present only when there is at least one."
168
168
  },
169
169
  "error": {
170
170
  "type": "object",
@@ -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). error.reason names the run that holds a 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",
@@ -128,7 +136,7 @@
128
136
  "warnings": {
129
137
  "type": "array",
130
138
  "items": { "type": "string" },
131
- "description": "Non-fatal anomalies surfaced during the run. May include workspace_drift_overridden, scope_reconciliation_post entries, qmd indexing failures, source-tree:, source-not-fetched, source-version-lower, file-diff-unavailable, no-baseline-time, moved-check-skipped and unknown-language (detect-changes.md §2.1 Category A), workspace-clone-not-updated, target-ref-not-recorded, source-tree-not-removed, run-lock-replaced (init.md §1b took over a stale run lock) and run-lock-not-released entries, unconsumed-test-report (init.md §4b: a failing test report newer than the skill that this normal run did not apply), interrupted-run-cleaned and interrupted-run-not-cleaned (init.md §1b undid what an update whose stale lock it replaced had written), proposed-amendment: entries (a --detect-only or --dry-run run's skill brief decisions, not written), doc-fetch-failed, doc-not-hashed and doc-drift-not-checked (a docs-only skill's documents), rollback-incomplete (a halt could not undo all of this run's writes), run-state-not-finished, customization_resolver_unavailable, test-report: entries (what the test report helpers could not read, and test-report: not routed: for each gap a gap-driven run does not repair), etc. Empty when none."
139
+ "description": "Non-fatal anomalies surfaced during the run. May include workspace_drift_overridden, scope_reconciliation_post entries, qmd indexing failures, source-tree:, source-not-fetched, source-version-lower, file-diff-unavailable, no-baseline-time, moved-check-skipped and unknown-language (detect-changes.md §2.1 Category A), new-files-not-checked (Category D: a skill with no brief, so no new script or asset was looked for), workspace-clone-not-updated, target-ref-not-recorded, source-tree-not-removed, run-lock-replaced (init.md §1b took over a stale run lock) and run-lock-not-released entries, unconsumed-test-report (init.md §4b: a failing test report newer than the skill that this normal run did not apply), interrupted-run-cleaned and interrupted-run-not-cleaned (init.md §1b undid what an update whose stale lock it replaced had written), proposed-amendment: entries (a --detect-only or --dry-run run's skill brief decisions, not written), doc-fetch-failed, doc-not-hashed and doc-drift-not-checked (a docs-only skill's documents), rollback-incomplete (a halt could not undo all of this run's writes), run-state-not-finished, public-api-counted-by-hand: entries (write.md §2: the recipe runner exited 2, so the public names were found without it; each quotes the runner's error line), customization_resolver_unavailable, test-report: entries (what the test report helpers could not read, and test-report: not routed: for each gap a gap-driven run does not repair), etc. Empty when none."
132
140
  },
133
141
  "error": {
134
142
  "oneOf": [
@@ -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
  },