bmad-module-skill-forge 1.8.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +10 -5
  3. package/docs/_data/pinned.yaml +2 -2
  4. package/docs/agents.md +11 -2
  5. package/docs/architecture.md +5 -4
  6. package/docs/bmad-synergy.md +42 -3
  7. package/docs/campaign.md +172 -0
  8. package/docs/examples.md +7 -3
  9. package/docs/forge-auto.md +90 -0
  10. package/docs/getting-started.md +9 -0
  11. package/docs/how-it-works.md +6 -4
  12. package/docs/index.md +4 -3
  13. package/docs/skill-model.md +1 -1
  14. package/docs/troubleshooting.md +17 -1
  15. package/docs/verifying-a-skill.md +9 -3
  16. package/docs/why-skf.md +1 -1
  17. package/docs/workflows.md +73 -27
  18. package/package.json +2 -2
  19. package/src/module-help.csv +2 -1
  20. package/src/shared/_known-workarounds.yaml +155 -0
  21. package/src/shared/data/language-corpora.json +32 -0
  22. package/src/shared/references/pipeline-contracts.md +14 -2
  23. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -1
  24. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  25. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  26. package/src/shared/scripts/skf-detect-docs.py +417 -0
  27. package/src/shared/scripts/skf-detect-language.py +5 -3
  28. package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
  29. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  30. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  31. package/src/shared/scripts/skf-language-corpora.py +100 -0
  32. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  33. package/src/shared/scripts/skf-preapply.py +192 -0
  34. package/src/shared/scripts/skf-shape-detect.py +1114 -0
  35. package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
  36. package/src/shared/scripts/skf-validate-pins.py +368 -0
  37. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  38. package/src/skf-analyze-source/SKILL.md +26 -20
  39. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  40. package/src/skf-analyze-source/references/init.md +30 -0
  41. package/src/skf-analyze-source/references/step-auto-scope.md +677 -0
  42. package/src/skf-analyze-source/references/step-shape-detect.md +86 -0
  43. package/src/skf-audit-skill/SKILL.md +1 -0
  44. package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
  45. package/src/skf-audit-skill/references/init.md +1 -1
  46. package/src/skf-audit-skill/references/report.md +4 -0
  47. package/src/skf-audit-skill/references/severity-classify.md +1 -1
  48. package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
  49. package/src/skf-brief-skill/SKILL.md +7 -3
  50. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  51. package/src/skf-brief-skill/references/analyze-target.md +7 -4
  52. package/src/skf-brief-skill/references/confirm-brief.md +0 -1
  53. package/src/skf-brief-skill/references/gather-intent.md +22 -2
  54. package/src/skf-brief-skill/references/scope-definition.md +2 -1
  55. package/src/skf-brief-skill/references/step-auto-brief.md +188 -0
  56. package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
  57. package/src/skf-brief-skill/references/write-brief.md +2 -3
  58. package/src/skf-campaign/SKILL.md +190 -0
  59. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  60. package/src/skf-campaign/customize.toml +73 -0
  61. package/src/skf-campaign/manifest.yaml +11 -0
  62. package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
  63. package/src/skf-campaign/references/health-check.md +35 -0
  64. package/src/skf-campaign/references/step-01-setup.md +122 -0
  65. package/src/skf-campaign/references/step-02-strategy.md +97 -0
  66. package/src/skf-campaign/references/step-03-pins.md +56 -0
  67. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  68. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  69. package/src/skf-campaign/references/step-06-batch.md +87 -0
  70. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  71. package/src/skf-campaign/references/step-08-verify.md +75 -0
  72. package/src/skf-campaign/references/step-09-refine.md +83 -0
  73. package/src/skf-campaign/references/step-10-export.md +106 -0
  74. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  75. package/src/skf-campaign/references/step-resume.md +114 -0
  76. package/src/skf-campaign/scripts/.gitkeep +0 -0
  77. package/src/skf-campaign/scripts/campaign-deps.py +235 -0
  78. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  79. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  80. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  81. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  82. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  83. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  84. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  85. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  86. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  87. package/src/skf-create-skill/SKILL.md +4 -1
  88. package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
  89. package/src/skf-create-skill/assets/skill-sections.md +2 -0
  90. package/src/skf-create-skill/references/compile.md +5 -2
  91. package/src/skf-create-skill/references/extract.md +9 -1
  92. package/src/skf-create-skill/references/extraction-patterns.md +3 -1
  93. package/src/skf-create-skill/references/generate-artifacts.md +11 -2
  94. package/src/skf-create-skill/references/health-check.md +2 -2
  95. package/src/skf-create-skill/references/report.md +17 -1
  96. package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
  97. package/src/skf-create-skill/references/step-auto-shard.md +110 -0
  98. package/src/skf-create-skill/references/step-doc-rot.md +109 -0
  99. package/src/skf-create-skill/references/step-doc-sources.md +123 -0
  100. package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
  101. package/src/skf-create-skill/references/validate.md +12 -3
  102. package/src/skf-drop-skill/SKILL.md +2 -2
  103. package/src/skf-drop-skill/references/execute.md +20 -9
  104. package/src/skf-drop-skill/references/report.md +2 -0
  105. package/src/skf-drop-skill/references/select.md +7 -2
  106. package/src/skf-forger/SKILL.md +15 -4
  107. package/src/skf-refine-architecture/SKILL.md +2 -1
  108. package/src/skf-refine-architecture/references/compile.md +1 -1
  109. package/src/skf-refine-architecture/references/report.md +1 -1
  110. package/src/skf-rename-skill/SKILL.md +2 -2
  111. package/src/skf-rename-skill/references/execute.md +5 -4
  112. package/src/skf-rename-skill/references/rebuild-context.md +2 -2
  113. package/src/skf-rename-skill/references/select.md +3 -3
  114. package/src/skf-setup/SKILL.md +1 -1
  115. package/src/skf-setup/references/auto-index.md +3 -1
  116. package/src/skf-setup/references/detect-and-tier.md +4 -0
  117. package/src/skf-setup/references/report.md +4 -1
  118. package/src/skf-setup/references/tier-rules.md +1 -1
  119. package/src/skf-test-skill/SKILL.md +8 -7
  120. package/src/skf-test-skill/customize.toml +2 -1
  121. package/src/skf-test-skill/references/coverage-check.md +10 -0
  122. package/src/skf-test-skill/references/external-validators.md +1 -1
  123. package/src/skf-test-skill/references/init.md +16 -1
  124. package/src/skf-test-skill/references/report.md +5 -1
  125. package/src/skf-test-skill/references/score.md +95 -6
  126. package/src/skf-test-skill/references/source-access-protocol.md +13 -3
  127. package/src/skf-test-skill/references/step-hard-gate.md +73 -0
  128. package/src/skf-test-skill/scripts/compute-score.py +4 -3
  129. package/src/skf-test-skill/templates/test-report-template.md +1 -0
  130. package/src/skf-update-skill/customize.toml +0 -9
  131. package/src/skf-update-skill/references/detect-changes.md +33 -3
  132. package/src/skf-update-skill/references/health-check.md +2 -2
  133. package/src/skf-update-skill/references/init.md +1 -0
  134. package/src/skf-update-skill/references/re-extract.md +15 -1
  135. package/src/skf-verify-stack/references/report.md +1 -1
  136. package/tools/cli/lib/ui.js +3 -2
