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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +10 -5
- package/docs/_data/pinned.yaml +2 -2
- package/docs/agents.md +11 -2
- package/docs/architecture.md +5 -4
- package/docs/bmad-synergy.md +42 -3
- package/docs/campaign.md +172 -0
- package/docs/examples.md +7 -3
- package/docs/forge-auto.md +90 -0
- package/docs/getting-started.md +9 -0
- package/docs/how-it-works.md +6 -4
- package/docs/index.md +4 -3
- package/docs/skill-model.md +1 -1
- package/docs/troubleshooting.md +17 -1
- package/docs/verifying-a-skill.md +9 -3
- package/docs/why-skf.md +1 -1
- package/docs/workflows.md +73 -27
- package/package.json +2 -2
- package/src/module-help.csv +2 -1
- package/src/shared/_known-workarounds.yaml +155 -0
- package/src/shared/data/language-corpora.json +32 -0
- package/src/shared/references/pipeline-contracts.md +14 -2
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -1
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
- package/src/shared/scripts/skf-detect-docs.py +417 -0
- package/src/shared/scripts/skf-detect-language.py +5 -3
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
- package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
- package/src/shared/scripts/skf-extract-public-api.py +53 -0
- package/src/shared/scripts/skf-language-corpora.py +100 -0
- package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
- package/src/shared/scripts/skf-preapply.py +192 -0
- package/src/shared/scripts/skf-shape-detect.py +1114 -0
- package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
- package/src/shared/scripts/skf-validate-pins.py +368 -0
- package/src/shared/scripts/skf-write-skill-brief.py +21 -5
- package/src/skf-analyze-source/SKILL.md +26 -20
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- package/src/skf-analyze-source/references/init.md +30 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +677 -0
- package/src/skf-analyze-source/references/step-shape-detect.md +86 -0
- package/src/skf-audit-skill/SKILL.md +1 -0
- package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
- package/src/skf-audit-skill/references/init.md +1 -1
- package/src/skf-audit-skill/references/report.md +4 -0
- package/src/skf-audit-skill/references/severity-classify.md +1 -1
- package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
- package/src/skf-brief-skill/SKILL.md +7 -3
- package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
- package/src/skf-brief-skill/references/analyze-target.md +7 -4
- package/src/skf-brief-skill/references/confirm-brief.md +0 -1
- package/src/skf-brief-skill/references/gather-intent.md +22 -2
- package/src/skf-brief-skill/references/scope-definition.md +2 -1
- package/src/skf-brief-skill/references/step-auto-brief.md +188 -0
- package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
- package/src/skf-brief-skill/references/write-brief.md +2 -3
- package/src/skf-campaign/SKILL.md +190 -0
- package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
- package/src/skf-campaign/customize.toml +73 -0
- package/src/skf-campaign/manifest.yaml +11 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
- package/src/skf-campaign/references/health-check.md +35 -0
- package/src/skf-campaign/references/step-01-setup.md +122 -0
- package/src/skf-campaign/references/step-02-strategy.md +97 -0
- package/src/skf-campaign/references/step-03-pins.md +56 -0
- package/src/skf-campaign/references/step-04-provenance.md +63 -0
- package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
- package/src/skf-campaign/references/step-06-batch.md +87 -0
- package/src/skf-campaign/references/step-07-capstone.md +63 -0
- package/src/skf-campaign/references/step-08-verify.md +75 -0
- package/src/skf-campaign/references/step-09-refine.md +83 -0
- package/src/skf-campaign/references/step-10-export.md +106 -0
- package/src/skf-campaign/references/step-11-maintenance.md +84 -0
- package/src/skf-campaign/references/step-resume.md +114 -0
- package/src/skf-campaign/scripts/.gitkeep +0 -0
- package/src/skf-campaign/scripts/campaign-deps.py +235 -0
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
- package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
- package/src/skf-campaign/scripts/campaign-report.py +249 -0
- package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
- package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
- package/src/skf-campaign/templates/campaign-report-template.md +54 -0
- package/src/skf-campaign/templates/kickoff-template.md +48 -0
- package/src/skf-create-skill/SKILL.md +4 -1
- package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
- package/src/skf-create-skill/assets/skill-sections.md +2 -0
- package/src/skf-create-skill/references/compile.md +5 -2
- package/src/skf-create-skill/references/extract.md +9 -1
- package/src/skf-create-skill/references/extraction-patterns.md +3 -1
- package/src/skf-create-skill/references/generate-artifacts.md +11 -2
- package/src/skf-create-skill/references/health-check.md +2 -2
- package/src/skf-create-skill/references/report.md +17 -1
- package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
- package/src/skf-create-skill/references/step-auto-shard.md +110 -0
- package/src/skf-create-skill/references/step-doc-rot.md +109 -0
- package/src/skf-create-skill/references/step-doc-sources.md +123 -0
- package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
- package/src/skf-create-skill/references/validate.md +12 -3
- package/src/skf-drop-skill/SKILL.md +2 -2
- package/src/skf-drop-skill/references/execute.md +20 -9
- package/src/skf-drop-skill/references/report.md +2 -0
- package/src/skf-drop-skill/references/select.md +7 -2
- package/src/skf-forger/SKILL.md +15 -4
- package/src/skf-refine-architecture/SKILL.md +2 -1
- package/src/skf-refine-architecture/references/compile.md +1 -1
- package/src/skf-refine-architecture/references/report.md +1 -1
- package/src/skf-rename-skill/SKILL.md +2 -2
- package/src/skf-rename-skill/references/execute.md +5 -4
- package/src/skf-rename-skill/references/rebuild-context.md +2 -2
- package/src/skf-rename-skill/references/select.md +3 -3
- package/src/skf-setup/SKILL.md +1 -1
- package/src/skf-setup/references/auto-index.md +3 -1
- package/src/skf-setup/references/detect-and-tier.md +4 -0
- package/src/skf-setup/references/report.md +4 -1
- package/src/skf-setup/references/tier-rules.md +1 -1
- package/src/skf-test-skill/SKILL.md +8 -7
- package/src/skf-test-skill/customize.toml +2 -1
- package/src/skf-test-skill/references/coverage-check.md +10 -0
- package/src/skf-test-skill/references/external-validators.md +1 -1
- package/src/skf-test-skill/references/init.md +16 -1
- package/src/skf-test-skill/references/report.md +5 -1
- package/src/skf-test-skill/references/score.md +95 -6
- package/src/skf-test-skill/references/source-access-protocol.md +13 -3
- package/src/skf-test-skill/references/step-hard-gate.md +73 -0
- package/src/skf-test-skill/scripts/compute-score.py +4 -3
- package/src/skf-test-skill/templates/test-report-template.md +1 -0
- package/src/skf-update-skill/customize.toml +0 -9
- package/src/skf-update-skill/references/detect-changes.md +33 -3
- package/src/skf-update-skill/references/health-check.md +2 -2
- package/src/skf-update-skill/references/init.md +1 -0
- package/src/skf-update-skill/references/re-extract.md +15 -1
- package/src/skf-verify-stack/references/report.md +1 -1
- 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
|
|
27
|
-
3. Else
|
|
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
|
-
|
|
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 `
|
|
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
|
|
33
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
package/tools/cli/lib/ui.js
CHANGED
|
@@ -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)
|
|
360
|
-
|
|
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
|
|