bmad-module-skill-forge 2.2.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 +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 +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 +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 +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 +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 +1 -1
  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 +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 +15 -5
  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 +28 -19
  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 -59
  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 -90
  348. package/src/skf-setup/references/detect-and-tier.md +58 -67
  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 -679
  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,6 +1,5 @@
1
1
  ---
2
2
  nextStepFile: 'report.md'
3
- descriptionGuardProtocol: '{project-root}/src/shared/references/description-guard-protocol.md'
4
3
  # Resolve `{descriptionGuardHelper}` by probing `{descriptionGuardProbeOrder}`
5
4
  # in order (installed SKF module path first, src/ dev-checkout fallback);
6
5
  # first existing path wins. HALT if neither resolves — letting an external
@@ -10,26 +9,34 @@ descriptionGuardProbeOrder:
10
9
  - '{project-root}/_bmad/skf/shared/scripts/skf-description-guard.py'
11
10
  - '{project-root}/src/shared/scripts/skf-description-guard.py'
12
11
  # Resolve `{updateActiveSymlinkHelper}` to the first existing path; HALT if
13
- # neither candidate exists. §5b uses it to atomically flip the active
14
- # symlink; §6 uses it (verify mode) to confirm the post-state. Without
15
- # the helper, §5b's "rm and recreate" pattern leaves a brief window where
12
+ # neither candidate exists. §8 uses it to atomically flip the active
13
+ # symlink; §8a uses it (verify mode) to confirm the post-state. Without
14
+ # the helper, an "rm and recreate" pattern would leave a brief window where
16
15
  # concurrent readers see a missing symlink.
17
16
  updateActiveSymlinkProbeOrder:
18
17
  - '{project-root}/_bmad/skf/shared/scripts/skf-update-active-symlink.py'
19
18
  - '{project-root}/src/shared/scripts/skf-update-active-symlink.py'
20
19
  # Resolve `{verifyProvenanceCompletenessHelper}` to the first existing path.
21
- # §6a runs Check D (Provenance Completeness), deferred from step 5 because it
22
- # needs both metadata.json + provenance-map.json on disk: the export/provenance
23
- # set-diff (missing + orphaned entries) and file:line citation resolution.
24
- # Advisory — do not HALT if neither path resolves; fall back to the in-prose
25
- # set comparison §6a documents (graceful degradation).
20
+ # Advisory: if neither resolves, §2 and §6 look up no node kind and §6 runs
21
+ # its by-hand set comparison, since the files it checks are already written.
26
22
  verifyProvenanceCompletenessProbeOrder:
27
23
  - '{project-root}/_bmad/skf/shared/scripts/skf-verify-provenance-completeness.py'
28
24
  - '{project-root}/src/shared/scripts/skf-verify-provenance-completeness.py'
25
+ # Resolve `{extractionPatternsData}` to the first existing path. If neither
26
+ # exists, §2 relabels nothing and §6 fixes no node kind: list each label
27
+ # violation and each node-kind finding as a WARN.
28
+ extractionPatternsDataProbeOrder:
29
+ - '{project-root}/_bmad/skf/skf-create-skill/references/extraction-patterns.md'
30
+ - '{project-root}/src/skf-create-skill/references/extraction-patterns.md'
31
+ # Resolve `{atomicWriteHelper}` to the first existing path. Advisory: if
32
+ # neither resolves, §6 fixes no node kind and lists each one as a WARN.
33
+ atomicWriteProbeOrder:
34
+ - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
35
+ - '{project-root}/src/shared/scripts/skf-atomic-write.py'
29
36
  # Resolve `{hashContentHelper}` to the first existing path; HALT if neither
30
- # candidate exists. §1 uses its `manual-verify` subcommand to verify the
31
- # post-merge file against the byte-exact [MANUAL] inventory captured in step 1
32
- # §5; a marker-count comparison would pass a block whose interior was truncated
37
+ # candidate exists. §1 and §7 use its `manual-verify` subcommand to verify
38
+ # SKILL.md against the byte-exact [MANUAL] inventory step 4 §4 amended; a
39
+ # marker-count comparison would pass a block whose interior was truncated
33
40
  # without changing the marker count.
34
41
  hashContentProbeOrder:
35
42
  - '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
@@ -42,65 +49,157 @@ hashContentProbeOrder:
42
49
  renderMetadataStatsProbeOrder:
43
50
  - '{project-root}/_bmad/skf/shared/scripts/skf-render-metadata-stats.py'
44
51
  - '{project-root}/src/shared/scripts/skf-render-metadata-stats.py'
52
+ # Resolve `{sourceTreeHelper}` to the first existing path. §9 uses its
53
+ # `advance` subcommand to move the workspace clone to the commit §2
54
+ # recorded. If neither path exists, skip the call and continue: it never
55
+ # gates the workflow.
56
+ sourceTreeProbeOrder:
57
+ - '{project-root}/_bmad/skf/shared/scripts/skf-source-tree.py'
58
+ - '{project-root}/src/shared/scripts/skf-source-tree.py'
59
+ # Resolve `{cccGitHygieneHelper}` to the first existing path. It keeps ccc's
60
+ # index folders and SKF's workspace lock out of git, and undoes the
61
+ # `.gitignore` edit `ccc init` makes in a workspace clone. If neither path
62
+ # exists, skip the call and continue: it never gates the workflow.
63
+ cccGitHygieneProbeOrder:
64
+ - '{project-root}/_bmad/skf/shared/scripts/skf-ccc-git-hygiene.py'
65
+ - '{project-root}/src/shared/scripts/skf-ccc-git-hygiene.py'
66
+ # Resolve `{checkWorkspaceDriftHelper}` to the first existing path. §9
67
+ # passes it to the advance, which moves the workspace clone only when the
68
+ # helper confirms the clone still holds the skill's previous commit. If
69
+ # neither path exists, run the advance without `--drift-helper`: it then
70
+ # leaves an existing clone where it is (`head-unverified`), and §9 warns.
71
+ checkWorkspaceDriftProbeOrder:
72
+ - '{project-root}/_bmad/skf/shared/scripts/skf-check-workspace-drift.py'
73
+ - '{project-root}/src/shared/scripts/skf-check-workspace-drift.py'
74
+ # Resolve `{buildChangeManifestHelper}` to the first existing path; HALT if
75
+ # neither exists: §3's `apply` writes the provenance map from this run's
76
+ # records, never by hand.
77
+ buildChangeManifestProbeOrder:
78
+ - '{project-root}/_bmad/skf/shared/scripts/skf-build-change-manifest.py'
79
+ - '{project-root}/src/shared/scripts/skf-build-change-manifest.py'
80
+ # Resolve `{compareDocHashesHelper}` to the first existing path when step 2
81
+ # wrote `{run_dir}/doc-hashes.json` (a docs-only skill); HALT if neither
82
+ # exists: §2 records the changed documents' new hashes with its
83
+ # `refresh-hashes`, so the next update does not find them changed again.
84
+ compareDocHashesProbeOrder:
85
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
86
+ - '{project-root}/src/shared/scripts/skf-detect-docs.py'
87
+ # Resolve `{validateOutputHelper}` to the first existing path when §7 finds no
88
+ # skill-check. If neither exists, §7 records the spec check as skipped.
89
+ validateOutputProbeOrder:
90
+ - '{project-root}/_bmad/skf/shared/scripts/skf-validate-output.py'
91
+ - '{project-root}/src/shared/scripts/skf-validate-output.py'
92
+ # `{extractPublicApiHelper}`: §2's public API counts; if neither exists, §2
93
+ # counts by hand.
94
+ extractPublicApiProbeOrder:
95
+ - '{project-root}/_bmad/skf/shared/scripts/skf-extract-public-api.py'
96
+ - '{project-root}/src/shared/scripts/skf-extract-public-api.py'
97
+ entryPointsByHandData: 'skf-create-skill/references/entry-points-by-hand.md'
45
98
  ---
46
99
 
47
100
  <!-- Config: communicate in {communication_language}. -->
48
101
 
49
- # Step 6: Write Updated Files
102
+ # Step 5: Write Updated Files
50
103
 
51
104
  ## STEP GOAL:
52
105
 
53
- Verify the merged SKILL.md that step 4 section 6b wrote to disk, then write the derived artifacts (metadata.json, provenance-map.json, evidence-report.md, context-snippet.md, and the active symlink).
106
+ Verify the merged SKILL.md that step 4 section 6b wrote to disk, write the derived artifacts (metadata.json, provenance-map.json, evidence-report.md, context-snippet.md), check the written package, and only then point the active symlink at the new version.
54
107
 
55
108
  ## Rules
56
109
 
57
110
  - Focus only on verifying merged files and writing derived artifacts — merge content was already written in step 4
58
- - Do not modify merged SKILL.md content — any mismatch detected during verification triggers HALT, not repair
111
+ - Do not modify merged SKILL.md or `references/` content, apart from §6's citation line and prefix fixes outside `[MANUAL]` blocks, and §7's `skill-check check --fix` edits and body split, which moves no section that holds a `[MANUAL]` block; §7 re-checks the [MANUAL] blocks after them. Any mismatch detected during verification triggers HALT, not repair
59
112
  - Do not skip provenance map update — critical for future audits
60
- - HALT immediately on verification failure before writing any derived artifact — a partial-write skill package is worse than an unchanged one
113
+ - HALT immediately on verification failure: §1 halts before any derived artifact is written, and §8 points the `active` link at the new version only after §6 and §7, the last checks that can halt, so every halt in this step rolls back a version no reader sees yet. A partial-write skill package is worse than an unchanged one
61
114
 
62
115
  ## Steps
63
116
 
