bmad-module-skill-forge 2.1.0 → 3.0.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 (435) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +51 -49
  3. package/docs/404.md +10 -8
  4. package/docs/_data/pinned.yaml +22 -13
  5. package/docs/_internal/STABILITY.md +58 -35
  6. package/docs/agents.md +39 -53
  7. package/docs/architecture.md +65 -51
  8. package/docs/bmad-synergy.md +53 -51
  9. package/docs/campaign.md +127 -75
  10. package/docs/concepts.md +34 -28
  11. package/docs/examples.md +70 -68
  12. package/docs/forge-auto.md +34 -31
  13. package/docs/getting-started.md +118 -57
  14. package/docs/how-it-works.md +25 -21
  15. package/docs/index.md +15 -15
  16. package/docs/skill-model.md +70 -59
  17. package/docs/troubleshooting.md +135 -18
  18. package/docs/verifying-a-skill.md +83 -57
  19. package/docs/why-skf.md +24 -22
  20. package/docs/workflows.md +274 -110
  21. package/package.json +18 -10
  22. package/src/forger/preferences.yaml +8 -1
  23. package/src/knowledge/agentskills-spec.md +2 -0
  24. package/src/knowledge/ccc-bridge.md +40 -24
  25. package/src/knowledge/confidence-tiers.md +9 -5
  26. package/src/knowledge/overview.md +3 -3
  27. package/src/knowledge/progressive-capability.md +8 -8
  28. package/src/knowledge/provenance-tracking.md +9 -5
  29. package/src/knowledge/qmd-registry.md +2 -2
  30. package/src/knowledge/skf-knowledge-index.csv +2 -2
  31. package/src/knowledge/skill-lifecycle.md +18 -11
  32. package/src/knowledge/split-body-strategy.md +0 -3
  33. package/src/knowledge/tool-resolution.md +4 -2
  34. package/src/knowledge/version-paths.md +101 -15
  35. package/src/module-help.csv +3 -3
  36. package/src/module.yaml +10 -0
  37. package/src/shared/data/ast-grep-recipes.yaml +608 -0
  38. package/src/shared/data/ide-context-files.json +30 -0
  39. package/src/shared/health-check.md +32 -12
  40. package/src/shared/references/description-guard-protocol.md +14 -9
  41. package/src/shared/references/feasibility-report-schema.md +19 -13
  42. package/src/shared/references/headless-gate-convention.md +55 -3
  43. package/src/shared/references/output-contract-schema.md +19 -8
  44. package/src/shared/references/pipeline-contracts.md +94 -29
  45. package/src/shared/references/tessl-review.md +77 -0
  46. package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
  47. package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
  48. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
  49. package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
  50. package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
  51. package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
  52. package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
  53. package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
  54. package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
  55. package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
  56. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
  57. package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
  58. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
  59. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
  60. package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
  61. package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
  62. package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
  63. package/src/shared/scripts/skf-atomic-write.py +30 -14
  64. package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
  65. package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
  66. package/src/shared/scripts/skf-check-unit-records.py +184 -0
  67. package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
  68. package/src/shared/scripts/skf-classify-changed-files.py +694 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
  71. package/src/shared/scripts/skf-count-imports.py +1867 -0
  72. package/src/shared/scripts/skf-count-tokens.py +1 -1
  73. package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
  74. package/src/shared/scripts/skf-description-guard.py +117 -9
  75. package/src/shared/scripts/skf-detect-docs.py +892 -4
  76. package/src/shared/scripts/skf-detect-language.py +486 -107
  77. package/src/shared/scripts/skf-detect-registry.py +887 -0
  78. package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
  79. package/src/shared/scripts/skf-detect-tools.py +215 -27
  80. package/src/shared/scripts/skf-detect-workspaces.py +506 -30
  81. package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
  82. package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
  83. package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
  84. package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
  85. package/src/shared/scripts/skf-extract-public-api.py +4084 -85
  86. package/src/shared/scripts/skf-extraction-inventory.py +746 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
  88. package/src/shared/scripts/skf-fetch-temporal.py +674 -0
  89. package/src/shared/scripts/skf-find-cycles.py +97 -29
  90. package/src/shared/scripts/skf-find-test-report.py +476 -0
  91. package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +661 -0
  94. package/src/shared/scripts/skf-hash-content.py +166 -12
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +159 -27
  97. package/src/shared/scripts/skf-manifest-ops.py +1 -1
  98. package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
  99. package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
  100. package/src/shared/scripts/skf-names-present.py +221 -0
  101. package/src/shared/scripts/skf-pair-intersect.py +119 -28
  102. package/src/shared/scripts/skf-parse-gaps.py +1384 -0
  103. package/src/shared/scripts/skf-preapply.py +1 -1
  104. package/src/shared/scripts/skf-preflight.py +161 -27
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
  107. package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
  108. package/src/shared/scripts/skf-quick-batch.py +774 -0
  109. package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
  110. package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
  111. package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
  112. package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
  113. package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
  114. package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
  115. package/src/shared/scripts/skf-resolve-package.py +523 -106
  116. package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
  117. package/src/shared/scripts/skf-run-lock.py +577 -0
  118. package/src/shared/scripts/skf-scan-manifests.py +998 -57
  119. package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
  120. package/src/shared/scripts/skf-severity-classify.py +712 -108
  121. package/src/shared/scripts/skf-shape-detect.py +298 -204
  122. package/src/shared/scripts/skf-shard-body.py +9 -8
  123. package/src/shared/scripts/skf-skill-inventory.py +2412 -89
  124. package/src/shared/scripts/skf-skills-module.py +472 -0
  125. package/src/shared/scripts/skf-source-tree.py +1796 -0
  126. package/src/shared/scripts/skf-structural-diff.py +670 -119
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +5 -5
  129. package/src/shared/scripts/skf-update-run-state.py +624 -0
  130. package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
  131. package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
  132. package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
  133. package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
  134. package/src/shared/scripts/skf-validate-output.py +712 -64
  135. package/src/shared/scripts/skf-validate-pins.py +1 -1
  136. package/src/shared/scripts/skf-verify-no-trace.py +116 -10
  137. package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
  138. package/src/shared/scripts/skf-write-skill-brief.py +367 -19
  139. package/src/shared/tool-requirements.yaml +201 -0
  140. package/src/skf-analyze-source/SKILL.md +41 -36
  141. package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
  142. package/src/skf-analyze-source/customize.toml +36 -24
  143. package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
  144. package/src/skf-analyze-source/references/continue.md +2 -13
  145. package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
  146. package/src/skf-analyze-source/references/generate-briefs.md +121 -55
  147. package/src/skf-analyze-source/references/headless-contract.md +42 -15
  148. package/src/skf-analyze-source/references/identify-units.md +96 -110
  149. package/src/skf-analyze-source/references/init.md +73 -71
  150. package/src/skf-analyze-source/references/map-and-detect.md +123 -105
  151. package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
  152. package/src/skf-analyze-source/references/recommend.md +51 -70
  153. package/src/skf-analyze-source/references/scan-project.md +60 -84
  154. package/src/skf-analyze-source/references/scan-root.md +39 -0
  155. package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
  156. package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
  157. package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
  158. package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
  159. package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
  160. package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
  161. package/src/skf-audit-skill/SKILL.md +43 -51
  162. package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
  163. package/src/skf-audit-skill/customize.toml +41 -20
  164. package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
  165. package/src/skf-audit-skill/references/doc-drift.md +141 -0
  166. package/src/skf-audit-skill/references/headless-contract.md +29 -0
  167. package/src/skf-audit-skill/references/init.md +148 -210
  168. package/src/skf-audit-skill/references/re-index.md +96 -108
  169. package/src/skf-audit-skill/references/report.md +96 -33
  170. package/src/skf-audit-skill/references/semantic-diff.md +27 -18
  171. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  172. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  173. package/src/skf-audit-skill/references/structural-diff.md +75 -90
  174. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  175. package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
  176. package/src/skf-brief-skill/SKILL.md +36 -25
  177. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  178. package/src/skf-brief-skill/assets/scope-templates.md +17 -24
  179. package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
  180. package/src/skf-brief-skill/customize.toml +48 -20
  181. package/src/skf-brief-skill/references/analyze-target.md +142 -125
  182. package/src/skf-brief-skill/references/confirm-brief.md +10 -8
  183. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  184. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  185. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  186. package/src/skf-brief-skill/references/headless-args.md +84 -16
  187. package/src/skf-brief-skill/references/health-check.md +1 -1
  188. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  189. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  190. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  191. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  192. package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
  193. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  194. package/src/skf-brief-skill/references/version-resolution.md +3 -2
  195. package/src/skf-brief-skill/references/write-brief.md +66 -57
  196. package/src/skf-campaign/SKILL.md +31 -34
  197. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  198. package/src/skf-campaign/customize.toml +47 -16
  199. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  200. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  201. package/src/skf-campaign/references/health-check.md +7 -16
  202. package/src/skf-campaign/references/step-01-setup.md +46 -84
  203. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  204. package/src/skf-campaign/references/step-03-pins.md +26 -18
  205. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  206. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  207. package/src/skf-campaign/references/step-06-batch.md +41 -34
  208. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  209. package/src/skf-campaign/references/step-08-verify.md +28 -29
  210. package/src/skf-campaign/references/step-09-refine.md +29 -30
  211. package/src/skf-campaign/references/step-10-export.md +48 -36
  212. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  213. package/src/skf-campaign/references/step-resume.md +37 -68
  214. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  215. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  216. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  217. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  218. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  219. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  220. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  221. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  222. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  223. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  224. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  225. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  226. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  227. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  228. package/src/skf-create-skill/SKILL.md +27 -20
  229. package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
  230. package/src/skf-create-skill/assets/skill-sections.md +17 -6
  231. package/src/skf-create-skill/customize.toml +20 -8
  232. package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
  233. package/src/skf-create-skill/references/batch-mode.md +81 -0
  234. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  235. package/src/skf-create-skill/references/compile.md +107 -29
  236. package/src/skf-create-skill/references/component-extraction.md +113 -91
  237. package/src/skf-create-skill/references/enrich.md +41 -23
  238. package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
  239. package/src/skf-create-skill/references/extract.md +200 -161
  240. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  241. package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
  242. package/src/skf-create-skill/references/extraction-patterns.md +584 -196
  243. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  244. package/src/skf-create-skill/references/health-check.md +1 -1
  245. package/src/skf-create-skill/references/load-brief.md +53 -36
  246. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  247. package/src/skf-create-skill/references/report.md +28 -59
  248. package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
  249. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  250. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  251. package/src/skf-create-skill/references/step-doc-sources.md +55 -18
  252. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  253. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
  254. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  255. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  256. package/src/skf-create-skill/references/validate.md +173 -99
  257. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  258. package/src/skf-create-stack-skill/SKILL.md +29 -56
  259. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  260. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  261. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  262. package/src/skf-create-stack-skill/customize.toml +23 -12
  263. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  264. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  265. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  266. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  267. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  268. package/src/skf-create-stack-skill/references/init.md +90 -32
  269. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  270. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  271. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  272. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  273. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  274. package/src/skf-create-stack-skill/references/report.md +39 -33
  275. package/src/skf-create-stack-skill/references/validate.md +20 -39
  276. package/src/skf-drop-skill/SKILL.md +28 -51
  277. package/src/skf-drop-skill/customize.toml +44 -30
  278. package/src/skf-drop-skill/references/execute.md +79 -146
  279. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  280. package/src/skf-drop-skill/references/report.md +46 -30
  281. package/src/skf-drop-skill/references/select.md +118 -124
  282. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  283. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  284. package/src/skf-export-skill/SKILL.md +40 -62
  285. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  286. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  287. package/src/skf-export-skill/customize.toml +42 -13
  288. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  289. package/src/skf-export-skill/references/health-check.md +2 -0
  290. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  291. package/src/skf-export-skill/references/load-skill.md +88 -62
  292. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  293. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  294. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  295. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  296. package/src/skf-export-skill/references/package.md +15 -73
  297. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  298. package/src/skf-export-skill/references/result-envelope.md +31 -16
  299. package/src/skf-export-skill/references/summary.md +41 -17
  300. package/src/skf-export-skill/references/token-report.md +4 -12
  301. package/src/skf-export-skill/references/update-context.md +118 -237
  302. package/src/skf-forger/SKILL.md +61 -37
  303. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  304. package/src/skf-forger/customize.toml +43 -0
  305. package/src/skf-forger/references/pipeline-mode.md +73 -18
  306. package/src/skf-forger/scripts/forge-status.py +299 -0
  307. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  308. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  309. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  310. package/src/skf-quick-skill/SKILL.md +54 -53
  311. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  312. package/src/skf-quick-skill/customize.toml +51 -20
  313. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  314. package/src/skf-quick-skill/references/compile.md +30 -31
  315. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  316. package/src/skf-quick-skill/references/finalize.md +69 -18
  317. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  318. package/src/skf-quick-skill/references/health-check.md +5 -3
  319. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  320. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  321. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  322. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  323. package/src/skf-refine-architecture/SKILL.md +43 -53
  324. package/src/skf-refine-architecture/customize.toml +49 -20
  325. package/src/skf-refine-architecture/references/compile.md +97 -54
  326. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  327. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  328. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  329. package/src/skf-refine-architecture/references/improvements.md +14 -20
  330. package/src/skf-refine-architecture/references/init.md +98 -57
  331. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  332. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  333. package/src/skf-refine-architecture/references/report.md +65 -54
  334. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  335. package/src/skf-rename-skill/SKILL.md +19 -54
  336. package/src/skf-rename-skill/customize.toml +32 -26
  337. package/src/skf-rename-skill/references/execute.md +230 -184
  338. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  339. package/src/skf-rename-skill/references/health-check.md +1 -7
  340. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  341. package/src/skf-rename-skill/references/report.md +63 -52
  342. package/src/skf-rename-skill/references/select.md +197 -115
  343. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  344. package/src/skf-setup/SKILL.md +26 -26
  345. package/src/skf-setup/customize.toml +14 -10
  346. package/src/skf-setup/references/auto-index.md +50 -63
  347. package/src/skf-setup/references/ccc-index.md +29 -89
  348. package/src/skf-setup/references/detect-and-tier.md +58 -66
  349. package/src/skf-setup/references/health-check.md +2 -0
  350. package/src/skf-setup/references/invocation-contract.md +14 -0
  351. package/src/skf-setup/references/report.md +43 -166
  352. package/src/skf-setup/references/tier-rules.md +5 -5
  353. package/src/skf-setup/references/write-config.md +34 -57
  354. package/src/skf-test-skill/SKILL.md +17 -57
  355. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  356. package/src/skf-test-skill/customize.toml +27 -13
  357. package/src/skf-test-skill/references/coherence-check.md +134 -62
  358. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  359. package/src/skf-test-skill/references/coverage-check.md +217 -254
  360. package/src/skf-test-skill/references/detect-mode.md +5 -5
  361. package/src/skf-test-skill/references/external-validators.md +94 -82
  362. package/src/skf-test-skill/references/health-check.md +6 -7
  363. package/src/skf-test-skill/references/init.md +163 -88
  364. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  365. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  366. package/src/skf-test-skill/references/report.md +162 -142
  367. package/src/skf-test-skill/references/score.md +80 -107
  368. package/src/skf-test-skill/references/scoring-rules.md +41 -155
  369. package/src/skf-test-skill/references/source-access-protocol.md +19 -92
  370. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  371. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  372. package/src/skf-test-skill/scripts/build-result-context.py +287 -0
  373. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  374. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  375. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  376. package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
  377. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  378. package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
  379. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  380. package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
  381. package/src/skf-test-skill/scripts/score-signatures.py +459 -0
  382. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  383. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  384. package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
  385. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  386. package/src/skf-update-skill/SKILL.md +38 -25
  387. package/src/skf-update-skill/customize.toml +27 -12
  388. package/src/skf-update-skill/references/detect-changes.md +233 -222
  389. package/src/skf-update-skill/references/gap-driven.md +336 -0
  390. package/src/skf-update-skill/references/health-check.md +31 -5
  391. package/src/skf-update-skill/references/init.md +250 -84
  392. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  393. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  394. package/src/skf-update-skill/references/merge.md +180 -47
  395. package/src/skf-update-skill/references/re-extract.md +53 -262
  396. package/src/skf-update-skill/references/report.md +100 -35
  397. package/src/skf-update-skill/references/write.md +331 -149
  398. package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
  399. package/src/skf-verify-stack/SKILL.md +23 -42
  400. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  401. package/src/skf-verify-stack/customize.toml +55 -22
  402. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  403. package/src/skf-verify-stack/references/coverage.md +67 -64
  404. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  405. package/src/skf-verify-stack/references/init.md +107 -62
  406. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  407. package/src/skf-verify-stack/references/integrations.md +106 -65
  408. package/src/skf-verify-stack/references/report.md +59 -42
  409. package/src/skf-verify-stack/references/requirements.md +32 -9
  410. package/src/skf-verify-stack/references/synthesize.md +53 -39
  411. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  412. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  413. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  414. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  415. package/tools/cli/commands/install.js +11 -4
  416. package/tools/cli/commands/status.js +15 -3
  417. package/tools/cli/commands/update.js +11 -4
  418. package/tools/cli/lib/tool-check.js +320 -0
  419. package/tools/cli/lib/version-check.js +22 -9
  420. package/tools/cli/skf-cli.js +4 -1
  421. package/.gitattributes +0 -16
  422. package/.nvmrc +0 -1
  423. package/docs/_internal/RELEASING.md +0 -645
  424. package/release-audits/v1.0.0-launch-audit.md +0 -728
  425. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  426. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  427. package/src/skf-campaign/manifest.yaml +0 -15
  428. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  429. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  430. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  431. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  432. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  433. package/src/skf-update-skill/references/validate.md +0 -124
  434. package/tools/validate-docs-drift.js +0 -233
  435. package/tools/validate-docs-links.js +0 -665
