bmad-module-skill-forge 1.9.0 → 2.0.1

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 (281) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.nvmrc +1 -1
  3. package/README.md +10 -5
  4. package/docs/_data/pinned.yaml +2 -2
  5. package/docs/agents.md +11 -2
  6. package/docs/architecture.md +5 -4
  7. package/docs/bmad-synergy.md +30 -7
  8. package/docs/campaign.md +172 -0
  9. package/docs/examples.md +7 -3
  10. package/docs/forge-auto.md +90 -0
  11. package/docs/getting-started.md +9 -0
  12. package/docs/how-it-works.md +6 -4
  13. package/docs/index.md +4 -3
  14. package/docs/skill-model.md +1 -1
  15. package/docs/troubleshooting.md +17 -1
  16. package/docs/verifying-a-skill.md +2 -2
  17. package/docs/why-skf.md +1 -1
  18. package/docs/workflows.md +64 -24
  19. package/package.json +2 -2
  20. package/src/knowledge/skill-lifecycle.md +1 -1
  21. package/src/module-help.csv +4 -3
  22. package/src/shared/data/language-corpora.json +32 -0
  23. package/src/shared/references/pipeline-contracts.md +6 -3
  24. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  25. package/src/shared/scripts/skf-comention-pairs.py +350 -0
  26. package/src/shared/scripts/skf-count-tokens.py +222 -0
  27. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  28. package/src/shared/scripts/skf-detect-docs.py +240 -4
  29. package/src/shared/scripts/skf-detect-language.py +82 -3
  30. package/src/shared/scripts/skf-detect-tools.py +105 -2
  31. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  32. package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
  33. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  34. package/src/shared/scripts/skf-find-cycles.py +246 -0
  35. package/src/shared/scripts/skf-hash-content.py +487 -2
  36. package/src/shared/scripts/skf-language-corpora.py +100 -0
  37. package/src/shared/scripts/skf-manifest-ops.py +67 -1
  38. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  39. package/src/shared/scripts/skf-preapply.py +1 -1
  40. package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
  41. package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
  42. package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
  43. package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
  44. package/src/shared/scripts/skf-shape-detect.py +576 -25
  45. package/src/shared/scripts/skf-shard-body.py +461 -0
  46. package/src/shared/scripts/skf-skill-inventory.py +139 -3
  47. package/src/shared/scripts/skf-structural-diff.py +307 -67
  48. package/src/shared/scripts/skf-validate-brief-schema.py +41 -6
  49. package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
  50. package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
  51. package/src/shared/scripts/skf-validate-output.py +443 -10
  52. package/src/shared/scripts/skf-validate-pins.py +3 -3
  53. package/src/shared/scripts/skf-verify-no-trace.py +214 -0
  54. package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
  55. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  56. package/src/skf-analyze-source/SKILL.md +17 -32
  57. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  58. package/src/skf-analyze-source/customize.toml +2 -0
  59. package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
  60. package/src/skf-analyze-source/references/continue.md +8 -13
  61. package/src/skf-analyze-source/references/generate-briefs.md +28 -12
  62. package/src/skf-analyze-source/references/headless-contract.md +33 -0
  63. package/src/skf-analyze-source/references/health-check.md +1 -2
  64. package/src/skf-analyze-source/references/identify-units.md +25 -36
  65. package/src/skf-analyze-source/references/init.md +12 -25
  66. package/src/skf-analyze-source/references/map-and-detect.md +8 -20
  67. package/src/skf-analyze-source/references/recommend.md +4 -14
  68. package/src/skf-analyze-source/references/scan-project.md +3 -11
  69. package/src/skf-analyze-source/references/step-auto-scope.md +211 -197
  70. package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
  71. package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
  72. package/src/skf-audit-skill/SKILL.md +9 -6
  73. package/src/skf-audit-skill/customize.toml +2 -0
  74. package/src/skf-audit-skill/references/init.md +58 -32
  75. package/src/skf-audit-skill/references/re-index.md +15 -34
  76. package/src/skf-audit-skill/references/report.md +11 -58
  77. package/src/skf-audit-skill/references/semantic-diff.md +4 -22
  78. package/src/skf-audit-skill/references/severity-classify.md +53 -87
  79. package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
  80. package/src/skf-audit-skill/references/structural-diff.md +43 -68
  81. package/src/skf-brief-skill/SKILL.md +7 -36
  82. package/src/skf-brief-skill/assets/scope-templates.md +1 -1
  83. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  84. package/src/skf-brief-skill/customize.toml +14 -0
  85. package/src/skf-brief-skill/references/analyze-target.md +13 -18
  86. package/src/skf-brief-skill/references/confirm-brief.md +2 -15
  87. package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
  88. package/src/skf-brief-skill/references/gather-intent.md +27 -24
  89. package/src/skf-brief-skill/references/headless-args.md +1 -1
  90. package/src/skf-brief-skill/references/health-check.md +1 -22
  91. package/src/skf-brief-skill/references/invocation-contract.md +37 -0
  92. package/src/skf-brief-skill/references/scope-definition.md +6 -15
  93. package/src/skf-brief-skill/references/step-auto-brief.md +22 -9
  94. package/src/skf-brief-skill/references/step-auto-validate.md +3 -7
  95. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  96. package/src/skf-brief-skill/references/write-brief.md +19 -13
  97. package/src/skf-campaign/SKILL.md +121 -0
  98. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  99. package/src/skf-campaign/customize.toml +75 -0
  100. package/src/skf-campaign/manifest.yaml +15 -0
  101. package/src/skf-campaign/references/campaign-contracts.md +67 -0
  102. package/src/skf-campaign/references/campaign-directive-spec.md +32 -0
  103. package/src/skf-campaign/references/health-check.md +35 -0
  104. package/src/skf-campaign/references/step-01-setup.md +121 -0
  105. package/src/skf-campaign/references/step-02-strategy.md +102 -0
  106. package/src/skf-campaign/references/step-03-pins.md +56 -0
  107. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  108. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  109. package/src/skf-campaign/references/step-06-batch.md +88 -0
  110. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  111. package/src/skf-campaign/references/step-08-verify.md +75 -0
  112. package/src/skf-campaign/references/step-09-refine.md +83 -0
  113. package/src/skf-campaign/references/step-10-export.md +106 -0
  114. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  115. package/src/skf-campaign/references/step-resume.md +115 -0
  116. package/src/skf-campaign/scripts/.gitkeep +0 -0
  117. package/src/skf-campaign/scripts/campaign-deps.py +244 -0
  118. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  119. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  120. package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
  121. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  122. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  123. package/src/skf-campaign/scripts/campaign-status.py +162 -0
  124. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  125. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  126. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  127. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  128. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  129. package/src/skf-create-skill/SKILL.md +15 -4
  130. package/src/skf-create-skill/assets/compile-assembly-rules.md +35 -34
  131. package/src/skf-create-skill/assets/skill-sections.md +9 -100
  132. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
  133. package/src/skf-create-skill/customize.toml +13 -0
  134. package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
  135. package/src/skf-create-skill/references/compile.md +37 -47
  136. package/src/skf-create-skill/references/component-extraction.md +8 -35
  137. package/src/skf-create-skill/references/ecosystem-check.md +9 -25
  138. package/src/skf-create-skill/references/enrich.md +4 -17
  139. package/src/skf-create-skill/references/extract.md +20 -27
  140. package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
  141. package/src/skf-create-skill/references/extraction-patterns.md +10 -8
  142. package/src/skf-create-skill/references/generate-artifacts.md +20 -25
  143. package/src/skf-create-skill/references/health-check.md +2 -2
  144. package/src/skf-create-skill/references/load-brief.md +16 -18
  145. package/src/skf-create-skill/references/report.md +27 -14
  146. package/src/skf-create-skill/references/source-resolution-protocols.md +4 -4
  147. package/src/skf-create-skill/references/step-auto-shard.md +52 -25
  148. package/src/skf-create-skill/references/step-doc-rot.md +33 -15
  149. package/src/skf-create-skill/references/step-doc-sources.md +13 -4
  150. package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
  151. package/src/skf-create-skill/references/sub/fetch-docs.md +33 -46
  152. package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
  153. package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
  154. package/src/skf-create-skill/references/validate.md +69 -29
  155. package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
  156. package/src/skf-create-stack-skill/SKILL.md +15 -9
  157. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
  158. package/src/skf-create-stack-skill/customize.toml +10 -0
  159. package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
  160. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
  161. package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
  162. package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
  163. package/src/skf-create-stack-skill/references/generate-output.md +24 -75
  164. package/src/skf-create-stack-skill/references/init.md +9 -81
  165. package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
  166. package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
  167. package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
  168. package/src/skf-create-stack-skill/references/report.md +27 -62
  169. package/src/skf-create-stack-skill/references/validate.md +35 -31
  170. package/src/skf-drop-skill/SKILL.md +20 -23
  171. package/src/skf-drop-skill/customize.toml +10 -0
  172. package/src/skf-drop-skill/references/execute.md +67 -36
  173. package/src/skf-drop-skill/references/headless-contract.md +18 -0
  174. package/src/skf-drop-skill/references/health-check.md +1 -13
  175. package/src/skf-drop-skill/references/report.md +44 -10
  176. package/src/skf-drop-skill/references/select.md +93 -48
  177. package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
  178. package/src/skf-export-skill/SKILL.md +17 -14
  179. package/src/skf-export-skill/assets/managed-section-format.md +2 -5
  180. package/src/skf-export-skill/customize.toml +10 -6
  181. package/src/skf-export-skill/references/generate-snippet.md +21 -35
  182. package/src/skf-export-skill/references/load-skill.md +44 -42
  183. package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
  184. package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
  185. package/src/skf-export-skill/references/package.md +27 -33
  186. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
  187. package/src/skf-export-skill/references/result-envelope.md +28 -0
  188. package/src/skf-export-skill/references/summary.md +11 -5
  189. package/src/skf-export-skill/references/token-report.md +42 -24
  190. package/src/skf-export-skill/references/update-context.md +32 -33
  191. package/src/skf-forger/SKILL.md +32 -39
  192. package/src/skf-forger/references/pipeline-mode.md +34 -0
  193. package/src/skf-forger/scripts/parse-pipeline.py +270 -0
  194. package/src/skf-quick-skill/SKILL.md +10 -60
  195. package/src/skf-quick-skill/customize.toml +9 -0
  196. package/src/skf-quick-skill/references/batch-mode.md +6 -4
  197. package/src/skf-quick-skill/references/compile.md +15 -52
  198. package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
  199. package/src/skf-quick-skill/references/finalize.md +12 -6
  200. package/src/skf-quick-skill/references/halt-contract.md +54 -0
  201. package/src/skf-quick-skill/references/health-check.md +3 -4
  202. package/src/skf-quick-skill/references/quick-extract.md +7 -18
  203. package/src/skf-quick-skill/references/registry-resolution.md +1 -23
  204. package/src/skf-quick-skill/references/resolve-target.md +27 -34
  205. package/src/skf-quick-skill/references/write-and-validate.md +10 -25
  206. package/src/skf-refine-architecture/SKILL.md +16 -8
  207. package/src/skf-refine-architecture/customize.toml +9 -0
  208. package/src/skf-refine-architecture/references/compile.md +10 -8
  209. package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
  210. package/src/skf-refine-architecture/references/health-check.md +1 -1
  211. package/src/skf-refine-architecture/references/improvements.md +9 -25
  212. package/src/skf-refine-architecture/references/init.md +9 -16
  213. package/src/skf-refine-architecture/references/issue-detection.md +10 -25
  214. package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
  215. package/src/skf-refine-architecture/references/report.md +14 -11
  216. package/src/skf-rename-skill/SKILL.md +11 -21
  217. package/src/skf-rename-skill/customize.toml +10 -0
  218. package/src/skf-rename-skill/references/execute.md +112 -82
  219. package/src/skf-rename-skill/references/exit-codes.md +12 -0
  220. package/src/skf-rename-skill/references/report.md +16 -8
  221. package/src/skf-rename-skill/references/select.md +69 -42
  222. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
  223. package/src/skf-setup/SKILL.md +21 -13
  224. package/src/skf-setup/customize.toml +10 -0
  225. package/src/skf-setup/references/auto-index.md +11 -10
  226. package/src/skf-setup/references/ccc-index.md +14 -15
  227. package/src/skf-setup/references/detect-and-tier.md +10 -8
  228. package/src/skf-setup/references/report.md +26 -32
  229. package/src/skf-setup/references/tier-rules.md +2 -32
  230. package/src/skf-setup/references/write-config.md +9 -10
  231. package/src/skf-test-skill/SKILL.md +10 -8
  232. package/src/skf-test-skill/customize.toml +2 -0
  233. package/src/skf-test-skill/references/coherence-check.md +19 -31
  234. package/src/skf-test-skill/references/coverage-check.md +111 -67
  235. package/src/skf-test-skill/references/detect-mode.md +10 -41
  236. package/src/skf-test-skill/references/external-validators.md +11 -19
  237. package/src/skf-test-skill/references/init.md +39 -21
  238. package/src/skf-test-skill/references/migration-section-rules.md +4 -4
  239. package/src/skf-test-skill/references/report.md +60 -40
  240. package/src/skf-test-skill/references/score.md +32 -64
  241. package/src/skf-test-skill/references/scoring-rules.md +6 -62
  242. package/src/skf-test-skill/references/source-access-protocol.md +15 -5
  243. package/src/skf-test-skill/references/step-hard-gate.md +1 -3
  244. package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
  245. package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
  246. package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
  247. package/src/skf-test-skill/scripts/compute-score.py +135 -4
  248. package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
  249. package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
  250. package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
  251. package/src/skf-update-skill/SKILL.md +17 -5
  252. package/src/skf-update-skill/customize.toml +11 -6
  253. package/src/skf-update-skill/references/detect-changes.md +48 -36
  254. package/src/skf-update-skill/references/health-check.md +5 -11
  255. package/src/skf-update-skill/references/init.md +40 -32
  256. package/src/skf-update-skill/references/manual-section-rules.md +1 -2
  257. package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
  258. package/src/skf-update-skill/references/merge.md +13 -57
  259. package/src/skf-update-skill/references/re-extract.md +27 -25
  260. package/src/skf-update-skill/references/report.md +14 -7
  261. package/src/skf-update-skill/references/validate.md +32 -30
  262. package/src/skf-update-skill/references/write.md +86 -51
  263. package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
  264. package/src/skf-verify-stack/SKILL.md +11 -21
  265. package/src/skf-verify-stack/customize.toml +12 -3
  266. package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
  267. package/src/skf-verify-stack/references/coverage.md +21 -10
  268. package/src/skf-verify-stack/references/exit-codes.md +14 -0
  269. package/src/skf-verify-stack/references/health-check.md +1 -1
  270. package/src/skf-verify-stack/references/init.md +20 -25
  271. package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
  272. package/src/skf-verify-stack/references/integrations.md +44 -49
  273. package/src/skf-verify-stack/references/report.md +28 -34
  274. package/src/skf-verify-stack/references/requirements.md +3 -3
  275. package/src/skf-verify-stack/references/synthesize.md +36 -39
  276. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
  277. package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
  278. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
  279. package/tools/cli/lib/ui.js +3 -2
  280. package/docs/deepwiki.md +0 -89
  281. package/src/skf-rename-skill/references/rebuild-context.md +0 -110
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # ///
5
+ """SKF Validate Feasibility Report — deterministic structure/schema gate.
6
+
7
+ Validates a skf-verify-stack feasibility report (.md) against the two
8
+ producer↔consumer contract invariants the report.md §1 prompt previously
9
+ eyeballed by re-reading the file each run:
10
+
11
+ 1. The five required body sections are all present and appear in the
12
+ canonical order defined by the shared feasibility-report schema:
13
+ ## Executive Summary
14
+ ## Coverage Analysis
15
+ ## Integration Verdicts
16
+ ## Recommendations
17
+ ## Evidence Sources
18
+ 2. Frontmatter `schemaVersion` equals the literal producer version "1.0".
19
+
20
+ Both are pure structure/schema checks — identical input always yields
21
+ identical pass/fail — so this belongs in a deterministic script, not an LLM
22
+ re-read. The verdict is emitted as JSON on stdout:
23
+
24
+ {
25
+ "status": "ok" | "error",
26
+ "path": "<report path>",
27
+ "schemaVersionOk": bool,
28
+ "schemaVersionFound": "<value>" | null,
29
+ "headingsOk": bool,
30
+ "missingHeadings": [...],
31
+ "orderViolations": [...],
32
+ "violation": "schema-violation" | null
33
+ }
34
+
35
+ Frontmatter is parsed with the standard library only (no pyyaml dep) using the
36
+ same delimiter handling as skf-validate-output.py: a `---`-delimited block at
37
+ the top of the file, parsed as simple `key: value` scalars with surrounding
38
+ quotes stripped. This is sufficient for the single `schemaVersion` scalar and
39
+ keeps the script stdlib-only.
40
+
41
+ Exit codes (per script-standards):
42
+ 0 valid — all sections present + in order AND schemaVersion == "1.0"
43
+ 1 schema-violation — missing/mis-ordered section, or schemaVersion mismatch
44
+ (the JSON `violation` field + detail lists carry why)
45
+ 2 IO/parse error — report file could not be read
46
+
47
+ CLI:
48
+ python3 skf-validate-feasibility-report.py <report.md>
49
+ python3 skf-validate-feasibility-report.py <report.md> -o result.json
50
+ """
51
+
52
+ from __future__ import annotations
53
+
54
+ import argparse
55
+ import json
56
+ import re
57
+ import sys
58
+ from pathlib import Path
59
+
60
+ # Canonical body-section order per the shared feasibility-report schema
61
+ # (src/shared/references/feasibility-report-schema.md → "Body section headings").
62
+ # Case-sensitive, exact heading text.
63
+ CANONICAL_HEADINGS = [
64
+ "Executive Summary",
65
+ "Coverage Analysis",
66
+ "Integration Verdicts",
67
+ "Recommendations",
68
+ "Evidence Sources",
69
+ ]
70
+
71
+ # Producer schema version the consumer/producer both pin to.
72
+ EXPECTED_SCHEMA_VERSION = "1.0"
73
+
74
+ # A level-2 markdown heading: exactly two '#', then whitespace, then the text.
75
+ # `### Foo` does not match (the 3rd char is '#', not whitespace).
76
+ _H2_RE = re.compile(r"^##\s+(.+?)\s*$")
77
+
78
+
79
+ def split_frontmatter(content):
80
+ """Return (frontmatter_dict, body_text).
81
+
82
+ Stdlib-only, no YAML dependency — mirrors skf-validate-output.py's delimiter
83
+ handling. The opening delimiter must be a line that is exactly `---`; the
84
+ closing delimiter is the next line that is exactly `---`. The block between
85
+ them is parsed as simple `key: value` scalars with surrounding quotes
86
+ stripped. If no valid frontmatter block is present, returns ({}, content).
87
+ """
88
+ lines = content.split("\n")
89
+ if not lines or lines[0].rstrip() != "---":
90
+ return {}, content
91
+
92
+ end_idx = None
93
+ for i in range(1, len(lines)):
94
+ if lines[i].rstrip() == "---":
95
+ end_idx = i
96
+ break
97
+ if end_idx is None:
98
+ return {}, content
99
+
100
+ fm = {}
101
+ for line in lines[1:end_idx]:
102
+ if ":" in line:
103
+ key, _, val = line.partition(":")
104
+ fm[key.strip()] = val.strip().strip("'\"")
105
+
106
+ body = "\n".join(lines[end_idx + 1:])
107
+ return fm, body
108
+
109
+
110
+ def scan_headings(body):
111
+ """Scan the body for the canonical level-2 sections.
112
+
113
+ Returns (missing, order_violations):
114
+ - missing: canonical headings not found at all (in canonical order).
115
+ - order_violations: human-readable strings describing each inversion,
116
+ detected by walking the canonical order and flagging any present
117
+ heading whose first occurrence is earlier than the previous present
118
+ canonical heading's first occurrence.
119
+
120
+ Deterministic: same body → same lists.
121
+ """
122
+ lines = body.split("\n")
123
+ first_line = {} # heading text -> 0-based body line index of first occurrence
124
+ for idx, line in enumerate(lines):
125
+ m = _H2_RE.match(line)
126
+ if not m:
127
+ continue
128
+ text = m.group(1).strip()
129
+ if text in CANONICAL_HEADINGS and text not in first_line:
130
+ first_line[text] = idx
131
+
132
+ missing = [h for h in CANONICAL_HEADINGS if h not in first_line]
133
+
134
+ order_violations = []
135
+ prev_name = None
136
+ prev_idx = -1
137
+ for h in CANONICAL_HEADINGS:
138
+ if h not in first_line:
139
+ continue
140
+ cur_idx = first_line[h]
141
+ if prev_name is not None and cur_idx < prev_idx:
142
+ order_violations.append(
143
+ f"'## {h}' appears at body line {cur_idx + 1}, before "
144
+ f"'## {prev_name}' at body line {prev_idx + 1} "
145
+ f"(expected '{prev_name}' to precede '{h}')"
146
+ )
147
+ prev_name = h
148
+ prev_idx = cur_idx
149
+
150
+ return missing, order_violations
151
+
152
+
153
+ def validate_report(path):
154
+ """Validate one feasibility report. Returns (result_dict, exit_code)."""
155
+ p = Path(path)
156
+ try:
157
+ content = p.read_text(encoding="utf-8")
158
+ except (OSError, UnicodeDecodeError) as e:
159
+ return (
160
+ {
161
+ "status": "error",
162
+ "path": str(p),
163
+ "error": f"could not read report file: {e}",
164
+ "schemaVersionOk": False,
165
+ "schemaVersionFound": None,
166
+ "headingsOk": False,
167
+ "missingHeadings": [],
168
+ "orderViolations": [],
169
+ "violation": "io-error",
170
+ },
171
+ 2,
172
+ )
173
+
174
+ fm, body = split_frontmatter(content)
175
+ schema_version = fm.get("schemaVersion") # None if the key is absent
176
+ schema_version_ok = schema_version == EXPECTED_SCHEMA_VERSION
177
+
178
+ missing, order_violations = scan_headings(body)
179
+ headings_ok = not missing and not order_violations
180
+
181
+ ok = schema_version_ok and headings_ok
182
+ result = {
183
+ "status": "ok" if ok else "error",
184
+ "path": str(p),
185
+ "schemaVersionOk": schema_version_ok,
186
+ "schemaVersionFound": schema_version,
187
+ "headingsOk": headings_ok,
188
+ "missingHeadings": missing,
189
+ "orderViolations": order_violations,
190
+ "violation": None if ok else "schema-violation",
191
+ }
192
+ return result, (0 if ok else 1)
193
+
194
+
195
+ def main(argv=None):
196
+ parser = argparse.ArgumentParser(
197
+ description=(
198
+ "Validate a feasibility report's section order and schemaVersion. "
199
+ "Confirms the five required body sections (Executive Summary, "
200
+ "Coverage Analysis, Integration Verdicts, Recommendations, "
201
+ "Evidence Sources) are present and in canonical order, and that "
202
+ "frontmatter schemaVersion == \"1.0\". Exit 0 valid, 1 "
203
+ "schema-violation, 2 IO/parse error."
204
+ )
205
+ )
206
+ parser.add_argument("report", help="path to the feasibility report .md file")
207
+ parser.add_argument(
208
+ "-o",
209
+ "--output",
210
+ help="write the JSON verdict to this file instead of stdout",
211
+ )
212
+ parser.add_argument(
213
+ "--verbose",
214
+ action="store_true",
215
+ help="print the resolved exit code to stderr",
216
+ )
217
+ args = parser.parse_args(argv)
218
+
219
+ result, exit_code = validate_report(args.report)
220
+ text = json.dumps(result, indent=2)
221
+
222
+ if args.output:
223
+ Path(args.output).write_text(text + "\n", encoding="utf-8")
224
+ else:
225
+ print(text)
226
+
227
+ if args.verbose:
228
+ print(f"exit_code={exit_code} violation={result.get('violation')}", file=sys.stderr)
229
+
230
+ return exit_code
231
+
232
+
233
+ if __name__ == "__main__":
234
+ sys.exit(main())
@@ -32,11 +32,11 @@ Input:
32
32
  exceeds N lines. Opt-in — when omitted, body size is never checked and