@@ -20,13 +20,16 @@ Load `{scoringRulesFile}` to get:
20
20
  - Category weights (naive vs contextual distribution)
21
21
  - Tier-dependent scoring adjustments
22
22
 
23
- **Resolve the pass threshold (precedence: CLI > scalar > bundled fallback):**
23
+ **Resolve the pass threshold (precedence: CLI > pipeline default > scalar > bundled fallback):**
24
24
 
25
- 1. If the workflow received `--threshold=<N>` on invocation, use that integer as `effective_threshold` (CLI wins).
26
- 2. Else if the resolved `{defaultThreshold}` workflow-context variable (from SKILL.md On Activation §3 `workflow.default_threshold` scalar, default `80`) is set, use it as `effective_threshold`.
27
- 3. Else fall back to `80` (the bundled default this branch should be unreachable when SKILL.md resolution ran correctly, but keeps the step robust if customize.toml resolution failed silently).
25
+ 1. If the workflow received `--threshold=<N>` on invocation, use that integer as `effective_threshold` (CLI wins). Set `threshold_source` = `"CLI override ({N}%)"`.
26
+ 2. Else if `{pipeline_default_threshold}` is set in workflow context (resolved by init.md §1b from the per-pipeline threshold lookup table when `{pipeline_alias}` is present), use it as `effective_threshold`. Set `threshold_source` = `"pipeline default ({pipeline_alias} → {N}%)"`.
27
+ 3. Else if the resolved `{defaultThreshold}` workflow-context variable (from SKILL.md On Activation §3 `workflow.default_threshold` scalar, default `80`) is set, use it as `effective_threshold`. Set `threshold_source` = `"workflow default ({N}%)"`.
28
+ 4. Else fall back to `80` (the bundled default — this branch should be unreachable when SKILL.md resolution ran correctly, but keeps the step robust if customize.toml resolution failed silently). Set `threshold_source` = `"bundled fallback (80%)"`.
28
29
 
29
- Pass `effective_threshold` into the scoring-input JSON's `threshold` field in §3a (the compute-score.py script already honors this field). The CLI flag and the scalar feed the same downstream field; the script does not need to know which layer supplied the value.
30
+ Store `threshold_source` in workflow context for use in the score report section.
31
+
32
+ Pass `effective_threshold` into the scoring-input JSON's `threshold` field in §3a (the compute-score.py script already honors this field). The CLI flag, pipeline default, and the scalar all feed the same downstream field; the script does not need to know which layer supplied the value.
30
33
 
31
34
  **Docs-only mode check:** If the Coverage Analysis section in `{outputFile}` notes docs-only mode (set by step 3 for skills with all `[EXT:...]` citations and no local source), apply Quick-tier weight redistribution: Signature Accuracy and Type Coverage are not scored, their weights (22% + 14%) are redistributed proportionally to remaining active categories. Coverage score is based on documentation completeness rather than source coverage (as calculated by step 3).
32
35
 
