bmad-module-skill-forge 2.0.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 (235) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.nvmrc +1 -1
  3. package/docs/_data/pinned.yaml +1 -1
  4. package/package.json +2 -2
  5. package/src/knowledge/skill-lifecycle.md +1 -1
  6. package/src/module-help.csv +2 -2
  7. package/src/shared/scripts/skf-comention-pairs.py +350 -0
  8. package/src/shared/scripts/skf-count-tokens.py +222 -0
  9. package/src/shared/scripts/skf-detect-docs.py +232 -1
  10. package/src/shared/scripts/skf-detect-language.py +77 -0
  11. package/src/shared/scripts/skf-detect-tools.py +105 -2
  12. package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
  13. package/src/shared/scripts/skf-find-cycles.py +246 -0
  14. package/src/shared/scripts/skf-hash-content.py +487 -2
  15. package/src/shared/scripts/skf-manifest-ops.py +67 -1
  16. package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
  17. package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
  18. package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
  19. package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
  20. package/src/shared/scripts/skf-shard-body.py +461 -0
  21. package/src/shared/scripts/skf-skill-inventory.py +139 -3
  22. package/src/shared/scripts/skf-structural-diff.py +307 -67
  23. package/src/shared/scripts/skf-validate-brief-schema.py +39 -1
  24. package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
  25. package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
  26. package/src/shared/scripts/skf-validate-output.py +443 -10
  27. package/src/shared/scripts/skf-validate-pins.py +1 -1
  28. package/src/shared/scripts/skf-verify-no-trace.py +214 -0
  29. package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
  30. package/src/skf-analyze-source/SKILL.md +17 -32
  31. package/src/skf-analyze-source/customize.toml +2 -0
  32. package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
  33. package/src/skf-analyze-source/references/continue.md +8 -13
  34. package/src/skf-analyze-source/references/generate-briefs.md +28 -12
  35. package/src/skf-analyze-source/references/headless-contract.md +33 -0
  36. package/src/skf-analyze-source/references/health-check.md +1 -2
  37. package/src/skf-analyze-source/references/identify-units.md +25 -36
  38. package/src/skf-analyze-source/references/init.md +12 -25
  39. package/src/skf-analyze-source/references/map-and-detect.md +8 -20
  40. package/src/skf-analyze-source/references/recommend.md +4 -14
  41. package/src/skf-analyze-source/references/scan-project.md +3 -11
  42. package/src/skf-analyze-source/references/step-auto-scope.md +42 -180
  43. package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
  44. package/src/skf-audit-skill/SKILL.md +9 -6
  45. package/src/skf-audit-skill/customize.toml +2 -0
  46. package/src/skf-audit-skill/references/init.md +58 -32
  47. package/src/skf-audit-skill/references/re-index.md +15 -34
  48. package/src/skf-audit-skill/references/report.md +11 -58
  49. package/src/skf-audit-skill/references/semantic-diff.md +4 -22
  50. package/src/skf-audit-skill/references/severity-classify.md +53 -87
  51. package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
  52. package/src/skf-audit-skill/references/structural-diff.md +43 -68
  53. package/src/skf-brief-skill/SKILL.md +7 -36
  54. package/src/skf-brief-skill/assets/scope-templates.md +1 -1
  55. package/src/skf-brief-skill/customize.toml +14 -0
  56. package/src/skf-brief-skill/references/analyze-target.md +6 -14
  57. package/src/skf-brief-skill/references/confirm-brief.md +2 -14
  58. package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
  59. package/src/skf-brief-skill/references/gather-intent.md +19 -22
  60. package/src/skf-brief-skill/references/headless-args.md +1 -1
  61. package/src/skf-brief-skill/references/health-check.md +1 -22
  62. package/src/skf-brief-skill/references/invocation-contract.md +37 -0
  63. package/src/skf-brief-skill/references/scope-definition.md +4 -14
  64. package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
  65. package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
  66. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  67. package/src/skf-brief-skill/references/write-brief.md +17 -10
  68. package/src/skf-campaign/SKILL.md +15 -84
  69. package/src/skf-campaign/customize.toml +2 -0
  70. package/src/skf-campaign/manifest.yaml +4 -0
  71. package/src/skf-campaign/references/campaign-contracts.md +67 -0
  72. package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
  73. package/src/skf-campaign/references/step-01-setup.md +4 -5
  74. package/src/skf-campaign/references/step-02-strategy.md +8 -3
  75. package/src/skf-campaign/references/step-03-pins.md +1 -1
  76. package/src/skf-campaign/references/step-04-provenance.md +1 -1
  77. package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
  78. package/src/skf-campaign/references/step-06-batch.md +7 -6
  79. package/src/skf-campaign/references/step-11-maintenance.md +2 -2
  80. package/src/skf-campaign/references/step-resume.md +9 -8
  81. package/src/skf-campaign/scripts/campaign-deps.py +10 -1
  82. package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
  83. package/src/skf-campaign/scripts/campaign-status.py +162 -0
  84. package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
  85. package/src/skf-create-skill/SKILL.md +14 -3
  86. package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
  87. package/src/skf-create-skill/assets/skill-sections.md +9 -100
  88. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
  89. package/src/skf-create-skill/customize.toml +13 -0
  90. package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
  91. package/src/skf-create-skill/references/compile.md +34 -47
  92. package/src/skf-create-skill/references/component-extraction.md +8 -35
  93. package/src/skf-create-skill/references/ecosystem-check.md +9 -25
  94. package/src/skf-create-skill/references/enrich.md +4 -17
  95. package/src/skf-create-skill/references/extract.md +14 -29
  96. package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
  97. package/src/skf-create-skill/references/extraction-patterns.md +7 -7
  98. package/src/skf-create-skill/references/generate-artifacts.md +9 -23
  99. package/src/skf-create-skill/references/health-check.md +1 -1
  100. package/src/skf-create-skill/references/load-brief.md +16 -18
  101. package/src/skf-create-skill/references/report.md +12 -15
  102. package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
  103. package/src/skf-create-skill/references/step-auto-shard.md +52 -25
  104. package/src/skf-create-skill/references/step-doc-rot.md +30 -12
  105. package/src/skf-create-skill/references/step-doc-sources.md +2 -2
  106. package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
  107. package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
  108. package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
  109. package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
  110. package/src/skf-create-skill/references/validate.md +59 -28
  111. package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
  112. package/src/skf-create-stack-skill/SKILL.md +15 -9
  113. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
  114. package/src/skf-create-stack-skill/customize.toml +10 -0
  115. package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
  116. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
  117. package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
  118. package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
  119. package/src/skf-create-stack-skill/references/generate-output.md +24 -75
  120. package/src/skf-create-stack-skill/references/init.md +9 -81
  121. package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
  122. package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
  123. package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
  124. package/src/skf-create-stack-skill/references/report.md +27 -62
  125. package/src/skf-create-stack-skill/references/validate.md +35 -31
  126. package/src/skf-drop-skill/SKILL.md +20 -23
  127. package/src/skf-drop-skill/customize.toml +10 -0
  128. package/src/skf-drop-skill/references/execute.md +51 -31
  129. package/src/skf-drop-skill/references/headless-contract.md +18 -0
  130. package/src/skf-drop-skill/references/health-check.md +1 -13
  131. package/src/skf-drop-skill/references/report.md +42 -10
  132. package/src/skf-drop-skill/references/select.md +88 -48
  133. package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
  134. package/src/skf-export-skill/SKILL.md +17 -14
  135. package/src/skf-export-skill/assets/managed-section-format.md +2 -5
  136. package/src/skf-export-skill/customize.toml +10 -6
  137. package/src/skf-export-skill/references/generate-snippet.md +21 -35
  138. package/src/skf-export-skill/references/load-skill.md +44 -42
  139. package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
  140. package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
  141. package/src/skf-export-skill/references/package.md +27 -33
  142. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
  143. package/src/skf-export-skill/references/result-envelope.md +28 -0
  144. package/src/skf-export-skill/references/summary.md +11 -5
  145. package/src/skf-export-skill/references/token-report.md +42 -24
  146. package/src/skf-export-skill/references/update-context.md +32 -33
  147. package/src/skf-forger/SKILL.md +27 -39
  148. package/src/skf-forger/references/pipeline-mode.md +34 -0
  149. package/src/skf-forger/scripts/parse-pipeline.py +270 -0
  150. package/src/skf-quick-skill/SKILL.md +10 -60
  151. package/src/skf-quick-skill/customize.toml +9 -0
  152. package/src/skf-quick-skill/references/batch-mode.md +6 -4
  153. package/src/skf-quick-skill/references/compile.md +15 -52
  154. package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
  155. package/src/skf-quick-skill/references/finalize.md +12 -6
  156. package/src/skf-quick-skill/references/halt-contract.md +54 -0
  157. package/src/skf-quick-skill/references/health-check.md +3 -4
  158. package/src/skf-quick-skill/references/quick-extract.md +7 -18
  159. package/src/skf-quick-skill/references/registry-resolution.md +1 -23
  160. package/src/skf-quick-skill/references/resolve-target.md +27 -34
  161. package/src/skf-quick-skill/references/write-and-validate.md +10 -25
  162. package/src/skf-refine-architecture/SKILL.md +14 -7
  163. package/src/skf-refine-architecture/customize.toml +9 -0
  164. package/src/skf-refine-architecture/references/compile.md +9 -7
  165. package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
  166. package/src/skf-refine-architecture/references/health-check.md +1 -1
  167. package/src/skf-refine-architecture/references/improvements.md +9 -25
  168. package/src/skf-refine-architecture/references/init.md +9 -16
  169. package/src/skf-refine-architecture/references/issue-detection.md +10 -25
  170. package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
  171. package/src/skf-refine-architecture/references/report.md +13 -10
  172. package/src/skf-rename-skill/SKILL.md +11 -21
  173. package/src/skf-rename-skill/customize.toml +10 -0
  174. package/src/skf-rename-skill/references/execute.md +107 -78
  175. package/src/skf-rename-skill/references/exit-codes.md +12 -0
  176. package/src/skf-rename-skill/references/report.md +16 -8
  177. package/src/skf-rename-skill/references/select.md +69 -42
  178. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
  179. package/src/skf-setup/SKILL.md +20 -12
  180. package/src/skf-setup/customize.toml +10 -0
  181. package/src/skf-setup/references/auto-index.md +9 -10
  182. package/src/skf-setup/references/ccc-index.md +14 -15
  183. package/src/skf-setup/references/detect-and-tier.md +6 -8
  184. package/src/skf-setup/references/report.md +22 -31
  185. package/src/skf-setup/references/tier-rules.md +2 -32
  186. package/src/skf-setup/references/write-config.md +9 -10
  187. package/src/skf-test-skill/SKILL.md +10 -8
  188. package/src/skf-test-skill/customize.toml +2 -0
  189. package/src/skf-test-skill/references/coherence-check.md +19 -31
  190. package/src/skf-test-skill/references/coverage-check.md +109 -75
  191. package/src/skf-test-skill/references/detect-mode.md +10 -41
  192. package/src/skf-test-skill/references/external-validators.md +11 -19
  193. package/src/skf-test-skill/references/init.md +38 -20
  194. package/src/skf-test-skill/references/migration-section-rules.md +4 -4
  195. package/src/skf-test-skill/references/report.md +60 -40
  196. package/src/skf-test-skill/references/score.md +32 -64
  197. package/src/skf-test-skill/references/scoring-rules.md +6 -62
  198. package/src/skf-test-skill/references/source-access-protocol.md +6 -6
  199. package/src/skf-test-skill/references/step-hard-gate.md +1 -3
  200. package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
  201. package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
  202. package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
  203. package/src/skf-test-skill/scripts/compute-score.py +131 -1
  204. package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
  205. package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
  206. package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
  207. package/src/skf-update-skill/SKILL.md +17 -5
  208. package/src/skf-update-skill/customize.toml +14 -0
  209. package/src/skf-update-skill/references/detect-changes.md +19 -37
  210. package/src/skf-update-skill/references/health-check.md +3 -9
  211. package/src/skf-update-skill/references/init.md +39 -32
  212. package/src/skf-update-skill/references/manual-section-rules.md +1 -2
  213. package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
  214. package/src/skf-update-skill/references/merge.md +13 -57
  215. package/src/skf-update-skill/references/re-extract.md +12 -24
  216. package/src/skf-update-skill/references/report.md +14 -7
  217. package/src/skf-update-skill/references/validate.md +32 -30
  218. package/src/skf-update-skill/references/write.md +86 -51
  219. package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
  220. package/src/skf-verify-stack/SKILL.md +11 -21
  221. package/src/skf-verify-stack/customize.toml +12 -3
  222. package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
  223. package/src/skf-verify-stack/references/coverage.md +21 -10
  224. package/src/skf-verify-stack/references/exit-codes.md +14 -0
  225. package/src/skf-verify-stack/references/health-check.md +1 -1
  226. package/src/skf-verify-stack/references/init.md +20 -25
  227. package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
  228. package/src/skf-verify-stack/references/integrations.md +44 -49
  229. package/src/skf-verify-stack/references/report.md +28 -34
  230. package/src/skf-verify-stack/references/requirements.md +3 -3
  231. package/src/skf-verify-stack/references/synthesize.md +36 -39
  232. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
  233. package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
  234. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
  235. package/src/skf-rename-skill/references/rebuild-context.md +0 -110
