bmad-module-skill-forge 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +51 -49
  3. package/docs/404.md +10 -8
  4. package/docs/_data/pinned.yaml +22 -13
  5. package/docs/_internal/STABILITY.md +58 -35
  6. package/docs/agents.md +39 -53
  7. package/docs/architecture.md +65 -51
  8. package/docs/bmad-synergy.md +53 -51
  9. package/docs/campaign.md +127 -75
  10. package/docs/concepts.md +34 -28
  11. package/docs/examples.md +70 -68
  12. package/docs/forge-auto.md +34 -31
  13. package/docs/getting-started.md +118 -57
  14. package/docs/how-it-works.md +25 -21
  15. package/docs/index.md +15 -15
  16. package/docs/skill-model.md +70 -59
  17. package/docs/troubleshooting.md +135 -18
  18. package/docs/verifying-a-skill.md +83 -57
  19. package/docs/why-skf.md +24 -22
  20. package/docs/workflows.md +274 -110
  21. package/package.json +18 -10
  22. package/src/forger/preferences.yaml +8 -1
  23. package/src/knowledge/agentskills-spec.md +2 -0
  24. package/src/knowledge/ccc-bridge.md +40 -24
  25. package/src/knowledge/confidence-tiers.md +9 -5
  26. package/src/knowledge/overview.md +3 -3
  27. package/src/knowledge/progressive-capability.md +8 -8
  28. package/src/knowledge/provenance-tracking.md +9 -5
  29. package/src/knowledge/qmd-registry.md +2 -2
  30. package/src/knowledge/skf-knowledge-index.csv +2 -2
  31. package/src/knowledge/skill-lifecycle.md +18 -11
  32. package/src/knowledge/split-body-strategy.md +0 -3
  33. package/src/knowledge/tool-resolution.md +4 -2
  34. package/src/knowledge/version-paths.md +101 -15
  35. package/src/module-help.csv +3 -3
  36. package/src/module.yaml +10 -0
  37. package/src/shared/data/ast-grep-recipes.yaml +608 -0
  38. package/src/shared/data/ide-context-files.json +30 -0
  39. package/src/shared/health-check.md +32 -12
  40. package/src/shared/references/description-guard-protocol.md +14 -9
  41. package/src/shared/references/feasibility-report-schema.md +19 -13
  42. package/src/shared/references/headless-gate-convention.md +55 -3
  43. package/src/shared/references/output-contract-schema.md +19 -8
  44. package/src/shared/references/pipeline-contracts.md +94 -29
  45. package/src/shared/references/tessl-review.md +77 -0
  46. package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
  47. package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
  48. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
  49. package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
  50. package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
  51. package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
  52. package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
  53. package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
  54. package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
  55. package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
  56. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
  57. package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
  58. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
  59. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
  60. package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
  61. package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
  62. package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
  63. package/src/shared/scripts/skf-atomic-write.py +30 -14
  64. package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
  65. package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
  66. package/src/shared/scripts/skf-check-unit-records.py +184 -0
  67. package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
  68. package/src/shared/scripts/skf-classify-changed-files.py +694 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
  71. package/src/shared/scripts/skf-count-imports.py +1867 -0
  72. package/src/shared/scripts/skf-count-tokens.py +1 -1
  73. package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
  74. package/src/shared/scripts/skf-description-guard.py +117 -9
  75. package/src/shared/scripts/skf-detect-docs.py +892 -4
  76. package/src/shared/scripts/skf-detect-language.py +486 -107
  77. package/src/shared/scripts/skf-detect-registry.py +887 -0
  78. package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
  79. package/src/shared/scripts/skf-detect-tools.py +215 -27
  80. package/src/shared/scripts/skf-detect-workspaces.py +506 -30
  81. package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
  82. package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
  83. package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
  84. package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
  85. package/src/shared/scripts/skf-extract-public-api.py +4084 -85
  86. package/src/shared/scripts/skf-extraction-inventory.py +746 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
  88. package/src/shared/scripts/skf-fetch-temporal.py +674 -0
  89. package/src/shared/scripts/skf-find-cycles.py +97 -29
  90. package/src/shared/scripts/skf-find-test-report.py +476 -0
  91. package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +661 -0
  94. package/src/shared/scripts/skf-hash-content.py +166 -12
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +159 -27
  97. package/src/shared/scripts/skf-manifest-ops.py +1 -1
  98. package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
  99. package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
  100. package/src/shared/scripts/skf-names-present.py +221 -0
  101. package/src/shared/scripts/skf-pair-intersect.py +119 -28
  102. package/src/shared/scripts/skf-parse-gaps.py +1384 -0
  103. package/src/shared/scripts/skf-preapply.py +1 -1
  104. package/src/shared/scripts/skf-preflight.py +161 -27
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
  107. package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
  108. package/src/shared/scripts/skf-quick-batch.py +774 -0
  109. package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
  110. package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
  111. package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
  112. package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
  113. package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
  114. package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
  115. package/src/shared/scripts/skf-resolve-package.py +523 -106
  116. package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
  117. package/src/shared/scripts/skf-run-lock.py +577 -0
  118. package/src/shared/scripts/skf-scan-manifests.py +998 -57
  119. package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
  120. package/src/shared/scripts/skf-severity-classify.py +712 -108
  121. package/src/shared/scripts/skf-shape-detect.py +298 -204
  122. package/src/shared/scripts/skf-shard-body.py +9 -8
  123. package/src/shared/scripts/skf-skill-inventory.py +2412 -89
  124. package/src/shared/scripts/skf-skills-module.py +472 -0
  125. package/src/shared/scripts/skf-source-tree.py +1796 -0
  126. package/src/shared/scripts/skf-structural-diff.py +670 -119
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +5 -5
  129. package/src/shared/scripts/skf-update-run-state.py +624 -0
  130. package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
  131. package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
  132. package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
  133. package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
  134. package/src/shared/scripts/skf-validate-output.py +712 -64
  135. package/src/shared/scripts/skf-validate-pins.py +1 -1
  136. package/src/shared/scripts/skf-verify-no-trace.py +116 -10
  137. package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
  138. package/src/shared/scripts/skf-write-skill-brief.py +367 -19
  139. package/src/shared/tool-requirements.yaml +201 -0
  140. package/src/skf-analyze-source/SKILL.md +41 -36
  141. package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
  142. package/src/skf-analyze-source/customize.toml +36 -24
  143. package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
  144. package/src/skf-analyze-source/references/continue.md +2 -13
  145. package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
  146. package/src/skf-analyze-source/references/generate-briefs.md +121 -55
  147. package/src/skf-analyze-source/references/headless-contract.md +42 -15
  148. package/src/skf-analyze-source/references/identify-units.md +96 -110
  149. package/src/skf-analyze-source/references/init.md +73 -71
  150. package/src/skf-analyze-source/references/map-and-detect.md +123 -105
  151. package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
  152. package/src/skf-analyze-source/references/recommend.md +51 -70
  153. package/src/skf-analyze-source/references/scan-project.md +60 -84
  154. package/src/skf-analyze-source/references/scan-root.md +39 -0
  155. package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
  156. package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
  157. package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
  158. package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
  159. package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
  160. package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
  161. package/src/skf-audit-skill/SKILL.md +43 -51
  162. package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
  163. package/src/skf-audit-skill/customize.toml +41 -20
  164. package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
  165. package/src/skf-audit-skill/references/doc-drift.md +141 -0
  166. package/src/skf-audit-skill/references/headless-contract.md +29 -0
  167. package/src/skf-audit-skill/references/init.md +148 -210
  168. package/src/skf-audit-skill/references/re-index.md +96 -108
  169. package/src/skf-audit-skill/references/report.md +96 -33
  170. package/src/skf-audit-skill/references/semantic-diff.md +27 -18
  171. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  172. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  173. package/src/skf-audit-skill/references/structural-diff.md +75 -90
  174. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  175. package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
  176. package/src/skf-brief-skill/SKILL.md +36 -25
  177. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  178. package/src/skf-brief-skill/assets/scope-templates.md +17 -24
  179. package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
  180. package/src/skf-brief-skill/customize.toml +48 -20
  181. package/src/skf-brief-skill/references/analyze-target.md +142 -125
  182. package/src/skf-brief-skill/references/confirm-brief.md +10 -8
  183. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  184. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  185. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  186. package/src/skf-brief-skill/references/headless-args.md +84 -16
  187. package/src/skf-brief-skill/references/health-check.md +1 -1
  188. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  189. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  190. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  191. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  192. package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
  193. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  194. package/src/skf-brief-skill/references/version-resolution.md +3 -2
  195. package/src/skf-brief-skill/references/write-brief.md +66 -57
  196. package/src/skf-campaign/SKILL.md +31 -34
  197. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  198. package/src/skf-campaign/customize.toml +47 -16
  199. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  200. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  201. package/src/skf-campaign/references/health-check.md +7 -16
  202. package/src/skf-campaign/references/step-01-setup.md +46 -84
  203. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  204. package/src/skf-campaign/references/step-03-pins.md +26 -18
  205. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  206. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  207. package/src/skf-campaign/references/step-06-batch.md +41 -34
  208. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  209. package/src/skf-campaign/references/step-08-verify.md +28 -29
  210. package/src/skf-campaign/references/step-09-refine.md +29 -30
  211. package/src/skf-campaign/references/step-10-export.md +48 -36
  212. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  213. package/src/skf-campaign/references/step-resume.md +37 -68
  214. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  215. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  216. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  217. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  218. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  219. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  220. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  221. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  222. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  223. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  224. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  225. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  226. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  227. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  228. package/src/skf-create-skill/SKILL.md +27 -20
  229. package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
  230. package/src/skf-create-skill/assets/skill-sections.md +17 -6
  231. package/src/skf-create-skill/customize.toml +20 -8
  232. package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
  233. package/src/skf-create-skill/references/batch-mode.md +81 -0
  234. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  235. package/src/skf-create-skill/references/compile.md +107 -29
  236. package/src/skf-create-skill/references/component-extraction.md +113 -91
  237. package/src/skf-create-skill/references/enrich.md +41 -23
  238. package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
  239. package/src/skf-create-skill/references/extract.md +200 -161
  240. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  241. package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
  242. package/src/skf-create-skill/references/extraction-patterns.md +584 -196
  243. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  244. package/src/skf-create-skill/references/health-check.md +1 -1
  245. package/src/skf-create-skill/references/load-brief.md +53 -36
  246. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  247. package/src/skf-create-skill/references/report.md +28 -59
  248. package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
  249. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  250. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  251. package/src/skf-create-skill/references/step-doc-sources.md +55 -18
  252. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  253. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
  254. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  255. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  256. package/src/skf-create-skill/references/validate.md +173 -99
  257. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  258. package/src/skf-create-stack-skill/SKILL.md +29 -56
  259. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  260. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  261. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  262. package/src/skf-create-stack-skill/customize.toml +23 -12
  263. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  264. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  265. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  266. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  267. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  268. package/src/skf-create-stack-skill/references/init.md +90 -32
  269. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  270. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  271. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  272. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  273. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  274. package/src/skf-create-stack-skill/references/report.md +39 -33
  275. package/src/skf-create-stack-skill/references/validate.md +20 -39
  276. package/src/skf-drop-skill/SKILL.md +28 -51
  277. package/src/skf-drop-skill/customize.toml +44 -30
  278. package/src/skf-drop-skill/references/execute.md +79 -146
  279. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  280. package/src/skf-drop-skill/references/report.md +46 -30
  281. package/src/skf-drop-skill/references/select.md +118 -124
  282. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  283. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  284. package/src/skf-export-skill/SKILL.md +40 -62
  285. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  286. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  287. package/src/skf-export-skill/customize.toml +42 -13
  288. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  289. package/src/skf-export-skill/references/health-check.md +2 -0
  290. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  291. package/src/skf-export-skill/references/load-skill.md +88 -62
  292. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  293. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  294. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  295. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  296. package/src/skf-export-skill/references/package.md +15 -73
  297. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  298. package/src/skf-export-skill/references/result-envelope.md +31 -16
  299. package/src/skf-export-skill/references/summary.md +41 -17
  300. package/src/skf-export-skill/references/token-report.md +4 -12
  301. package/src/skf-export-skill/references/update-context.md +118 -237
  302. package/src/skf-forger/SKILL.md +61 -37
  303. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  304. package/src/skf-forger/customize.toml +43 -0
  305. package/src/skf-forger/references/pipeline-mode.md +73 -18
  306. package/src/skf-forger/scripts/forge-status.py +299 -0
  307. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  308. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  309. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  310. package/src/skf-quick-skill/SKILL.md +54 -53
  311. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  312. package/src/skf-quick-skill/customize.toml +51 -20
  313. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  314. package/src/skf-quick-skill/references/compile.md +30 -31
  315. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  316. package/src/skf-quick-skill/references/finalize.md +69 -18
  317. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  318. package/src/skf-quick-skill/references/health-check.md +5 -3
  319. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  320. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  321. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  322. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  323. package/src/skf-refine-architecture/SKILL.md +43 -53
  324. package/src/skf-refine-architecture/customize.toml +49 -20
  325. package/src/skf-refine-architecture/references/compile.md +97 -54
  326. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  327. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  328. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  329. package/src/skf-refine-architecture/references/improvements.md +14 -20
  330. package/src/skf-refine-architecture/references/init.md +98 -57
  331. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  332. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  333. package/src/skf-refine-architecture/references/report.md +65 -54
  334. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  335. package/src/skf-rename-skill/SKILL.md +19 -54
  336. package/src/skf-rename-skill/customize.toml +32 -26
  337. package/src/skf-rename-skill/references/execute.md +230 -184
  338. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  339. package/src/skf-rename-skill/references/health-check.md +1 -7
  340. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  341. package/src/skf-rename-skill/references/report.md +63 -52
  342. package/src/skf-rename-skill/references/select.md +197 -115
  343. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  344. package/src/skf-setup/SKILL.md +26 -26
  345. package/src/skf-setup/customize.toml +14 -10
  346. package/src/skf-setup/references/auto-index.md +50 -63
  347. package/src/skf-setup/references/ccc-index.md +29 -89
  348. package/src/skf-setup/references/detect-and-tier.md +58 -66
  349. package/src/skf-setup/references/health-check.md +2 -0
  350. package/src/skf-setup/references/invocation-contract.md +14 -0
  351. package/src/skf-setup/references/report.md +43 -166
  352. package/src/skf-setup/references/tier-rules.md +5 -5
  353. package/src/skf-setup/references/write-config.md +34 -57
  354. package/src/skf-test-skill/SKILL.md +17 -57
  355. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  356. package/src/skf-test-skill/customize.toml +27 -13
  357. package/src/skf-test-skill/references/coherence-check.md +134 -62
  358. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  359. package/src/skf-test-skill/references/coverage-check.md +217 -254
  360. package/src/skf-test-skill/references/detect-mode.md +5 -5
  361. package/src/skf-test-skill/references/external-validators.md +94 -82
  362. package/src/skf-test-skill/references/health-check.md +6 -7
  363. package/src/skf-test-skill/references/init.md +163 -88
  364. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  365. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  366. package/src/skf-test-skill/references/report.md +162 -142
  367. package/src/skf-test-skill/references/score.md +80 -107
  368. package/src/skf-test-skill/references/scoring-rules.md +41 -155
  369. package/src/skf-test-skill/references/source-access-protocol.md +19 -92
  370. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  371. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  372. package/src/skf-test-skill/scripts/build-result-context.py +287 -0
  373. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  374. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  375. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  376. package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
  377. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  378. package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
  379. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  380. package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
  381. package/src/skf-test-skill/scripts/score-signatures.py +459 -0
  382. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  383. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  384. package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
  385. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  386. package/src/skf-update-skill/SKILL.md +38 -25
  387. package/src/skf-update-skill/customize.toml +27 -12
  388. package/src/skf-update-skill/references/detect-changes.md +233 -222
  389. package/src/skf-update-skill/references/gap-driven.md +336 -0
  390. package/src/skf-update-skill/references/health-check.md +31 -5
  391. package/src/skf-update-skill/references/init.md +250 -84
  392. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  393. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  394. package/src/skf-update-skill/references/merge.md +180 -47
  395. package/src/skf-update-skill/references/re-extract.md +53 -262
  396. package/src/skf-update-skill/references/report.md +100 -35
  397. package/src/skf-update-skill/references/write.md +331 -149
  398. package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
  399. package/src/skf-verify-stack/SKILL.md +23 -42
  400. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  401. package/src/skf-verify-stack/customize.toml +55 -22
  402. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  403. package/src/skf-verify-stack/references/coverage.md +67 -64
  404. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  405. package/src/skf-verify-stack/references/init.md +107 -62
  406. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  407. package/src/skf-verify-stack/references/integrations.md +106 -65
  408. package/src/skf-verify-stack/references/report.md +59 -42
  409. package/src/skf-verify-stack/references/requirements.md +32 -9
  410. package/src/skf-verify-stack/references/synthesize.md +53 -39
  411. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  412. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  413. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  414. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  415. package/tools/cli/commands/install.js +11 -4
  416. package/tools/cli/commands/status.js +15 -3
  417. package/tools/cli/commands/update.js +11 -4
  418. package/tools/cli/lib/tool-check.js +320 -0
  419. package/tools/cli/lib/version-check.js +22 -9
  420. package/tools/cli/skf-cli.js +4 -1
  421. package/.gitattributes +0 -16
  422. package/.nvmrc +0 -1
  423. package/docs/_internal/RELEASING.md +0 -645
  424. package/release-audits/v1.0.0-launch-audit.md +0 -728
  425. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  426. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  427. package/src/skf-campaign/manifest.yaml +0 -15
  428. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  429. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  430. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  431. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  432. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  433. package/src/skf-update-skill/references/validate.md +0 -124
  434. package/tools/validate-docs-drift.js +0 -233
  435. package/tools/validate-docs-links.js +0 -665
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env python3
2
2
  # /// script
