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,15 +1,39 @@
1
1
  ---
2
2
  nextStepFile: 'detect-changes.md'
3
- manualSectionRulesFile: 'references/manual-section-rules.md'
3
+ # `{gapDrivenStepFile}`: §8 loads it in place of `{nextStepFile}` when
4
+ # `update_mode` is `gap-driven`: it translates and verifies the test
5
+ # report's gaps in place of steps 2 and 3, and goes on to step 4.
6
+ gapDrivenStepFile: 'gap-driven.md'
4
7
  # Resolve `{hashContentHelper}` to the first existing path; HALT if neither
5
- # candidate exists. §5 uses its `manual-inventory` subcommand to capture the
6
- # exact pre-write [MANUAL] inventory (per-block byte-exact interior hashes),
7
- # which write.md §1 (HALT gate) and validate.md Check B later verify against.
8
- # An LLM marker-count would miss an interior truncation that leaves the marker
9
- # count unchanged.
8
+ # candidate exists: a [MANUAL] marker count by eye misses a truncated block.
10
9
  hashContentProbeOrder:
11
10
  - '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
12
11
  - '{project-root}/src/shared/scripts/skf-hash-content.py'
12
+ # Resolve `{skillInventoryHelper}` to the first existing path. §1 lists the
13
+ # skills SKF generated when the invocation names none (interactive only), and
14
+ # step 4 runs it with `--skill` before a flat skill moves: only a flat skill
15
+ # whose metadata.json carries an SKF marker (`flat_skf`) is migrated. If
16
+ # neither exists, §1 shows its prompt without the list and §6c records no
17
+ # source version.
18
+ skillInventoryProbeOrder:
19
+ - '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py'
20
+ - '{project-root}/src/shared/scripts/skf-skill-inventory.py'
21
+ # Resolve `{runLockHelper}` to the first existing path; HALT if neither
22
+ # candidate exists: without the run lock two updates could write one skill.
23
+ runLockProbeOrder:
24
+ - '{project-root}/_bmad/skf/shared/scripts/skf-run-lock.py'
25
+ - '{project-root}/src/shared/scripts/skf-run-lock.py'
26
+ # Resolve `{sourceTreeHelper}` to the first existing path.
27
+ # HALT if neither resolves: without it a remote skill is read at whatever
28
+ # commit SKF's shared clone of its repository is on.
29
+ sourceTreeProbeOrder:
30
+ - '{project-root}/_bmad/skf/shared/scripts/skf-source-tree.py'
31
+ - '{project-root}/src/shared/scripts/skf-source-tree.py'
32
+ # `{findTestReportHelper}`: §1 and §4b find the test report. If neither
33
+ # exists, §1 warns and keeps normal mode and §4b offers nothing.
34
+ findTestReportProbeOrder:
35
+ - '{project-root}/_bmad/skf/shared/scripts/skf-find-test-report.py'
36
+ - '{project-root}/src/shared/scripts/skf-find-test-report.py'
13
37
  ---
14
38
 
15
39
  <!-- Config: communicate in {communication_language}. -->
@@ -22,106 +46,138 @@ Load the existing skill and all its provenance data, detect whether this is an i
22
46
 
23
47
  ## Rules
24
48
 