@@ -7,12 +7,12 @@ description: Campaign orchestration — multi-library skill production with depe
7
7
 
8
8
  ## Overview
9
9
 
10
- Orchestrates the production of 15+ skills across multiple sessions by driving them through the full SKF pipeline (brief, generate, compile, test, export) in dependency order. Campaign sits at the top of the pipeline ladder it does not produce skill artifacts directly but sequences the workflows that do. File-based state (`_campaign-state.yaml`) survives context death, enabling resume from any point.
10
+ Orchestrates the production of 15+ skills across multiple sessions by driving them through the full SKF pipeline (brief, generate, compile, test, export) in dependency order. Campaign sits atop the pipeline ladder: it sequences the workflows that produce skills rather than producing artifacts itself. File-based state (`_campaign-state.yaml`) survives context death, enabling resume from any point.
11
11
 
12
12
  ## Conventions
13
13
 
14
14
  - Bare paths (e.g. `references/step-01-setup.md`) resolve from the skill root.
15
- - `references/` holds step files chained by stage number, plus the directive contract at `references/campaign-directive-spec.md` (the canonical spec for `_campaign-directive.md`); `templates/` holds workflow-loaded templates; `scripts/` and `assets/` hold deterministic helpers and the state schema.
15
+ - `references/` holds the stage-chained step files plus reference specs (e.g. the `_campaign-directive.md` contract at `references/campaign-directive-spec.md`); `templates/`, `scripts/`, and `assets/` hold templates, deterministic helpers, and the state schema.
16
16
  - `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
17
17
  - `{project-root}`-prefixed paths resolve from the project working directory.
18
18
  - `{skill-name}` resolves to the skill directory's basename.
@@ -41,7 +41,7 @@ Run these steps once, in order, before dispatching to Mode Routing.
41
41
  - `_bmad/custom/<skill-name>.toml` under `{project-root}` — team overrides (committed)
42
42
  - `_bmad/custom/<skill-name>.user.toml` under `{project-root}` — personal overrides (gitignored)
43
43
 
44
- If it fails or is missing, fall back to `{skill-root}/customize.toml` directly. Apply the fallback for each scalar now so step files never repeat the conditional, and stash as workflow-context variables:
44
+ If it fails or is missing, fall back to `{skill-root}/customize.toml` directly. Resolve each scalar now (so step files never repeat the conditional) and stash as workflow-context variables:
45
45
 
46
46
  - `{campaignWorkspacePath}` ← `workflow.campaign_workspace_path` if non-empty, else `{forge_data_folder}/_campaign`
47
47
  - `{qualityGateHard}` / `{qualityGateSoftTarget}` / `{qualityGateSoftFallback}` ← the `quality_gate_*` scalars (defaults `zero-critical-high` / `90` / `80`)
@@ -52,8 +52,6 @@ Run these steps once, in order, before dispatching to Mode Routing.
52
52
 
53
53
  Load `workflow.persistent_facts` (literal sentences and `file:` references, globs expanded) and keep them in mind for the whole campaign — they are injected into every per-skill kickoff. Run any `activation_steps_prepend` before step 1 and any `activation_steps_append` after this step.
54
54
 
55
- Every step file resolves its frontmatter path vars (`stateFile`, `backupFile`, …) under `{campaignWorkspacePath}` — overriding `campaign_workspace_path` relocates the whole campaign workspace without editing a single step.
56
-
57
55
  4. **Parse CLI overrides** into the workflow context:
58
56
 
59
57
  | Flag | Effect |
@@ -65,7 +63,7 @@ Run these steps once, in order, before dispatching to Mode Routing.
65
63
 
66
64
  If `--brief` or `--manifest` is set, force `{headless_mode} = true` (log "headless: coerced by --brief/--manifest" if it was false).
67
65
 
68
- **`--manifest` format:** one target per line, comma-separated `name,repo_url,tier,pin` (omit `pin` or leave it empty for latest); add a trailing `;dep1,dep2` segment for `depends_on`. Blank lines and `#` comments are skipped. If any line fails to parse, HALT in step-01 listing the offending line numbers rather than silently dropping targets — a partial target set is never run.
66
+ **`--manifest` format:** one `name,repo_url,tier,pin` target per line (empty `pin` = latest); a trailing `;dep1,dep2` segment sets `depends_on`; blank and `#` lines are skipped. A malformed line HALTs step-01 with the offending line numbers never a partial target set.
69
67
 