117
+ **Halt procedure.** Every HALT in this step names its payload (`status`, `phase`, `path` when it has one, and `reason`), displays its message, and then runs, from `{project-root}`, the halt helper SKILL.md On Activation resolved.
118
+
119
+ ```bash
120
+ uv run {runStateHelper} halt --run-dir "{run_dir}" \
121
+ [--tree "{source_tree}"] \
122
+ [--lock "{forge_data_folder}/{skill_name}/.skf-update.lock" --owner "{lock_owner}"] \
123
+ [--emit] <<'SKF_JSON'
124
+ {"status": "<status>", "phase": "<phase>", "path": "<path; leave the key out when the halt names none>", "reason": "<reason>", "skill_name": "{skill_name}", "version": "<the metadata.json version the run started from>", "previous_version": "<the same>", "update_mode": "<normal, gap-driven or degraded>", "files_written": [<what stands: none after a rollback; the artifacts this step wrote when the helper kept a live version>]}
125
+ SKF_JSON
126
+ ```
127
+
128
+ Pass `--tree` when init.md §6b bound `{source_tree}`, always `--lock` and `--owner`, and `--emit` in `{headless_mode}`. Until §10 closes the window, the helper first undoes what this run wrote: in gap-driven mode it restores the package, the version's provenance map and evidence report, and the skill brief from the snapshot step 4 §6b took; in every other mode it removes the version folders step 4 created, unless the `active` link already names them (§8), which keeps that version, now the skill's live one (its `kept`). Then it removes the private source tree, releases the run lock (never one another run holds) and, with `--emit`, prints the halt's `SKF_UPDATE_RESULT_JSON:` line through the shared emitter, which adds the decisions recorded so far, the `error` object and a warning for each step the helper could not finish (`rollback-incomplete`, `source-tree-not-removed`, `run-lock-not-released`); it never stops on a result. Tell the user in one line what it restored, removed or kept, and name each path its `failed[]` lists for the user to restore or delete by hand. Write each payload value as a JSON string: escape `"` and `\`, and write every path with `/`. Display the line it prints verbatim. When it exits 1 and its message names the payload, fix the payload once and run it again, which redoes nothing already done; when it still fails or prints no line, display the halt message alone. An interactive HALT displays its message and emits nothing. A HALT that names no payload (a helper the frontmatter says to HALT without, resolving to no path) takes `status: "blocked"`, `phase: "write:<the helper's file name>"` and `reason: "<the helper's file name> is missing; re-install SKF"`.
129
+
130
+ The halt leaves `{run_dir}` in place.
131
+
132
+ A `Write` or `Edit` call in §2 to §5 that errors (permission denied, disk full, path invalid) HALTs with status `halted-for-write-failure`: the halt procedure takes `phase: "write:artifact-write"`, `path: "{the file that failed}"`, `reason: "<the error>"`. §6 checks what `metadata.json` and `provenance-map.json` hold.
133
+
134
+ **Warnings go to the run log.** Record each warning this step adds to `warnings[]` the moment it is raised: write its text to `{run_dir}/warning.txt` with a file write (a warning can hold quotes, `$` or backticks), then, from `{project-root}`, run `uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/warning.txt")"`. The halt line and the result line read warnings only from `{run_dir}/warnings.jsonl`.
135
+
64
136
  ### 0. Description Guard Protocol
65
137
 
66
- **Used by:** §7 (`skill-check check --fix` and `skill-check split-body --write`), and any future tool invocation that may modify SKILL.md's frontmatter on disk.
138
+ **Used by:** §7 (`skill-check check --fix` and `skill-check split-body --write`).
139
+
140
+ The deterministic phases are executed via `{descriptionGuardHelper}`: §7 invokes the helper at the capture and verify-restore points around every `skill-check` call.
67
141
 
68
- Load `{descriptionGuardProtocol}` for the full prose explanation of the four-phase guard (why it exists, what counts as divergence, why token-stream comparison is the right shape). The deterministic phases are executed via `{descriptionGuardHelper}` — §7 invokes the helper at the capture and verify-restore points around every `skill-check` call. `verify-restore` refuses an empty `--captured-description` (exit 1, file untouched); when that happens, follow the protocol's empty-snapshot rule instead of re-running with the empty value.
142
+ **Guard outputs.** Bind `{guarded_description}` ← `description` from each `capture`, run while the in-context SKILL.md copy matches the file on disk. Bind `{guard_restored}` ← `restored` and `{guard_diff_kind}` ← `diff_kind` from each `verify-restore`. When `{guard_restored}` is true, set the in-context `description` to `{guarded_description}` so later sections do not work from the tool-mutated value, and record `description_guard_restored: true` with the tool name and `description_guard_diff_kind: {guard_diff_kind}` in workflow context for the evidence report (§4). A later `verify-restore` that exits 0 with `{guard_restored}` false leaves those records in place.
69
143
 
70
- Update-skill does not run the optional post-restore frontmatter re-validation today — the post-write checks in §1 catch downstream issues, and a `restored: true` outcome is already surfaced through the evidence report (§4).
144
+ **Empty-snapshot rule.** `verify-restore` refuses an empty or whitespace-only `--captured-description` (exit 1, file untouched). Never re-run it with the empty value: writing it back would blank the field the guard protects. If the merged description is still in context (the in-context SKILL.md copy), re-run `verify-restore` with that value. Otherwise record `description_guard_restored: false` and `description_guard_refused: empty-capture` with the tool name; the evidence report (§4) renders that as a fired guard, not as a clean run.
71
145
 
72
146
  ### 1. Verify SKILL.md Write
73
147
 
74
148
  SKILL.md was written in step 4 section 6b. Verify the write landed intact before proceeding to any derived-artifact writes.
75
149
 
76
- - Verify the resolved `{skill_package}` path matches the version directory step 4 wrote to (if the version changed, step 4 updated `{skill_package}` in context to point at the new path)
77
- - Run the deterministic [MANUAL]-integrity verifier against the byte-exact inventory captured in step 1 §5:
150
+ - Verify the resolved `{skill_package}` path matches the version directory step 4 wrote to (outside gap-driven mode, step 4 §6b created `{skill_group}/{new_version}/` and rebound `{skill_package}` and `{forge_version}` to the new version)
151
+ - Run the deterministic [MANUAL]-integrity verifier against the byte-exact inventory step 4 §4 amended with the user's [MANUAL] decisions (the step 1 §5 inventory, when there were none):
78
152
 
79
153
  ```bash
80
- uv run {hashContentHelper} manual-verify {skill_package}/SKILL.md \
81
- --inventory {manual_inventory}
154
+ uv run {hashContentHelper} manual-verify "{skill_package}/SKILL.md" \
155
+ --inventory "{manual_inventory}"
82
156
  ```
83
157
 
84
158
  The verdict JSON is `{"preserved":[...], "modified":[...], "missing":[...], "moved":[...], "ok":bool}`. A `modified` block is one whose byte-exact interior changed (an interior truncation); a `missing` block lost its markers entirely; a `moved` block is byte-identical but relocated with its logical parent section (clean — does not fail the gate). `ok == (modified empty AND missing empty)`.
85
- - If `ok == true` and the path resolves: proceed to section 2
86
- - **If `ok == false`: HALT immediately** with status `halted-for-manual-mismatch`. Do not write `metadata.json`, `provenance-map.json`, or any other artifact — further writes would compound the inconsistency. In `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:verify-manual-integrity", path: "{skill_package}/SKILL.md", reason: "..."}`). Alert the user:
159
+ - If `ok == true` and the path resolves: proceed to section 2. Step 4 §6b checked the same file against the same inventory before it published it, so this is the confirmation that the write landed
160
+ - **If `ok == false`: HALT immediately** with status `halted-for-manual-mismatch`. Do not write `metadata.json`, `provenance-map.json`, or any other artifact: further writes would compound the inconsistency. The halt procedure takes `phase: "write:verify-manual-integrity"`, `path: "{skill_package}/SKILL.md"`, `reason: "[MANUAL] blocks differ from the approved inventory: modified {modified}; missing {missing}"`. Alert the user:
161
+
162
+ "**[MANUAL] section integrity failure after write.** Blocks modified (interior changed): {modified}. Blocks missing (markers lost): {missing}. Relocated-but-intact (advisory only): {moved}. Verified against the inventory `{manual_inventory}`, on disk at `{skill_package}/SKILL.md`. {manual_recovery}"
163
+
164
+ `{manual_recovery}` depends on the mode:
87
165
 
88
- "**[MANUAL] section integrity failure after write.** Blocks modified (interior changed): {modified}. Blocks missing (markers lost): {missing}. Relocated-but-intact (advisory only): {moved}. Verified against the step-1 inventory `{manual_inventory}`, on disk at `{skill_package}/SKILL.md`. The skill package is in an inconsistent state. Manual recovery required — restore the previous version from `{skill_group}/{previous_version}/` or fix the file in place, then re-run update-skill."
166
+ - **Outside gap-driven mode** (step 4 §6b created a new version folder): "The previous version at `{skill_group}/{baseline_version}/` is unchanged, and the halt removed `{skill_group}/{new_version}/` and `{forge_data_folder}/{skill_name}/{new_version}/`, which step 4 created for this update: re-run update-skill."
167
+ - **Gap-driven mode** (the repair edited the current version in place): "The halt restored the package, with its [MANUAL] blocks, its provenance map and evidence report, and the skill brief, from the snapshot step 4 took before its first write: the skill is as it was. Re-run update-skill."
89
168
 
90
169
  ### 2. Write Updated metadata.json
91
170
 
171
+ **Renew the run lock first:** step 4 §8 may have waited at its gate since step 4 §6b renewed it. From `{project-root}`, run the renewal step 4 §6b ran:
172
+
173
+ ```bash
174
+ uv run {runLockHelper} acquire \
175
+ --lock "{forge_data_folder}/{skill_name}/.skf-update.lock" \
176
+ --owner "{lock_owner}" \
177
+ --stale-after 60
178
+ ```
179
+
180
+ - **Exit 0 with `refreshed` true:** continue.
181
+ - **Exit 0 with `refreshed` false, or exit 3:** this run's lock lapsed while it waited, so another update may have changed the skill since this run read it (on exit 3 that update holds the lock). HALT with status `halted-for-concurrent-run` before writing `metadata.json`: display "**This update of {skill_name} lost its run lock while it waited.** {lock_recovery}", with the helper's `message` on exit 3. The halt procedure takes `phase: "write:run-lock"`, `path: "{forge_data_folder}/{skill_name}/.skf-update.lock"`, `reason: "run-lock-lost: this run's lock lapsed while it waited; another update may have changed the skill since this run read it"`, or on exit 3 `reason: "another update in progress: {message}"`.
182
+ - **Any other exit, or no JSON:** HALT with status `blocked` the same way: display "**The run lock could not be renewed:** {the message the helper printed on stderr}. {lock_recovery}", with `reason: "run-lock-failed: {that message}"`.
183
+
184
+ `{lock_recovery}` is, outside gap-driven mode, "The previous version is unchanged, and the halt removed `{skill_group}/{new_version}/` and `{forge_data_folder}/{skill_name}/{new_version}/`, which step 4 created for this update: re-run update-skill once no other update of {skill_name} runs." and, in gap-driven mode, "This repair already wrote SKILL.md in place; the halt restored the package and the skill brief from the snapshot step 4 took, so the skill is as it was: re-run update-skill once no other update of {skill_name} runs."
185
+
92
186
  Update `{skill_package}/metadata.json`:
93
187
  - **First, apply any queued `metadata_patches[]`** (staged by merge Priority 8b from gap-driven `metadata update` entries): apply each surgical patch described in the gap's remediation (reconcile a divergent count, add an explanatory stat, etc.) *before* the automatic recount below, so the recount overrides only the fields it owns and the patch survives for any field it does not. If a patch and the recount disagree on a field the recount owns (e.g., `exports_documented`), the recount wins — log the divergence so a still-stale stat surfaces in the report rather than being silently overwritten.
94
- - **For gap-driven rescopes** (`DELETED_EXPORT` / verification `rescoped`): the removed exports are already dropped from the `exports` array below, and `stats` recompute from that reduced surface — never set a `stats` count by hand to match the documented total. The reduction is justified by the `brief.scope.exclude` + `scope.amendments[]` (`action: "excluded"`) written in step 2; the recount simply reflects the smaller surface.
95
- - Update `version`: **if `update_mode == "gap-driven"`, do not bump — the skill is being repaired against the same source commit, so leave `version` unchanged and update only `generation_date` / `last_update` below.** This keeps metadata `version` consistent with the on-disk `{skill_package}` path, which step 4 §6b also leaves unchanged in gap-driven mode (see step 4 §6b's "If the source version detected during step 3 differs..." carve-out — in gap-driven mode no source version is detected, so step 4 writes into the existing version directory). Otherwise, if a source version was detected during re-extraction and differs from the current metadata version, use the source version; otherwise increment patch version
96
- - Update `generation_date` timestamp to current ISO-8601 date
188
+ - **For gap-driven rescopes** (`DELETED_EXPORT` / verification `rescoped`): the removed exports are already dropped from the `exports` array below, and `stats` recompute from that reduced surface: never set a `stats` count by hand to match the documented total. The reduction is justified by the `brief.scope.exclude` + `scope.amendments[]` (`action: "excluded"`) step 4 §6b wrote from gap-driven.md §1's rule R1; the recount simply reflects the smaller surface.
189
+ - Update `version` to `{new_version}`, the version step 4 §6b chose: **if `update_mode == "gap-driven"`, that is the unchanged version**: the skill is being repaired against the same source commit, so of the fields that mark an update only `generation_date` below changes, and step 4 wrote into the existing version directory (step 1 §6c records no source version in gap-driven mode). Otherwise it is the version whose folder step 4 §6b created: `{source_version_detected}` when step 1 §6c recorded `source_version_detected`, else the next patch version. Never pick another value here: `version` must name the folder `metadata.json` sits in, the one §8 points the `active` link at. From here on `{version}` is `{new_version}`.
190
+ - Update `generation_date` timestamp to the current UTC time to the second, `YYYY-MM-DDTHH:MM:SSZ`, read from the clock (`date -u +%Y-%m-%dT%H:%M:%SZ`), never typed: step 1 §4b offers a test report only when it is newer. It is the only update bookkeeping metadata.json carries, in every mode: §3's update operation block records `last_update` and `update_type` in provenance-map.json. Write neither key into metadata.json, and remove a `last_update` or `update_type` an older SKF version left there, so metadata.json never keeps the date or type of an earlier update beside the map's current one.
191
+ - **Record the source commit** (when `{source_tree_status}` is `ready` or `offline`): set `source_commit` to `{target_commit}`, the commit every step of this run read, and, when `{target_ref_override}` is set, `source_ref` to `{target_ref}`. Leave `source_root` as metadata.json records it: it names the clone SKF keeps for the repository; never write `{source_tree}` or any other path of this run into an artifact. For any other source (a local folder, gap-driven mode, docs-only) leave `source_commit` and `source_ref` unchanged. The in-context `{source_commit}` stays the value init.md §6 read; §9 needs it.
97
192
  - Update `exports` array to reflect current export list
98
- - **Compute the `stats` block and `confidence_distribution` deterministically** with `{renderMetadataStatsHelper}` (resolve from `{renderMetadataStatsProbeOrder}`; first existing path wins) — the same helper sibling create-skill compiles them with, so create and update emit byte-identical stats for identical inputs. The helper owns all the arithmetic: it bins each provenance `entries[]` row once by its `signature_source` tier into `confidence_distribution.{t1,t1_low,t2,t3}`, sets `exports_documented` = the entry count, and derives `exports_total` = `exports_public_api` + `exports_internal`, `public_api_coverage` = documented / public_api (`null` if public_api is 0), `total_coverage` = documented / total (`null` if total is 0), plus `scripts_count` / `assets_count` from the inventory arrays. Run `uv run {renderMetadataStatsHelper} --help` for the full contract. Do not hand-bin the distribution — binning T2 annotations + T3 doc items on top of the per-export tiers double-counts, which per-entry binning by `signature_source` makes structurally impossible. You supply only the judgment payload:
193
+ - **Compute the `stats` block and `confidence_distribution` deterministically** with `{renderMetadataStatsHelper}` (resolve from `{renderMetadataStatsProbeOrder}`; first existing path wins): the same helper sibling create-skill compiles them with, so create and update emit byte-identical stats for identical inputs. The helper owns all the arithmetic: it bins each provenance `entries[]` row once by its `signature_source` tier into `confidence_distribution.{t1,t1_low,t2,t3}`, sets `exports_documented` = the entry count for the library shape (for a reference app, the Pattern Surface row count; see **Shape** below), and derives `exports_total` = `exports_public_api` + `exports_internal`, `public_api_coverage` = documented / public_api (`null` if public_api is 0), `total_coverage` = documented / total (`null` if total is 0), plus `scripts_count` / `assets_count` from the inventory arrays. Run `uv run {renderMetadataStatsHelper} --help` for the full contract. Do not hand-bin the distribution: binning T2 annotations + T3 doc items on top of the per-export tiers double-counts, which per-entry binning by `signature_source` makes structurally impossible. You supply only the payload:
99
194
 
100
- **Judgment payload (what you decide — passed as JSON on stdin):**
101
- - `exports_public_api`: count of exports from public entry points (`__init__.py`, `index.ts`, `lib.rs`, or equivalent)
102
- - `exports_internal`: count of all other non-underscore-prefixed exports
195
+ **Payload (passed as JSON on stdin):**
196
+ - `exports_public_api` and `exports_internal`, unless the drift override below applies. At Quick tier, count the entry points by hand with `{entryPointsByHandData}`, as create-skill does. Otherwise, from `{project-root}`, run `uv run {extractPublicApiHelper} --mode full --source-root "{source_root}" [--brief "{forge_data_folder}/{skill_name}/skill-brief.yaml"] [--language "{language}"] [--scope-type "{scope_type}"] --head-cap 0 -o "{run_dir}/public-api.json"` once over the whole scope (no `--files-from`), with the longest timeout your shell tool takes (a large tree takes minutes), as create-skill's extract.md §4 does, and pass its `counts.exports_public_api` and `counts.exports_internal`, as its compile.md §4 does. Pass `--brief` when that file exists (step 4 §6b wrote a rescope's amendment into it), and `--language` and `--scope-type` when metadata.json records them. On exit 1 (`incomplete`) run it once more. When its `files_without_recipes` is not empty (in-scope files no recipe reads), read those files' entry points as `{entryPointsByHandData}` says and add their names to the two counts, as create-skill's extract.md §4b does. Still incomplete, or on exit 2 or 3, no JSON, no candidate, or `entry_points.status` `no-entry-point`, count the entry points by hand with `{entryPointsByHandData}`. A docs-only skill has no tree to count: pass the values metadata.json records, as the drift override does.
103
197
  - `scripts` / `assets`: the scripts / assets inventory arrays (or `[]` when empty) — the helper sets `scripts_count` / `assets_count` from their lengths
