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,207 @@
1
+ # Scoring Rules
2
+
3
+ ## Default Threshold
4
+
5
+ **Pass threshold:** 80%
6
+
7
+ ## Category Weights
8
+
9
+ | Category | Weight | Description |
10
+ |------------------------|--------|-------------------------------------------------------------------------------------------|
11
+ | Export Coverage | 36% | Percentage of source exports documented in SKILL.md |
12
+ | Signature Accuracy | 22% | Documented signatures match actual source signatures |
13
+ | Type Coverage | 14% | Types and interfaces referenced are complete |
14
+ | Coherence (contextual) | 18% | Cross-references valid, integration patterns complete |
15
+ | Coherence (naive) | 0% | Not applicable — weight redistributed to other categories |
16
+ | External Validation | 10% | Average of skill-check quality score + tessl average score (redistributed if unavailable) |
17
+
18
+ ## Naive Mode Weight Redistribution
19
+
20
+ The following weights replace the default table for naive mode. The 18% coherence weight from the default table has been proportionally redistributed into these values. Do not re-redistribute for coherence (already handled in this table). Quick-tier redistribution (zeroing Signature Accuracy and Type Coverage) still applies as an additional step.
21
+
22
+ When running in naive mode (no coherence category):
23
+ - Export Coverage: 45%
24
+ - Signature Accuracy: 25%
25
+ - Type Coverage: 20%
26
+ - External Validation: 10%
27
+
28
+ ## External Validation Unavailable
29
+
30
+ When neither skill-check nor tessl is available, redistribute the 10% external validation weight proportionally to the other active categories. When only one tool is available, use that tool's score as the external validation score.
31
+
32
+ ## tessl and Split-Body Interaction
33
+
34
+ tessl evaluates SKILL.md body content only — it does not read `references/*.md` files. After split-body extraction, the tessl content score will drop significantly (e.g., 65% to 38%) because Tier 2 content is no longer inline. This is expected behavior and does not reflect actual content quality. When reporting scores for a split-body skill, note: "tessl content score reflects post-split inline content only. Use the pre-split tessl score as the content quality baseline."
35
+
36
+ ## Tier-Dependent Scoring
37
+
38
+ ### Quick Tier (no tools)
39
+ - Export Coverage: file/structure existence check only
40
+ - Signature Accuracy: skipped (no AST)
41
+ - Type Coverage: skipped (no AST)
42
+ - Score based on: structural completeness only
43
+ - Weight redistribution: skipped categories' weights (Signature Accuracy 22% + Type Coverage 14%) redistributed proportionally to remaining active categories
44
+
45
+ ### Docs-Only Mode (all [EXT:...] citations, any tier)
46
+
47
+ When `docs_only_mode: true` is set by step-03 (indicating a skill where all SKILL.md citations are `[EXT:...]` format with no local source code):
48
+
49
+ - **Signature Accuracy:** Not scored (no source to compare against)
50
+ - **Type Coverage:** Not scored (no source to compare against)
51
+ - **Weight redistribution:** Same as Quick tier — Signature Accuracy (22%) and Type Coverage (14%) weights redistributed proportionally to remaining active categories
52
+ - **Export Coverage basis:** Documentation completeness rather than source coverage. Score = (documented_items_with_complete_descriptions / total_documented_items) * 100. A "complete" item has: description, parameters (if function/method), and return type (if function/method).
53
+ - **Coherence:** Standard rules for the detected mode (naive or contextual) apply unchanged
54
+
55
+ This is functionally identical to Quick tier weight redistribution but with a different coverage denominator (self-consistency instead of source comparison).
56
+
57
+ **S4 external-validator requirement for docs-only:** docs-only mode removes two categories (Signature Accuracy, Type Coverage) from scoring. If External Validation is ALSO unavailable, the evidence base collapses to Coverage alone (naive) or Coverage + Coherence (contextual) — which in the naive/Quick case trips the minimum-evidence floor (INCONCLUSIVE). To keep docs-only skills gradable when external validators are present but still deterministic when they are missing: **when `docsOnly: true` AND `externalValidation is null`, step-05 MUST cap `totalScore` at `threshold - 1` (forcing FAIL) before the INCONCLUSIVE floor is evaluated.** This prevents a docs-only skill from PASSing with only one or two redistributed categories carrying all the weight. Implement in step-05 §4 as a pre-compare cap, recorded in the report as `scoring_notes: docs-only without external validators — capped below threshold per S4`.
58
+
59
+ ### Stack Skills (Any Tier)
60
+
61
+ When `metadata.json.skill_type == "stack"` (set `stackSkill: true` in the scoring input):
62
+
63
+ - **Signature Accuracy:** N/A — a stack skill's "signature surface" is the external library API it composes (pydantic, SQLAlchemy, FastAPI, etc.), not a proprietary surface the skill authors. Grading signatures against a surface the skill does not own produces meaningless numbers.
64
+ - **Type Coverage:** N/A — same rationale; the type surface belongs to the external libraries.
65
+ - **Weight redistribution:** Same as Quick tier / docs-only / State 2 — Signature Accuracy (22%) and Type Coverage (14%) weights redistributed proportionally to remaining active categories (Export Coverage, Coherence, External Validation).
66
+ - **Applies regardless of detected tier** (Quick, Forge, Forge+, Deep) and is independent of `docsOnly` and `state2`. A stack skill can also be docs-only or State 2; the skip reasons combine additively (e.g. `"stack skill (external type surface) + State 2 (provenance-map)"`).
67
+ - **Detection:** step-05 reads `metadata.json.skill_type` from the skill package. If the value is `"stack"`, set `stackSkill: true` in the scoring input JSON.
68
+
69
+ Equivalence-class note: stack skills with `docsOnly:false` / `state2:false` map to the same equivalence class as State 2 contextual rows (class B) or State 2 naive rows (class D) — the redistribution math is identical; only the `skipReasons` string changes.
70
+
71
+ ### State 2 Source Access (Any Tier, Provenance-Map Only)
72
+
73
+ When source is not locally available and analysis resolves to State 2 (provenance-map baseline per source-access-protocol.md):
74
+
75
+ - **Signature Accuracy:** N/A — provenance-map stores parameters as flat string arrays; verification is string comparison only, not semantic AST verification. Type aliases (`str` vs `String`, `list` vs `List[Any]`) cannot be resolved without live source.
76
+ - **Type Coverage:** N/A — cannot verify type completeness without local source access for AST re-parsing.
77
+ - **Weight redistribution:** Same as Quick tier — Signature Accuracy (22%) and Type Coverage (14%) weights redistributed proportionally to remaining active categories (Export Coverage, Coherence, External Validation).
78
+ - **Applies regardless of detected tier** (including Forge, Forge+, Deep) whenever `analysis_confidence` is `provenance-map` and local source is unavailable.
79
+ - **Export Coverage denominator:** Uses the union of provenance-map entry names and metadata.json `exports[]` names (per source-access-protocol.md State 2 rules).
80
+
81
+ Note: When provenance-map entries are predominantly T1 (AST-verified at compilation time), the coverage and name-matching data is already at highest confidence. The N/A categories reflect the inability to re-verify at test time, not low-quality extraction data.
82
+
83
+ **S9 State 2 undercount risk acknowledgement:** provenance-map is a cached extraction snapshot — if the source has evolved since extraction, public API adds/removes will NOT surface in Export Coverage (denominator is frozen to the provenance-map union). When `state2: true` AND step-03 records any provenance vs metadata divergence (e.g. union > either source by >5%), apply a flat **10% deduction** to `exportCoverage` before calling the scoring script, AND set `analysis_confidence: provenance-map` (already set) with a report note: `scoring_notes: State 2 undercount risk acknowledged — 10% deduction applied to Export Coverage`. Rationale: the skill cannot be reliably scored on a frozen denominator when the cache is known to disagree with its own metadata; prefer understating over overstating.
84
+
85
+ ### Forge Tier (ast-grep)
86
+ - Export Coverage: AST-backed export comparison
87
+ - Signature Accuracy: AST-verified signature matching
88
+ - Type Coverage: AST-verified type completeness
89
+ - Full scoring formula applied
90
+
91
+ ### Forge+ Tier (ast-grep + ccc)
92
+ - Same scoring as Forge tier — ccc provides pre-ranking but does not change scoring weights
93
+ - Improved extraction coverage (from ccc pre-discovery) may increase T1 count, but scoring formula is identical to Forge
94
+ - Full scoring formula applied
95
+
96
+ ### Deep Tier (ast-grep + gh + QMD)
97
+ - All Forge tier checks plus:
98
+ - Cross-repository reference verification
99
+ - QMD knowledge enrichment for coherence
100
+ - Full scoring formula with maximum depth
101
+ - **Migration & Deprecation Warnings section:** If T2-future annotations exist in the enrichment data, verify that Section 4b is present in SKILL.md Tier 1 and that each warning traces to a T2 provenance citation. If no T2-future annotations exist, Section 4b should normally be absent (not empty). Presence/absence mismatch is a Medium severity gap — with one Info-severity exception for historical-migration content (completed package renames, consolidated import paths, shipped API cutovers that remain load-bearing for training-data drift remediation). See `steps-c/step-04-coherence-check.md` §2b/§5b for the three-case rule.
102
+
103
+ ## Redistribution Combinations Matrix (M3 — terminology + determinism)
104
+
105
+ The table below enumerates every `(mode × tier × docsOnly × state2)` cell and
106
+ the redistribution outcome. Two cells map to the same **equivalence class**
107
+ whenever their skip set and base-weight table are identical — the scoring
108
+ script then emits the same `weights` / `activeCategories` / `skippedCategories`
109
+ output regardless of which specific cell produced it. The rightmost column
110
+ cites the representative fixture in
111
+ `test/fixtures/compute-score-contract.json` used to pin the class's math.
112
+
113
+ | # | mode | tier | docsOnly | state2 | Base table | Skipped (sig/type) | Skipped (ext if null) | Equiv. class | Representative fixture |
114
+ |---|------------|----------|----------|--------|----------------|-----------------------|-----------------------|--------------|-------------------------------|
115
+ | 1 | contextual | Deep | F | F | contextual | — | if null | **A** | `suite_a_all_active` |
116
+ | 2 | contextual | Forge+ | F | F | contextual | — | if null | A | `suite_k_forge_plus` |
117
+ | 3 | contextual | Forge | F | F | contextual | — | if null | A | `suite_p_contextual_forge` |
118
+ | 4 | contextual | Quick | F | F | contextual | Quick tier | if null | **B** | `suite_c_quick_tier` |
119
+ | 5 | contextual | Deep | T | F | contextual | docs-only | if null | B | `suite_r_contextual_deep_docsonly` |
120
+ | 6 | contextual | Forge+ | T | F | contextual | docs-only | if null | B | (equiv. — see `suite_r_*`) |
121
+ | 7 | contextual | Forge | T | F | contextual | docs-only | if null | B | (equiv.) |
122
+ | 8 | contextual | Quick | T | F | contextual | Quick tier + docs-only| if null | B | `suite_f_docs_only` |
123
+ | 9 | contextual | Deep | F | T | contextual | State 2 | if null | B | `suite_g_state2` |
124
+ | 10| contextual | Forge+ | F | T | contextual | State 2 | if null | B | (equiv.) |
125
+ | 11| contextual | Forge | F | T | contextual | State 2 | if null | B | (equiv.) |
126
+ | 12| contextual | Quick | F | T | contextual | Quick + State 2 | if null | B | (equiv. — Quick+state2) |
127
+ | 13| contextual | * | T | T | contextual | docs-only + State 2 | if null | B | (equiv.) |
128
+ | 14| naive | Deep | F | F | naive | — | if null | **C** | `suite_q_naive_deep` |
129
+ | 15| naive | Forge+ | F | F | naive | — | if null | C | (equiv.) |
130
+ | 16| naive | Forge | F | F | naive | — | if null | C | `suite_b_naive` |
131
+ | 17| naive | Quick | F | F | naive | Quick tier | if null | **D** | `suite_e_triple_skip` |
132
+ | 18| naive | Deep | T | F | naive | docs-only | if null | D | `suite_s_naive_deep_docsonly` |
133
+ | 19| naive | Forge+ | T | F | naive | docs-only | if null | D | (equiv.) |
134
+ | 20| naive | Forge | T | F | naive | docs-only | if null | D | (equiv.) |
135
+ | 21| naive | Quick | T | F | naive | Quick + docs-only | if null | D | `suite_o_input_echo` |
136
+ | 22| naive | Deep | F | T | naive | State 2 | if null | D | `suite_t_naive_state2` |
137
+ | 23| naive | Forge+ | F | T | naive | State 2 | if null | D | (equiv.) |
138
+ | 24| naive | Forge | F | T | naive | State 2 | if null | D | (equiv.) |
139
+ | 25| naive | Quick | F | T | naive | Quick + State 2 | if null | D | (equiv.) |
140
+ | 26| naive | * | T | T | naive | docs-only + State 2 | if null | D | (equiv.) |
141
+
142
+ **How to read the matrix.** The four equivalence classes A/B/C/D are
143
+ exhaustive: the scoring script's weight-redistribution output depends ONLY on
144
+ (base table, sig/type skip, ext skip), so every row mapping to class X emits
145
+ the same final weights for identical input scores. "(equiv.)" rows are *not*
146
+ missing coverage — they reduce algebraically to a listed representative. The
147
+ M3 fixture set ships at least one representative per equivalence class and one
148
+ representative per unique skip-reason combination so the `skipReasons` string
149
+ is also pinned (e.g. "Quick tier", "docs-only mode", "State 2
150
+ (provenance-map)", "Quick tier + docs-only mode").
151
+
152
+ **Quick-tier INCONCLUSIVE interaction.** Rows 4, 8, 12, 17, 21, 25 all force
153
+ the minimum-evidence floor to evaluate "Quick tier + Export Coverage alone"
154
+ cases separately — see `Result Determination` below. The matrix above is the
155
+ pre-floor redistribution only; INCONCLUSIVE verdict determination runs after.
156
+
157
+ ## Score Calculation
158
+
159
+ ```
160
+ score = sum(category_weight * category_score) for each category
161
+ category_score = (items_passing / items_total) * 100
162
+ ```
163
+
164
+ ## Coherence Score Aggregation (Contextual Mode)
165
+
166
+ ```
167
+ reference_validity = (valid_references / total_references) * 100
168
+ integration_completeness = (complete_patterns / total_patterns) * 100
169
+ combined_coherence = (reference_validity * 0.6) + (integration_completeness * 0.4)
170
+ ```
171
+
172
+ If no integration patterns exist, combined coherence equals reference validity.
173
+
174
+ ## Result Determination
175
+
176
+ Three-state gate — **PASS / FAIL / INCONCLUSIVE**. `INCONCLUSIVE` is not PASS and not FAIL; it signals insufficient evidence to grade the skill. Downstream workflows MUST treat `INCONCLUSIVE` as a hard gate — do not export, do not auto-retry, surface to the human.
177
+
178
+ - **Minimum-Evidence Floor (MANDATORY — applies before PASS/FAIL comparison):**
179
+ - `active_categories` = count of categories with a non-zero final weight *after* all redistribution (Quick tier, docs-only, State 2, external-validator-unavailable). Categories with a redistributed weight of 0 do not count as active, even if they received a score.
180
+ - **If `active_categories < 2`** → force `result: INCONCLUSIVE` with rationale `"insufficient evidence: only {N} active category"`. A single active category cannot cross-validate itself and a PASS would be a false signal.
181
+ - **If `tier == "Quick"` AND the sole active contributor is Export Coverage** → force `result: INCONCLUSIVE` with rationale `"Quick tier: Export Coverage alone is insufficient evidence — add a second active category by upgrading tier or enabling external validators"`. This catches the degenerate case where every signature/type/coherence/external category gets redistributed to 0 and Export Coverage is doing all the work.
182
+ - The floor is enforced by `scripts/compute-score.py`. The step-05 scoring step reads `result` from the script output and writes it into the test report frontmatter unchanged.
183
+
184
+ - Otherwise:
185
+ - score >= threshold → PASS
186
+ - score < threshold → FAIL
187
+
188
+ The floor is intentionally conservative: skf-test-skill grades other skills, so a false PASS has catastrophic downstream effects (polluted exports, misleading feasibility data). Falling back to INCONCLUSIVE is always preferred over a low-evidence PASS.
189
+
190
+ ## Gap Severity
191
+
192
+ | Severity | Criteria |
193
+ |----------|----------------------------------------------------------------------------------------------------------------|
194
+ | Critical | Missing exported function/class documentation |
195
+ | High | Signature mismatch between source and SKILL.md |
196
+ | Medium | Missing type or interface documentation |
197
+ | Medium | Migration section present/absent mismatch with T2-future annotation data (Deep tier) |
198
+ | Medium | Metadata drift — intra-cluster export counts diverge (barrel: `stats.exports_public_api` vs `exports[].length`; or documented-surface: `stats.exports_documented` vs provenance-map entry count; >10% divergence) |
199
+ | Medium | Denominator inflation — stratified-scope `scope.include` union exceeds provenance-map entry count by >25% (brief missing `scope.tier_a_include`) |
200
+ | Medium | Script/asset directory exists but no Scripts & Assets section in SKILL.md |
201
+ | Medium | Scripts & Assets section references file not found in scripts/ or assets/ directory |
202
+ | Low | Script/asset file present without provenance entry in provenance-map.json file_entries |
203
+ | Low | Missing optional metadata or examples |
204
+ | Low | Description trigger optimization recommended (third-person voice, negative triggers, or keyword coverage gaps) |
205
+ | Info | Style suggestions, non-blocking observations |
206
+ | Info | Discovery testing not performed — realistic prompt testing recommended before export |
207
+ | Info | Multi-denominator reporting — barrel vs documented-surface clusters diverge by design (>10% cross-cluster) |
@@ -0,0 +1,109 @@
1
+ # Source Access Protocol
2
+
3
+ ## Source API Surface Definition
4
+
5
+ **Source API surface** = the package's top-level public exports. These are the symbols reachable from the primary entry point without importing internal modules:
6
+
7
+ - **Python:** symbols exported in `__init__.py` (including re-exports) — exclude private (`_prefixed`) names
8
+ - **TypeScript/JavaScript:** named exports from `index.ts` / `index.js` — exclude unexported locals
9
+ - **Go:** exported identifiers (capitalized) from the package's public-facing files
10
+ - **Rust:** items in `pub use` from `lib.rs` or `mod.rs`
11
+ - **Empty-barrel packages (copy-paste / subpath-only distribution):** If the primary entry point is empty or re-exports nothing (e.g., `export {};` in `index.ts`, an empty `__init__.py`, `lib.rs` with no `pub use`), the package does not expose a barrel API. Do **not** compute coverage against the empty barrel — the denominator would be zero and the score meaningless. Instead, consult the skill brief's `scope.include` globs (`forge-data/{skill_name}/skill-brief.yaml`) to identify the authorized entry points, and build the public API surface from the **union of named exports across those files**. The skill brief's `scope.notes` field should document this distribution model explicitly; if present, treat it as confirmation that the empty barrel is by design rather than a bug. If no skill brief is available and the barrel is empty, set `analysis_confidence: docs-only` and report that the source API surface could not be determined.
12
+
13
+ - **Stratified-scope monorepo packages (curated subsets of multi-package repos):** If the source is a monorepo (detect via `packages/` layout, `workspaces` field in root `package.json`, `lerna.json`, `rush.json`, `nx.json`, or Cargo `[workspace]`) AND the skill brief's `scope.include` lists a curated file/directory subset rather than the full workspace, the coverage denominator must reflect only the authored surface, not the monorepo's global export count. This is distinct from the empty-barrel case: each workspace package may have a non-empty barrel, but the skill intentionally documents only a tiered subset.
14
+
15
+ **Resolution order:**
16
+
17
+ 1. **Prefer `metadata.json.stats.effective_denominator`** when present. `skf-create-skill` step-05 §4 writes this field for stratified-scope skills. When set, use it directly as the `exports_public_api` count for coverage scoring.
18
+ 2. **Fall back to live re-derivation** when `effective_denominator` is absent (older skills, quick-tier output, or skills compiled before this rule existed). Read the brief's scope globs from `forge-data/{skill_name}/skill-brief.yaml`, resolve them against `source_path`, filter out files matching `scope.exclude`, and compute the source API surface as the **union of named exports across the matched files only**. The skill brief's `scope.notes` field should document the stratification strategy (e.g., "Tier A: fully documented; Tier B: deferred to references; Tier C: excluded") — when present, treat it as confirmation that the curated subset is by design, not a scope gap.
19
+
20
+ **Honor `scope.tier_a_include` when present.** When re-deriving, prefer the brief-level `scope.tier_a_include` narrow include list over the coarse `scope.include`. `tier_a_include` is an optional brief field that lists only the authoring surface the brief actually intends to document (tier A), letting the denominator match the brief's authoring-vs-installing intent even when `scope.include` uses coarse globs that also match internal infrastructure. When `tier_a_include` is present, resolve its globs (still filtered by `scope.exclude`), compute the union across those files, and use that count as the denominator. When absent, fall back to resolving `scope.include`.
21
+
22
+ 3. **Denominator inflation check (absent `tier_a_include`).** If re-derivation used `scope.include` because no `tier_a_include` was provided, compare the resulting union count against the provenance-map entry count (when provenance-map exists). If the `scope.include` union is more than 25% larger than the provenance-map entry count, the coarse globs are almost certainly sweeping in internal infrastructure that the brief did not intend to document. Emit a **Medium**-severity gap titled `denominator inflation — coarse scope.include union exceeds authored surface` that points the user at the brief for rescoping via `scope.tier_a_include`. Report both counts (`scope.include union: {N}`, `provenance-map entries: {M}`, `{percent}% inflation`) and state that the coverage score it produced is driven by denominator inflation rather than documentation gaps. The check is skipped when provenance-map is unavailable (there is no baseline to compare against).
23
+
24
+ Leave `analysis_confidence` unchanged (still `full` or `provenance-map` per the waterfall) — stratified scope does not degrade confidence, only the denominator. Annotate the coverage report with: `Stratified scope — denominator: {effective_denominator | tier_a_include union | scope.include union} ({N} files matched, {M} exports union)`.
25
+
26
+ **When this clause does NOT apply:** `scope.type: "full-library"` skills, single-package repositories, or stratified briefs where the full monorepo is intentionally in scope. For those, use the standard barrel-based denominator — **unless** the single-package repo is a pattern-reference app (see next bullet).
27
+
28
+ - **Pattern-reference apps (non-library source):** If the source is a single-package repo whose purpose is demonstrating an integration pattern rather than distributing a library API — typical markers are `scope.type: "full-library"` **without** a barrel file at any recognized entry-point path (`__init__.py`, `index.ts`/`index.js`, `lib.rs`, `mod.rs`) AND without a monorepo layout — the skill's value lives in wiring patterns, not exports. None of the preceding three clauses fits: there is no barrel to count from, no empty-barrel `scope.include` to consult, and no monorepo stratification to re-derive.
29
+
30
+ **Trigger (either fires):**
31
+
32
+ 1. `scope.notes` in `forge-data/{skill_name}/skill-brief.yaml` flags pattern-reference intent (phrases such as "Reference app, not a library", "pattern-reference", "embedded-pattern skill", or "skill value is the … pattern"). The `scope.notes` field is authoritative when the author wrote it.
33
+ 2. Source tree lacks a barrel file at every recognized entry-point path AND the repo is not a monorepo (no `packages/`, `workspaces`, `lerna.json`, `rush.json`, `nx.json`, or Cargo `[workspace]`). Detected at test time by filesystem inspection of `{source_path}`.
34
+
35
+ **Denominator:** canonicalized provenance-map entry count (same canonicalization as the "Provenance-map canonicalization" section below). `skf-create-skill`'s extraction pass has already curated the provenance-map to the authored pattern surface; treat it as the authoritative enumeration of the skill's documented reach.
36
+
37
+ **Recommendation — prefer `tier_a_include`:** authors should add `scope.tier_a_include` to the brief listing the files that constitute the authored pattern surface, the same way stratified-scope briefs do. When `tier_a_include` is present, use its re-derived union (filtered by `scope.exclude`) as the denominator exactly as in the stratified-scope clause. When absent, fall back to the canonicalized provenance-map count — do not fabricate a denominator from arbitrary source-tree sweeps.
38
+
39
+ **Confidence:** leave `analysis_confidence` unchanged (still `full` or `provenance-map` per the waterfall). Pattern-reference does not degrade confidence — the surface is smaller than a library barrel, not lower quality. Annotate the coverage report with: `Pattern-reference — denominator: {tier_a_include union | canonicalized provenance-map count} ({N} pattern surfaces)`.
40
+
41
+ **When this clause does NOT apply:** any repo with a non-empty barrel file, any monorepo (use the stratified-scope clause), or any single-package repo whose `scope.type` is explicitly `public-api` / `specific-modules` / `component-library` / `docs-only` (those scope types have their own denominator semantics). Also does NOT apply if `scope.type: "reference-app"` exists in the enum (pending upgrade in `skf-create-skill/steps-c/step-03-extract.md`) — in that case the brief speaks for itself and this clause's filesystem trigger is moot.
42
+
43
+ Internal module symbols are **excluded** from the coverage denominator unless they are explicitly documented in SKILL.md (in which case they count as documented extras, not missing coverage).
44
+
45
+ This matches the extraction-patterns.md convention used during skill creation: coverage measures how well SKILL.md documents what users actually import, not the entire internal codebase.
46
+
47
+ ### Provenance-map canonicalization
48
+
49
+ When the test-side intersects documented SKILL.md exports against a stratified-scope provenance-map, raw provenance-map entry names may include **bookkeeping variants** of the same underlying export. These variants are artifacts of how the source library structures its registry (e.g., Storybook's component-plus-story decomposition, accessibility renderer shadowing, exact-match versus fuzzy-match renderer disambiguation). Counting them as separate exports inflates the denominator and produces false "missing documentation" findings for names that are structurally duplicates of an already-documented base export.
50
+
51
+ Before intersecting documented names against the provenance-map entry list, **fold bookkeeping variants back to their base name** using the rules below. This matches the convention `skf-create-skill` records in `metadata.json.stats.effective_denominator_source` (e.g., `"provenance-map canonicalized count (ThemesGlobals_def folds with ThemesGlobals under _def convention)"`) — the base form is authoritative; the variant form is a sibling record, not an independent export.
52
+
53
+ **Folding rules (apply in order, case-sensitive):**
54
+
55
+ 1. **Suffix `_def`** — registry definition twin. `ThemesGlobals_def` folds to `ThemesGlobals`. Common in Storybook-style component registries where the definition object and the rendered component share the same public name.
56
+ 2. **Suffix `_exact`** — exact-match variant. `ButtonSpec_exact` folds to `ButtonSpec`. Common in matcher/renderer registries where an `_exact` sibling signals a stricter resolution path.
57
+ 3. **Prefix `a11y_`** — accessibility renderer shadow. `a11y_Checkbox` folds to `Checkbox`. Common in accessibility-wrapper layers that re-export every component under a parallel prefixed namespace.
58
+ 4. **Other renderer-prefix disambiguation** — when the library uses a prefix-namespace convention (e.g., `mobile_`, `web_`, `ssr_`) to shadow the base export, fold the prefix form back to the base. **Only apply when the base form is also present in the provenance-map** — otherwise the prefix form is the real export and should be kept. Document the specific prefix used in the test report so the rule is auditable.
59
+
60
+ **How to apply:**
61
+
62
+ 1. Read all entry names from the provenance-map.
63
+ 2. Build a canonical-name set by applying the folding rules above — each variant maps to its base. Retain the original variant → base mapping for reporting.
64
+ 3. Intersect the documented SKILL.md export names against the **canonical** set, not the raw entry list.
65
+ 4. When computing `Export Coverage`, use the **canonical count** as the denominator — not the raw provenance-map entry count. This aligns the denominator with `metadata.json.stats.effective_denominator` (when present), which `skf-create-skill` already writes as the canonicalized count.
66
+ 5. In the test report, note the fold summary: `Provenance-map canonicalization: {N} raw entries → {M} canonical bases ({N−M} bookkeeping variants folded: _def×{a}, _exact×{b}, a11y_×{c}, other×{d})`. This makes the reduction auditable by future testers and update runs.
67
+
68
+ **When to skip canonicalization:**
69
+
70
+ - If the library's public surface genuinely distinguishes the variants (e.g., `a11y_Checkbox` is a separately-documented, separately-installed component and not a shadow), do not fold — the variant is a real export. Check SKILL.md for explicit documentation of the variant before folding. When in doubt, err on the side of not folding and report both forms.
71
+ - If `metadata.json.stats.effective_denominator` is present and the provenance-map raw count matches it (no drift), canonicalization is not needed — the denominator is already canonical. Fold only when raw count > `effective_denominator` and the drift corresponds to recognizable bookkeeping suffixes/prefixes.
72
+ - If drift remains after folding (e.g., raw 222 → canonical 215 but `effective_denominator` says 216), record the residual 1-count drift as an unexplained-reconciliation note in the test report. Do not fabricate additional fold rules to close the gap.
73
+
74
+ ## Source Access Resolution
75
+
76
+ Before analysis, determine source access level. Walk through these states in order — use the first that succeeds:
77
+
78
+ **State 1 — Local source available:**
79
+ Check if `{source_path}` (from metadata.json `source_root`) exists on disk. If yes → full analysis at detected tier (AST + signatures). Set `analysis_confidence: full`.
80
+
81
+ **State 2 — Local absent, provenance-map exists:**
82
+ Check `{forge_data_folder}/{skill_name}/provenance-map.json`. If present AND contains at least 1 entry, use it as the baseline export inventory — each entry contains structured fields: `export_name`, `export_type`, `params[]`, `return_type`, `source_file`, `source_line`, `confidence`, and `ast_node_type`. Cross-reference against SKILL.md documented exports for name-matching and param-by-param coverage. Signature verification compares SKILL.md's documented params/return types against provenance-map entries directly.
83
+
84
+ **Cross-reference with metadata.json:** After loading provenance-map entries, compare the entry count against `metadata.json`'s `exports[]` array length and `stats.exports_public_api` count. If metadata reports more exports than provenance-map entries:
85
+ - Compute `gap = metadata.exports.length - provenance_map.entries.length`
86
+ - Report: "Provenance-map contains {pmap_count} entries but metadata.json lists {meta_count} exports ({gap} gap). Coverage denominator uses the union."
87
+ - Build the coverage denominator from the **union** of provenance-map entry names and metadata.json `exports[]` names. Exports present in metadata but absent from provenance-map are counted as "missing documentation" in the coverage calculation.
88
+ - If metadata.json is unavailable or has no `exports[]` array, use provenance-map count alone with a note: "Coverage denominator is provenance-map only — may undercount if extraction was incomplete." If remote reading tools are available (zread, deepwiki, gh API, or similar), supplement by reading the entry point file for live signature verification. Set `analysis_confidence: provenance-map`.
89
+
90
+ **State 2 limitations:** Signature verification at State 2 is **string comparison only**, not semantic. Provenance-map stores parameters as flat string arrays (e.g., `["data: Union[BinaryIO, list, str]"]`), so `str` vs `String` or `list` vs `List[Any]` would be treated as mismatches even when semantically equivalent. For full type-aware verification (handling type aliases, generic equivalence), State 1 (local source) with AST re-parsing is required. When the SKILL.md was compiled from the same provenance-map (typical for create-then-test flows), most strings will match. However, enrichment (step-04) and doc-fetching (step-03c) during compilation may alter parameter descriptions, add type annotations, or normalize signatures, causing mismatches even in create-then-test flows. Expect some string-level mismatches and treat them as compilation artifacts, not source drift signals, until signature fidelity is enforced by step-05's Signature Fidelity Rule (see `signature_source` field in provenance-map entries).
91
+
92
+ **State 3 — No provenance-map, metadata exports exist (quick-skill path):**
93
+ If no provenance-map.json exists (typical for quick-skill output), fall back to `metadata.json`'s `exports[]` array for the export name list. Coverage check becomes a self-consistency comparison: are all names in `exports[]` documented in SKILL.md with description, parameters, and return type? Signatures cannot be verified. If remote reading tools are available, supplement by reading the entry point for live export comparison. Set `analysis_confidence: metadata-only`.
94
+
95
+ **State 4 — No local source, no forge-data, remote tools available:**
96
+ If neither provenance-map nor metadata exports provide a usable baseline, but remote reading tools (zread, deepwiki, gh API, or similar) are available and `source_repo` is set in metadata.json, read the entry point remotely to build the export inventory from scratch. Name-matching only — no AST. Set `analysis_confidence: remote-only`.
97
+
98
+ **State 5 — No source access at all:**
99
+ If none of the above succeed, fall through to docs-only mode (as defined in step-03-coverage-check.md Section 0: pre-analysis source type detection). Set `analysis_confidence: docs-only`. Warn: "**No source access available.** Coverage check evaluates documentation self-consistency only. Re-run with local clone or remote access for source-backed verification."
100
+
101
+ Set `analysis_confidence` in context for use in Section 2 analysis depth, step-05 output, and step-05 scoring.
102
+
103
+ **Confidence tier mapping:** `full` = T1, `provenance-map` = T1, `metadata-only` = T1-low, `remote-only` = T1-low, `docs-only` = T3. This aligns with the T1/T1-low/T2/T3 scale used across all SKF workflows.
104
+
105
+ **Degradation notice rules:** When `analysis_confidence` is `provenance-map`, check the `confidence` field of provenance-map entries before emitting a degradation recommendation:
106
+
107
+ - **All/most entries T1 (AST-verified):** The provenance-map data is already at highest confidence. Do NOT recommend re-running with a local clone — it would produce identical results. Use: "Resolved via: provenance-map (T1 AST-verified at compilation time). Local clone not required — provenance data is already at highest confidence."
108
+ - **Mixed T1/T1-low entries:** Report the breakdown. Recommend local clone only for the T1-low entries: "Resolved via: provenance-map ({n} T1, {m} T1-low). Re-run with local clone to upgrade T1-low entries to full AST verification."
109
+ - **All/most entries T1-low or lower:** Keep the standard recommendation: "Re-run with local clone for full AST-backed verification."
@@ -0,0 +1,310 @@
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = []
4
+ # ///
5
+ """Deterministic Completeness Score Calculator.
6
+
7
+ Pure-function scoring script for the SKF test-skill workflow (step-05).
8
+ Implements the weight tables, skip conditions, and proportional redistribution
9
+ defined in scoring-rules.md.
10
+
11
+ CLI: python3 compute-score.py '<JSON>'
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import math
18
+ import sys
19
+
20
+ # --- Weight Tables (from scoring-rules.md) ---
21
+
22
+ CONTEXTUAL_WEIGHTS = {
23
+ "exportCoverage": 36,
24
+ "signatureAccuracy": 22,
25
+ "typeCoverage": 14,
26
+ "coherence": 18,
27
+ "externalValidation": 10,
28
+ }
29
+
30
+ NAIVE_WEIGHTS = {
31
+ "exportCoverage": 45,
32
+ "signatureAccuracy": 25,
33
+ "typeCoverage": 20,
34
+ "coherence": 0,
35
+ "externalValidation": 10,
36
+ }
37
+
38
+ CATEGORIES = [
39
+ "exportCoverage",
40
+ "signatureAccuracy",
41
+ "typeCoverage",
42
+ "coherence",
43
+ "externalValidation",
44
+ ]
45
+
46
+ DEFAULT_THRESHOLD = 80
47
+
48
+
49
+ # --- Helpers ---
50
+
51
+
52
+ def round2(value):
53
+ """Round to 2 decimal places using JavaScript-compatible rounding.
54
+
55
+ JavaScript Math.round rounds .5 up (away from zero for positives).
56
+ Python's built-in round uses banker's rounding (.5 to even).
57
+ We replicate JS behavior: floor(value * 100 + 0.5) / 100.
58
+ """
59
+ return math.floor(value * 100 + 0.5) / 100
60
+
61
+
62
+ def make_error(message):
63
+ return {"error": message, "code": "INVALID_INPUT"}
64
+
65
+
66
+ # --- Validation ---
67
+
68
+
69
+ BOOL_FIELDS = ("docsOnly", "state2", "stackSkill")
70
+
71
+
72
+ def validate_input(inp):
73
+ if inp is None or not isinstance(inp, dict):
74
+ return "Input must be a JSON object"
75
+
76
+ if not inp.get("mode") or inp["mode"] not in ("contextual", "naive"):
77
+ return 'Missing or invalid required field: mode (must be "contextual" or "naive")'
78
+
79
+ valid_tiers = ["Quick", "Forge", "Forge+", "Deep"]
80
+ if not inp.get("tier") or inp["tier"] not in valid_tiers:
81
+ return f"Missing or invalid required field: tier (must be one of: {', '.join(valid_tiers)})"
82
+
83
+ # H3: reject string booleans — bare true/false only.
84
+ # `bool` is a subclass of `int` in Python; accept only actual bools or the
85
+ # absence of the field. Strings like "true"/"false" and ints 0/1 are rejected
86
+ # to catch the common YAML/JSON hand-editing mistake of quoting the value.
87
+ for field in BOOL_FIELDS:
88
+ if field in inp and inp[field] is not None:
89
+ if not isinstance(inp[field], bool):
90
+ return (
91
+ f"Field `{field}` must be a bare boolean (true/false). "
92
+ f"Got {type(inp[field]).__name__}: {inp[field]!r}. "
93
+ "String 'true'/'false' or 0/1 is not accepted — "
94
+ "this typically indicates a YAML/JSON quoting mistake."
95
+ )
96
+
97
+ if "scores" not in inp or not isinstance(inp.get("scores"), dict):
98
+ return "Missing required field: scores"
99
+
100
+ if inp["scores"].get("exportCoverage") is None:
101
+ return "scores.exportCoverage is required and cannot be null"
102
+
103
+ threshold = inp.get("threshold")
104
+ if threshold is not None:
105
+ if not isinstance(threshold, (int, float)) or threshold < 0 or threshold > 100:
106
+ return "threshold must be a number between 0 and 100"
107
+
108
+ for cat in CATEGORIES:
109
+ score = inp["scores"].get(cat)
110
+ if score is not None:
111
+ if isinstance(score, bool) or not isinstance(score, (int, float)):
112
+ return f"scores.{cat} must be a number or null, got: {type(score).__name__}"
113
+ if score < 0 or score > 100:
114
+ return f"scores.{cat} must be between 0 and 100, got: {score}"
115
+
116
+ return None
117
+
118
+
119
+ # --- Core Scoring Function ---
120
+
121
+
122
+ def compute_score(inp):
123
+ # 1. Validate
124
+ validation_error = validate_input(inp)
125
+ if validation_error:
126
+ return make_error(validation_error)
127
+
128
+ mode = inp["mode"]
129
+ tier = inp["tier"]
130
+ docs_only = inp.get("docsOnly") is True
131
+ state2 = inp.get("state2") is True
132
+ stack_skill = inp.get("stackSkill") is True
133
+ threshold = inp.get("threshold") if inp.get("threshold") is not None else DEFAULT_THRESHOLD
134
+ scores = inp["scores"]
135
+
136
+ # 2. Select base weight table
137
+ base_weights = dict(NAIVE_WEIGHTS if mode == "naive" else CONTEXTUAL_WEIGHTS)
138
+
139
+ # 3. Determine skip set
140
+ skip_reasons = {}
141
+ skip_sig_type = tier == "Quick" or docs_only or state2 or stack_skill
142
+
143
+ if skip_sig_type:
144
+ reasons = []
145
+ if tier == "Quick":
146
+ reasons.append("Quick tier")
147
+ if docs_only:
148
+ reasons.append("docs-only mode")
149
+ if state2:
150
+ reasons.append("State 2 (provenance-map)")
151
+ if stack_skill:
152
+ reasons.append("stack skill (external type surface)")
153
+ reason = " + ".join(reasons)
154
+ skip_reasons["signatureAccuracy"] = reason
155
+ skip_reasons["typeCoverage"] = reason
156
+
157
+ if scores.get("externalValidation") is None:
158
+ skip_reasons["externalValidation"] = "No external validators available"
159
+
160
+ # Collect warnings
161
+ warnings = []
162
+ for cat in skip_reasons:
163
+ if scores.get(cat) is not None:
164
+ warnings.append(
165
+ f"{cat} score provided ({scores[cat]}) but category is skipped — score ignored"
166
+ )
167
+
168
+ # Validate active categories have scores
169
+ skipped_set = set(skip_reasons.keys())
170
+ for cat in CATEGORIES:
171
+ is_active = cat not in skipped_set and base_weights[cat] > 0
172
+ score_missing = scores.get(cat) is None
173
+ if is_active and score_missing:
174
+ return make_error(
175
+ f"Category {cat} is active but score is null. "
176
+ "Provide a numeric score or set the appropriate skip condition."
177
+ )
178
+
179
+ # 4. Redistribute weights
180
+ adjusted_weights = dict(base_weights)
181
+ for cat in skip_reasons:
182
+ adjusted_weights[cat] = 0
183
+
184
+ sum_active_weights = sum(adjusted_weights[cat] for cat in CATEGORIES)
185
+
186
+ final_weights = {}
187
+ for cat in CATEGORIES:
188
+ if adjusted_weights[cat] == 0:
189
+ final_weights[cat] = 0
190
+ else:
191
+ final_weights[cat] = round2((adjusted_weights[cat] / sum_active_weights) * 100)
192
+
193
+ # 5. Compute weighted scores
194
+ weighted_scores = {}
195
+ for cat in CATEGORIES:
196
+ if final_weights[cat] == 0:
197
+ weighted_scores[cat] = 0
198
+ else:
199
+ weighted_scores[cat] = round2((final_weights[cat] / 100) * scores[cat])
200
+
201
+ # 6. Compute total
202
+ total_score = round2(sum(weighted_scores[cat] for cat in CATEGORIES))
203
+
204
+ # Weight sum for verification
205
+ weight_sum = round2(sum(final_weights[cat] for cat in CATEGORIES))
206
+
207
+ # 7. Determine result — MINIMUM-EVIDENCE FLOOR first, then PASS/FAIL.
208
+ # skf-test-skill grades other skills; a false PASS is catastrophic.
209
+ # If the evidence base is too thin to cross-validate itself, force
210
+ # INCONCLUSIVE (a gate, not a pass/fail). See scoring-rules.md.
211
+ active_categories = [cat for cat in CATEGORIES if final_weights[cat] > 0]
212
+ skipped_categories = [
213
+ cat for cat in CATEGORIES if cat in skipped_set or base_weights[cat] == 0
214
+ ]
215
+
216
+ floor_reasons = []
217
+ if len(active_categories) < 2:
218
+ floor_reasons.append(
219
+ f"insufficient evidence: only {len(active_categories)} active category"
220
+ )
221
+ elif tier == "Quick" and active_categories == ["exportCoverage"]:
222
+ # Defensive second check: if somehow there are >= 2 active categories
223
+ # but they collapse to just exportCoverage (shouldn't happen given the
224
+ # first clause, kept for robustness), still force INCONCLUSIVE.
225
+ floor_reasons.append(
226
+ "Quick tier: Export Coverage alone is insufficient evidence "
227
+ "— add a second active category by upgrading tier or enabling "
228
+ "external validators"
229
+ )
230
+ elif tier == "Quick":
231
+ # Cover the case where Export Coverage is the only active category
232
+ # carrying signal in Quick tier even when technically another
233
+ # non-contributing category survived redistribution.
234
+ non_export_active_scores = [
235
+ scores.get(cat) for cat in active_categories if cat != "exportCoverage"
236
+ ]
237
+ # All other active categories have a zero score => Export Coverage is
238
+ # the sole real contributor.
239
+ if active_categories and "exportCoverage" in active_categories and all(
240
+ (s == 0) for s in non_export_active_scores
241
+ ) and non_export_active_scores:
242
+ floor_reasons.append(
243
+ "Quick tier: Export Coverage is the sole scoring contributor "
244
+ "(other active categories scored 0) — insufficient evidence"
245
+ )
246
+
247
+ if floor_reasons:
248
+ result = "INCONCLUSIVE"
249
+ else:
250
+ result = "PASS" if total_score >= threshold else "FAIL"
251
+
252
+ # Build scores echo with null preservation
253
+ scores_echo = {}
254
+ for cat in CATEGORIES:
255
+ scores_echo[cat] = scores.get(cat)
256
+
257
+ output = {
258
+ "input": {
259
+ "mode": mode,
260
+ "tier": tier,
261
+ "docsOnly": docs_only,
262
+ "state2": state2,
263
+ "stackSkill": stack_skill,
264
+ "threshold": threshold,
265
+ "scores": scores_echo,
266
+ },
267
+ "activeCategories": active_categories,
268
+ "skippedCategories": skipped_categories,
269
+ "skipReasons": skip_reasons,
270
+ "weights": final_weights,
271
+ "weightedScores": weighted_scores,
272
+ "totalScore": total_score,
273
+ "threshold": threshold,
274
+ "result": result,
275
+ "weightSum": weight_sum,
276
+ }
277
+
278
+ if warnings:
279
+ output["warnings"] = warnings
280
+
281
+ if floor_reasons:
282
+ output["inconclusiveReasons"] = floor_reasons
283
+
284
+ return output
285
+
286
+
287
+ # --- CLI Entry Point ---
288
+
289
+ if __name__ == "__main__":
290
+ if len(sys.argv) < 2:
291
+ print("Usage: python3 compute-score.py '<JSON>'", file=sys.stderr)
292
+ print(
293
+ 'Example: python3 compute-score.py \'{"mode":"contextual","tier":"Deep",'
294
+ '"scores":{"exportCoverage":92,"signatureAccuracy":85,"typeCoverage":100,'
295
+ '"coherence":80,"externalValidation":78}}\'',
296
+ file=sys.stderr,
297
+ )
298
+ sys.exit(1)
299
+
300
+ try:
301
+ data = json.loads(sys.argv[1])
302
+ except json.JSONDecodeError:
303
+ print(
304
+ json.dumps(make_error(f"Invalid JSON: {sys.argv[1][:100]}"), indent=2)
305
+ )
306
+ sys.exit(1)
307
+
308
+ result = compute_score(data)
309
+ print(json.dumps(result, indent=2))
310
+ sys.exit(1 if "error" in result else 0)