@@ -1,5 +1,5 @@
1
1
  # /// script
2
- # requires-python = ">=3.9"
2
+ # requires-python = ">=3.11"
3
3
  # dependencies = []
4
4
  # ///
5
5
  """SKF Merge Doc URLs — merge corpora-seeded + detected docs for a skill brief.
@@ -0,0 +1,221 @@
1
+ # /// script
2
+ # requires-python = ">=3.11"
3
+ # dependencies = []
4
+ # ///
5
+ """SKF Names Present: the provenance entries whose export name a skill package never writes.
6
+
7
+ skf-test-skill credits a provenance entry toward a stack's Export Coverage
8
+ only when the package writes its `export_name`: reconcile-coverage.py
9
+ (`load_doc_text`, `names_present`) joins the text of `SKILL.md` and of each
10
+ `.md` file directly in `references/` (sorted, read as UTF-8, no subfolder)
11
+ and looks for the name in it as a fixed string, case-sensitive, never inside
12
+ a longer identifier: on a side where the name ends in a letter, digit, `_`
13
+ or `$`, the next character may not be one of those, so `get` is not found in
14
+ `target` or `getAll` (skf-test-skill's validate-inventory.py keeps that
15
+ rule; NAME_CHARS here must stay the same). A name holding
16
+ `::` (an impl-block method, `Type::method`) rolls up under its type and is
17
+ never looked up, and an entry with no name is no cited contract.
18
+ create-stack-skill (generate-output section 8) runs this helper on a
19
+ compose-mode map before the commit, so a stack never commits a name
20
+ skf-test-skill will not credit. The rule here is the scorer's:
21
+ test/test-skf-stack-step-rules.py runs both on the same package.
22
+
23
+ Usage:
24
+ uv run skf-names-present.py --provenance <provenance-map.json> \\
25
+ --skill-dir <dir> [--drop-absent]
26
+
27
+ Output (stdout):
28
+ {
29
+ "checked": <int>, # entries whose export_name was looked up
30
+ "skipped": <int>, # entries with a `::` name or no name
31
+ "absent": [ # in entries[] order
32
+ {"entry_index": <int>, "export_name": "<name>",
33
+ "source_library": "<library>" | null},
34
+ ...
35
+ ],
36
+ "dropped": [...], # --drop-absent: the absent entries removed
37
+ "provenance_written": <bool>
38
+ }
39
+
40
+ --drop-absent removes every absent entry from the map and rewrites it
41
+ through skf-atomic-write.py beside this script (JSON indented by two
42
+ spaces, non-ASCII kept, a final newline). `absent[]` still lists what the
43
+ lookup found, and `dropped[]` repeats it. `entry_index` is the position in
44
+ the map as it was read.
45
+
46
+ Exit codes:
47
+ 0 no entry is absent, or --drop-absent removed every absent entry
48
+ 1 an entry is absent (absent[] lists it)
49
+ 2 usage, file, JSON or write error (one line on stderr, no JSON)
50
+ """
51
+
52
+ from __future__ import annotations
53
+
54
+ import argparse
55
+ import json
56
+ import re
57
+ import subprocess
58
+ import sys
59
+ from pathlib import Path
60
+
61
+ ATOMIC_WRITE_HELPER = "skf-atomic-write.py"
62
+ ATOMIC_WRITE_TIMEOUT_SEC = 60
63
+ METHOD_SEPARATOR = "::"
64
+ # Characters that continue an identifier in the languages SKF documents (`$`
65
+ # for JS/TS names such as `$state`): skf-test-skill's IDENT_CHARS.
66
+ NAME_CHARS = r"\w$"
67
+ _NAME_CHAR_RE = re.compile(f"[{NAME_CHARS}]")
68
+
69
+
70
+ def load_doc_text(skill_dir: Path) -> str:
71
+ """The text skf-test-skill looks names up in: SKILL.md, then each .md file
72
+ directly in references/ in sorted order, joined by newlines. Raises
73
+ OSError, or UnicodeDecodeError for a file that is not UTF-8."""
74
+ parts = []
75
+ skill_md = skill_dir / "SKILL.md"
76
+ if skill_md.is_file():
77
+ parts.append(skill_md.read_text(encoding="utf-8"))
78
+ refs = skill_dir / "references"
79
+ if refs.is_dir():
80
+ for ref in sorted(refs.glob("*.md")):
81
+ parts.append(ref.read_text(encoding="utf-8"))
82
+ return "\n".join(parts)
83
+
84
+
85
+ def name_written(name: str, doc_text: str) -> bool:
86
+ """True when `doc_text` holds `name` as skf-test-skill's scorer finds it."""
87
+ body = re.escape(name)
88
+ if _NAME_CHAR_RE.match(name[0]):
89
+ body = f"(?<![{NAME_CHARS}])" + body
90
+ if _NAME_CHAR_RE.match(name[-1]):
91
+ body += f"(?![{NAME_CHARS}])"
92
+ return re.search(body, doc_text) is not None
93
+
94
+
95
+ def looked_up_name(entry: object) -> str | None:
96
+ """The name skf-test-skill looks up for an entry, or None when it looks
97
+ up none: no string name, or a `Type::method` name."""
98
+ if not isinstance(entry, dict):
99
+ return None
100
+ name = entry.get("export_name")
101
+ if not isinstance(name, str) or not name or METHOD_SEPARATOR in name:
102
+ return None
103
+ return name
104
+
105
+
106
+ def find_absent(entries: list, doc_text: str) -> tuple[int, list[dict]]:
107
+ """(entries looked up, the absent ones in entries[] order)."""
108
+ checked, absent = 0, []
109
+ for index, entry in enumerate(entries):
110
+ name = looked_up_name(entry)
111
+ if name is None:
112
+ continue
113
+ checked += 1
114
+ if not name_written(name, doc_text):
115
+ library = entry.get("source_library")
116
+ absent.append({
117
+ "entry_index": index,
118
+ "export_name": name,
119
+ "source_library": library if isinstance(library, str) else None,
120
+ })
121
+ return checked, absent
122
+
123
+
124
+ def atomic_write(target: Path, data: bytes) -> None:
125
+ """Write `data` to `target` through skf-atomic-write.py beside this
126
+ script. Raises OSError when the helper is missing or fails."""
127
+ helper = Path(__file__).resolve().parent / ATOMIC_WRITE_HELPER
128
+ if not helper.is_file():
129
+ raise OSError(f"{ATOMIC_WRITE_HELPER} not found beside {Path(__file__).name}; nothing written")
130
+ try:
131
+ result = subprocess.run(
132
+ [sys.executable, str(helper), "write", "--target", str(target)],
133
+ input=data,
134
+ capture_output=True,
135
+ timeout=ATOMIC_WRITE_TIMEOUT_SEC,
136
+ check=False,
137
+ )
138
+ except (OSError, subprocess.SubprocessError) as exc:
139
+ raise OSError(f"atomic write of {target} failed: {exc}") from exc
140
+ if result.returncode != 0:
141
+ detail = (result.stderr or b"").decode("utf-8", errors="replace").strip()
142
+ raise OSError(f"atomic write of {target} failed: {detail}")
143
+
144
+
145
+ def _fail(message: str) -> int:
146
+ print(f"error: {' '.join(message.split())}", file=sys.stderr)
147
+ return 2
148
+
149
+
150
+ def _build_parser() -> argparse.ArgumentParser:
151
+ parser = argparse.ArgumentParser(
152
+ prog="skf-names-present",
153
+ description=(
154
+ "List the provenance entries whose export_name the skill package "
155
+ "never writes, by skf-test-skill's Export Coverage rule "
156
+ "(SKILL.md and references/*.md, case-sensitive substring, `::` "
157
+ "names skipped); with --drop-absent, remove them from the map."
158
+ ),
159
+ )
160
+ parser.add_argument("--provenance", required=True, help="path to provenance-map.json")
161
+ parser.add_argument(
162
+ "--skill-dir",
163
+ required=True,
164
+ help="skill package folder holding the SKILL.md and references/ to search",
165
+ )
166
+ parser.add_argument(
167
+ "--drop-absent",
168
+ action="store_true",
169
+ help="remove every absent entry and rewrite the map through skf-atomic-write.py",
170
+ )
171
+ return parser
172
+
173
+
174
+ def main(argv: list[str] | None = None) -> int:
175
+ args = _build_parser().parse_args(argv)
176
+ prov_path = Path(args.provenance)
177
+ skill_dir = Path(args.skill_dir)
178
+ if not prov_path.is_file():
179
+ return _fail(f"provenance map not found: {prov_path}")
180
+ # A wrong folder would read every name as absent, and --drop-absent
181
+ # would then empty the map: require the package's SKILL.md.
182
+ if not (skill_dir / "SKILL.md").is_file():
183
+ return _fail(f"skill dir has no SKILL.md: {skill_dir}")
184
+ try:
185
+ prov = json.loads(prov_path.read_text(encoding="utf-8"))
186
+ except (OSError, ValueError) as exc:
187
+ return _fail(f"cannot read the provenance map {prov_path}: {exc}")
188
+ entries = prov.get("entries") if isinstance(prov, dict) else None
189
+ if not isinstance(entries, list):
190
+ return _fail(f"the provenance map {prov_path} has no entries[] list")
191
+ try:
192
+ doc_text = load_doc_text(skill_dir)
193
+ except (OSError, ValueError) as exc:
194
+ return _fail(f"cannot read the skill package {skill_dir}: {exc}")
195
+
196
+ checked, absent = find_absent(entries, doc_text)
197
+ dropped: list[dict] = []
198
+ if args.drop_absent and absent:
199
+ gone = {item["entry_index"] for item in absent}
200
+ prov["entries"] = [entry for index, entry in enumerate(entries) if index not in gone]
201
+ data = (json.dumps(prov, indent=2, ensure_ascii=False) + "\n").encode("utf-8")
202
+ try:
203
+ atomic_write(prov_path, data)
204
+ except OSError as exc:
205
+ return _fail(str(exc))
206
+ dropped = absent
207
+
208
+ result = {
209
+ "checked": checked,
210
+ "skipped": len(entries) - checked,
211
+ "absent": absent,
212
+ "dropped": dropped,
213
+ "provenance_written": bool(dropped),
214
+ }
215
+ # ASCII escapes: a Windows console's code page cannot encode every name.
216
+ sys.stdout.write(json.dumps(result, indent=2) + "\n")
217
+ return 0 if not absent or dropped else 1
218
+
219
+
220
+ if __name__ == "__main__":
221
+ sys.exit(main())
@@ -1,15 +1,15 @@
1
1
  # /// script
