bmad-module-skill-forge 2.2.0 → 4.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 (437) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +49 -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 +143 -18
  18. package/docs/verifying-a-skill.md +87 -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 +3 -3
  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 +31 -59
  40. package/src/shared/references/description-guard-protocol.md +12 -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 +233 -64
  64. package/src/shared/scripts/skf-build-change-manifest.py +2753 -28
  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 +772 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +267 -105
  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 +21 -3
  74. package/src/shared/scripts/skf-description-guard.py +99 -11
  75. package/src/shared/scripts/skf-detect-docs.py +694 -2
  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 +197 -55
  79. package/src/shared/scripts/skf-detect-tools.py +209 -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 +5247 -96
  86. package/src/shared/scripts/skf-extraction-inventory.py +1280 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +664 -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 +469 -205
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +700 -0
  94. package/src/shared/scripts/skf-hash-content.py +180 -20
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +161 -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 +143 -20
  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 +176 -28
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +64 -8
  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 +155 -14
  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 +787 -121
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +120 -67
  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 +23 -2
  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 +3945 -152
  138. package/src/shared/scripts/skf-write-skill-brief.py +406 -25
  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 +28 -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/health-check.md +2 -0
  168. package/src/skf-audit-skill/references/init.md +149 -210
  169. package/src/skf-audit-skill/references/re-index.md +103 -110
  170. package/src/skf-audit-skill/references/report.md +96 -33
  171. package/src/skf-audit-skill/references/semantic-diff.md +34 -25
  172. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  173. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  174. package/src/skf-audit-skill/references/structural-diff.md +80 -90
  175. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  176. package/src/skf-audit-skill/scripts/render-drift-tables.py +712 -0
  177. package/src/skf-brief-skill/SKILL.md +37 -26
  178. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  179. package/src/skf-brief-skill/assets/scope-templates.md +18 -25
  180. package/src/skf-brief-skill/assets/skill-brief-schema.md +30 -23
  181. package/src/skf-brief-skill/customize.toml +48 -20
  182. package/src/skf-brief-skill/references/analyze-target.md +144 -127
  183. package/src/skf-brief-skill/references/confirm-brief.md +11 -9
  184. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  185. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  186. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  187. package/src/skf-brief-skill/references/headless-args.md +84 -16
  188. package/src/skf-brief-skill/references/health-check.md +1 -1
  189. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  190. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  191. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  192. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  193. package/src/skf-brief-skill/references/step-auto-brief.md +26 -100
  194. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  195. package/src/skf-brief-skill/references/version-resolution.md +7 -10
  196. package/src/skf-brief-skill/references/write-brief.md +66 -57
  197. package/src/skf-campaign/SKILL.md +31 -34
  198. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  199. package/src/skf-campaign/customize.toml +47 -16
  200. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  201. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  202. package/src/skf-campaign/references/health-check.md +7 -16
  203. package/src/skf-campaign/references/step-01-setup.md +46 -84
  204. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  205. package/src/skf-campaign/references/step-03-pins.md +26 -18
  206. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  207. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  208. package/src/skf-campaign/references/step-06-batch.md +41 -34
  209. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  210. package/src/skf-campaign/references/step-08-verify.md +28 -29
  211. package/src/skf-campaign/references/step-09-refine.md +29 -30
  212. package/src/skf-campaign/references/step-10-export.md +48 -36
  213. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  214. package/src/skf-campaign/references/step-resume.md +37 -68
  215. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  216. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  217. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  218. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  219. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  220. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  221. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  222. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  223. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  224. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  225. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  226. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  227. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  228. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  229. package/src/skf-create-skill/SKILL.md +27 -20
  230. package/src/skf-create-skill/assets/compile-assembly-rules.md +11 -8
  231. package/src/skf-create-skill/assets/skill-sections.md +21 -11
  232. package/src/skf-create-skill/customize.toml +20 -8
  233. package/src/skf-create-skill/references/authoritative-files-protocol.md +36 -58
  234. package/src/skf-create-skill/references/batch-mode.md +81 -0
  235. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  236. package/src/skf-create-skill/references/compile.md +119 -70
  237. package/src/skf-create-skill/references/component-extraction.md +113 -91
  238. package/src/skf-create-skill/references/enrich.md +41 -23
  239. package/src/skf-create-skill/references/entry-points-by-hand.md +37 -0
  240. package/src/skf-create-skill/references/extract.md +204 -162
  241. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  242. package/src/skf-create-skill/references/extraction-patterns-tracing.md +11 -11
  243. package/src/skf-create-skill/references/extraction-patterns.md +588 -201
  244. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  245. package/src/skf-create-skill/references/health-check.md +1 -1
  246. package/src/skf-create-skill/references/load-brief.md +53 -36
  247. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  248. package/src/skf-create-skill/references/report.md +37 -57
  249. package/src/skf-create-skill/references/source-resolution-protocols.md +58 -169
  250. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  251. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  252. package/src/skf-create-skill/references/step-doc-sources.md +28 -19
  253. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  254. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
  255. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  256. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  257. package/src/skf-create-skill/references/validate.md +173 -99
  258. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  259. package/src/skf-create-stack-skill/SKILL.md +29 -56
  260. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  261. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  262. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  263. package/src/skf-create-stack-skill/customize.toml +23 -12
  264. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  265. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  266. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  267. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  268. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  269. package/src/skf-create-stack-skill/references/init.md +90 -32
  270. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  271. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  272. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  273. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  274. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  275. package/src/skf-create-stack-skill/references/report.md +39 -33
  276. package/src/skf-create-stack-skill/references/validate.md +20 -39
  277. package/src/skf-drop-skill/SKILL.md +28 -51
  278. package/src/skf-drop-skill/customize.toml +44 -30
  279. package/src/skf-drop-skill/references/execute.md +83 -146
  280. package/src/skf-drop-skill/references/health-check.md +2 -0
  281. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  282. package/src/skf-drop-skill/references/report.md +46 -30
  283. package/src/skf-drop-skill/references/select.md +122 -124
  284. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  285. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  286. package/src/skf-export-skill/SKILL.md +40 -62
  287. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  288. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  289. package/src/skf-export-skill/customize.toml +42 -13
  290. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  291. package/src/skf-export-skill/references/health-check.md +2 -0
  292. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  293. package/src/skf-export-skill/references/load-skill.md +88 -62
  294. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  295. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  296. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  297. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  298. package/src/skf-export-skill/references/package.md +15 -73
  299. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  300. package/src/skf-export-skill/references/result-envelope.md +31 -16
  301. package/src/skf-export-skill/references/summary.md +41 -17
  302. package/src/skf-export-skill/references/token-report.md +4 -12
  303. package/src/skf-export-skill/references/update-context.md +118 -237
  304. package/src/skf-forger/SKILL.md +61 -37
  305. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  306. package/src/skf-forger/customize.toml +43 -0
  307. package/src/skf-forger/references/pipeline-mode.md +73 -18
  308. package/src/skf-forger/scripts/forge-status.py +299 -0
  309. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  310. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  311. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  312. package/src/skf-quick-skill/SKILL.md +54 -53
  313. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  314. package/src/skf-quick-skill/customize.toml +51 -20
  315. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  316. package/src/skf-quick-skill/references/compile.md +30 -31
  317. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  318. package/src/skf-quick-skill/references/finalize.md +69 -18
  319. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  320. package/src/skf-quick-skill/references/health-check.md +5 -3
  321. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  322. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  323. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  324. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  325. package/src/skf-refine-architecture/SKILL.md +43 -53
  326. package/src/skf-refine-architecture/customize.toml +49 -20
  327. package/src/skf-refine-architecture/references/compile.md +97 -54
  328. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  329. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  330. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  331. package/src/skf-refine-architecture/references/improvements.md +14 -20
  332. package/src/skf-refine-architecture/references/init.md +98 -57
  333. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  334. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  335. package/src/skf-refine-architecture/references/report.md +65 -54
  336. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  337. package/src/skf-rename-skill/SKILL.md +19 -54
  338. package/src/skf-rename-skill/customize.toml +32 -26
  339. package/src/skf-rename-skill/references/execute.md +230 -184
  340. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  341. package/src/skf-rename-skill/references/health-check.md +1 -7
  342. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  343. package/src/skf-rename-skill/references/report.md +63 -52
  344. package/src/skf-rename-skill/references/select.md +197 -115
  345. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  346. package/src/skf-setup/SKILL.md +26 -26
  347. package/src/skf-setup/customize.toml +14 -10
  348. package/src/skf-setup/references/auto-index.md +50 -63
  349. package/src/skf-setup/references/ccc-index.md +29 -90
  350. package/src/skf-setup/references/detect-and-tier.md +58 -67
  351. package/src/skf-setup/references/health-check.md +2 -0
  352. package/src/skf-setup/references/invocation-contract.md +14 -0
  353. package/src/skf-setup/references/report.md +43 -166
  354. package/src/skf-setup/references/tier-rules.md +5 -5
  355. package/src/skf-setup/references/write-config.md +34 -57
  356. package/src/skf-test-skill/SKILL.md +17 -57
  357. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  358. package/src/skf-test-skill/customize.toml +27 -13
  359. package/src/skf-test-skill/references/coherence-check.md +134 -62
  360. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  361. package/src/skf-test-skill/references/coverage-check.md +209 -264
  362. package/src/skf-test-skill/references/detect-mode.md +5 -5
  363. package/src/skf-test-skill/references/external-validators.md +94 -82
  364. package/src/skf-test-skill/references/health-check.md +6 -7
  365. package/src/skf-test-skill/references/init.md +164 -91
  366. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  367. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  368. package/src/skf-test-skill/references/report.md +164 -142
  369. package/src/skf-test-skill/references/score.md +80 -107
  370. package/src/skf-test-skill/references/scoring-rules.md +42 -155
  371. package/src/skf-test-skill/references/source-access-protocol.md +18 -94
  372. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  373. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  374. package/src/skf-test-skill/scripts/build-result-context.py +380 -0
  375. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  376. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  377. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  378. package/src/skf-test-skill/scripts/gap-ledger.py +1666 -0
  379. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  380. package/src/skf-test-skill/scripts/load-coverage-inputs.py +1198 -0
  381. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  382. package/src/skf-test-skill/scripts/reconcile-coverage.py +547 -79
  383. package/src/skf-test-skill/scripts/score-signatures.py +479 -0
  384. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  385. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  386. package/src/skf-test-skill/scripts/verify-declared-numerator.py +133 -56
  387. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  388. package/src/skf-update-skill/SKILL.md +38 -25
  389. package/src/skf-update-skill/customize.toml +27 -12
  390. package/src/skf-update-skill/references/detect-changes.md +253 -223
  391. package/src/skf-update-skill/references/gap-driven.md +336 -0
  392. package/src/skf-update-skill/references/health-check.md +31 -5
  393. package/src/skf-update-skill/references/init.md +254 -84
  394. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  395. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  396. package/src/skf-update-skill/references/merge.md +182 -49
  397. package/src/skf-update-skill/references/re-extract.md +53 -262
  398. package/src/skf-update-skill/references/report.md +102 -35
  399. package/src/skf-update-skill/references/write.md +357 -155
  400. package/src/skf-update-skill/scripts/skf-new-file-diff.py +183 -46
  401. package/src/skf-verify-stack/SKILL.md +23 -42
  402. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  403. package/src/skf-verify-stack/customize.toml +55 -22
  404. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  405. package/src/skf-verify-stack/references/coverage.md +67 -64
  406. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  407. package/src/skf-verify-stack/references/init.md +107 -62
  408. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  409. package/src/skf-verify-stack/references/integrations.md +106 -65
  410. package/src/skf-verify-stack/references/report.md +59 -42
  411. package/src/skf-verify-stack/references/requirements.md +32 -9
  412. package/src/skf-verify-stack/references/synthesize.md +53 -39
  413. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  414. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  415. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  416. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  417. package/tools/cli/commands/install.js +11 -4
  418. package/tools/cli/commands/status.js +15 -3
  419. package/tools/cli/commands/update.js +11 -4
  420. package/tools/cli/lib/tool-check.js +320 -0
  421. package/tools/cli/lib/version-check.js +22 -9
  422. package/tools/cli/skf-cli.js +4 -1
  423. package/.gitattributes +0 -16
  424. package/.nvmrc +0 -1
  425. package/docs/_internal/RELEASING.md +0 -679
  426. package/release-audits/v1.0.0-launch-audit.md +0 -728
  427. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  428. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  429. package/src/skf-campaign/manifest.yaml +0 -15
  430. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  431. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  432. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  433. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  434. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  435. package/src/skf-update-skill/references/validate.md +0 -124
  436. package/tools/validate-docs-drift.js +0 -233
  437. package/tools/validate-docs-links.js +0 -665
