bmad-module-skill-forge 1.6.0 → 1.7.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 (83) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/_data/pinned.yaml +1 -1
  3. package/docs/workflows.md +1 -1
  4. package/package.json +2 -2
  5. package/src/knowledge/version-paths.md +4 -3
  6. package/src/shared/health-check.md +1 -1
  7. package/src/shared/scripts/schemas/skill-brief.v1.json +5 -0
  8. package/src/shared/scripts/skf-atomic-write.py +4 -1
  9. package/src/shared/scripts/skf-detect-language.py +34 -0
  10. package/src/shared/scripts/skf-detect-workspaces.py +54 -0
  11. package/src/shared/scripts/skf-enumerate-stack-skills.py +107 -16
  12. package/src/shared/scripts/skf-forge-tier-rw.py +4 -1
  13. package/src/shared/scripts/skf-manifest-ops.py +8 -4
  14. package/src/shared/scripts/skf-merge-ccc-exclusions.py +4 -1
  15. package/src/shared/scripts/skf-qmd-classify-collections.py +29 -3
  16. package/src/shared/scripts/skf-rebuild-managed-sections.py +72 -7
  17. package/src/shared/scripts/skf-scan-skill-md-structure.py +13 -3
  18. package/src/shared/scripts/skf-validate-brief-inputs.py +79 -18
  19. package/src/shared/scripts/skf-validate-brief-schema.py +47 -1
  20. package/src/shared/scripts/skf-validate-frontmatter.py +60 -2
  21. package/src/shared/scripts/skf-write-skill-brief.py +97 -17
  22. package/src/skf-analyze-source/assets/skill-brief-schema.md +10 -2
  23. package/src/skf-analyze-source/references/generate-briefs.md +30 -9
  24. package/src/skf-analyze-source/references/recommend.md +1 -1
  25. package/src/skf-brief-skill/SKILL.md +3 -3
  26. package/src/skf-brief-skill/assets/skill-brief-schema.md +6 -1
  27. package/src/skf-brief-skill/references/analyze-target.md +22 -6
  28. package/src/skf-brief-skill/references/confirm-brief.md +4 -0
  29. package/src/skf-brief-skill/references/gather-intent.md +14 -4
  30. package/src/skf-brief-skill/references/headless-args.md +3 -2
  31. package/src/skf-brief-skill/references/portfolio-similarity-check.md +16 -8
  32. package/src/skf-brief-skill/references/scope-definition.md +20 -2
  33. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  34. package/src/skf-brief-skill/references/write-brief.md +8 -2
  35. package/src/skf-create-skill/assets/compile-assembly-rules.md +19 -0
  36. package/src/skf-create-skill/assets/skill-sections.md +1 -0
  37. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +18 -5
  38. package/src/skf-create-skill/references/compile.md +5 -2
  39. package/src/skf-create-skill/references/extract.md +8 -3
  40. package/src/skf-create-skill/references/extraction-patterns.md +14 -5
  41. package/src/skf-create-skill/references/generate-artifacts.md +5 -1
  42. package/src/skf-create-skill/references/source-resolution-protocols.md +5 -2
  43. package/src/skf-create-skill/references/validate.md +4 -3
  44. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +3 -3
  45. package/src/skf-create-stack-skill/assets/stack-skill-template.md +56 -0
  46. package/src/skf-create-stack-skill/references/compile-stack.md +13 -2
  47. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -0
  48. package/src/skf-create-stack-skill/references/detect-integrations.md +5 -2
  49. package/src/skf-create-stack-skill/references/generate-output.md +49 -9
  50. package/src/skf-create-stack-skill/references/validate.md +8 -5
  51. package/src/skf-export-skill/references/load-skill.md +5 -4
  52. package/src/skf-export-skill/references/manifest-rebuild.md +1 -1
  53. package/src/skf-export-skill/references/token-report.md +2 -2
  54. package/src/skf-export-skill/references/update-context.md +25 -11
  55. package/src/skf-quick-skill/assets/skill-template.md +2 -2
  56. package/src/skf-quick-skill/references/compile.md +4 -2
  57. package/src/skf-quick-skill/references/write-and-validate.md +2 -2
  58. package/src/skf-refine-architecture/SKILL.md +1 -1
  59. package/src/skf-refine-architecture/references/gap-analysis.md +30 -4
  60. package/src/skf-refine-architecture/references/init.md +2 -0
  61. package/src/skf-refine-architecture/references/issue-detection.md +8 -3
  62. package/src/skf-test-skill/references/coherence-check.md +4 -4
  63. package/src/skf-test-skill/references/coverage-check.md +58 -16
  64. package/src/skf-test-skill/references/external-validators.md +7 -7
  65. package/src/skf-test-skill/references/init.md +5 -4
  66. package/src/skf-test-skill/references/score.md +6 -3
  67. package/src/skf-test-skill/references/scoring-rules.md +12 -0
  68. package/src/skf-test-skill/references/source-access-protocol.md +19 -3
  69. package/src/skf-test-skill/scripts/compute-score.py +4 -1
  70. package/src/skf-update-skill/references/detect-changes.md +15 -2
  71. package/src/skf-update-skill/references/merge.md +22 -0
  72. package/src/skf-update-skill/references/re-extract.md +19 -7
  73. package/src/skf-update-skill/references/write.md +3 -0
  74. package/src/skf-verify-stack/SKILL.md +2 -2
  75. package/src/skf-verify-stack/assets/feasibility-report-template.md +1 -1
  76. package/src/skf-verify-stack/references/coverage.md +35 -6
  77. package/src/skf-verify-stack/references/init.md +6 -2
  78. package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
  79. package/src/skf-verify-stack/references/integrations.md +7 -3
  80. package/src/skf-verify-stack/references/report.md +5 -1
  81. package/src/skf-verify-stack/references/requirements.md +3 -1
  82. package/src/skf-verify-stack/references/synthesize.md +11 -3
  83. package/tools/cli/lib/installer.js +1 -0
@@ -55,11 +55,11 @@ timeout 10s npx --no-install skill-check -h
55
55
 
56
56
  **If available**, run: `npx skill-check check <skill-dir> --fix --format json --no-security-scan`
57
57
 
58
- This validates frontmatter, description, body limits, links, formatting — and auto-fixes deterministic issues. Parse JSON for `qualityScore`, `diagnostics[]`, `fixed[]`.
58
+ This validates frontmatter, description, body limits, links, formatting — and auto-fixes deterministic issues. Parse JSON for `scores[].score` (match the entry by `relativePath`/`skillId`; falls back to a top-level `qualityScore` on older skill-check builds), `diagnostics[]`, `fixed[]`.
59
59
 
60
60
  **Post-fix provenance drift guard (S15):** If `fixed[]` is non-empty, `skill-check --fix` has modified `SKILL.md` after step 7 wrote it. The safe default for v1.0 is to emit a **WARNING** finding listing each auto-fix (`"skill-check --fix modified SKILL.md: {fix_description} — metadata.json hashes/provenance may be out of date"`). Do NOT silently accept the fixes without surfacing the drift. If the caller wants authoritative metadata, they should re-run the workflow.
61
61
 
62
- **If `body.max_lines` reported**, prefer selective split: extract only the largest Tier 2 section(s) to `references/`, keeping Tier 1 content inline (inline passive context achieves 100% task accuracy vs 79% for on-demand retrieval). Fall back to `npx skill-check split-body <skill-dir> --write` if not feasible. Verify `#quick-start` and `#key-types` anchors still resolve after split. Then re-validate.
62
+ **If `body.max_lines` reported**, prefer selective split: extract only the largest Tier 2 section(s) to `references/`, keeping Tier 1 content inline (inline passive context achieves 100% task accuracy vs 79% for on-demand retrieval). For a stack capstone the canonical split is the catalog (`Library Reference Index` + `Per-Library Summaries`) → `references/stack-catalog.md`, leaving an inline pointer (see `{stackSkillTemplate}` "Sizing Guidance"). This is the **intended** large-stack layout, not a violation: §4 below accepts the pointer form, so clearing the skill-check body ERROR this way does not also trip the structure check. Fall back to `npx skill-check split-body <skill-dir> --write` if not feasible. Verify any in-SKILL.md anchor links (e.g. to the catalog/pointer or other moved sections) still resolve after the split. Then re-validate.
63
63
 
64
64
  **If unavailable**, perform manual frontmatter check:
65
65
  - [ ] Frontmatter present with `---` delimiters
@@ -75,11 +75,14 @@ Load `{stackSkillTemplate}` and verify SKILL.md contains expected sections:
75
75
 
76
76
  - [ ] Header with project name, library count, integration count, forge tier
77
77
  - [ ] Integration Patterns section (before per-library summaries)