3
- # requires-python = ">=3.10"
3
+ # requires-python = ">=3.11"
4
4
  # ///
5
5
  """Deterministic parse + validation of a forger pipeline invocation.
6
6
 
7
7
  Pipeline Mode (references/pipeline-mode.md §1-2) turns a raw code sequence
8
- (`BS CS[cocoindex] TS[min:80] EX`, `AN -> CS -> TS -> EX`, or a bare alias like
9
- `forge-auto`) into a normalized run plan and a list of sequence anti-patterns.
8
+ (`BS CS[cocoindex] TS[min:80] EX`, `AN -> CS -> TS -> EX`, or an alias such as
9
+ `forge-quick cognee`) into a normalized run plan and a list of sequence
10
+ anti-patterns.
10
11
  Splitting the tokens, expanding aliases against a fixed table, classifying each
11
12
  bracket argument, and detecting anti-patterns (duplicates, ordering, missing
12
13
  prerequisites) is set/order plumbing with exactly one correct answer per input —
@@ -14,34 +15,75 @@ so it runs here, not in the prompt. The prompt keeps only the judgment the
14
15
  script cannot make: interpreting the user's freeform request into a code
15
16
  sequence, and deciding whether to proceed past a warned anti-pattern.
16
17
 
18
+ The forger passes the user's whole invocation, so an alias may carry its
19
+ arguments after it (`forge-auto <repo-or-doc-url>`, `forge <repo-url-or-path>
20
+ <skill-name>`, `forge-quick <package-or-url>`, `maintain <skill>`) and
21
+ `--pin <version>` may appear anywhere. The alias expands, and its arguments
22
+ bind, in order, to the inputs of the alias's first workflow (ALIAS_INPUTS);
23
+ `--pin` joins them, as the Pipeline Arguments rule says. An input left
24
+ unbound is reported, because that workflow requires it and a pipeline runs
25
+ headless. A chain of codes binds no input, so a runnable plan whose first
26
+ workflow (past a leading SF) starts from an input that no bracket gives
27
+ reports it as `first_input` (FIRST_INPUTS) and stays valid: the forger asks
28
+ for it, or halts headless, before any workflow runs. A quoted argument (a
29
+ path with a space) stays one token. Codes and the bracket keywords `min` and
30
+ `auto` match in any case; a bracket that starts like `min` but is not
31
+ `min:<number>` is reported as malformed instead of passing on as a target.
32
+
17
33
  The alias-expansion and anti-pattern tables mirror
18
34
  `src/shared/references/pipeline-contracts.md` (the human-readable contract);
19
35
  this script is the executable form the forger consumes.
20
36
 
21
37
  CLI usage:
22
38
  uv run scripts/parse-pipeline.py 'BS CS TS EX' # positional
23
- uv run scripts/parse-pipeline.py 'forge-auto' # bare alias
39
+ uv run scripts/parse-pipeline.py 'forge-quick cognee'
40
+ uv run scripts/parse-pipeline.py 'forge-quick' # exit 3: missing_args [target]
41
+ uv run scripts/parse-pipeline.py 'QS TS EX' # exit 0: first_input, QS's target
42
+ uv run scripts/parse-pipeline.py 'forge-auto https://github.com/o/r --pin 0.2.1'
24
43
  echo 'AN -> CS -> TS -> EX' | uv run scripts/parse-pipeline.py --stdin
25
44
 
26
45
  Output (stdout, one object):
27
46
  {
28
47
  "raw": "<input>",
29
- "alias": "forge" | null, # set when the input was a lone alias
48
+ "alias": "forge" | null, # set when the input starts with an alias
30
49
  "deprecated_alias": "deepwiki" | null,
31
50
  "removed_alias": "onboard" | null,
32
51
  "expanded": ["BS", "CS", "TS", "EX"], # normalized tokens (post-expansion)
33
52
  "plan": [{"code","min","mode","target"}, ...],
34
53
  "codes": ["BS", "CS", "TS", "EX"],
54
+ "args": {"<input>": "<value>", "pin": "<version>"}, # for the first workflow
35
55
  "unknown_codes": [],
56
+ "unexpected_args": [], # extra alias arguments, unknown flags
57
+ "missing_args": [], # alias inputs left unbound, e.g. "target"
58
+ "malformed_brackets": [], # e.g. "TS[min:80%]"
36
59
  "anti_patterns": [{"pattern","message","suggestion"}, ...],
37
- "valid": <bool> # runnable: real codes, no unknowns, not removed
38
- }
60
+ "first_input": null | {"code","input","form","halt_reason"},
61
+ "valid": <bool> # runnable: real codes, nothing unknown,
62
+ } # unexpected, missing or malformed, not
63
+ # removed
64
+
65
+ `args` holds only what the invocation gave: `project_path` (forge-auto),
66
+ `target_repo` and `skill_name` (forge), `target` (forge-quick), `skill_name`
67
+ (maintain), and `pin`. `--headless`/`-H` is Ferris's own flag, read at
68
+ activation, so it is accepted and not returned.
69
+
70
+ `first_input` names the input the chain's first workflow lacks (`input`, as
71
+ "a target"). `form` is the invocation to parse again with the answer in
72
+ place of each `<...>` placeholder (`QS[<target>] TS EX`), or null when no
73
+ chain can carry that input: RS and DS need more than one bracket, a BS
74
+ outside `forge <repo-url-or-path> <skill-name>` has no bracket for its skill
75
+ name, and a bracket that already holds `min:N` or `auto` holds one value.
76
+ `halt_reason` is what a headless run halts with (`QS needs a target before
77
+ the pipeline can start: give it as QS[<target>]`), or null for a leading CS:
78
+ create-skill compiles the only brief, or halts naming the briefs.
39
79
 
40
80
  Exit codes:
41
- 0 — parsed, runnable (anti-patterns and a deprecated alias are warnings)
42
- 1 — no input provided (usage error)
43
- 2 — removed alias (e.g. `onboard`) — the forger HALTs
44
- 3 — nothing runnable (empty, or unknown codes) — the forger corrects course
81
+ 0 parsed, runnable (anti-patterns and a deprecated alias are warnings)
82
+ 1 no input provided (usage error)
83
+ 2 removed alias (e.g. `onboard`): the forger HALTs
84
+ 3 nothing runnable (empty, or an unknown code, an unexpected or missing
85
+ argument or a malformed bracket): the forger names the problem and asks,
86
+ or halts headless, before any workflow runs
45
87
  """
