bmad-module-skill-forge 1.5.1 → 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 (87) 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-description-guard.py +33 -80
  10. package/src/shared/scripts/skf-detect-language.py +34 -0
  11. package/src/shared/scripts/skf-detect-workspaces.py +54 -0
  12. package/src/shared/scripts/skf-enumerate-stack-skills.py +107 -16
  13. package/src/shared/scripts/skf-forge-tier-rw.py +4 -1
  14. package/src/shared/scripts/skf-manifest-ops.py +8 -4
  15. package/src/shared/scripts/skf-merge-ccc-exclusions.py +4 -1
  16. package/src/shared/scripts/skf-qmd-classify-collections.py +29 -3
  17. package/src/shared/scripts/skf-rebuild-managed-sections.py +72 -7
  18. package/src/shared/scripts/skf-scan-skill-md-structure.py +13 -3
  19. package/src/shared/scripts/skf-validate-brief-inputs.py +79 -18
  20. package/src/shared/scripts/skf-validate-brief-schema.py +47 -1
  21. package/src/shared/scripts/skf-validate-frontmatter.py +60 -2
  22. package/src/shared/scripts/skf-write-skill-brief.py +97 -17
  23. package/src/skf-analyze-source/assets/skill-brief-schema.md +10 -2
  24. package/src/skf-analyze-source/references/continue.md +3 -1
  25. package/src/skf-analyze-source/references/generate-briefs.md +30 -9
  26. package/src/skf-analyze-source/references/recommend.md +1 -1
  27. package/src/skf-brief-skill/SKILL.md +4 -2
  28. package/src/skf-brief-skill/assets/skill-brief-schema.md +6 -1
  29. package/src/skf-brief-skill/references/analyze-target.md +22 -6
  30. package/src/skf-brief-skill/references/confirm-brief.md +4 -0
  31. package/src/skf-brief-skill/references/gather-intent.md +72 -3
  32. package/src/skf-brief-skill/references/headless-args.md +3 -2
  33. package/src/skf-brief-skill/references/portfolio-similarity-check.md +16 -8
  34. package/src/skf-brief-skill/references/scope-definition.md +20 -2
  35. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  36. package/src/skf-brief-skill/references/write-brief.md +12 -2
  37. package/src/skf-create-skill/assets/compile-assembly-rules.md +20 -1
  38. package/src/skf-create-skill/assets/skill-sections.md +2 -1
  39. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +18 -5
  40. package/src/skf-create-skill/references/compile.md +5 -2
  41. package/src/skf-create-skill/references/extract.md +8 -3
  42. package/src/skf-create-skill/references/extraction-patterns.md +14 -5
  43. package/src/skf-create-skill/references/generate-artifacts.md +5 -1
  44. package/src/skf-create-skill/references/source-resolution-protocols.md +5 -2
  45. package/src/skf-create-skill/references/validate.md +4 -3
  46. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +3 -3
  47. package/src/skf-create-stack-skill/assets/stack-skill-template.md +56 -0
  48. package/src/skf-create-stack-skill/references/compile-stack.md +13 -2
  49. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -0
  50. package/src/skf-create-stack-skill/references/detect-integrations.md +5 -2
  51. package/src/skf-create-stack-skill/references/generate-output.md +49 -9
  52. package/src/skf-create-stack-skill/references/validate.md +8 -5
  53. package/src/skf-export-skill/references/load-skill.md +5 -4
  54. package/src/skf-export-skill/references/manifest-rebuild.md +1 -1
  55. package/src/skf-export-skill/references/token-report.md +2 -2
  56. package/src/skf-export-skill/references/update-context.md +25 -11
  57. package/src/skf-quick-skill/assets/skill-template.md +2 -2
  58. package/src/skf-quick-skill/references/compile.md +4 -2
  59. package/src/skf-quick-skill/references/write-and-validate.md +2 -2
  60. package/src/skf-refine-architecture/SKILL.md +1 -1
  61. package/src/skf-refine-architecture/references/gap-analysis.md +30 -4
  62. package/src/skf-refine-architecture/references/init.md +2 -0
  63. package/src/skf-refine-architecture/references/issue-detection.md +8 -3
  64. package/src/skf-test-skill/SKILL.md +1 -1
  65. package/src/skf-test-skill/references/coherence-check.md +4 -4
  66. package/src/skf-test-skill/references/coverage-check.md +58 -16
  67. package/src/skf-test-skill/references/external-validators.md +7 -7
  68. package/src/skf-test-skill/references/init.md +5 -4
  69. package/src/skf-test-skill/references/score.md +6 -3
  70. package/src/skf-test-skill/references/scoring-rules.md +12 -0
  71. package/src/skf-test-skill/references/source-access-protocol.md +19 -3
  72. package/src/skf-test-skill/scripts/compute-score.py +4 -1
  73. package/src/skf-update-skill/references/detect-changes.md +15 -2
  74. package/src/skf-update-skill/references/init.md +8 -1
  75. package/src/skf-update-skill/references/merge.md +22 -0
  76. package/src/skf-update-skill/references/re-extract.md +19 -7
  77. package/src/skf-update-skill/references/write.md +3 -0
  78. package/src/skf-verify-stack/SKILL.md +2 -2
  79. package/src/skf-verify-stack/assets/feasibility-report-template.md +1 -1
  80. package/src/skf-verify-stack/references/coverage.md +35 -6
  81. package/src/skf-verify-stack/references/init.md +6 -2
  82. package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
  83. package/src/skf-verify-stack/references/integrations.md +7 -3
  84. package/src/skf-verify-stack/references/report.md +5 -1
  85. package/src/skf-verify-stack/references/requirements.md +3 -1
  86. package/src/skf-verify-stack/references/synthesize.md +11 -3
  87. package/tools/cli/lib/installer.js +1 -0