@@ -1,34 +1,62 @@
1
1
  # /// script
2
- # requires-python = ">=3.10"
3
- # dependencies = []
2
+ # requires-python = ">=3.11"
3
+ # dependencies = ["pyyaml"]
4
4
  # ///
5
- """SKF Compare File Hashes — script/asset/doc drift detection for audit-skill.
5
+ """SKF Compare File Hashes: script/asset/doc drift detection for audit-skill.
6
6
 
7
7
  This script replaces the per-file hash-compute-and-classify prose at
8
8
  `src/skf-audit-skill/references/structural-diff.md` §4b (Script/Asset Drift).
9
9
  It compares the `file_entries[]` block of a provenance map against the
10
10
  current state of the source tree and emits three drift categories:
11
11
 
12
- - **added**: files present in the source tree but NOT recorded in
13
- `file_entries[]`. Walk is restricted to standard script/
14
- asset/doc directories (scripts/, bin/, tools/, cli/,
15
- assets/, templates/, schemas/, configs/, examples/) and
16
- excludes binary extensions and generated paths — same
17
- path discipline as `skf-detect-scripts-assets.py`.
12
+ - **added**: with --brief, files present in the source tree but NOT
13
+ recorded in `file_entries[]` that the skill brief takes.
14
+ The walk finds scripts and assets with the detector
15
+ create-skill and update-skill use
16
+ (skf-detect-scripts-assets.py, loaded from this folder:
17
+ its iter_files, is_binary_path, entry_point_script_paths
18
+ and file_kind), so it takes what update-skill's Category D
19
+ takes: a file under a script or asset folder at any depth,
20
+ a shebang file or a `package.json` `bin` entry point in
21
+ any folder, and a pattern asset (`*.schema.json`,
22
+ `openapi.*`, `*.example`, ...) anywhere, skipping binary
23
+ extensions, generated paths and symbolic links, and
24
+ leaving out a module of a Python package named like a
25
+ script folder, which is code unless it runs on its own.
26
+ It also takes each docs/authoritative/ file, a document.
27
+ A file under `scripts/[MANUAL]/` or `assets/[MANUAL]/` is
28
+ user-authored, never added (Category D sets it aside too).
29
+ Without --brief, or with a brief it cannot read, no new
30
+ file is looked for: `added` is empty and
31
+ `added_not_checked` says why.
18
32
  - **removed**: files in `file_entries[]` whose `source_file` is gone