2
- # requires-python = ">=3.10"
2
+ # requires-python = ">=3.11"
3
3
  # dependencies = []
4
4
  # ///
5
5
  """SKF Pair Intersect — compute file-list intersections between library pairs.
6
6
 
7
- `detect-integrations.md` §1 ("File-list intersection fast path") asks the LLM
8
- to prune the N*(N-1)/2 pair-grep matrix by intersecting the per-library file
9
- lists that step 3 import-count extraction already produced. At N≈21 this
10
- collapses ~210 prescribed pair greps to a handful of non-empty-intersection
11
- pairs in typical codebases; the LLM only needs to run further grep passes
12
- against those.
7
+ `detect-integrations.md` §1 prunes the N*(N-1)/2 library pairs by
8
+ intersecting the per-library file lists that step 3's `skf-count-imports.py`
9
+ run already produced. At N≈21 this collapses ~210 pairs to a handful of
10
+ non-empty-intersection pairs in typical codebases, and each pair's `files`
11
+ are its co-import files: §2 reads them, with their import lines from step
12
+ 3's counts, and greps nothing again.
13
13
 
14
14
  This script extracts that intersection from prose-orchestrated work to a
15
15
  deterministic helper. The LLM still composes the user-facing warning when
@@ -17,12 +17,23 @@ the Top-K cap kicks in — the script just flags `truncated: true` so the
17
17
  caller knows there's something to warn about.
18
18
 
19
19
  Subcommand:
20
- intersect --libraries <json-file-or-'-'> [--top-k N]
21
- Read a libraries JSON (either from <json-file> or stdin if '-') of the
22
- shape:
23
- [{"name": "<lib>", "files": ["<path>", ...]}, ...]
24
- where each library entry lists the project files that import it (the
25
- per-library file enumeration from detect-integrations.md step 3).
20
+ intersect (--libraries <json-file-or-'-'> | --counts <json-file-or-'-'>)
21
+ [--only a,b] [--top-k N]
22
+ Read the libraries (from the file, or stdin if '-') in one of two
23
+ shapes:
24
+ --libraries [{"name": "<lib>", "files": ["<path>", ...]}, ...]
25
+ where each library entry lists the project files that
26
+ import it (`skf-count-imports.py count --format
27
+ libraries` prints this list).
28
+ --counts the `skf-count-imports.py count` envelope as it is
29
+ (its default `full` format): each `dependencies[]`
30
+ entry, then each `units[]` entry, is a library whose
31
+ files are its `files[]` ({path, line}). Entries that
32
+ share a name are one library, with the first line each
33
+ file imports it on.
34
+ --only names the libraries to pair, comma-separated (trimmed, empty
35
+ items dropped); every other library is left out, and a name the input
36
+ does not hold has no files, so it joins no pair.
26
37
 
27
38
  Emit JSON:
28
39
  {
@@ -34,11 +45,15 @@ Subcommand:
34
45
  "truncated": false,
35
46
  "total_pairs": <full non-empty-intersection count before cap>
36
47
  }
48
+ With --counts, each `files` entry is instead
49
+ {"path": "<rel-path-forward-slash>", "line_a": N, "line_b": N}
50
+ where `line_a` and `line_b` are the first lines of that file that
51
+ import `a` and `b`: the co-import files of the pair, with their lines.
37
52
 
38
53
  Pairs are computed for each unordered pair (a, b) with a < b
39
54
  lexicographically; only non-empty intersections are emitted. The pairs
40
55
  list is sorted by intersection_count DESC, then by (a, b) ASC for
41
- stable, reproducible ordering.
56
+ stable, reproducible ordering; the files of a pair are sorted by path.
42
57
 
43
58
  Top-K cap: default --top-k 20 (matches detect-integrations.md §1's
44
59
  "Top-K cap at 20 with explicit warning"). If the full count exceeds
@@ -49,11 +64,14 @@ CLI examples:
49
64
  uv run skf-pair-intersect.py intersect --libraries libs.json
50
65
  cat libs.json | uv run skf-pair-intersect.py intersect --libraries -
51
66
  uv run skf-pair-intersect.py intersect --libraries libs.json --top-k 50
67
+ uv run skf-count-imports.py count . --deps manifests.json \\
68
+ | uv run skf-pair-intersect.py intersect --counts - --only "react,zustand"
52
69
 
53
70
  Exit codes:
54
- 0 — operation succeeded (including: empty input → empty pairs list)
55
- 1 — user error (bad JSON, missing required `name`/`files` field on any
56
- library entry, file not readable)
71
+ 0 - operation succeeded (including: empty input → empty pairs list)
72
+ 1 - user error (bad JSON, missing required `name`/`files` field on any
73
+ library entry, a --counts input that is not the count envelope or
74
+ a file entry that is not a {path, line} object, file not readable)
57
75
  """