@@ -82,7 +85,7 @@ Build a JSON object from the data gathered in steps 1-2:
82
85
  "coherence": "{combined_coherence_percentage or null if naive mode}",
83
86
  "externalValidation": "{external_validation_score or null if N/A}"
84
87
  },
85
- "threshold": "{effective_threshold from §1 — CLI --threshold wins, then workflow.default_threshold scalar, then 80}"
88
+ "threshold": "{effective_threshold from §1 — CLI --threshold wins, then pipeline default, then workflow.default_threshold scalar, then 80}"
86
89
  }
87
90
  ```
88
91
 
@@ -150,6 +153,85 @@ IF score < threshold → FAIL
150
153
 
151
154
  **Tooling-degraded cap:** If the `analysisConfidence` in output frontmatter is `degraded` (python3 missing, frontmatter validator missing, or other degraded state flagged in step 1), the step MUST cap the score at `threshold - 1` BEFORE the PASS/FAIL comparison. This forces a deterministic FAIL until tooling is restored. Do NOT override an INCONCLUSIVE result with the cap — INCONCLUSIVE remains the verdict.
152
155
 
156
+ ### 4b. Threshold Fallback and Evidence Report
157
+
158
+ After §4 determines the result but before §5 recommends the next workflow, check whether a threshold fallback applies. The fallback converts a FAIL into a PASS at the 80% floor when the score is between 80% and the effective threshold, and documents the quality compromise in an evidence report.
159
+
160
+ **Fallback trigger conditions:**
161
+
162
+ ```
163
+ IF result == "FAIL"
164
+ AND totalScore >= 80
165
+ AND effective_threshold > 80
166
+ THEN
167
+ → THRESHOLD FALLBACK triggered
168
+ ```
169
+
170
+ **Do NOT trigger fallback when:**
171
+ - `result == "INCONCLUSIVE"` — the evidence-floor verdict is never overridden by fallback
172
+ - `totalScore < 80` — that is a genuine FAIL (floor not met)
173
+ - `effective_threshold == 80` — there is nothing to fall back from
174
+
175
+ **When fallback triggers:**
176
+
177
+ 1. Record `threshold_fallback: true`, `original_threshold: {effective_threshold}`, `fallback_threshold: 80` in workflow context.
178
+ 2. Override `result` to `"PASS"`.
179
+ 3. Set `effective_threshold = 80` for use by §5/§6/§7/§8.
180
+ 4. Generate the evidence report (§4b.1 below).
181
+
182
+ #### 4b.1 Generate Evidence Report
183
+
184
+ Write the evidence report to `{forge_version}/evidence-report-fallback.md`. The report documents the quality compromise for audit purposes.
185
+
186
+ **Read gap entries:** extract findings from the Coverage Analysis and Coherence Analysis sections in `{outputFile}` — list each gap with severity (Critical through Low).
187
+
188
+ **Check for prior remediation:** glob `{forge_version}/test-report-{skill_name}-*.md` for a prior test report. If found, note the path — this implies remediation was attempted between runs. If not found, note "first test run — no prior remediation cycle".
189
+
190
+ **Check for post-score cap:** if Cap 1 or Cap 2 (§3d) fired, note the cap reason in the remediation section: "Score capped due to {cap_reason} — address tooling to test at the higher threshold."
191
+
192
+ **Evidence report template:**
193
+
194
+ ```markdown
195
+ # Evidence Report: Threshold Fallback
196
+
197
+ **Skill:** {skill_name}
198
+ **Date:** {ISO-8601 timestamp}
199
+ **Run ID:** {run_id}
200
+
201
+ ## Threshold Summary
202
+
203
+ | Field | Value |
204
+ |-------|-------|
205
+ | Attempted Threshold | {original_threshold}% |
206
+ | Achieved Score | {totalScore}% |
207
+ | Threshold Source | {threshold_source} |
208
+ | Final Accepted Threshold | 80% |
209
+
210
+ ## Findings Preventing Higher Threshold
211
+
212
+ {For each gap entry from Coverage Analysis and Coherence Analysis:}
213
+ - **{GAP-NNN}: {title}** — Severity: {severity}, Category: {category}
214
+
215
+ {Count: N critical, M high, P medium, Q low, R info findings}
216
+
217
+ ## Remediation Context
218
+
219
+ {If prior test report exists:}
220
+ A prior test run was found at `{prior_report_path}`, indicating remediation was attempted between runs.
221
+
222
+ {If no prior test report:}
223
+ No prior test report found for this skill version — this is the first test run.
224
+
225
+ {If post-score cap was active:}
226
+ **Note:** Score was capped due to {cap_reason}. Address tooling limitations to test at the higher threshold.
227
+
228
+ ## Conclusion
229
+
230
+ Skill accepted at 80% floor (original target: {original_threshold}%). The {N} findings above prevented meeting the higher threshold. Review and address findings before the next pipeline run to achieve the {original_threshold}% target.
231
+ ```
232
+
233
+ Record `evidence_report_path: '{forge_version}/evidence-report-fallback.md'` in workflow context for use by §6/§7/§8 and by report.md.
234
+
153
235
  ### 5. Determine Next Workflow Recommendation
154
236
 
155
237
  Based on test result:
@@ -203,6 +285,9 @@ Append the **Completeness Score** section to `{outputFile}`:
203
285
  **Inconclusive Reasons:**
204
286
  {bulleted list from script `inconclusiveReasons`}
205
287
 
288
+ **Threshold Source:** {threshold_source}
289
+ {If threshold_fallback is true:}
290
+ **Threshold Fallback:** scored {totalScore}% against {original_threshold}% target — accepted at 80% floor. Evidence report: {evidence_report_path}
206
291
  **Weight Distribution:** {naive (redistributed) | contextual (full)}
207
292
  **Tier Adjustment:** {none | Quick tier — signature and type coverage not scored}
208
293
  **External Validators:** {both available | skill-check only | tessl only | none — weight redistributed}
@@ -225,6 +310,8 @@ Update `{outputFile}` frontmatter:
225
310
  - `testResult: '{pass|pass-with-drift|fail|inconclusive}'` (lowercase; mirrors script `result`, with `pass-with-drift` substituted for `pass` when `allow_workspace_drift` was set and drift was observed — see §5 drift override)
226
311
  - `score: '{total}%'`
227
312
  - `threshold: '{threshold}%'`
313
+ - `thresholdSource: '{threshold_source}'`
314
+ - When `threshold_fallback` is true, add: `thresholdFallback: true`, `originalThreshold: '{original_threshold}%'`, `evidenceReportPath: '{evidence_report_path}'`
228
315
  - `analysisConfidence: '{full|degraded|provenance-map|metadata-only|remote-only|docs-only}'`
229
316
  - `nextWorkflow: '{export-skill|update-skill|manual-review}'`
230
317
  - Append `'score'` to `stepsCompleted`
@@ -244,6 +331,8 @@ Update `{outputFile}` frontmatter:
244
331
  | External Validation | {N}% | {WS}% |
245
332
 
246
333
  **Threshold:** {threshold}%
334
+ {If threshold_fallback is true:}
335
+ **Threshold fallback:** scored {totalScore}% against {original_threshold}% target — accepted at 80% floor. Evidence report: {evidence_report_path}
247
336
  **Recommendation:** {export-skill if pass | update-skill if fail}
248
337
 
249
338
  **Proceeding to gap report...**"
@@ -41,14 +41,14 @@
41
41
 
42
42
  Leave `analysis_confidence` unchanged (still `full` or `provenance-map` per the waterfall) — stratified scope does not degrade confidence, only the denominator. Annotate the coverage report with: `Stratified scope — denominator: {effective_denominator | tier_a_include union | scope.include union} ({N} files matched, {M} exports union)`.
43
43
 
44
- **When this clause does NOT apply:** `scope.type: "full-library"` skills, single-package repositories, or stratified briefs where the full monorepo is intentionally in scope. For those, use the standard barrel-based denominator — **unless** the single-package repo is a pattern-reference app (see next bullet).
44
+ **When this clause does NOT apply:** `scope.type: "full-library"` skills, single-package repositories, or stratified briefs where the full monorepo is intentionally in scope. For those, use the standard barrel-based denominator — **unless** the single-package repo is a pattern-reference app (see next bullet) or publishes a multi-subpath `exports` map (use the multi-entry clause below).
45
45
 
46
46
  - **Pattern-reference apps (non-library source):** If the source is a single-package repo whose purpose is demonstrating an integration pattern rather than distributing a library API — typical markers are `scope.type: "full-library"` **without** a barrel file at any recognized entry-point path (`__init__.py`, `index.ts`/`index.js`, `lib.rs`, `mod.rs`) AND without a monorepo layout — the skill's value lives in wiring patterns, not exports. None of the preceding three clauses fits: there is no barrel to count from, no empty-barrel `scope.include` to consult, and no monorepo stratification to re-derive.
47
47
 
48
48
  **Trigger (either fires):**
49
49
 
50
50
  1. `scope.notes` in `forge-data/{skill_name}/skill-brief.yaml` flags pattern-reference intent (phrases such as "Reference app, not a library", "pattern-reference", "embedded-pattern skill", or "skill value is the … pattern"). The `scope.notes` field is authoritative when the author wrote it.
51
- 2. Source tree lacks a barrel file at every recognized entry-point path AND the repo is not a monorepo (no `packages/`, `workspaces`, `lerna.json`, `rush.json`, `nx.json`, or Cargo `[workspace]`). Detected at test time by filesystem inspection of `{source_path}`.
51
+ 2. Source tree lacks a barrel file at every recognized entry-point path AND the repo is not a monorepo (no `packages/`, `workspaces`, `lerna.json`, `rush.json`, `nx.json`, or Cargo `[workspace]`) AND the package does not declare a multi-subpath `exports` map (those route to the multi-entry clause below). Detected at test time by filesystem inspection of `{source_path}`.
52
52
 
53
53
  **Denominator:** canonicalized provenance-map entry count (same canonicalization as the "Provenance-map canonicalization" section below). `skf-create-skill`'s extraction pass has already curated the provenance-map to the authored pattern surface; treat it as the authoritative enumeration of the skill's documented reach.
54
54
 
@@ -56,7 +56,7 @@
56
56
 
57
57
  **Confidence:** leave `analysis_confidence` unchanged (still `full` or `provenance-map` per the waterfall). Pattern-reference does not degrade confidence — the surface is smaller than a library barrel, not lower quality. Annotate the coverage report with: `Pattern-reference — denominator: {tier_a_include union | canonicalized provenance-map count} ({N} pattern surfaces)`.
58
58
 
59
- **When this clause does NOT apply:** any repo with a non-empty barrel file, any monorepo (use the stratified-scope clause), or any single-package repo whose `scope.type` is explicitly `public-api` / `specific-modules` / `component-library` / `docs-only` (those scope types have their own denominator semantics). Also does NOT apply when `scope.type: "reference-app"` — that scope type carries its own pattern-surface denominator semantics (the brief speaks for itself), so this clause's filesystem trigger is moot.
59
+ **When this clause does NOT apply:** any repo with a non-empty barrel file, any monorepo (use the stratified-scope clause), or any single-package repo whose `scope.type` is explicitly `specific-modules` (use the specific-modules clause), `public-api` with a multi-subpath `exports` map (use the multi-entry clause below a `public-api` package WITHOUT such a map keeps the standard root-barrel rule), `component-library`, or `docs-only`. Also does NOT apply when `scope.type: "reference-app"` — that scope type carries its own pattern-surface denominator semantics (the brief speaks for itself), so this clause's filesystem trigger is moot.
60
60
 
61
61
  - **Single-crate curated subset (`scope.type: "specific-modules"`):** If the source is a single-package (non-monorepo) repo whose skill brief sets `scope.type: "specific-modules"` and uses `scope.include`/`scope.exclude` to carve a subset of the crate's public surface, the coverage denominator is the **in-scope reachable barrel** — not the full barrel.
62
62
 
@@ -72,6 +72,16 @@
72
72
 
73
73
  **When this clause does NOT apply:** monorepo packages (use the stratified-scope clause), `scope.type: "full-library"` (use the standard barrel), or empty-barrel packages (use the empty-barrel clause). This clause is specifically for single-crate repos where the brief intentionally documents a curated subset rather than the full public surface.
74
74
 
75
+ - **Multi-entry (exports-map) packages (single-package libraries publishing via a `package.json` `exports` map):** If the in-scope `package.json` declares an `exports` map with **multiple non-root subpath entries** (more than just `"."`) and the repo carries **no** monorepo markers (`packages/` layout, `workspaces` field, `lerna.json`, `rush.json`, `nx.json`, Cargo `[workspace]`), the package's public surface spans every published subpath, not just the root barrel. The standard "named exports from `index.ts`" rule undercounts: it measures only the `"."` barrel while installers reach the full subpath set. This clause covers both `scope.type: "full-library"` AND `scope.type: "public-api"` for such packages.
76
+
77
+ **Denominator:** the **union of named exports across the files each NON-WILDCARD `exports` subpath resolves to**. Resolve each subpath to its target file (or its committed `.d.ts` / `.d.mts` declaration), then apply the same multi-line brace-accumulation and `export *` star-resolution rules documented for barrel re-derivation earlier in this file ("TypeScript / JavaScript barrel re-derivation"). **Explicitly exclude wildcard subpaths** (`"./*"` forms — they map to an open-ended file set whose surface is unbounded and uncountable). If the `exports` map has only a root `"."` entry, or only wildcard subpaths, fall back to the standard root-barrel rule.
78
+
79
+ **Curation/priority (same ladder the specific-modules clause uses):** prefer `metadata.json.stats.effective_denominator` first (subject to the existing deflation guard), then `scope.tier_a_include` globs (filtered by `scope.exclude`, the umbrella-barrel note applies) when the brief supplies it, else the full subpath union.
80
+
81
+ **Audit:** the root-barrel named-export count MUST be reported as a **secondary candidate** in the Denominator Candidates audit block (coverage-check.md §4) so the root-barrel-vs-subpath-union choice is auditable. Annotate the coverage report with: `Multi-entry (exports-map) — denominator: {effective_denominator | tier_a_include union | subpath union} ({N} subpaths resolved, {M} exports union; root barrel: {R})`.
82
+
83
+ **When this clause does NOT apply:** monorepo packages (use the stratified-scope clause), empty-barrel packages (use the empty-barrel clause), pattern-reference apps (use the pattern-reference clause), `scope.type: "specific-modules"` (use the specific-modules clause), or single-entry / wildcard-only `exports` maps (use the standard root-barrel rule).
84
+
75
85
  Internal module symbols are **excluded** from the coverage denominator unless they are explicitly documented in SKILL.md (in which case they count as documented extras, not missing coverage).
76
86
 
77
87
  This matches the extraction-patterns.md convention used during skill creation: coverage measures how well SKILL.md documents what users actually import, not the entire internal codebase.
@@ -0,0 +1,73 @@
1
+ ---
2
+ nextStepFile: 'score.md'
3
+ outputFile: '{forge_version}/test-report-{skill_name}-{run_id}.md'
4
+ ---
5
+
6
+ <!-- Config: communicate in {communication_language}. -->
7
+
8
+ # Step 4c: Hard Gate
9
+
10
+ ## STEP GOAL:
11
+
12
+ Scan accumulated findings from coverage and coherence analysis for critical or high severity gaps. If any exist, block the pipeline with a clear listing of every blocking finding — no coverage score is computed or reported. If only medium, low, or info findings exist, pass through to scoring.
13
+
14
+ ### §1. Read Findings from Output
15
+
16
+ Read `{outputFile}` and scan the **Coverage Analysis** and **Coherence Analysis** sections for GAP entries. Each GAP entry contains a severity marker in this format:
17
+
18
+ ```
19
+ **Severity:** {Critical|High|Medium|Low|Info}
20
+ ```
21
+
22
+ Extract every line matching `**Severity:** Critical` or `**Severity:** High`. For each match, also capture the parent GAP heading (`### GAP-{NNN}: {title}`) and the `**Source:**` line to build a blocking-findings list.
23
+
24
+ ### §2. Evaluate Gate
25
+
26
+ **Count blocking findings** (Critical + High severity).
27
+
28
+ **IF blocking findings exist → BLOCK (§3)**
29
+
30
+ **IF no blocking findings → PASS (§4)**
31
+
32
+ ### §3. Block — Critical/High Findings Detected
33
+
34
+ The hard gate blocks the pipeline. No scoring step runs.
35
+
36
+ Update `{outputFile}` frontmatter:
37
+ - Set `testResult: 'fail'`
38
+ - Append `'hard-gate'` to `stepsCompleted`
39
+
40
+ Report to the user:
41
+
42
+ "**Hard gate BLOCKED — {N} critical/high finding(s) must be resolved before scoring.**
43
+
44
+ | # | GAP | Severity | Source |
45
+ |---|-----|----------|--------|
46
+ {for each blocking finding:}
47
+ | {i} | {GAP-NNN}: {title} | {severity} | {source} |
48
+
49
+ **{M} medium/low/info findings also noted (non-blocking).**
50
+
51
+ **Action required:** resolve all Critical and High findings, then re-run test-skill.
52
+ **Recommended next step:** update-skill"
53
+
54
+ **Headless envelope (if `{headless_mode}`):** emit to **stderr**:
55
+
56
+ ```
57
+ SKF_TEST_RESULT_JSON: {"status":"error","skill_name":"{skill_name}","verdict":"FAIL","score":null,"threshold":null,"report_path":"{outputFile}","next_workflow":"update-skill","exit_code":2,"halt_reason":"hard-gate-blocked"}
58
+ ```
59
+
60
+ HALT — do not chain to `{nextStepFile}`.
61
+
62
+ ### §4. Pass — No Critical/High Findings
63
+
64
+ The hard gate passes. Medium, low, and info findings are documented in the gap report but do not block.
65
+
66
+ Update `{outputFile}` frontmatter:
67
+ - Append `'hard-gate'` to `stepsCompleted`
68
+
69
+ Report:
70
+
71
+ "**Hard gate passed** — {N} medium/low/info finding(s) noted, proceeding to scoring."
72
+
73
+ Update stepsCompleted, then load and execute `{nextStepFile}`.
@@ -29,8 +29,10 @@ Input schema (one object):
29
29
  }
