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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.nvmrc +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +2 -2
- package/src/shared/scripts/skf-comention-pairs.py +350 -0
- package/src/shared/scripts/skf-count-tokens.py +222 -0
- package/src/shared/scripts/skf-detect-docs.py +232 -1
- package/src/shared/scripts/skf-detect-language.py +77 -0
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-find-cycles.py +246 -0
- package/src/shared/scripts/skf-hash-content.py +487 -2
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
- package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
- package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
- package/src/shared/scripts/skf-shard-body.py +461 -0
- package/src/shared/scripts/skf-skill-inventory.py +139 -3
- package/src/shared/scripts/skf-structural-diff.py +307 -67
- package/src/shared/scripts/skf-validate-brief-schema.py +39 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
- package/src/shared/scripts/skf-validate-output.py +443 -10
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/customize.toml +2 -0
- package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
- package/src/skf-analyze-source/references/continue.md +8 -13
- package/src/skf-analyze-source/references/generate-briefs.md +28 -12
- package/src/skf-analyze-source/references/headless-contract.md +33 -0
- package/src/skf-analyze-source/references/health-check.md +1 -2
- package/src/skf-analyze-source/references/identify-units.md +25 -36
- package/src/skf-analyze-source/references/init.md +12 -25
- package/src/skf-analyze-source/references/map-and-detect.md +8 -20
- package/src/skf-analyze-source/references/recommend.md +4 -14
- package/src/skf-analyze-source/references/scan-project.md +3 -11
- package/src/skf-analyze-source/references/step-auto-scope.md +42 -180
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
- package/src/skf-audit-skill/SKILL.md +9 -6
- package/src/skf-audit-skill/customize.toml +2 -0
- package/src/skf-audit-skill/references/init.md +58 -32
- package/src/skf-audit-skill/references/re-index.md +15 -34
- package/src/skf-audit-skill/references/report.md +11 -58
- package/src/skf-audit-skill/references/semantic-diff.md +4 -22
- package/src/skf-audit-skill/references/severity-classify.md +53 -87
- package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
- package/src/skf-audit-skill/references/structural-diff.md +43 -68
- package/src/skf-brief-skill/SKILL.md +7 -36
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +6 -14
- package/src/skf-brief-skill/references/confirm-brief.md +2 -14
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +19 -22
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/health-check.md +1 -22
- package/src/skf-brief-skill/references/invocation-contract.md +37 -0
- package/src/skf-brief-skill/references/scope-definition.md +4 -14
- package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
- package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +17 -10
- package/src/skf-campaign/SKILL.md +15 -84
- package/src/skf-campaign/customize.toml +2 -0
- package/src/skf-campaign/manifest.yaml +4 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
- package/src/skf-campaign/references/step-01-setup.md +4 -5
- package/src/skf-campaign/references/step-02-strategy.md +8 -3
- package/src/skf-campaign/references/step-03-pins.md +1 -1
- package/src/skf-campaign/references/step-04-provenance.md +1 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +7 -6
- package/src/skf-campaign/references/step-11-maintenance.md +2 -2
- package/src/skf-campaign/references/step-resume.md +9 -8
- package/src/skf-campaign/scripts/campaign-deps.py +10 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
- package/src/skf-create-skill/SKILL.md +14 -3
- package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
- package/src/skf-create-skill/assets/skill-sections.md +9 -100
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
- package/src/skf-create-skill/customize.toml +13 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
- package/src/skf-create-skill/references/compile.md +34 -47
- package/src/skf-create-skill/references/component-extraction.md +8 -35
- package/src/skf-create-skill/references/ecosystem-check.md +9 -25
- package/src/skf-create-skill/references/enrich.md +4 -17
- package/src/skf-create-skill/references/extract.md +14 -29
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +7 -7
- package/src/skf-create-skill/references/generate-artifacts.md +9 -23
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +12 -15
- package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +30 -12
- package/src/skf-create-skill/references/step-doc-sources.md +2 -2
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
- package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
- package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
- package/src/skf-create-skill/references/validate.md +59 -28
- package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
- package/src/skf-create-stack-skill/SKILL.md +15 -9
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +10 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
- package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
- package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
- package/src/skf-create-stack-skill/references/generate-output.md +24 -75
- package/src/skf-create-stack-skill/references/init.md +9 -81
- package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
- package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
- package/src/skf-create-stack-skill/references/report.md +27 -62
- package/src/skf-create-stack-skill/references/validate.md +35 -31
- package/src/skf-drop-skill/SKILL.md +20 -23
- package/src/skf-drop-skill/customize.toml +10 -0
- package/src/skf-drop-skill/references/execute.md +51 -31
- package/src/skf-drop-skill/references/headless-contract.md +18 -0
- package/src/skf-drop-skill/references/health-check.md +1 -13
- package/src/skf-drop-skill/references/report.md +42 -10
- package/src/skf-drop-skill/references/select.md +88 -48
- package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
- package/src/skf-export-skill/SKILL.md +17 -14
- package/src/skf-export-skill/assets/managed-section-format.md +2 -5
- package/src/skf-export-skill/customize.toml +10 -6
- package/src/skf-export-skill/references/generate-snippet.md +21 -35
- package/src/skf-export-skill/references/load-skill.md +44 -42
- package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
- package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
- package/src/skf-export-skill/references/package.md +27 -33
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
- package/src/skf-export-skill/references/result-envelope.md +28 -0
- package/src/skf-export-skill/references/summary.md +11 -5
- package/src/skf-export-skill/references/token-report.md +42 -24
- package/src/skf-export-skill/references/update-context.md +32 -33
- package/src/skf-forger/SKILL.md +27 -39
- package/src/skf-forger/references/pipeline-mode.md +34 -0
- package/src/skf-forger/scripts/parse-pipeline.py +270 -0
- package/src/skf-quick-skill/SKILL.md +10 -60
- package/src/skf-quick-skill/customize.toml +9 -0
- package/src/skf-quick-skill/references/batch-mode.md +6 -4
- package/src/skf-quick-skill/references/compile.md +15 -52
- package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
- package/src/skf-quick-skill/references/finalize.md +12 -6
- package/src/skf-quick-skill/references/halt-contract.md +54 -0
- package/src/skf-quick-skill/references/health-check.md +3 -4
- package/src/skf-quick-skill/references/quick-extract.md +7 -18
- package/src/skf-quick-skill/references/registry-resolution.md +1 -23
- package/src/skf-quick-skill/references/resolve-target.md +27 -34
- package/src/skf-quick-skill/references/write-and-validate.md +10 -25
- package/src/skf-refine-architecture/SKILL.md +14 -7
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +9 -7
- package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
- package/src/skf-refine-architecture/references/health-check.md +1 -1
- package/src/skf-refine-architecture/references/improvements.md +9 -25
- package/src/skf-refine-architecture/references/init.md +9 -16
- package/src/skf-refine-architecture/references/issue-detection.md +10 -25
- package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
- package/src/skf-refine-architecture/references/report.md +13 -10
- package/src/skf-rename-skill/SKILL.md +11 -21
- package/src/skf-rename-skill/customize.toml +10 -0
- package/src/skf-rename-skill/references/execute.md +107 -78
- package/src/skf-rename-skill/references/exit-codes.md +12 -0
- package/src/skf-rename-skill/references/report.md +16 -8
- package/src/skf-rename-skill/references/select.md +69 -42
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
- package/src/skf-setup/SKILL.md +20 -12
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +9 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +6 -8
- package/src/skf-setup/references/report.md +22 -31
- package/src/skf-setup/references/tier-rules.md +2 -32
- package/src/skf-setup/references/write-config.md +9 -10
- package/src/skf-test-skill/SKILL.md +10 -8
- package/src/skf-test-skill/customize.toml +2 -0
- package/src/skf-test-skill/references/coherence-check.md +19 -31
- package/src/skf-test-skill/references/coverage-check.md +109 -75
- package/src/skf-test-skill/references/detect-mode.md +10 -41
- package/src/skf-test-skill/references/external-validators.md +11 -19
- package/src/skf-test-skill/references/init.md +38 -20
- package/src/skf-test-skill/references/migration-section-rules.md +4 -4
- package/src/skf-test-skill/references/report.md +60 -40
- package/src/skf-test-skill/references/score.md +32 -64
- package/src/skf-test-skill/references/scoring-rules.md +6 -62
- package/src/skf-test-skill/references/source-access-protocol.md +6 -6
- package/src/skf-test-skill/references/step-hard-gate.md +1 -3
- package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
- package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
- package/src/skf-test-skill/scripts/compute-score.py +131 -1
- package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
- package/src/skf-update-skill/SKILL.md +17 -5
- package/src/skf-update-skill/customize.toml +14 -0
- package/src/skf-update-skill/references/detect-changes.md +19 -37
- package/src/skf-update-skill/references/health-check.md +3 -9
- package/src/skf-update-skill/references/init.md +39 -32
- package/src/skf-update-skill/references/manual-section-rules.md +1 -2
- package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
- package/src/skf-update-skill/references/merge.md +13 -57
- package/src/skf-update-skill/references/re-extract.md +12 -24
- package/src/skf-update-skill/references/report.md +14 -7
- package/src/skf-update-skill/references/validate.md +32 -30
- package/src/skf-update-skill/references/write.md +86 -51
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
- package/src/skf-verify-stack/SKILL.md +11 -21
- package/src/skf-verify-stack/customize.toml +12 -3
- package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
- package/src/skf-verify-stack/references/coverage.md +21 -10
- package/src/skf-verify-stack/references/exit-codes.md +14 -0
- package/src/skf-verify-stack/references/health-check.md +1 -1
- package/src/skf-verify-stack/references/init.md +20 -25
- package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
- package/src/skf-verify-stack/references/integrations.md +44 -49
- package/src/skf-verify-stack/references/report.md +28 -34
- package/src/skf-verify-stack/references/requirements.md +3 -3
- package/src/skf-verify-stack/references/synthesize.md +36 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
- 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
|
|
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
|
|
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.
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
82
|
-
- **Universal cancel affordance** — at any interactive gate the
|
|
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
|
|
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` (
|
|
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
|
-
##
|
|
110
|
+
## Contracts
|
|
140
111
|
|
|
141
|
-
|
|
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
|
|
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
|
|
156
|
-
4. **`campaign status`** (read-only) — load `{campaignWorkspacePath}/_campaign-state.yaml`, validate it via `campaign-validate-state.py`,
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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
|
|
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),
|
|
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
|
|
51
|
+
### §5 — Handle Unorderable Graph
|
|
52
52
|
|
|
53
|
-
If
|
|
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}`.
|
|
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
|
|
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
|
-
|
|
46
|
+
Generate the QS `--batch` input file deterministically:
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
```
|
|
49
|
+
uv run {batchScript} --state-file {stateFile} --brief-file {briefFile} -o {batchFile}
|
|
50
|
+
```
|
|
48
51
|
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
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).
|
|
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
|
|
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: {
|
|
105
|
-
Skills remaining: {
|
|
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
|
|