19
33
  from disk under `<source-root>`.
20
34
  - **changed**: files in `file_entries[]` present on disk but with a
21
35
  different content hash than the stored hash.
22
36
 
37
+ --brief <skill-brief.yaml> keeps a walked file as added only when the
38
+ brief's scope takes it, by the one brief-scope test of
39
+ skf-classify-changed-files.py (load_scope, loaded from this folder only
40
+ when --brief is given), which update-skill's Category A and Category D
41
+ and create-skill's script and asset detector use too: with the glob rules
42
+ of skf-resolve-authoritative-files.py, and, for an empty `scope.include`,
43
+ the extensions of the map's `entries[]` files and of the brief's
44
+ `language`. A file the map's `entries[]` cite is code, never an added file
45
+ (update-skill's `tracked_code`). A walked file is a script or an asset by
46
+ the detector's file_kind, a script first (a shebang file under an asset
47
+ folder is a script), except a docs/authoritative/ file, a document
48
+ whatever the detector says: a script or an asset whose `scripts_intent` or
49
+ `assets_intent` is `none` is no added file, and no intent filters a
50
+ document.
51
+
23
52
  Distinct from `skf-hash-content.py compare`: that script classifies entries
24
53
  that ARE in the provenance map (UNCHANGED / MODIFIED_FILE / DELETED_FILE).