70
68
  5. **Dispatch** per Mode Routing below.
71
69
 
@@ -74,20 +72,15 @@ Run these steps once, in order, before dispatching to Mode Routing.
74
72
  These rules apply to every step in this workflow:
75
73
 
76
74
  - State-first — write state to disk before chaining to the next step or workflow
77
- - Read-backup-modify-write for all state mutations (see State Contract below)
75
+ - Read-backup-modify-write for all state mutations (State Contract in `references/campaign-contracts.md`)
78
76
  - Validate `_campaign-state.yaml` on every load by running `uv run scripts/campaign-validate-state.py --state-file {stateFile}` and HALT (exit code 3, `invalid-state`) on non-zero — never hand-validate the schema
79
- - Zero memory dependency (NFR-2) — campaign state is 100% recoverable from disk; never rely on conversation context for progress tracking
77
+ - Zero memory dependency — campaign state is 100% recoverable from disk; never rely on conversation context for progress tracking
80
78
  - Treat a missing or unparseable `SKF_*_RESULT_JSON` envelope from any sub-skill as a sub-skill failure; never write partial state from an unparsed envelope
81
- - Append a one-line entry to the campaign decision log (`{campaignWorkspacePath}/_campaign-decision-log.md`, append-only) at every operator decision or auto-decision (skip/force, architecture-doc skip, export cancel/proceed, campaign overwrite, `.bak` recovery, user-cancel) so rationale survives compaction and resume
82
- - **Universal cancel affordance** — at any interactive gate the operator may type `cancel`, `exit`, or `:q` to leave cleanly. HARD HALT with **exit code 12 (`user-cancelled`)**, log the cancellation to the decision log, and leave state intact and resumable. This gives the operator a documented way out at every prompt between Setup and the Export gate. **The Export gate is the one exception:** it has its own `[C]ancel` with exit code 11 (`export-cancelled`), which remains canonical there do not also emit 12 at the export gate, so an automator's exit-code branch stays deterministic. These keywords are recognized only as a response *to an interactive prompt*; a skill or campaign named `cancel`/`exit` supplied as data is never treated as a cancel.
79
+ - Append a one-line entry to the campaign decision log (`{campaignWorkspacePath}/_campaign-decision-log.md`, append-only) at every operator or auto-decision (skip/force, overwrite, export cancel/proceed, `.bak` recovery, user-cancel) so rationale survives compaction and resume
80
+ - **Universal cancel affordance** — at any interactive gate between Setup and the Export gate, `cancel`/`exit`/`:q` triggers a HARD HALT with **exit code 12 (`user-cancelled`)**: log it and leave state intact and resumable. Exception: the Export gate's own `[C]ancel` stays exit code 11 (`export-cancelled`) — never also emit 12 there, so an automator's exit-code branch stays deterministic. These keywords count only as a response *to a prompt*; a skill or campaign named `cancel`/`exit` supplied as data is never treated as a cancel.
83
81
  - Always communicate in `{communication_language}`
84
82
  - If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
85
- - If `{headless_mode}` is true, emit a single-line JSON progress event to **stderr** at each step's entry and exit so schedulers stream live progress instead of post-mortem-parsing the final envelope:
86
- - entry: `{"stage":N,"name":"<slug>","status":"start"}`
87
- - exit (just before chaining): `{"stage":N,"name":"<slug>","status":"done"}`
88
- - on HARD HALT: `{"stage":N,"name":"<slug>","status":"halt","exit":<code>}` instead of "done"
89
-
90
- `N` is the stage number (0–10, per the Stages table) and `<slug>` is the kebab portion of the step filename. For the non-numbered routing/terminal steps (`resume`, `health-check`) emit `"stage":null` with the slug. One line per event; do not pretty-print.
83
+ - If `{headless_mode}` is true, emit a single-line JSON progress event to **stderr** at each step's entry, exit, and HARD HALT so schedulers stream live progress event format in `references/campaign-contracts.md` (Headless Progress Events)
91
84
 
92
85
  ## Stages
93
86
 
@@ -105,86 +98,24 @@ These rules apply to every step in this workflow:
105
98
  | 9 | Export | references/step-10-export.md | No (write-gate HALT) |
106
99
  | 10 | Maintenance | references/step-11-maintenance.md | Yes |
107
100
 
108
- **Stage numbering:** step files are 1-indexed (`step-01` … `step-11`); `campaign.current_stage` in state is 0-indexed, so step-`NN` runs stage `NN − 1` (e.g. step-01 = stage 0, step-11 = stage 10). The Resume Routing table in `references/step-resume.md` maps a resolved stage number to its step file; because `current_stage` records the highest *completed* stage, resume without an active skill advances to `current_stage + 1` (the stage that still needs to run) before consulting the table.
101
+ **Stage numbering:** step files are 1-indexed (`step-01` … `step-11`); `campaign.current_stage` in state is 0-indexed, so step-`NN` runs stage `NN − 1` (step-01 = stage 0, step-11 = stage 10). `references/step-resume.md` §3–§4 own how a resolved stage maps back to its step file on resume (including the `current_stage + 1` advance when no skill is active).
109
102
 
110
103
  ## Invocation Contract
111
104
 
112
105
  | Aspect | Detail |
113
106
  |--------|--------|
114
107
  | **Inputs** | `campaign` to start a new campaign; `campaign resume [--from=<skill>]` to resume from last active or specified skill; `campaign status` for a read-only progress summary |
115
- | **Overrides** | `--headless` / `-H`, `--brief <file>`, `--manifest <file>`, `--from <skill>` — see On Activation step 4 |
116
- | **Gates** | Step 9 (Export): write-gate HALT — requires explicit user approval before writing exported skills to disk |
117
108
  | **Outputs** | `_campaign-state.yaml` (state), `campaign-brief.yaml` (machine-generated brief), `campaign-report.md` (post-campaign summary), `_campaign-decision-log.md` (append-only rationale), `SKF_CAMPAIGN_RESULT_JSON` (headless envelope) — all under `{campaignWorkspacePath}` |