198
+ - `pattern_surfaces_documented` (reference app only): the number of rows in the `## Pattern Surface` table of the merged SKILL.md, the count create-skill's compile.md §4 passes. The helper uses it as `exports_documented` and emits it as `stats.pattern_surfaces_documented`, the count test-skill measures a reference app's coverage by.
199
+
200
+ **Public API counts under the drift override** (`{workspace_drift_status}` is `overridden`, gap-driven.md §3): count nothing at `{source_root}`, which is HEAD, not the pinned commit. Pass as `exports_public_api` and `exports_internal` the values `{skill_package}/metadata.json` records in `stats` once the queued `metadata_patches[]` above are applied (0 for one it does not record, with a WARN).
201
+
202
+ **Shape:** read `scope_type` from `{skill_package}/metadata.json` before this section rewrites it (create-skill records the brief's `scope.type` there). When it is `reference-app`, pass `--shape reference-app` and put `pattern_surfaces_documented` in the payload, as create-skill's compile.md §4 does; for any other `scope_type`, or none, pass no `--shape` (the helper's default, the library shape).
104
203
 
105
204
  **Invoke** — since the helper reads `entries[]`, stage §3's `provenance-map.json` write first (§3 does not depend on these stats):
106
205
 
@@ -109,57 +208,68 @@ Update `{skill_package}/metadata.json`:
109
208
  | uv run {renderMetadataStatsHelper} {forge_version}/provenance-map.json
110
209
  ```
111
210
 
112
- Write the returned `stats` and `confidence_distribution` objects into `metadata.json` **verbatim**. If the helper reports `coherence.ok: false`, some provenance entries carry a missing/unrecognized `signature_source` (§3 must write it on every entry) — fix the provenance map, do not hand-edit the stats.
211
+ For a reference app, the same call with the Pattern Surface row count and the shape:
212
+
213
+ ```bash
214
+ echo '{"exports_public_api": {N}, "exports_internal": {M}, "pattern_surfaces_documented": {P}, "scripts": {scripts-inventory-or-[]}, "assets": {assets-inventory-or-[]}}' \
215
+ | uv run {renderMetadataStatsHelper} {forge_version}/provenance-map.json --shape reference-app
216
+ ```
217
+
218
+ Write the returned `stats` and `confidence_distribution` objects into `metadata.json` **verbatim**. If the helper reports `coherence.ok: false`, fix the provenance map and re-run the helper; never hand-edit the stats. A `confidence_distribution` violation means some provenance entries carry a missing or unrecognized `signature_source` (§3 must write it on every entry).
219
+
220
+ **Label violations.** A violation whose `field` is `provenance.entries[<i>].<field>` means entry `<i>` (its `export_name` is in the violation) carries a label its `extraction_method` does not allow: relabel it by the `## Relabel Rule` section of `{extractionPatternsData}`, with `{source_root}` as the tree its node-kind lookup reads. **Under the drift override** (`{workspace_drift_status}` is `overridden`, gap-driven.md §3) run no recipe at `{source_root}`, so no `kind-at`: HEAD is not the pinned commit, so a kind read there says nothing about the entry. Leave each violation that needs a kind from the tree in place and list it as a WARN ending ` (drift override: HEAD {head_short_sha} is not pinned {pinned_short_sha})`; a relabel that reads nothing from the tree still applies. With the status `ok` or `skipped`, run the lookup as the rule says. Rewrite `{forge_version}/provenance-map.json` with the relabeled entries, re-run the helper with the same payload and `--shape`, and write the `stats` and `confidence_distribution` it returns: a relabeled `signature_source` moves the distribution, and a violation left as a WARN does not block the write. This relabel applies to every entry the helper flags, including a gap-driven `verified` entry (§3). Record the relabeled export names, and each WARN left, as `{provenance_relabels}` in workflow context: the update report lists them under its Confidence Tier Breakdown (report.md), so no entry moves from T1 to T1-low unannounced.
221
+
222
+ **A docs-only skill's document hashes.** When step 2 wrote `{run_dir}/doc-hashes.json` (its docs-only branch), record the new hash of each document it found changed, after the writes above, so the next update compares against the documents this one read. Resolve `{compareDocHashesHelper}` ← first existing path in `{compareDocHashesProbeOrder}` and, from `{project-root}`, run:
223
+
224
+ ```bash
225
+ uv run {compareDocHashesHelper} refresh-hashes "{skill_package}/metadata.json" --compare "{run_dir}/doc-hashes.json"
226
+ ```
227
+
228
+ It sets the `content_hash` and `recorded_at` of each `doc_sources[]` entry whose URL is in the comparison's `changed[]`, keeps every other entry and field, and rewrites `metadata.json` through one rename: never edit `doc_sources` by hand. On exit 2, no JSON, or no candidate resolves: HALT with status `halted-for-write-failure` (halt procedure: `phase: "write:doc-sources"`, `path: "{skill_package}/metadata.json"`, `reason: "<its error, or skf-detect-docs.py is missing; re-install SKF>"`).
113
229
 
114
230
  ### 3. Write Updated provenance-map.json
115
231
 
116
- Write to `{forge_version}/provenance-map.json`:
117
-
118
- **Every entry this step writes or rewrites carries a `signature_source` (`T1` / `T1-low` / `T2` / `T3`)** — the tier that contributed the structural signature, matching create-skill's entry contract. §2's stats helper bins each entry on this field, so a missing value trips its `coherence.ok: false` check. Preserve it byte-identical on untouched entries; set it from the contributing extraction tier on every re-extracted or new entry.
119
-
120
- **If `no_reextraction == true` (gap-driven mode from step 3 section 0):**
121
- Dispatch per-entry on the verification outcome recorded by step 3 — gap-driven runs produce a mix of `verified`, `moved`, `re-extracted`, and `unknown` outcomes, and each requires a different provenance-map write strategy:
122
-
123
- - **`verified` exports**: no fresh extraction data exists — do NOT overwrite `confidence`, `extraction_method`, `ast_node_type`, `params[]`, `return_type`, `source_file`, or `source_line`. The provenance entry stays byte-identical.
124
- - **`moved` exports**: update `source_line` (and `source_file` if different) to the new location recorded by the spot-check. Do not touch other fields.
125
- - **`re-extracted` exports** (resolved via step 3 §0a's Targeted Re-Extraction Branch from `remediation_paths[]`): write a full entry — `source_file`, `source_line`, `confidence`, `extraction_method`, `ast_node_type`, `params[]`, `return_type` — from §0a's fresh AST extraction record. This is the only gap-driven outcome that produces normal-mode-quality provenance; do NOT fall through to the byte-identical preservation above.
126
- - **`unknown` exports** (not in provenance map; no `source_citation`; `severity` is `Medium`, `Low`, or `Info`, OR `remediation_paths[]` was empty and §0a did not halt): add new entries with fields populated from step 4 merge output. `source_file`/`source_line` may be `null` here — leave these fields unset rather than writing stale values. **This path is only acceptable for `severity` in `Medium`, `Low`, or `Info`.** A Critical/High `unknown` reaching this branch indicates step 3 §0a was skipped or bypassed and is a workflow bug — step 3 §0a should have halted with status `halted-for-remediation-path` before step 6 ran. If you encounter one, halt with a pointer to §0a rather than writing null citations for a blocking gap.
127
- - **`rescoped` exports** (`DELETED_EXPORT`, removed from the public surface per detect-changes §0 rule R1): remove the entry from the provenance map — identical to the normal-mode "For deleted exports" path below. The reduction's audit trail is the `brief.scope.exclude` + `scope.amendments[]` (`action: "excluded"`) entry written in step 2; do not record the removal by editing `stats` directly — §2 recomputes `stats` from the reduced `exports` array.
128
- - Skip the "For each export in the updated skill" bullets below — they apply only to normal re-extraction mode.
129
-
130
- **For each export in the updated skill (normal mode only):**
131
- - Update `export_name` if renamed
132
- - Update `params[]` array if parameters changed (add, remove, or modify individual entries)
133
- - Update `return_type` if changed
134
- - Update `source_file` if moved
135
- - Update `source_line` from fresh extraction
136
- - Update `confidence` from extraction results
137
- - Update `extraction_method` and `ast_node_type` if re-extracted with different tools
138
-
139
- **For deleted exports:**
140
- - Remove entry from provenance map
141
-
142
- **For new exports:**
143
- - Add new entry with full structured fields: `export_name`, `export_type`, `params[]`, `return_type`, `source_file`, `source_line`, `confidence`, `extraction_method`, `ast_node_type`
144
-
145
- **For script/asset file changes (if `file_entries` exists):**
146
- - MODIFIED_FILE: copy updated file to `scripts/` or `assets/`, update `content_hash` in `file_entries`
147
- - DELETED_FILE: remove file from `scripts/` or `assets/`, remove entry from `file_entries`
148
- - NEW_FILE: copy file to `scripts/` or `assets/`, add entry to `file_entries` with `file_name`, `file_type`, `source_file`, `confidence: "T1-low"`, `extraction_method: "file-copy"`, `content_hash`
149
-
150
- **Add update operation metadata:**
151
- ```json
152
- {
153
- "last_update": "{current_date}",
154
- "update_type": "{incremental if normal mode | full if degraded_mode}",
155
- "files_changed": {count},
156
- "exports_affected": {count},
157
- "confidence_tier": "{tier}",
158
- "manual_sections_preserved": {count}
159
- }
232
+ Write `{forge_version}/provenance-map.json` with `{buildChangeManifestHelper}` `apply` (resolve it ← first existing path in `{buildChangeManifestProbeOrder}`), never by hand: it reads the map the update started from and this run's records in `{run_dir}`, and writes the whole updated map through a temporary file and one rename. When step 4 §6b created a new version, `{forge_version}` is its forge folder, which holds a copy of the previous map when there was one: write the whole updated map there, and leave the previous version's map as it was. From `{project-root}`, run:
233
+
234
+ ```bash
235
+ uv run {buildChangeManifestHelper} apply \
236
+ --update-type "{update_type}" \
237
+ [--provenance-map "{forge_version}/provenance-map.json"] \
238
+ [--manifest "{run_dir}/change-manifest.json"] \
239
+ [--category-c "{run_dir}/category-c.json"] \
240
+ [--ccc-pairs "{run_dir}/ccc-pairs.json"] \
241
+ [--extraction "{run_dir}/extraction.json"] \
242
+ [--export-details "{run_dir}/export-details.json"] \
243
+ [--reextract-records "{run_dir}/reextract-records.json"] \
244
+ [--merge-records "{run_dir}/merge-records.json"] \
245
+ [--file-compare "{run_dir}/category-d-compare.json"] \
246
+ [--new-files "{run_dir}/new-files.json"] \
247
+ [--promoted-docs "{run_dir}/promoted-docs.json"] \
248
+ [--source-root "{source_root}"] \
249
+ --skill-name "{skill_name}" \
250
+ --generation-date "{generation_date}" \
251
+ [--test-report-run-id "{test_report_run_id}"] \
252
+ --confidence-tier "{forge_tier}" \
253
+ --manual-sections-preserved "{manual_sections_preserved}" \
254
+ --source-commit "{map_source_commit}" \
255
+ --source-ref "{map_source_ref}" \
256
+ [--drift-head "{head_short_sha}" --drift-pinned "{pinned_short_sha}"] \
257
+ -o "{forge_version}/provenance-map.json"
160
258
  ```
161
259
 
162
- `manual_sections_preserved` = `len(preserved) + len(moved)` from the §1 `manual-verify` verdict (blocks that survived byte-identical, whether in place or relocated). Do not re-count markers by hand — the §1 verdict is the deterministic source.
260
+ `{update_type}` follows the run's mode as report.md §2 names it (`{update_mode}`): `incremental` for `normal`, `gap-driven` for `gap-driven`, `full` for `degraded` (a degraded run has no map: leave out `--provenance-map`, and `apply` starts a new one). Pass each `{run_dir}` file the run wrote: the change manifest and the records in every mode, `category-c.json` and `ccc-pairs.json` (step 2's Category C and its CCC check), `extraction.json` and `export-details.json` when step 2 wrote them, `merge-records.json` when step 4 did, the Category D files (`category-d-compare.json`, `new-files.json`) with `--source-root` when step 2 ran Category D, and `promoted-docs.json` when step 2 §1b wrote it. `{generation_date}` is the value §2 wrote, `--test-report-run-id` goes only with a gap-driven run (the `{test_report_run_id}` of the report it applied), `{manual_sections_preserved}` is `len(preserved) + len(moved)` from the §1 `manual-verify` verdict (never a marker count), and `{map_source_commit}` and `{map_source_ref}` are the `source_commit` and `source_ref` §2 writes to `metadata.json` (an empty string for a null one): audit-skill reads its baseline commit from the map's top-level `source_commit` and `source_ref`. Pass `--drift-head` and `--drift-pinned` only when `{workspace_drift_status}` is `overridden` (gap-driven.md §3). Every entry `apply` writes carries `signature_source`, which §2's stats helper bins, and `apply` sets the map's update operation block (`"last_update"`, `"update_type"` and its other keys), replacing an earlier update's.
261
+
262
+ Dispatch on its exit code:
263
+
264
+ - **0:** it printed `{status: "written", entries, file_entries, warnings, map}`. Bind `{provenance_spot_check_warnings}` ← its `warnings`, and add each to `warnings[]`; the update report lists each as a WARN (report.md §2).
265
+ - **3** (`status` `refused`): a blocking gap reached the provenance write with no source line (`blocking_unresolved[]`: a `NEW_EXPORT` the map does not hold, `unknown`, with a severity other than `Medium`, `Low` or `Info`, a missing one included). gap-driven.md §4 sends every such gap to its §4a and halts with `halted-for-remediation-path` before merge when §4a does not find it, so this happens only when that check was skipped or bypassed: `apply` wrote nothing, and no null citation is written. HALT with status `blocked` and write no `metadata.json`, `provenance-map.json` or other artifact. Tell the user: "**A blocking gap reached the provenance write with no source line:** {each export name, with its severity or "no severity"}. gap-driven.md §4 should have stopped on it with `halted-for-remediation-path`. The halt restored SKILL.md and references/ from the snapshot step 4 took; give each gap a `file:line` Source or name the file that defines its export in its Remediation (or downgrade it to Medium, Low or Info), then re-run update-skill." The halt procedure takes `phase: "write:provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "blocking-gap-unresolved: {export names}: gap-driven.md §4 should have halted with halted-for-remediation-path"`.
266
+ - **Exit 1, no JSON, or no candidate resolves:** HALT with status `halted-for-write-failure` (halt procedure: `phase: "write:provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "<its stderr, or skf-build-change-manifest.py is missing; re-install SKF>"`).
267
+
268
+ **For script/asset file changes:** `apply` updates the rows; copy or remove the files themselves in `{skill_package}`, each path the change manifest's `category_d` lists: a `scripts_*` path under `scripts/`, an `assets_*` path under `assets/`:
269
+ - MODIFIED_FILE (`*_modified`): copy the file from `{source_root}` (the row's `content_hash` is updated by `apply`)
270
+ - DELETED_FILE (`*_deleted`): remove the file
271
+ - NEW_FILE (`*_added`): copy the file from `{source_root}` (`apply` adds its row with `file_name`, `file_type`, `source_file`, `confidence: "T1-low"`, `extraction_method: "file-copy"`, `content_hash`)
272
+ - A `docs_*` path is source-tracked, never bundled: copy and remove nothing for it.
163
273
 
164
274
  ### 4. Write Updated evidence-report.md
165
275
 
@@ -171,6 +281,7 @@ Append update operation section to `{forge_version}/evidence-report.md` (create
171
281
  **Trigger:** {manual / audit-skill chain}
172
282
  **Forge Tier:** {tier}
173
283
  **Mode:** {normal / degraded}
284
+ **Source commit:** {source_commit_line}
174
285
 
175
286
  ### Changes Detected
176
287
  - Files modified: {count}
@@ -185,11 +296,19 @@ Append update operation section to `{forge_version}/evidence-report.md` (create
185
296
  - [MANUAL] sections preserved: {count}
186
297
  - Conflicts resolved: {count}
187
298
 
299
+ ### Scope and Targeted Re-Extraction
300
+ - Authoritative files: {authoritative_files_mirror, or none}
301
+ - Scope reconciliation before detection: {scope_reconciliation_pre, or none}
302
+ - Scope reconciliation after detection: {scope_reconciliation_post, or none}
303
+ - Targeted re-extraction: {targeted_reextraction, or none}
304
+
188
305
  ### Validation Summary
189
- - Spec compliance: {PASS/WARN/FAIL}
190
- - [MANUAL] integrity: {PASS/WARN/FAIL}
191
- - Confidence tiers: {PASS/WARN/FAIL}
192
- - Provenance: {PASS/WARN/FAIL}
306
+ - Spec compliance: {PASS/WARN/FAIL/SKIP} (quality score {score}/100)
307
+ - [MANUAL] integrity: {PASS/FAIL}
308
+ - Confidence tiers: {PASS/WARN}
309
+ - Provenance: {PASS/WARN}
310
+ - Diff: {new} new, {fixed} fixed issues, or SKIP
311
+ - Security: {PASS/WARN/SKIP}
193
312
 
194
313
  ### Description Guard
195
314
  - Restored: {true/false}
@@ -203,7 +322,11 @@ Append update operation section to `{forge_version}/evidence-report.md` (create
203
322
  - Notes: {one-sentence detail or —}
204
323
  ```
205
324
 
206
- **Description Guard population** (used by §7 Post-Write Validation when the §0 protocol fires): fill all four fields from context when `description_guard_restored == true` (triggering tool, whether restore succeeded, what changed). When `Restored: false`, the other three fields are `—` — this is the clean-run expected state — except when `description_guard_refused == "empty-capture"` (the §0 protocol's empty-snapshot rule): then set `Triggering tool` to the recorded tool name, `Original description preserved: false`, and `Notes: guard refused — empty captured snapshot (empty-capture)`, so a refused restore is distinguishable from a run where the guard never fired. Same field semantics and populator logic as create-skill step 6 §8.
325
+ **Scope and Targeted Re-Extraction population:** read `{run_dir}/evidence-records.jsonl`, where step 2 (§1b, §1c and §2.2) and gap-driven.md §4 appended their records, one JSON line each. Write each record under its key as it was recorded, and `none` for a key no line holds (or when the file does not exist), so a headless decision such as a deletion-ratio continue stays visible after the run.
326
+
327
+ **Validation Summary population:** §4 writes `[MANUAL] integrity` from §1's `manual-verify` verdict (`PASS` when `ok`) and `Confidence tiers` from §2 (`PASS` when the stats helper reported no label violation, else `WARN` naming `{provenance_relabels}`), with placeholders for the rest. §6 fills `Provenance`, and §7 fills `Spec compliance`, `Diff` and `Security`, each in the on-disk report, as §5 fills the Context Snippet sub-block.
328
+
329
+ **Description Guard population** (used by §7 Post-Write Validation when the §0 protocol fires): fill all four fields from context when `description_guard_restored == true` (triggering tool, whether restore succeeded, and what changed, based on the recorded `description_guard_diff_kind`). When `Restored: false`, the other three fields are `—` — this is the clean-run expected state — except when `description_guard_refused == "empty-capture"` (§0's empty-snapshot rule): then set `Triggering tool` to the recorded tool name, `Original description preserved: false`, and `Notes: guard refused — empty captured snapshot (empty-capture)`, so a refused restore is distinguishable from a run where the guard never fired. Same field semantics and populator logic as create-skill step 6 §8.
207
330
 
208
331
  **Context Snippet population** (used by §5 after the staleness check runs): §4 writes the sub-block with placeholders; §5 updates the on-disk evidence report in place after deciding whether to regenerate. Set `Regenerated: true` and populate `Triggers fired` with any combination of `headline-exports`, `version`, `gotchas` when at least one trigger fired. Set `Regenerated: false` and `Triggers fired: —` when none fired (the gap-driven / internals-only outcome). Always fill `Notes` with a one-sentence reason (e.g., `"Gap-driven repair — no snippet surface changed"`, `"Version bumped 0.1.0 → 0.2.0; headline exports re-ranked"`).
209
332
 
@@ -221,7 +344,7 @@ Append update operation section to `{forge_version}/evidence-report.md` (create
221
344
 
222
345
  **Record the decision on the on-disk evidence report:** open `{forge_version}/evidence-report.md` (written by §4 with placeholder values in the `### Context Snippet` sub-block) and update that sub-block under the Update Operation section just written. Set `Regenerated: true|false`, fill `Triggers fired:` with the list of triggers that fired (or `—` when none), and write a one-sentence `Notes:` entry. See §4's "Context Snippet population" note for field semantics.
223
346
 
224
- **If no trigger fired:** skip regeneration — do not touch `context-snippet.md` on disk. The snippet remains valid against the prior run's surface. Continue to §5b.
347
+ **If no trigger fired:** skip regeneration: do not touch `context-snippet.md` on disk. The snippet remains valid against the prior run's surface. Continue to §6.
225
348
 
226
349
  **If at least one trigger fired:** regenerate the snippet using the format from `skf-create-skill/assets/skill-sections.md` (pipe-delimited indexed format).
227
350
 
@@ -231,108 +354,167 @@ Pull values for the regenerated snippet from the updated metadata.json (version,
231
354
 
232
355
  Write the regenerated snippet to `{skill_package}/context-snippet.md`, preserving file permissions.
233
356
 
234
- ### 5b. Update Active Symlink
357
+ ### 6. Provenance Completeness
235
358
 
236
- Flip `{skill_group}/active` to point at the current `{version}` via the helper. The call is **always** run — atomic, idempotent, and verified in one shot. The helper's no-op path handles the "version did not change" case (gap-driven mode, or no source drift) without writing to disk:
359
+ Provenance completeness is a deterministic set-diff plus citation resolution over `metadata.json` (§2) and `provenance-map.json` (§3). Run it against the just-written artifacts via `{verifyProvenanceCompletenessHelper}`:
237
360
 
238
361
  ```bash
239
- uv run {updateActiveSymlinkHelper} update \
240
- --skill-group {skill_group} \
241
- --version {version}
362
+ uv run {verifyProvenanceCompletenessHelper} verify \
363
+ --metadata "{skill_package}/metadata.json" \
364
+ --provenance "{forge_version}/provenance-map.json" \
365
+ --source-root "{source_root}" \
366
+ --skill-dir "{skill_package}" \
367
+ --check-node-kinds \
368
+ -o "{forge_data_folder}/{skill_name}/.skf-update-verify.json"
242
369
  ```
243
370
 
244
- The helper emits a result envelope with `status` ∈ `{ok, flipped, mismatch, missing-target}` and a pre-formatted `log_message`. Log the message to the evidence report.
371
+ The helper reads its `--source-root` (falling back to the provenance map's own `source_root` field when the flag is omitted); pass the resolved `{source_root}` so citation resolution runs against the same tree re-extraction read. When `{source_root}` is null, empty or a URL (a docs-only skill), leave out the `--source-root` line: an empty flag value fails the helper's arguments, and the citation check must still run. When `{source_tree_status}` is `ready` or `offline`, `{source_root}` is still the tree init.md §6b prepared (step 7 removes it), so citations resolve against the commit §2 recorded. It writes its JSON to `{forge_data_folder}/{skill_name}/.skf-update-verify.json`, beside the §1b lock (each run overwrites it). **Read that JSON and do NOT recompute the set operations by eye, since an LLM set-diff can silently pass a dropped or orphaned entry:**
245
372
 
246
- **Dispatch on `status`:**
373
+ - `missing[]` — documented exports (metadata `exports[]`) with no provenance entry: a coverage gap.
374
+ - `orphaned[]` — provenance `entries[].export_name` whose export was removed but the entry remains.
375
+ - `summary.set_diff`: `not-applicable` for a reference app (`scope_type: reference-app` in `{skill_package}/metadata.json`), whose `exports[]` is empty by design while its entries follow each citation: the helper then diffs no sets, `missing[]` and `orphaned[]` stay empty, and its `status` comes from `stale[]`, `citations[]` and `node_kinds[]` alone. `checked` for every other skill.
376
+ - `stale[]`: entries whose `source_file:source_line` no longer resolves or is not the line that defines the export; each carries a `reason` of `file-missing`, `line-out-of-bounds`, `line-invalid` or `line-not-definition`. A `line-not-definition` item lists in `definition_lines` every line of the file that defines the export (the `def` or declaration line itself, not a decorator or blank line above it). An empty list means the rules found no definition line, which may be a shape they do not cover: the entry is unverified, not gone. Internal names are canonicalized through `reexport_map` before the diff, so a barrel-renamed export does not read as missing or orphaned.
377
+ - `citations[]`: `[AST:]` / `[SRC:]` citations in SKILL.md and `references/` whose prefix disagrees with the entry they cite (`prefix-mismatch`: `ast-grep` gives `[AST:]`, `source-read` gives `[SRC:]`), and `[AST:]` citations in a skill whose map has no `ast-grep` entry (`ast-without-ast-grep`). Each gives the markdown `file` and `line`, the `citation` text and its `expected_prefix`.
378
+ - `summary.stale_check` — `"checked"` when citations were resolved against the source tree, or `"skipped-no-source-root"` when no source root resolved on disk (the completeness + orphan diffs still ran; `stale` is empty by construction, not clean-by-verification).
379
+ - `summary.skill_citations_scanned` and `summary.skill_citations_matched`: when citations were scanned and none matched, no citation names a path and line the map records (the citations use another root), so the prefix check compared nothing. List that as a WARN.
380
+ - `node_kinds[]`: `ast-grep` entries whose `ast_node_type` is not shaped like a kind or that the ast-grep CLI does not know as a node kind of the entry's language (`invalid-kind`), or whose kind is `ERROR` (`error-kind`). Each gives the entry's `entry_index`, `export_name`, `source_file`, `source_line`, `ast_node_type` and `language`. `summary.node_kind_check` is `skipped-no-ast-grep` when no ast-grep CLI is on PATH, or `skipped-unrecognized-ast-grep` when the ast-grep found does not reject a kind no grammar has: either way ast-grep judged no kind. `node_kinds_unchecked[]` lists, with a `reason`, the entries whose kind ast-grep could not judge, and `summary.node_kind_check_skipped` counts the ast-grep entries in files ast-grep has no language for: list each unchecked entry, and the skipped count, as a WARN.
247
381
 
248
- - **`ok`** (exit 0): symlink already points at `{version}` — no disk write. Continue to §6.
249
- - **`flipped`** (exit 0): symlink was atomically updated (temp-and-replace). Continue to §6.
250
- - **`missing-target`** (exit 2): `{skill_group}/{version}/` directory does not exist on disk. HALT — display `halt_message` verbatim. This indicates §4 §6b did not write the version directory before §5b ran (a workflow bug, not a user error).
251
- - **`mismatch`** (exit 2): re-read after flip showed the symlink still points elsewhere. HALT — display `halt_message`. Should be impossible because the helper uses `os.replace` (atomic rename); a mismatch here indicates filesystem-level interference (concurrent writer, broken FUSE mount).
382
+ When the helper exits 2 it writes no JSON: record `Provenance: WARN (not run: verifier error)` with its stderr in the evidence report's Validation Summary and continue to §7. When the second run below exits 2, record the fixes already applied and `second run: verifier error` the same way and continue to §7.
252
383
 
253
- Both exit-2 halts carry status `halted-for-write-failure`; in `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:active-symlink", reason: "..."}`).
384
+ **Fix the findings that have one answer**, in this order, then run the command above once more:
254
385
 
255
- ### 6. Verify Derived Artifact Writes
386
+ 1. **Citation prefixes and source lines.** Never apply them by hand: from `{project-root}`, run the verifier's `fix` on the JSON the run above wrote:
256
387
 
257
- SKILL.md was verified in section 1 (written by step 4 section 6b). This section verifies the artifacts this step wrote: `metadata.json`, `provenance-map.json`, `evidence-report.md`, `context-snippet.md`, and the `active` symlink from §5b.
388
+ ```bash
389
+ uv run {verifyProvenanceCompletenessHelper} fix \
390
+ --verify "{forge_data_folder}/{skill_name}/.skf-update-verify.json" \
391
+ --provenance "{forge_version}/provenance-map.json" \
392
+ --skill-dir "{skill_package}" \
393
+ --manual-inventory "{manual_inventory}" \
394
+ [--no-line-moves]
395
+ ```
258
396
 
259
- For each derived artifact:
260
- - Read back the file
261
- - Confirm content matches expected output
262
- - Report verification status
397
+ Pass `--no-line-moves` when `{workspace_drift_status}` is `overridden` (gap-driven.md §3): the tree read is not the recorded commit, so its lines prove nothing about the map. `fix` prints `applied[]` (each fix it made), `left_as_warn[]` (each finding it left for a person, with its `why`), `files_written[]` and `manual_verify` (the SKILL.md [MANUAL] blocks checked against `{manual_inventory}` after its writes).
398
+ - **`manual_verify.ok` is false** (a [MANUAL] block changed): HALT with status `halted-for-manual-mismatch`. The halt procedure takes `phase: "write:verify-manual-integrity"`, `path: "{skill_package}/SKILL.md"`, `reason: "[MANUAL] blocks changed after the provenance fixes: ..."`, as §1's does. Alert the user: "**[MANUAL] section integrity failure after the provenance fixes.** Blocks modified (interior changed): {modified}. Blocks missing (markers lost): {missing}. Verified against the inventory `{manual_inventory}`. {manual_recovery}", with `{manual_recovery}` as §1 gives it: §8 has not pointed the `active` link at a new version yet, so this halt rolls back as §1's does.
399
+ - **Exit 2** (no JSON; its stderr line names any file it already wrote): record `Provenance: WARN (not fixed: verifier error)` with that line, re-read each file it names into context, and go on to step 2.
400
+ - **Otherwise:** re-read each file in `files_written[]` into the in-context copies (the merged SKILL.md and `references/` content and §3's provenance map), so later sections and the report read the fixed text, and add `SKILL.md` and `provenance-map.json` to `files_written[]` (report.md §5b) when `fix` wrote them.
401
+ 2. **Node kinds.** For each `node_kinds[]` item, take the entry at its `entry_index` in `{forge_version}/provenance-map.json` as step 1 left it (its `export_name` and `source_file` confirm it). When this run's extraction record (a `files` record of `{run_dir}/reextract-records.json`, which re-extract.md §4 or gap-driven.md §4 wrote) names the recipe that matched that export (`ast_recipe`), set the entry's `ast_node_type` to the `kind` that recipe declares in `{extractionPatternsData}` (the ast-grep Patterns table in create-skill's `extraction-patterns-by-hand.md`, which that section of `{extractionPatternsData}` points to, gives the kind of a `find_code` pattern). When no record names one (a `verified` or `moved` entry has none), look the kind up at the entry's `source_file` and `source_line`: from `{project-root}`, run `uv run {verifyProvenanceCompletenessHelper} kind-at --source-root "{source_root}" --file "{source_file}" --line {source_line} --name "{export_name}" --recipes "{extractionPatternsData}"` and set the `kind` it prints when `status` is `found`. List a WARN, with the status, for any other status or an exit 2, when there is no local source tree, or when `{workspace_drift_status}` is `overridden` (gap-driven.md §3: the tree read is not the recorded commit, so run no `kind-at`). Never invent a kind, and never change `extraction_method` to clear the finding. Write the map with `python3 {atomicWriteHelper} write --target {forge_version}/provenance-map.json` (resolve it ← first existing path in `{atomicWriteProbeOrder}`, the new map on stdin); if neither path resolves, fix no node kind and list each one as a WARN. Apply the same change to §3's in-context map, and add `provenance-map.json` to `files_written[]`.
263
402
 
264
- **Active symlink verification:** run `{updateActiveSymlinkHelper} verify --skill-group {skill_group} --version {version}` — read-only check that the symlink resolves to the version just written to `metadata.json` in §2. This closes the §5b gap where a silent skip would otherwise leave the manifest and symlink divergent — the symlink is the fallback resolver for consumers that don't read the manifest (see `knowledge/version-paths.md` §Reading Workflows step 5), so a `mismatch` must fail the step, not warn. Applies in every mode — gap-driven runs do not bump `version`, but the symlink must still point to the current `version`, otherwise a prior partial run left it pointing elsewhere.
403
+ These fixes apply to any entry, a gap-driven `verified` or `moved` entry included (§3). Fix nothing else: each `left_as_warn[]` item stays for a person to decide, and so do `missing` / `orphaned` exports. A `line-not-definition` item with an empty `definition_lines` is never fixed: list its export as unverified.
265
404
 
266
- "**Write Verification:**
405
+ Map the re-run's `status` to the `Provenance:` line of the §4 evidence report's Validation Summary: `PASS` when `status == "pass"`, `WARN` when `status == "findings"`. For a reference app that status comes from its stale lines, citation prefixes and node kinds alone (`summary.set_diff` `not-applicable`): note `set diff not applicable: reference app` in the Validation Summary, never as a WARN. Provenance findings are **advisory**: they do not block the update. In the evidence report's Validation Summary, list the source lines, citation prefixes and node kinds this section fixed (`fix`'s `applied[]` and step 2's kinds) and the unverified exports, then each `left_as_warn[]` item and each other `missing` / `orphaned` / `stale` / `citations` / `node_kinds` finding the re-run still reports as a WARN so the user can decide, and note `source lines not checked: no local source tree` when `stale_check` was `skipped-no-source-root`, `node kinds not checked: no ast-grep CLI` or `node kinds not checked: unrecognized ast-grep` when `node_kind_check` was `skipped-no-ast-grep` or `skipped-unrecognized-ast-grep`, each `node_kinds_unchecked[]` entry with its `reason`, and `summary.node_kind_check_skipped` when it is above 0. Add each of those WARNs and the unverified exports to `warnings[]` with a `provenance:` prefix (for example `provenance: search line 26 does not define it; definition lines 27, 31`); §3 recorded `{provenance_spot_check_warnings}` already.
267
406
 
268
- | File | Status |
269
- |------|--------|
270
- | SKILL.md | {VERIFIED in section 1} |
271
- | metadata.json | {VERIFIED/FAILED} |
272
- | provenance-map.json | {VERIFIED/FAILED} |
273
- | evidence-report.md | {VERIFIED/FAILED} |
274
- | context-snippet.md | {VERIFIED/FAILED} |
275
- | {skill_group}/active symlink | {VERIFIED/FAILED} (readlink → {resolved_version}, expected {version}) |
407
+ **Under the drift override** (`{workspace_drift_status}` is `overridden`, gap-driven.md §3), the verifier read `{source_root}` at HEAD, not the pinned commit, and a file or line missing at HEAD may still be at the pinned commit: end the `Provenance:` line of the Validation Summary, and each WARN this section lists for a `stale[]` finding (`file-missing`, `line-out-of-bounds`, `line-invalid`, `line-not-definition`), an unverified export or a finding `fix` left as `line-moves-skipped`, with ` (drift override: HEAD {head_short_sha} is not pinned {pinned_short_sha})`.
276
408
 
277
- **On symlink `mismatch` (helper exit 2):** HALT with status `halted-for-write-failure`. Do not proceed to §7 post-write validation or §8 menu. Display the helper's `halt_message` verbatim — it already includes the diverged target, the expected version, and the recovery command; in `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:verify-active-symlink", reason: "..."}`). This matches the severity of the other four artifact checks — silent divergence here mis-routes any downstream consumer that uses the symlink fallback.
409
+ **Graceful degradation:** if neither probe path resolves (no `uv` / script available), fall back to the manual set comparison the script encapsulates: enumerate metadata `exports[]` and provenance `entries[].export_name` (canonicalizing internal names through `reexport_map`), diff the two sets for missing/orphaned entries, and spot-check that each `source_file:source_line` still points at a real line in the source tree. Skip the set diff for a reference app (`scope_type: reference-app` in `{skill_package}/metadata.json`), as the helper does: its `exports[]` is empty by design while its entries follow each citation, so spot-check the lines only and note `set diff not applicable: reference app`. The definition-line, citation-prefix and node-kind checks do not run in this fallback, and nothing is fixed: record `line, citation and node-kind checks not run: verifier missing` in the Validation Summary. Prefer the script: it does this deterministically.
278
410
 
279
- **All files written and verified.**"
411
+ ### 7. Run Post-Write Validation
280
412
 
281
- ### 6a. Provenance Completeness (Check D — Deferred from Step 05)
413
+ skill-check runs once per update, here, against the written package. Check that it is available, never assume it: run `npx skill-check -h`.
282
414
 
283
- `validate.md` Check D (Provenance Completeness) is a deterministic set-diff plus citation resolution, and it needs both `metadata.json` (written in §2) and `provenance-map.json` (written in §3) on disk — neither exists at validate time, so the check is deferred here. Run it against the just-written artifacts via `{verifyProvenanceCompletenessHelper}`:
415
+ **Description Guard Protocol:** every invocation below that may modify SKILL.md (`skill-check check --fix` and any `split-body` write) must run inside the four-phase guard defined in §0. Invoke `{descriptionGuardHelper}` at the capture and verify-restore points around each call:
284
416
 
285
417
  ```bash
286
- uv run {verifyProvenanceCompletenessHelper} verify \
287
- --metadata {skill_package}/metadata.json \
288
- --provenance {forge_version}/provenance-map.json \
289
- --source-root {source_root}
418
+ # Phase 1: capture before any frontmatter-touching tool call
419
+ uv run {descriptionGuardHelper} capture "{skill_package}/SKILL.md"
420
+ # stash returned `description` as `guarded_description`
421
+
422
+ # Phase 2: run the tool (skill-check --fix, split-body --write)
423
+
424
+ # Phases 3+4: verify and restore after the tool call
425
+ uv run {descriptionGuardHelper} verify-restore "{skill_package}/SKILL.md" \
426
+ --captured-description "{guarded_description}"
290
427
  ```
291
428
 
292
- The helper reads its `--source-root` (falling back to the provenance map's own `source_root` field when the flag is omitted); pass the resolved `{source_root}` so citation resolution runs against the same tree re-extraction read. **Read the emitted JSON — do NOT recompute the set operations by eye; an LLM set-diff can silently pass a dropped or orphaned entry:**
429
+ Do not rely on per-call ad-hoc preservation logic: use the helper.
293
430
 
294
- - `missing[]` — documented exports (metadata `exports[]`) with no provenance entry: a coverage gap.
295
- - `orphaned[]` — provenance `entries[].export_name` whose export was removed but the entry remains.
296
- - `stale[]` — entries whose `source_file:source_line` no longer resolves; each carries a `reason` of `file-missing`, `line-out-of-bounds`, or `line-invalid`. Internal names are canonicalized through `reexport_map` before the diff, so a barrel-renamed export does not read as missing or orphaned.
297
- - `summary.stale_check` — `"checked"` when citations were resolved against the source tree, or `"skipped-no-source-root"` when no source root resolved on disk (the completeness + orphan diffs still ran; `stale` is empty by construction, not clean-by-verification).
431
+ **If skill-check is available:**
298
432
 
299
- Map `status` to the `Provenance:` line of the §4 evidence report's Validation Summary: `PASS` when `status == "pass"`, `WARN` when `status == "findings"` (this is the persistent record — step 5 §5's table was rendered before the provenance map existed, so it necessarily showed this row as deferred). Provenance findings are **advisory** — they do not block the update. Surface each `missing` / `orphaned` / `stale` entry in the evidence report's Validation Summary so the user can decide, and note when `stale_check` was `skipped-no-source-root`.
433
+ - Run `npx skill-check check "{skill_package}" --fix --format json` **inside the §0 guard**. One call validates the package against the spec, scores it, fixes what has one answer and runs the security scan (on unless `--no-security-scan` is passed). Read the JSON, not the exit code: the quality score (`scores[].score`, the entry matching the skill, or a top-level `qualityScore` on an older skill-check), the remaining `diagnostics[]`, `fixed[]` and the security findings. When the scan could not run (no `SNYK_TOKEN`), record `Security: SKIP (SNYK_TOKEN not configured)`.
434
+ - **Context sync after --fix:** if `fixed[]` is non-empty, re-read the SKILL.md `--fix` changed into the in-context copy, so the report does not work from a copy that differs from the file on disk. The §0 guard has already restored `description` if it diverged.
435
+ - If `body.max_lines` is reported, prefer a selective split: extract only the largest Tier 2 section(s) to `references/`, keeping Tier 1 inline (inline passive context achieves 100% task accuracy vs 79% for on-demand retrieval). **Never move a section that holds a `[MANUAL]` block:** init.md §5 inventories SKILL.md only, so a block moved to `references/` would leave the next update's inventory. Fall back to `npx skill-check split-body "{skill_package}" --write`, **inside the §0 guard** (it can also touch frontmatter), only when SKILL.md holds no `[MANUAL]` block (§1's verdict lists none): it moves every section. Verify anchors resolve after a split.
436
+ - When a previous version is on disk (outside gap-driven mode), run `npx skill-check diff "{skill_group}/{baseline_version}/{skill_name}" "{skill_package}"` and record its new and fixed issues as informational; otherwise record `Diff: SKIP`.
300
437
 
301
- **Graceful degradation:** if neither probe path resolves (no `uv` / script available), fall back to the manual set comparison the script encapsulates — enumerate metadata `exports[]` and provenance `entries[].export_name` (canonicalizing internal names through `reexport_map`), diff the two sets for missing/orphaned entries, and spot-check that each `source_file:source_line` still points at a real line in the source tree. Prefer the script — it does this deterministically.
438
+ **Re-check the [MANUAL] blocks** once `--fix` and any split have run, from `{project-root}`:
302
439
 
303
- ### 7. Run Post-Write Validation (Deferred from Step 05)
440
+ ```bash
441
+ uv run {hashContentHelper} manual-verify "{skill_package}/SKILL.md" \
442
+ --inventory "{manual_inventory}"
443
+ ```
304
444
 
305
- External tool checks deferred from step 5 now run against the written files.
445
+ On `ok` false, HALT with status `halted-for-manual-mismatch`, as §1 does. The halt procedure takes `phase: "write:verify-manual-integrity"`, `path: "{skill_package}/SKILL.md"`, `reason: "[MANUAL] blocks changed after skill-check: modified {modified}; missing {missing}"`. Alert the user: "**[MANUAL] section integrity failure after skill-check.** Blocks modified (interior changed): {modified}. Blocks missing (markers lost): {missing}. Verified against the inventory `{manual_inventory}`. {manual_recovery}", with `{manual_recovery}` as §1 gives it: §8 has not pointed the `active` link at a new version yet, so this halt rolls back as §1's does.
306
446
 
307
- **Description Guard Protocol:** every invocation below that may modify SKILL.md (`skill-check check --fix` and any `split-body` write) must run inside the four-phase guard defined in §0. Invoke `{descriptionGuardHelper}` at the capture and verify-restore points around each call:
447
+ **If skill-check is unavailable:** record `Diff: SKIP` and `Security: SKIP (skill-check unavailable)`, and check the structure by script instead: resolve `{validateOutputHelper}` ← first existing path in `{validateOutputProbeOrder}` and, from `{project-root}`, run `uv run {validateOutputHelper} "{skill_package}"`. Record `Spec compliance: PASS (skf-validate-output.py)` when it reports no issue, and `Spec compliance: WARN (skf-validate-output.py)` with each issue it reports otherwise; when no candidate resolves, record `Spec compliance: SKIP (skill-check unavailable, no validator)`. Nothing here changes SKILL.md, so the [MANUAL] re-check above does not run.
448
+
449
+ Record the results in the evidence report's Validation Summary (§4): `Spec compliance` from the score and the remaining `diagnostics[]`, `Diff` and `Security`, with any `description_guard_restored` event the §0 protocol recorded. They are advisory: do not block on warnings.
450
+
451
+ ### 8. Update the Active Symlink
452
+
453
+ Flip `{skill_group}/active` to point at the current `{version}` via the helper, now that §6 and §7 have checked the written package. The call is **always** run: atomic, idempotent, and verified in one shot. The helper's no-op path handles the "version did not change" case (gap-driven mode, or no source drift) without writing to disk:
308
454
 
309
455
  ```bash
310
- # Phase 1 — capture before any frontmatter-touching tool call
311
- uv run {descriptionGuardHelper} capture {skill_package}/SKILL.md
312
- # stash returned `description` as `guarded_description`
456
+ uv run {updateActiveSymlinkHelper} update \
457
+ --skill-group {skill_group} \
458
+ --version {version}
459
+ ```
460
+
461
+ The helper emits a result envelope with `status` ∈ `{ok, flipped, mismatch, missing-target}` and a pre-formatted `log_message`. Log the message to the evidence report.
313
462
 
314
- # Phase 2 — run the tool (skill-check --fix, split-body --write, etc.)
463
+ **Dispatch on `status`:**
315
464
 
316
- # Phases 3+4 — verify and restore after the tool call
317
- uv run {descriptionGuardHelper} verify-restore {skill_package}/SKILL.md \
318
- --captured-description "{guarded_description}"
465
+ - **`ok`** (exit 0): symlink already points at `{version}`, so no disk write. Continue to §8a.
466
+ - **`flipped`** (exit 0): symlink was atomically updated (temp-and-replace). Continue to §8a.
467
+ - **`missing-target`** (exit 2): `{skill_group}/{version}/` does not exist on disk, so step 4 §6b did not create it. HALT with status `halted-for-write-failure` and display `halt_message` verbatim.
468
+ - **`mismatch`** (exit 2): the link still points elsewhere after the flip. HALT with status `halted-for-write-failure` and display `halt_message` verbatim.
469
+
470
+ For both exit-2 halts, the halt procedure takes `phase: "write:active-symlink"`, `path: "{skill_group}/active"`, `reason: "<status>: <halt_message>"`. Its rollback removes the new version (the `active` link does not name it) or, in gap-driven mode, restores the package from the snapshot.
471
+
472
+ ### 8a. Verify the Active Symlink
473
+
474
+ Check, read-only, that the `active` link resolves to the version §2 wrote to `metadata.json`, in every mode (consumers that do not read the export manifest fall back to the link, `knowledge/version-paths.md` §Reading Workflows step 5):
475
+
476
+ ```bash
477
+ uv run {updateActiveSymlinkHelper} verify \
478
+ --skill-group {skill_group} \
479
+ --version {version}
480
+ ```
481
+
482
+ On `mismatch` (exit 2), HALT with status `halted-for-write-failure` and display the helper's `halt_message` verbatim: it names the diverged target, the expected version and the recovery command. The halt procedure takes `phase: "write:verify-active-symlink"`, `path: "{skill_group}/active"`, `reason: "mismatch: <halt_message>"`.
483
+
484
+ ### 9. Move the Workspace Clone to the Recorded Commit
485
+
486
+ Run only when `{source_tree_status}` is `ready` and `{workspace_clone}` is not null. test-skill and audit-skill read the skill's source from metadata.json `source_root`, the clone SKF keeps for this repository, and test-skill compares its HEAD with the `source_commit` §2 just wrote. From `{project-root}`, resolve `{sourceTreeHelper}` ← first existing path in `{sourceTreeProbeOrder}`, `{cccGitHygieneHelper}` ← first existing path in `{cccGitHygieneProbeOrder}` and `{checkWorkspaceDriftHelper}` ← first existing path in `{checkWorkspaceDriftProbeOrder}`, then run:
487
+
488
+ ```bash
489
+ uv run {sourceTreeHelper} advance \
490
+ --clone "{workspace_clone}" \
491
+ --source-repo "{source_repo}" \
492
+ --expect-commit "{source_commit}" \
493
+ --target "{target_commit}" \
494
+ --target-ref "{target_ref}" \
495
+ --tree "{source_tree}" \
496
+ --timeout "{tree_timeout}" \
497
+ [--hygiene-helper "{cccGitHygieneHelper}"] \
498
+ [--drift-helper "{checkWorkspaceDriftHelper}"]
319
499
  ```
320
500
 
321
- Do not rely on per-call ad-hoc preservation logic — use the helper.
501
+ Pass `--hygiene-helper` and `--drift-helper` only when they resolved. `{source_commit}` is the commit init.md §6 read from metadata.json, before §2 recorded `{target_commit}`, and `{tree_timeout}` is the time limit init.md §6b chose: the helper stops itself within it.
502
+
503
+ The helper takes the clone's `.skf-workspace.lock`, the lock create-skill also takes, waiting up to a minute, and runs the git hygiene check inside it. It then does nothing when the clone already holds `{target_commit}`; otherwise it moves the clone there only when `{checkWorkspaceDriftHelper}` confirms the clone still holds `{source_commit}` and no tracked file has local changes, taking the commit from `{source_tree}` rather than the network. It never forces a checkout and never moves a clone another run moved. When the clone is missing, it clones it first, as create-skill does.
504
+
505
+ Bind `{advance_status}` ← `status`, `{advance_skip_reason}` ← `skip_reason`, `{advance_head}` ← `head_sha`, `{advance_log}` ← `log_message` and `{advance_warnings}` ← `warnings`. Log `{advance_log}` in the evidence report and add each `{advance_warnings}` entry to `warnings[]` as `source-tree: <entry>`.
322
506
 
323
- **If skill-check available:**
507
+ - **`advanced` or `ok`:** continue.
508
+ - **`skipped`:** add `workspace-clone-not-updated: {advance_skip_reason}; {workspace_clone} holds {advance_head or "no commit"}, so test-skill stops with workspace-drift until it holds {target_commit}` to `warnings[]`, and continue.
509
+ - **`{sourceTreeHelper}` does not resolve, or the command fails or prints no JSON:** add the same warning with reason `helper-unavailable`, and continue.
324
510
 
325
- - Run: `npx skill-check check {skill_package} --fix --format json --no-security-scan` **inside the §0 guard**.
326
- - **Context sync after --fix:** If `fixed[]` is non-empty (i.e., `--fix` modified files on disk), re-read the modified SKILL.md to update the in-context copy. This prevents silent divergence between the in-context SKILL.md and the on-disk version that report will reference. The §0 guard has already restored `description` if divergent; the re-read picks up any other fix-corrected content.
327
- - If `body.max_lines` reported, prefer selective split — extract only the largest Tier 2 section(s) to `references/`, keeping Tier 1 inline (inline passive context achieves 100% task accuracy vs 79% for on-demand retrieval). **If falling back to `npx skill-check split-body {skill_package} --write`, run it inside the §0 guard** — split-body can also touch frontmatter. Verify anchors resolve after split.
328
- - Run: `npx skill-check diff` if original version was preserved.
329
- - Run: `npx skill-check check {skill_package} --format json` for security scan. (Read-only; guard not required.)
511
+ This section never halts: the skill is written, and only the clone test-skill reads is out of step.
330
512
 
331
- Record findings in the evidence report (section 4), including any `description_guard_restored` events recorded by the §0 protocol. These are advisory — do not block on warnings.
513
+ ### 10. Close the Rollback Window
332
514
 
333
- **If skill-check unavailable:** Skip with note — structural checks from step 5 are sufficient.
515
+ Once §9 has run, this run's writes stand: from `{project-root}`, run `uv run {runStateHelper} finish --run-dir "{run_dir}"`. It marks the run finished, then deletes the snapshot: from then on a halt undoes nothing. When it fails or prints no JSON, add `run-state-not-finished: {its message}` to `warnings[]`: no step after this one halts, and step 7 removes the run folder.
334
516
 
335
- ### 8. Route to Next Step
517
+ ### 11. Route to Next Step
336
518
 
337
- This step auto-proceeds — no user choices. Once all files have been written and verified and post-write validation is complete, display "**Proceeding to report...**", then load, fully read, and execute `{nextStepFile}` to display the change report.
519
+ This step auto-proceeds: no user choices. Once §10 has run, display "**Proceeding to report...**", then load, fully read, and execute `{nextStepFile}` to display the change report.
338
520