@@ -6,9 +6,11 @@ Every shell snippet in this document uses `{...}` placeholders for paths. **Alwa
6
6
 
7
7
  ## Tag Resolution
8
8
 
9
- Tag resolution maps a declared version in the brief onto a concrete git ref before cloning, so the skill is built from code matching its declared version. Two paths trigger tag resolution: an **explicit** `brief.target_version` (deliberate user intent) or an **implicit** `brief.version` (auto-populated hint from `brief-skill`). Both apply only when `source_repo` is a remote URL.
9
+ Tag resolution maps a declared version in the brief onto a concrete git ref before cloning, so the skill is built from code matching its declared version. Three signals can drive it, in priority order: an explicit `brief.target_ref` (a ref the user states verbatim — highest priority), an **explicit** `brief.target_version` (deliberate user intent), or an **implicit** `brief.version` (auto-populated hint from `brief-skill`). All apply only when `source_repo` is a remote URL.
10
10
 
11
- **When neither `brief.target_version` nor `brief.version` is set:** skip tag resolution entirely. Set `source_ref` to `HEAD` (default branch).
11
+ **Explicit ref override (when `target_ref` is set):** When `brief.target_ref` is present AND `source_repo` is a remote URL, use its value verbatim as `source_ref` and skip all version-to-tag matching below. This is the escape hatch for ref conventions the matching heuristics don't cover — notably monorepo crate tags whose prefix differs from the skill name (e.g. skill `livekit-rust` built from tag `livekit/v0.7.42`). Confirm it resolves first: `git ls-remote "{source_repo}" "{target_ref}"` (matches a tag or branch) — if it returns nothing, ⚠️ warn "`target_ref` ({target_ref}) does not resolve in {source_repo}; falling back to version matching" and continue with the version-based matching below.
12
+
13
+ **When none of `brief.target_ref`, `brief.target_version`, or `brief.version` is set:** skip tag resolution entirely. Set `source_ref` to `HEAD` (default branch).
12
14
 
13
15
  ### Explicit Tag Resolution (when target_version is set)
14
16
 
@@ -22,6 +24,7 @@ When `brief.target_version` is present AND `source_repo` is a remote URL, resolv
22
24
  - **Exact match:** `{target_version}` (e.g., `0.5.0`)
23
25
  - **With `v` prefix:** `v{target_version}` (e.g., `v0.5.0`)
24
26
  - **With package scope (monorepos):** `{brief.name}@{target_version}` or `@{scope}/{brief.name}@{target_version}`
27
+ - **With crate/package-directory prefix (monorepos):** `{brief.name}/v{target_version}`, `{brief.name}/{target_version}`, or `{brief.name}-v{target_version}` (e.g. `tokio/v1.0.0`). Covers monorepos whose tags are prefixed by the crate/package directory **when that directory equals the skill name**. When the directory differs from the skill name (e.g. crate `livekit` for skill `livekit-rust`, tag `livekit/v0.7.42`), this heuristic can't infer it — set `target_ref` explicitly instead.
25
28
 
26
29
  3. **Resolution outcomes:**
27
30
  - **Single match:** Store the matched tag as `source_ref`. Use it as `{branch}` in all subsequent clone/API commands.