118
- | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
119
- | **Exit codes** | See "Exit Codes" below |
120
-
121
- ## Exit Codes
122
-
123
- Every HARD HALT exits with a stable, documented code so headless automators can branch on the failure class without grepping message text:
124
-
125
- | Code | Meaning | Raised by |
126
- | ---- | -------------------- | ----------------------------------------------------------- |
127
- | 0 | success | step-11 (terminal) |
128
- | 3 | invalid-state | any step §1 (`campaign-validate-state.py` non-zero on load) |
129
- | 4 | circular-deps | step-02 §5 |
130
- | 5 | invalid-pin | step-03 §5 |
131
- | 6 | inaccessible-repo | step-04 §5 |
132
- | 7 | dependency-deadlock | step-05 §4 (no skill ready and no recovery chosen) |
133
- | 8 | missing-brief | step-03/04/05 §2 (brief missing or unreadable) |
134
- | 9 | corrupt-state | step-resume §1 (primary unrecoverable, `.bak` also invalid) |
135
- | 10 | report-failure | step-11 §2 — **degraded only**: the report could not be generated; the campaign still completes and state stays intact (never a hard halt that discards a finished campaign) |
136
- | 11 | export-cancelled | step-10 §4 (operator chose `[C]ancel` — graceful, resumable) |
137
- | 12 | user-cancelled | any interactive gate (operator typed `cancel` / `exit` / `:q` — graceful, resumable; see Workflow Rules) |
138
109
 
139
- ## Result Contract on HARD HALT
110
+ ## Contracts
140
111
 
141
- In addition to the success-variant envelope (see Campaign Headless Envelope), every HARD HALT emits an **error variant** so automators don't silently break. Emit one line on **stderr**:
142
-
143
- ```
144
- SKF_CAMPAIGN_RESULT_JSON: {"status":"error","exit_code":<N>,"phase":"<slug>","error":{"code":"<class>","message":"<short>"},"skills_completed":N,"skills_failed":N,"campaign_report_path":null,"decision_log":"<path-or-null>"}
145
- ```
146
-
147
- `<class>` is the Exit Codes meaning (e.g. `circular-deps`, `inaccessible-repo`); `<slug>` is the step where the HALT occurred. One line, no pretty-print.
112
+ Exit codes, the HARD-HALT error envelope, the read-backup-modify-write **State Contract**, the headless success envelope, and per-step progress events live in `references/campaign-contracts.md` consult it when you HALT, mutate state, or emit headless output.
148
113
 
149
114
  ## Mode Routing
150
115
 
151
116
  On invocation:
152
117
 
153
- 1. **`campaign resume [--from=<skill>]`** — load `references/step-resume.md`. Validates state integrity, checks backup consistency, and chains to the appropriate stage step file. If `--from=<skill>` is provided, override the resume point to the named skill.
118
+ 1. **`campaign resume [--from=<skill>]`** — load `references/step-resume.md` (validates state, recovers from backup, chains to the right stage). `--from=<skill>` overrides the resume point to the named skill.
154
119
  2. **`campaign`** (new, no existing state) — run from stage 0 (Setup).
155
- 3. **`campaign`** (state exists) — detect existing `{campaignWorkspacePath}/_campaign-state.yaml` and prompt: **resume** via `references/step-resume.md`, or **overwrite** with a new campaign. On overwrite, first archive the existing `_campaign-state.yaml` and `campaign-brief.yaml` to `{campaignWorkspacePath}/archive/{name}-{timestamp}/`, log the archive to the decision log, and show a one-line summary of what is being set aside before chaining to step-01. In headless mode, default to **resume** (never silently clobber); archive-and-overwrite only when `--brief`/`--manifest` explicitly seeds a new campaign.
156
- 4. **`campaign status`** (read-only) — load `{campaignWorkspacePath}/_campaign-state.yaml`, validate it via `campaign-validate-state.py`, display the Resume Detection summary block followed by the last ~15 lines of `{campaignWorkspacePath}/_campaign-decision-log.md` (so the operator sees the recent decision trail without flooding the terminal on a long campaign), then stop. No backup, no mutation, no chaining. Exit 0 (or 9 if the state is unrecoverable).
157
-
158
- ## Resume Detection
159
-
160
- When resuming:
161
-
162
- 1. Read `_campaign-state.yaml`
163
- 2. Validate via `campaign-validate-state.py` (halt on invalid; attempt `.bak` recovery per step-resume)
164
- 3. Find last active or completed stage from `campaign.current_stage` (the highest completed stage); when no skill is active, resume continues from the stage *after* it (`current_stage + 1`, terminal-capped)
165
- 4. Skip completed skills (status = `completed` or `skipped`)
166
- 5. If `--from=<skill>` is provided, find the named skill and resume from its stage
167
- 6. Continue from the next incomplete skill in `dependency_graph.execution_order`
168
-
169
- ## State Contract
170
-
171
- All state mutations follow the read-backup-modify-write pattern:
172
-
173
- 1. **Read** `_campaign-state.yaml`
174
- 2. **Validate** via `uv run scripts/campaign-validate-state.py --state-file {stateFile}` (halt on invalid)
175
- 3. **Backup** — copy current `_campaign-state.yaml` to `_campaign-state.yaml.bak`
176
- 4. **Modify** in memory
177
- 5. **Update** `campaign.last_updated` to current ISO-8601 timestamp
178
- 6. **Write** modified state back to `_campaign-state.yaml`
179
-
180
- The `.bak` file is one-deep (overwritten on every write). If the primary file is corrupted (crash during write), the `.bak` file contains the last valid state — step-resume §1 recovers from it automatically rather than dead-halting.
181
-
182
- ## Campaign Headless Envelope
183
-
184
- When `{headless_mode}` is true, the final step emits a single-line JSON envelope on stdout:
185
-
186
- ```
187
- SKF_CAMPAIGN_RESULT_JSON: {"status":"success|error","skills_completed":0,"skills_failed":0,"quality_scores":{},"campaign_report_path":"","decision_log":"","duration":""}
188
- ```
189
-
190
- `status` is `"success"` when the campaign completes normally, `"error"` on any unrecoverable halt (with `exit_code` per the Exit Codes table — see Result Contract on HARD HALT). `skills_completed` and `skills_failed` count per-skill outcomes. `quality_scores` maps skill names to their test-skill scores. `campaign_report_path` points to the generated `campaign-report.md`. `decision_log` points to `_campaign-decision-log.md`. `duration` is the wall-clock time of the campaign run. Populate the counts, `quality_scores`, and `duration` directly from the `campaign-report.py` result JSON (step-11 §2) — do not recompute them by hand.
120
+ 3. **`campaign`** (state exists) — detect existing `{campaignWorkspacePath}/_campaign-state.yaml` and prompt **resume** (via `references/step-resume.md`) or **overwrite**. On overwrite, first archive the existing `_campaign-state.yaml` and `campaign-brief.yaml` to `{campaignWorkspacePath}/archive/{name}-{timestamp}/` and log it before chaining to step-01. In headless mode, default to **resume** (never silently clobber); archive-and-overwrite only when `--brief`/`--manifest` explicitly seeds a new campaign.
121
+ 4. **`campaign status`** (read-only) — load `{campaignWorkspacePath}/_campaign-state.yaml`, validate it via `campaign-validate-state.py`, then run `uv run scripts/campaign-status.py --state-file {campaignWorkspacePath}/_campaign-state.yaml` and display its summary (campaign name, current stage, completed-vs-total, per-status counts) followed by the last ~15 lines of `{campaignWorkspacePath}/_campaign-decision-log.md` for the recent decision trail, then stop. No backup, no mutation, no chaining. Exit 0 (or 9 if the state is unrecoverable).
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-campaign.
4
+ # Team overrides: _bmad/custom/skf-campaign.toml (under {project-root})
5
+ # Personal overrides: _bmad/custom/skf-campaign.user.toml (under {project-root})
4
6
 
