bmad-module-skill-forge 0.10.0 → 1.0.0-rc.3

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 (269) hide show
  1. package/.claude-plugin/marketplace.json +56 -0
  2. package/.gitattributes +16 -0
  3. package/README.md +105 -38
  4. package/docs/404.md +13 -10
  5. package/docs/RELEASING.md +616 -0
  6. package/docs/STABILITY.md +123 -0
  7. package/docs/_data/pinned.yaml +98 -0
  8. package/docs/agents.md +16 -11
  9. package/docs/architecture.md +221 -0
  10. package/docs/bmad-synergy.md +11 -13
  11. package/docs/concepts.md +18 -108
  12. package/docs/examples.md +121 -80
  13. package/docs/getting-started.md +85 -147
  14. package/docs/how-it-works.md +25 -536
  15. package/docs/index.md +44 -27
  16. package/docs/skill-model.md +328 -0
  17. package/docs/troubleshooting.md +39 -0
  18. package/docs/verifying-a-skill.md +232 -0
  19. package/docs/why-skf.md +93 -0
  20. package/docs/workflows.md +119 -13
  21. package/package.json +30 -9
  22. package/release-audits/v1.0.0-launch-audit.md +207 -0
  23. package/src/README.md +23 -25
  24. package/src/forger/forge-tier.yaml +4 -2
  25. package/src/forger/preferences.yaml +7 -1
  26. package/src/knowledge/agentskills-spec.md +1 -1
  27. package/src/knowledge/architecture-verification.md +1 -1
  28. package/src/knowledge/ccc-bridge.md +16 -15
  29. package/src/knowledge/overview.md +11 -11
  30. package/src/knowledge/progressive-capability.md +3 -3
  31. package/src/knowledge/provenance-tracking.md +9 -3
  32. package/src/knowledge/qmd-registry.md +8 -8
  33. package/src/knowledge/skf-knowledge-index.csv +2 -2
  34. package/src/knowledge/skill-lifecycle.md +16 -1
  35. package/src/knowledge/split-body-strategy.md +1 -1
  36. package/src/knowledge/version-paths.md +17 -12
  37. package/src/module-help.csv +18 -17
  38. package/src/shared/health-check.md +372 -0
  39. package/src/shared/references/feasibility-report-schema.md +86 -0
  40. package/src/shared/references/headless-gate-convention.md +62 -0
  41. package/src/shared/references/output-contract-schema.md +35 -0
  42. package/src/shared/references/pipeline-contracts.md +102 -0
  43. package/src/shared/scripts/skf-atomic-write.py +370 -0
  44. package/src/shared/scripts/skf-manifest-ops.py +236 -0
  45. package/src/shared/scripts/skf-preflight.py +164 -0
  46. package/src/shared/scripts/skf-rebuild-managed-sections.py +201 -0
  47. package/src/shared/scripts/skf-severity-classify.py +163 -0
  48. package/src/shared/scripts/skf-skill-inventory.py +210 -0
  49. package/src/shared/scripts/skf-structural-diff.py +257 -0
  50. package/src/shared/scripts/skf-validate-frontmatter.py +318 -0
  51. package/src/shared/scripts/skf-validate-output.py +247 -0
  52. package/src/skf-analyze-source/SKILL.md +57 -0
  53. package/src/skf-analyze-source/assets/skill-brief-schema.md +125 -0
  54. package/src/skf-analyze-source/references/unit-detection-heuristics.md +124 -0
  55. package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-01-init.md +6 -66
  56. package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-01b-continue.md +4 -59
  57. package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-02-scan-project.md +7 -68
  58. package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-03-identify-units.md +6 -66
  59. package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-04-map-and-detect.md +7 -68
  60. package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-05-recommend.md +6 -66
  61. package/src/{workflows/analyze-source → skf-analyze-source}/steps-c/step-06-generate-briefs.md +14 -71
  62. package/src/skf-analyze-source/steps-c/step-07-health-check.md +22 -0
  63. package/src/skf-audit-skill/SKILL.md +59 -0
  64. package/src/{workflows/audit-skill/data → skf-audit-skill/assets}/drift-report-template.md +7 -7
  65. package/src/{workflows/audit-skill/data → skf-audit-skill/references}/severity-rules.md +11 -11
  66. package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-01-init.md +24 -68
  67. package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-02-re-index.md +36 -71
  68. package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-03-structural-diff.md +21 -63
  69. package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-04-semantic-diff.md +6 -65
  70. package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-05-severity-classify.md +5 -63
  71. package/src/{workflows/audit-skill → skf-audit-skill}/steps-c/step-06-report.md +10 -64
  72. package/src/skf-audit-skill/steps-c/step-07-health-check.md +22 -0
  73. package/src/skf-brief-skill/SKILL.md +53 -0
  74. package/src/{workflows/brief-skill/data → skf-brief-skill/assets}/scope-templates.md +23 -0
  75. package/src/skf-brief-skill/assets/skill-brief-schema.md +206 -0
  76. package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-01-gather-intent.md +6 -67
  77. package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-02-analyze-target.md +6 -66
  78. package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-03-scope-definition.md +12 -68
  79. package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-04-confirm-brief.md +5 -63
  80. package/src/{workflows/brief-skill → skf-brief-skill}/steps-c/step-05-write-brief.md +10 -70
  81. package/src/skf-brief-skill/steps-c/step-06-health-check.md +22 -0
  82. package/src/skf-create-skill/SKILL.md +63 -0
  83. package/src/{workflows/create-skill/data → skf-create-skill/assets}/compile-assembly-rules.md +60 -0
  84. package/src/{workflows/create-skill/data → skf-create-skill/assets}/skill-sections.md +92 -23
  85. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +102 -0
  86. package/src/{workflows/create-skill/data → skf-create-skill/references}/extraction-patterns-tracing.md +13 -13
  87. package/src/{workflows/create-skill/data → skf-create-skill/references}/extraction-patterns.md +8 -3
  88. package/src/skf-create-skill/references/source-resolution-protocols.md +239 -0
  89. package/src/{workflows/create-skill/data → skf-create-skill/references}/tier-degradation-rules.md +8 -7
  90. package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-01-load-brief.md +25 -63
  91. package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-02-ecosystem-check.md +8 -64
  92. package/src/skf-create-skill/steps-c/step-03-extract.md +379 -0
  93. package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-03d-component-extraction.md +18 -35
  94. package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-04-enrich.md +12 -69
  95. package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-05-compile.md +71 -75
  96. package/src/skf-create-skill/steps-c/step-06-validate.md +267 -0
  97. package/src/{workflows/create-skill → skf-create-skill}/steps-c/step-07-generate-artifacts.md +30 -74
  98. package/src/skf-create-skill/steps-c/step-08-report.md +145 -0
  99. package/src/skf-create-skill/steps-c/step-09-health-check.md +23 -0
  100. package/src/{workflows/create-skill/steps-c → skf-create-skill/steps-c/sub}/step-02b-ccc-discover.md +8 -66
  101. package/src/skf-create-skill/steps-c/sub/step-03b-fetch-temporal.md +229 -0
  102. package/src/{workflows/create-skill/steps-c → skf-create-skill/steps-c/sub}/step-03c-fetch-docs.md +18 -42
  103. package/src/skf-create-stack-skill/SKILL.md +64 -0
  104. package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/assets}/stack-skill-template.md +2 -1
  105. package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/compose-mode-rules.md +19 -5
  106. package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/integration-patterns.md +1 -1
  107. package/src/{workflows/create-stack-skill/data → skf-create-stack-skill/references}/manifest-patterns.md +10 -10
  108. package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-01-init.md +14 -55
  109. package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-02-detect-manifests.md +29 -63
  110. package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-03-rank-and-confirm.md +9 -59
  111. package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-04-parallel-extract.md +29 -64
  112. package/src/skf-create-stack-skill/steps-c/step-05-detect-integrations.md +164 -0
  113. package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-06-compile-stack.md +7 -60
  114. package/src/skf-create-stack-skill/steps-c/step-07-generate-output.md +328 -0
  115. package/src/{workflows/create-stack-skill → skf-create-stack-skill}/steps-c/step-08-validate.md +17 -63
  116. package/src/skf-create-stack-skill/steps-c/step-09-report.md +128 -0
  117. package/src/skf-create-stack-skill/steps-c/step-10-health-check.md +22 -0
  118. package/src/skf-drop-skill/SKILL.md +57 -0
  119. package/src/{workflows/drop-skill → skf-drop-skill}/steps-c/step-01-select.md +9 -69
  120. package/src/{workflows/drop-skill → skf-drop-skill}/steps-c/step-02-execute.md +30 -106
  121. package/src/skf-drop-skill/steps-c/step-03-report.md +83 -0
  122. package/src/skf-drop-skill/steps-c/step-04-health-check.md +22 -0
  123. package/src/skf-export-skill/SKILL.md +57 -0
  124. package/src/skf-export-skill/assets/managed-section-format.md +138 -0
  125. package/src/{workflows/export-skill/data → skf-export-skill/assets}/snippet-format.md +8 -12
  126. package/src/skf-export-skill/steps-c/step-01-load-skill.md +247 -0
  127. package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-02-package.md +5 -58
  128. package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-03-generate-snippet.md +15 -77
  129. package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-04-update-context.md +59 -133
  130. package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-05-token-report.md +4 -58
  131. package/src/{workflows/export-skill → skf-export-skill}/steps-c/step-06-summary.md +16 -68
  132. package/src/skf-export-skill/steps-c/step-07-health-check.md +22 -0
  133. package/src/skf-forger/SKILL.md +110 -0
  134. package/src/skf-forger/bmad-skill-manifest.yaml +11 -0
  135. package/src/skf-quick-skill/SKILL.md +56 -0
  136. package/src/{workflows/quick-skill/data → skf-quick-skill/references}/registry-resolution.md +6 -6
  137. package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-01-resolve-target.md +9 -62
  138. package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-02-ecosystem-check.md +5 -59
  139. package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-03-quick-extract.md +18 -61
  140. package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-04-compile.md +6 -60
  141. package/src/{workflows/quick-skill → skf-quick-skill}/steps-c/step-05-validate.md +58 -78
  142. package/src/skf-quick-skill/steps-c/step-06-write.md +73 -0
  143. package/src/skf-quick-skill/steps-c/step-07-health-check.md +22 -0
  144. package/src/skf-refine-architecture/SKILL.md +57 -0
  145. package/src/{workflows/refine-architecture/data → skf-refine-architecture/references}/refinement-rules.md +16 -16
  146. package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-01-init.md +6 -63
  147. package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-02-gap-analysis.md +29 -71
  148. package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-03-issue-detection.md +5 -65
  149. package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-04-improvements.md +5 -64
  150. package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-05-compile.md +5 -56
  151. package/src/{workflows/refine-architecture → skf-refine-architecture}/steps-c/step-06-report.md +11 -64
  152. package/src/skf-refine-architecture/steps-c/step-07-health-check.md +22 -0
  153. package/src/skf-rename-skill/SKILL.md +58 -0
  154. package/src/{workflows/rename-skill → skf-rename-skill}/steps-c/step-01-select.md +12 -73
  155. package/src/{workflows/rename-skill → skf-rename-skill}/steps-c/step-02-execute.md +32 -113
  156. package/src/skf-rename-skill/steps-c/step-03-report.md +83 -0
  157. package/src/skf-rename-skill/steps-c/step-04-health-check.md +22 -0
  158. package/src/skf-setup/SKILL.md +55 -0
  159. package/src/{workflows/setup-forge → skf-setup}/steps-c/step-01-detect-and-tier.md +6 -62
  160. package/src/{workflows/setup-forge → skf-setup}/steps-c/step-01b-ccc-index.md +9 -65
  161. package/src/{workflows/setup-forge → skf-setup}/steps-c/step-02-write-config.md +15 -60
  162. package/src/{workflows/setup-forge → skf-setup}/steps-c/step-03-auto-index.md +7 -66
  163. package/src/skf-setup/steps-c/step-04-report.md +92 -0
  164. package/src/skf-setup/steps-c/step-05-health-check.md +22 -0
  165. package/src/skf-test-skill/SKILL.md +59 -0
  166. package/src/skf-test-skill/references/migration-section-rules.md +114 -0
  167. package/src/skf-test-skill/references/scoring-rules.md +207 -0
  168. package/src/skf-test-skill/references/source-access-protocol.md +109 -0
  169. package/src/skf-test-skill/scripts/compute-score.py +310 -0
  170. package/src/skf-test-skill/steps-c/step-01-init.md +238 -0
  171. package/src/{workflows/test-skill → skf-test-skill}/steps-c/step-02-detect-mode.md +4 -62
  172. package/src/skf-test-skill/steps-c/step-03-coverage-check.md +347 -0
  173. package/src/skf-test-skill/steps-c/step-04-coherence-check.md +239 -0
  174. package/src/{workflows/test-skill → skf-test-skill}/steps-c/step-04b-external-validators.md +42 -77
  175. package/src/skf-test-skill/steps-c/step-05-score.md +266 -0
  176. package/src/skf-test-skill/steps-c/step-06-report.md +290 -0
  177. package/src/skf-test-skill/steps-c/step-07-health-check.md +25 -0
  178. package/src/skf-test-skill/templates/test-report-template.md +58 -0
  179. package/src/skf-update-skill/SKILL.md +58 -0
  180. package/src/{workflows/update-skill/data → skf-update-skill/references}/manual-section-rules.md +6 -6
  181. package/src/{workflows/update-skill/data → skf-update-skill/references}/merge-conflict-rules.md +5 -5
  182. package/src/skf-update-skill/references/remote-source-resolution.md +94 -0
  183. package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-01-init.md +22 -69
  184. package/src/skf-update-skill/steps-c/step-02-detect-changes.md +255 -0
  185. package/src/skf-update-skill/steps-c/step-03-re-extract.md +336 -0
  186. package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-04-merge.md +60 -72
  187. package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-05-validate.md +5 -62
  188. package/src/skf-update-skill/steps-c/step-06-write.md +278 -0
  189. package/src/{workflows/update-skill → skf-update-skill}/steps-c/step-07-report.md +11 -69
  190. package/src/skf-update-skill/steps-c/step-08-health-check.md +22 -0
  191. package/src/skf-verify-stack/SKILL.md +60 -0
  192. package/src/skf-verify-stack/assets/feasibility-report-template.md +76 -0
  193. package/src/{workflows/verify-stack/data → skf-verify-stack/references}/coverage-patterns.md +12 -4
  194. package/src/skf-verify-stack/references/integration-verification-rules.md +78 -0
  195. package/src/skf-verify-stack/steps-c/step-01-init.md +178 -0
  196. package/src/{workflows/verify-stack → skf-verify-stack}/steps-c/step-02-coverage.md +27 -72
  197. package/src/skf-verify-stack/steps-c/step-03-integrations.md +179 -0
  198. package/src/{workflows/verify-stack → skf-verify-stack}/steps-c/step-04-requirements.md +14 -67
  199. package/src/skf-verify-stack/steps-c/step-05-synthesize.md +147 -0
  200. package/src/skf-verify-stack/steps-c/step-06-report.md +156 -0
  201. package/src/skf-verify-stack/steps-c/step-07-health-check.md +25 -0
  202. package/tools/cli/commands/install.js +4 -1
  203. package/tools/cli/commands/status.js +21 -23
  204. package/tools/cli/commands/uninstall.js +11 -19
  205. package/tools/cli/commands/update.js +6 -4
  206. package/tools/cli/lib/ide-skills.js +216 -0
  207. package/tools/cli/lib/installer.js +29 -52
  208. package/tools/cli/lib/manifest.js +11 -5
  209. package/tools/cli/lib/platform-codes.yaml +223 -0
  210. package/tools/cli/lib/ui.js +76 -47
  211. package/tools/validate-docs-drift.js +193 -0
  212. package/src/agents/forger.agent.yaml +0 -114
  213. package/src/workflows/README.md +0 -174
  214. package/src/workflows/analyze-source/data/skill-brief-schema.md +0 -125
  215. package/src/workflows/analyze-source/data/unit-detection-heuristics.md +0 -124
  216. package/src/workflows/analyze-source/workflow.md +0 -61
  217. package/src/workflows/audit-skill/workflow.md +0 -64
  218. package/src/workflows/brief-skill/data/skill-brief-schema.md +0 -159
  219. package/src/workflows/brief-skill/workflow.md +0 -58
  220. package/src/workflows/create-skill/data/source-resolution-protocols.md +0 -200
  221. package/src/workflows/create-skill/steps-c/step-03-extract.md +0 -301
  222. package/src/workflows/create-skill/steps-c/step-03b-fetch-temporal.md +0 -253
  223. package/src/workflows/create-skill/steps-c/step-06-validate.md +0 -254
  224. package/src/workflows/create-skill/steps-c/step-08-report.md +0 -179
  225. package/src/workflows/create-skill/workflow.md +0 -53
  226. package/src/workflows/create-stack-skill/steps-c/step-05-detect-integrations.md +0 -196
  227. package/src/workflows/create-stack-skill/steps-c/step-07-generate-output.md +0 -270
  228. package/src/workflows/create-stack-skill/steps-c/step-09-report.md +0 -150
  229. package/src/workflows/create-stack-skill/workflow.md +0 -59
  230. package/src/workflows/drop-skill/steps-c/step-03-report.md +0 -136
  231. package/src/workflows/drop-skill/workflow.md +0 -63
  232. package/src/workflows/export-skill/data/managed-section-format.md +0 -103
  233. package/src/workflows/export-skill/steps-c/step-01-load-skill.md +0 -237
  234. package/src/workflows/export-skill/workflow.md +0 -58
  235. package/src/workflows/quick-skill/steps-c/step-06-write.md +0 -178
  236. package/src/workflows/quick-skill/workflow.md +0 -58
  237. package/src/workflows/refine-architecture/workflow.md +0 -61
  238. package/src/workflows/rename-skill/steps-c/step-03-report.md +0 -133
  239. package/src/workflows/rename-skill/workflow.md +0 -64
  240. package/src/workflows/setup-forge/steps-c/step-04-report.md +0 -159
  241. package/src/workflows/setup-forge/workflow.md +0 -51
  242. package/src/workflows/shared/health-check.md +0 -231
  243. package/src/workflows/test-skill/data/scoring-rules.md +0 -123
  244. package/src/workflows/test-skill/data/source-access-protocol.md +0 -51
  245. package/src/workflows/test-skill/steps-c/step-01-init.md +0 -217
  246. package/src/workflows/test-skill/steps-c/step-03-coverage-check.md +0 -280
  247. package/src/workflows/test-skill/steps-c/step-04-coherence-check.md +0 -271
  248. package/src/workflows/test-skill/steps-c/step-05-score.md +0 -222
  249. package/src/workflows/test-skill/steps-c/step-06-report.md +0 -208
  250. package/src/workflows/test-skill/templates/test-report-template.md +0 -28
  251. package/src/workflows/test-skill/workflow.md +0 -57
  252. package/src/workflows/update-skill/data/remote-source-resolution.md +0 -57
  253. package/src/workflows/update-skill/steps-c/step-02-detect-changes.md +0 -221
  254. package/src/workflows/update-skill/steps-c/step-03-re-extract.md +0 -256
  255. package/src/workflows/update-skill/steps-c/step-06-write.md +0 -265
  256. package/src/workflows/update-skill/workflow.md +0 -56
  257. package/src/workflows/verify-stack/data/feasibility-report-template.md +0 -63
  258. package/src/workflows/verify-stack/data/integration-verification-rules.md +0 -73
  259. package/src/workflows/verify-stack/steps-c/step-01-init.md +0 -179
  260. package/src/workflows/verify-stack/steps-c/step-03-integrations.md +0 -195
  261. package/src/workflows/verify-stack/steps-c/step-05-synthesize.md +0 -191
  262. package/src/workflows/verify-stack/steps-c/step-06-report.md +0 -200
  263. package/src/workflows/verify-stack/workflow.md +0 -61
  264. package/tools/cli/lib/compiler.js +0 -306
  265. package/tools/cli/lib/ide-commands.js +0 -244
  266. /package/src/{workflows/analyze-source → skf-analyze-source}/templates/analysis-report-template.md +0 -0
  267. /package/src/{workflows/quick-skill/data → skf-quick-skill/assets}/skill-template.md +0 -0
  268. /package/src/{workflows/setup-forge/data → skf-setup/references}/tier-rules.md +0 -0
  269. /package/src/{workflows/test-skill/data → skf-test-skill/assets}/output-section-formats.md +0 -0