@@ -76,7 +76,7 @@ npx skill-check check <staging-skill-dir> --fix --format json {security_scan_fla
76
76
 
77
77
  This performs frontmatter validation, description quality checks, body limit enforcement, local link resolution, file formatting, auto-fix of deterministic issues, and quality scoring (0-100) across five weighted categories.
78
78
 
79
- **Parse the JSON output** for: `qualityScore` (0-100), `diagnostics[]` (remaining issues), `fixed[]` (auto-corrected issues).
79
+ **Parse the JSON output** for: `scores[].score` (0-100 — match the entry by `relativePath`/`skillId`; falls back to a top-level `qualityScore` on older skill-check builds), `diagnostics[]` (remaining issues), `fixed[]` (auto-corrected issues).
80
80
 
81
81
  **Description Guard Protocol:** This invocation may modify SKILL.md (especially when `fixed[]` is non-empty). Wrap the `skill-check check --fix` call in the four-phase guard defined in §0 by invoking `{descriptionGuardHelper}` at the capture and verify-restore points:
82
82
 
@@ -94,7 +94,7 @@ uv run {descriptionGuardHelper} verify-restore <staging-skill-dir>/SKILL.md \
94
94
 
95
95
  If `restored: true` in the verify-restore output, apply §0's post-restore re-validation hook. If `fixed[]` was non-empty in the skill-check output, also re-read the modified SKILL.md to sync the in-context copy before proceeding — this prevents silent divergence between the in-context and on-disk versions that step 7 will use for artifact generation.
96
96
 
97
- **Note:** `skill-check` may return non-zero exit code even when `errorCount` is 0. Always rely on parsed JSON, not the shell exit code.
97
+ **Note:** `skill-check` may return non-zero exit code even when `summary.errorCount` is 0. Always rely on parsed JSON, not the shell exit code.
98
98
 
99
99
  - **Score ≥ 70:** Record "Schema: PASS (score: {score}/100)" in evidence-report
100
100
  - **Score < 70:** Log remaining diagnostics as warnings, record "Schema: WARN — score {score}/100, {count} remaining issues", proceed
@@ -182,7 +182,8 @@ Parse output for: `description_score`, `content_score`, `review_score`, `validat
182
182
  **Apply dismissal rules** in this order:
183
183
 
184
184
  1. **Check score thresholds** against the "Score Thresholds" table in `{tesslDismissalData}`. Most importantly:
185
- - If `description_score < 100`: follow the **recover-then-halt** path defined by the `description-xml-tags-guarded-upstream` rule in `{tesslDismissalData}`. Re-apply step 5 §2a's `<`/`>` → `{`/`}` substitution in place on the staging SKILL.md frontmatter `description`, re-sync the in-context copy, and re-run `npx -y tessl skill review <staging-skill-dir>` once. **Re-run gate:** treat `description_score == 100` on the re-run as the only successful recovery outcome. ANY value strictly less than 100 on the re-run — including 99, intermediate-but-improved scores like 95 (formerly tolerated as "close enough"), or 0 — counts as recovery failure: halt with the original `description-xml-tags-guarded-upstream` failure message from `{tesslDismissalData}`, do NOT proceed to §6b, and do NOT downgrade the recovery to a warning. If the re-run produces `description_score == 100`, log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
185
+ - If tessl's output contains a `findings[]` entry with rule ID `description_field` (the deterministic angle-bracket / XML-tag validator): follow the **recover-then-halt** path defined by the `description-xml-tags-guarded-upstream` rule in `{tesslDismissalData}`. Re-apply step 5 §2a's `<`/`>` → `{`/`}` substitution in place on the staging SKILL.md frontmatter `description`, re-sync the in-context copy, and re-run `npx -y tessl skill review <staging-skill-dir>` once. **Re-run gate:** treat the `description_field` finding being **absent** from the re-run as the only successful recovery outcome. If the finding persists on the re-run — whether the re-substitution improved the description or notthat counts as recovery failure: halt with the original `description-xml-tags-guarded-upstream` failure message from `{tesslDismissalData}`, do NOT proceed to §6b, and do NOT downgrade the recovery to a warning. On successful recovery (finding cleared), log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
186
+ - If the LLM-judge `description_score` is below 100 **but no `description_field` finding is present** (the deterministic validator PASSED): this is a soft discoverability signal (jargon density, trigger-term phrasing) from the judge's sub-scores, not a sanitizer bypass. Record `description_judge_score: {n}% (deterministic description_field validator PASSED)` as a warning in the evidence report and continue — do NOT trigger the recover-then-halt path and do NOT halt.
186
187
  - If `review_score < 60` or `content_score < 60`: record warnings in the evidence report, continue.
187
188
  2. **Iterate `judge_suggestions[]`.** For each suggestion:
188
189
  - Cross-reference against the rules in `{tesslDismissalData}` in order.
@@ -53,7 +53,7 @@ Used when the workflow ran in code-mode against an actual codebase. `source_repo
53
53
 
54
54
  ## Compose-mode variant
55
55
 
56
- Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc) or `"inferred_from_shared_domain"` (synthesized inference, must carry T2/T3 confidence).
56
+ Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc), `"constituent_documented_contract"` (a cross-library contract documented in a constituent skill's integration docs but not co-mentioned in the architecture document — e.g. a grep-verified upstream seam cited from a source skill), or `"inferred_from_shared_domain"` (synthesized inference from shared language/domain, no cited contract). `detection_method` records *how* an edge was discovered; it is orthogonal to `confidence`, which is inherited from the constituent skills per the Confidence Tier Inheritance matrix in `references/compose-mode-rules.md` (the integration tier is the weaker of the pair — never forced to a fixed band by detection method).
57
57
 
58
58
  ```json
59
59
  {
@@ -82,9 +82,9 @@ Used when the workflow ran in compose-mode against pre-generated constituent ski
82
82
  {
83
83
  "libraries": ["{libA}", "{libB}"],
84
84
  "pattern_type": "{type}",
85
- "detection_method": "architecture_co_mention|inferred_from_shared_domain",
85
+ "detection_method": "architecture_co_mention|constituent_documented_contract|inferred_from_shared_domain",
86
86
  "co_import_files": [],
87
- "confidence": "T2|T3"
87
+ "confidence": "T1|T1-low|T2|T3"
88
88
  }
89
89
  ],
90
90
  "constituents": [
@@ -49,6 +49,36 @@ description: >
49
49
  - {convention_2}
50
50
  ```
51
51
 
52
+ ## Sizing Guidance for Large Stacks
53
+
54
+ A stack capstone grows monotonically as patterns and libraries are added, so a
55
+ mature stack eventually pushes SKILL.md past skill-check's `body.max_lines`
56
+ budget (default **500**) and/or the `description` past 1024 chars. Pre-empt both
57
+ ceilings at compile time:
58
+
59
+ - **Catalog placement.** The `Library Reference Index` table and `Per-Library
60
+ Summaries` are the largest, most reference-like sections. For a **large stack**
61
+ (heuristic: **> 6 libraries OR > 6 integration patterns**, the point at which
62
+ the inline catalog crowds the 500-line budget), author both into
63
+ `references/stack-catalog.md` and leave only an inline pointer in SKILL.md (see
64
+ below). For a **small stack**, keep them inline — inline passive context yields
65
+ higher task accuracy than on-demand retrieval, and small stacks fit comfortably.
66
+ Integration Patterns and Conventions stay inline regardless (Tier 1, load-bearing).
67
+ - **Inline pointer form** (replaces the two sections above when extracted):
68
+
69
+ ```markdown
70
+ ## Library Catalog
71
+
72
+ {lib_count} libraries indexed in [references/stack-catalog.md](references/stack-catalog.md) —
73
+ reference-index table + per-library summaries. Load it for a specific library's exports or role.
74
+ ```
75
+
76
+ - **Description cap.** Keep `description` ≤ 1024 chars. Do NOT enumerate every
77
+ library by name; the generic "{lib_count} libraries with {integration_count}
78
+ integration patterns" form already scales. If a per-library parenthetical is
79
+ used, cap it to the top libraries by import/export count with a `+{N} more`
80
+ suffix — the full list lives in `metadata.json` `libraries[]` and the catalog.
81
+
52
82
  ## context-snippet.md Format (Vercel-Aligned)
53
83
 
54
84
  Indexed format targeting ~80-120 tokens per stack:
@@ -107,6 +137,32 @@ Indexed format targeting ~80-120 tokens per stack:
107
137
  }