78
- - [ ] Library Reference Index table
79
- - [ ] Per-Library Summaries section
80
78
  - [ ] Conventions section
79
+ - [ ] **Catalog** — `Library Reference Index` table + `Per-Library Summaries`, in **either** form:
80
+ - *Inline* (small stacks): both sections present in SKILL.md, **or**
81
+ - *Pointer* (large stacks): a `Library Catalog` pointer to `references/stack-catalog.md`, **and** that file exists and contains both sections.
81
82
 
82
- Record missing sections as **WARNING** findings.
83
+ Accept either form do not WARN when the catalog has been extracted to clear the `body.max_lines` budget (§3). Only record a **WARNING** when *neither* the inline sections *nor* a pointer-plus-`stack-catalog.md` is present, or when the pointer's target file is missing.
84
+
85
+ Record other missing sections (Header, Integration Patterns, Conventions) as **WARNING** findings.
83
86
 
84
87
  ### 5. Validate metadata.json Fields
85
88
 
@@ -79,10 +79,11 @@ For each IDE in `config.yaml.ides`:
79
79
  Resolve the skill's versioned path before loading artifacts:
80
80
 
81
81
  1. Read `{skills_output_folder}/.export-manifest.json` and look up `{skill-name}` in `exports` to get `active_version`
82
- 2. If found: resolve to `{skill_package}` = `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/`
83
- 3. If not in manifest: check for `active` symlink at `{skills_output_folder}/{skill-name}/active` — resolve to `{skill_group}/active/{skill-name}/`
84
- 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
85
- 5. Store the resolved path as `{resolved_skill_package}` for all subsequent artifact loading
82
+ 2. **Manifest-lag guard.** If the skill is in the manifest, also read the `active` symlink target at `{skills_output_folder}/{skill-name}/active`. If that symlink resolves to a *different* version than `active_version`, prefer the **symlink target** as `{resolved_version}` and emit an Info note: "manifest active_version {M} lags the active symlink {N} — exporting the symlink target (the just-forged version); the manifest active_version advances to {N} on this export." This is the canonical SS→TS→EX case: create-stack-skill flipped `active` to the new version, but the manifest only advances when *this* export runs. A bare manifest-first resolution would re-export the *previously exported* version, and step 4 §4b/step-5 (`update-context.md`) — which derives the published version from `{resolved_skill_package}/metadata.json` — would then write that stale version straight back as `active_version`, so the forged version could never be published. Resolving to the symlink target here makes this export publish {N} and reconcile the manifest. When the symlink matches `active_version` (or no `active` symlink exists), use `active_version`. See `knowledge/version-paths.md` "Reading Workflows".
83
+ 3. If found: resolve to `{skill_package}` = `{skills_output_folder}/{skill-name}/{resolved_version}/{skill-name}/`
84
+ 4. If not in manifest: check for `active` symlink at `{skills_output_folder}/{skill-name}/active` resolve to `{skill_group}/active/{skill-name}/`
85
+ 5. 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
86
+ 6. Store the resolved path as `{resolved_skill_package}` for all subsequent artifact loading
86
87
 
87
88
  Load all files from `{resolved_skill_package}`:
88
89
 
@@ -61,7 +61,7 @@ For v1 manifests (no `schema_version` field), the helper migrates in-place on th
61
61
  3. Wrap in v2 structure: `active_version` ← resolved version, single entry in `versions` with `status: "active"`, `ides: []` (unknown — fills on next successful export), and `last_exported`
62
62
  4. Set `schema_version: "2"` at root
63
63
 
64
- Workflows that load the manifest via `skf-manifest-ops.py read` always receive the v2 shape regardless of on-disk state.
64
+ Workflows that load the manifest via `skf-manifest-ops.py read` receive a `{"status": "ok", "manifest": {...}}` envelope; the `manifest` value is always in canonical v2 shape regardless of on-disk state (parse `result["manifest"]`, not the top-level object).
65
65
 
66
66
  ## Integrity invariant
67
67
 