33
33
  the verdict is unchanged. Callers pass the skill-check `body.max_lines`
34
34
  default (500) to pre-catch that hard reject before commit.
35
- Optional --max-body-tokens N: when set, emit a high-severity `body`
36
- issue if the estimated body token count exceeds N. Token count is
37
- estimated as ceil(character_count / 4). Callers pass the skill-check
38
- `body.max_tokens` default (5000) to pre-catch that soft/hard reject
39
- before commit.
35
+ Optional --max-body-tokens N: when set, emit a low-severity (advisory)
36
+ `body` issue if the estimated body token count exceeds N. Token count is
37
+ estimated as ceil(character_count / 4). skill-check treats
38
+ `body.max_tokens` as a non-blocking warning, so this is advisory — the
39
+ hard body pre-check is --max-body-lines.
40
40
 
41
41
  Output:
42
42
  JSON object:
@@ -254,8 +254,9 @@ def validate_frontmatter(
254
254
 
255
255
  When `max_body_lines` is set, additionally emit a high-severity `body`
256
256
  issue if the body exceeds that many lines. When `max_body_tokens` is
257
- set, emit a high-severity `body` issue if the estimated token count
258
- exceeds that limit. Both are opt-in the verdict is unchanged when
257
+ set, emit a low-severity (advisory) `body` issue if the estimated token
258
+ count exceeds that limit skill-check treats body.max_tokens as a
259
+ non-blocking warning. Both are opt-in — the verdict is unchanged when
259
260
  they are None. Returns a result dict with status, issues, frontmatter,
260
261
  body_lines, body_tokens, and summary.
261
262
  """
@@ -272,7 +273,11 @@ def validate_frontmatter(
272
273
  body_tokens = body_token_estimate(content)
273
274
  if max_body_tokens is not None and body_tokens is not None and body_tokens > max_body_tokens:
274
275
  issues.append({
275
- "severity": "high",
276
+ # skill-check treats body.max_tokens as a non-blocking warning, so
277
+ # this is advisory (low), not a hard reject; --max-body-lines is the
278
+ # hard body pre-check. The char/4 estimate also runs high versus
279
+ # skill-check's own whitespace-split count, so keep it soft.
280
+ "severity": "low",
276
281
  "field": "body",
277
282
  "message": f"body token estimate {body_tokens} exceeds max {max_body_tokens}",
278
283
  })
@@ -390,10 +395,10 @@ def main() -> int:
390
395
  type=int,
391
396
  default=None,
392
397
  help=(
393
- "when set, emit a high-severity 'body' issue if the estimated body "
394
- "token count exceeds N (opt-in; omitted = body tokens not checked). "
395
- "Token estimate uses ceil(char_count / 4). Pass the skill-check "
396
- "body.max_tokens default (5000) to pre-catch that reject."
398
+ "when set, emit a low-severity (advisory) 'body' issue if the "
399
+ "estimated body token count exceeds N (opt-in; omitted = body "
400
+ "tokens not checked). Token estimate uses ceil(char_count / 4). "
401
+ "skill-check treats body.max_tokens as a non-blocking warning."
397
402
  ),
398
403
  )
399
404
  parser.add_argument(