108
138
  ```
109
139
 
140
+ ## references/stack-catalog.md Structure
141
+
142
+ Written **only for large stacks** (see Sizing Guidance) when the catalog is
143
+ extracted out of SKILL.md. Holds the two sections verbatim from the inline form:
144
+
145
+ ```markdown
146
+ # {project_name} Stack — Library Catalog
147
+
148
+ > Reference index + per-library summaries extracted from SKILL.md to keep the
149
+ > capstone under the body-size budget. See SKILL.md for integration patterns.
150
+
151
+ ## Library Reference Index
152
+
153
+ | Library | Imports | Key Exports | Confidence | Reference |
154
+ |---------|---------|-------------|------------|-----------|
155
+ | {name} | {count} | {top_exports} | {tier} | [ref](references/{name}.md) |
156
+
157
+ ## Per-Library Summaries
158
+
159
+ ### {library_name}
160
+ **Role in stack:** {one-line description of what this library does in this project}
161
+ **Key exports used:** {comma-separated list}
162
+ **Usage pattern:** {brief pattern description}
163
+ **Confidence:** {T1/T1-low/T2}
164
+ ```
165
+
110
166
  ## references/{library}.md Structure
111
167
 
112
168
  ```markdown
@@ -41,7 +41,7 @@ description: >
41
41
  **Frontmatter rules:**
42
42
 
43
43
  - `name`: lowercase alphanumeric + hyphens only, must match skill output directory name. **Stack skills MUST end in `-stack`** (e.g., `{project_name}-stack`) — this is how consumers (skf-verify-stack, skf-test-skill) detect stack vs individual skills.
