bmad-module-skill-forge 0.9.0 → 0.10.1-alpha.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 (268) hide show
  1. package/.claude-plugin/marketplace.json +39 -0
  2. package/.gitattributes +16 -0
  3. package/README.md +105 -38
  4. package/docs/404.md +13 -10
  5. package/docs/RELEASING.md +185 -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 +25 -4
  22. package/src/README.md +23 -25
  23. package/src/forger/forge-tier.yaml +4 -2
  24. package/src/forger/preferences.yaml +7 -1
  25. package/src/knowledge/agentskills-spec.md +1 -1
  26. package/src/knowledge/architecture-verification.md +1 -1
  27. package/src/knowledge/ccc-bridge.md +16 -15
  28. package/src/knowledge/overview.md +11 -11
  29. package/src/knowledge/progressive-capability.md +3 -3
  30. package/src/knowledge/provenance-tracking.md +9 -3
  31. package/src/knowledge/qmd-registry.md +8 -8
  32. package/src/knowledge/skf-knowledge-index.csv +2 -2
  33. package/src/knowledge/skill-lifecycle.md +16 -1
  34. package/src/knowledge/split-body-strategy.md +1 -1
  35. package/src/knowledge/version-paths.md +17 -12
  36. package/src/module-help.csv +18 -17
  37. package/src/module.yaml +4 -0
  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 -66
  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 -59
  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 -69
  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 +20 -64
  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 +14 -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 -66
  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 +1 -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 +2 -2
  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 -176
  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 -145
  229. package/src/workflows/create-stack-skill/workflow.md +0 -59
  230. package/src/workflows/drop-skill/steps-c/step-03-report.md +0 -135
  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 -179
  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 -132
  239. package/src/workflows/rename-skill/workflow.md +0 -64
  240. package/src/workflows/setup-forge/steps-c/step-04-report.md +0 -156
  241. package/src/workflows/setup-forge/workflow.md +0 -51
  242. package/src/workflows/test-skill/data/scoring-rules.md +0 -123
  243. package/src/workflows/test-skill/data/source-access-protocol.md +0 -51
  244. package/src/workflows/test-skill/steps-c/step-01-init.md +0 -217
  245. package/src/workflows/test-skill/steps-c/step-03-coverage-check.md +0 -280
  246. package/src/workflows/test-skill/steps-c/step-04-coherence-check.md +0 -271
  247. package/src/workflows/test-skill/steps-c/step-05-score.md +0 -222
  248. package/src/workflows/test-skill/steps-c/step-06-report.md +0 -207
  249. package/src/workflows/test-skill/templates/test-report-template.md +0 -28
  250. package/src/workflows/test-skill/workflow.md +0 -57
  251. package/src/workflows/update-skill/data/remote-source-resolution.md +0 -57
  252. package/src/workflows/update-skill/steps-c/step-02-detect-changes.md +0 -221
  253. package/src/workflows/update-skill/steps-c/step-03-re-extract.md +0 -256
  254. package/src/workflows/update-skill/steps-c/step-06-write.md +0 -265
  255. package/src/workflows/update-skill/workflow.md +0 -56
  256. package/src/workflows/verify-stack/data/feasibility-report-template.md +0 -63
  257. package/src/workflows/verify-stack/data/integration-verification-rules.md +0 -73
  258. package/src/workflows/verify-stack/steps-c/step-01-init.md +0 -179
  259. package/src/workflows/verify-stack/steps-c/step-03-integrations.md +0 -195
  260. package/src/workflows/verify-stack/steps-c/step-05-synthesize.md +0 -191
  261. package/src/workflows/verify-stack/steps-c/step-06-report.md +0 -197
  262. package/src/workflows/verify-stack/workflow.md +0 -61
  263. package/tools/cli/lib/compiler.js +0 -306
  264. package/tools/cli/lib/ide-commands.js +0 -244
  265. /package/src/{workflows/analyze-source → skf-analyze-source}/templates/analysis-report-template.md +0 -0
  266. /package/src/{workflows/quick-skill/data → skf-quick-skill/assets}/skill-template.md +0 -0
  267. /package/src/{workflows/setup-forge/data → skf-setup/references}/tier-rules.md +0 -0
  268. /package/src/{workflows/test-skill/data → skf-test-skill/assets}/output-section-formats.md +0 -0