25
- audit-skill additionally needs the **inverse walk** — what new
54
+ audit-skill additionally needs the **inverse walk**: what new
26
55
  script/asset/doc files have appeared in the source tree since the skill
27
- was created? That's `added[]`. We delegate the path-resolution and
28
- hash-comparison logic for entries IN the map by duplicating the small
29
- sha256 helper here (15 lines) rather than importing across files — keeps
30
- the runtime import surface flat and matches the existing canonical pattern
31
- in `skf-detect-scripts-assets.py:216`.
56
+ was created? That's `added[]`, the set update-skill's Category D pipes
57
+ the detector into skf-new-file-diff.py for. The sha256 helper is the one
58
+ skf-detect-scripts-assets.py has, kept here so hashing, and so removed and
59
+ changed, need no other file.
32
60
 
33
61
  Hash-prefix normalization (writer-vs-reader compatibility): stored hashes
34
62
  in `file_entries[].content_hash` carry a `sha256:` prefix by SKF convention
@@ -40,10 +68,11 @@ the original form. A bare-hex hash from a future writer would pass through
40
68
  unchanged.
41
69
 
42
70
  Subcommand:
43
- compare <provenance-map.json> <source-root>
71
+ compare <provenance-map.json> <source-root> [--brief <skill-brief.yaml>]
44
72
  Emit JSON:
45
73
  {
46
74
  "added": ["<rel-path, forward-slash>", ...],
75
+ "added_not_checked": null | "<reason>",
47
76
  "removed": ["<rel-path, forward-slash>", ...],
48
77
  "changed": [
49
78
  {"path": "<rel-path>", "stored_hash": "sha256:...",
@@ -54,55 +83,100 @@ Subcommand:
54
83
 
55
84
  All paths are forward-slash relative to `<source-root>`. Stable sort
56
85
  on the lists for deterministic output (lexicographic on path).
86
+ `added_not_checked` is null when the walk ran, else the reason no
87
+ new file was looked for, on one line: `no skill brief` without
88
+ --brief (or with an empty one), the error the brief gave
89
+ (`brief not found: <path>`, or a brief that is not valid YAML or no
90
+ mapping), or `cannot load <helper> beside skf-compare-file-hashes.py:
91
+ ...; re-install SKF` when a helper of this folder the walk needs
92
+ cannot be loaded or is out of date. `stats.added` counts `added`.
57
93
 
58
94
  CLI examples:
59
- uv run skf-compare-file-hashes.py compare prov-map.json /path/to/src
95
+ uv run skf-compare-file-hashes.py compare prov-map.json /path/to/src \\
96
+ --brief skill-brief.yaml
60
97
 
61
98
  Exit codes:
62
- 0 — comparison succeeded (including empty/well-formed input that yields
63
- all-empty drift lists)
64
- 1 — user error (missing files, malformed JSON, unreadable paths)
99
+ 0 comparison succeeded (including empty/well-formed input that yields
100
+ all-empty drift lists, and a brief or a helper of this folder it
101
+ cannot read or load: see `added_not_checked`)
102
+ 1 user error (missing files, malformed JSON, unreadable paths)
65
103
  """
66
104
 
67
105
  from __future__ import annotations
68
106
 
69
107
  import argparse
70
108
  import hashlib
109
+ import importlib.util
71
110
  import json
72
111
  import re
73
112
  import sys
74
113
  from pathlib import Path
75
- from typing import Iterable
114
+ from typing import Callable, Iterable
76
115
 
77
116
 
78
117
  # --------------------------------------------------------------------------
79
- # Constants — mirror skf-detect-scripts-assets.py so the inverse walk
80
- # explores the same set of trees the writer side would have considered.
118
+ # Constants: the walk's script and asset rules are the detector's
119
+ # (skf-detect-scripts-assets.py), loaded from this folder, not copies.
81
120
  # --------------------------------------------------------------------------
82
121
 
83
122
 
84
- SCRIPT_DIRS = {"scripts", "bin", "tools", "cli"}
85
- ASSET_DIRS = {"assets", "templates", "schemas", "configs", "examples"}
86
123
  DOC_DIR_PREFIXES = ("docs/authoritative/",) # synthetic namespace from create-skill §6
87
124
 
88
- # Path-segment names that mark generated/vendored output trees — pruned
89
- # from the walk so the inverse never reports build-tree artifacts as added.
90
- EXCLUDED_DIR_NAMES = {
91
- "node_modules", "__pycache__", "dist", "build", ".webpack",
92
- "target", ".next", ".nuxt", "out", "coverage", ".git",
93
- ".venv", "venv", ".tox", ".mypy_cache", ".pytest_cache",
94
- ".ruff_cache", ".gradle", ".idea", ".vscode",
95
- }
125
+ # scripts/[MANUAL]/... or assets/[MANUAL]/... anywhere in the (posix) path:
126
+ # a copy of skf-new-file-diff.py's _MANUAL_RE (update-skill's Category D),
127
+ # which a test keeps equal to it.
128
+ _MANUAL_RE = re.compile(r"(?:^|/)(?:scripts|assets)/\[MANUAL\]/")
96
129
 
97
- BINARY_EXTS = {
98
- ".so", ".dll", ".jar", ".wasm", ".exe", ".dylib", ".a", ".o",
99
- ".pyc", ".class", ".png", ".jpg", ".jpeg", ".gif", ".ico",
100
- ".pdf", ".zip", ".tar", ".gz", ".tgz", ".bz2", ".xz", ".7z",
101
- ".woff", ".woff2", ".ttf", ".otf", ".eot",
102
- }
130
+ _HASH_PREFIX_RE = re.compile(r"^[a-z0-9]+:")
103
131
 
132
+ DETECTOR = "skf-detect-scripts-assets.py" # the walk (--brief): see _detector
133
+ CLASSIFIER = "skf-classify-changed-files.py" # load_scope: the brief's scope (--brief)
134
+ NO_BRIEF = "no skill brief"
104
135
 
105
- _HASH_PREFIX_RE = re.compile(r"^[a-z0-9]+:")
136
+ _SIBLINGS: dict[str, object] = {}
137
+
138
+
139
+ class HelperError(Exception):
140
+ """A helper beside this script that cannot be loaded, lacks what this
141
+ script calls or fails in it: no new file can be looked for."""
142
+
143
+
144
+ def _helper_error(filename: str, exc: BaseException) -> HelperError:
145
+ return HelperError(f"cannot load {filename} beside {Path(__file__).name}: {exc}; re-install SKF")
146
+
147
+
148
+ def _sibling(filename: str):
149
+ """A helper installed beside this script, loaded once as a module.
150
+ Raises HelperError when it cannot be loaded."""
151
+ module = _SIBLINGS.get(filename)
152
+ if module is None:
153
+ path = Path(__file__).resolve().parent / filename
154
+ name = "skf_" + filename.removeprefix("skf-").removesuffix(".py").replace("-", "_")
155
+ try:
156
+ spec = importlib.util.spec_from_file_location(name, path)
157
+ if spec is None or spec.loader is None or not path.is_file():
158
+ raise ImportError(f"{filename} not found")
159
+ module = importlib.util.module_from_spec(spec)
160
+ spec.loader.exec_module(module)
161
+ except Exception as exc: # whatever the helper raises, the run reports it
162
+ raise _helper_error(filename, exc) from exc
163
+ _SIBLINGS[filename] = module
164
+ return module
165
+
166
+
167
+ def _posix(p: str) -> str:
168
+ """A path as skf-resolve-authoritative-files.py's normalize_rel_path
169
+ writes it: stripped, forward slashes, no leading `./`."""
170
+ p = p.strip().replace("\\", "/")
171
+ while p.startswith("./"):
172
+ p = p[2:]
173
+ return p
174
+
175
+
176
+ def _one_line(message: str) -> str:
177
+ """Each line break of `message` and the space around it as one space
178
+ (a YAML error spans several lines)."""
179
+ return re.sub(r"\s*[\r\n]\s*", " ", message.strip("\r\n"))
106
180
 
107
181
 
108
182
  # --------------------------------------------------------------------------
@@ -136,16 +210,8 @@ def normalize_hash(value: str | None) -> str | None:
136
210
  # --------------------------------------------------------------------------
137
211
 
138
212
 
139
- def load_file_entries(provenance_path: Path) -> list[dict]:
140
- """Extract `file_entries[]` from a provenance map. Accepts:
141
- - top-level object with a `file_entries` key (canonical)
142
- - top-level array (already extracted)
143
- - top-level object with NO `file_entries` field → empty list
144
- (a single-skill with no scripts/assets/docs may omit the field
145
- entirely per skill-sections.md §file_entries)
146
-
147
- Raises ValueError on read failure or structural defects.
148
- """
213
+ def _read_map(provenance_path: Path) -> object:
214
+ """The provenance map's JSON. Raises ValueError when it cannot be read."""
149
215
  try:
150
216
  text = provenance_path.read_text(encoding="utf-8")
151
217
  except OSError as exc:
@@ -153,12 +219,27 @@ def load_file_entries(provenance_path: Path) -> list[dict]:
153
219
  f"failed to read provenance file {provenance_path}: {exc}"
154
220
  ) from exc
155
221
  try:
156
- data = json.loads(text)
222
+ return json.loads(text)
157
223
  except json.JSONDecodeError as exc:
158
224
  raise ValueError(
159
225
  f"malformed JSON in provenance file {provenance_path}: {exc}"
160
226
  ) from exc
161
227
 
228
+
229
+ def load_file_entries(provenance_path: Path) -> list[dict]:
230
+ """Extract `file_entries[]` from a provenance map. Accepts:
231
+ - top-level object with a `file_entries` key (canonical)
232
+ - top-level array (already extracted)
233
+ - top-level object with NO `file_entries` field → empty list
234
+ (a single-skill with no scripts/assets/docs may omit the field
235
+ entirely per skill-sections.md §file_entries)
236
+
237
+ Raises ValueError on read failure or structural defects.
238
+ """
239
+ return _file_entries(_read_map(provenance_path), provenance_path)
240
+
241
+
242
+ def _file_entries(data: object, provenance_path: Path) -> list[dict]:
162
243
  if isinstance(data, list):
163
244
  return list(data)
164
245
  if isinstance(data, dict):
@@ -176,69 +257,96 @@ def load_file_entries(provenance_path: Path) -> list[dict]:
176
257
  )
177
258
 
178
259
 
179
- # --------------------------------------------------------------------------
180
- # Walk — the inverse direction (source tree → candidate set)
181
- # --------------------------------------------------------------------------
182
-
183
-
184
- def _segment_in_excluded(rel_parts: tuple[str, ...]) -> bool:
185
- return any(seg in EXCLUDED_DIR_NAMES for seg in rel_parts)
260
+ def _cited(data: object) -> set[str]:
261
+ """The map's `entries[].source_file`, the code files it cites (none for
262
+ a bare array, or an `entries` that is not a list)."""
263
+ rows = data.get("entries") if isinstance(data, dict) else None
264
+ if not isinstance(rows, list):
265
+ return set()
266
+ return {
267
+ _posix(row["source_file"])
268
+ for row in rows
269
+ if isinstance(row, dict) and isinstance(row.get("source_file"), str) and _posix(row["source_file"])
270
+ }
186
271
 
187
272
 
188
- def _segment_in_tracked_dir(rel_parts: tuple[str, ...]) -> bool:
189
- """True if any segment of the relative path is a tracked script/asset
190
- directory."""
191
- for seg in rel_parts:
192
- if seg in SCRIPT_DIRS or seg in ASSET_DIRS:
193
- return True
194
- return False
273
+ # --------------------------------------------------------------------------
274
+ # Walk: the inverse direction (source tree to candidate set)
275
+ # --------------------------------------------------------------------------
195
276
 
196
277
 
197
278
  def _matches_doc_prefix(rel_posix: str) -> bool:
198
279
  return any(rel_posix.startswith(p) for p in DOC_DIR_PREFIXES)
199
280
 
200
281
 
282
+ def _detector(source_root: Path) -> tuple:
283
+ """(iter_files, is_binary_path, file_kind,
284
+ entry_point_script_paths(source_root)) from the detector, read before
285
+ any file is walked. Raises HelperError when the detector cannot be
286
+ loaded, lacks one of these four functions or fails in
287
+ entry_point_script_paths."""
288
+ detector = _sibling(DETECTOR)
289
+ try:
290
+ return (detector.iter_files, detector.is_binary_path, detector.file_kind,
291
+ detector.entry_point_script_paths(source_root))
292
+ except Exception as exc: # an out-of-date detector, or one that fails
293
+ raise _helper_error(DETECTOR, exc) from exc
294
+
295
+
296
+ def _walk(source_root: Path, keep: Callable[[str], bool] | None = None) -> Iterable[tuple[str, str]]:
297
+ """(POSIX relative path, kind) of each file under source_root that could
298
+ be tracked as a script, asset or doc file_entries row, by the detector's
299
+ walk (iter_files, is_binary_path): a docs/authoritative/ file is a
300
+ `doc`, whatever the detector says, and any other file the kind the
301
+ detector's file_kind gives it, both kinds on (`script` or `asset`), or
302
+ left out. `keep`, a test of the relative path, runs before the doc rule
303
+ and file_kind, so a file it refuses is never read. The detector is
304
+ loaded when the walk starts. Raises HelperError when it cannot be
305
+ loaded, lacks a function the walk calls or fails in one, and lets
306
+ through a HelperError `keep` raises."""
307
+ iter_files, is_binary_path, file_kind, entry_points = _detector(source_root)
308
+ try:
309
+ for entry in iter_files(source_root):
310
+ if is_binary_path(entry):
311
+ continue
312
+ rel = entry.relative_to(source_root).as_posix()
313
+ if keep is not None and not keep(rel):
314
+ continue
315
+ if _matches_doc_prefix(rel):
316
+ yield rel, "doc"
317
+ continue
318
+ found = file_kind(entry, source_root, entry_points)
319
+ if found is not None:
320
+ yield rel, found[0]
321
+ except HelperError:
322
+ raise
323
+ except Exception as exc: # the detector failing on a file
324
+ raise _helper_error(DETECTOR, exc) from exc
325
+
326
+
201
327
  def candidate_source_files(source_root: Path) -> Iterable[str]:
202
328
  """Yield POSIX relative paths under source_root that could be tracked
203
- as script / asset / doc file_entries.
329
+ as script / asset / doc file_entries (see _walk).
204
330
 
205
331
  Selection rules:
206
- - File is under a SCRIPT_DIRS or ASSET_DIRS directory at any depth, OR
207
- - File path matches a DOC_DIR_PREFIXES synthetic namespace.
332
+ - A script or an asset by skf-detect-scripts-assets.py's file_kind:
333
+ a file under a script or asset folder at any depth, a shebang file
334
+ or a `package.json` `bin` entry point in any folder, a pattern
335
+ asset (`*.schema.json`, `openapi.*`, `*.example`, ...) anywhere;
336
+ a module of a Python package named like a script folder is code,
337
+ not a script, unless an asset folder holds it or its name is an
338
+ asset pattern.
339
+ - Or a file under a DOC_DIR_PREFIXES synthetic namespace.
208
340
  - Binary extensions are excluded.
209
341
  - Generated/vendored directories are pruned from the walk.
210
342
 
211
- This is deliberately narrower than "every file" — audit-skill §4b only
343
+ This is deliberately narrower than "every file": audit-skill §4b only
212
344
  tracks files matching script/asset patterns. Treating every random
213
345
  source file as a candidate `added` row would drown real drift in noise.
346
+ Raises HelperError when the detector cannot be loaded.
214
347
  """
215
- stack: list[Path] = [source_root]
216
- while stack:
217
- current = stack.pop()
218
- try:
219
- entries = list(current.iterdir())
220
- except (PermissionError, FileNotFoundError):
221
- continue
222
- for entry in entries:
223
- if entry.is_symlink():
224
- continue
225
- if entry.is_dir():
226
- if entry.name in EXCLUDED_DIR_NAMES:
227
- continue
228
- stack.append(entry)
229
- continue
230
- if not entry.is_file():
231
- continue
232
- if entry.suffix.lower() in BINARY_EXTS:
233
- continue
234
- try:
235
- rel = entry.relative_to(source_root)
236
- except ValueError:
237
- continue
238
- rel_parts = rel.parts[:-1]
239
- rel_posix = rel.as_posix()
240
- if _segment_in_tracked_dir(rel_parts) or _matches_doc_prefix(rel_posix):
241
- yield rel_posix
348
+ for rel, _kind_ in _walk(source_root):
349
+ yield rel
242
350
 
243
351
 
244
352
  # --------------------------------------------------------------------------
@@ -246,14 +354,19 @@ def candidate_source_files(source_root: Path) -> Iterable[str]:
246
354
  # --------------------------------------------------------------------------
247
355
 
248
356
 
249
- def compare(source_root: Path, provenance_path: Path) -> dict:
357
+ def compare(source_root: Path, provenance_path: Path, brief_path: Path | None = None) -> dict:
250
358
  """Compute added / removed / changed / unchanged for tracked file_entries.
251
359
 
252
360
  Stored hashes are normalized before comparison; the diff record carries
253
361
  the original stored value (writer-form) plus the freshly computed
254
- current value (always prefixed sha256:) so reviewers see both.
362
+ current value (always prefixed sha256:) so reviewers see both. Added
363
+ files are looked for only with a brief it can read (`brief_path`):
364
+ otherwise `added` is empty and `added_not_checked` says why, as it
365
+ does when a helper the walk needs cannot be loaded. Raises ValueError
366
+ for a map it cannot read.
255
367
  """
256
- entries = load_file_entries(provenance_path)
368
+ data = _read_map(provenance_path)
369
+ entries = _file_entries(data, provenance_path)
257
370
  # Build the {posix_path -> stored_hash} index from the provenance.
258
371
  stored: dict[str, str | None] = {}
259
372
  for entry in entries:
@@ -266,9 +379,6 @@ def compare(source_root: Path, provenance_path: Path) -> dict:
266
379
  entry.get("content_hash"), str
267
380
  ) else None