25
- - Focus only on loading existing artifacts and establishing the baseline — read-only operations
26
- - Do not begin change detection (Step 02)
49
+ - Focus only on loading existing artifacts and establishing the baseline: read-only operations, except the flat-to-versioned migration in §1, the cleanup of an interrupted update §1b finds, and the private source tree §6b prepares (a folder of this run's own, never the shared workspace clone)
50
+ - Do not begin change detection (step 2)
27
51
 
28
52
  ## Steps
29
53
 
54
+ **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.
55
+
56
+ ```bash
57
+ uv run {runStateHelper} halt --run-dir "{run_dir}" \
58
+ [--tree "{source_tree}"] \
59
+ [--lock "{forge_data_folder}/{skill_name}/.skf-update.lock" --owner "{lock_owner}"] \
60
+ [--emit] <<'SKF_JSON'
61
+ {"status": "<status>", "phase": "<phase>", "path": "<path; leave the key out when the halt names none>", "reason": "<reason>", "skill_name": "<{skill_name}, or unknown before §1 resolves it>", "version": "<the metadata.json version, or unknown before §2 reads it>", "previous_version": "<the same>", "update_mode": "<normal, gap-driven or degraded; normal before §1 decides>"}
62
+ SKF_JSON
63
+ ```
64
+
65
+ Pass `--tree` once §6b has bound `{source_tree}`, `--lock` and `--owner` once §1b has bound `{lock_owner}`, and `--emit` in `{headless_mode}`. 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 (`source-tree-not-removed`, `run-lock-not-released`); it never stops on a result. The emitter adds `files_written: []`. 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: "init:<the helper's file name>"` and `reason: "<the helper's file name> is missing; re-install SKF"`.
66
+
67
+ The halt leaves `{run_dir}` in place. No HALT in this step adds a `headless_decisions[]` entry: a halt is not an auto-resolved gate.
68
+
69
+ **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`.
70
+
30
71
  ### 1. Request Skill Path
31
72
 
73
+ **The invocation's skill.** When `{requested_skill}` is set (SKILL.md On Activation), it answers the question below in either mode, and the prompt is not shown.
74
+
75
+ **No skill passed, interactive:** offer the skills SKF generated. Resolve `{skillInventoryHelper}` ← first existing path in `{skillInventoryProbeOrder}` and, from `{project-root}`, run `uv run {skillInventoryHelper} "{skills_output_folder}"`. Above the prompt, list, numbered, the `name` and `active_version` of each `skills[]` entry whose `skf_skill` is true; a number answers with that skill's name. When no candidate resolves, the command fails or prints no JSON, or no entry qualifies, show the prompt alone.
76
+
77
+ **No skill passed, headless:** there is no one to answer, and update-skill never guesses a skill. HALT (halt procedure: `status: "blocked"`, `phase: "init:skill-name"`, `path: "{skills_output_folder}"`, `reason: "input-missing: a headless run needs the skill's name or folder path as its argument"`) and display "**No skill to update.** A headless run takes the skill's name or folder path as its argument, for example `@Ferris US <skill> --headless`."
78
+
32
79
  "**Which skill would you like to update?**
33
80
 
34
81
  Provide either:
35
82
  - A skill name (resolves via version-aware path resolution — see `knowledge/version-paths.md`)
36
83
  - A full path to the skill folder
37
84
  - A skill name with `--from-test-report` to use the test report's gap findings instead of source drift detection
38
- - `--allow-workspace-drift` (gap-driven mode only) to intentionally bypass the step 3 §0.a guard that halts when the local workspace HEAD does not match `metadata.source_commit`. Only use this if you know the spot-checks should read the current workspace instead of the pinned tree — step 6 will NOT automatically re-pin
39
- - `--allow-degraded` (headless mode only) to pre-authorize the lossy degraded full re-extraction if §4 finds no provenance map — without it, a headless run halts `blocked` there rather than silently rebuilding
85
+ - `--allow-workspace-drift` (gap-driven mode only) to intentionally bypass the gap-driven.md §3 guard that halts when the local workspace HEAD does not match `metadata.source_commit`. Under it update-skill takes nothing from HEAD: it moves or pins no provenance line read there, reads no signature, parameter list, return type or node kind there and counts no public API there, and gap-driven.md §3 halts `halted-for-workspace-drift` before merge on any gap that needs one, which every new or modified export does, and on every rescope; step 5 will NOT automatically re-pin
86
+ - `--allow-degraded` (headless mode only) to pre-authorize the lossy degraded full re-extraction if §4 finds no provenance map (not with `--from-test-report`, which needs the map): without it, a headless run halts `blocked` there rather than silently rebuilding
87
+ - `--target-ref <tag|branch|HEAD|commit>` (normal mode, a skill forged from a remote repository) to read that ref's current commit instead of the skill's recorded `source_ref` (for example the `upstream_ref` an audit reports). When the update writes, step 5 records the ref as the new `source_ref` together with the commit it read. `HEAD` follows the remote's default branch; a full 40-character commit pins that commit
40
88
  - `--detect-only` to run detect-changes only and exit; emits the change manifest with no further work and no writes
41
- - `--dry-run` to run detect-changes + re-extract and exit before merge/write; emits what WOULD change without modifying any artifact
89
+ - `--dry-run` to run detect-changes + re-extract and exit before merge/write; emits what WOULD change without modifying any artifact, and a gap-driven.md halt such as `halted-for-remediation-path` or `halted-for-workspace-drift` still stops it
42
90
 
43
- **Skill:** {user provides path or name}"
91
+ **Skill:** {user provides a number from the list, a name or a path}"
44
92
 
45
93
  **Version-Aware Path Resolution:**
46
94
  1. Read `{skills_output_folder}/.export-manifest.json` and look up the skill name in `exports` to get `active_version`
47
- 2. If found: resolve to `{skill_package}` = `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/`
48
- 3. If not in manifest: check for `active` symlink at `{skills_output_folder}/{skill-name}/active` — resolve to `{skill_group}/active/{skill-name}/`
49
- 4. If neither: fall back to flat path `{skills_output_folder}/{skill-name}/`. If SKILL.md exists at the flat path, auto-migrate per `knowledge/version-paths.md` migration rules
95
+ 2. If found: **Manifest-lag guard.** Also read the `active` link at `{skills_output_folder}/{skill-name}/active`. When it resolves to a different version than `active_version`, bind `{active_version}` to the link's target version instead and show an Info note: "manifest active_version {M} lags the active link {N}: updating {N}, the version the last update or forge wrote; run export-skill to publish it." The manifest advances only when export-skill runs, while every update that writes makes a new version and points `active` at it (step 5 §8): a manifest-first read would update the previous version again, and step 4 §6b would then stop at the version folder the last update created. Resolve to `{skill_package}` = `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/` (see `knowledge/version-paths.md` "Reading Workflows")
96
+ 3. If not in manifest: check for `active` symlink at `{skills_output_folder}/{skill-name}/active` — bind `{active_version}` to the version it names and resolve to `{skill_group}/active/{skill-name}/`
97
+ 4. If neither: fall back to the flat path `{skills_output_folder}/{skill-name}/`. If `SKILL.md` exists there, check that SKF generated it before anything moves:
98
+ - Resolve `{skillInventoryHelper}` ← first existing path in `{skillInventoryProbeOrder}`, run `uv run {skillInventoryHelper} {skills_output_folder} --skill {skill-name}`, and bind `{group_flat_skf}` ← `skills[0].flat_skf` and `{group_errors}` ← `skills[0].errors`.
99
+ - **`{group_flat_skf}` is true:** if the invocation carries `--detect-only` or `--dry-run` (read them from the invocation here; the flag handling below comes later), do not migrate and HALT: those modes never move a skill, and every later step reads the versioned forge workspace, which a flat skill does not have yet. Display "**`{skill-name}` still uses the flat layout: nothing was moved.** `--detect-only` and `--dry-run` never migrate a skill, and they need the versioned layout. Run `@Ferris US {skill-name}` once without them, or AS, TS or EX, to move it into that layout, then re-run with the flag." This runs before the §1b lock, so there is no lock to release. The halt procedure takes `status: "blocked"`, `phase: "init:read-only-flat-layout"`, `path: "{skills_output_folder}/{skill-name}/"`, `reason: "flat-layout: read-only modes never migrate a skill; run once without --detect-only or --dry-run"`, with `version` and `previous_version` `"unknown"`. Otherwise auto-migrate per `knowledge/version-paths.md` migration rules.
100
+ - **Otherwise** (`{group_flat_skf}` is false, the status is not `ok`, `skills[]` has no entry, or no helper candidate resolves): do not migrate. HALT before anything moves, with `halt_reason: "not-skf-output"` and this message: "**`{skill-name}` is not SKF output: nothing was moved.** `{skills_output_folder}/{skill-name}/SKILL.md` has no SKF marker in the `metadata.json` beside it, so SKF will not move or update it. A shared `{skills_output_folder}` is supported: SKF leaves the skills it did not generate alone, so manage `{skill-name}` yourself. Only if `{skills_output_folder}` holds a module's own source rather than skills, set `skills_output_folder` in `{project-root}/_bmad/skf/config.yaml` to a folder of its own, move your SKF skills there and re-run `/skf-setup`." When there is another reason, show it in place of the marker sentence: `{group_errors}` when it is non-empty (for example, the folder is a link, which SKF never moves), the helper's `error` when the status is not `ok`, and, when no helper candidate resolved, that SKF could not check the marker because `skf-skill-inventory.py` is missing, so re-install SKF. This runs before the §1b lock, so there is no lock to release. The halt procedure takes `status: "blocked"`, `phase: "init:ownership-gate"`, `path: "{skills_output_folder}/{skill-name}/"`, `reason: "not-skf-output: {the reason the message shows}"`, with `version` and `previous_version` `"unknown"`.
50
101
  5. Store the resolved path as `{resolved_skill_package}` for all subsequent artifact loading
51
- 6. Bind `{baseline_version}` to the pre-update version — for an update this is the version being updated, i.e. the `{active_version}` resolved in step 1 above (the flat-path fallback in step 4 has no version, so use the package version read from metadata.json in §2). Step 2 §1c passes `{baseline_version}` to `skf-provenance-gap-dispatch.py` as a required argument; leaving it unbound makes the helper search a wrong/empty directory and silently return `no-report`, dropping the major-version off-ramp.
102
+ 6. Bind `{baseline_version}` to the pre-update version — for an update this is the version being updated, i.e. the `{active_version}` steps 1–3 resolved (the flat-path fallback in step 4 has no version, so use the package version read from metadata.json in §2). Step 2 §1c passes `{baseline_version}` to `skf-provenance-gap-dispatch.py` as a required argument; leaving it unbound makes the helper search a wrong/empty directory and silently return `no-report`, dropping the major-version off-ramp.
52
103
 
53
104
  Resolve the path to an absolute skill folder location.
54
105
 
55
- **If `--from-test-report` was provided (or user references a test report):**
106
+ **If `--from-test-report` was provided (or user references a test report):** find the newest finished test report of the resolved skill with `{findTestReportHelper}` (resolve it ← first existing path in `{findTestReportProbeOrder}`). From `{project-root}`, run:
107
+
108
+ ```bash
109
+ uv run {findTestReportHelper} find \
110
+ --forge-data-folder "{forge_data_folder}" \
111
+ --skill-name "{skill_name}" \
112
+ [--version "{active_version}"]
113
+ ```
56
114
 
57
- `skf-test-skill` writes timestamped test-report filenames (`test-report-{skill_name}-{ISO-TIMESTAMP}-{HASH}.md`) — there is no exact-name `test-report-{skill_name}.md` on disk. Locate the most recent report by glob, mirroring `skf-export-skill/references/load-skill.md §4b`:
115
+ Pass `--version` only when steps 1-3 above bound `{active_version}` (the flat fallback in step 4 has none). Never glob, sort or read a report's frontmatter by hand.
58
116
 
59
- 1. Glob `{forge_data_folder}/{skill_name}/{active_version}/test-report-{skill_name}-*.md` (i.e. `{forge_version}/test-report-{skill_name}-*.md`). Sort matches descending by the parsed ISO-timestamp segment in the filename (`YYYYMMDDTHHMMSSZ` between the skill name and the hash — `sort -r` on the filename works because the timestamp is the first variable component). Take the first match.
60
- 2. If the versioned glob returns nothing, fall back to the same glob at the flat path `{forge_data_folder}/{skill_name}/test-report-{skill_name}-*.md`. Pick the newest by parsed timestamp.
61
- 3. If neither glob returns anything, look for the stable companion `skf-test-skill-result-latest.json` in the same two directories (versioned first, then flat). Read the report path from `outputs[]` per the canonical contract documented at `shared/references/output-contract-schema.md` (resolved by skf-test-skill step 6 §4c) and load that file.
117
+ - **`status` is `found` and `report_exists` is true:** set `test_report_path` ← `path`, `{test_report_run_id}` ← `run_id` and `update_mode: gap-driven`. Name the report the helper picked (its file name, such as `test-report-{skill_name}-20260507T050917Z-487606-9b2f.md`, with its `testResult`, `score` and `source`) and each newer one it passed over as unfinished (`skipped[]`), so an operator can find them from the log, and add each of its `warnings[]` to `warnings[]` as `test-report: <entry>`.
118
+ - **Otherwise** (`not-found`; `found` with `report_exists` false, a result file naming a report that is gone; no candidate resolves; or the command fails or prints no JSON): warn that no test report was found, with the cause, and continue in normal source drift mode.
62
119
 
63
- If a report is located, set `test_report_path` in context to the resolved absolute path and set `update_mode: gap-driven`. Surface the actual file picked in the message (e.g. `test-report-{skill_name}-20260507T050917Z-487606-9b2f.md`) so an operator can navigate to the report from the log. If all three lookups fail, warn and continue with normal source drift mode.
120
+ **If `--allow-workspace-drift` was provided:** set `allow_workspace_drift: true` in workflow context. This flag is consumed by gap-driven.md §3's pre-flight drift guard (gap-driven mode only) and has no effect in normal source-drift mode.
64
121
 
65
- **If `--allow-workspace-drift` was provided:** set `allow_workspace_drift: true` in workflow context. This flag is consumed by step 3 §0.a's pre-flight drift guard (gap-driven mode only) and has no effect in normal source-drift mode.
122
+ **If `--allow-degraded` was provided:** set `allow_degraded: true` in workflow context. This flag is consumed by §4 below when no provenance map is found under `{headless_mode}`; it has no effect interactively (the [D]/[X] prompt is shown), when a provenance map is present, or in gap-driven mode, where §4 halts `blocked` instead.
66
123
 
67
- **If `--allow-degraded` was provided:** set `allow_degraded: true` in workflow context. This flag is consumed by §4 below when no provenance map is found under `{headless_mode}`; it has no effect interactively (the [D]/[X] prompt is shown) or when a provenance map is present.
124
+ **If `--target-ref` was provided:** set `{target_ref_override}` to its value in workflow context; §6b passes it to `{sourceTreeHelper}`. Decide this after the test-report lookup above: when `update_mode` is `gap-driven` it has no effect, since gap-driven mode repairs the skill at its pinned commit, so warn the user once at flag-parse time ("`--target-ref` has no effect with `--from-test-report`: gap-driven mode repairs the skill at its pinned commit") and leave `{target_ref_override}` unset. When `--from-test-report` found no report, the run continues in normal mode and keeps `{target_ref_override}`; when §4b switches a run to gap-driven mode, it unsets it with the same warning.
68
125
 
69
- **If `--detect-only` was provided:** set `detect_only_mode: true` in workflow context. After step 2 (detect-changes) completes, jump directly to step 7 (report) — skip re-extract, merge, validate, and write. The report emits the change manifest and a `SKF_UPDATE_RESULT_JSON` envelope with `status: "detect-only"`. **Compatibility:** `--detect-only` short-circuits before §0.a runs, so `--allow-workspace-drift` is silently ignored in detect-only mode (warn the user once at flag-parse time: "`--allow-workspace-drift` has no effect with `--detect-only` — workspace drift guard runs in step 3 §0.a, which is skipped").
126
+ **If `--detect-only` was provided:** set `detect_only_mode: true` in workflow context. After step 2 (detect-changes) completes (gap-driven.md §2 in gap-driven mode), jump directly to step 6 (report): skip re-extract, merge and write. The report emits the change manifest and a `SKF_UPDATE_RESULT_JSON` envelope with `status: "detect-only"`. **Compatibility:** `--detect-only` short-circuits before gap-driven.md §3 runs, so `--allow-workspace-drift` is silently ignored in detect-only mode (warn the user once at flag-parse time: "`--allow-workspace-drift` has no effect with `--detect-only`: the workspace drift guard runs after the gaps are translated, which `--detect-only` skips").
70
127
 
71
- **If `--dry-run` was provided:** set `dry_run_mode: true` in workflow context. After step 3 (re-extract) completes, jump directly to step 7 (report) — skip merge, validate, and write. The report emits what would change with `status: "dry-run"` in the envelope. No artifact on disk is modified — `--dry-run` is the "show me what an update would do without committing" mode.
128
+ **If `--dry-run` was provided:** set `dry_run_mode: true` in workflow context. After step 3 (re-extract) completes (gap-driven.md §5 in gap-driven mode), jump directly to step 6 (report): skip merge and write. The report emits what would change with `status: "dry-run"` in the envelope. No artifact on disk is modified, in any mode (a repair from a test report and a docs-only skill included), and step 2 reports each skill brief amendment it would make as proposed instead of writing it: `--dry-run` is the "show me what an update would do without committing" mode.
72
129
 
73
130
  **If BOTH `--detect-only` AND `--dry-run` were provided:** `--detect-only` wins (it's the more restrictive). Warn the user once: "`--detect-only` supersedes `--dry-run`; re-extract is skipped." Set `detect_only_mode: true`, ignore `dry_run_mode`.
74
131
 
75
132
  ### 1b. Concurrency Guard
76
133
 
77
- **Skip this section entirely if `detect_only_mode` OR `dry_run_mode` is true.** Both inspection modes are read-only — they do not modify any artifact and are safe to run alongside a concurrent real update.
134
+ **Skip this section entirely if `detect_only_mode` OR `dry_run_mode` is true.** Both inspection modes are read-only, and the private source tree §6b prepares for them never writes to the shared workspace clone.
78
135
 
79
- Two concurrent `skf-update-skill` runs against the same `{forge_data_folder}/{skill_name}/` can corrupt provenance: one would write metadata.json mid-way through the other's extraction. The lock below catches the common accidental-double-invoke case (user re-runs in another shell before the first finishes). It is a **best-effort PID-file guard**, not a held flock — the LLM-driven workflow spans many turn boundaries and no single bash invocation can hold flock across them. Use the pattern from `skf-create-skill/references/source-resolution-protocols.md:87` (workspace concurrency guard) as the conceptual model.
136
+ Two concurrent `skf-update-skill` runs against the same `{forge_data_folder}/{skill_name}/` can corrupt provenance: one would write metadata.json mid-way through the other's extraction. The run lock is separate from the `.skf-workspace.lock` on the source clone, which only write.md §9 takes.
80
137
 
81
- **Mirror this exactly so the guard works the same way every run:**
138
+ Resolve `{runLockHelper}` ← first existing path in `{runLockProbeOrder}`; it stays bound for the rest of the run. The lock's owner carries this run's `{run_id}`, the name of its run folder, so a later update that finds the lock stale knows which run folder holds what this run wrote. From `{project-root}`, run:
82
139
 
83
140
  ```bash
84
- # Lock file path — one per skill, lives next to skill-brief.yaml
85
- LOCK={forge_data_folder}/{skill_name}/.skf-update.lock
86
- mkdir -p "$(dirname "$LOCK")"
87
-
88
- if [ -f "$LOCK" ]; then
89
- HELD_PID=$(head -n1 "$LOCK" 2>/dev/null | awk '{print $1}')
90
- if [ -n "$HELD_PID" ] && kill -0 "$HELD_PID" 2>/dev/null; then
91
- # Live PID — another update is running. HALT.
92
- echo "skf-update-skill: another update is in progress (pid=$HELD_PID, started $(awk 'NR==2' "$LOCK" 2>/dev/null))"
93
- # (LLM emits SKF_UPDATE_RESULT_JSON status=halted-for-concurrent-run, see below)
94
- exit 1
95
- fi
96
- # Dead PID — lock left by a prior halted or crashed run; clear + overwrite
97
- echo "skf-update-skill: clearing lock from a prior halted/crashed run (pid=$HELD_PID)"
98
- fi
99
-
100
- # Acquire: write our PID + start timestamp (one per line)
101
- printf '%s\n%s\n' "$$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$LOCK"
141
+ uv run {runLockHelper} acquire \
142
+ --lock "{forge_data_folder}/{skill_name}/.skf-update.lock" \
143
+ --owner "update-skill:{skill_name}:{run_id}" \
144
+ --stale-after 60
102
145
  ```
103
146
 
104
- **Halt protocol on live-PID collision:**
147
+ It prints one JSON line. Dispatch on its exit code:
105
148
 
106
- - Display: `"**Another update is in progress.** The skill {skill_name} is locked by pid={HELD_PID} (started {timestamp from line 2 of the lock file}). Wait for that run to finish, or — if you know that pid is no longer running — delete {LOCK} manually and re-run."`
107
- - In `{headless_mode}`, emit `SKF_UPDATE_RESULT_JSON` with `status: "halted-for-concurrent-run"`, `error: {phase: "init:concurrency-guard", path: "{LOCK}", reason: "another update in progress (pid={HELD_PID})"}`, and exit immediately. **No `headless_decisions[]` entry** — this is a hard halt before any gate fires.
149
+ - **0** (`acquired` true): bind `{lock_owner}` ← `owner`; every renewal and release passes that value. When `stale_replaced` is not null, this run replaced a stale lock, left by a run that stopped without releasing it or held by one still waiting at a gate, whose renewal will then halt. Display the helper's `message` and add `run-lock-replaced: {stale_replaced.held_by} since {stale_replaced.held_since}` to `warnings[]` (`an unnamed owner` in place of a null `held_by`, as in a lock file an older SKF version wrote), then clean up after it as below.
150
+ - **3** (`acquired` false): another run holds a fresh lock. HALT (halt procedure: `status: "halted-for-concurrent-run"`, `phase: "init:concurrency-guard"`, `path: "{forge_data_folder}/{skill_name}/.skf-update.lock"`, `reason: "another update in progress: {message}"`, with `version` and `previous_version` `"unknown"`, since §2 reads metadata.json later) and display "**Another update of {skill_name} is in progress.** {message}", with the helper's `message`, which names that run, the lock file to delete when no update of the skill is running and when the lock goes stale. This run took no lock, so it releases none.
151
+ - **No candidate resolves, any other exit, or no JSON:** HALT (halt procedure: `status: "blocked"`, `phase: "init:concurrency-guard"`, `path: "{forge_data_folder}/{skill_name}/.skf-update.lock"`, `reason: "run-lock-failed: {that message}"`) and display "**The run lock could not be taken:** {the `message` the helper printed on stderr, or 'skf-run-lock.py is missing; re-install SKF'}". No lock was taken, so none is released.
152
+
153
+ **Clean up an interrupted update.** When `stale_replaced.held_by` is not null, the update that held the lock may have stopped without finishing (a crash, a killed session, or a wait at a gate past the stale time) and left what it was writing half done: a gap-driven repair's package edited in place, or a new version folder the `active` link never reached. Undo it now, rather than stopping later at "version already exists". From `{project-root}`, run the helper SKILL.md On Activation resolved, with that owner exactly as the acquire printed it:
154
+
155
+ ```bash
156
+ uv run {runStateHelper} rollback \
157
+ --run-root "{project-root}/_bmad-output/.skf-run" \
158
+ --owner "{stale_replaced.held_by}" \
159
+ --skill "{skill_name}" \
160
+ --remove-run-dir
161
+ ```
162
+
163
+ It finds that run's folder by the run id in the owner (`update-skill:{skill_name}:<run id>`), reads what the run recorded before its first write, restores a repair's package (with the version's provenance map, evidence report and skill brief) from the snapshot it took, removes the version folders it created (never one the `active` link names), and deletes its run folder (only a `.skf-run/skf-update-skill-<run id>` folder: it refuses a run id that would lead anywhere else); a run that finished its writes, or never began them, needs nothing undone. An owner of another skill or workflow, or one with no run id (an older SKF's lock), has no run folder: `status` `no-run-folder`, and nothing to report. On exit 0 with any other `status`, add `interrupted-run-cleaned: {stale_replaced.held_by}: {status}; restored {restored}; removed {removed}` to `warnings[]` and tell the user in one line what was put back. On any other exit, or no JSON, add `interrupted-run-not-cleaned: {stale_replaced.held_by}: {its failed[] or message}` and go on: a version folder it left then stops step 4 §6b, whose message says which folders to delete.
108
164
 
109
165
  **Release contract:**
110
166
 
111
- - The terminal health-check step (step 8) deletes the lock as its final action — the normal end of every non-inspection run. The two init-stage headless halts below (§4 no-provenance-map, §6 invalid-source-path) also delete it explicitly, since they fire right after acquisition, before the terminal step runs.
112
- - Mid-workflow halts (detect-changes, re-extract, merge, write) do **not** delete the lock themselves — they rely on the self-heal below. This is deliberate: several of those halt sites are also reachable under `--detect-only`/`--dry-run`, which never acquired this lock, so a blind `rm -f` there could clobber a concurrent real update's lock.
113
- - The lock is best-effort and self-healing: whatever a halt or crash (process kill, host reboot) leaves behind is cleared by the next run's live-PID check above, since the stored PID is a short-lived bash PID that is already dead. No manual cleanup needed in the common case.
167
+ - Every exit after this section releases the lock, so a run that halts does not block the next one: step 7 on a run that reaches it, and every other exit through the halt procedure of the step file it fires in: in this step §2's ABORTs, the Stack Skill Guard, §3, the §4 and §6 halts and the §6b source tree halts, and every later step's halts through their own. The release never stops on the result: when it fails or prints no JSON, the halt helper adds `run-lock-not-released: {forge_data_folder}/{skill_name}/.skf-update.lock` to its envelope's `warnings[]`. A release deletes the lock only while `{lock_owner}` holds it, so it never removes a lock another run took; the read-only modes take no lock and release none.
168
+ - A crashed or killed run leaves its lock until it goes stale. A run that waits at a gate past that time can lose its lock to the next update, so merge.md §6b and write.md §2 renew the lock before the run writes the skill, and halt `halted-for-concurrent-run` when this run no longer holds it.
169
+ - The private source tree §6b prepares has its own contract: step 7 removes it, every HALT or ABORT after §6b removes it first (the halt procedure of its step file), and a later run's §6b removes a tree a crashed or abandoned run left behind once it is seven days old.
114
170
 
115
171
  ### 2. Validate Required Artifacts
116
172
 
117
173
  **Check SKILL.md exists:**
118
174
  - Load `{resolved_skill_package}/SKILL.md`
119
- - If missing: **ABORT** — "No SKILL.md found at `{resolved_skill_package}`. Run create-skill first."
175
+ - If missing: HALT (halt procedure: `status: "blocked"`, `phase: "init:validate-artifacts"`, `path: "{resolved_skill_package}/SKILL.md"`, `reason: "no SKILL.md in the skill package"`): "No SKILL.md found at `{resolved_skill_package}`. Run create-skill first."
120
176
 
121
177
  **Check metadata.json exists:**
122
178
  - Load `{resolved_skill_package}/metadata.json`
123
- - Extract: `name`, `skill_type` (single or stack), `version`, `generation_date`, `confidence_tier`, `source_root`
124
- - If missing: **ABORT** — "No metadata.json found. This skill may have been created manually. Run create-skill to generate provenance data."
179
+ - Extract: `name`, `skill_type` (single or stack), `version`, `generation_date`, `confidence_tier`, `source_type`, `scope_type` and `language` (when present), `source_repo`, `source_root`, `source_ref`, `source_commit`
180
+ - If missing: HALT (halt procedure: `status: "blocked"`, `phase: "init:validate-artifacts"`, `path: "{resolved_skill_package}/metadata.json"`, `reason: "no metadata.json in the skill package"`): "No metadata.json found. This skill may have been created manually. Run create-skill to generate provenance data."
125
181
 
126
182
  **Detect skill type from metadata:**
127
183
  - If `skill_type == "single"` or absent: flag as single skill
@@ -136,7 +192,7 @@ After loading metadata.json, check `skill_type`:
136
192
  **To update this stack skill**, run `skf-create-stack-skill` with the same project path. It will re-analyze manifests (code-mode) or re-read constituent skills (compose-mode) and produce an updated stack.
137
193
 
138
194
  If you came here from an audit report, the drift report identifies which constituent libraries changed — use that to decide whether re-composition is needed."
139
- - Exit the workflow (do not proceed to step 2)
195
+ - Then HALT (halt procedure: `status: "blocked"`, `phase: "init:stack-skill-guard"`, `path: "{resolved_skill_package}"`, `reason: "stack-skill: a stack skill is re-composed with skf-create-stack-skill, never updated"`): the redirect ends the run, and step 2 never runs
140
196
 
141
197
  **This guard is the single gate for stack skills** — every stack is redirected to `skf-create-stack-skill` here, before step 2, and no flag (`--detect-only`, `--dry-run`, `--from-test-report`, `--allow-workspace-drift`) bypasses it. Every later stage therefore runs against a single skill only and carries no stack-merge branch.
142
198
 
@@ -144,24 +200,26 @@ After loading metadata.json, check `skill_type`:
144
200
 
145
201
  **Load `{sidecar_path}/forge-tier.yaml`:**
146
202
  - Extract: `tier` (Quick, Forge, Forge+, or Deep), available tools
147
- - If missing: **ABORT** — "No forge-tier.yaml found. Run setup first to detect available tools."
203
+ - If missing: HALT (halt procedure: `status: "blocked"`, `phase: "init:forge-tier"`, `path: "{sidecar_path}/forge-tier.yaml"`, `reason: "no forge-tier.yaml; run setup first"`): "No forge-tier.yaml found. Run setup first to detect available tools."
148
204
 
149
205
  **Apply tier override:** Read `{sidecar_path}/preferences.yaml`. If `tier_override` is set and is a valid tier value (Quick, Forge, Forge+, or Deep), use it instead of the detected tier.
150
206
 
151
207
  **Determine analysis capabilities:**
152
208
  - **Quick:** text pattern matching only → T1-low confidence
153
- - **Forge:** AST structural extraction → T1 confidence
154
- - **Forge+:** AST structural extraction + CCC semantic ranking → T1 confidence (with ccc signals)
155
- - **Deep:** AST + QMD semantic enrichment → T1 + T2 confidence
209
+ - **Forge:** AST structural extraction → T1 for each export an ast-grep rule matches, T1-low for each export read by eye
210
+ - **Forge+:** AST structural extraction, plus a CCC check of rename candidates in a local source (step 2 Category C) → the same labels as Forge
211
+ - **Deep:** AST + QMD semantic enrichment → the same labels as Forge, plus T2
156
212
 
157
213
  ### 4. Load Provenance Map
158
214
 
159
- **Load `{forge_data_folder}/{skill_name}/{active_version}/provenance-map.json`** (i.e., `{forge_version}/provenance-map.json`). If not found at the versioned path, fall back to `{forge_data_folder}/{skill_name}/provenance-map.json`:
160
- - Extract: export list, file mappings, extraction timestamps, confidence tiers
161
- - Calculate provenance age (days since last extraction)
215
+ **Find `{forge_data_folder}/{skill_name}/{active_version}/provenance-map.json`** (i.e., `{forge_version}/provenance-map.json`). If not found at the versioned path, fall back to `{forge_data_folder}/{skill_name}/provenance-map.json`. Bind `{provenance_map_path}` to the path found. Do not load the map into context: the steps that need it read the file through their helpers (step 2's, gap-driven.md §1's in a repair, and step 5's `apply`). Read from it only what §7 shows: the number of `entries[]` (`{export_count}`) and the provenance age, the days since its `last_update`, else its `generated_at`.
162
216
 
163
217
  **If provenance map missing at both paths:**
164
218
 
219
+ **When `update_mode` is `gap-driven`, offer no [D]**, interactive or headless, `--allow-degraded` included: a repair spot-checks the report's gaps against this map. HALT (halt procedure: `status: "blocked"`, `phase: "init:load-provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "gap-driven-needs-provenance-map: run a normal update first, which offers the degraded rebuild and writes a map, then re-run test-skill"`) and display "**A repair from a test report needs a provenance map, and `{skill_name}` has none.** Run a normal update first, which offers the degraded rebuild and writes a map, then re-run test-skill."
220
+
221
+ Otherwise:
222
+
165
223
  "**WARNING:** No provenance map found at `{forge_version}/provenance-map.json` or flat fallback.
166
224
 
167
225
  Without a provenance map, update-skill cannot perform targeted change detection. Options:
@@ -172,39 +230,130 @@ Without a provenance map, update-skill cannot perform targeted change detection.
172
230
  Select: [D] Degraded / [X] Abort"
173
231
 
174
232
  - If D: set `degraded_mode = true`, proceed with full extraction scope
175
- - If X: **ABORT**
233
+ - If X: HALT (halt procedure: `status: "blocked"`, `phase: "init:load-provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "no provenance map; the user chose to run create-skill first"`)
176
234
 
177
- **In `{headless_mode}` without `--allow-degraded` (default):** do not auto-select [D]. Degraded mode is a full, lossy T1-low re-extraction — choosing it unattended would silently swap surgical update for a create-skill-equivalent rebuild, a policy call that belongs to an operator. Halt instead: release the lock (`rm -f "$LOCK"`), emit `SKF_UPDATE_RESULT_JSON` with `status: "blocked"`, `error: {phase: "init:load-provenance-map", path: "{forge_version}/provenance-map.json", reason: "no provenance map at versioned or flat path; degraded full re-extraction needs a human decision"}`, and exit. No `headless_decisions[]` entry — this is a hard halt, not an auto-resolved gate.
235
+ **GATE [default: HALT unless `--allow-degraded`]**: in `{headless_mode}` without `--allow-degraded` (default), do not auto-select [D]. Degraded mode is a full, lossy T1-low re-extraction: choosing it unattended would silently swap surgical update for a create-skill-equivalent rebuild, a policy call that belongs to an operator. HALT instead (halt procedure: `status: "blocked"`, `phase: "init:load-provenance-map"`, `path: "{forge_version}/provenance-map.json"`, `reason: "no provenance map at versioned or flat path; degraded full re-extraction needs a human decision"`).
178
236
 
179
- **In `{headless_mode}` with `--allow-degraded` (`allow_degraded: true`):** the operator pre-authorized the lossy rebuild for this run, so treat it as an auto-resolved [D] rather than a halt. Set `degraded_mode = true`, proceed with full extraction scope, and append to in-context `headless_decisions[]`: `{gate: "init.degraded-rebuild", default_action: "X", taken_action: "D", reason: "headless: --allow-degraded pre-authorized degraded full re-extraction", evidence: "no provenance map at {forge_version}/provenance-map.json or flat fallback"}`. Continue to step 2.
237
+ **In `{headless_mode}` with `--allow-degraded` (`allow_degraded: true`):** the operator pre-authorized the lossy rebuild for this run, so treat it as an auto-resolved [D] rather than a halt. Set `degraded_mode = true`, proceed with full extraction scope, and record the decision in the run's decision log, from `{project-root}`:
180
238
 
181
- ### 5. Load [MANUAL] Section Inventory
239
+ ```bash
240
+ uv run {emitEnvelopeHelper} record --workflow skf-update-skill --run-dir "{run_dir}" --decision <<'SKF_JSON'
241
+ {"gate": "init.degraded-rebuild", "default_action": "X", "taken_action": "D", "reason": "headless: --allow-degraded pre-authorized degraded full re-extraction", "evidence": {"provenance_map": "<{forge_version}/provenance-map.json, with / separators>", "flat_fallback": "missing"}}
242
+ SKF_JSON
243
+ ```
182
244
 
183
- Load {manualSectionRulesFile} to understand [MANUAL] detection patterns (the human-readable rules for markers, parent-section mapping, and orphan/nesting handling).
245
+ Continue to §4b, which skips itself in degraded mode.
184
246
 
185
- **Capture the [MANUAL] inventory deterministically.** The workflow's headline rule is "[MANUAL] sections survive regeneration with zero content loss" — the pre-write inventory captured here is the exact baseline that write.md §1 and validate.md Check B verify against, so it must be a per-block byte-exact hash, not an eyeballed marker count. Run the `manual-inventory` subcommand of `{hashContentHelper}` and persist its JSON:
247
+ ### 4b. Offer an Unconsumed Test Report
248
+
249
+ **Run this section only when `--from-test-report` was not given and `degraded_mode` is false** (a repair reads the provenance map): a user who follows a failing test often runs a plain update. Resolve `{findTestReportHelper}` as §1 does and, from `{project-root}`, run:
250
+
251
+ ```bash
252
+ uv run {findTestReportHelper} find \
253
+ --forge-data-folder "{forge_data_folder}" \
254
+ --skill-name "{skill_name}" \
255
+ [--version "{active_version}"] \
256
+ --newer-than "{generation_date}" \
257
+ --provenance-map "{provenance_map_path}"
258
+ ```
259
+
260
+ The report is **unconsumed** when `status` is `found`, `report_exists` is true, `testResult` is `fail` or `pass-with-drift` (the two verdicts test-skill sends to update-skill), `newer` is not `false` (`null`, a time that cannot be read, offers the report rather than hiding it) and `applied` is not `true` (write.md §3 records the report a gap-driven repair applied). Otherwise, or when no candidate resolves or the command fails or prints no JSON, continue to §5. For an unconsumed report, bind `{unconsumed_test_report}` ← `path` and `{unconsumed_test_result}` ← `testResult`, then:
261
+
262
+ - **Interactive:** present the choice below. Its default follows the verdict: [G] for `fail`; [S] for `pass-with-drift`, which test-skill reached against a workspace HEAD other than the pinned commit, so a repair would read that same tree and halt `halted-for-workspace-drift`, while a normal update reads a fresh tree and records its commit.
263
+
264
+ "**Test report `{its file name}` ({testResult}, score {score}) is newer than this skill and has not been applied.**
265
+
266
+ [G] Repair the gaps it lists (gap-driven mode, as `--from-test-report` runs it)
267
+ [S] Check the source for changes (normal mode)"
268
+
269
+ - **[G]:** set `test_report_path` ← `{unconsumed_test_report}`, `{test_report_run_id}` ← `run_id` and `update_mode: gap-driven`, then unbind `{unconsumed_test_report}`. When `{target_ref_override}` is set, give §1's `--target-ref` warning and unset it.
270
+ - **[S]:** keep normal mode and add `unconsumed-test-report: {unconsumed_test_report}` to `warnings[]`.
271
+ - **GATE [default: S]**: headless (`{headless_mode}` true), keep normal mode, the mode the caller asked for, and add `unconsumed-test-report: {unconsumed_test_report}` to `warnings[]`. The warning is the notice, so the gate adds no `headless_decisions[]` entry.
272
+
273
+ While `{unconsumed_test_report}` stays bound, step 6's no-change report points to it instead of saying no action is required.
274
+
275
+ ### 5. Load [MANUAL] Section Inventory
276
+
277
+ **Capture the [MANUAL] inventory deterministically.** The workflow's headline rule is "[MANUAL] sections survive regeneration with zero content loss", and the pre-write inventory captured here is the exact baseline step 4 §4 amends with the user's decisions and write.md §1 verifies against, so it must be a per-block byte-exact hash, not an eyeballed marker count. Run the `manual-inventory` subcommand of `{hashContentHelper}` and persist its JSON beside the §1b lock, never in a version folder: an update that writes leaves the previous version's folders unchanged (step 4 §6b).
186
278
 
187
279
  ```bash
188
280
  uv run {hashContentHelper} manual-inventory {resolved_skill_package}/SKILL.md \
189
- > {forge_version}/.manual-inventory.json
281
+ > {forge_data_folder}/{skill_name}/.skf-update-manual-inventory.json
190
282
  ```
191
283
 
192
- The emitted JSON is `{"blocks":[{name, content_hash, byte_offset, parent_heading}...], "count":N}` — each `content_hash` covers the block's byte-exact interior, so a later interior truncation that leaves the marker count unchanged is still caught. Bind the persisted path as `{manual_inventory}` in context; write.md §1 and validate.md Check B pass it to `manual-verify`. Surface the block `count` in the baseline summary (§7 `{manual_count}`).
284
+ When `detect_only_mode` or `dry_run_mode` is true, run the same command without the redirect and read its output instead: those modes write nothing and never verify the inventory. The emitted JSON is `{"blocks":[{name, content_hash, byte_offset, parent_heading}...], "count":N}`: each `content_hash` covers the block's byte-exact interior, so a later interior truncation that leaves the marker count unchanged is still caught. Bind the persisted path as `{manual_inventory}` in context (the read-only modes bind none); step 4 §4 amends it and rebinds `{manual_inventory}` to the amended copy that later `manual-verify` calls read. Surface the block `count` in the baseline summary (§7 `{manual_count}`).
285
+
286
+ ### 6. Resolve the Source
193
287
 
194
- ### 6. Resolve Source Code Path
288
+ Bind the source fields from the `metadata.json` loaded in §2 (the provenance map does not carry `source_root`): `{source_root}` ← `source_root`, `{source_repo}` ← `source_repo`, `{source_ref}` ← `source_ref` and `{source_commit}` ← `source_commit`, each an empty string when the field is null or missing. `{source_ref}` and `{source_commit}` keep these values for the whole run: step 5 records any new commit in the artifacts only.
195
289
 
196
- **From provenance map (if available):**
197
- - Extract `source_root` path
198
- - Validate source path exists and is accessible
290
+ - **Docs-only skill** (`source_type: "docs-only"` in the brief or metadata.json, as step 3 §1 checks): there is no source tree. Skip §6b, §6c and the path check below. If `{target_ref_override}` is set, HALT as §6b's **Every §6b HALT** describes, with `{source_tree_reason}` = `target-ref-needs-remote-source` and `{source_tree_message}` = "`--target-ref` applies only to a skill forged from a remote repository".
291
+ - **Gap-driven mode** (`update_mode` is `gap-driven`): a repair reads the commit the skill is pinned to, and gap-driven.md §3 checks that `{source_root}` holds it before reading anything. Skip §6b and §6c and run the path check below.
292
+ - **Every other mode**, `--detect-only` and `--dry-run` included: run §6b. §6b runs the path check below only when the helper reports `skipped`.
199
293
 
200
- **If source path invalid or missing:**
294
+ **Path check:** validate that `{source_root}` exists and is accessible.
201
295
 
202
- "**Source path from provenance map is invalid:** `{source_root}`
296
+ **If the source path is invalid or missing:**
297
+
298
+ "**Source path from metadata.json is invalid:** `{source_root}`
203
299
 
204
300
  Provide the current source code path:
205
301
  **Path:** {user provides path}"
206
302
 
207
- **In `{headless_mode}`:** there is no operator to supply a path. Halt: release the lock (`rm -f "$LOCK"`), emit `SKF_UPDATE_RESULT_JSON` with `status: "blocked"`, `error: {phase: "init:resolve-source-path", path: "{source_root}", reason: "source_root from provenance map is invalid or inaccessible and no interactive path can be supplied"}`, and exit. No `headless_decisions[]` entry — this is a hard halt, not an auto-resolved gate.
303
+ Bind `{source_root}` to the path the user gives.
304
+
305
+ **In `{headless_mode}`:** there is no operator to supply a path. HALT (halt procedure: `status: "blocked"`, `phase: "init:resolve-source-path"`, `path: "{source_root}"`, `reason: "source_root from metadata.json is invalid or inaccessible and no interactive path can be supplied"`).
306
+
307
+ ### 6b. Prepare the Source Tree
308
+
309
+ A skill forged from a remote repository at Forge tier or above records, as its `source_root`, the clone SKF keeps for that repository. Every SKF run on the repository shares that clone and leaves it at the commit it needed, so this run never reads it as it stands. `{sourceTreeHelper}` gives the run a private tree at one commit (the commit `{source_ref}` points to upstream now, or the tag, branch, `HEAD` or commit `--target-ref` names) and lists the files git changed between `{source_commit}` and that commit. Change detection, re-extraction, merge's file copies and write's citation check all read that tree. The helper never writes to the shared clone (step 5 moves the clone at the end of a real update), so this section runs in every mode.
310
+
311
+ Resolve `{sourceTreeHelper}` ← first existing path in `{sourceTreeProbeOrder}`. Bind `{tree_timeout}` to the seconds the helper may take: `100` when your shell tool stops a command after two minutes or you do not know its limit, otherwise a little under that limit, such as `540` under a 10-minute limit: a first fetch of a large repository is slow. The helper stops itself within `--timeout` seconds and still prints its result, so give the command a shell timeout longer than `{tree_timeout}`. From `{project-root}`, run:
312
+
313
+ ```bash
314
+ uv run {sourceTreeHelper} open \
315
+ --source-repo "{source_repo}" \
316
+ --source-root "{source_root}" \
317
+ --source-ref "{source_ref}" \
318
+ --pinned-commit "{source_commit}" \
319
+ --timeout "{tree_timeout}" \
320
+ [--target-ref "{target_ref_override}"]
321
+ ```
322
+
323
+ Pass `--target-ref` only when `{target_ref_override}` is set.
324
+
325
+ Bind `{source_tree_status}` ← `status`, `{source_tree_reason}` ← `reason`, `{source_tree_message}` ← `message`, `{source_tree}` ← `tree`, `{workspace_clone}` ← `clone`, `{target_ref}` ← `target_ref`, `{target_commit}` ← `target_commit`, `{source_moved}` ← `moved`, `{source_diff_status}` ← `diff_status`, `{source_changed_files}` ← `changed_files`, `{source_changed_counts}` ← `changed_counts` and `{source_tree_warnings}` ← `warnings`. Add each `{source_tree_warnings}` entry to `warnings[]` as `source-tree: <entry>`. `{source_tree}` is the tree itself; `{source_changed_files}` and `run.json` sit in its run folder, one level up.
326
+
327
+ What the helper does:
328
+
329
+ - **Which sources it takes.** Only the clone SKF keeps for a remote repository: `{source_repo}` names that repository and `{source_root}` is the clone path SKF computes for it (compared as resolved paths, so a trailing slash or `~` does not matter), or a clone of the same repository at `repos/<host>/<owner>/<repo>` in any SKF workspace folder, whatever the letter case of those folder names. Any other source — a local folder, or the remote URL a Quick-tier skill records — is read as it stands (`skipped`).
330
+ - **The commit.** It asks the remote which commit the ref points to (a tag before a branch of the same name; an annotated tag resolves to its commit), fetches that one commit into a folder of this run's own and checks it out there. It takes the objects from the shared clone when the clone already has them, and never writes to the clone. When the remote cannot be reached or the commit cannot be fetched, and `--target-ref` was not given, it checks out `{source_commit}` from the shared clone instead (`offline`).
331
+ - **The file list.** It writes git's list of added, modified and deleted files between `{source_commit}` and the tree's commit to `{source_changed_files}`; `{source_diff_status}` is `unavailable` when `{source_commit}` cannot be read.
332
+ - **Housekeeping.** It first removes trees earlier runs left behind for seven days or more.
333
+
334
+ Dispatch on `{source_tree_status}`:
335
+
336
+ - **`ready`** (exit 0): bind `{source_root}` ← `{source_tree}`; every later step reads source there. No step writes `{source_tree}` into an artifact.
337
+ - **`offline`** (exit 0): bind `{source_root}` ← `{source_tree}` — a tree at the skill's own `{source_commit}` — display `{source_tree_message}` as a warning, and add `source-not-fetched: {source_tree_message}` to `warnings[]`. This run compares the skill with the commit it was built from and cannot see upstream changes.
338
+ - **`skipped`** (exit 0): keep `{source_root}` from metadata.json and run §6's path check. If `{target_ref_override}` is set, HALT as below with `{source_tree_reason}` = `target-ref-needs-remote-source` and `{source_tree_message}` = "`--target-ref` applies only to a skill forged from a remote repository".
339
+ - **`unavailable`** (exit 3): no commit to read could be obtained (`{source_tree_reason}` is `invalid-ref`, `git-unavailable`, `upstream-unreachable`, `ref-not-found`, `fetch-failed`, `checkout-failed`, `tree-folder-failed` or `timed-out`). When it is `timed-out` and your shell tool allows a command longer than `{tree_timeout}` seconds, raise `{tree_timeout}` toward that limit, run the command once more and dispatch on that result. Otherwise HALT before any step reads source; display `{source_tree_message}`. The helper leaves no tree behind.
340
+ - **No candidate resolves, or the command exits 1 or 2, or prints no JSON:** HALT with `{source_tree_reason}` = `helper-failed` and `{source_tree_message}` = "skf-source-tree.py did not finish: {the first stderr line; 'the shell stopped it before it printed a result' when your shell tool's timeout ended it; or 'it is missing; re-install SKF'}". A tree a stopped run left behind is removed by a later run once it is seven days old. Reading the shared clone as it stands is what this section exists to prevent.
341
+
342
+ **Every §6b HALT** runs the halt procedure with `status: "blocked"`, `phase: "init:source-tree"`, `path: "{source_repo}"`, `reason: "{source_tree_reason}: {source_tree_message}"`, and `version` and `previous_version` the metadata.json `version`. No `headless_decisions[]` entry: this is a hard halt, not an auto-resolved gate.
343
+
344
+ ### 6c. Detect the Source Version
345
+
346
+ Run only when `{source_tree_status}` is `ready` or `offline`. Read the source's version from `{source_root}` with the Version Reconciliation rules of `skf-create-skill/references/source-resolution-protocols.md`: the version file for the detected language (`pyproject.toml`, `setup.py`, `__version__`, `package.json`, `Cargo.toml`, `go.mod`) and its monorepo `package.json` priority, matching package names against this skill's `name`. When no version file gives one, leave `{source_version_detected}` unset and skip the rest of this section. Otherwise compare it with the metadata.json `version` through `{skillInventoryHelper}` (resolve it ← first existing path in `{skillInventoryProbeOrder}`), from `{project-root}`:
347
+
348
+ ```bash
349
+ uv run {skillInventoryHelper} version order "{the source's version}" "{version}"
350
+ ```
351
+
352
+ It prints `order` and `major_minor` (the source's version against `{version}`) and `a.normalized` (the source's version as a version folder name). Never compare the two by hand.
353
+
354
+ - **`order` is `higher`** (a higher semantic version than the metadata.json `version`): bind `{source_version_detected}` ← `a.normalized`. Step 4 §6b and step 5 §2 name the version this update writes after it.
355
+ - **Otherwise** leave `{source_version_detected}` unset. When `major_minor` is `lower`, add `source-version-lower: {target_ref} reads {a.normalized}, older than {version}; this update keeps the patch-version rule` to `warnings[]`. A source version lower only in its patch number is expected, not a regression: every update that finds no higher version increments the skill's patch number (step 4 §6b), so a skill forged at the source's 1.2.0 is 1.2.1 after one update while the source still reads 1.2.0.
356
+ - **Exit 1** (`code` `NOT_A_VERSION`: a value that names no version, such as a dynamic version in `pyproject.toml`), **no candidate, or no JSON:** leave `{source_version_detected}` unset: step 4 §6b takes the next patch version.
208
357
 
209
358
  ### 7. Present Baseline Summary
210
359
 
@@ -216,7 +365,8 @@ Provide the current source code path:
216
365
  | **Type** | single |
217
366
  | **Version** | {version} |
218
367
  | **Created** | {created date} |
219
- | **Source** | {source_root} |
368
+ | **Source** | {source_display} |
369
+ | **Source commit** | {source_commit_line} |
220
370
  | **Forge Tier** | {forge_tier} (current) vs {original_tier} (at creation) |
221
371
  | **Provenance Age** | {days} days since last extraction |
222
372
  | **Exports** | {export_count} tracked exports |
@@ -225,14 +375,30 @@ Provide the current source code path:
225
375
 
226
376
  **Analysis plan:** {tier_description}
227
377
  - {Quick: text pattern diff → T1-low findings}
228
- - {Forge: AST structural diff → T1 findings}
229
- - {Deep: AST structural + QMD semantic diff → T1 + T2 findings}
378
+ - {Forge: AST structural diff → T1 findings where an ast-grep rule matches, T1-low where an export is read by eye}
379
+ - {Deep: AST structural + QMD semantic diff → the same labels as Forge, plus T2 findings}
230
380
 
231
381
  **Ready to detect changes and update this skill?**"
232
382
 
383
+ `{source_display}` is `{source_repo}` at `{target_ref}` when `{source_tree_status}` is `ready` or `offline` — plus ` (re-pinned from {source_ref})` when `{target_ref_override}` is set — and `{source_root}` otherwise. `{source_commit_line}` shows commits as their first 8 characters:
384
+
385
+ - `ready` and `{source_moved}` true: `{source_commit} → {target_commit}` plus ` ({added} added, {modified} modified, {deleted} deleted files)` from `{source_changed_counts}`, or ` (file list unavailable)` when `{source_diff_status}` is `unavailable`;
386
+ - `ready` and `{source_moved}` false: `{target_commit} (no newer commit)`;
387
+ - `ready` and `{source_moved}` null: `{target_commit} (no pinned commit before this run)`;
388
+ - `offline`: `{source_commit} (upstream not reached — comparing the pinned commit only)`;
389
+ - any other source: `{source_commit} (read as it stands)`.
390
+
391
+ Steps 5 and 6 reuse `{source_display}` and `{source_commit_line}`.
392
+
233
393
  ### 8. Confirmation Gate
234
394
 
235
- Present "**Select:** [C] Continue to Change Detection" and wait for the user to confirm; on [C], load, read the full file, then execute {nextStepFile}.
395
+ Present "**Select:** [C] Continue to Change Detection" ("**Select:** [C] Continue to Gap-Driven Repair" when `update_mode` is `gap-driven`) and wait for the user to confirm; on [C], load, read the full file, then execute `{gapDrivenStepFile}` when `update_mode` is `gap-driven`, else `{nextStepFile}`.
396
+
397
+ **GATE [default: C]**: headless (`{headless_mode}` true), auto-continue and record the decision in the run's decision log, from `{project-root}` (the emitter checks it against `shared/scripts/schemas/skf-update-result-envelope.v1.json`, and step 6's line carries it):
236
398
 
237
- **Headless (`{headless_mode}` true):** auto-continue and append to in-context `headless_decisions[]` (step 7 surfaces it in `SKF_UPDATE_RESULT_JSON`): `{gate: "init.update-confirmation", default_action: "C", taken_action: "C", reason: "headless: no user to prompt"}`. Entry shape: `src/shared/scripts/schemas/skf-update-result-envelope.v1.json`.
399
+ ```bash
400
+ uv run {emitEnvelopeHelper} record --workflow skf-update-skill --run-dir "{run_dir}" --decision <<'SKF_JSON'
401
+ {"gate": "init.update-confirmation", "default_action": "C", "taken_action": "C", "reason": "headless: no user to prompt"}
402
+ SKF_JSON
403
+ ```
238
404
 
@@ -0,0 +1,12 @@
1
+ # Invocation Contract: skf-update-skill
2
+
3
+ The argument set, gate map, outputs and headless result line of `skf-update-skill`. Interactive runs do not need this file; headless callers and pipeline integrators read it.
4
+
5
+ | Aspect | Detail |
6
+ |--------|--------|
7
+ | **Inputs** | skill_name [required]: the skill's name or folder path, as the argument. With none, an interactive run offers the skills SKF generated, and a headless run halts `blocked` (`error.phase` `init:skill-name`, `error.reason` starting `input-missing`) |
8
+ | **Flags** | `--headless` / `-H`; `--from-test-report` (gap-driven mode); `--target-ref <ref>`; `--allow-workspace-drift` (gap-driven only: update-skill takes nothing from HEAD, and a gap that needs the pinned tree, a rescope included, halts `halted-for-workspace-drift` before merge); `--allow-degraded`; `--detect-only` (status `detect-only`); `--dry-run` (status `dry-run`, writes nothing in any mode; a gap-driven.md halt such as `halted-for-remediation-path` or `halted-for-workspace-drift` still stops it). init.md §1 describes each; `--detect-only` wins over `--dry-run`. |
9
+ | **Gates** | step 1: init.md §4's degraded-rebuild gate [D]/[X] when no provenance map is found (headless: halts, or takes [D] under `--allow-degraded`; a gap-driven run halts `blocked` there instead); init.md §4b's [G]/[S] test-report offer, interactive only (headless warns `unconsumed-test-report`); Confirm Gate [C] (init.md §8) | step 2, each only when triggered: detect-changes.md §1b's promote prompt and §1c's scope-expansion prompt [P]/[S]/[U] (headless: defers each candidate to a person), §2.2's deletion-ratio gate [C]/[B]/[A] (headless: [C]) | step 2g (gap-driven): gap-driven.md §1 rule R1's [D]/[R] for each gap whose remediation names removal (headless: [D], or [R] only when the export is internal or `#[doc(hidden)]`) | step 4: Confirm Gate [C] only after the user resolved [MANUAL] conflicts (merge.md §4); a clean merge auto-proceeds, and a headless run with conflicts halts `halted-for-manual-mismatch` before anything is written |
10
+ | **Outputs** | A new version folder `{skill_group}/{new_version}/` and its forge folder, beside the unchanged previous version (a gap-driven repair writes the current version in place), and a finished run's `update-skill-result-{YYYYMMDD-HHmmss}.json` and `update-skill-result-latest.json` in that version's forge folder (a run that found no change: the current one's). `--detect-only` and `--dry-run` write none of these, nor the skill brief. Run state goes to a run folder under `{project-root}/_bmad-output/.skf-run/` that step 7 removes; a halt keeps it |
11
+ | **Concurrency** | A second real update of the same skill halts `halted-for-concurrent-run` while the first holds its run lock (init.md §1b); `--detect-only` and `--dry-run` take no lock. A remote skill is read from a private tree, and no mode writes to the shared workspace clone before write.md §9. |
12
+ | **Headless** | No gate prompts: each resolves as the Gates row says, and the run records the decision. One `SKF_UPDATE_RESULT_JSON` line (report.md §5b), at step 6 or at a HALT, printed by the shared emitter and checked against `shared/scripts/schemas/skf-update-result-envelope.v1.json`. It is the run's last line: the shared health check displays it after everything else; a HALT displays its line and stops. Pipelines branch on `skf_update.status`: `success`, `no-changes`, `detect-only` and `dry-run` are successful exits. |
@@ -22,26 +22,9 @@ It must be preserved during any update operation.
22
22
 
23
23
  ### Rules
24
24
 
25
- 1. **Never modify content between [MANUAL] markers** — treat as immutable
26
- 2. **Preserve marker positions** — if the surrounding generated content moves, the [MANUAL] block moves with its logical parent section
27
- 3. **Orphan detection** — if the parent section is deleted (export removed), flag as WARNING and present to user
28
- 4. **Multiple [MANUAL] blocks** — a single SKILL.md may have multiple [MANUAL] sections; preserve all
29
- 5. **Nested [MANUAL] forbidden** — [MANUAL] blocks cannot be nested; if detected, flag as ERROR
30
-
31
- ### Conflict Types
32
-
33
- | Conflict | Severity | Resolution |
34
- |------------------------------------------------|----------|-------------------------------------------------------|
35
- | Regenerated content overlaps [MANUAL] position | HIGH | Present both versions, user chooses |
36
- | Parent section deleted | WARNING | Flag orphaned [MANUAL], user decides keep/remove |
37
- | [MANUAL] references deleted export | MEDIUM | Flag stale reference, suggest update |
38
- | New export inserted adjacent to [MANUAL] | LOW | Auto-resolve: place new content before [MANUAL] block |
39
-
40
- ### Preservation Algorithm
41
-
42
- 1. Extract all [MANUAL] blocks with their section-name identifiers
43
- 2. Map each block to its parent section (by heading hierarchy)
44
- 3. Perform merge on generated content only
45
- 4. Re-insert [MANUAL] blocks at their mapped positions
46
- 5. If position conflict: halt and present to user
47
- 6. If clean insert: auto-place and continue
25
+ 1. **Never delete or modify content between [MANUAL] markers without the user's merge §4 [R]emove or [E]dit decision**: treat it as immutable otherwise. The decision is recorded in the run's manual plan, and the post-merge check verifies the blocks against the inventory that plan amends
26
+ 2. **Preserve marker positions**: if the surrounding generated content moves, the [MANUAL] block moves with its logical parent section
27
+ 3. **Multiple [MANUAL] blocks**: a single SKILL.md may have multiple [MANUAL] sections; preserve all
28
+ 4. **Nested [MANUAL] forbidden**: [MANUAL] blocks cannot be nested; if detected, flag as ERROR
29
+
30
+ merge.md §3 and §4 name the conflicts (ORPHAN, STALE_REFERENCE, POSITION) and how each is resolved.