30
30
 
31
31
  Exit codes:
32
- 0 — score computed (verdict may be PASS/FAIL/INCONCLUSIVE)
33
- 1 — input error (bad JSON, missing fields, schema violation)
32
+ 0 — input was parsed; either a score was computed (verdict may be
33
+ PASS/FAIL/INCONCLUSIVE) or a schema/validation error object
34
+ ({"error": ..., "code": "INVALID_INPUT"}) was emitted as JSON
35
+ 1 — input could not be parsed at all (no input provided, or malformed JSON)
34
36
  """
35
37
 
36
38
  from __future__ import annotations
@@ -381,4 +383,3 @@ def main(argv: list[str] | None = None) -> int:
381
383
 
382
384
  if __name__ == "__main__":
383
385
  raise SystemExit(main())
384
- sys.exit(1 if "error" in result else 0)
@@ -28,6 +28,7 @@ Anchor / Step mapping:
28
28
  Coverage Analysis → coverage-check
29
29
  Coherence Analysis → coherence-check
30
30
  External Validation→ external-validators
31
+ (hard gate) → step-hard-gate (reads findings, blocks or passes; no report section)
31
32
  Completeness Score → score
32
33
  Gap Report → report (includes Discovery Quality subsection)
33
34
  -->
@@ -33,12 +33,3 @@ activation_steps_append = []
33
33
  persistent_facts = [
34
34
  "file:{project-root}/**/project-context.md",
35
35
  ]
36
-
37
- # --- Optional gate thresholds ---
38
- #
39
- # Override the default thresholds used by detect-changes and merge. Empty
40
- # string or commented = use the bundled default (50% deletion ratio,
41
- # 80% rename similarity).
42
-
43
- deletion_ratio_threshold = ""
44
- rename_similarity_threshold = ""
@@ -25,6 +25,17 @@ buildChangeManifestProbeOrder:
25
25
  provenanceGapDispatchProbeOrder:
26
26
  - '{project-root}/_bmad/skf/shared/scripts/skf-provenance-gap-dispatch.py'
27
27
  - '{project-root}/src/shared/scripts/skf-provenance-gap-dispatch.py'
28
+ # Resolve `{detectScriptsAssetsHelper}` to the first existing path; HALT if
29
+ # neither candidate exists. §Category D uses `detect` to walk the current
30
+ # source tree for scripts/assets (directory conventions, shebang signals,
31
+ # `package.json` `bin` entry-points, asset filename patterns) so NEW_FILE
32
+ # detection mirrors create-skill §4c exactly. Falling back to prose-driven
33
+ # file walking would let the LLM drift on the heuristic list and miss new
34
+ # scripts/assets at deeper directory depths — and on installed modules
35
+ # (no `src/` tree) the LLM would otherwise guess a non-existent path.
36
+ detectScriptsAssetsProbeOrder:
37
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-scripts-assets.py'
38
+ - '{project-root}/src/shared/scripts/skf-detect-scripts-assets.py'
28
39
  ---
29
40
 
30
41
  <!-- Config: communicate in {communication_language}. -->
@@ -305,7 +316,20 @@ Launch subprocesses in parallel that compare source state against provenance map
305
316
 
306
317
  **Category C — Rename detection:**
307
318
  - Cross-reference deleted files/exports with added files/exports
308
- - If content similarity > 80%: classify as RENAMED instead of deleted+added. **Similarity mechanism by tier:** Quick: compare file size ratio (within 20%) and export name overlap (>70% of exports match by name). Forge and above: use ast-grep to compare export signatures between the deleted and added files. Forge+/Deep: use CCC semantic similarity when available
319
+ - If content similarity > 80% (fixed bundled threshold — not configurable): classify as RENAMED instead of deleted+added. **Similarity mechanism by tier:** Quick: compare file size ratio (within 20%) and export name overlap (>70% of exports match by name). Forge and above: use ast-grep to compare export signatures between the deleted and added files. Forge+/Deep: use CCC semantic similarity when available
320
+
321
+ **Subprocess return contract.** Hand each Category worker its exact output slice so it returns parse-ready JSON, not prose the parent must re-read. Each worker returns ONLY its own object — no prose, no commentary, no markdown fences (parent strips wrapping fences before parsing). The slice each worker fills is exactly the key §3's `build` helper consumes (the `category_a/b/c` shape below; Category D is helper-driven, not a worker):
322
+
323
+ ```json
324
+ // Category A worker returns ONLY:
325
+ {"category_a": {"modified": [...], "added": [...], "deleted": [...]}}
326
+ // Category B worker returns ONLY:
327
+ {"category_b": {"modified_exports": [...], "new_exports": [...], "deleted_exports": [...], "moved_exports": [...]}}
328
+ // Category C worker returns ONLY:
329
+ {"category_c": {"renamed_files": [...], "renamed_exports": [...]}}
330
+ ```
331
+
332
+ The parent merges the three slices (plus Category D and the `degraded_mode`/`update_mode` flags) into the single category-JSON object §3 pipes to the `build` helper.
309
333
 
310
334
  **Category D — Script/asset file changes:**
311
335
 
@@ -334,7 +358,13 @@ Translate the helper's output into the change manifest:
334
358
  - `DELETED_FILE` rows → add to manifest as DELETED_FILE
335
359
  - `UNCHANGED` rows → omit from the manifest (no action needed)
336
360
 
337
- The helper does NOT detect NEW_FILE — its job is provenance comparison only. Detect new files via `skf-detect-scripts-assets.py` from create-skill step 3 §4c against the current source tree, then subtract the paths already present in `provenance.file_entries[].source_file`. Remaining files matching detection patterns (`scripts/`, `bin/`, `assets/`, `templates/`) and NOT under `scripts/[MANUAL]/` or `assets/[MANUAL]/` are NEW_FILE.
361
+ The helper does NOT detect NEW_FILE — its job is provenance comparison only. Detect new files by running the same deterministic detector create-skill step 3 §4c uses (resolved via `detectScriptsAssetsProbeOrder`) against the current source tree:
362
+
363
+ ```bash
364
+ uv run {detectScriptsAssetsHelper} detect <source-root>
365
+ ```
366
+
367
+ It emits `scripts_inventory[]` and `assets_inventory[]` (each entry carries `source_file`). Take every `source_file` across both inventories, then subtract the paths already present in `provenance.file_entries[].source_file`. Of the remaining files, those matching detection patterns (`scripts/`, `bin/`, `assets/`, `templates/`) and NOT under `scripts/[MANUAL]/` or `assets/[MANUAL]/` are NEW_FILE.
338
368
 
339
369
  Files in `scripts/[MANUAL]/` or `assets/[MANUAL]/` → SKIP (user-authored, preserved).
340
370
 
@@ -356,7 +386,7 @@ echo "{category JSON}" | uv run {buildChangeManifestHelper} deletion-ratio \
356
386
  --provenance-map {forge_version}/provenance-map.json
357
387
  ```
358
388
 
359
- The helper handles the three skip conditions internally — when the input has `update_mode: "gap-driven"`, `degraded_mode: true`, or the provenance has zero entries, it returns `skip_reason` set and `should_trigger: false`. The output envelope:
389
+ The helper handles the three skip conditions internally — when the input has `update_mode: "gap-driven"`, `degraded_mode: true`, or the provenance has zero entries, it returns `skip_reason` set and `should_trigger: false`. `should_trigger` fires when the deletion ratio reaches the fixed bundled threshold of 50% (`ratio >= 0.50`, baked into the helper — not configurable). The output envelope:
360
390
 
361
391
  ```json
362
392
  {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  # `shared/health-check.md` resolves relative to the SKF module root
3
- # (`_bmad/skf/` when installed, `src/` during development), NOT relative
4
- # to this step file.
3
+ # (`{project-root}/_bmad/skf/` when installed, `{project-root}/src/` during
4
+ # development), NOT relative to this step file.
5
5
  nextStepFile: 'shared/health-check.md'
6
6
  ---
7
7
 
@@ -38,6 +38,7 @@ Provide either:
38
38
  3. If not in manifest: check for `active` symlink at `{skills_output_folder}/{skill-name}/active` — resolve to `{skill_group}/active/{skill-name}/`
39
39
  4. If neither: fall back to flat path `{skills_output_folder}/{skill-name}/`. If SKILL.md exists at the flat path, auto-migrate per `knowledge/version-paths.md` migration rules
40
40
  5. Store the resolved path as `{resolved_skill_package}` for all subsequent artifact loading
41
+ 6. Bind `{baseline_version}` to the pre-update version — for an update this is the version being updated, i.e. the `{active_version}` resolved in step 1 above (the flat-path fallback in step 4 has no version, so use the package version read from metadata.json in §2). Step 2 §1c passes `{baseline_version}` to `skf-provenance-gap-dispatch.py` as a required argument; leaving it unbound makes the helper search a wrong/empty directory and silently return `no-report`, dropping the major-version off-ramp.
41
42
 
42
43
  Resolve the path to an absolute skill folder location.
43
44
 
@@ -254,7 +254,21 @@ DO NOT BE LAZY — For EACH remaining file in the change manifest with status MO
254
254
  - Record: return type (if function/method)
255
255
  - Record: JSDoc/docstring summary (if present)
256
256
  - Label: confidence tier (T1/T1-low/T2)
257
- 4. Returns structured extraction findings to parent
257
+ 4. **Return contract.** Each extraction worker returns ONLY this per-file block — no prose, no commentary, no markdown fences (the parent strips wrapping fences before parsing). The shape is exactly the per-file record §4 aggregates (the `Per-file extractions` block, lines below), so the parent appends it verbatim rather than re-parsing free text:
258
+
259
+ ```json
260
+ {
261
+ "file_path": "...",
262
+ "exports": [
263
+ {"name": "...", "type": "function|class|type|constant",
264
+ "signature": "...", "location": "{file}:{start_line}-{end_line}",
265
+ "confidence": "T1|T1-low|T2",
266
+ "parameters": [{"name": "...", "type": "..."}],
267
+ "return_type": "...", "docstring": "...",
268
+ "qmd_evidence": "<if Deep tier, else omit>"}
269
+ ]
270
+ }
271
+ ```
258
272
 
259
273
  **For DELETED files:** No extraction needed — deletions handled in merge step.
260
274
 
@@ -106,7 +106,7 @@ Based on the overall verdict, present the appropriate recommendation:
106
106
 
107
107
  **Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. HALT if no candidate exists.
108
108
 
109
- Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{forge_data_folder}/verify-stack-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_data_folder}/verify-stack-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include the feasibility report path (both `{outputFile}` and `{outputFileLatest}`) in `outputs`; include `overallVerdict` (`FEASIBLE` / `CONDITIONALLY_FEASIBLE` / `NOT_FEASIBLE`), `coveragePercentage`, and `recommendationCount` in `summary` — use the case-sensitive schema tokens.
109
+ Write the result contract per `shared/references/output-contract-schema.md` (this path resolves relative to the SKF module root — `{project-root}/_bmad/skf/` when installed, `{project-root}/src/` during development — NOT relative to this step file): the per-run record at `{forge_data_folder}/verify-stack-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_data_folder}/verify-stack-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include the feasibility report path (both `{outputFile}` and `{outputFileLatest}`) in `outputs`; include `overallVerdict` (`FEASIBLE` / `CONDITIONALLY_FEASIBLE` / `NOT_FEASIBLE`), `coveragePercentage`, and `recommendationCount` in `summary` — use the case-sensitive schema tokens.
110
110
 
111
111
  Write both JSON files through `python3 {atomicWriteHelper} write --target ...` to avoid partial-write corruption. On any non-zero exit: HALT (exit code 4, `halt_reason: "write-failed"`) and emit the error envelope.
112
112
 
@@ -356,8 +356,9 @@ class UI {
356
356
  `${chalk.white.bold('Get Started')}`,
357
357
  `1. Open this folder in ${ideDisplay}`,
358
358
  `2. Activate Ferris: ${activateLine}`,
359
- '3. Ferris (your Skill Architect) will guide you through',
360
- ' setting up and forging your first agent skill',
359
+ '3. Ferris (your Skill Architect) guides you through forge setup',
360
+ `4. Fastest first skill ask Ferris to run ${brand.gold('forge-auto <repo-or-doc-url>')}:`,
361
+ ' one command auto-scopes, compiles, tests, and exports a verified skill',
361
362
  ].join('\n');
362
363
  }
363
364