44
- - `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can...").
44
+ - `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can..."). **Do NOT enumerate every library by name** — a 12+ library stack overruns 1024 chars. Keep the generic "{lib_count} libraries with {integration_count} integration patterns" form; if a per-library parenthetical is used, cap it to the top libraries by import/export count with a `+{N} more` suffix (full list lives in `metadata.json` `libraries[]`). See "Sizing Guidance for Large Stacks" in `{stackSkillTemplate}`.
45
45
  - No other frontmatter fields — only `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools` are permitted by spec
46
46
 
47
47
  ### 3. Compile Integration Layer
@@ -68,6 +68,16 @@ description: >
68
68
 
69
69
  ### 4. Compile Per-Library Sections
70
70
 
71
+ **Catalog placement (decide once, applies to this section and §6).** The
72
+ `Per-Library Summaries` and `Library Reference Index` are the largest sections
73
+ and grow with the stack. For a **large stack** (heuristic: **> 6 libraries OR
74
+ > 6 integration patterns**), author both into `references/stack-catalog.md`
75
+ (structure in `{stackSkillTemplate}`) and place only the inline pointer from the
76
+ template's "Sizing Guidance" in SKILL.md — this keeps the body under the 500-line
77
+ `body.max_lines` budget that step 08 enforces. For a **small stack**, keep both
78
+ inline (inline passive context yields higher task accuracy). Either way, step 08's
79
+ body-size gate is the backstop; step 08 (`validate.md` §4) accepts both forms.
80
+
71
81
  For each confirmed library (ordered by integration connectivity, then import count — **in compose-mode**, order by integration connectivity, then skill confidence tier since import counts are not available):
72
82
 
73
83
  - Role in stack (one-line description)
@@ -86,7 +96,8 @@ Extract project-specific conventions from the extractions:
86
96
 
87
97
  ### 6. Compile Library Reference Index
88
98
 
89
- Create the reference index table:
99
+ Place per the §4 catalog-placement decision (inline for small stacks; in
100
+ `references/stack-catalog.md` for large stacks). Create the reference index table:
90
101
 
91
102
  | Library | Imports | Key Exports | Confidence | Reference |
92
103
  |---------|---------|-------------|------------|-----------|
@@ -85,3 +85,5 @@ When no architecture document is available:
85
85
  - Infer from skills sharing domain keywords in their `SKILL.md` descriptions
86
86
  - Mark all inferred integrations: `[inferred from shared domain]`
87
87
  - Inferred integrations default to lowest confidence of the pair with `[inferred from shared domain]` suffix (use this instead of `[composed]` for inferred integrations)
88
+
89
+ **Constituent-documented contracts (distinct from shared-domain inference):** When a constituent skill's own integration docs cite a verifiable cross-library contract (e.g. a grep-verified upstream seam) that the architecture document does not co-mention, record it with `detection_method: constituent_documented_contract` (see `assets/provenance-map-schema.md`) — NOT `inferred_from_shared_domain`. It is a cited contract, not a synthesized guess. Its confidence still inherits the weaker tier of the pair per the matrix above — detection method is orthogonal to tier and never forces a fixed band.
@@ -142,10 +142,13 @@ For each detected integration:
142
142
  - **Assign confidence (M1/M3) — derive from per-library tiers + detection-method qualifier (NOT from AST):** integration detection here is grep + co-import (optionally CCC-augmented), never AST. The integration's confidence is the **weaker** of the two libraries' tiers from `per_library_extractions[]` (tie-break: T1-low > T1, T2 > T1-low, T3 > T2 — never overstate). Then append a detection-method qualifier:
143
143
  - `grep-co-import` — the pair qualified via direct co-import grep (the default).
144
144
  - `ccc-augmented` — the pair qualified only after CCC semantic search elevated it (per §2 CCC augmentation), and the post-hoc import verification (H3) confirmed both imports.
145
- - `architecture-co-mention` — compose-mode pair qualified via word-boundary co-mention in the architecture document (per §2 H2 guards).
146
- - `inferred-shared-domain` — compose-mode pair without an architecture document, inferred from shared `language` or domain keywords.
145
+ - `architecture-co-mention` — compose-mode pair qualified via word-boundary co-mention in the architecture document (per §2 H2 guards). Maps to `detection_method: architecture_co_mention` in `provenance-map.json`.
146
+ - `constituent-documented-contract` — compose-mode pair whose cross-library contract is documented in a constituent skill's integration docs (cited, e.g. a grep-verified upstream seam) but not co-mentioned in the architecture document. Maps to `detection_method: constituent_documented_contract` in `provenance-map.json`.
147
+ - `inferred-shared-domain` — compose-mode pair without an architecture document, inferred from shared `language` or domain keywords (no cited contract). Maps to `detection_method: inferred_from_shared_domain` in `provenance-map.json`.
147
148
  - Render as `{tier} ({qualifier})` — e.g., `T1-low (grep-co-import)`, `T1 (ccc-augmented)`, `T1-low (architecture-co-mention) [composed]`. The `[composed]`/`[inferred from shared domain]` suffix from `compose-mode-rules.md` is appended after the qualifier in compose-mode.