46
88
 
47
89
  from __future__ import annotations
@@ -49,6 +91,7 @@ from __future__ import annotations
49
91
  import argparse
50
92
  import json
51
93
  import re
94
+ import shlex
52
95
  import sys
53
96
 
54
97
  # Workflow codes that can appear in a pipeline. KI/WS are inline actions, not
@@ -57,9 +100,11 @@ KNOWN_CODES = frozenset(
57
100
  {"SF", "AN", "BS", "CS", "QS", "SS", "US", "AS", "VS", "RA", "TS", "EX", "RS", "DS"}
58
101
  )
59
102
 
60
- # Only these accept a `min:N` circuit-breaker override; on other codes a
61
- # `min:N` bracket is ignored (recorded as `min: null`).
62
- CIRCUIT_BREAKER_CODES = frozenset({"AN", "CS", "TS", "AS", "VS"})
103
+ # Only these take a `min:N`: AN as the fewest units it must confirm (the
104
+ # pipeline gate's --min) and TS as its test threshold (--threshold). CS, AS and
105
+ # VS have circuit breakers with no number to set, so on them, as on any other
106
+ # code, a `min:N` bracket is ignored (recorded as `min: null`, with a warning).
107
+ THRESHOLD_CODES = frozenset({"AN", "TS"})
63
108
 