@@ -29,7 +29,7 @@ For each artifact, estimate tokens using the heuristic: **words * 1.3** (approxi
29
29
  3. **SKILL.md** — The full active skill document
30
30
  4. **metadata.json** — The machine-readable metadata
31
31
  5. **references/** — Total across all reference files (if present)
32
- 6. **Full package total** — Sum of all above
32
+ 6. **Full package total** — Sum of this skill's own artifacts (context-snippet.md + SKILL.md + metadata.json + references/). **Exclude the Managed section row (item 2)** — it is a shared all-skills cost (it bundles this skill's snippet plus every other skill's snippet), reported separately under Context Budget Impact. Including it would double-count this skill's snippet and fold in unrelated skills' costs.
33
33
 
34
34
  **If passive_context was disabled:** Skip context-snippet.md and managed section measurements, note as "N/A (disabled)".
35
35
 
@@ -44,7 +44,7 @@ For each artifact, estimate tokens using the heuristic: **words * 1.3** (approxi
44
44
  | SKILL.md | {n} | ~{t} | Active skill (on-trigger) |
45
45
  | metadata.json | {n} | ~{t} | Machine-readable |
46
46
  | references/ | {n} | ~{t} | {count} files |
47
- | **Package total** | **{n}** | **~{t}** | **All artifacts combined** |
47
+ | **Package total** | **{n}** | **~{t}** | **This skill's own artifacts (snippet + SKILL.md + metadata + references); excludes the shared Managed section row** |
48
48
 
49
49
  **Context Budget Impact:**
50
50
  - **Always-on cost:** ~{managed-section-tokens} tokens (managed section in {target-file-list})
@@ -87,13 +87,13 @@ Read the manifest via `{manifestOpsHelper}` (resolved at §9 from `{manifestOpsP
87
87
  python3 {manifestOpsHelper} {skills_output_folder} read
88
88
  ```
89
89
 
90
- The helper handles v2-schema enforcement, v1→v2 migration, and the `platforms`→`ides` rename internally. The returned JSON is always in canonical v2 shape regardless of on-disk state.
90
+ The helper handles v2-schema enforcement, v1→v2 migration, and the `platforms`→`ides` rename internally. The `read` action returns a `{"status": "ok", "manifest": {...}}` envelope — parse `result["manifest"]`, **not** the top-level object (`result["exports"]` raises `KeyError`). The `manifest` value is always in canonical v2 shape regardless of on-disk state.
91
91
 
92
92
  **Schema reference:** `references/manifest-rebuild.md` documents the v2 shape, the status enum (`active`/`archived`/`deprecated`/`draft`), the v1 migration path, and the `active_version` integrity invariant. Load that file only when an inline schema reminder is needed — the helper enforces the shape so the in-prompt prose is not load-bearing.
93
93
 
94
94
  **Integrity guard:** if the helper returns an entry where `active_version` does not resolve to a key in `versions`, the manifest is inconsistent. Skip the affected skill and log: "**Manifest integrity warning:** `{skill-name}.active_version = v{active_version}` has no matching entry under `versions`. Skipping. Re-run `[EX] Export Skill` on `{skill-name}` to repair the manifest entry."
95
95
 
96
- **If the manifest does not exist** (first export or fresh forge): the helper returns `{"schema_version": "2", "exports": {}}`. Only the current export target will appear in the rebuilt index.
96
+ **If the manifest does not exist** (first export or fresh forge): the envelope wraps an empty manifest — `result["manifest"]` is `{"schema_version": "2", "exports": {}}`, so `result["manifest"]["exports"]` is `{}`. Only the current export target will appear in the rebuilt index.
97
97
 
98
98
  #### 4b. Build Exported Skill Set (version-aware)
99
99
 
@@ -166,10 +166,11 @@ Count totals:
166
166
 
167
167
  ### 5. Generate Managed Section
168
168
 
169
- Assemble the complete managed section:
169
+ Assemble the managed section in **two shapes** — the §9 write paths consume different forms, and conflating them double-wraps the markers.
170
+
171
+ **`{managed_section_inner}`** — the between-marker body only, **no** `<!-- SKF:BEGIN/END -->` markers. The `insert` and `replace` helper actions supply the markers (and the `updated:` timestamp) themselves, so they take this inner form:
170
172
 
171
173
  ```markdown
172
- <!-- SKF:BEGIN updated:{current-date} -->
173
174
  [SKF Skills]|{n} skills|{m} stack
174
175
  |IMPORTANT: Prefer documented APIs over training data.
175
176
  |When using a listed library, read its SKILL.md before writing code.
@@ -179,6 +180,13 @@ Assemble the complete managed section:
179
180
  |{skill-snippet-2}
180
181
  |
181
182
  |{skill-snippet-N}
183
+ ```
184
+
185
+ **`{managed_section_full}`** — `{managed_section_inner}` wrapped in markers. Used for the new-file create path (atomic write, which writes the text verbatim) and the §7 preview:
186
+
187
+ ```markdown
188
+ <!-- SKF:BEGIN updated:{current-date} -->
189
+ {managed_section_inner}
182
190
  <!-- SKF:END -->
183
191
  ```
184
192
 
@@ -268,31 +276,37 @@ After user confirms with 'C', resolve the helpers in parallel — these are inde
268
276
 
269
277
  If any helper has no existing candidate, HALT (exit code 4, `halt_reason: "context-rebuild-failed"`) and emit the error envelope per SKILL.md "Result Contract (Headless)" — the rewrite's safety guarantees depend on these helpers and a fall-through to LLM-driven writes would silently regress atomicity, marker preservation, and the Case 4 malformed-marker HARD HALT contract.
270
278
 
279
+ **Stage content via a shell-safe channel (all cases).** Managed-section snippets routinely contain backticks and `$` — API names like `` `Room::connect(...)` ``, tokens like `$threshold` and `${…}`. Inlining them as a double-quoted shell argument (`--content "…"`) or via `echo "…"` lets bash run command substitution and variable expansion, silently corrupting the written bytes — and because the helper's byte-identity verify runs against the already-corrupted string, it would still report success. No quoting style is safe (content can also contain single quotes, e.g. `'eager' | 'on-demand'`), so **never** inline the section into the command. Instead write the assembled section to a staging file with your file-write tool (which performs no shell interpretation), then feed it to the helper via stdin redirection — the helper reads stdin whenever `--content` is absent:
280
+
281
+ - For **Cases 2 and 3**, write `{managed_section_inner}` to `{target-file}.skf-content` with **no trailing newline** (the helper appends `\n<!-- SKF:END -->` itself; a trailing newline would insert a blank line before the end marker).
282
+ - For **Case 1**, write `{managed_section_full}` to `{target-file}.skf-content` followed by a single trailing newline (matches the verbatim file-end newline convention).
283
+ - After the helper reports success, delete `{target-file}.skf-content`.
284
+
271
285
  For each target context file, dispatch by case:
272
286
 
273
287
  **Case 1 (Create — file does not exist):**
274
288
 
275
289
  ```bash
276
- echo "{new_managed_section_text}" | python3 {atomicWriteHelper} write "{target-file}"
290
+ python3 {atomicWriteHelper} write --target "{target-file}" < "{target-file}.skf-content"
277
291
  ```
278
292
 
279
- The helper stages the content into `<target>.skf-tmp`, fsyncs, and atomically renames into place.
293
+ The helper stages the content into `<target>.skf-tmp`, fsyncs, and atomically renames into place. This path writes verbatim, so the staging file holds the **marker-bearing** `{managed_section_full}`.
280
294
 
281
295
  **Case 2 (Append — file exists, no `<!-- SKF:BEGIN` marker):**
282
296
 
283
297
  ```bash
284
- python3 {rebuildManagedSectionsHelper} {target-file} insert --content "{new_managed_section_text}"
298
+ python3 {rebuildManagedSectionsHelper} {target-file} insert < "{target-file}.skf-content"
285
299
  ```
286
300
 
287
- The helper appends the managed section to the end of the file via the same atomic temp-file + rename pattern.
301
+ The helper appends the managed section to the end of the file via the same atomic temp-file + rename pattern. The staging file holds the **inner-only** `{managed_section_inner}` — the helper adds the markers and `updated:` timestamp itself; marker-bearing text double-wraps them.
288
302
 
289
303
  **Case 3 (Regenerate — file contains `<!-- SKF:BEGIN` and `<!-- SKF:END -->`):**
290
304
 
291
305
  ```bash
292
- python3 {rebuildManagedSectionsHelper} {target-file} replace --content "{new_managed_section_text}"
306
+ python3 {rebuildManagedSectionsHelper} {target-file} replace < "{target-file}.skf-content"
293
307
  ```
294
308
 
295
- The helper performs the surgical between-marker swap with post-write verification (markers present, content outside markers byte-identical).
309
+ The helper performs the surgical between-marker swap with post-write verification (markers present, content outside markers byte-identical). The staging file holds the **inner-only** `{managed_section_inner}` — as with `insert`, the helper supplies the markers; marker-bearing text double-wraps them.
296
310
 
297
311
  **Case 4 (malformed markers — already HALTed in §6):** never reaches here.
298
312
 
@@ -338,7 +352,7 @@ On success per file, report: "**{target-file} updated successfully.** Verified b
338
352
  python3 {manifestOpsHelper} {skills_output_folder} set {skill-name} {version} --ides {ides_written}
339
353
  ```
340
354
 
341
- `{ides_written}` is the comma-joined sorted IDE set computed in step 3. The helper handles v2-schema validation, v1→v2 migration, and `platforms`→`ides` rename internally — no in-prompt JSON manipulation needed. Each `set` invocation unions the supplied `--ides` into the version entry's existing `ides` (deduplicated, sorted) server-side and refreshes `last_exported`. After all skills have been set, re-read the manifest via `{manifestOpsHelper} {skills_output_folder} read` to confirm the final state matches expectations.
355
+ `{ides_written}` is the comma-joined sorted IDE set computed in step 3. The helper handles v2-schema validation, v1→v2 migration, and `platforms`→`ides` rename internally — no in-prompt JSON manipulation needed. Each `set` invocation unions the supplied `--ides` into the version entry's existing `ides` (deduplicated, sorted) server-side and refreshes `last_exported`. After all skills have been set, re-read the manifest via `{manifestOpsHelper} {skills_output_folder} read` (the v2 manifest is under the `manifest` key of the returned envelope — see §4a) to confirm the final state matches expectations.
342
356
 
343
357
  **Dry-run mode:** Do NOT update the manifest. Display: "**[DRY RUN] Export manifest would be updated for {skill-name-list} — ides: {ides_written}.**" (list every skill in `skill_batch`)
344
358
 
@@ -54,9 +54,9 @@ Indexed format targeting ~80-120 tokens per skill:
54
54
  ```markdown
55
55
  [{skill_name} v{version}]|root: skills/{skill_name}/
56
56
  |IMPORTANT: {skill_name} v{version} — read SKILL.md before writing {skill_name} code. Do NOT rely on training data.
57
- |quick-start:{SKILL.md#quick-start}
57
+ |quick-start:{SKILL.md#usage-patterns}
58
58
  |api: {top-5 exports with () for functions}
59
- |key-types:{SKILL.md#key-types} — {inline summary of most important type values}
59
+ |key-types:{SKILL.md#key-exports} — {inline summary of most important type values}
60
60
  |gotchas: {2-3 most critical pitfalls or breaking changes, inline}
61
61
  ```
62
62
 
@@ -75,12 +75,14 @@ Otherwise, create context-snippet.md in Vercel-aligned indexed format (~80-120 t
75
75
  ```
76
76
  [{skill_name} v{version}]|root: skills/{skill_name}/
77
77
  |IMPORTANT: {skill_name} v{version} — read SKILL.md before writing {skill_name} code. Do NOT rely on training data.
78
- |quick-start:{SKILL.md#quick-start}
78
+ |quick-start:{SKILL.md#usage-patterns}
79
79
  |api: {top-5 exports with () for functions}
80
- |key-types:{SKILL.md#key-types} — {inline summary of most important type values}
80
+ |key-types:{SKILL.md#key-exports} — {inline summary of most important type values}
81
81
  |gotchas: {2-3 most critical pitfalls or breaking changes, inline}
82
82
  ```
83
83
 
84
+ The anchors point to the QS template's actual headings — `#usage-patterns` (Usage Patterns) and `#key-exports` (Key Exports). The QS template has no `## Quick Start` / `## Key Types` headings (those are Deep-tier sections), so the Deep-tier anchors `#quick-start` / `#key-types` would dangle. If the assembled SKILL.md is missing the referenced heading, omit that line rather than emit a dangling anchor.
85
+
84
86
  **If fewer than 5 exports:** Use all available exports.
85
87
  **If no exports:** Omit the api line.
86
88
  **If no gotchas known:** Omit the gotchas line.
@@ -84,7 +84,7 @@ npx skill-check check {skill_package} --fix --format json
84
84
  This validates frontmatter, description, body limits, links, and formatting; runs the security scan; and auto-fixes deterministic issues (field ordering, slug format, required fields, trailing newlines).
85
85
 
86
86
  **Parse JSON output** to extract:
87
- - `qualityScore` — overall score (0-100)
87
+ - `scores[].score` — overall score (0-100); match the entry by `relativePath`/`skillId` (older skill-check builds exposed this as a top-level `qualityScore`)
88
88
  - `diagnostics[]` — remaining issues after auto-fix
89
89
  - `fixed[]` — issues automatically corrected
90
90
  - `security[]` (when present) — security findings, recorded as advisory warnings (security issues do not block output)
@@ -94,7 +94,7 @@ Record quality score, remaining diagnostics, and security findings as validation
94
94
  **If skill-check is NOT available**, run the shared frontmatter validator instead of an LLM-walked checklist. Resolve `{frontmatterValidator}` from `{frontmatterValidatorProbeOrder}`; first existing path wins. If no candidate exists, log a high-severity issue ("frontmatter validator unavailable — both `npx skill-check` and `skf-validate-frontmatter.py` missing") and skip frontmatter validation.
95
95
 
96
96
  ```bash
97
- python3 {frontmatterValidator} {skill_package}/SKILL.md --skill-dir-name {repo_name}
97
+ uv run {frontmatterValidator} {skill_package}/SKILL.md --skill-dir-name {repo_name}
98
98
  ```
99
99
 
100
100
  The validator emits JSON with `status` (`pass`/`fail`), `issues[]` (each with `severity`, `code`, `message`), and `frontmatter` (the parsed name/description). It checks frontmatter delimiters, name format (Unicode letters + digits + hyphens, no consecutive/trailing hyphens), name-directory match, description presence and length, and unknown fields against the agentskills.io spec — the same shape this step would otherwise hand-walk. Record each `issues[]` entry as a validation issue with its reported severity. Missing frontmatter or missing required fields are high-severity — skills without valid frontmatter will fail `npx skills add` and `npx skill-check check`.
@@ -49,7 +49,7 @@ These rules apply to every step in this workflow:
49
49
  | Aspect | Detail |
50
50
  |--------|--------|
51
51
  | **Inputs** | architecture_doc_path [required], vs_report_path [optional] |
52
- | **Flags** | `--headless` / `-H` (auto-resolve all gates); `--architecture-doc <path>` (skip step 1 prompt for the required input); `--vs-report-path <path>` (skip step 1 prompt for the optional VS report) |
52
+ | **Flags** | `--headless` / `-H` (auto-resolve all gates); `--architecture-doc <path>` (skip step 1 prompt for the required input); `--vs-report-path <path>` (skip step 1 prompt for the optional VS report); `--scope-skills <names>` (comma-separated in-scope skill names; overrides scope derivation in gap analysis) |
53
53
  | **Gates** | step 1: Input Gate [use args] | step 5: Review Gate [C] |
54
54
  | **Outputs** | `refined-architecture-{project_name}.md` at `{outputFolderPath}`, plus `refine-architecture-result-{timestamp}.json` and `refine-architecture-result-latest.json` |
55
55
  | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Per-flag args (`--architecture-doc`, `--vs-report-path`) consumed at the gates that would otherwise prompt. |
@@ -42,6 +42,21 @@ Parse the architecture document for statements describing two or more technologi
42
42
  - Each pair: `{library_a, library_b, architectural_context}`
43
43
  - `architectural_context`: the quoted text or paraphrased description of their relationship
44
44
 
45
+ ### 2b. Establish Document Scope
46
+
47
+ The skill inventory (Step 01 §2) can span a wider product surface than the architecture document under refinement. Pairs drawn from a different surface are NOT actionable gaps for THIS document — surfacing them injects irrelevant integration recommendations (e.g. wiring real-time A/V libraries into an admin-dashboard architecture).
48
+
49
+ Resolve the in-scope skill set:
50
+
51
+ - **If `{scope_skills}` was provided** (via `--scope-skills`, resolved in Step 01 §1): use it verbatim as `{in_scope_skills}` — it is authoritative.
52
+ - **Otherwise derive:** `{in_scope_skills}` = every inventory skill whose name or primary technology is referenced anywhere in the architecture document (reuse the technology references parsed in §2; match on skill name and library/technology keywords, case-insensitive, word-boundary). Be conservative — when a skill's relevance is ambiguous, treat it as **in-scope**. Surfacing a borderline gap is safer than burying a real one.
53
+
54
+ `{out_of_scope_skills}` = inventory skills not in `{in_scope_skills}`. A library pair is **out-of-scope** when either of its libraries is in `{out_of_scope_skills}`.
55
+
56
+ **Safe default:** If scope cannot be derived (e.g. the architecture references no inventory skill by name) and no `{scope_skills}` was provided, treat ALL skills as in-scope and note: "Could not derive document scope — analyzing all skill pairs." This preserves prior behavior rather than hiding gaps.
57
+
58
+ Store `{in_scope_skills}` and `{out_of_scope_skills}` as workflow state — Step 03 (issue detection) reuses them.
59
+
45
60
  ### 3. Generate All Possible Library Pairs
46
61
 
47
62
  From the skill inventory, generate all unique combinations of library pairs.
@@ -89,7 +104,9 @@ For each possible library pair NOT already documented in the architecture:
89
104
  - Do both libraries share a compatible protocol or data format?
90
105
  - Are they in the same language or is there a bridge mechanism available?
91
106
 
92
- **If compatible APIs exist but NO architecture mention:**
107
+ **Scope routing (from §2b):** Before classifying, check the pair's scope. If the pair is **out-of-scope** (either library is in `{out_of_scope_skills}`), do NOT add it to the gap list even when its APIs are compatible record it in the informational **Out-of-Scope** bucket instead (a compatible pair that belongs to a different product surface than this architecture). Only **in-scope** pairs proceed to gap classification below.
108
+
109
+ **If compatible APIs exist (in-scope pair) but NO architecture mention:**
93
110
  - Classify the gap type (Missing Integration Path, Undocumented Data Flow, or Absent Bridge Layer)
94
111
  - Document the connecting APIs from both skills
95
112
  - Propose a brief architecture section describing the integration
@@ -112,7 +129,7 @@ Suggestion: {proposed architecture section content}
112
129
 
113
130
  "**Pass 1: Gap Analysis — Undocumented Integration Paths**
114
131
 
115
- **Gaps Found:** {count}
132
+ **Gaps Found (in-scope):** {count}
116
133
 
117
134
  {IF gaps found:}
118
135
  | # | Library A | Library B | Gap Type | Connecting APIs |
@@ -121,15 +138,24 @@ Suggestion: {proposed architecture section content}
121
138
 
122
139
  {For each gap, display the full citation with evidence and suggestion}
123
140
 
141
+ {IF out-of-scope compatible pairs exist (from §2b/§5):}
142
+ **Out of scope for this document:** {oos_count} compatible pair(s) involve skills outside this architecture's surface — `{out_of_scope_skills}` — and were NOT counted as gaps. Listed for awareness only:
143
+
144
+ | Library A | Library B | Reason |
145
+ |-----------|-----------|--------|
146
+ | {lib_a} | {lib_b} | out-of-scope — not referenced in this architecture |
147
+
148
+ If any of these belongs in this architecture, re-run with `--scope-skills` naming the skills to include.
149
+
124
150
  {IF no gaps found AND N > 1:}
125
- **No undocumented integration paths detected.** The architecture document covers all compatible library pairs.
151
+ **No undocumented integration paths detected.** The architecture document covers all compatible in-scope library pairs.
126
152
 
127
153
  {IF no gaps found AND N == 1:}
128
154
  **⚠️ Gap analysis skipped — only 1 skill loaded.** Pairwise integration analysis requires at least 2 skills. If the architecture references multiple libraries, those without a matching skill are invisible to gap analysis. **Recommendation:** Generate skills for all architecture libraries with [CS] or [QS] before running [RA] for comprehensive gap detection.
129
155
 
130
156
  **Proceeding to issue detection (still produces value with 1 skill)...**"
131
157
 
132
- Store all gap findings as workflow state for Step 05. To ensure durability across long runs, also append a `<!-- [RA-GAPS] ... -->` comment block to `{forge_data_folder}/ra-state-{project_name}.md` containing the **complete formatted gap findings** (full citation blocks with evidence and suggestions, not just counts) — Step 05 can read this back if context degrades. **Do NOT write to `{output_folder}/refined-architecture-{project_name}.md` — that file is created only in step 5.**
158
+ Store all **in-scope** gap findings as workflow state for Step 05. To ensure durability across long runs, also append a `<!-- [RA-GAPS] ... -->` comment block to `{forge_data_folder}/ra-state-{project_name}.md` containing the **complete formatted gap findings** (full citation blocks with evidence and suggestions, not just counts) — Step 05 can read this back if context degrades. Record out-of-scope pairs under a separate `<!-- [RA-OUT-OF-SCOPE] ... -->` marker (NOT `[RA-GAPS]`) so Step 05 does not compile them into the refined document — they are informational only. **Do NOT write to `{output_folder}/refined-architecture-{project_name}.md` — that file is created only in step 5.**
133
159
 
134
160
  ### 7. Auto-Proceed to Next Step
135
161
 
@@ -51,6 +51,8 @@ Wait for user input. Store the validated architecture document path as `architec
51
51
  - If missing at user-provided path: attempt auto-probe (below) before giving up
52
52
  - Store VS report availability as `vs_report_available: true|false` and `vs_report_path`
53
53
 
54
+ **Scope hint (optional, `--scope-skills`):** If `--scope-skills <names>` was provided, store the parsed comma-separated list as `{scope_skills}` — gap analysis (Step 02 §2b) uses it as the authoritative in-scope skill set. If absent, leave `{scope_skills}` empty; Step 02 derives scope from the architecture document instead.
55
+
54
56
  ### 2. Scan Skills Folder
55
57
 
56
58
  **Resolve `{enumerateStackSkillsHelper}`** from `{enumerateStackSkillsProbeOrder}`; first existing path wins.
@@ -66,6 +66,8 @@ For each extracted claim, load the relevant skill(s) and check:
66
66
 
67
67
  If `vs_report_available` is true:
68
68
 
69
+ **Scope filter (reuse `{out_of_scope_skills}` from Step 02 §2b):** The VS report carries verdicts across the entire skill set, which may exceed this architecture's surface. Before promoting any verdict, check the pair's scope — if a verdict is for an **out-of-scope** pair (either library in `{out_of_scope_skills}`), do NOT promote it to an issue for THIS architecture; record it under the informational Out-of-Scope bucket instead. Only **in-scope** pairs' verdicts are promoted by the rules below.
70
+
69
71
  **Load the VS feasibility report and extract verdicts:**
70
72
  - **Risky verdicts** (match case-insensitively: "Risky", "RISKY", "risky"): Promote to confirmed issues with the VS evidence as additional citation
71
73
  - **Blocked verdicts** (match case-insensitively: "Blocked", "BLOCKED", "blocked"): Promote to critical issues requiring architecture redesign
@@ -100,7 +102,7 @@ Suggestion: {specific correction with API evidence}
100
102
 
101
103
  "**Pass 2: Issue Detection — Architecture vs. API Reality**
102
104
 
103
- **Issues Found:** {count} ({critical_count} critical, {major_count} major, {minor_count} minor)
105
+ **Issues Found (in-scope):** {count} ({critical_count} critical, {major_count} major, {minor_count} minor)
104
106
 
105
107
  {IF issues found:}
106
108
  | # | Libraries | Issue Type | Severity | Summary |
@@ -109,12 +111,15 @@ Suggestion: {specific correction with API evidence}
109
111
 
110
112
  {For each issue, display the full citation with evidence and suggestion}
111
113
 
114
+ {IF out-of-scope VS verdicts were set aside (from §4):}
115
+ **Out of scope for this document:** {oos_issue_count} VS verdict(s) involve skills outside this architecture's surface (`{out_of_scope_skills}`) and were NOT counted as issues. Listed for awareness only — re-run with `--scope-skills` if any belongs in scope.
116
+
112
117
  {IF no issues found:}
113
- **No contradictions detected.** Architecture claims align with verified skill API surfaces.
118
+ **No contradictions detected.** Architecture claims align with verified in-scope skill API surfaces.
114
119
 
115
120
  **Proceeding to improvement detection...**"
116
121
 
117
- Store all issue findings as workflow state for Step 05. To ensure durability across long runs, also append a `<!-- [RA-ISSUES] ... -->` comment block to `{forge_data_folder}/ra-state-{project_name}.md` containing the **complete formatted issue findings** (full citation blocks with architecture claims, skill evidence, VS verdicts, severity, and suggestions — not just counts) — Step 05 can read this back if context degrades. **Do NOT write to `{output_folder}/refined-architecture-{project_name}.md` — that file is created only in step 5.**
122
+ Store all **in-scope** issue findings as workflow state for Step 05. To ensure durability across long runs, also append a `<!-- [RA-ISSUES] ... -->` comment block to `{forge_data_folder}/ra-state-{project_name}.md` containing the **complete formatted issue findings** (full citation blocks with architecture claims, skill evidence, VS verdicts, severity, and suggestions — not just counts) — Step 05 can read this back if context degrades. Record any out-of-scope VS verdicts under the shared `<!-- [RA-OUT-OF-SCOPE] ... -->` marker (NOT `[RA-ISSUES]`) so Step 05 does not compile them — they are informational only. **Do NOT write to `{output_folder}/refined-architecture-{project_name}.md` — that file is created only in step 5.**
118
123
 
119
124
  ### 7. Auto-Proceed to Next Step
120
125
 
@@ -43,9 +43,9 @@ The first call returns `{ description: {satisfied, matched_synonym, tried[]}, us
43
43
 
44
44
  - `satisfied: true` → no finding.
45
45
  - `satisfied: false` AND family is `description` AND the SKILL.md frontmatter has a non-empty `description` field → no finding (the frontmatter alternative satisfies the family per the original rule).
46
- - Otherwise → **High severity** finding: `naive-coherence — missing required section: {family}` (the `tried[]` list from the JSON identifies which synonyms were checked: `Description`/`Overview`/`Purpose`/`Summary` for description; `Usage`/`Examples`/`How to use`/`Quickstart`/`Quick Start`/`Getting Started`/`Common Workflows` for usage; `API`/`API Surface`/`Exports`/`Public API`/`Interface`/`Reference`/`Key API Summary` for api_surface).
46
+ - Otherwise → **High severity** finding: `naive-coherence — missing required section: {family}` (the `tried[]` list from the JSON identifies which synonyms were checked: `Description`/`Overview`/`Purpose`/`Summary` for description; `Usage`/`Usage Patterns`/`Examples`/`How to use`/`Quickstart`/`Quick Start`/`Getting Started`/`Common Workflows`/`Adoption Steps` for usage; `API`/`API Surface`/`Exports`/`Key Exports`/`Public API`/`Interface`/`Reference`/`Key API Summary`/`Pattern Surface` for api_surface).
47
47
 
48
- The script matches case-insensitively and tolerates `##`/`###` heading levels. SKF-template skills' `## Quick Start`, `## Common Workflows`, and `## Key API Summary` are first-class synonyms baked into the script — they will surface with `satisfied: true` and the corresponding `matched_synonym` field.
48
+ The script matches case-insensitively and tolerates `##`/`###` heading levels. SKF-template skills' headings are first-class synonyms baked into the script — the Deep/create-skill `## Quick Start`, `## Common Workflows`, and `## Key API Summary`, the quick-skill `## Usage Patterns` and `## Key Exports`, and the reference-app overrides `## Adoption Steps` (usage) and `## Pattern Surface` (api_surface) so they all surface with `satisfied: true` and the corresponding `matched_synonym` field.
49
49
 
50
50
  **2.2 Code fence balance.** Read `unbalanced_fences` from the second JSON blob. **`true` → High severity** finding: `naive-coherence — unbalanced code fence (unclosed block)` (the JSON's `fence_count` may be cited in the detail).
51
51
 
@@ -54,7 +54,7 @@ The script matches case-insensitively and tolerates `##`/`###` heading levels. S
54
54
  **2.4 Exports cross-used in a usage-family section.** For each function name reported in the step 3 subagent inventory (`exports[].name` where `kind == "function"` or `kind == "method"`):
55
55
  - Determine the usage-family search scope:
56
56
  - **Single-body skill** (no `references/` directory, or `## Full*` sections carry real content): the span from §2.1's `matched_synonym` anchor to the next `^## ` anchor.
57
- - **Split-body skill** (a `references/` directory exists alongside SKILL.md AND the SKILL.md `## Full*` sections are stubs/pointers): the union of EVERY usage-family heading present in SKILL.md (`Usage`/`Examples`/`How to use`/`Quickstart`/`Quick Start`/`Getting Started`/`Common Workflows`/`Key API Summary`), each from its anchor to the next `^## ` anchor, PLUS the full text of every file under `references/`.
57
+ - **Split-body skill** (a `references/` directory exists alongside SKILL.md AND the SKILL.md `## Full*` sections are stubs/pointers): the union of EVERY usage-family heading present in SKILL.md (`Usage`/`Usage Patterns`/`Examples`/`How to use`/`Quickstart`/`Quick Start`/`Getting Started`/`Common Workflows`/`Adoption Steps`/`Key API Summary`/`Pattern Surface`/`Key Exports`), each from its anchor to the next `^## ` anchor, PLUS the full text of every file under `references/`.
58
58
  - `grep -c "{export.name}"` across that scope and sum the counts.
59
59
  - **Zero occurrences across the entire scope → High severity** finding: `naive-coherence — exported {kind} \`{name}\` is not referenced in any usage-family section or reference file`. This catches the "documented but unused" failure mode that trivially fails discovery testing. A method referenced in any usage-family section OR any `references/` file satisfies the check.
60
60
 
@@ -123,7 +123,7 @@ Parent strips wrapping markdown fences (if present) before parsing. If subagent
123
123
 
124
124
  4. **Scripts/assets directory check:** If a `scripts/` or `assets/` directory exists alongside SKILL.md, verify that a "Scripts & Assets" section (Section 7b) is present in SKILL.md. This directory-level check applies in both modes (naive mode performs it in Section 2; contextual mode performs it here alongside per-reference validation). Flag absence as Medium severity gap per `{scoringRulesFile}`.
125
125
 
126
- 5. **Path containment:** for every resolved reference target, compute its canonical path (`os.path.realpath`) and require that it lives inside `{skillDir}` OR inside `{source_path}` (the extraction tree recorded in metadata.json). References whose canonical path escapes both roots (e.g. `../../../etc/passwd`, absolute paths to unrelated dirs, symlink redirections outside the skill or its source) are **High severity** findings: `coherence — reference escapes skill/source sandbox: {raw_ref} → {canonical_path}`. Do NOT validate the target's contents for escaping references — the escape itself is the finding.
126
+ 5. **Path containment:** for every resolved reference target, compute its canonical path (`os.path.realpath`) and require that it lives inside `{skillDir}`, inside `{source_path}` (the extraction tree recorded in metadata.json), OR — for stack skills — inside `{skills_output_folder}`. The third root applies only here in contextual mode: a stack's constituent cross-references legitimately resolve to `skills/{name}/active` under `{skills_output_folder}`, which lies outside `{skillDir}`, and a stack's metadata.json records no single `source_path` to anchor them. References whose canonical path escapes all applicable roots (e.g. `../../../etc/passwd`, absolute paths to unrelated dirs, symlink redirections outside the skill, its source, or for a stack — the skills output tree) are **High severity** findings: `coherence — reference escapes skill/source sandbox: {raw_ref} → {canonical_path}`. Canonicalization happens before the root check, so a symlink that points outside every applicable root is still caught. Do NOT validate the target's contents for escaping references — the escape itself is the finding.
127
127
 
128
128
  ### 5. Contextual Mode: Check Integration Pattern Completeness
129
129
 
@@ -73,15 +73,15 @@ test-skill is a quality gate — it MUST NOT trust subagent output blindly. Befo
73
73
 
74
74
  1. Strip wrapping markdown fences before parsing. Subagents frequently return JSON wrapped in a code fence — a line of three backticks (optionally followed by a language tag like `json`) preceding the JSON and a closing line of three backticks after it — despite prompt instructions to return raw JSON. When the first non-empty line of the response is three backticks (optionally with a language tag) and the last non-empty line is three backticks, remove those two fence lines before parsing. Then parse the remaining content as JSON. On parse failure of the inner content → HALT "coverage-check: subagent response not valid JSON".
75
75
  2. Required keys present: `exports` (list), `cross_check_mismatches` (list — may be empty). Missing key or wrong type → HALT "coverage-check: subagent JSON schema invalid — missing/typo: {key}". Note: the parent already knows the skill name from workflow context (`{resolved_skill_package}` from step 1) — the subagent is not required to echo it back, and doing so introduces a contract-drift surface without improving verification.
76
- 3. Each `exports[]` entry must be a dict with at minimum `name` (non-empty string) and `kind` (one of `function|class|type|constant|hook|interface|method`). Reject entries violating this; if >0 rejections, HALT "coverage-check: subagent returned malformed export entries — {count} entries do not match schema".
76
+ 3. Each `exports[]` entry must be a dict with at minimum `name` (non-empty string) and `kind` (one of `function|class|type|constant|hook|interface|method|struct|enum|trait|macro|adapter`). The enum spans the constructs SKF actually documents across languages and skill types: JS/TS (`function`/`class`/`type`/`constant`/`hook`/`interface`/`method`), Rust public-API items (`struct`/`enum`/`trait`/`macro` — alongside the shared `type`/`constant`/`function`), and stack-composition scaffolds (`adapter`). Reject entries violating this; if >0 rejections, HALT "coverage-check: subagent returned malformed export entries — {count} entries do not match schema".
77
77
  4. `cross_check_mismatches[]` entries (when non-empty) must carry `export`, `skill_md_line`, `reference_file`, `reference_line`, `issue`. Missing fields → HALT.
78
78
 
79
79
  **Spot-check (ground-truth verification, zero-hallucination guard):**
80
80
 
81
81
  1. If `len(exports) == 0`: skip the spot-check (no names to verify). Zero-exports policy is handled in the §2b zero-exports guard.
82
82
  2. Otherwise, sample `min(3, len(exports))` exports deterministically — by default take indices `[0, len//2, len-1]` (first, middle, last) from the `exports` array after a stable sort by `name`.
83
- 3. For each sampled export, run: `grep -n "{export.name}" {resolved_skill_package}/SKILL.md` in the parent context. The name MUST appear at least once.
84
- 4. If ANY sampled name returns zero matches, HALT "coverage-check: subagent inventory failed ground-truth spot-check — `{name}` claimed as export but absent from SKILL.md".
83
+ 3. For each sampled export, grep for the name across SKILL.md **and every reference file the subagent listed in its `references[]` array** (the documented surface of a split-body skill spans both): `grep -n "{export.name}" {resolved_skill_package}/SKILL.md {resolved_skill_package}/{each references[] path}` in the parent context. The name MUST appear at least once somewhere in that file set. Greping SKILL.md alone would false-HALT a split-body skill whose sampled export is documented only in a `references/*.md` file (a legitimate placement per §1 step 2 and the split-body note below).
84
+ 4. If a sampled name returns zero matches across SKILL.md **and** all listed reference files, HALT "coverage-check: subagent inventory failed ground-truth spot-check — `{name}` claimed as export but absent from SKILL.md and the listed reference files".
85
85
 
86
86
  These checks catch two hallucination classes: schema-shape drift (subagent paraphrased or dropped the contract) and fabricated exports (subagent invented names not in the document). Both are disqualifying for a grader skill — do not downgrade to a warning.
87
87
 
@@ -130,9 +130,12 @@ Start from the package entry point (see 0b) and identify the public API surface.
130
130
  - Cannot verify signatures — note as "unverified" in report
131
131
 
132
132
  **Forge Tier (ast-grep available):**
133
+
134
+ **Before delegating, the parent builds a `documented_signatures` map** from the §1 inventory: `{ "{name}": {"params": "...", "return_type": "..."} }` for every documented export that carries a signature. Pass this map as a structured input alongside the per-file ast-grep instructions. Without it the subagent has only the bare source — it cannot diff against the documented signatures, so `signature_mismatches[]` collapses to empty and Signature Accuracy defaults to 100% on a name-only pass (a silent false positive). The subagent must populate `documented_sig` from this map, not invent it.
135
+
133
136
  For EACH source file that defines public API exports, delegate to a subagent that:
134
- 1. Uses ast-grep to extract all exported symbols with their full signatures
135
- 2. Matches each export against the documented inventory
137
+ 1. Uses ast-grep to extract all exported symbols with their full signatures (the `source_sig`)
138
+ 2. Matches each export against the `documented_signatures` map supplied by the parent, comparing params (name, type, order, optionality) and return type
136
139
  3. Returns ONLY the JSON object below — no prose, no commentary, no markdown fences:
137
140
 
138
141
  ```json
@@ -164,7 +167,16 @@ Parent strips wrapping markdown fences (if present) before parsing, same as §1a
164
167
 
165
168
  After the source-code analysis (§2) completes, compute `total_exports` — the count of exports discovered in the source / provenance-map / metadata.json, per the stratified-scope and State 2 rules resolved in §4.
166
169
 
167
- **If `total_exports == 0` AND `docs_only_mode == false`:** HALT with:
170
+ **Stack-skill branch (`metadata.json.skill_type == "stack"`):** A stack skill's own barrel is empty by design — it composes constituent skills rather than exporting a proprietary surface — so `total_exports` derived from its own barrel is `0` for a *correctly* built stack, and its `[from skill: …]` citations never trip §0's `[EXT:…]`-only docs-only trigger. The zero-exports HALT below targets individual source-based skills and must NOT fire for stacks. Derive the stack's coverage denominator (`stack_denominator`) from its composition surface, in priority order, and use it as `total_exports` for the rest of coverage scoring:
171
+
172
+ 1. Provenance-map cited-contract count — when `{forge_data_folder}/{skill_name}/provenance-map.json` exists **and its `entries[]` is non-empty**: count the named cited contracts, **excluding entries whose `export_name` contains `::`** (impl-block methods roll up under an already-counted type). Use the same exclusion as §4b's named-export rule so the §2b and §4b stack denominators agree.
173
+ 2. Otherwise the composition surface from `metadata.json`: `len(libraries) + len(integration_pairs)`.
174
+
175
+ **If `stack_denominator == 0`** (no provenance-map or empty `entries[]`, AND `libraries` and `integration_pairs` are both empty): HALT with `Error: stack composition surface empty — {skill_name} cites no contracts, libraries, or integration pairs, so Export Coverage is undefined. Verify the stack was compiled from at least one constituent skill.` Do not write the Coverage Analysis section; this is an indeterminate state, not a FAIL.
176
+
177
+ Otherwise carry `stack_denominator` forward as `total_exports`, skip the HALT below, and continue — §2c and §4b consume this same denominator via their own stack-skill branches. (Stacks route to contextual mode per `detect-mode.md` and have a dedicated section in `scoring-rules.md`, so they are first-class — the indeterminate-surface HALT is an individual-skill guard only.)
178
+
179
+ **If `total_exports == 0` AND `docs_only_mode == false` AND `metadata.json.skill_type != "stack"`:** HALT with:
168
180
 
169
181
  ```
170
182
  Error: indeterminate API surface — 0 exports discovered in source for {skill_name}.
@@ -183,6 +195,24 @@ Do not write the Coverage Analysis section. Do not proceed to scoring. This is a
183
195
 
184
196
  **If `docs_only_mode == true` and the documented inventory is empty:** HALT with the analogous docs-only message ("docs-only skill declares zero items — no API surface to test").
185
197
 
198
+ ### 2c. Reconcile Documented vs Source Surface (Deterministic Intersection)
199
+
200
+ On a split-body skill the §1 inventory (documented surface) and the §2 AST output (source barrel) are two independent lists, so the `Documented` count must be their **intersection**, not a parent estimate. Compute three sets deterministically so the Export Coverage numerator is reproducible across runs and reviewers:
201
+
202
+ **Stack-skill branch (`metadata.json.skill_type == "stack"`):** A stack's source barrel is empty by design, so `barrel_set` is `{}` and the `|documented_set ∩ barrel_set|` / `|barrel_set|` formulas in steps 3–4 would divide by zero. Do NOT use the source barrel for a stack. Instead use the §2b `stack_denominator` as the denominator and compute the numerator by full-grep verification (same mechanism as the scalar-denominator branch below): enumerate the stack's composition-surface names — the provenance-map cited-contract names (`::`-excluded) when the map exists and is non-empty, else the `libraries` and `integration_pairs` names — and for each, grep across `SKILL.md ∪ references/*.md`; `Documented` := the count that appear at least once. Set `Missing` := `stack_denominator − Documented` and omit `Stale` (no source barrel to enumerate against). Carry these into §3/§4 with `Export Coverage = Documented / stack_denominator * 100`, and skip steps 1–4 below.
203
+
204
+ 1. **`documented_set`** := the de-duplicated set of `name` from the §1 inventory `exports[]`, excluding `kind: "method"` (methods are members of an already-counted class/type, not top-level barrel exports).
205
+ 2. **`barrel_set`** := the union of `exports_found[]` across every §2 per-file result (the actual source public surface). When a stratified-scope or State-2 denominator applies (see §4) **and it resolves to an enumerated name set** — the priority-2/3 re-derivation from `scope.tier_a_include` / `scope.include` globs — `barrel_set` is that resolved denominator's name set instead of the raw union.
206
+
207
+ **Scalar-denominator branch (§4 priority 1):** when the resolved denominator is the scalar `metadata.json.stats.effective_denominator` (a count with **no enumerated name set**), there is no `barrel_set` to intersect against — the `documented_set ∩ barrel_set` formula in step 3 does not apply. Instead compute the numerator by full-grep verification: for each name in `documented_set`, grep it across `SKILL.md ∪ references/*.md`; `Documented` := the count of `documented_set` names that appear at least once. Use `effective_denominator` directly as the denominator, set `Missing` := `effective_denominator − Documented`, and omit `Stale` (not enumerable without a barrel name set). If §4b's numerator-ground-truth arm fires (it triggers only when `exports_documented == effective_denominator`), its verified count is authoritative and **overrides** this numerator — do not apply both.
208
+ 3. Compute (**enumerated path only** — skip when the scalar-denominator branch above applies):
209
+ - `Documented` := `|documented_set ∩ barrel_set|`
210
+ - `Missing` := `barrel_set − documented_set` (in source, not documented)
211
+ - `Stale` := `documented_set − barrel_set` (documented, not in source)
212
+ 4. Carry the resulting counts into §3's table and §4's Export Coverage: `Export Coverage = |Documented| / |barrel_set| * 100` (enumerated path) or `Documented / effective_denominator * 100` (scalar-denominator branch). Record the counts in the Coverage Analysis section so the numerator is auditable.
213
+
214
+ This removes the parent-side guess that otherwise swings the documented count between runs (e.g., "85 from the AST agent" vs "~120 from a hand intersection") and can cross the PASS threshold on split-body skills.
215
+
186
216
  ### 3. Build Coverage Results
187
217
 
188
218
  Aggregate findings across all source files:
@@ -193,12 +223,12 @@ Aggregate findings across all source files:
193
223
  |--------|------|-----------|-----------------|-----------|--------|
194
224
  | {name} | function/class/type | yes/no | yes/no/unverified | src/file.ts:42 | PASS/FAIL/WARN |
195
225
 
196
- **Summary counts:**
197
- - Total exports in source: {N}
198
- - Documented in SKILL.md: {N}
199
- - Missing documentation: {N}
226
+ **Summary counts** (from the §2c reconciliation — not re-estimated here):
227
+ - Total exports in source: `|barrel_set|`
228
+ - Documented in SKILL.md: `Documented` (`|documented_set ∩ barrel_set|`)
229
+ - Missing documentation: `|Missing|`
200
230
  - Signature mismatches: {N}
201
- - Undocumented in SKILL.md but not in source (stale docs): {N}
231
+ - Undocumented in SKILL.md but not in source (stale docs): `|Stale|`
202
232
 
203
233
  ### 4. Load Scoring Rules
204
234
 
@@ -210,8 +240,8 @@ Load `{scoringRulesFile}` to determine category scores:
210
240
 
211
241
  **Stratified-scope denominator (monorepo curated subsets):** Before computing Export Coverage, check whether the Source Access Protocol's stratified-scope clause applies to this skill (see `{sourceAccessProtocol}` §Source API Surface Definition — "Stratified-scope monorepo packages"). When it applies:
212
242
 
213
- 1. **Prefer `metadata.json.stats.effective_denominator`** when present. Use it directly as `total_exports`.
214
- 2. **Otherwise re-derive at test time** from the brief's scope globs per the protocol. When the brief supplies `scope.tier_a_include`, re-derive from that narrower list; otherwise re-derive from `scope.include`. Use the resulting union count as `total_exports`.
243
+ 1. **Prefer `metadata.json.stats.effective_denominator`** when present. Use it directly as `total_exports` — but apply the **denominator deflation guard** from `{sourceAccessProtocol}` stratified-scope resolution step 1: when source is readable, re-derive the source barrel and, if it exceeds `effective_denominator` by >25% with no `scope.tier_a_include`, treat the stored value as deflated, use the re-derived count, and emit the `denominator deflation` gap.
244
+ 2. **Otherwise re-derive at test time** from the brief's scope globs per the protocol. When the brief supplies `scope.tier_a_include`, re-derive from that narrower list (excluding umbrella barrel files per the protocol's umbrella-barrel note); otherwise re-derive from `scope.include`. Use the resulting union count as `total_exports`.
215
245
  3. **Run the denominator inflation check** defined in `{sourceAccessProtocol}` stratified-scope resolution step 3 whenever re-derivation fell back to `scope.include`. If the `scope.include` union exceeds the provenance-map entry count by more than 25%, emit the Medium-severity `denominator inflation — coarse scope.include union exceeds authored surface` gap and append it to the Coverage Analysis gap list.
216
246
  4. **Apply provenance-map canonicalization** before intersecting documented exports against the raw provenance-map entry list — see `{sourceAccessProtocol}` §Source API Surface Definition → "Provenance-map canonicalization" for the folding rules (`_def`/`_exact` suffix, `a11y_` prefix, renderer-prefix disambiguation). Skip folding when `metadata.json.stats.effective_denominator` is present and already equals the raw provenance-map entry count. Record the fold summary in the Coverage Analysis section so it's auditable.
217
247
 
@@ -242,6 +272,10 @@ suspects denominator gaming has the evidence inline.
242
272
 
243
273
  After the denominator has been resolved (standard, stratified, or State 2), cross-check export counts *within each semantic cluster* to detect extraction drift without false-positiving on intentional multi-denominator reporting. Picking the denominator silently when sources disagree is a known friction — the tester cannot tell whether to trust the pick, ignore the drift, or report it. Make it explicit, but only for counts that are authored to measure the *same* surface.
244
274
 
275
+ **Stack-skill branch (`metadata.json.skill_type == "stack"`):** Skip the intra-cluster and cross-cluster count comparisons, the `confidence_distribution` sum check, AND the numerator ground-truth check below — none apply to a stack. For a stack the three counts measure intentionally *different* surfaces, so comparing them yields only false drift: `exports_documented` / `exports[]` measure the stack's own barrel (empty by design → `0`), the provenance-map enumerates the cited *constituent* contracts, and `confidence_distribution` bins those constituents — so for a stack treat `confidence_distribution` as a per-constituent count (it sums to the constituent count, not to `exports_documented`) and do not assert it against `exports_documented`. The numerator-inflation arm below targets an *individual* skill whose `exports_documented` was padded to equal `effective_denominator`; a stack's numerator is instead computed by full-grep in §2c's stack-skill branch and stack `metadata.json.stats` carries no `effective_denominator`, so the arm is not run. Record the denominator using the §2b source — `Denominator: stack composition ({N} cited contracts)` when the provenance-map supplied it, or `Denominator: stack composition ({N} libraries + integration pairs)` when the composition surface did — then proceed.
276
+
277
+ **Reference-app branch (`metadata.json.scope_type == "reference-app"`):** Skip the intra-cluster and cross-cluster count comparisons, the `confidence_distribution` sum check, AND the numerator ground-truth check below — none apply to a reference app, whose counts measure intentionally *different* surfaces. A reference app documents wiring/construct **pattern surfaces**, not a library export barrel, so `metadata.json.exports[]` is empty by design (`0`), `stats.exports_public_api` / `stats.pattern_surfaces_documented` count the documented pattern surfaces, and `confidence_distribution` bins the per-citation provenance entries (it sums to the citation count, not to `pattern_surfaces_documented`). Comparing these yields only false drift: a spurious Cluster-A "barrel drift" (`exports_public_api` vs `exports[].length == 0`) and a Cluster-B "documented-surface drift" (`pattern_surfaces_documented` vs the larger `confidence_distribution` sum). The numerator-inflation arm also does not apply — a reference app carries no `effective_denominator` (see the `skf-create-skill` reference-app carve-out), so the `exports_documented == effective_denominator` signature never fires. Record the denominator as `Denominator: pattern-surface ({pattern_surfaces_documented})` and proceed. (`referenceApp` and a stack skill are distinct signals — a skill is one or the other, never both, so only one of these two branches applies.)
278
+
245
279
  **Collect available counts (skip any that are absent) and bin them into two clusters:**
246
280
 
247
281
  **Cluster A — public-barrel surface** (what `__init__.py` / `index.ts` / `lib.rs` re-exports):
@@ -252,11 +286,12 @@ After the denominator has been resolved (standard, stratified, or State 2), cros
252
286
  **Cluster B — documented surface** (what was extracted and documented, including methods and submodule members):
253
287
 
254
288
  3. `metadata.json.stats.exports_documented` — the declared documented count
255
- 4. Provenance-map entry count (if `{forge_data_folder}/{skill_name}/provenance-map.json` exists)
289
+ 4. Provenance-map **named-export count** (if `{forge_data_folder}/{skill_name}/provenance-map.json` exists) — count only top-level named exports: **exclude entries whose `export_name` contains `::`** (impl-block methods like `Type::method`, which roll up under an already-counted type and are not separate barrel exports). Comparing the raw entry count instead false-positives on any method-enumerating provenance map (common for Rust/TS type-heavy skills): e.g. a map with 88 named exports + 48 `Type::method` entries reports 136 against `exports_documented` ≈ 92 → a spurious ~32% Cluster-B drift, while the comparable named-export count (88) agrees within ~4%.
290
+ 5. `confidence_distribution` sum (`t1 + t1_low + t2 + t3`, when present in `metadata.json.stats`) — every extracted/documented export is binned into exactly one confidence tier, so the distribution must sum to the documented-surface total; a divergence (e.g., distribution sums to 91 while `exports_documented` is 85) is an internal-consistency defect even when the two clusters look fine
256
291
 
257
292
  Cluster assignment is canonical: `skf-create-skill` step 5 derives `exports_public_api` from entry-point validation and writes the `exports[]` array from the same barrel surface (see `skf-create-skill/references/compile.md:105`), while `exports_documented` tracks the broader documented surface that the provenance-map also enumerates.
258
293
 
259
- **Intra-cluster divergence (Medium):** For each cluster, if two counts are present and disagree by more than 10% of the larger, emit a **Medium**-severity gap titled `metadata drift — {cluster} export counts diverge` (substitute `barrel` for Cluster A, `documented-surface` for Cluster B). Enumerate the offending counts in the gap body (e.g., `stats.exports_public_api=55, exports[].length=48` → 13% drift). This is the real drift signal — the two sources should mirror the same surface and they don't, so upstream extraction or compilation produced inconsistent output that a re-compile should reconcile. Classify under structural/metadata coherence regardless of naive/contextual mode.
294
+ **Intra-cluster divergence (Medium):** For each cluster, if two or more counts are present and the largest and smallest disagree by more than 10% of the larger, emit a **Medium**-severity gap titled `metadata drift — {cluster} export counts diverge` (substitute `barrel` for Cluster A, `documented-surface` for Cluster B). Enumerate the offending counts in the gap body (e.g., `stats.exports_public_api=55, exports[].length=48` → 13% drift). This is the real drift signal — the two sources should mirror the same surface and they don't, so upstream extraction or compilation produced inconsistent output that a re-compile should reconcile. Classify under structural/metadata coherence regardless of naive/contextual mode.
260
295
 
261
296
  **Cross-cluster divergence (Info):** After intra-cluster checks, if both clusters resolved to a representative count (pick the higher of each cluster's available counts) and the two cluster values differ by more than 10%, append a single **Info**-severity note titled `multi-denominator reporting — barrel vs documented surface` with both values (e.g., `barrel=55, documented=114`). This is expected for skills whose documented surface intentionally exceeds the barrel (methods, submodule members, re-exported classes) — it is not drift. The note exists so the test report makes the dual-denominator design visible and auditable without demanding action.
262
297
 
@@ -266,7 +301,14 @@ Cluster assignment is canonical: `skf-create-skill` step 5 derives `exports_publ
266
301
 
267
302
  **When only one count is available across both clusters:** Skip silently — there is nothing to cross-check.
268
303
 
269
- Append any findings (Medium gaps and/or the Info note) to the Coverage Analysis section's gap list (built in section 5) so they surface in the final test report alongside coverage and signature findings. Findings are informational about data quality they do not change the denominator chosen above.
304
+ **Numerator ground-truth force a full grep on the inflation signature:** The intra/cross-cluster checks above only compare *counts*; they cannot tell whether the declared documented exports actually appear in the skill. When `metadata.json.stats.exports_documented == effective_denominator` exactly (the numerator equals the denominator — the signature of a numerator inflated to match the full surface), do **not** trust the documented count. Grep every declared export name (the full `metadata.exports[]` / provenance-map declared set — not the §1a 3-sample) against `SKILL.md ∪ references/*.md`. The count of declared names that actually appear is the **verified numerator**:
305
+
306
+ - If verified == declared, the skill is genuinely fully documented — no finding; coverage stands.
307
+ - If verified < declared, emit a **High**-severity gap `numerator inflation — {declared − verified} of {declared} declared exports absent from SKILL.md/references` listing the absent names, and use the verified count as the Export Coverage numerator (overriding `exports_documented`). A numerator padded to equal the denominator otherwise produces a tautological 100% that passes the gate.
308
+
309
+ The full grep runs only on the exact equality signature, so it adds no cost to the common case where the numerator is already below the denominator. Unlike the count-coherence findings above, this arm is authoritative — it changes the numerator used for scoring.
310
+
311
+ Append any findings (Medium gaps, the Info note, and/or the High numerator-inflation gap) to the Coverage Analysis section's gap list (built in section 5) so they surface in the final test report alongside coverage and signature findings. The count-coherence findings are informational about data quality and do not change the denominator chosen above; the numerator ground-truth arm is the one exception that overrides the numerator.
270
312
 
271
313
  ### 5. Append Coverage Analysis to Output
272
314