148
149
 
150
+ **Provenance ↔ SKILL.md tier parity:** The tier derived above is the single value for this edge — write the *same* tier to both the SKILL.md integration label and `provenance-map.json` `integrations[].confidence`. The detection-method qualifier (and the `provenance-map.json` `detection_method` it maps to) records *how* the edge was found and is orthogonal to confidence; it never forces the tier into a fixed band.
151
+
149
152
  ### 4. Build Integration Graph
150
153
 
151
154
  Assemble the integration graph:
@@ -9,6 +9,14 @@ provenanceMapSchemaPath: 'assets/provenance-map-schema.md'
9
9
  atomicWriteProbeOrder:
10
10
  - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
11
11
  - '{project-root}/src/shared/scripts/skf-atomic-write.py'
12
+ # Resolve `{frontmatterValidator}` by probing `{frontmatterValidatorProbeOrder}`
13
+ # in order (installed SKF module path first, src/ dev-checkout fallback); first
14
+ # existing path wins. Used by the §8 pre-commit frontmatter + body-size gate
15
+ # (`--max-body-lines`). If neither resolves, the gate degrades to a WARNING —
16
+ # step 8 (validate.md) still runs the full post-commit check.
17
+ frontmatterValidatorProbeOrder:
18
+ - '{project-root}/_bmad/skf/shared/scripts/skf-validate-frontmatter.py'
19
+ - '{project-root}/src/shared/scripts/skf-validate-frontmatter.py'
12
20
  ---
13
21
 
14
22
  <!-- Config: communicate in {communication_language}. Artifact text in {document_output_language}. -->
@@ -29,7 +37,7 @@ Write all deliverable and workspace artifact files to their target directories.
29
37
 
30
38
  ### 1. Resolve Paths and Stage Target Directory
31
39
 
32
- Resolve `{version}` from the primary library version or default to `1.0.0` (see S11 in "Primary library" note below). The final artifact paths are:
40
+ Resolve `{version}` per S11 below — the primary library version in code-mode, or the stack-local scheme in compose-mode (`1.0.0` for a new stack, or a bump of `{prior_stack_version}` on re-composition). The final artifact paths are:
33
41
 
34
42
  ```
35
43
  {skill_group} # {skills_output_folder}/{project_name}-stack/
@@ -41,7 +49,14 @@ Resolve `{version}` from the primary library version or default to `1.0.0` (see
41
49
 
42
50
  Where the skill name is `{project_name}-stack` and `{version}` is the semver version (with build metadata stripped per `knowledge/version-paths.md`).
43
51
 
44
- **Primary library definition (S11):** In code-mode, the primary library is the dependency with the highest import count from step 3; its `version` (from the manifest) becomes `{primary_library_version}`. In compose-mode, use the highest semver across constituent skill `metadata.json` files. If neither is available, fall back to `1.0.0`.
52
+ **Primary library definition (S11):** In code-mode, the primary library is the dependency with the highest import count from step 3; its `version` (from the manifest) becomes `{primary_library_version}`, falling back to `1.0.0` if unavailable. In compose-mode, the stack carries its own release identity: default `{version}` to `1.0.0` (a stack-local scheme) — do NOT borrow the highest constituent semver. Constituent versions are preserved in `dependencies[]`, so no information is lost, and the package no longer reads as tracking whichever constituent happened to have the highest version. The `1.0.0` default applies only to a **genuinely new** stack — see the re-composition rule below.
53
+
54
+ **Re-composition versioning (S11, compose-mode):** When the S3 pre-flight resolves a `{prior_stack_version}` (re-composing a stack that already has a release line), continue that line instead of resetting — defaulting to `1.0.0` would publish a version *below* the existing release (e.g. `1.0.0` shadowing a prior `3.0.5`, a backward jump). Bump `{prior_stack_version}`:
55
+
56
+ - **Major** if any library in `{prior_libraries}` is removed or replaced — dropping a documented library is breaking for consumers of the stack.
57
+ - **Minor** otherwise — libraries only added, and/or integration content changed (backward-compatible).
58
+
59
+ Never emit a `{version}` ≤ `{prior_stack_version}`. Narrate the resolved version and the bump rationale.
45
60
 
46
61
  **Pre-flight: group-dir type check (S3):** If `{skills_output_folder}/{project_name}-stack/` already exists, probe `{skills_output_folder}/{project_name}-stack/active/{project_name}-stack/metadata.json`. If that metadata exists and `skill_type != "stack"`, HALT with:
47
62
 
@@ -49,6 +64,8 @@ Where the skill name is `{project_name}-stack` and `{version}` is the semver ver
49
64
 
50
65
  Do NOT proceed to staging or commit.
51
66
 
67
+ If that metadata exists and `skill_type == "stack"`, this run is a **re-composition**: capture its `version` as `{prior_stack_version}` and its `libraries` array as `{prior_libraries}` for the S11 re-composition rule above. If the group dir is absent — or exists but has no resolvable `active` stack metadata — treat this as a new stack and leave `{prior_stack_version}` unset.
68
+
52
69
  **Atomic write strategy (C2 / B5):** All artifact writes for `{skill_package}` MUST stage into a temp directory first, then commit atomically via `skf-atomic-write.py commit-dir`. The active symlink flip only happens AFTER the commit succeeds.
53
70
 
54
71
  Create the staging directory:
@@ -92,6 +109,8 @@ Load structure from `{stackSkillTemplate}` references section:
92
109
  - Usage patterns with file:line citations (**in compose-mode**: usage patterns from source skill SKILL.md)
93
110
  - Confidence tier label
94
111
 
112
+ **If the catalog was extracted** (large stack — step 06 §4 placed the `Library Reference Index` + `Per-Library Summaries` out of SKILL.md), also write `{skill_staging}/references/stack-catalog.md` using the structure in `{stackSkillTemplate}`, and confirm SKILL.md carries the inline pointer instead of the two sections. Small stacks keep the catalog inline and write no `stack-catalog.md`.
113
+
95
114
  ### 4. Stage Integration Pair Reference Files
96
115
 
97
116
  For each detected integration pair, write `{skill_staging}/references/integrations/{libraryA}-{libraryB}.md`:
@@ -112,7 +131,7 @@ Write `{skill_staging}/context-snippet.md`:
112
131
  Use the Vercel-aligned indexed format targeting **~80-120 tokens** (M2). Token estimation is heuristic — use `ceil(char_count / 4)` as the working approximation (the standard rule-of-thumb for English text in BPE-style tokenizers; precise counts differ per model). Compute against the rendered snippet body (excluding trailing newline).
113
132
 
114
133
  ```