64
109
  # Alias table — mirrors pipeline-contracts.md. `forge-auto`'s TS[min:90] gate is
65
110
  # non-default and is kept in lockstep with init.md §1b's forge-auto → 90 lookup.
@@ -72,27 +117,146 @@ ALIASES = {
72
117
  DEPRECATED_ALIASES = {"deepwiki": "forge-auto"} # renamed; still expands
73
118
  REMOVED_ALIASES = {"onboard"} # no expansion — the forger HALTs
74
119
 
120
+ # The arguments each alias takes after it, in order. Each names the input of
121
+ # the alias's first workflow that receives it: analyze-source's project_path,
122
+ # brief-skill's target_repo and skill_name, quick-skill's target and
123
+ # audit-skill's skill_name. Mirrors the alias forms in SKILL.md's Pipelines
124
+ # paragraph.
125
+ ALIAS_INPUTS = {
126
+ "forge-auto": ("project_path",),
127
+ "forge": ("target_repo", "skill_name"),
128
+ "forge-quick": ("target",),
129
+ "maintain": ("skill_name",),
130
+ }
131
+
132
+ # What a code's workflow starts from at the head of a chain of codes, where no
133
+ # alias argument binds it: the `args` keys that give it, the input as the ask
134
+ # names it, and the placeholder its bracket takes. A pipeline runs every
135
+ # workflow headless, so one left without its input halts at once. SF and SS
136
+ # start from none, and the check looks past a leading SF. CS's input is its
137
+ # brief: headless, create-skill compiles the only brief or halts naming them,
138
+ # so a leading CS is asked for its input but never halted for it.
139
+ FIRST_INPUTS = {
140
+ "AN": (("project_path",), "a project path", "<project-path>"),
141
+ "QS": (("target",), "a target", "<target>"),
142
+ "CS": (("skill_name", "brief_path"), "a skill name", "<skill-name>"),
143
+ "TS": (("skill_name",), "a skill name", "<skill-name>"),
144
+ "EX": (("skill_name",), "a skill name", "<skill-name>"),
145
+ "US": (("skill_name",), "a skill name", "<skill-name>"),
146
+ "AS": (("skill_name",), "a skill name", "<skill-name>"),
147
+ "VS": (("architecture_doc_path",), "an architecture document", "<architecture-doc>"),
148
+ "RA": (("architecture_doc_path",), "an architecture document", "<architecture-doc>"),
149
+ }
150
+ # BS needs a target and a skill name, and a bracket holds one value: only the
151
+ # `forge` alias gives it both.
152
+ BS_INPUTS = ("target_repo", "skill_name")
153
+ FORGE_FORM = "forge <repo-url-or-path> <skill-name>"
154
+ # These need more than a bracket can hold (rename-skill a new name, drop-skill
155
+ # a version and a drop mode), so they run on their own, outside a chain.
156
+ ON_THEIR_OWN = {
157
+ "RS": "a skill name and its new name",
158
+ "DS": "a skill name, a version and a drop mode",
159
+ }
160
+
161
+ # Pipeline-level flags (pipeline-contracts.md Pipeline Arguments): each takes a
162
+ # value and reaches the first workflow as the named input.
163
+ VALUE_FLAGS = {"--pin": "pin"}
164
+ # Ferris's own flag: On Activation already read it from the invocation.
165
+ IGNORED_FLAGS = frozenset({"--headless", "-H"})
166
+
75
167
  _TOKEN_RE = re.compile(r"^([A-Za-z]+)(?:\[([^\]]*)\])?$")
76
168
  _ARROWS = re.compile(r"->|→|—>|»")
169
+ _MIN_RE = re.compile(r"min:(\d+)", re.IGNORECASE)
170
+ # `min`, `min:`, `min=80`, `MIN:80%`: a threshold written wrong. A target such
171
+ # as `minimatch` does not match, because no `:` or `=` follows `min`.
172
+ _MIN_LIKE_RE = re.compile(r"min\s*(?:[:=]|$)", re.IGNORECASE)
173
+
174
+
175
+ def _unquote(token):
176
+ """Drop one pair of matching quotes around a token."""
177
+ if len(token) >= 2 and token[0] == token[-1] and token[0] in "'\"":
178
+ return token[1:-1]
179
+ return token
77
180
 
78
181
 
79
182
  def _tokenize(raw):
80
- """Split a raw sequence on whitespace and arrow separators."""
183
+ """Split a raw invocation on whitespace and arrow separators.
184
+
185
+ A quoted argument stays one token, without its quotes. Backslashes are
186
+ kept as they are, so a Windows path survives; an unbalanced quote falls
187
+ back to a plain whitespace split.
188
+ """
81
189
  normalized = _ARROWS.sub(" ", raw)
82
- return [t for t in normalized.split() if t]
190
+ lexer = shlex.shlex(normalized, posix=False)
191
+ lexer.whitespace_split = True
192
+ lexer.commenters = "" # a URL fragment (`#readme`) is not a comment
193
+ try:
194
+ tokens = list(lexer)
195
+ except ValueError:
196
+ return normalized.split()
197
+ return [t for t in map(_unquote, tokens) if t]
198
+
199
+
200
+ def _split_flags(tokens):
201
+ """Return (other tokens, args from flags, unexpected flag tokens).
202
+
203
+ `--pin <version>` and `--pin=<version>` give `pin`. A flag with no value,
204
+ one given twice or one the pipeline does not take is unexpected: which
205
+ value was meant has no single answer, so the parse reports it.
206
+ """
207
+ rest, args, unexpected = [], {}, []
208
+ tokens = list(tokens)
209
+ while tokens:
210
+ tok = tokens.pop(0)
211
+ if not tok.startswith("-") or tok == "-":
212
+ rest.append(tok)
213
+ continue
214
+ if tok in IGNORED_FLAGS:
215
+ continue
216
+ name, eq, value = tok.partition("=")
217
+ if name not in VALUE_FLAGS:
218
+ unexpected.append(tok)
219
+ continue
220
+ if not eq and tokens and not tokens[0].startswith("-"):
221
+ value = tokens.pop(0)
222
+ tok = f"{name} {value}"
223
+ key = VALUE_FLAGS[name]
224
+ if not value or key in args:
225
+ unexpected.append(tok)
226
+ else:
227
+ args[key] = value
228
+ return rest, args, unexpected
83
229
 
84
230
 
85
231
  def _classify_bracket(code, value):
86
- """Return (min, mode, target) for a bracket value on a given code."""
232
+ """Return (min, mode, target, malformed) for a bracket value on a code.
233
+
234
+ Keywords match in any case (`TS[MIN:80]`, `AN[Auto]`). `min:N` counts only
235
+ on AN and TS; elsewhere it is recorded as `min: null`.
236
+ """
87
237
  if value is None or value == "":
88
- return None, None, None
89
- m = re.fullmatch(r"min:(\d+)", value)
238
+ return None, None, None, False
239
+ m = _MIN_RE.fullmatch(value)
240
+ if m:
241
+ # min:N only applies to AN and TS; ignored elsewhere.
242
+ return (int(m.group(1)) if code in THRESHOLD_CODES else None), None, None, False
243
+ if _MIN_LIKE_RE.match(value):
244
+ return None, None, None, True
245
+ if value.lower() == "auto":
246
+ return None, "auto", None, False
247
+ return None, None, value, False
248
+
249
+
250
+ def _normalized_token(code, value):
251
+ """The token as `expanded` lists it: keywords lower case, `min:N` as a number."""
252
+ if value in (None, ""):
253
+ return code
254
+ m = _MIN_RE.fullmatch(value)
90
255
  if m:
91
- # min:N only applies to circuit-breaker codes; ignored elsewhere.
92
- return (int(m.group(1)) if code in CIRCUIT_BREAKER_CODES else None), None, None
93
- if value == "auto":
94
- return None, "auto", None
95
- return None, None, value
256
+ return f"{code}[min:{int(m.group(1))}]"
257
+ if value.lower() == "auto":
258
+ return f"{code}[auto]"
259
+ return f"{code}[{value}]"
96
260
 
97
261
 
98
262
  def _detect_anti_patterns(codes):
@@ -154,9 +318,86 @@ def _detect_anti_patterns(codes):
154
318
  return found
155
319
 
156
320
 
321
+ def _min_ignored(tokens):
322
+ """The warning for `min:N` brackets on codes other than AN and TS."""
323
+ return {
324
+ "pattern": "min-ignored",
325
+ "codes": [t.split("[", 1)[0] for t in tokens],
326
+ "message": f"{', '.join(tokens)} is ignored: only AN (a unit count) and TS (a test threshold) take min:N",
327
+ "suggestion": "remove it, or put the threshold on TS (TS[min:N])",
328
+ }
329
+
330
+
331
+ def _form(result, index, token):
332
+ """The invocation again, with `token` in place of the plan entry at `index`."""
333
+ tokens = list(result["expanded"])
334
+ tokens[index] = token
335
+ return _with_pin(" ".join(tokens), result["args"])
336
+
337
+
338
+ def _with_pin(text, args):
339
+ return f"{text} --pin {shlex.quote(args['pin'])}" if args.get("pin") else text
340
+
341
+
342
+ def _needs(code, needs, form, rest):
343
+ """A `first_input` entry; `rest` follows the reason's fixed opening."""
344
+ reason = f"{code} needs {needs} before the pipeline can start{rest}" if rest is not None else None
345
+ return {"code": code, "input": needs, "form": form, "halt_reason": reason}
346
+
347
+
348
+ def _bs_input(result, index, entry):
349
+ """BS's two inputs: the forge alias gives both, a chain of codes no skill name."""
350
+ if all(key in result["args"] for key in BS_INPUTS):
351
+ return None
352
+ target = entry["target"]
353
+ after = result["plan"][index + 1:]
354
+ if (index == 0 and result["codes"] == ALIASES["forge"] and entry["mode"] is None
355
+ and all(e["min"] is None and e["mode"] is None and e["target"] is None for e in after)):
356
+ needs = "a skill name" if target else "a target and a skill name"
357
+ form = FORGE_FORM.replace("<repo-url-or-path>", target) if target else FORGE_FORM
358
+ return _needs("BS", needs, _with_pin(form, result["args"]),
359
+ f": give {'it' if target else 'them'} as {form}")
360
+ return _needs("BS", "a target and a skill name", None,
361
+ f", and a chain gives it one bracket: run BS on its own first, or use {FORGE_FORM}")
362
+
363
+
364
+ def _first_input(result):
365
+ """The input the chain's first workflow lacks, or None (pipeline-mode.md step 1).
366
+
367
+ Only for a runnable plan. The first workflow is the first plan entry
368
+ other than SF, and it lacks its input when no bracket target and no
369
+ alias argument gives it.
370
+ """
371
+ if not result["valid"]:
372
+ return None
373
+ index = next((i for i, e in enumerate(result["plan"]) if e["code"] != "SF"), None)
374
+ if index is None:
375
+ return None
376
+ entry = result["plan"][index]
377
+ code = entry["code"]
378
+ if code in ON_THEIR_OWN:
379
+ return _needs(code, ON_THEIR_OWN[code], None,
380
+ f", and a chain gives it one bracket: run {code} on its own, outside a chain")
381
+ if code == "BS":
382
+ return _bs_input(result, index, entry)
383
+ if code not in FIRST_INPUTS:
384
+ return None
385
+ keys, needs, placeholder = FIRST_INPUTS[code]
386
+ if entry["target"] is not None or any(key in result["args"] for key in keys):
387
+ return None
388
+ taken = "auto" if entry["mode"] else (f"min:{entry['min']}" if entry["min"] is not None else None)
389
+ token = f"{code}[{placeholder}]"
390
+ if code == "CS":
391
+ return None if taken else _needs(code, needs, _form(result, index, token), None)
392
+ if taken:
393
+ hint = ", so start the chain as forge-auto <repo-or-doc-url>" if code == "AN" and taken == "auto" else ""
394
+ return _needs(code, needs, None,
395
+ f", and its bracket already holds {taken}: a bracket holds one value{hint}")
396
+ return _needs(code, needs, _form(result, index, token), f": give it as {token}")
397
+
398
+
157
399
  def parse_pipeline(raw):
158
- """Parse and validate a raw pipeline sequence. Deterministic."""
159
- tokens = _tokenize(raw)
400
+ """Parse and validate a raw pipeline invocation. Deterministic."""
160
401
  result = {
161
402
  "raw": raw,
162
403
  "alias": None,
@@ -165,29 +406,42 @@ def parse_pipeline(raw):
165
406
  "expanded": [],
166
407
  "plan": [],
167
408
  "codes": [],
409
+ "args": {},
168
410
  "unknown_codes": [],
411
+ "unexpected_args": [],
412
+ "missing_args": [],
413
+ "malformed_brackets": [],
169
414
  "anti_patterns": [],
415
+ "first_input": None,
170
416
  "valid": False,
171
417
  }
418
+ tokens, args, unexpected = _split_flags(_tokenize(raw))
419
+ result["args"] = args
420
+ result["unexpected_args"] = unexpected
172
421
  if not tokens:
173
422
  return result
174
423
 
175
- # Alias handling — only when the sequence is a lone alias token.
176
- if len(tokens) == 1:
177
- lone = tokens[0].lower()
178
- if lone in REMOVED_ALIASES:
179
- result["removed_alias"] = lone
180
- return result
181
- if lone in DEPRECATED_ALIASES:
182
- result["deprecated_alias"] = lone
183
- target = DEPRECATED_ALIASES[lone]
184
- result["alias"] = target
185
- tokens = list(ALIASES[target])
186
- elif lone in ALIASES:
187
- result["alias"] = lone
188
- tokens = list(ALIASES[lone])
189
-
190
- result["expanded"] = []
424
+ # A leading alias expands; the tokens after it are its arguments.
425
+ lead = tokens[0].lower()
426
+ if lead in REMOVED_ALIASES:
427
+ result["removed_alias"] = lead
428
+ return result
429
+ alias = None
430
+ if lead in DEPRECATED_ALIASES:
431
+ result["deprecated_alias"] = lead
432
+ alias = DEPRECATED_ALIASES[lead]
433
+ elif lead in ALIASES:
434
+ alias = lead
435
+ if alias:
436
+ result["alias"] = alias
437
+ positionals = tokens[1:]
438
+ names = ALIAS_INPUTS[alias]
439
+ result["args"] = {**dict(zip(names, positionals)), **args}
440
+ result["unexpected_args"] = positionals[len(names):] + unexpected
441
+ result["missing_args"] = list(names[len(positionals):])
442
+ tokens = list(ALIASES[alias])
443
+
444
+ ignored_min = []
191
445
  for tok in tokens:
192
446
  m = _TOKEN_RE.match(tok)
193
447
  if not m:
@@ -195,20 +449,31 @@ def parse_pipeline(raw):
195
449
  continue
196
450
  code = m.group(1).upper()
197
451
  value = m.group(2)
198
- result["expanded"].append(
199
- f"{code}[{value}]" if value not in (None, "") else code
200
- )
452
+ result["expanded"].append(_normalized_token(code, value))
201
453
  if code not in KNOWN_CODES:
202
454
  result["unknown_codes"].append(tok)
203
455
  continue
204
- min_n, mode, target = _classify_bracket(code, value)
456
+ min_n, mode, target, malformed = _classify_bracket(code, value)
457
+ if malformed:
458
+ result["malformed_brackets"].append(tok)
459
+ elif value and _MIN_RE.fullmatch(value) and code not in THRESHOLD_CODES:
460
+ ignored_min.append(_normalized_token(code, value))
205
461
  result["codes"].append(code)
206
462
  result["plan"].append(
207
463
  {"code": code, "min": min_n, "mode": mode, "target": target}
208
464
  )
209
465
 
210
466
  result["anti_patterns"] = _detect_anti_patterns(result["codes"])
211
- result["valid"] = bool(result["codes"]) and not result["unknown_codes"]
467
+ if ignored_min:
468
+ result["anti_patterns"].append(_min_ignored(ignored_min))
469
+ result["valid"] = (
470
+ bool(result["codes"])
471
+ and not result["unknown_codes"]
472
+ and not result["unexpected_args"]
473
+ and not result["missing_args"]
474
+ and not result["malformed_brackets"]
475
+ )
476
+ result["first_input"] = _first_input(result)
212
477
  return result
213
478
 
214
479
 
@@ -220,9 +485,9 @@ def _build_parser():
220
485
  prog="parse-pipeline",
221
486
  description=(
222
487
  "Deterministic parse + validation of a forger pipeline invocation "
223
- "(pipeline-mode.md §1-2): tokenize, expand aliases, classify bracket "
224
- "arguments, and detect sequence anti-patterns, returning a normalized "
225
- "run plan as JSON."
488
+ "(pipeline-mode.md §1-2): tokenize, expand aliases, bind an alias's "
489
+ "arguments to its first workflow, classify bracket arguments, and "
490
+ "detect sequence anti-patterns, returning a normalized run plan as JSON."
226
491
  ),
227
492
  formatter_class=argparse.RawDescriptionHelpFormatter,
228
493
  )
@@ -230,19 +495,22 @@ def _build_parser():
230
495
  src.add_argument(
231
496
  "sequence",
232
497
  nargs="?",
233
- help="Raw pipeline sequence as a positional argument.",
498
+ help="The whole pipeline invocation as a positional argument.",
234
499
  )
235
500
  src.add_argument(
236
501
  "--stdin",
237
502
  action="store_true",
238
- help="Read the raw sequence from stdin.",
503
+ help="Read the whole pipeline invocation from stdin.",
239
504
  )
240
505
  return parser
241
506
 
242
507
 
243
508
  def _resolve_input(args):
244
509
  if args.stdin:
245
- return sys.stdin.read()
510
+ # A Windows console reads cp1252: the arrows and paths need UTF-8.
511
+ if hasattr(sys.stdin, "reconfigure"):
512
+ sys.stdin.reconfigure(encoding="utf-8")
513
+ return sys.stdin.read().strip()
246
514
  if args.sequence is not None:
247
515
  return args.sequence
248
516
  return ""
@@ -267,4 +535,4 @@ def main(argv=None):
267
535
 
268
536
 
269
537
  if __name__ == "__main__":
270
- raise SystemExit(main())
538
+ sys.exit(main())