58
76
 
59
77
  from __future__ import annotations
@@ -111,11 +129,62 @@ def validate_libraries(libraries: object) -> list[dict]:
111
129
  return normalized
112
130
 
113
131
 
132
+ def libraries_from_counts(counts: object) -> list[dict]:
133
+ """The libraries of a `skf-count-imports.py count` envelope.
134
+
135
+ Each `dependencies[]` entry, then each `units[]` entry, becomes a
136
+ {name, files, lines} record: `lines` maps each forward-slash path to the
137
+ first line that imports the library. Entries that share a name merge,
138
+ keeping the lower line. Raises ValueError on any structural defect.
139
+ """
140
+ if not isinstance(counts, dict) or not isinstance(counts.get("dependencies"), list):
141
+ raise ValueError(
142
+ "counts input must be the skf-count-imports.py count envelope "
143
+ "(an object with a `dependencies` array)"
144
+ )
145
+ units = counts.get("units", [])
146
+ if not isinstance(units, list):
147
+ raise ValueError("counts `units` is not an array")
148
+ merged: dict[str, dict[str, int]] = {}
149
+ for idx, entry in enumerate(counts["dependencies"] + units):
150
+ if not isinstance(entry, dict):
151
+ raise ValueError(f"counts entry at index {idx} is not an object: {entry!r}")
152
+ name = entry.get("name")
153
+ if not isinstance(name, str) or not name:
154
+ raise ValueError(f"counts entry at index {idx} missing required `name` field")
155
+ files = entry.get("files")
156
+ if not isinstance(files, list):
157
+ raise ValueError(f"counts entry {name!r} missing required `files` array")
158
+ lines = merged.setdefault(name, {})
159
+ for f_idx, f in enumerate(files):
160
+ line = f.get("line") if isinstance(f, dict) else None
161
+ if (not isinstance(f, dict) or not isinstance(f.get("path"), str)
162
+ or not isinstance(line, int) or isinstance(line, bool)):
163
+ raise ValueError(
164
+ f"counts entry {name!r} files[{f_idx}] is not a {{path, line}} object: {f!r}"
165
+ )
166
+ path = f["path"].replace("\\", "/")
167
+ if path not in lines or line < lines[path]:
168
+ lines[path] = line
169
+ return [
170
+ {"name": name, "files": sorted(lines), "lines": lines}
171
+ for name, lines in merged.items()
172
+ ]
173
+
174
+
175
+ def only(libraries: list[dict], names: list[str]) -> list[dict]:
176
+ """The libraries `names` lists; a listed name with no library adds nothing."""
177
+ keep = set(names)
178
+ return [lib for lib in libraries if lib["name"] in keep]
179
+
180
+
114
181
  def compute_pairs(libraries: list[dict]) -> list[dict]:
115
182
  """For each unordered (a, b) with a < b lexicographically, compute the
116
183
  file-list intersection. Emit only pairs with non-empty intersections.
117
184
 
118
185
  The returned list is sorted by intersection_count DESC, then (a, b) ASC.
186
+ When both libraries carry `lines` (the --counts input), each file is a
187
+ {path, line_a, line_b} object instead of a path.
119
188
  """
120
189
  # sort libraries by name for stable iteration order
121
190
  sorted_libs = sorted(libraries, key=lambda lib: lib["name"])
@@ -135,11 +204,17 @@ def compute_pairs(libraries: list[dict]) -> list[dict]:
135
204
  intersection = sorted(set_a & set_b)
136
205
  if not intersection:
137
206
  continue
207
+ files: list = intersection
208
+ if "lines" in a and "lines" in b:
209
+ files = [
210
+ {"path": path, "line_a": a["lines"][path], "line_b": b["lines"][path]}
211
+ for path in intersection
212
+ ]
138
213
  pairs.append({
139
214
  "a": a["name"],
140
215
  "b": b["name"],
141
216
  "intersection_count": len(intersection),
142
- "files": intersection,
217
+ "files": files,
143
218
  })
144
219
  pairs.sort(key=lambda p: (-p["intersection_count"], p["a"], p["b"]))
