bmad-module-skill-forge 6.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +1 -1
- package/docs/_data/pinned.yaml +9 -10
- package/docs/campaign.md +2 -2
- package/docs/skill-model.md +3 -3
- package/docs/verifying-a-skill.md +17 -18
- package/docs/workflows.md +10 -10
- package/package.json +1 -1
- package/src/shared/references/description-guard-protocol.md +12 -10
- package/src/shared/references/output-contract-schema.md +1 -1
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +38 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +5 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +8 -3
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +6 -1
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +7 -2
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +1 -1
- package/src/shared/scripts/skf-parse-gaps.py +4 -1
- package/src/shared/scripts/skf-quick-batch.py +24 -7
- package/src/shared/scripts/skf-verify-provenance-completeness.py +809 -85
- package/src/skf-analyze-source/customize.toml +3 -2
- package/src/skf-analyze-source/references/auto-docs-only.md +2 -2
- package/src/skf-analyze-source/references/generate-briefs.md +5 -3
- package/src/skf-analyze-source/references/headless-contract.md +2 -2
- package/src/skf-analyze-source/references/step-auto-scope.md +2 -2
- package/src/skf-audit-skill/customize.toml +4 -3
- package/src/skf-audit-skill/references/report.md +1 -1
- package/src/skf-campaign/assets/campaign-state-schema.json +58 -0
- package/src/skf-campaign/references/campaign-contracts.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +2 -2
- package/src/skf-campaign/references/step-07-capstone.md +3 -3
- package/src/skf-campaign/references/step-08-verify.md +3 -3
- package/src/skf-campaign/references/step-09-refine.md +3 -3
- package/src/skf-campaign/references/step-11-maintenance.md +1 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +43 -10
- package/src/skf-campaign/scripts/campaign-report.py +60 -4
- package/src/skf-campaign/scripts/campaign-state.py +82 -17
- package/src/skf-campaign/templates/campaign-report-template.md +4 -0
- package/src/skf-create-skill/SKILL.md +2 -2
- package/src/skf-create-skill/assets/skill-sections.md +2 -2
- package/src/skf-create-skill/customize.toml +4 -4
- package/src/skf-create-skill/references/batch-mode.md +3 -1
- package/src/skf-create-skill/references/report.md +9 -4
- package/src/skf-create-skill/references/validate.md +49 -29
- package/src/skf-create-stack-skill/references/invocation-contract.md +1 -1
- package/src/skf-create-stack-skill/references/report.md +3 -3
- package/src/skf-create-stack-skill/references/validate.md +21 -8
- package/src/skf-drop-skill/references/invocation-contract.md +1 -1
- package/src/skf-export-skill/references/summary.md +1 -1
- package/src/skf-quick-skill/SKILL.md +1 -1
- package/src/skf-quick-skill/references/batch-mode.md +30 -2
- package/src/skf-quick-skill/references/compile.md +2 -0
- package/src/skf-quick-skill/references/finalize.md +3 -2
- package/src/skf-quick-skill/references/halt-contract.md +3 -3
- package/src/skf-quick-skill/references/quick-extract.md +2 -1
- package/src/skf-quick-skill/references/write-and-validate.md +27 -11
- package/src/skf-refine-architecture/customize.toml +1 -1
- package/src/skf-refine-architecture/references/report.md +2 -2
- package/src/skf-rename-skill/references/report.md +1 -1
- package/src/skf-test-skill/references/coverage-check-tiers.md +3 -3
- package/src/skf-test-skill/references/coverage-check.md +4 -4
- package/src/skf-test-skill/references/inventory-contract.md +1 -1
- package/src/skf-test-skill/references/invocation-contract.md +1 -1
- package/src/skf-test-skill/references/report.md +1 -1
- package/src/skf-test-skill/references/scoring-rules.md +2 -2
- package/src/skf-test-skill/references/signature-comparison-rules.md +7 -2
- package/src/skf-test-skill/scripts/gap-ledger.py +196 -46
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +253 -17
- package/src/skf-test-skill/scripts/score-signatures.py +9 -13
- package/src/skf-test-skill/scripts/validate-inventory.py +91 -12
- package/src/skf-update-skill/customize.toml +1 -1
- package/src/skf-update-skill/references/gap-driven.md +1 -1
- package/src/skf-update-skill/references/invocation-contract.md +1 -1
- package/src/skf-update-skill/references/merge.md +1 -1
- package/src/skf-update-skill/references/report.md +27 -9
- package/src/skf-update-skill/references/write.md +24 -12
- package/src/skf-verify-stack/references/report.md +1 -1
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ For docs-only skills, the audit shape is the same: `provenance-map.json` still l
|
|
|
130
130
|
|
|
131
131
|
If it doesn't, that's a bug. Open an issue and SKF will republish the skill with a fresh provenance map (a new commit SHA for source skills, a fresh fetch for docs-only). Falsifiability isn't a feature; it's the whole deal.
|
|
132
132
|
|
|
133
|
-
**Reference output: [oh-my-skills](https://github.com/armelhbobdad/oh-my-skills).** It holds four Deep-tier skills compiled by SKF (cocoindex, cognee, Storybook v10, uitripled), each shipping its full audit trail in-repo.
|
|
133
|
+
**Reference output: [oh-my-skills](https://github.com/armelhbobdad/oh-my-skills).** It holds four Deep-tier skills compiled by SKF (cocoindex, cognee, Storybook v10, uitripled), each shipping its full audit trail in-repo. On a strict Skill Forge 4.0.0 test of the version each one ships, two pass (cocoindex at 100%, cognee at 99%) and two are blocked (uitripled, Storybook); every report is published, failing ones included. Every claim walks to an upstream line in under 60 seconds. Serves as both the worked example for this section and a public record of what the pipeline gets right and where its test still falls short.
|
|
134
134
|
|
|
135
135
|
## Help SKF Improve
|
|
136
136
|
|
package/docs/_data/pinned.yaml
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
# Enforced two ways: (1) release.yaml bumps this line in the same commit
|
|
33
33
|
# as package.json + marketplace.json on every release; (2) validate-docs-drift.js
|
|
34
34
|
# cross-checks this value against package.json.version and fails on mismatch.
|
|
35
|
-
skf_version: "
|
|
35
|
+
skf_version: "8.0.0"
|
|
36
36
|
|
|
37
37
|
# Path to the oh-my-skills repo, resolved relative to this repo's root.
|
|
38
38
|
# Override at runtime with the OMS environment variable if oh-my-skills
|
|
@@ -51,8 +51,9 @@ skills:
|
|
|
51
51
|
source_commit: "ba3631f2ed363a6ea50d649c34c56885af6b36fe"
|
|
52
52
|
source_ref: "v1.6.2"
|
|
53
53
|
upstream_commit_date: "2026-09-29"
|
|
54
|
-
# strict SKF
|
|
55
|
-
|
|
54
|
+
# null: the strict SKF 7.0.0 re-test of the version that ships
|
|
55
|
+
# (20261106T224257Z-45558de5) is blocked by the hard gate, so it records no score.
|
|
56
|
+
test_score: null
|
|
56
57
|
test_mode: "naive"
|
|
57
58
|
confidence_tier: "Deep"
|
|
58
59
|
source_authority: "community"
|
|
@@ -65,7 +66,7 @@ skills:
|
|
|
65
66
|
source_commit: "5fe4f37a526e51516655832ec9814e7ca24ade9a"
|
|
66
67
|
source_ref: "v1.0.0"
|
|
67
68
|
upstream_commit_date: "2026-04-22"
|
|
68
|
-
# strict SKF
|
|
69
|
+
# strict SKF 7.0.0 re-test of the version that ships (20261106T225304Z-494ddfa2)
|
|
69
70
|
test_score: 100.0
|
|
70
71
|
test_mode: "naive"
|
|
71
72
|
confidence_tier: "Deep"
|
|
@@ -79,9 +80,8 @@ skills:
|
|
|
79
80
|
source_commit: "e486d3826bcd40c52db1c766966d1c8ec16df6cb"
|
|
80
81
|
source_ref: "v10.3.5"
|
|
81
82
|
upstream_commit_date: "2026-04-07"
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
test_score: null
|
|
83
|
+
# strict SKF 7.0.0 re-test of the version that ships (20261106T235200Z-cc17d5d0)
|
|
84
|
+
test_score: 98.23
|
|
85
85
|
test_mode: "naive"
|
|
86
86
|
confidence_tier: "Deep"
|
|
87
87
|
source_authority: "community"
|
|
@@ -94,9 +94,8 @@ skills:
|
|
|
94
94
|
source_commit: "a5ffb45be05335d2c547436664cfbfb8c22d04df"
|
|
95
95
|
source_ref: "HEAD"
|
|
96
96
|
upstream_commit_date: "2026-03-22"
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
test_score: null
|
|
97
|
+
# strict SKF 7.0.0 re-test of the version that ships (20261106T225241Z-1907bdd0)
|
|
98
|
+
test_score: 100.0
|
|
100
99
|
test_mode: "naive"
|
|
101
100
|
confidence_tier: "Deep"
|
|
102
101
|
source_authority: "community"
|
package/docs/campaign.md
CHANGED
|
@@ -184,9 +184,9 @@ A finished campaign leaves:
|
|
|
184
184
|
- **Individual skills**: one for each library that completed. The Export stage exports the ones that clear the quality gate; the others stay on disk and in the report.
|
|
185
185
|
- **One capstone stack skill**: composed from the completed skills that clear the quality gate. The Export stage exports only the individual skills, so run `skf-export-skill` on the stack skill yourself when you want it packaged.
|
|
186
186
|
- **Verification report and refined architecture**: from Verification and Refinement, when an architecture document was found.
|
|
187
|
-
- **Campaign report** (`campaign-report.md`): the quality gate; a table of every skill with its tier, status, score, pin and workarounds; an Export Gate section with each completed skill's verdict, whether Export exported it or why its export failed, and the skills the gate kept from export; the lowest, highest and average scores; time per skill; and a section on failed or skipped skills.
|
|
187
|
+
- **Campaign report** (`campaign-report.md`): the quality gate; a table of every skill with its tier, status, score, pin and workarounds; a Stages section with whether the capstone, verification and refinement stages completed, were skipped or failed, and why a stage failed; an Export Gate section with each completed skill's verdict, whether Export exported it or why its export failed, and the skills the gate kept from export; the lowest, highest and average scores; time per skill; and a section on failed or skipped skills.
|
|
188
188
|
- **Decision log** (`_campaign-decision-log.md`): every decision made during the campaign.
|
|
189
|
-
- **Headless result line** (`SKF_CAMPAIGN_RESULT_JSON`): printed in headless mode for scripts to read, as the run's last line. Besides the counts and the score of each skill, it carries `export_verdicts` (each completed skill's verdict at Export), `skills_excluded` (the completed skills the gate kept from export), `skills_exported` (the skills Export exported) and `export_failures` (each skill whose export failed, with its `halt_reason` and `exit_code`). The last two are null when the Export stage did not run. An `export_failures` entry's `exit_code` is null when the export printed no line (its `halt_reason` is then `no-result`) or one Campaign could not read (`result-unreadable`). A failed export does not fail the campaign, which still ends with `status` `success`, so a CI job that needs every skill exported checks `export_failures`.
|
|
189
|
+
- **Headless result line** (`SKF_CAMPAIGN_RESULT_JSON`): printed in headless mode for scripts to read, as the run's last line. Besides the counts and the score of each skill, it carries `export_verdicts` (each completed skill's verdict at Export), `skills_excluded` (the completed skills the gate kept from export), `skills_exported` (the skills Export exported) and `export_failures` (each skill whose export failed, with its `halt_reason` and `exit_code`). The last two are null when the Export stage did not run. An `export_failures` entry's `exit_code` is null when the export printed no line (its `halt_reason` is then `no-result`) or one Campaign could not read (`result-unreadable`). A failed export does not fail the campaign, which still ends with `status` `success`, so a CI job that needs every skill exported checks `export_failures`. It also carries `stage_outcomes`: for the capstone, verification and refinement stages, `completed`, `skipped` (a stage skipped on purpose, such as verification without an architecture document) or `failed`, with the failure's `halt_reason` (`no-result` when the sub-skill printed no line, `result-unreadable` when Campaign could not read it, otherwise the sub-skill's own) and `exit_code`. It is null when the capstone stage did not run, and a failed stage does not fail the campaign either, so a pipeline can tell a failed verification from a skipped one.
|
|
190
190
|
|
|
191
191
|
The Export stage (stage 9) asks before it writes anything. It lists each completed skill with its verdict, names the skills it leaves out because they fall below the soft fallback or have no score, and waits for `[E]xport all` or `[C]ancel`. `[E]xport all` exports only the `pass` and `fallback` skills, each under the skill folder its build wrote: a Tier B skill under the name Quick Skill gave it, the library's name, even when you named the target differently. In headless mode it exports them without asking.
|
|
192
192
|
|
package/docs/skill-model.md
CHANGED
|
@@ -232,8 +232,8 @@ This is a trimmed excerpt from the real [`oms-cognee/1.6.2/metadata.json`](https
|
|
|
232
232
|
"language": "python",
|
|
233
233
|
"ast_node_count": 42,
|
|
234
234
|
"confidence_distribution": {
|
|
235
|
-
"t1":
|
|
236
|
-
"t1_low":
|
|
235
|
+
"t1": 36,
|
|
236
|
+
"t1_low": 50,
|
|
237
237
|
"t2": 0,
|
|
238
238
|
"t3": 0
|
|
239
239
|
},
|
|
@@ -254,7 +254,7 @@ Fields omitted from this excerpt for brevity: `source_root`, `description`, `exp
|
|
|
254
254
|
|
|
255
255
|
Two fields are easy to mix up. `confidence_tier` holds the forge tier a single skill like this one was compiled at (Quick, Forge, Forge+ or Deep). A stack skill records there the dominant confidence tier of its libraries (T1, T1-low, T2 or T3) and keeps its forge tier in `forge_tier`; Export Skill still accepts a forge tier from a stack made by an earlier SKF version, with a warning that running Stack Skill again records the dominant tier. `confidence_distribution` counts each claim by the tier of its signature (`signature_source`: T1, T1-low, T2 or T3), which can be lower than the entry's `confidence` label. In a stack it counts each library once, by the library's tier, so its bins sum to `library_count`.
|
|
256
256
|
|
|
257
|
-
Its counts follow the tool that read each export's signature:
|
|
257
|
+
Its counts follow the tool that read each export's signature: 36 exports whose signature an ast-grep rule matched count as `t1`, and 50 count as `t1_low`. Of those 50, 45 were read by eye (`source-read`), such as aliases, module namespaces, the names gap-driven repairs added and `run_migrations`, whose file the brief's `scope.exclude` leaves out of the extraction. The other 5 were located by an ast-grep rule, but their signatures were read by eye, such as the methods of the `config` class. `ast_node_count` (42) counts every export an ast-grep rule matched when the skill was compiled, `run_migrations` included. The skill's 1.0.0 metadata, written by an SKF version that labeled exports by the forge tier, counted all 34 of its exports as `t1`. `exports_documented` (86) is below `exports_public_api` (102): the brief leaves some top-level names out of scope, and the skill's test report lists them and logs the remaining difference as a Medium `metadata-drift` gap.
|
|
258
258
|
|
|
259
259
|
`scripts` and `assets` arrays are optional: SKF leaves them out entirely, rather than writing empty arrays, when the source has no scripts or assets.
|
|
260
260
|
|
|
@@ -44,7 +44,7 @@ Provenance maps live in `forge-data/{skill}/{version}/provenance-map.json` in th
|
|
|
44
44
|
|
|
45
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 1.6.2 map passes the [line, label and citation checks](#workflow-time-enforcement) that SKF 3.0.0 added. SKF 4.0.0
|
|
47
|
+
The 1.6.2 map passes the [line, label and citation checks](#workflow-time-enforcement) that SKF 3.0.0 added. Under SKF 4.0.0 a strict test passed the skill at 99%. SKF 7.0.0's Test Skill found real errors in it, all since fixed by hand against the pinned source, and still blocks it on two false positives: it compares the skill's forms of the lightweight `cognee.pipelines.run_pipeline` with the dataset orchestrator of the same name ([#918](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/918)). So the skill has no score (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
|
|
|
@@ -85,27 +85,24 @@ 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.
|
|
88
|
+
Take oh-my-skills' four reference skills as an example. Under SKF 4.0.0, strict tests that applied the signature rule as then written passed oms-cocoindex at 100% and oms-cognee at 99%, and blocked the other two. Earlier passes are kept in each skill's history; the oms-cocoindex, oms-cognee and oms-storybook-react-vite ones read the rule leniently.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
SKF 5.0.0 to 7.0.0 put every signature rule in one file that every judge reads, compare each form a skill documents, and pin the names and signatures it writes in a fixed form. On the same skills, the first 7.0.0 tests passed oms-cocoindex and oms-uitripled at 100% (uitripled's `cn` false positive was gone) and blocked oms-cognee, on 8 Critical and 1 High gaps, and oms-storybook-react-vite, on 1 Critical gap with Export Coverage at 63.78% of the names its brief's `scope.tier_a_include` lists. Two rounds of fixes per skill (three for oms-storybook-react-vite), each followed by a strict re-test, then found real errors in all four skills, the two that passed included:
|
|
91
|
+
- `@coco.fn(batching=True)` documented as a working decorator, although at the pinned commit it raises `ValueError` for any function.
|
|
92
|
+
- Defaults written on props that a component's props type requires.
|
|
93
|
+
- Prose calls that leave out a required argument.
|
|
94
|
+
- 134 tier A names the storybook skill did not document.
|
|
91
95
|
|
|
92
|
-
|
|
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
|
+
Every fix was made by hand against the pinned source. Each round's fixes were reviewed before its test, except the last one-row storybook correction.
|
|
96
97
|
|
|
97
|
-
|
|
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:
|
|
98
|
+
Today, on a strict SKF 7.0.0 test of the version each one ships, three skills pass and one is blocked. The oms-storybook-react-vite and oms-uitripled passes turn on calls the rules leave open: the storybook report writes its calls down, while the uitripled run passed three button components as matches without recording how it read them ([#932](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/932)). The judges also still read some cases differently from run to run: three oms-uitripled lines were matches in one run and Critical gaps in the next, with no change to the lines. Every report, the failing ones included, is kept: in the skill's `forge-data/` folder, or, for the reverted oms-storybook-react-vite repair, in oh-my-skills' git history. Even a passing report names the drift it found:
|
|
102
99
|
|
|
103
100
|
| Skill | Score | What the report discloses |
|
|
104
101
|
|---|---|---|
|
|
105
|
-
| [oms-cocoindex](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cocoindex/1.0.0/test-report-oms-cocoindex-
|
|
106
|
-
| [oms-cognee](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cognee/1.6.2/test-report-oms-cognee-
|
|
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-
|
|
108
|
-
| [oms-uitripled](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-uitripled/0.1.0/test-report-oms-uitripled-
|
|
102
|
+
| [oms-cocoindex](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cocoindex/1.0.0/test-report-oms-cocoindex-20261106T225304Z-494ddfa2.md) | **100%** | 72/72 exports of the root barrel (7.0.0 applies the brief's `scope.exclude` before it decides the source is one Python package, not a Cargo workspace), 46/46 signatures. Type Coverage is 1/1: the root exports Test Skill restores when the brief's doc globs match no file carry no kind, so the category counts a single type ([#913](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/913)). Left open: 11 Medium stale-documentation gaps on connector exports that the reconcile counts outside the surface ([#912](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/912)), a brief glob that matches no file and a barrel count. |
|
|
103
|
+
| [oms-cognee](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-cognee/1.6.2/test-report-oms-cognee-20261106T224257Z-45558de5.md) | **blocked** | 2 Critical false positives: the test compares the skill's two forms of the lightweight `cognee.pipelines.run_pipeline` with the orchestrator of the same name, the only `run_pipeline` its surface holds ([#918](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/918)). Every other check passes: 92/92 exports of the `cognee/__init__.py` barrel at pinned commit `ba3631f2` (v1.6.2), 70 of 71 signature names and 35/35 types. External Validation is 90%: skill-check finds the `SKILL.md` body over its 5,000-token budget. |
|
|
104
|
+
| [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-20261106T235200Z-cc17d5d0.md) | **98.23%** | Counted on the brief's `scope.tier_a_include` set. Export Coverage reads 100% against the stored count of 370; this run read 389 names into the set, 376 of them documented. Type Coverage is 134/147: 13 `@vitest/spy` types that `storybook/test` re-exports with `export type *` are undocumented, and the run before it read that line as six names and scored 100% ([#926](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/926)). 177/177 signatures. The pass also turns on how the split-body check reads two different exports that share a name ([#927](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/927)). |
|
|
105
|
+
| [oms-uitripled](https://github.com/armelhbobdad/oh-my-skills/blob/main/forge-data/oms-uitripled/0.1.0/test-report-oms-uitripled-20261106T225241Z-1907bdd0.md) | **100%** | 327/327 exports against the stored count for the brief's `scope.include` union, 269/269 signatures and 42/42 types. The pass also turns on three forms the report passes without comment: three button components documented with their source declarations, `...props` rest element included, which the run judged as matches. Read strictly, each would be a Critical gap ([#932](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/932)). |
|
|
109
106
|
|
|
110
107
|
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.
|
|
111
108
|
|
|
@@ -119,7 +116,9 @@ After the tiered brief, two re-tests were blocked and repaired by Update Skill b
|
|
|
119
116
|
|
|
120
117
|
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
118
|
|
|
122
|
-
A strict test of the version that
|
|
119
|
+
A strict SKF 4.0.0 test of the version that shipped then ([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)) covered all of those fixes. It was blocked on 4 Critical gaps: two compared the React portable-stories wrappers with core's functions, and two were older real errors no earlier run had flagged. So oh-my-skills published no score instead of quoting the earlier number.
|
|
120
|
+
|
|
121
|
+
SKF 7.0.0 measures the skill against the names its brief lists. Its first test found 63.78% of them documented. The next rounds documented the 134 missing names and fixed the errors the tests and reviews found, by hand against the pinned source. A strict test of the version that ships now passes at 98.23% ([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-20261106T235200Z-cc17d5d0.md)). Nothing was hidden: every report on the way is kept.
|
|
123
122
|
|
|
124
123
|
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.
|
|
125
124
|
|
|
@@ -232,7 +231,7 @@ Each step rates every gap it records against the Gap Severity table in Test Skil
|
|
|
232
231
|
|
|
233
232
|
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
233
|
|
|
235
|
-
A declaration the run finds only in an `example`, `demo`, `demos`, `sample`, `samples`, `scripts`, `playground`, `bench`, `benchmark` or `
|
|
234
|
+
A declaration the run finds only in an `example`, `examples`, `demo`, `demos`, `sample`, `samples`, `scripts`, `playground`, `bench`, `benchmark`, `benchmarks`, `migrations` or `docs` 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 =`).
|
|
236
235
|
|
|
237
236
|
Discovery testing runs after the hard gate, so a discovery gap (Info, Medium or High) is counted in the Gap Report and blocks nothing.
|
|
238
237
|
|
package/docs/workflows.md
CHANGED
|
@@ -73,7 +73,7 @@ Each workflow is also a skill you can run directly, without Ferris: `/skf-setup`
|
|
|
73
73
|
|
|
74
74
|
**Which brief:** Name the brief by its path or by the skill's name. With no brief path, skill name or `--batch`, CS loads the only brief in `forge_data_folder` and names it in its opening banner. When the folder holds several, an interactive run asks which one to compile, and a headless run stops with exit `2` (`brief-missing`), naming them.
|
|
75
75
|
|
|
76
|
-
**Batch mode:** The paths after `--batch` name `skill-brief.yaml` files or folders of briefs; with no path, CS takes the briefs in `forge_data_folder`. Every brief is validated before the first one compiles, and the plan lists the briefs to compile and each refused brief: one that fails the brief schema, with the validator's first message, or one whose `name` an earlier brief of the batch already has. A HARD HALT ends only its own brief. The batch writes `create-skill-batch-<timestamp>.json` and `create-skill-batch-latest.json` under `<skills_output_folder>/_batch/` and exits with the highest exit code among its briefs, `0` when every brief finished. Run again with the same briefs, a batch that an ended session left goes on where it stopped.
|
|
76
|
+
**Batch mode:** The paths after `--batch` name `skill-brief.yaml` files or folders of briefs; with no path, CS takes the briefs in `forge_data_folder`. Every brief is validated before the first one compiles, and the plan lists the briefs to compile and each refused brief: one that fails the brief schema, with the validator's first message, or one whose `name` an earlier brief of the batch already has. A HARD HALT ends only its own brief. The batch writes `create-skill-batch-<timestamp>.json` and `create-skill-batch-latest.json` under `<skills_output_folder>/_batch/` and exits with the highest exit code among its briefs, `0` when every brief finished. Its result is the file its `batch_summary` event's `summary_path` names: the `-latest.json` copy holds the last batch summary written, which may be another batch's. Run again with the same briefs, a batch that an ended session left goes on where it stopped.
|
|
77
77
|
|
|
78
78
|
**Safety:** Writes a version only into a skill folder SKF generated, or a new one. When `skills_output_folder` already holds a folder with the skill's name that SKF did not generate, or a version folder SKF did not generate, it stops before writing anything (`not-skf-output`); an SKF skill still in the old flat layout stops with `flat-layout` until `@Ferris TS` moves it. Set a different `name` in the brief to create the skill beside a folder SKF did not generate.
|
|
79
79
|
|
|
@@ -99,7 +99,7 @@ Each workflow is also a skill you can run directly, without Ferris: `/skf-setup`
|
|
|
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
|
|
|
102
|
-
**Headless:** A headless run needs the skill's name or folder path as its argument: without one it stops `blocked` before it reads anything (`error.phase` `init:skill-name`, with an `error.reason` that starts `input-missing`). A skill with no `provenance-map.json`, such as one made by Quick Skill, needs a full rebuild. A headless run stops with `blocked` there unless you pass `--allow-degraded`. `--from-test-report` needs the map, interactive or headless: without one it stops `blocked` before any gate, so run a normal update first, which offers the rebuild and writes the map, then re-run Test Skill. Files outside the skill's scope that a headless run finds, such as a new AI documentation file, are left for a person: the run records each one as `deferred-headless` in the skill brief, and the next interactive run asks about it. A normal or gap-driven run that finds no change still writes its result files and runs `on_complete`, with the status `no-changes`. In the `SKF_UPDATE_RESULT_JSON` line, `skf_update.status` values `success`, `no-changes`, `detect-only` and `dry-run` all mean the run went fine.
|
|
102
|
+
**Headless:** A headless run needs the skill's name or folder path as its argument: without one it stops `blocked` before it reads anything (`error.phase` `init:skill-name`, with an `error.reason` that starts `input-missing`). A skill with no `provenance-map.json`, such as one made by Quick Skill, needs a full rebuild. A headless run stops with `blocked` there unless you pass `--allow-degraded`. `--from-test-report` needs the map, interactive or headless: without one it stops `blocked` before any gate, so run a normal update first, which offers the rebuild and writes the map, then re-run Test Skill. Files outside the skill's scope that a headless run finds, such as a new AI documentation file, are left for a person: the run records each one as `deferred-headless` in the skill brief, and the next interactive run asks about it. A normal or gap-driven run that finds no change still writes its result files and runs `on_complete`, with the status `no-changes`. In the `SKF_UPDATE_RESULT_JSON` line, `skf_update.status` values `success`, `no-changes`, `detect-only` and `dry-run` all mean the run went fine. A headless run whose emitter refused its result line for good ends on a `halted-for-write-failure` line instead, with `error.phase` `report:result-contract` and an `error.reason` that starts `result-contract-not-written`, and nothing it wrote is undone.
|
|
103
103
|
|
|
104
104
|
**Agent:** Ferris (Surgeon mode)
|
|
105
105
|
|
|
@@ -547,9 +547,9 @@ Setup is not the only workflow whose result line is the run's final message: a h
|
|
|
547
547
|
|
|
548
548
|
3. **Error-variant result contract on every HARD HALT.** A `SKF_QUICK_SKILL_RESULT_JSON: {…}` envelope is emitted on `stderr` (always) and copied to `{skill_package}/quick-skill-result-latest.json` once the skill package holds `metadata.json` (any HALT from the write step onward, except the exit `9` ownership halt, which writes nothing on disk). The full population rules live in [`src/skf-quick-skill/references/halt-contract.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-quick-skill/references/halt-contract.md) § "Result Contract on HARD HALT", and the envelope's schema is [`src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json).
|
|
549
549
|
|
|
550
|
-
4. **Success result contract.** A finished run prints one `SKF_QUICK_SKILL_RESULT_JSON: {…}` line on `stdout`. Its `summary` carries `quality_score` (the skill-check score of the written package, not a Test Skill score), `validation_issues`, `repo_shape`, `language_resolution` and `active_pointer`, beside the top-level `headless_decisions` and `warnings`, such as `also_found_in` when a headless run kept the first registry's pick of a package name another registry also holds. The run also writes `quick-skill-result-latest.json` in the skill package.
|
|
550
|
+
4. **Success result contract.** A finished run prints one `SKF_QUICK_SKILL_RESULT_JSON: {…}` line on `stdout`. Its `summary` carries `quality_score` (the skill-check score of the written package, not a Test Skill score), `validation_issues`, `security_result` (the security scan's outcome: `pass` when the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing, `warn` when it reported findings, `skipped` when it did not run, such as without `SNYK_TOKEN`), `repo_shape`, `language_resolution` and `active_pointer`, beside the top-level `headless_decisions` and `warnings`, such as `also_found_in` when a headless run kept the first registry's pick of a package name another registry also holds. The run also writes `quick-skill-result-latest.json` in the skill package.
|
|
551
551
|
|
|
552
|
-
**Batch mode (`--batch <file>`).** Drives N targets in sequence: each target runs steps 1 to 6, and the health check runs once, after the batch summary. Input format: one target per line, `#` comments and blank lines ignored, optional per-line modifiers `language=<lang>` and `scope=<path>`. Each target's output lands in its own `{skill_package}/`; an aggregated summary writes to `{skills_output_folder}/_batch/quick-skill-batch-{ts}.json` (with `quick-skill-batch-latest.json` copy). Per-target boundary events (`{"batch":N,"target":"…","status":"start|done|fail",…}`) and a final `{"batch_summary":true,…}` event extend the per-step event stream above. The batch exits with the highest exit code among its failed targets (`0` when none failed); `--fail-fast` stops it at the first failed target and exits with that target's code. A batch refused before its first target writes no summary and exits `2`, or `4` when its run folder cannot be written. Full input grammar, summary schema, and exit-code rules in [`src/skf-quick-skill/references/batch-mode.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-quick-skill/references/batch-mode.md).
|
|
552
|
+
**Batch mode (`--batch <file>`).** Drives N targets in sequence: each target runs steps 1 to 6, and the health check runs once, after the batch summary. Input format: one target per line, `#` comments and blank lines ignored, optional per-line modifiers `language=<lang>` and `scope=<path>`. Each target's output lands in its own `{skill_package}/`; an aggregated summary writes to `{skills_output_folder}/_batch/quick-skill-batch-{ts}.json` (with `quick-skill-batch-latest.json` copy). Per-target boundary events (`{"batch":N,"target":"…","status":"start|done|fail",…}`) and a final `{"batch_summary":true,…}` event extend the per-step event stream above. The batch's result is the file that event's `summary_path` names: the `-latest` copy holds the last batch summary written, which may be another batch's. The batch exits with the highest exit code among its failed targets (`0` when none failed); `--fail-fast` stops it at the first failed target and exits with that target's code. A batch refused before its first target writes no summary and exits `2`, or `4` when its run folder cannot be written. A batch whose helper cannot hand out or record a target, or cannot summarize the batch, stops with exit `4` and a `batch-mode` halt line and skips the health check; a `batch_summary` event it already printed stays its result. Full input grammar, summary schema, and exit-code rules in [`src/skf-quick-skill/references/batch-mode.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-quick-skill/references/batch-mode.md).
|
|
553
553
|
|
|
554
554
|
**Exception: `/skf-brief-skill` headless emits a final result envelope and supports presets.** Headless `/skf-brief-skill` is a first-class building block for scripted brief generation (for example, seeding briefs for N SaaS SDKs that share scope and authority defaults). Three operational contracts go beyond per-gate auto-proceed:
|
|
555
555
|
|
|
@@ -572,18 +572,18 @@ Setup is not the only workflow whose result line is the run's final message: a h
|
|
|
572
572
|
|
|
573
573
|
**Other workflows emit result envelopes too.** In headless mode, AN, US, SS, AS, TS, VS, EX and campaign each print a single-line result envelope when they finish or halt: `SKF_ANALYZE_RESULT_JSON`, `SKF_UPDATE_RESULT_JSON`, `SKF_STACK_RESULT_JSON`, `SKF_AUDIT_RESULT_JSON`, `SKF_TEST_RESULT_JSON`, `SKF_VERIFY_STACK_RESULT_JSON`, `SKF_EXPORT_RESULT_JSON` and `SKF_CAMPAIGN_RESULT_JSON`. CS prints one `SKF_CREATE_SKILL_RESULT_JSON` line per finished brief on stdout (each `--batch` brief included) and one on stderr at every HARD HALT, in every mode, and once the skill's version folder under `forge_data_folder` exists (every finished brief, and a halt after step 7 creates it) writes `create-skill-result-<timestamp>.json` and its `-latest.json` copy there. SS checks its line against [`src/shared/scripts/schemas/skf-stack-result-envelope.v1.json`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json). Drop Skill, Rename Skill and Refine Architecture, described below, each emit a single-line `SKF_*_RESULT_JSON: {…}` envelope on every terminal exit (`status: "success"` on the happy path, `status: "error"` with a typed `halt_reason` on any HARD HALT) and exit with a stable code, so automators branch on the failure class without grepping message text. All three honour the universal `cancel`/`exit`/`:q` affordance at any prompt (exit `6`, `halt_reason: "user-cancelled"`).
|
|
574
574
|
|
|
575
|
-
- **`/skf-drop-skill` (DS)**: exit `2` `input-missing`/`input-invalid` (including `--mode deprecate` on a skill with no manifest entry), `3` resolution-failure (`manifest-corrupt`, `nothing-to-drop`), `4` write-failure (`manifest-write-failed`, `context-rebuild-failed`, `delete-failed` for a failed full purge, `write-failed`), `5` state-conflict (`active-version-guard-refused`; `not-skf-output` for a folder SKF did not generate, a purge it cannot confirm, or a skill whose folder in `forge_data_folder` also holds other files), `6` `user-cancelled` or `headless-purge-forbidden`. Besides `status`, `exit_code` and `halt_reason`, the envelope carries `would_delete` (in a dry run of a purge, the folders the purge would delete), `result_path` (the per-run result file, `null` for a dry run or a halt) and `error` (the `phase` and `reason` of a halt). A `success` drop can still carry `warnings` that name what needs a manual fix (`context_rebuild_failed`, `active_link_dangling`, `delete_failed`, `verification_failed`), and any run can carry `customization_resolver_unavailable` when the customization resolver could not run. Exit codes in [`src/skf-drop-skill/references/invocation-contract.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-drop-skill/references/invocation-contract.md) § "Exit Codes"; the envelope and full `halt_reason` list in the same file's § "Result Contract (Headless)".
|
|
575
|
+
- **`/skf-drop-skill` (DS)**: exit `2` `input-missing`/`input-invalid` (including `--mode deprecate` on a skill with no manifest entry), `3` resolution-failure (`manifest-corrupt`, `nothing-to-drop`), `4` write-failure (`manifest-write-failed`, `context-rebuild-failed`, `delete-failed` for a failed full purge, `write-failed`), `5` state-conflict (`active-version-guard-refused`; `not-skf-output` for a folder SKF did not generate, a purge it cannot confirm, or a skill whose folder in `forge_data_folder` also holds other files), `6` `user-cancelled` or `headless-purge-forbidden`. Besides `status`, `exit_code` and `halt_reason`, the envelope carries `would_delete` (in a dry run of a purge, the folders the purge would delete), `result_path` (the per-run result file, `null` for a dry run or a halt; a per-run record that could not be written leaves `result_path` null with a `result_file_write_failed` warning) and `error` (the `phase` and `reason` of a halt). A `success` drop can still carry `warnings` that name what needs a manual fix (`context_rebuild_failed`, `active_link_dangling`, `delete_failed`, `verification_failed`), and any run can carry `customization_resolver_unavailable` when the customization resolver could not run. Exit codes in [`src/skf-drop-skill/references/invocation-contract.md`](https://github.com/armelhbobdad/bmad-module-skill-forge/blob/main/src/skf-drop-skill/references/invocation-contract.md) § "Exit Codes"; the envelope and full `halt_reason` list in the same file's § "Result Contract (Headless)".
|
|
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.** 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`).
|
|
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 Quick Skill or Create Skill `--batch` run is read from its `batch_summary` event and the summary file that event's `summary_path` names (the event plus the batch run folder's `batch.jsonl` when `summary_path` is null), never from `quick-skill-batch-latest.json` or `create-skill-batch-latest.json`: each holds the last batch summary written, which may be another batch's. The event comes before the health check, so a caller reads it from the session's output stream (`claude -p --output-format stream-json`) or transcript, not from the final message. A printed `batch_summary` event stays the batch's result even if a halt line follows it. A batch that printed no `batch_summary` event has no batch result, whatever the process exit code: a halt line of its own (phase `batch-mode`, or `on-activation` for Quick Skill) says why it stopped, and with no halt line it was cut off or could not print its halt line. Each target it printed a `done` or `fail` event for ended as that event says, and a target with only a `start` event has no result. After a cut-off batch, run it again: a Quick Skill batch starts over from the first target, and a Create Skill batch run with the same briefs goes on with the brief it was working on. 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
|
| --- | --- | --- |
|
|
583
583
|
| AN | `SKF_ANALYZE_RESULT_JSON` | `brief_paths`, `unit_counts` |
|
|
584
|
-
| CS | `SKF_CREATE_SKILL_RESULT_JSON` | `status`, `exit_code`, `phase`, `summary.halt_reason` |
|
|
585
|
-
| SS | `SKF_STACK_RESULT_JSON` | `mode` (`code` or `compose`), `stack_libraries`, `skill_package`, `quality_score` (the skill-check score SS records for the committed stack, `null` when skill-check did not run; not a Test Skill score), `run_id`, `result_path`, `headless_decisions` and `warnings`, plus `exit_code`, `halt_reason` and `error` on a halt |
|
|
586
|
-
| US | `SKF_UPDATE_RESULT_JSON` | `skf_update.status`: `success`, `no-changes`, `detect-only` and `dry-run` are all good outcomes |
|
|
584
|
+
| CS | `SKF_CREATE_SKILL_RESULT_JSON` | `status`, `exit_code`, `phase`, `summary.halt_reason`, `summary.security_result` (`pass`, `warn` or `skipped`, the security scan's outcome, on a success line; `pass` when the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing) |
|
|
585
|
+
| SS | `SKF_STACK_RESULT_JSON` | `mode` (`code` or `compose`), `stack_libraries`, `skill_package`, `quality_score` (the skill-check score SS records for the committed stack, `null` when skill-check did not run; not a Test Skill score), `security_result` (`pass`, `warn` or `skipped`, the security scan's outcome, on a success line; `pass` when the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing), `run_id`, `result_path`, `headless_decisions` and `warnings`, plus `exit_code`, `halt_reason` and `error` on a halt |
|
|
586
|
+
| US | `SKF_UPDATE_RESULT_JSON` | `skf_update.status`: `success`, `no-changes`, `detect-only` and `dry-run` are all good outcomes; `skf_update.security_result` (`pass`, `warn` or `skipped`, the security scan's outcome; `pass` when the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing) on a `success` line |
|
|
587
587
|
| AS | `SKF_AUDIT_RESULT_JSON` | `drift_score`, `next_workflow` (`update-skill` whenever upstream moved), `upstream_moved`, `upstream_ref` (a `maintain` pipeline passes it to US as `--target-ref`) |
|
|
588
588
|
| TS | `SKF_TEST_RESULT_JSON` | `verdict`, `score`, `threshold` |
|
|
589
589
|
| EX | `SKF_EXPORT_RESULT_JSON` | `context_files_updated` |
|
|
@@ -605,7 +605,7 @@ Settings every workflow has:
|
|
|
605
605
|
|
|
606
606
|
- `persistent_facts`: rules the workflow keeps in mind for the whole run. Every workflow except Setup Forge loads any `project-context.md` in your project by default. In a team or personal override, an entry that starts with `!` drops each earlier entry it names and loads nothing itself. To turn that default off for one workflow, add `persistent_facts = ["!file:{project-root}/**/project-context.md"]` under `[workflow]` in `_bmad/custom/<skill-name>.toml`. Every workflow that loads the default honours such an entry, and Campaign's kickoff loader drops the default from each skill's kickoff the same way.
|
|
607
607
|
- `activation_steps_prepend` and `activation_steps_append`: extra steps to run before or after start-up.
|
|
608
|
-
- `on_complete`: a command to run after the workflow writes its result file, such as a notifier. Every workflow except Campaign, Stack Skill, Verify Stack and Setup Forge (Quick Skill included) calls it with `--result-path=<result file>` (for Brief Skill, the `skill-brief.yaml` it wrote), and the hook is skipped when the emitter wrote no result file. Quick Skill's hook gets `--result-path=<skill package>/quick-skill-result-latest.json`, where the skill package is that file's folder, and is skipped when the emitter wrote no result file, or did not write that `-latest` copy. Campaign passes `--report-path=<report>` when it wrote its report, Stack Skill runs the command with no argument, and Setup Forge and Verify Stack carry it out as an instruction once they finish. A failing command never fails the workflow.
|
|
608
|
+
- `on_complete`: a command to run after the workflow writes its result file, such as a notifier. Every workflow except Campaign, Stack Skill, Verify Stack and Setup Forge (Quick Skill included) calls it with `--result-path=<result file>` (for Brief Skill, the `skill-brief.yaml` it wrote), and the hook is skipped when the emitter wrote no result file. Quick Skill's hook gets `--result-path=<skill package>/quick-skill-result-latest.json`, where the skill package is that file's folder, and is skipped when the emitter wrote no result file, or did not write that `-latest` copy. Analyze Source, Create Skill and Update Skill pass their `-latest` copy too (`<forge_data_folder>/analyze-source-result-latest.json`, `<version folder>/create-skill-result-latest.json` and `<version folder>/update-skill-result-latest.json`), and skip the hook, with a line that says so, when the run did not write it. Campaign passes `--report-path=<report>` when it wrote its report, Stack Skill runs the command with no argument, and Setup Forge and Verify Stack carry it out as an instruction once they finish. A failing command never fails the workflow.
|
|
609
609
|
|
|
610
610
|
Useful settings in specific workflows:
|
|
611
611
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-module-skill-forge",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "8.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",
|
|
@@ -12,14 +12,14 @@ Any tool invocation that may touch SKILL.md must run inside the four-phase guard
|
|
|
12
12
|
|
|
13
13
|
### 1. Capture
|
|
14
14
|
|
|
15
|
-
Before invoking the tool, snapshot the on-disk `description` value:
|
|
15
|
+
Before invoking the tool, snapshot the on-disk `description` value into the run folder:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
uv run {descriptionGuardHelper} \
|
|
19
|
-
capture <skill-md-path>
|
|
19
|
+
capture <skill-md-path> > <run-folder>/description-guard.json
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
The file holds `capture`'s output:
|
|
23
23
|
|
|
24
24
|
```json
|
|
25
25
|
{
|
|
@@ -28,7 +28,7 @@ Output JSON:
|
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
A caller may read the `description` value from the file into workflow context as `guarded_description`, never into a shell string. Also keep the in-context copy synced with the on-disk pre-call state.
|
|
32
32
|
|
|
33
33
|
### 2. Execute
|
|
34
34
|
|
|
@@ -41,16 +41,18 @@ After the tool completes, run:
|
|
|
41
41
|
```bash
|
|
42
42
|
uv run {descriptionGuardHelper} \
|
|
43
43
|
verify-restore <skill-md-path> \
|
|
44
|
-
--captured-description
|
|
44
|
+
--captured-file <run-folder>/description-guard.json
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
`--captured-file <path>` reads the snapshot from a JSON file's `description`: `capture`'s output saved in the run folder, or another JSON file whose `description` holds the value (quick-skill re-runs with its `metadata.json`). Every calling step passes the file, so the description never passes through a shell string. An empty or whitespace-only `description` in the file is refused like an empty `--captured-description` (below), and so is a file the helper cannot read or that holds no string `description`.
|
|
48
|
+
|
|
49
|
+
The alternative, `--captured-description <value>`, is for a caller that holds no file and runs the helper with an argument list, without a shell. A shell string must never carry a description: inside double quotes the shell runs backticks and `$(...)`, expands `$name`, and a `"` ends the argument or drops out of it, so the call breaks, or the guard writes a changed description over one the tool never touched.
|
|
48
50
|
|
|
49
51
|
The script:
|
|
50
52
|
|
|
51
53
|
1. Re-reads on-disk `description`
|
|
52
|
-
2. Compares against `
|
|
53
|
-
3. If diverged: atomically rewrites the frontmatter `description` field with
|
|
54
|
+
2. Compares it against the snapshot (the `description` in the `--captured-file` file, or `--captured-description`) using **token-stream equality**: split each string on whitespace, compare resulting token lists element-by-element
|
|
55
|
+
3. If diverged: atomically rewrites the frontmatter `description` field with the snapshot
|
|
54
56
|
|
|
55
57
|
Output JSON:
|
|
56
58
|
|
|
@@ -63,9 +65,9 @@ Output JSON:
|
|
|
63
65
|
}
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
If `restored == true`, also
|
|
68
|
+
If `restored == true`, also set the in-context copy of `description` to the snapshot's `description`, read from the file as `guarded_description`, so subsequent stages do not work from a stale tool-mutated version. Record `description_guard_restored: true` (with the tool name and the `diff_kind`) in workflow context for the evidence report.
|
|
67
69
|
|
|
68
|
-
**Empty snapshot refusal.** The helper exits 1 and leaves the file untouched when
|
|
70
|
+
**Empty snapshot refusal.** The helper exits 1 and leaves the file untouched when the snapshot is empty or whitespace-only: the `description` in `<run-folder>/description-guard.json`, or an empty `--captured-description`. It does the same when it cannot read that file, or the file holds no JSON with a string `description`. There is nothing to restore from: either the snapshot was lost between capture and verify, or the field was already empty before the tool ran. Never retry with the empty value: writing it back would blank the field the guard exists to protect. If the compiled description is still available in context (the in-context SKILL.md copy), write it as `{"description": <that value as a JSON string>}` to the same file with a file write, never `echo`, and re-run `verify-restore` on it once. Otherwise, or when that re-run exits 1 too, record `description_guard_restored: false` and `description_guard_refused: empty-capture` (with the tool name) in workflow context; the calling stage's evidence-report populator renders that as a fired guard (`Restored: false`, the triggering tool, `Original description preserved: false`, and a `Notes` line that names the `empty-capture` refusal) rather than as the clean-run state, whose follow-up fields all hold the template's no-fire dash, and the frontmatter validator surfaces the empty field through the normal artifact path.
|
|
69
71
|
|
|
70
72
|
## Why Token-Stream Comparison
|
|
71
73
|
|
|
@@ -38,7 +38,7 @@ Each run writes **two files** to `{output_dir}`:
|
|
|
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):** 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.
|
|
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. Under `--batch` (skf-quick-skill, skf-create-skill), a batch's result is its `batch_summary` event and the summary file its `summary_path` names, never the `quick-skill-batch-latest.json` or `create-skill-batch-latest.json` copy: that copy holds the last batch summary written, which may be another batch's, and a batch that printed no `batch_summary` event has no batch result (each workflow's `references/batch-mode.md`).
|
|
42
42
|
|
|
43
43
|
## Writing the Files
|
|
44
44
|
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
"result_path": {
|
|
84
84
|
"type": ["string", "null"],
|
|
85
|
-
"description": "The per-run result file the emitter wrote, {forge_data_folder}/analyze-source-result-<YYYYMMDD-HHmmss>.json (UTC), beside its analyze-source-result-latest.json copy. Stamped by the emitter, never typed. Null for a HALT, which writes no result file, and when the
|
|
85
|
+
"description": "The per-run result file the emitter wrote, {forge_data_folder}/analyze-source-result-<YYYYMMDD-HHmmss>.json (UTC), beside its analyze-source-result-latest.json copy. Stamped by the emitter, never typed. Null for a HALT, which writes no result file, when --result-dir names no folder (nothing is written, and no warning is added), and when the per-run record could not be written (a result_file_write_failed warning says why); a -latest copy that could not be written leaves it set, and a result_file_write_failed warning names the copy."
|
|
86
86
|
},
|
|
87
87
|
"error": {
|
|
88
88
|
"oneOf": [
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"result_path": {
|
|
124
124
|
"type": ["string", "null"],
|
|
125
125
|
"minLength": 1,
|
|
126
|
-
"description": "The per-run result file the emitter wrote in the audited version's forge folder, audit-skill-result-<YYYYMMDD-HHmmss>.json (UTC), beside its audit-skill-result-latest.json copy. Stamped by the emitter, never typed. Null on every halt, which writes no result file, and when the
|
|
126
|
+
"description": "The per-run result file the emitter wrote in the audited version's forge folder, audit-skill-result-<YYYYMMDD-HHmmss>.json (UTC), beside its audit-skill-result-latest.json copy. Stamped by the emitter, never typed. Null on every halt, which writes no result file, when --result-dir names no folder (nothing is written, and no warning is added), and when the per-run record could not be written (a result_file_write_failed warning says why); a -latest copy that could not be written leaves it set, and a result_file_write_failed warning names the copy."
|
|
127
127
|
},
|
|
128
128
|
"headless_decisions": {
|
|
129
129
|
"type": "array",
|
|
@@ -147,6 +147,44 @@
|
|
|
147
147
|
},
|
|
148
148
|
"description": "Optional. Each skill whose skf-export-skill call failed, with that call's halt_reason and exit code (null when it gave none). The campaign still ends with status success, so a CI job gates on this list. Null when the Export stage did not run; absent on a HARD HALT."
|
|
149
149
|
},
|
|
150
|
+
"stage_outcomes": {
|
|
151
|
+
"type": ["object", "null"],
|
|
152
|
+
"additionalProperties": false,
|
|
153
|
+
"required": ["capstone", "verification", "refinement"],
|
|
154
|
+
"properties": {
|
|
155
|
+
"capstone": {
|
|
156
|
+
"type": ["object", "null"],
|
|
157
|
+
"additionalProperties": false,
|
|
158
|
+
"required": ["outcome", "halt_reason", "exit_code"],
|
|
159
|
+
"properties": {
|
|
160
|
+
"outcome": { "type": "string", "enum": ["completed", "skipped", "failed"] },
|
|
161
|
+
"halt_reason": { "type": ["string", "null"] },
|
|
162
|
+
"exit_code": { "type": ["integer", "null"] }
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"verification": {
|
|
166
|
+
"type": ["object", "null"],
|
|
167
|
+
"additionalProperties": false,
|
|
168
|
+
"required": ["outcome", "halt_reason", "exit_code"],
|
|
169
|
+
"properties": {
|
|
170
|
+
"outcome": { "type": "string", "enum": ["completed", "skipped", "failed"] },
|
|
171
|
+
"halt_reason": { "type": ["string", "null"] },
|
|
172
|
+
"exit_code": { "type": ["integer", "null"] }
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"refinement": {
|
|
176
|
+
"type": ["object", "null"],
|
|
177
|
+
"additionalProperties": false,
|
|
178
|
+
"required": ["outcome", "halt_reason", "exit_code"],
|
|
179
|
+
"properties": {
|
|
180
|
+
"outcome": { "type": "string", "enum": ["completed", "skipped", "failed"] },
|
|
181
|
+
"halt_reason": { "type": ["string", "null"] },
|
|
182
|
+
"exit_code": { "type": ["integer", "null"] }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"description": "Optional. What the capstone, verification and refinement stages did, from the outcome campaign-state.py recorded for each one: completed, skipped (a stage skipped on purpose, such as verification with no architecture document) or failed, with the failure's halt_reason (no-result when the sub-skill printed no result line, result-unreadable when Campaign could not read it, otherwise the sub-skill's own) and exit code (null when it gave none). A stage with no recorded outcome is null. The campaign still ends with status success, so a pipeline tells a failed verification from a skipped one here. Null before the capstone stage ran; absent on a HARD HALT."
|
|
187
|
+
},
|
|
150
188
|
"campaign_report_path": {
|
|
151
189
|
"type": ["string", "null"],
|
|
152
190
|
"minLength": 1,
|
|
@@ -195,6 +195,11 @@
|
|
|
195
195
|
"minimum": 0,
|
|
196
196
|
"description": "The decisions the run took without asking, as recorded in its run folder: every gate a headless run resolved by its default, and step 1's tier override in any mode. The rows of the evidence report's Auto-Decisions table; 0 when there were none."
|
|
197
197
|
},
|
|
198
|
+
"security_result": {
|
|
199
|
+
"type": "string",
|
|
200
|
+
"enum": ["pass", "warn", "skipped"],
|
|
201
|
+
"description": "The outcome of step 6's security scan, the first word of the evidence report's Security row under Validation Results, and the value follows that row. 'pass': the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing. 'warn': it reported findings. 'skipped': it did not run (skill-check unavailable, or the scan could not run, such as without SNYK_TOKEN). Absent at a HARD HALT, and when that row is missing or starts with another word."
|
|
202
|
+
},
|
|
198
203
|
"warning": {
|
|
199
204
|
"type": "string",
|
|
200
205
|
"enum": ["zero-exports"],
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
},
|
|
103
103
|
"result_path": {
|
|
104
104
|
"type": ["string", "null"],
|
|
105
|
-
"description": "The per-run result file the emitter wrote for a finished drop, {skills_output_folder}/drop-skill-result-<YYYYMMDD-HHmmss>.json (UTC), beside its drop-skill-result-latest.json copy. Stamped by the emitter, never typed. Null for a dry run and a HALT, which write no result file, and when the
|
|
105
|
+
"description": "The per-run result file the emitter wrote for a finished drop, {skills_output_folder}/drop-skill-result-<YYYYMMDD-HHmmss>.json (UTC), beside its drop-skill-result-latest.json copy. Stamped by the emitter, never typed. Null for a dry run and a HALT, which write no result file, when --result-dir names no folder (nothing is written, and no warning is added), and when the per-run record could not be written (a result_file_write_failed warning says why); a -latest copy that could not be written leaves it set, and a result_file_write_failed warning names the copy."
|
|
106
106
|
},
|
|
107
107
|
"exit_code": {
|
|
108
108
|
"type": "integer",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"result_path": {
|
|
128
128
|
"type": ["string", "null"],
|
|
129
129
|
"minLength": 1,
|
|
130
|
-
"description": "The per-run result file the emitter wrote in the skills output folder, export-skill-result-<YYYYMMDD-HHmmss>.json (its copy is export-skill-result-latest.json beside it). Null on a dry run, on every halt, and when the
|
|
130
|
+
"description": "The per-run result file the emitter wrote in the skills output folder, export-skill-result-<YYYYMMDD-HHmmss>.json (its copy is export-skill-result-latest.json beside it). Null on a dry run, on every halt, when --result-dir names no folder (nothing is written, and no warning is added), and when the per-run record could not be written (a result_file_write_failed warning says why); a -latest copy that could not be written leaves it set, and a result_file_write_failed warning names the copy."
|
|
131
131
|
},
|
|
132
132
|
"warnings": {
|
|
133
133
|
"type": "array",
|