@@ -0,0 +1,236 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = []
4
+ # ///
5
+ """SKF Manifest Ops — CRUD operations on the export manifest.
6
+
7
+ Reads, adds, updates, removes entries in .export-manifest.json.
8
+ Used by export-skill, drop-skill, and rename-skill.
9
+
10
+ CLI: python3 skf-manifest-ops.py <skills-folder> <command> [args]
11
+
12
+ Commands:
13
+ read — Read entire manifest
14
+ get <skill-name> — Get a single skill entry
15
+ set <skill-name> <version> — Add/update skill with active version
16
+ remove <skill-name> — Remove skill from manifest
17
+ deprecate <skill-name> [ver] — Mark skill or version as deprecated
18
+ rename <old-name> <new-name> — Rename a skill entry
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import sys
26
+ import tempfile
27
+ from datetime import datetime, timezone
28
+ from pathlib import Path
29
+
30
+
31
+ def read_manifest(manifest_path):
32
+ """Read manifest file, returning (data, None) or (None, error).
33
+
34
+ Handles both v1 (flat list) and v2 (dict) manifest formats.
35
+ V1 manifests are migrated in-place to v2 on next write.
36
+ Legacy `platforms` field at the version level is renamed to `ides`.
37
+ """
38
+ try:
39
+ with open(manifest_path, encoding="utf-8") as f:
40
+ data = json.load(f)
41
+ if data.get("schema_version") != "2":
42
+ data = _migrate_v1_to_v2(data)
43
+ data = _normalize_platforms_to_ides(data)
44
+ return data, None
45
+ except FileNotFoundError:
46
+ return {"schema_version": "2", "exports": {}, "updated_at": None}, None
47
+ except json.JSONDecodeError as e:
48
+ return None, f"Manifest JSON parse error: {e}"
49
+
50
+
51
+ def _migrate_v1_to_v2(data):
52
+ """Migrate a v1 manifest to v2 format in memory."""
53
+ data["schema_version"] = "2"
54
+ for skill_name, entry in data.get("exports", {}).items():
55
+ versions = entry.get("versions", [])
56
+ if isinstance(versions, list):
57
+ active = entry.get("active_version", "")
58
+ deprecated = entry.get("deprecated", False)
59
+ new_versions = {}
60
+ for v in versions:
61
+ status = "active" if v == active and not deprecated else "archived"
62
+ new_versions[v] = {
63
+ "ides": [],
64
+ "last_exported": data.get("updated_at", ""),
65
+ "status": "deprecated" if deprecated and v == active else status,
66
+ }
67
+ entry["versions"] = new_versions
68
+ entry.pop("deprecated", None)
69
+ entry.pop("deprecated_versions", None)
70
+ return data
71
+
72
+
73
+ def _normalize_platforms_to_ides(data):
74
+ """Rename legacy `platforms` key → `ides` on each version entry.
75
+
76
+ Pre-rename v2 manifests used `platforms` for the list of IDE identifiers
77
+ the version was exported to. The field was ambiguous (IDE name vs context
78
+ file vs skill root), so it was renamed to `ides` to match
79
+ `config.yaml.ides`. Existing manifests are upgraded on next read.
80
+ """
81
+ for entry in data.get("exports", {}).values():
82
+ versions = entry.get("versions", {})
83
+ if not isinstance(versions, dict):
84
+ continue
85
+ for v_data in versions.values():
86
+ if not isinstance(v_data, dict):
87
+ continue
88
+ if "platforms" in v_data:
89
+ legacy = v_data.pop("platforms")
90
+ if "ides" not in v_data:
91
+ v_data["ides"] = legacy
92
+ return data
93
+
94
+
95
+ def write_manifest(manifest_path, data):
96
+ """Write manifest file atomically via write-to-temp-then-rename."""
97
+ data["updated_at"] = datetime.now(timezone.utc).isoformat()
98
+ manifest_path = Path(manifest_path)
99
+ tmp_fd, tmp_path = tempfile.mkstemp(dir=manifest_path.parent, suffix=".tmp")
100
+ try:
101
+ with os.fdopen(tmp_fd, "w", encoding="utf-8") as f:
102
+ json.dump(data, f, indent=2)
103
+ f.write("\n")
104
+ os.replace(tmp_path, manifest_path)
105
+ except Exception:
106
+ os.unlink(tmp_path)
107
+ raise
108
+
109
+
110
+ def cmd_read(manifest_path):
111
+ data, err = read_manifest(manifest_path)
112
+ if err:
113
+ return {"status": "error", "error": err}
114
+ return {"status": "ok", "manifest": data}
115
+
116
+
117
+ def cmd_get(manifest_path, skill_name):
118
+ data, err = read_manifest(manifest_path)
119
+ if err:
120
+ return {"status": "error", "error": err}
121
+ exports = data.get("exports", {})
122
+ if skill_name not in exports:
123
+ return {"status": "not_found", "skill": skill_name, "available": sorted(exports.keys())}
124
+ return {"status": "ok", "skill": skill_name, "entry": exports[skill_name]}
125
+
126
+
127
+ def cmd_set(manifest_path, skill_name, version):
128
+ data, err = read_manifest(manifest_path)
129
+ if err:
130
+ return {"status": "error", "error": err}
131
+ exports = data.setdefault("exports", {})
132
+ existing = exports.get(skill_name, {})
133
+ today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
134
+
135
+ # Preserve existing versions dict, archive the previously-active version
136
+ versions = existing.get("versions", {})
137
+ if isinstance(versions, list):
138
+ versions = {} # Safety: handle any residual v1 data
139
+ old_active = existing.get("active_version")
140
+ if old_active and old_active in versions and old_active != version:
141
+ versions[old_active]["status"] = "archived"
142
+
143
+ # Add or update the new active version
144
+ existing_version = versions.get(version, {})
145
+ versions[version] = {
146
+ "ides": existing_version.get("ides", existing_version.get("platforms", [])),
147
+ "last_exported": today,
148
+ "status": "active",
149
+ }
150
+
151
+ exports[skill_name] = {
152
+ "active_version": version,
153
+ "versions": versions,
154
+ }
155
+ write_manifest(manifest_path, data)
156
+ return {"status": "ok", "action": "set", "skill": skill_name, "version": version}
157
+
158
+
159
+ def cmd_remove(manifest_path, skill_name):
160
+ data, err = read_manifest(manifest_path)
161
+ if err:
162
+ return {"status": "error", "error": err}
163
+ exports = data.get("exports", {})
164
+ if skill_name not in exports:
165
+ return {"status": "not_found", "skill": skill_name}
166
+ removed = exports.pop(skill_name)
167
+ write_manifest(manifest_path, data)
168
+ return {"status": "ok", "action": "removed", "skill": skill_name, "removed_entry": removed}
169
+
170
+
171
+ def cmd_deprecate(manifest_path, skill_name, version=None):
172
+ data, err = read_manifest(manifest_path)
173
+ if err:
174
+ return {"status": "error", "error": err}
175
+ exports = data.get("exports", {})
176
+ if skill_name not in exports:
177
+ return {"status": "not_found", "skill": skill_name}
178
+ versions = exports[skill_name].get("versions", {})
179
+ if version:
180
+ if version not in versions:
181
+ return {"status": "error", "error": f"Version '{version}' not found for '{skill_name}'"}
182
+ versions[version]["status"] = "deprecated"
183
+ else:
184
+ # Deprecate all versions
185
+ for v in versions.values():
186
+ v["status"] = "deprecated"
187
+ write_manifest(manifest_path, data)
188
+ return {"status": "ok", "action": "deprecated", "skill": skill_name, "version": version}
189
+
190
+
191
+ def cmd_rename(manifest_path, old_name, new_name):
192
+ data, err = read_manifest(manifest_path)
193
+ if err:
194
+ return {"status": "error", "error": err}
195
+ exports = data.get("exports", {})
196
+ if old_name not in exports:
197
+ return {"status": "not_found", "skill": old_name}
198
+ if new_name in exports:
199
+ return {"status": "error", "error": f"Target name '{new_name}' already exists in manifest"}
200
+ exports[new_name] = exports.pop(old_name)
201
+ write_manifest(manifest_path, data)
202
+ return {"status": "ok", "action": "renamed", "from": old_name, "to": new_name}
203
+
204
+
205
+ def main():
206
+ if len(sys.argv) < 3:
207
+ print("Usage: python3 skf-manifest-ops.py <skills-folder> <command> [args]", file=sys.stderr)
208
+ print("Commands: read, get <name>, set <name> <version>, remove <name>, deprecate <name> [version], rename <old> <new>", file=sys.stderr)
209
+ sys.exit(1)
210
+
211
+ skills_folder = Path(sys.argv[1])
212
+ command = sys.argv[2]
213
+ manifest_path = skills_folder / ".export-manifest.json"
214
+
215
+ if command == "read":
216
+ result = cmd_read(manifest_path)
217
+ elif command == "get" and len(sys.argv) >= 4:
218
+ result = cmd_get(manifest_path, sys.argv[3])
219
+ elif command == "set" and len(sys.argv) >= 5:
220
+ result = cmd_set(manifest_path, sys.argv[3], sys.argv[4])
221
+ elif command == "remove" and len(sys.argv) >= 4:
222
+ result = cmd_remove(manifest_path, sys.argv[3])
223
+ elif command == "deprecate" and len(sys.argv) >= 4:
224
+ ver = sys.argv[4] if len(sys.argv) >= 5 else None
225
+ result = cmd_deprecate(manifest_path, sys.argv[3], ver)
226
+ elif command == "rename" and len(sys.argv) >= 5:
227
+ result = cmd_rename(manifest_path, sys.argv[3], sys.argv[4])
228
+ else:
229
+ result = {"status": "error", "error": f"Unknown command or missing args: {command}"}
230
+
231
+ print(json.dumps(result, indent=2))
232
+ sys.exit(0 if result["status"] == "ok" else 1)
233
+
234
+
235
+ if __name__ == "__main__":
236
+ main()
@@ -0,0 +1,164 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["pyyaml"]
4
+ # ///
5
+ """SKF Preflight — Config and sidecar loader for all SKF skills.
6
+
7
+ Loads config.yaml, validates sidecar_path, loads preferences.yaml and
8
+ forge-tier.yaml, and outputs a unified JSON blob for step consumption.
9
+
10
+ CLI: python3 skf-preflight.py <project-root>
11
+ python3 skf-preflight.py <project-root> --config-path <alt-config>
12
+
13
+ Output: JSON to stdout with all resolved config variables and sidecar state.
14
+ Exit 0 on success, exit 1 on hard-halt conditions (missing config, bad sidecar).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ try:
24
+ import yaml
25
+ except ImportError:
26
+ print(
27
+ json.dumps({"error": "PyYAML not installed. Run: pip install pyyaml", "code": "MISSING_DEPENDENCY"}),
28
+ )
29
+ sys.exit(1)
30
+
31
+
32
+ def load_yaml_file(path):
33
+ """Load a YAML file, returning (data, None) or (None, error_string)."""
34
+ try:
35
+ with open(path, encoding="utf-8") as f:
36
+ data = yaml.safe_load(f)
37
+ return data or {}, None
38
+ except FileNotFoundError:
39
+ return None, f"File not found: {path}"
40
+ except yaml.YAMLError as e:
41
+ return None, f"YAML parse error in {path}: {e}"
42
+
43
+
44
+ def run_preflight(project_root, config_path=None):
45
+ """Run preflight checks and return a result dict."""
46
+ project_root = Path(project_root).resolve()
47
+
48
+ # 1. Load config.yaml
49
+ if config_path:
50
+ cfg_path = Path(config_path)
51
+ else:
52
+ cfg_path = project_root / "_bmad" / "skf" / "config.yaml"
53
+
54
+ config, err = load_yaml_file(cfg_path)
55
+ if err:
56
+ return {
57
+ "status": "hard-halt",
58
+ "error": f"Cannot initialize. SKF config not found at {cfg_path}. Run the skf-setup skill to initialize your forge environment.",
59
+ "code": "CONFIG_MISSING",
60
+ }
61
+
62
+ # 2. Resolve config variables
63
+ resolved = {
64
+ "project_root": str(project_root),
65
+ "config_path": str(cfg_path),
66
+ "project_name": config.get("project_name", ""),
67
+ "output_folder": config.get("output_folder", ""),
68
+ "user_name": config.get("user_name", ""),
69
+ "communication_language": config.get("communication_language", "English"),
70
+ "document_output_language": config.get("document_output_language", "English"),
71
+ "sidecar_path": config.get("sidecar_path", ""),
72
+ "skills_output_folder": config.get("skills_output_folder", ""),
73
+ "forge_data_folder": config.get("forge_data_folder", ""),
74
+ }
75
+
76
+ # 3. Validate sidecar_path
77
+ sidecar_path = resolved["sidecar_path"]
78
+ if not sidecar_path or sidecar_path == "{sidecar_path}":
79
+ return {
80
+ "status": "hard-halt",
81
+ "error": (
82
+ "Cannot initialize. sidecar_path is not defined in your installed config.yaml. "
83
+ f"Add sidecar_path: {project_root}/_bmad/_memory/forger-sidecar to your project "
84
+ "config.yaml and retry. This is a known installer issue with prompt: false config variables."
85
+ ),
86
+ "code": "SIDECAR_UNDEFINED",
87
+ "config": resolved,
88
+ }
89
+
90
+ sidecar_dir = Path(sidecar_path)
91
+ if not sidecar_dir.is_absolute():
92
+ sidecar_dir = project_root / sidecar_path
93
+
94
+ if not sidecar_dir.is_dir():
95
+ return {
96
+ "status": "hard-halt",
97
+ "error": f"Sidecar directory not found: {sidecar_dir}. Run skf-setup to initialize.",
98
+ "code": "SIDECAR_MISSING",
99
+ "config": resolved,
100
+ }
101
+
102
+ resolved["sidecar_path_resolved"] = str(sidecar_dir)
103
+
104
+ # 4. Load sidecar files
105
+ sidecar = {}
106
+
107
+ prefs_path = sidecar_dir / "preferences.yaml"
108
+ prefs, prefs_err = load_yaml_file(prefs_path)
109
+ if prefs_err:
110
+ sidecar["preferences"] = None
111
+ sidecar["preferences_error"] = prefs_err
112
+ else:
113
+ sidecar["preferences"] = prefs
114
+
115
+ tier_path = sidecar_dir / "forge-tier.yaml"
116
+ tier, tier_err = load_yaml_file(tier_path)
117
+ if tier_err:
118
+ sidecar["forge_tier"] = None
119
+ sidecar["forge_tier_error"] = tier_err
120
+ else:
121
+ sidecar["forge_tier"] = tier
122
+
123
+ # 5. Derive convenience fields
124
+ tier_value = None
125
+ if tier:
126
+ tier_value = tier.get("tier")
127
+
128
+ compact_greeting = False
129
+ if prefs:
130
+ compact_greeting = prefs.get("compact_greeting", False) is True
131
+
132
+ tier_override = None
133
+ if prefs:
134
+ tier_override = prefs.get("tier_override")
135
+
136
+ return {
137
+ "status": "ok",
138
+ "config": resolved,
139
+ "sidecar": sidecar,
140
+ "derived": {
141
+ "tier": tier_override or tier_value,
142
+ "tier_source": "override" if tier_override else ("detected" if tier_value else None),
143
+ "compact_greeting": compact_greeting,
144
+ "is_first_run": tier_value is None,
145
+ },
146
+ }
147
+
148
+
149
+ if __name__ == "__main__":
150
+ if len(sys.argv) < 2:
151
+ print("Usage: python3 skf-preflight.py <project-root> [--config-path <path>]", file=sys.stderr)
152
+ sys.exit(1)
153
+
154
+ proj_root = sys.argv[1]
155
+ cfg_path = None
156
+
157
+ if "--config-path" in sys.argv:
158
+ idx = sys.argv.index("--config-path")
159
+ if idx + 1 < len(sys.argv):
160
+ cfg_path = sys.argv[idx + 1]
161
+
162
+ result = run_preflight(proj_root, cfg_path)
163
+ print(json.dumps(result, indent=2))
164
+ sys.exit(1 if result["status"] == "hard-halt" else 0)
@@ -0,0 +1,201 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = []
4
+ # ///
5
+ """SKF Rebuild Managed Sections — Context file marker surgery.
6
+
7
+ Reads a context file (CLAUDE.md/AGENTS.md/.cursorrules), finds the
8
+ <!-- SKF:BEGIN --> / <!-- SKF:END --> markers, and replaces the managed
9
+ section with new content. Preserves all content outside the markers.
10
+
11
+ CLI: python3 skf-rebuild-managed-sections.py <context-file> <action> [args]
12
+
13
+ Actions:
14
+ read — Extract current managed section content
15
+ replace — Replace managed section with content from stdin or --content
16
+ clear — Remove managed section entirely (markers + content)
17
+ insert — Insert managed section if not present (at end of file)
18
+ check — Check if managed section exists, report status
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import re
25
+ import sys
26
+ from datetime import datetime, timezone
27
+ from pathlib import Path
28
+
29
+ BEGIN_MARKER_PREFIX = "<!-- SKF:BEGIN"
30
+ END_MARKER = "<!-- SKF:END -->"
31
+ MARKER_PATTERN = re.compile(
32
+ r"(<!-- SKF:BEGIN[^>]*-->)(.*?)(<!-- SKF:END -->)",
33
+ re.DOTALL,
34
+ )
35
+
36
+
37
+ def read_context_file(file_path):
38
+ """Read a context file. Returns (content, None) or (None, error)."""
39
+ try:
40
+ return Path(file_path).read_text(encoding="utf-8"), None
41
+ except FileNotFoundError:
42
+ return None, f"File not found: {file_path}"
43
+
44
+
45
+ def find_managed_section(content):
46
+ """Find the managed section in content. Returns match or None."""
47
+ return MARKER_PATTERN.search(content)
48
+
49
+
50
+ def cmd_check(file_path):
51
+ """Check if managed section exists."""
52
+ content, err = read_context_file(file_path)
53
+ if err:
54
+ return {"status": "error", "error": err}
55
+
56
+ has_begin = BEGIN_MARKER_PREFIX in content
57
+ has_end = END_MARKER in content
58
+ match = find_managed_section(content)
59
+
60
+ if match:
61
+ section_content = match.group(2)
62
+ return {
63
+ "status": "ok",
64
+ "has_managed_section": True,
65
+ "markers_valid": True,
66
+ "section_length": len(section_content.strip()),
67
+ "section_line_count": len(section_content.strip().split("\n")) if section_content.strip() else 0,
68
+ }
69
+ elif has_begin and not has_end:
70
+ return {
71
+ "status": "ok",
72
+ "has_managed_section": False,
73
+ "markers_valid": False,
74
+ "error_detail": "Found <!-- SKF:BEGIN but no matching <!-- SKF:END -->",
75
+ }
76
+ elif not has_begin and has_end:
77
+ return {
78
+ "status": "ok",
79
+ "has_managed_section": False,
80
+ "markers_valid": False,
81
+ "error_detail": "Found <!-- SKF:END --> but no matching <!-- SKF:BEGIN",
82
+ }
83
+ else:
84
+ return {"status": "ok", "has_managed_section": False, "markers_valid": True}
85
+
86
+
87
+ def cmd_read(file_path):
88
+ """Extract the managed section content."""
89
+ content, err = read_context_file(file_path)
90
+ if err:
91
+ return {"status": "error", "error": err}
92
+
93
+ match = find_managed_section(content)
94
+ if not match:
95
+ return {"status": "ok", "has_managed_section": False, "content": None}
96
+
97
+ return {"status": "ok", "has_managed_section": True, "content": match.group(2)}
98
+
99
+
100
+ def cmd_replace(file_path, new_content):
101
+ """Replace managed section content between markers."""
102
+ content, err = read_context_file(file_path)
103
+ if err:
104
+ return {"status": "error", "error": err}
105
+
106
+ match = find_managed_section(content)
107
+ if not match:
108
+ return {"status": "error", "error": "No managed section found. Use 'insert' to create one."}
109
+
110
+ # Replace content between markers with fresh timestamp
111
+ today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
112
+ new_section = f"<!-- SKF:BEGIN updated:{today} -->\n{new_content}\n{END_MARKER}"
113
+ updated = content[: match.start()] + new_section + content[match.end() :]
114
+
115
+ Path(file_path).write_text(updated, encoding="utf-8")
116
+ return {"status": "ok", "action": "replaced", "bytes_written": len(updated)}
117
+
118
+
119
+ def cmd_clear(file_path):
120
+ """Remove managed section entirely (markers + content)."""
121
+ content, err = read_context_file(file_path)
122
+ if err:
123
+ return {"status": "error", "error": err}
124
+
125
+ match = find_managed_section(content)
126
+ if not match:
127
+ return {"status": "ok", "action": "no_change", "reason": "No managed section found"}
128
+
129
+ # Remove the entire marker block, plus any surrounding blank lines
130
+ before = content[: match.start()].rstrip("\n")
131
+ after = content[match.end() :].lstrip("\n")
132
+ updated = before + ("\n\n" if before and after else "") + after
133
+
134
+ Path(file_path).write_text(updated, encoding="utf-8")
135
+ return {"status": "ok", "action": "cleared", "bytes_written": len(updated)}
136
+
137
+
138
+ def cmd_insert(file_path, new_content):
139
+ """Insert managed section at end of file if not present."""
140
+ content, err = read_context_file(file_path)
141
+ if err:
142
+ # File doesn't exist — create it
143
+ if "not found" in err.lower():
144
+ content = ""
145
+ else:
146
+ return {"status": "error", "error": err}
147
+
148
+ match = find_managed_section(content)
149
+ if match:
150
+ return {"status": "error", "error": "Managed section already exists. Use 'replace' instead."}
151
+
152
+ today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
153
+ section = f"\n<!-- SKF:BEGIN updated:{today} -->\n{new_content}\n{END_MARKER}\n"
154
+ updated = content.rstrip("\n") + "\n" + section if content.strip() else section.lstrip("\n")
155
+
156
+ Path(file_path).write_text(updated, encoding="utf-8")
157
+ return {"status": "ok", "action": "inserted", "bytes_written": len(updated)}
158
+
159
+
160
+ def main():
161
+ if len(sys.argv) < 3:
162
+ print("Usage: python3 skf-rebuild-managed-sections.py <context-file> <action> [--content <text>]", file=sys.stderr)
163
+ print("Actions: read, replace, clear, insert, check", file=sys.stderr)
164
+ sys.exit(1)
165
+
166
+ file_path = sys.argv[1]
167
+ action = sys.argv[2]
168
+
169
+ content_arg = None
170
+ if "--content" in sys.argv:
171
+ idx = sys.argv.index("--content")
172
+ if idx + 1 < len(sys.argv):
173
+ content_arg = sys.argv[idx + 1]
174
+ elif action in ("replace", "insert") and not sys.stdin.isatty():
175
+ content_arg = sys.stdin.read()
176
+
177
+ if action == "check":
178
+ result = cmd_check(file_path)
179
+ elif action == "read":
180
+ result = cmd_read(file_path)
181
+ elif action == "replace":
182
+ if content_arg is None:
183
+ result = {"status": "error", "error": "replace requires --content or stdin"}
184
+ else:
185
+ result = cmd_replace(file_path, content_arg)
186
+ elif action == "clear":
187
+ result = cmd_clear(file_path)
188
+ elif action == "insert":
189
+ if content_arg is None:
190
+ result = {"status": "error", "error": "insert requires --content or stdin"}
191
+ else:
192
+ result = cmd_insert(file_path, content_arg)
193
+ else:
194
+ result = {"status": "error", "error": f"Unknown action: {action}"}
195
+
196
+ print(json.dumps(result, indent=2))
197
+ sys.exit(0 if result["status"] == "ok" else 1)
198
+
199
+
200
+ if __name__ == "__main__":
201
+ main()