145
220
  return pairs
@@ -167,34 +242,38 @@ def intersect(libraries: list[dict], top_k: int = DEFAULT_TOP_K) -> dict:
167
242
  # --------------------------------------------------------------------------
168
243
 
169
244
 
170
- def _read_libraries_source(source: str) -> object:
171
- """Read libraries JSON from a file path or stdin (if source == '-')."""
245
+ def _read_libraries_source(source: str, what: str = "libraries") -> object:
246
+ """Read the input JSON from a file path or stdin (if source == '-')."""
172
247
  if source == "-":
173
248
  try:
174
249
  text = sys.stdin.read()
175
250
  except OSError as exc:
176
- raise ValueError(f"failed to read libraries JSON from stdin: {exc}") from exc
251
+ raise ValueError(f"failed to read {what} JSON from stdin: {exc}") from exc
177
252
  else:
178
253
  path = Path(source)
179
254
  if not path.is_file():
180
- raise ValueError(f"libraries file not found: {path}")
255
+ raise ValueError(f"{what} file not found: {path}")
181
256
  try:
182
257
  text = path.read_text(encoding="utf-8")
183
258
  except OSError as exc:
184
- raise ValueError(f"failed to read libraries file {path}: {exc}") from exc
259
+ raise ValueError(f"failed to read {what} file {path}: {exc}") from exc
185
260
  try:
186
261
  return json.loads(text)
187
262
  except json.JSONDecodeError as exc:
188
- raise ValueError(f"malformed JSON in libraries input: {exc}") from exc
263
+ raise ValueError(f"malformed JSON in {what} input: {exc}") from exc
189
264
 
190
265
 
191
266
  def _cmd_intersect(args: argparse.Namespace) -> int:
192
267
  try:
193
- raw = _read_libraries_source(args.libraries)
194
- libraries = validate_libraries(raw)
268
+ if args.counts is not None:
269
+ libraries = libraries_from_counts(_read_libraries_source(args.counts, "counts"))
270
+ else:
271
+ libraries = validate_libraries(_read_libraries_source(args.libraries))
195
272
  except ValueError as exc:
196
273
  print(f"error: {exc}", file=sys.stderr)
197
274
  return 1
275
+ if args.only is not None:
276
+ libraries = only(libraries, [n for n in (part.strip() for part in args.only.split(",")) if n])
198
277
  if args.top_k < 0:
199
278
  print(f"error: --top-k must be >= 0; got {args.top_k}", file=sys.stderr)
200
279
  return 1
@@ -218,14 +297,26 @@ def _build_parser() -> argparse.ArgumentParser:
218
297
  "intersect",
219
298
  help="emit non-empty-intersection pairs as JSON, sorted DESC by count",
220
299
  )
221
- p_int.add_argument(
300
+ source = p_int.add_mutually_exclusive_group(required=True)
301
+ source.add_argument(
222
302
  "--libraries",
223
- required=True,
224
303
  help=(
225
304
  "path to libraries JSON, or '-' to read from stdin. "
226
305
  "Shape: [{\"name\": \"<lib>\", \"files\": [\"<path>\", ...]}, ...]"
227
306
  ),
228
307
  )
308
+ source.add_argument(
309
+ "--counts",
310
+ help=(
311
+ "path to a skf-count-imports.py count envelope, or '-' to read from "
312
+ "stdin; each pair's files then carry line_a and line_b"
313
+ ),
314
+ )
315
+ p_int.add_argument(
316
+ "--only",
317
+ metavar="NAMES",
318
+ help="comma-separated library names to pair; every other library is left out",
319
+ )
229
320
  p_int.add_argument(
230
321
  "--top-k",
231
322
  type=int,