5
7
  [workflow]
6
8
 
@@ -5,6 +5,10 @@ version: "2.0.0"
5
5
  trigger: campaign
6
6
  parent_module: skf
7
7
 
8
+ # Canonical internal filenames for the campaign workspace. Fixed contract (the
9
+ # step chain references these names directly and test-skf-campaign-state guards
10
+ # them against drift) — NOT an operator-override surface; customize.toml is the
11
+ # only customization mechanism.
8
12
  config:
9
13
  state_file: "_campaign-state.yaml"
10
14
  backup_file: "_campaign-state.yaml.bak"
@@ -0,0 +1,67 @@
1
+ <!-- Config: communicate in {communication_language}. -->
2
+
3
+ # Campaign Contracts
4
+
5
+ The self-contained contracts consulted at a specific moment — when a step HALTs, mutates state, or emits the final headless envelope. Each stands alone so it survives context compaction.
6
+
7
+ ## Exit Codes
8
+
9
+ Every HARD HALT exits with a stable, documented code so headless automators can branch on the failure class without grepping message text:
10
+
11
+ | Code | Meaning | Raised by |
12
+ | ---- | -------------------- | ----------------------------------------------------------- |
13
+ | 0 | success | step-11 (terminal); step-resume §3–§4 (campaign already complete — nothing to resume) |
14
+ | 2 | invalid-input | step-01 §1 (no targets, or a malformed `--manifest` line); steps 02/03/04/06 §4 (a helper reports unreadable input or a required tool such as `gh` unavailable); step-resume §1/§3 (resume targets a missing campaign or unknown skill) |
15
+ | 3 | invalid-state | any step §1 (`campaign-validate-state.py` non-zero on load) |
16
+ | 4 | circular-deps | step-02 §5 (a dependency cycle, or a dangling `depends_on` reference — either way the graph cannot be ordered) |
17
+ | 5 | invalid-pin | step-03 §5 |
18
+ | 6 | inaccessible-repo | step-04 §5 |
19
+ | 7 | dependency-deadlock | step-05 §4 (no skill ready and no recovery chosen) |
20
+ | 8 | missing-brief | step-03/04/05 §2 and step-06 §4 (brief missing/unreadable, or a Tier B target has no matching brief entry) |
21
+ | 9 | corrupt-state | step-resume §1 (primary unrecoverable, `.bak` also invalid) |
22
+ | 10 | report-failure | step-11 §2 — **degraded only**: the report could not be generated; the campaign still completes and state stays intact (never a hard halt that discards a finished campaign) |
23
+ | 11 | export-cancelled | step-10 §4 (operator chose `[C]ancel` — graceful, resumable) |
24
+ | 12 | user-cancelled | any interactive gate (operator typed `cancel` / `exit` / `:q` at a prompt between Setup and the Export gate — graceful, resumable) |
25
+
26
+ ## Result Contract on HARD HALT
27
+
28
+ In addition to the success-variant envelope (see Campaign Headless Envelope below), every HARD HALT emits an **error variant** so automators don't silently break. Emit one line on **stderr**:
29
+
30
+ ```
31
+ SKF_CAMPAIGN_RESULT_JSON: {"status":"error","exit_code":<N>,"phase":"<slug>","error":{"code":"<class>","message":"<short>"},"skills_completed":N,"skills_failed":N,"campaign_report_path":null,"decision_log":"<path-or-null>"}
32
+ ```
33
+
34
+ `<class>` is the Exit Codes meaning (e.g. `circular-deps`, `inaccessible-repo`); `<slug>` is the step where the HALT occurred. One line, no pretty-print.
35
+
36
+ ## State Contract
37
+
38
+ All state mutations follow the read-backup-modify-write pattern:
39
+
40
+ 1. **Read** `_campaign-state.yaml`
41
+ 2. **Validate** via `uv run scripts/campaign-validate-state.py --state-file {stateFile}` (halt on invalid)
42
+ 3. **Backup** — copy current `_campaign-state.yaml` to `_campaign-state.yaml.bak`
43
+ 4. **Modify** in memory
44
+ 5. **Update** `campaign.last_updated` to current ISO-8601 timestamp
45
+ 6. **Write** modified state back to `_campaign-state.yaml`
46
+
47
+ The `.bak` file is one-deep (overwritten on every write). If the primary file is corrupted (crash during write), the `.bak` file contains the last valid state — step-resume §1 recovers from it automatically rather than dead-halting.
48
+
49
+ ## Campaign Headless Envelope
50
+
51
+ When `{headless_mode}` is true, the final step emits a single-line JSON envelope on stdout:
52
+
53
+ ```
54
+ SKF_CAMPAIGN_RESULT_JSON: {"status":"success|error","skills_completed":0,"skills_failed":0,"quality_scores":{},"campaign_report_path":"","decision_log":"","duration":""}
55
+ ```
56
+
57
+ `status` is `"success"` when the campaign completes normally, `"error"` on any unrecoverable halt (with `exit_code` per the Exit Codes table — see Result Contract on HARD HALT above). `skills_completed` and `skills_failed` count per-skill outcomes. `quality_scores` maps skill names to their test-skill scores. `campaign_report_path` points to the generated `campaign-report.md`. `decision_log` points to `_campaign-decision-log.md`. `duration` is the wall-clock time of the campaign run. Populate the counts, `quality_scores`, and `duration` directly from the `campaign-report.py` result JSON (step-11 §2) — do not recompute them by hand.
58
+
59
+ ## Headless Progress Events
60
+
61
+ When `{headless_mode}` is true, emit a single-line JSON progress event to **stderr** at each step's entry, exit, and HARD HALT, so schedulers stream live progress instead of post-mortem-parsing the final envelope:
62
+
63
+ - entry: `{"stage":N,"name":"<slug>","status":"start"}`
64
+ - exit (just before chaining): `{"stage":N,"name":"<slug>","status":"done"}`
65
+ - on HARD HALT: `{"stage":N,"name":"<slug>","status":"halt","exit":<code>}` instead of `"done"`
66
+
67
+ `N` is the 0-indexed stage number (0–10) and `<slug>` is the kebab portion of the step filename. For the non-numbered routing/terminal steps (`resume`, `health-check`) emit `"stage":null` with the slug. One line per event; do not pretty-print.
@@ -5,117 +5,28 @@ directiveFile: '_campaign-directive.md'
5
5
 
6
6
  # Campaign Directive Specification
7
7
 
8
- This is the **canonical contract** for the campaign directive. SKILL.md and the
9
- step files' "Read Directive" sections defer to it rather than re-specifying the
10
- format — when a step loads `campaign.directive_path`, it applies the contents
11
- per the sections below.
12
-
13
- ## Purpose
14
-
15
- The campaign directive (`_campaign-directive.md`) is a file-based standing directive that provides campaign-wide policy persisting across context boundaries. It is:
16
-
17
- - **Human-readable** — plain markdown, editable with any text editor
18
- - **Auditable** — version-controllable alongside campaign artifacts
19
- - **Context-boundary safe** — re-read from disk at each stage transition, with no dependency on LLM memory (NFR-2)
20
-
21
- The directive allows the operator to adjust campaign-wide policy without editing the state file or relying on LLM memory.
22
-
23
- ## File Format
24
-
25
- - **Encoding:** UTF-8 markdown
26
- - **Frontmatter:** not required (plain content is sufficient)
27
- - **Location:** the path specified by `campaign.directive_path` in the campaign state file
28
- - **Default filename:** `_campaign-directive.md` (configured in `manifest.yaml` as `config.directive_file`)
8
+ Canonical contract for the campaign directive (`_campaign-directive.md`) a
9
+ file-based standing directive holding campaign-wide policy. When a step loads
10
+ `campaign.directive_path` it re-reads the file fresh from disk at stage entry
11
+ (no caching, so operator edits between stages are picked up) and applies the
12
+ sections below as campaign-wide context. UTF-8 markdown; frontmatter not
13
+ required; default filename `_campaign-directive.md`.
29
14
 