@@ -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()
@@ -0,0 +1,163 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = []
4
+ # ///
5
+ """SKF Severity Classify — Rule-based severity classification for drift findings.
6
+
7
+ Applies severity rules to a list of drift findings and computes an overall
8
+ drift score. Used by audit-skill and test-skill.
9
+
10
+ CLI: python3 skf-severity-classify.py <findings-json>
11
+ echo '<JSON>' | python3 skf-severity-classify.py -
12
+
13
+ Input: JSON array of findings, each with:
14
+ - type: "removed"|"added"|"changed"|"moved"|"semantic"
15
+ - category: "export"|"signature"|"parameter"|"module"|"pattern"|"convention"|...
16
+ - detail: description string
17
+
18
+ Output: JSON with classified findings and overall drift score.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import sys
25
+
26
+ # --- Severity Rules (from severity-rules.md) ---
27
+
28
+ CRITICAL_RULES = [
29
+ {"type": "removed", "categories": {"export", "module", "class", "interface"}},
30
+ {"type": "changed", "categories": {"signature", "parameter_count", "return_type", "inheritance", "interface_contract"}},
31
+ {"type": "renamed", "categories": {"export", "module"}},
32
+ ]
33
+
34
+ HIGH_RULES = [
35
+ {"type": "added", "categories": {"export"}, "threshold": 3}, # >3 new exports
36
+ {"type": "removed", "categories": {"internal_helper"}},
37
+ {"type": "changed", "categories": {"default_value", "required_parameter"}},
38
+ {"type": "deprecated", "categories": {"export"}},
39
+ ]
40
+
41
+ MEDIUM_RULES = [
42
+ {"type": "changed", "categories": {"implementation", "optional_parameter", "internal_pattern"}},
43
+ {"type": "added", "categories": {"export"}, "threshold_max": 3}, # 1-3 new exports
44
+ {"type": "moved", "categories": {"export", "function"}},
45
+ ]
46
+
47
+ LOW_CATEGORIES = {"style", "convention", "comment", "documentation", "whitespace", "test", "internal", "private"}
48
+
49
+
50
+ def classify_finding(finding, added_export_count=0):
51
+ """Classify a single finding's severity. Returns severity string."""
52
+ f_type = finding.get("type", "").lower()
53
+ f_category = finding.get("category", "").lower()
54
+
55
+ # Check CRITICAL
56
+ for rule in CRITICAL_RULES:
57
+ if f_type == rule["type"] and f_category in rule["categories"]:
58
+ return "CRITICAL"
59
+
60
+ # Check HIGH
61
+ for rule in HIGH_RULES:
62
+ if f_type == rule["type"]:
63
+ if f_category in rule["categories"]:
64
+ if "threshold" in rule:
65
+ if f_type == "added" and f_category == "export" and added_export_count > rule["threshold"]:
66
+ return "HIGH"
67
+ else:
68
+ return "HIGH"
69
+
70
+ # Check MEDIUM
71
+ for rule in MEDIUM_RULES:
72
+ if f_type == rule["type"]:
73
+ if f_category in rule["categories"]:
74
+ if "threshold_max" in rule:
75
+ if f_type == "added" and f_category == "export" and added_export_count <= rule["threshold_max"]:
76
+ return "MEDIUM"
77
+ else:
78
+ return "MEDIUM"
79
+
80
+ # Check LOW
81
+ if f_category in LOW_CATEGORIES:
82
+ return "LOW"
83
+
84
+ # Semantic findings default to MEDIUM
85
+ if f_type == "semantic":
86
+ return "MEDIUM"
87
+
88
+ # Default: MEDIUM for unrecognized patterns
89
+ return "MEDIUM"
90
+
91
+
92
+ def compute_drift_score(classified):
93
+ """Compute overall drift score from classified findings."""
94
+ severities = {f["severity"] for f in classified}
95
+
96
+ if not classified:
97
+ return "CLEAN"
98
+ if "CRITICAL" in severities:
99
+ return "CRITICAL"
100
+ if "HIGH" in severities or "MEDIUM" in severities:
101
+ return "SIGNIFICANT"
102
+ return "MINOR"
103
+
104
+
105
+ def classify_all(findings):
106
+ """Classify all findings and compute drift score."""
107
+ if not isinstance(findings, list):
108
+ return {"status": "error", "error": "Input must be a JSON array of findings"}
109
+
110
+ # Count added exports for threshold rules
111
+ added_export_count = sum(
112
+ 1 for f in findings
113
+ if f.get("type", "").lower() == "added" and f.get("category", "").lower() == "export"
114
+ )
115
+
116
+ classified = []
117
+ for finding in findings:
118
+ severity = classify_finding(finding, added_export_count)
119
+ classified.append({**finding, "severity": severity})
120
+
121
+ # Sort by severity: CRITICAL > HIGH > MEDIUM > LOW
122
+ severity_order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
123
+ classified.sort(key=lambda f: severity_order.get(f["severity"], 4))
124
+
125
+ drift_score = compute_drift_score(classified)
126
+
127
+ # Summary
128
+ by_severity = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0}
129
+ for f in classified:
130
+ by_severity[f["severity"]] = by_severity.get(f["severity"], 0) + 1
131
+
132
+ return {
133
+ "status": "ok",
134
+ "drift_score": drift_score,
135
+ "total_findings": len(classified),
136
+ "by_severity": by_severity,
137
+ "findings": classified,
138
+ }
139
+
140
+
141
+ if __name__ == "__main__":
142
+ if len(sys.argv) < 2:
143
+ print("Usage: python3 skf-severity-classify.py <findings-json-or-file>", file=sys.stderr)
144
+ print(" echo '<JSON>' | python3 skf-severity-classify.py -", file=sys.stderr)
145
+ sys.exit(1)
146
+
147
+ arg = sys.argv[1]
148
+
149
+ try:
150
+ if arg == "-":
151
+ data = json.load(sys.stdin)
152
+ elif arg.startswith("["):
153
+ data = json.loads(arg)
154
+ else:
155
+ with open(arg, encoding="utf-8") as f:
156
+ data = json.load(f)
157
+ except (json.JSONDecodeError, FileNotFoundError) as e:
158
+ print(json.dumps({"status": "error", "error": str(e)}))
159
+ sys.exit(1)
160
+
161
+ result = classify_all(data)
162
+ print(json.dumps(result, indent=2))
163
+ sys.exit(0 if result["status"] == "ok" else 1)