268
381
 
269
- # Walk the source tree once; track which provenance entries are matched.
270
- candidates_on_disk = set(candidate_source_files(source_root))
271
-
272
382
  removed: list[str] = []
273
383
  changed: list[dict] = []
274
384
  unchanged_count = 0
@@ -288,11 +398,11 @@ def compare(source_root: Path, provenance_path: Path) -> dict:
288
398
  "current_hash": current,
289
399
  })
290
400
 
291
- # Anything on disk but NOT in the provenance store is "added".
292
- added = sorted(candidates_on_disk - set(stored.keys()))
401
+ added, not_checked = _added(source_root, data, stored, brief_path)
293
402
 
294
403
  return {
295
404
  "added": added,
405
+ "added_not_checked": not_checked,
296
406
  "removed": sorted(removed),
297
407
  "changed": sorted(changed, key=lambda r: r["path"]),
298
408
  "stats": {
@@ -304,6 +414,50 @@ def compare(source_root: Path, provenance_path: Path) -> dict:
304
414
  }
305
415
 
306
416
 
417
+ def _added(source_root: Path, data: object, stored: dict, brief_path: Path | None) -> tuple[list[str], str | None]:
418
+ """(added, added_not_checked): the walked files the map does not track
419
+ and the brief takes, or ([], the reason) without a brief it can read or
420
+ a helper the walk needs. removed and changed need neither. The brief's
421
+ scope is loaded before the detector, and the path rules (tracked or
422
+ cited, [MANUAL], the brief's scope) run before the detector reads a
423
+ file."""
424
+ if brief_path is None:
425
+ return [], NO_BRIEF
426
+ cited = _cited(data)
427
+ try:
428
+ try:
429
+ scope = _sibling(CLASSIFIER).load_scope(brief_path, cited)
430
+ except ValueError as exc: # a brief it cannot find or read
431
+ return [], _one_line(str(exc))
432
+ except HelperError:
433
+ raise
434
+ except Exception as exc: # an out-of-date classifier, or a resolver beside it that cannot load
435
+ raise _helper_error(CLASSIFIER, exc) from exc
436
+ known = {_posix(path) for path in stored}
437
+
438
+ def keep(rel: str) -> bool:
439
+ if rel in known or rel in cited: # tracked, or code the map's entries[] cite
440
+ return False
441
+ if _MANUAL_RE.search(rel): # user-authored, as Category D's skipped_manual
442
+ return False
443
+ return _scope_call(scope.takes, rel)
444
+
445
+ added = [rel for rel, kind in _walk(source_root, keep)
446
+ if kind == "doc" or _scope_call(scope.wants, kind)] # no intent filters a document
447
+ except HelperError as exc:
448
+ return [], _one_line(str(exc))
449
+ return sorted(added), None
450
+
451
+
452
+ def _scope_call(method, value) -> bool:
453
+ """A Scope method's answer. Raises HelperError when it fails (the
454
+ resolver it calls cannot be loaded or is out of date)."""
455
+ try:
456
+ return bool(method(value))
457
+ except Exception as exc:
458
+ raise _helper_error(CLASSIFIER, exc) from exc
459
+
460
+
307
461
  # --------------------------------------------------------------------------
308
462
  # CLI
309
463
  # --------------------------------------------------------------------------
@@ -318,8 +472,9 @@ def _cmd_compare(args: argparse.Namespace) -> int:
318
472
  if not source_root.is_dir():
319
473
  print(f"error: source root not a directory: {source_root}", file=sys.stderr)
320
474
  return 1
475
+ brief = Path(args.brief) if args.brief is not None and args.brief.strip() else None
321
476
  try:
322
- result = compare(source_root, provenance)
477
+ result = compare(source_root, provenance, brief)
323
478
  except ValueError as exc:
324
479
  print(f"error: {exc}", file=sys.stderr)
325
480
  return 1
@@ -343,6 +498,13 @@ def _build_parser() -> argparse.ArgumentParser:
343
498
  )
344
499
  p_cmp.add_argument("provenance_map", help="path to provenance-map.json")
345
500
  p_cmp.add_argument("source_root", help="path to the source tree root")
501
+ p_cmp.add_argument(
502
+ "--brief",
503
+ default=None,
504
+ metavar="BRIEF",
505
+ help="the skill brief (skill-brief.yaml): look for added files only in its scope "
506
+ "(none, or one it cannot read: no added file is looked for)",
507
+ )
346
508
  p_cmp.set_defaults(func=_cmd_compare)
347
509
  return parser
348
510