30
15
  ## Recognized Sections
31
16
 
32
- All sections are optional. The directive may contain any combination of these, or none at all.
33
-
34
- ### `## Quality Overrides`
35
-
36
- Operator adjustments to quality gates for specific skills or the entire campaign.
37
-
38
- Example:
39
- ```markdown
40
- ## Quality Overrides
41
-
42
- - Accept 85% for library-x due to incomplete upstream docs
43
- - Lower soft gate to 70% for experimental-sdk (early-stage repo)
44
- ```
45
-
46
- ### `## Skip List`
47
-
48
- Skills to skip during processing, with rationale.
49
-
50
- Example:
51
- ```markdown
52
- ## Skip List
53
-
54
- - Skip library-y — upstream repo is in maintenance mode
55
- - Skip legacy-tool — will be removed in next sprint
56
- ```
57
-
58
- ### `## Pipeline Flags`
17
+ All optional the directive may hold any combination of these, or none:
59
18
 
60
- Per-skill or campaign-wide pipeline modifiers.
19
+ - **`## Quality Overrides`** — operator adjustments to quality gates for specific skills or the whole campaign.
20
+ - **`## Skip List`** — skills to skip during processing, with rationale.
21
+ - **`## Pipeline Flags`** — per-skill or campaign-wide pipeline modifiers.
22
+ - **`## Notes`** — free-form operator context for the agent processing the campaign.
61
23
 
62
- Example:
63
- ```markdown
64
- ## Pipeline Flags
65
-
66
- - Use `--pin main` for all unpinned skills
67
- - Force forge-auto mode for large-repo-z
68
- ```
69
-
70
- ### `## Notes`
71
-
72
- Free-form operator context for the LLM agent processing the campaign.
73
-
74
- Example:
75
- ```markdown
76
- ## Notes
77
-
78
- Focus on documentation quality this run — the team is preparing for an external audit.
79
- Prioritize skills that feed into the public API layer.
80
- ```
81
-
82
- ## Unrecognized Sections
83
-
84
- Any section heading not listed above is treated as general guidance. The LLM agent reads and applies judgment based on the content. This allows operators to add ad-hoc context without modifying the specification.
85
-
86
- ## Read Contract
87
-
88
- The directive is read from the path stored in `campaign.directive_path` (in the campaign state file) at stage entry. Every step that loads state and performs actionable processing checks for and applies the directive.
89
-
90
- Steps that read the directive:
91
- - **step-02-strategy** — applies directive contents as campaign-wide context for strategy processing (e.g. `## Notes` visible while reviewing the computed order; the order itself is computed deterministically by `campaign-deps.py`)
92
- - **step-05-skill-loop** — directive populates `{{directive_content}}` in kickoff template and provides campaign-wide context for all skill processing
93
- - **step-06-batch** — operator may want skip/quality overrides for Tier B batch processing
94
- - **step-08-verify** — operator may want to influence verification focus areas
95
- - **step-09-refine** — operator may want to guide refinement priorities
96
- - **step-10-export** — operator may want to exclude specific skills from export
97
-
98
- Steps that do NOT read the directive:
99
- - **step-01-setup** — creates the state file; `directive_path` is collected as input, not read
100
- - **step-03-pins** — pin validation is deterministic (Python script); directive cannot influence gh API calls
101
- - **step-04-provenance** — repo access verification is deterministic; directive cannot influence commit SHA recording
102
- - **step-07-capstone** — SS compose-mode is mechanical aggregation; no operator policy applies
103
- - **step-11-maintenance** — report generation is templated; health check is autonomous
104
- - **step-resume** — routing step, not a processing step
24
+ Any heading not listed above is treated as general guidance: read it and apply
25
+ judgment based on the content. This lets operators add ad-hoc context without
26
+ modifying this specification.
105
27
 
106
28
  ## Absence Behavior
107
29
 
108
30
  - If `campaign.directive_path` is not set in the state file: no error, proceed with defaults
109
31
  - If `campaign.directive_path` is set but the file does not exist at that path: no error, proceed with defaults
110
32
  - The directive is always optional — a campaign runs identically without one
111
-
112
- ## Modification Contract
113
-
114
- The operator may edit the directive file at any time between stages. The next stage that reads the directive will pick up the updated version. There is no caching — the directive is always read fresh from disk at stage entry.
115
-
116
- ## Lifecycle
117
-
118
- 1. **Created:** before or during Stage 0 (setup) — the operator provides the file path, or it defaults to `_campaign-directive.md`
119
- 2. **Read:** at each stage entry by applicable steps (see Read Contract above)
120
- 3. **Modified:** at any time between stages by the operator
121
- 4. **Consumed:** by the LLM agent as campaign-wide context influencing processing decisions
@@ -16,7 +16,7 @@ manifestScript: 'scripts/campaign-parse-manifest.py'
16
16
 
17
17
  Collect campaign inputs from the operator, create the initial `_campaign-state.yaml`, and generate `campaign-brief.yaml` so the campaign has a persistent starting point that survives context death.
18
18
 
19
- This is the only step that creates the state file (it does not yet exist). All subsequent steps use **read-backup-modify-write** per the State Contract in SKILL.md.
19
+ This is the only step that creates the state file (it does not yet exist). All subsequent steps use **read-backup-modify-write** per the State Contract in `references/campaign-contracts.md`.
20
20
 
21
21
  ## RULES
22
22
 