115
- [{project_name}-stack v{version — in code-mode: primary_library_version or 1.0.0; in compose-mode: highest version across constituent skill metadata.json files, or 1.0.0 if none}]|root: skills/{project_name}-stack/
134
+ [{project_name}-stack v{version — per S11: code-mode primary_library_version or 1.0.0; compose-mode stack-local scheme 1.0.0 for a new stack, or the bumped {prior_stack_version} on re-composition}]|root: skills/{project_name}-stack/
116
135
  |IMPORTANT: {project_name}-stack — read SKILL.md before writing integration code. Do NOT rely on training data.
117
136
  |stack: {dep-1}@{v1}, {dep-2}@{v2}, {dep-3}@{v3}
118
137
  |integrations: {pattern-1}, {pattern-2}
@@ -144,7 +163,7 @@ Populate all fields from the metadata.json schema defined in `{stackSkillTemplat
144
163
  {
145
164
  "skill_type": "stack",
146
165
  "name": "{project_name}-stack",
147
- "version": "{primary_library_version or 1.0.0}",
166
+ "version": "{version — resolved per S11: code-mode primary_library_version or 1.0.0; compose-mode 1.0.0 (new stack) or bumped {prior_stack_version} (re-composition)}",
148
167
  "generation_date": "{current_date}",
149
168
  "forge_tier": "{Quick|Forge|Forge+|Deep — the tier under which this run executed}",
150
169
  "confidence_tier": "{T1|T1-low|T2|T3 — dominant T-code from confidence_distribution below}",
@@ -195,7 +214,7 @@ If any workspace write fails, invoke the rollback contract from §1.
195
214
  Use the schema from `{provenanceMapSchemaPath}` — see that asset for the canonical templates and field definitions of both variants:
196
215
 
197
216
  - **In code-mode:** use the code-mode variant (`source_repo` / `source_commit` populated; `extraction_method` ∈ `ast_bridge|source_reading|qmd_bridge`; `detection_method = "co-import grep"`).
198
- - **In compose-mode:** use the compose-mode variant (source-anchor fields `null`; `extraction_method = "compose-from-skill"`; `detection_method ∈ "architecture_co_mention|inferred_from_shared_domain"`; includes the additional `constituents[]` array for drift detection).
217
+ - **In compose-mode:** use the compose-mode variant (source-anchor fields `null`; `extraction_method = "compose-from-skill"`; `detection_method ∈ "architecture_co_mention|constituent_documented_contract|inferred_from_shared_domain"`; includes the additional `constituents[]` array for drift detection).
199
218
 
200
219
  **Use the `metadata_hash` value already stored in workflow state during step 2 (S13) — do NOT re-read and re-hash at step 7 time. The stored hash captures the state as it was at manifest-detection time, which is the correct provenance anchor.**
201
220
 
@@ -205,7 +224,28 @@ Use the schema from `{provenanceMapSchemaPath}` — see that asset for the canon
205
224
  - Warnings and failures encountered
206
225
  - Confidence tier distribution
207
226
 
208
- ### 8. Commit Staging Directory
227
+ ### 8. Pre-Commit Frontmatter & Body-Size Gate
228
+
229
+ The full schema/frontmatter validation runs in step 8 (`validate.md`) — but that runs *after* commit-dir and flip-link have already published the package. The two most common non-auto-fixable `skill-check` hard rejects — a `description` over the 1024-char limit (which `skill-check --fix` cannot trim for you) and a SKILL.md body over the `body.max_lines` limit (default **500**) — would therefore only surface on an already-committed, symlink-active artifact, forcing edits to the live `SKILL.md`. Additive re-composition of an already-large stack grows the body monotonically, so the body overflow is the likeliest trigger. Catch both here, while the package is still in `.skf-tmp`.
230
+
231
+ Resolve `{frontmatterValidator}` from `{frontmatterValidatorProbeOrder}` (first existing path wins). Run it against the **staged** `SKILL.md`, passing the real skill name so the directory-match check is not fooled by the `.skf-tmp` staging suffix, and `--max-body-lines 500` to assert the skill-check body limit pre-commit:
232
+
233
+ ```bash
234
+ uv run {frontmatterValidator} {skill_staging}/SKILL.md --skill-dir-name {project_name}-stack --max-body-lines 500
235
+ ```
236
+
237
+ The validator emits JSON: `status` (`pass`/`warn`/`fail`), `issues[]` (each with `severity` ∈ `high|medium|low`, `field`, `message`), `body_lines` (the counted body size), and `summary`. Disposition:
238
+
239
+ - **`status` is `fail`, OR any `issues[]` entry has `severity` `high` or `medium`** — a hard violation that `npx skill-check` (step 8) would reject and `--fix` cannot auto-correct. HALT-to-fix **in staging**, then re-run the validator until it clears. Remediate by `field`:
240
+ - `description` / `name` / `compatibility` — trim/correct `{skill_staging}/SKILL.md` (e.g. shorten `description` to ≤ 1024 chars).
241
+ - `body` (`body lines N exceeds max 500`) — reduce the staged body: prefer a **selective split** of the largest Tier-2 section(s) into `{skill_staging}/references/`, keeping Tier-1 content inline (mirrors `validate.md` §3); or trim redundant content. Re-run the gate until `body_lines ≤ 500`.
242
+
243
+ Do NOT proceed to §9 commit-dir with an unresolved high/medium issue. Note: an over-long `description` is rated `medium` and exits `0`, so key the HALT on the issue severities above — not on the exit code.
244
+ - **Only `low`-severity issues (e.g. an unexpected field)** — record each as a WARNING in the evidence report and proceed; these do not block the commit.
245
+
246
+ **If `{frontmatterValidator}` does not resolve** (neither probe path exists) **or the invocation cannot run**, emit a WARNING ("pre-commit frontmatter + body-size gate skipped — validator unavailable") and proceed. Step 8 (`validate.md`) remains the post-commit backstop (including the `body.max_lines` split path in its §3); this gate is a best-effort early catch, never a new hard dependency.
247
+
248
+ ### 9. Commit Staging Directory
209
249
 
210
250
  After all staged writes in sections 2–6 completed successfully, atomically swap the staging dir into place:
211
251
 
@@ -215,7 +255,7 @@ python3 {atomicWriteHelper} commit-dir --target {skill_package}
215
255
 
216
256
  The helper moves any existing `{skill_package}` aside to a `.skf-rollback-<pid>` dir before the swap. On failure the helper restores the prior target and exits non-zero — in that case invoke the rollback contract from §1 and HALT.
217
257
 
218
- ### 9. Flip Active Symlink
258
+ ### 10. Flip Active Symlink
219
259
 
220
260
  ONLY AFTER `commit-dir` succeeds, flip the `{skill_group}/active` symlink to point at `{version}`:
221
261
 
@@ -227,7 +267,7 @@ The helper holds an flock on `{skill_group}/active.skf-lock` and refuses to repl
227
267
 
228
268
  If `flip-link` fails, emit a warning (the committed package is still valid), note the symlink-flip failure in the evidence report, and continue.
229
269
 
230
- ### 10. Display Write Summary
270
+ ### 11. Display Write Summary
231
271
 
232
272
  "**Output files written.**
233
273
 
@@ -248,7 +288,7 @@ If `flip-link` fails, emit a warning (the committed package is still valid), not
248
288
 
249
289
  **Proceeding to validation...**"
250
290
 
251
- ### 11. Auto-Proceed to Next Step
291
+ ### 12. Auto-Proceed to Next Step
252
292
 
253
293
  Load, read the full file and then execute `{nextStepFile}`.
254
294
 
@@ -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`.