@@ -40,9 +40,9 @@ Accept from the operator (or, in headless mode, from the `--brief`/`--manifest`
40
40
  - `directive_path` (optional) — path to a `_campaign-directive.md` file with operator directives (contract: `references/campaign-directive-spec.md`)
41
41
  - `architecture_doc_path` (optional) — path to the architecture document the verify (Stage 7) and refine (Stage 8) stages consume. If omitted here, those stages discover it at runtime (`docs/architecture.md`, then `_bmad-output/planning-artifacts/architecture.md`). Capturing it now persists the choice across resume and avoids re-prompting.
42
42
 
43
- When seeding from `--manifest`, parse it deterministically: `uv run {manifestScript} <manifest-file>`. If the result's `errors[]` is non-empty (exit 1), HALT listing the offending line numbers — never run a partial target set. When seeding from `--brief`, read the existing `campaign-brief.yaml` directly.
43
+ When seeding from `--manifest`, parse it deterministically: `uv run {manifestScript} <manifest-file>`. If the result's `errors[]` is non-empty (script exit 1), HALT (exit code 2, `invalid-input`) listing the offending line numbers — never run a partial target set. When seeding from `--brief`, read the existing `campaign-brief.yaml` directly.
44
44
 
45
- If no targets can be collected (empty interactive input or empty `--brief`/`--manifest`), HALT with guidance — a campaign needs at least one target.
45
+ If no targets can be collected (empty interactive input or empty `--brief`/`--manifest`), HALT (exit code 2, `invalid-input`) with guidance — a campaign needs at least one target.
46
46
 
47
47
  ### §2 — Health Queue Preference
48
48
 
@@ -108,7 +108,7 @@ Populate `{templateFile}` with collected inputs and write to `{briefFile}`. Fill
108
108
  - `architecture_doc_path` — from collected input, or empty string if not provided
109
109
  - `notes` — operator-provided context, or empty string
110
110
 
111
- The brief is a machine-readable snapshot enabling fresh-context resume (FR-35).
111
+ The brief is a machine-readable snapshot enabling fresh-context resume.
112
112
 
113
113
  ## OUTPUT
114
114
 
@@ -116,7 +116,6 @@ Confirm state file creation and brief generation. Display summary:
116
116
 
117
117
  - Campaign name
118
118
  - Number of targets
119
- - Tier distribution (count of A vs B)
120
119
  - Health queue setting
121
120
 
122
121
  Chain to `{nextStepFile}`.
@@ -46,11 +46,14 @@ Run the deterministic topological sort — do not hand-compute it:
46
46
  uv run {depsScript} --compute --state-file {stateFile}
47
47
  ```
48
48
 
49
- Parse the JSON output: `execution_order` (the ordered skill names — Kahn's sort with Tier A placed before Tier B within a dependency level), `circular_deps_detected` (bool), and `cycle_participants` (the unplaced skills when a cycle exists, else null). Exit code 1 from the script signals a cycle or a dangling dependency reference; exit code 2 signals a state/parse error surface either and HALT.
49
+ Parse the JSON output: `execution_order` (the ordered skill names — Kahn's sort with Tier A placed before Tier B within a dependency level), `circular_deps_detected` (bool), `cycle_participants` (the unplaced skills when a cycle exists, else null), and `tier_counts` (`{"A": n, "B": m}`, for the §7 strategy view). Script exit 1 signals an unorderable graph — a cycle or a dangling `depends_on` reference — handled at §5. Script exit 2 signals the helper could not read/parse the state file; HALT (exit code 2, `invalid-input`) surfacing its error.
50
50
 
51
- ### §5 — Handle Circular Dependencies
51
+ ### §5 — Handle Unorderable Graph
52
52
 
53
- If `circular_deps_detected` is `true`, HALT (exit code 4, `circular-deps`) with a clear error listing `cycle_participants` and their mutual `depends_on` edges. Do NOT proceed — circular dependencies make execution order impossible.
53
+ If the graph cannot be ordered (script exit 1), HALT (exit code 4, `circular-deps`) the execution order is impossible, so do not proceed. Two cases:
54
+
55
+ - **Cycle** (`circular_deps_detected: true`): list `cycle_participants` and their mutual `depends_on` edges.
56
+ - **Dangling reference** (a `DANGLING_DEPENDENCY` error with no `execution_order`): name the skill and the unknown dependency it references.
54
57
 
55
58
  ### §6 — Write State
56
59
 
@@ -83,6 +86,8 @@ TIER DISTRIBUTION:
83
86
  Tier B (QS batch): {count}
84
87
  ```
85
88
 
89
+ Fill the TIER DISTRIBUTION counts from `tier_counts` in the §4 script output — do not re-tally `skills[]` by hand.
90
+
86
91
  ### §8 — Plan Confirmation Gate
87
92
 
88
93
  The strategy view is the last review surface before a potentially long, mostly-unattended run begins. Present a confirmation gate:
@@ -41,7 +41,7 @@ Copy `{stateFile}` to `{backupFile}` before any modification.
41
41
 
42
42
  ### §4 — Validate Pins
43
43
 
44
- Run `uv run {pinScript} --state-file {stateFile} --brief-file {briefFile}`. Parse the JSON output. For each result: if `status` is `"valid"` or `"resolved"`, the pin is good; if `"invalid"`, collect the failure with suggestions.
44
+ Run `uv run {pinScript} --state-file {stateFile} --brief-file {briefFile}`. If the script exits 2 (a required tool such as `gh` is unavailable, or a file is unreadable), HALT (exit code 2, `invalid-input`) surfacing its error — pins cannot be validated without `gh`. Otherwise parse the JSON output. For each result: if `status` is `"valid"` or `"resolved"`, the pin is good; if `"invalid"`, collect the failure with suggestions.
45
45
 
46
46
  ### §5 — Handle Invalid Pins
47
47
 
@@ -48,7 +48,7 @@ Run the deterministic provenance check — do not parse repo URLs or shell out t
48
48
  uv run {provenanceScript} --state-file {stateFile} --brief-file {briefFile}
49
49
  ```
50
50
 
51
- The script resolves `{owner}/{repo}` from each `repo_url` (tolerating `.git`/trailing slashes/SSH form), picks the ref (the skill's `pin`, else the repo default branch), runs `gh repo view` + `gh api commits`, and emits `results[]` with `commit_sha` and `status` per skill, plus `all_accessible`, `inaccessible_count`, and `systemic_hint`. Exit 0 = all accessible, 1 = one or more inaccessible, 2 = error (missing files, bad YAML, `gh` not installed).
51
+ The script resolves `{owner}/{repo}` from each `repo_url` (tolerating `.git`/trailing slashes/SSH form), picks the ref (the skill's `pin`, else the repo default branch), runs `gh repo view` + `gh api commits`, and emits `results[]` with `commit_sha` and `status` per skill, plus `all_accessible`, `inaccessible_count`, and `systemic_hint`. Exit 0 = all accessible, 1 = one or more inaccessible, 2 = error (missing files, bad YAML, `gh` not installed). On script exit 2, HALT (exit code 2, `invalid-input`) surfacing the error — repo access cannot be verified without `gh`.
52
52
 
53
53
  ### §5 — Handle Inaccessible Repos
54
54
 
@@ -25,7 +25,7 @@ Iterate skills in `dependency_graph.execution_order`, processing each Tier A ski
25
25
  - Update `campaign.current_stage` to `4`.
26
26
  - Update `campaign.last_updated` to current ISO-8601 with timezone on every write.
27
27
  - Write state after EACH skill completes (not just at end) — context death between skills must be survivable.
28
- - The per-skill pipeline body (§5.2) runs inline, not in a delegated subagent: AN→BS→CS→TS are nested skill activations, and a subagent cannot spawn further subagents. This is a deliberate constraint, not an oversight — inline keeps the full pipeline reachable and preserves NFR-2 (every skill's state is written before the next begins).
28
+ - The per-skill pipeline body (§5.2) runs inline, not in a delegated subagent: AN→BS→CS→TS are nested skill activations, and a subagent cannot spawn further subagents running inline keeps the full pipeline reachable and writes each skill's state before the next begins.
29
29
  - If `{headless_mode}` is true, auto-proceed through confirmation gates. Dependency gate blocks default to HALT (safest — never silently skip dependencies).
30
30
 
31
31
  ## TASKS
@@ -5,6 +5,7 @@ stateFile: '{campaignWorkspacePath}/_campaign-state.yaml'
5
5
  backupFile: '{campaignWorkspacePath}/_campaign-state.yaml.bak'
6
6
  briefFile: '{campaignWorkspacePath}/campaign-brief.yaml'
7
7
  batchFile: '{campaignWorkspacePath}/_batch-input.txt'
8
+ batchScript: 'scripts/campaign-render-batch.py'
8
9
  validateScript: 'scripts/campaign-validate-state.py'
9
10
  ---
10
11
 
@@ -42,15 +43,15 @@ If no Tier B skills need processing, skip to §7 (Stage Completion) — the batc
42
43
 
43
44
  ### §4 — Generate Batch File
44
45
 
45
- Load `{briefFile}` to look up `repo_url` for each Tier B skill (repo URLs are in the brief's `targets[]`, not in the state schema). HALT (exit code 8, `missing-brief`) if the brief is missing or unreadable.
46
+ Generate the QS `--batch` input file deterministically:
46
47
 
47
- Write a batch input file listing Tier B skills for QS `--batch` consumption. For each pending Tier B skill, include:
48
+ ```
49
+ uv run {batchScript} --state-file {stateFile} --brief-file {briefFile} -o {batchFile}
50
+ ```
48
51
 
49
- - Skill name (from `skills[].name`)
50
- - Repository URL (from brief's `targets[].repo_url`, matched by name)
51
- - Pin (from `skills[].pin`, or omit if null for latest)
52
+ The script filters `skills[]` for `tier == "B" && status == "pending"`, looks up each skill's `repo_url` from the brief's `targets[]` (repo URLs live in the brief, not the state schema), and writes one line per skill at `{batchFile}` in the exact single-target shape QS parses (see `src/skf-quick-skill/references/batch-mode.md`) — the line format is owned once by the script, not re-derived here. It emits a JSON summary (`written`, `count`, `skipped_non_tierB`, `skipped_non_pending`) on stderr.
52
53
 
53
- Place the batch file at `{batchFile}`.
54
+ HALT on non-zero exit: exit code 8 (`missing-brief`) when the brief is missing/unreadable **or** a pending Tier B skill has no matching brief target; exit code 2 (`invalid-input`) on a state file/parse error.
54
55
 
55
56
  ### §5 — Execute QS Batch
56
57
 
@@ -60,7 +60,7 @@ When `{headless_mode}` is true, emit the campaign result envelope on stdout, cop
60
60
  SKF_CAMPAIGN_RESULT_JSON: {"status":"success","skills_completed":N,"skills_failed":N,"quality_scores":{...},"campaign_report_path":"{reportFile}","decision_log":"{campaignWorkspacePath}/_campaign-decision-log.md","duration":"..."}
61
61
  ```
62
62
 
63
- - `status`: "success" if the campaign completed normally (HARD HALTs emit the error variant per SKILL.md "Result Contract on HARD HALT")
63
+ - `status`: "success" if the campaign completed normally (HARD HALTs emit the error variant per the "Result Contract on HARD HALT" in `references/campaign-contracts.md`)
64
64
  - `skills_completed` / `skills_failed` / `quality_scores` / `duration`: from the §2 report-script result JSON
65
65
  - `campaign_report_path`: `{reportFile}`
66
66
  - `decision_log`: path to the append-only decision log
@@ -73,7 +73,7 @@ Set `campaign.current_stage` to `10`. Update `campaign.last_updated` to current
73
73
 
74
74
  ### §5 — Chain to Health Check
75
75
 
76
- The operator's findings-routing consent (`campaign.health_findings_queue`) is applied by the terminal health-check step: `references/health-check.md` §1 reads it from state and carries it into the shared health check as the pre-decided opt-in (`"improvement"` → route non-bug findings to the shared improvement queue without re-prompting; `"local"` → local queue only). That is what makes the §2 setup consent prompt actionable rather than cosmetic — surface the active setting here for visibility.
76
+ The operator's findings-routing consent (`campaign.health_findings_queue`) is applied by the terminal health-check step: `references/health-check.md` §1 reads it from state and carries it into the shared health check as the pre-decided opt-in (`"improvement"` → route non-bug findings to the shared improvement queue without re-prompting; `"local"` → local queue only). Surface the active setting here.
77
77
 
78
78
  Display: "**Campaign complete.** Report at `{reportFile}`. Findings routing: {campaign.health_findings_queue}. Chaining to health check..."
79
79
 
@@ -3,6 +3,7 @@ stateSchemaFile: 'assets/campaign-state-schema.json'
3
3
  stateFile: '{campaignWorkspacePath}/_campaign-state.yaml'
4
4
  backupFile: '{campaignWorkspacePath}/_campaign-state.yaml.bak'
5
5
  validateScript: 'scripts/campaign-validate-state.py'
6
+ statusScript: 'scripts/campaign-status.py'
6
7
  ---
7
8
 
8
9
  <!-- Config: communicate in {communication_language}. -->
@@ -24,7 +25,7 @@ Validate campaign state integrity, determine the resume point, and chain to the
24
25
 
25
26
  ### §1 — Read + Validate State (with `.bak` recovery)
26
27
 
27
- Load `{stateFile}`. If the file does not exist, HALT: "No campaign state found. Run `campaign` to start a new campaign."
28
+ Load `{stateFile}`. If the file does not exist, HALT (exit code 2, `invalid-input`): "No campaign state found. Run `campaign` to start a new campaign."
28
29
 
29
30
  Run `uv run {validateScript} --state-file {stateFile}`. If it succeeds (exit 0), proceed to §2.
30
31
 
@@ -43,7 +44,7 @@ Check if `{backupFile}` exists.
43
44
  **If `.bak` exists** (and §1 did not already recover from it):
44
45
 
45
46
  1. Run `uv run {validateScript} --state-file {backupFile}`. If invalid, warn: "Backup file fails validation — cannot use for recovery." Continue with the primary.
46
- 2. Compare primary vs backup. If `primary.campaign.last_updated < backup.campaign.last_updated` OR `primary.campaign.current_stage < backup.campaign.current_stage`, the primary looks behind the backup (possible crash during last write). Present a recovery choice:
47
+ 2. Compare primary vs backup deterministically: run `uv run {statusScript} --state-file {stateFile} --backup-file {backupFile}` and read `backup_comparison.primary_behind` (the script does the ISO-8601 timestamp and `current_stage` compares — do not order the timestamps by hand). If it is `true`, the primary looks behind the backup (possible crash during last write). Present a recovery choice:
47
48
  - `[R]ecover` — copy `{backupFile}` over `{stateFile}` and resume from the backup's state.
48
49
  - `[K]eep` — keep the primary as authoritative (the default).
49
50
 
@@ -56,10 +57,10 @@ Two paths based on whether `--from=<skill>` was provided in the invocation:
56
57
  **With `--from=<skill>`:**
57
58
 
58
59
  1. Find the named skill in `skills[]` by `name`.
59
- 2. If not found → HALT: "Unknown skill '{name}'. Known skills: {comma-separated list of all skill names from state}."
60
+ 2. If not found → HALT (exit code 2, `invalid-input`): "Unknown skill '{name}'. Known skills: {comma-separated list of all skill names from state}."
60
61
  3. If the skill's `status` is `"completed"`, `"failed"`, or `"skipped"`, the operator may have meant to re-run it (e.g. it passed with a low score) rather than skip past it. Present a choice:
61
62
  - `[R]e-run` — reset the named skill to `"pending"` and resume from its stage. (Read-only step caveat: this single status reset follows read-backup-modify-write — back up first.)
62
- - `[N]ext` — find the next skill in `dependency_graph.execution_order` after the named one whose `status` is `"pending"` or `"active"` and resume there. If none found → HALT: "All remaining skills are complete. Run `campaign` to start a new campaign."
63
+ - `[N]ext` — find the next skill in `dependency_graph.execution_order` after the named one whose `status` is `"pending"` or `"active"` and resume there. If none found → HALT (exit code 0, campaign already complete): "All remaining skills are complete. Run `campaign` to start a new campaign."
63
64
  - `[H]alt` — stop without resuming.
64
65
 
65
66
  In headless mode, default to `[N]ext` and log the auto-decision. Log the chosen action to the decision log.
@@ -94,20 +95,20 @@ Map the resolved stage number to its step file. Both §3 branches feed this tabl
94
95
  | 9 | step-10-export.md |
95
96
  | 10 | step-11-maintenance.md |
96
97
 
97
- Display a resume summary before chaining:
98
+ Derive the skill counts deterministically — `uv run {statusScript} --state-file {stateFile}` returns `completed`, `total`, and the per-status counts (`pending` / `active` / `failed` / `skipped`); do not hand-count `skills[]`. Display a resume summary before chaining:
98
99
 
99
100
  ```
100
101
  CAMPAIGN RESUME: {campaign.name}
101
102
 
102
103
  Resuming from: Stage {stage_number} — {stage_name}
103
104
  Target skill: {skill_name} (if --from was used, otherwise "auto-detected" or "N/A")
104
- Skills completed: {completed_count} / {total_count}
105
- Skills remaining: {pending_count} pending, {active_count} active, {failed_count} failed, {skipped_count} skipped
105
+ Skills completed: {completed} / {total}
106
+ Skills remaining: {pending} pending, {active} active, {failed} failed, {skipped} skipped
106
107
  Last updated: {campaign.last_updated}
107
108
  ```
108
109
 
109
110
  If `campaign.current_stage` is `10` and all skills have status `"completed"`, `"failed"`, or `"skipped"`:
110
- HALT: "Campaign has reached its final stage. All skills have been processed."
111
+ HALT (exit code 0, campaign already complete): "Campaign has reached its final stage. All skills have been processed."
111
112
 
112
113
  ## OUTPUT
113
114