bmad-module-skill-forge 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +10 -5
- package/docs/_data/pinned.yaml +2 -2
- package/docs/agents.md +11 -2
- package/docs/architecture.md +5 -4
- package/docs/bmad-synergy.md +30 -7
- package/docs/campaign.md +172 -0
- package/docs/examples.md +7 -3
- package/docs/forge-auto.md +90 -0
- package/docs/getting-started.md +9 -0
- package/docs/how-it-works.md +6 -4
- package/docs/index.md +4 -3
- package/docs/skill-model.md +1 -1
- package/docs/troubleshooting.md +17 -1
- package/docs/verifying-a-skill.md +2 -2
- package/docs/why-skf.md +1 -1
- package/docs/workflows.md +64 -24
- package/package.json +2 -2
- package/src/module-help.csv +2 -1
- package/src/shared/data/language-corpora.json +32 -0
- package/src/shared/references/pipeline-contracts.md +6 -3
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
- package/src/shared/scripts/skf-detect-docs.py +8 -3
- package/src/shared/scripts/skf-detect-language.py +5 -3
- package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
- package/src/shared/scripts/skf-extract-public-api.py +53 -0
- package/src/shared/scripts/skf-language-corpora.py +100 -0
- package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-shape-detect.py +576 -25
- package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
- package/src/shared/scripts/skf-validate-pins.py +2 -2
- package/src/shared/scripts/skf-write-skill-brief.py +21 -5
- package/src/skf-analyze-source/SKILL.md +1 -1
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- package/src/skf-analyze-source/references/step-auto-scope.md +196 -44
- package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
- package/src/skf-audit-skill/references/init.md +1 -1
- package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
- package/src/skf-brief-skill/references/analyze-target.md +7 -4
- package/src/skf-brief-skill/references/confirm-brief.md +0 -1
- package/src/skf-brief-skill/references/gather-intent.md +8 -2
- package/src/skf-brief-skill/references/scope-definition.md +2 -1
- package/src/skf-brief-skill/references/step-auto-brief.md +21 -4
- package/src/skf-brief-skill/references/step-auto-validate.md +2 -2
- package/src/skf-brief-skill/references/write-brief.md +2 -3
- package/src/skf-campaign/SKILL.md +190 -0
- package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
- package/src/skf-campaign/customize.toml +73 -0
- package/src/skf-campaign/manifest.yaml +11 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
- package/src/skf-campaign/references/health-check.md +35 -0
- package/src/skf-campaign/references/step-01-setup.md +122 -0
- package/src/skf-campaign/references/step-02-strategy.md +97 -0
- package/src/skf-campaign/references/step-03-pins.md +56 -0
- package/src/skf-campaign/references/step-04-provenance.md +63 -0
- package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
- package/src/skf-campaign/references/step-06-batch.md +87 -0
- package/src/skf-campaign/references/step-07-capstone.md +63 -0
- package/src/skf-campaign/references/step-08-verify.md +75 -0
- package/src/skf-campaign/references/step-09-refine.md +83 -0
- package/src/skf-campaign/references/step-10-export.md +106 -0
- package/src/skf-campaign/references/step-11-maintenance.md +84 -0
- package/src/skf-campaign/references/step-resume.md +114 -0
- package/src/skf-campaign/scripts/.gitkeep +0 -0
- package/src/skf-campaign/scripts/campaign-deps.py +235 -0
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
- package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
- package/src/skf-campaign/scripts/campaign-report.py +249 -0
- package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
- package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
- package/src/skf-campaign/templates/campaign-report-template.md +54 -0
- package/src/skf-campaign/templates/kickoff-template.md +48 -0
- package/src/skf-create-skill/SKILL.md +1 -1
- package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
- package/src/skf-create-skill/references/compile.md +4 -1
- package/src/skf-create-skill/references/extract.md +9 -1
- package/src/skf-create-skill/references/extraction-patterns.md +3 -1
- package/src/skf-create-skill/references/generate-artifacts.md +11 -2
- package/src/skf-create-skill/references/health-check.md +2 -2
- package/src/skf-create-skill/references/report.md +17 -1
- package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
- package/src/skf-create-skill/references/step-doc-rot.md +4 -4
- package/src/skf-create-skill/references/step-doc-sources.md +11 -2
- package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
- package/src/skf-create-skill/references/validate.md +12 -3
- package/src/skf-drop-skill/SKILL.md +2 -2
- package/src/skf-drop-skill/references/execute.md +20 -9
- package/src/skf-drop-skill/references/report.md +2 -0
- package/src/skf-drop-skill/references/select.md +7 -2
- package/src/skf-forger/SKILL.md +12 -7
- package/src/skf-refine-architecture/SKILL.md +2 -1
- package/src/skf-refine-architecture/references/compile.md +1 -1
- package/src/skf-refine-architecture/references/report.md +1 -1
- package/src/skf-rename-skill/SKILL.md +2 -2
- package/src/skf-rename-skill/references/execute.md +5 -4
- package/src/skf-rename-skill/references/rebuild-context.md +2 -2
- package/src/skf-rename-skill/references/select.md +3 -3
- package/src/skf-setup/SKILL.md +1 -1
- package/src/skf-setup/references/auto-index.md +3 -1
- package/src/skf-setup/references/detect-and-tier.md +4 -0
- package/src/skf-setup/references/report.md +4 -1
- package/src/skf-setup/references/tier-rules.md +1 -1
- package/src/skf-test-skill/references/coverage-check.md +10 -0
- package/src/skf-test-skill/references/init.md +1 -1
- package/src/skf-test-skill/references/source-access-protocol.md +13 -3
- package/src/skf-test-skill/scripts/compute-score.py +4 -3
- package/src/skf-update-skill/customize.toml +0 -9
- package/src/skf-update-skill/references/detect-changes.md +33 -3
- package/src/skf-update-skill/references/health-check.md +2 -2
- package/src/skf-update-skill/references/init.md +1 -0
- package/src/skf-update-skill/references/re-extract.md +15 -1
- package/src/skf-verify-stack/references/report.md +1 -1
- package/tools/cli/lib/ui.js +3 -2
- package/docs/deepwiki.md +0 -89
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
| Field | Type | Constraint | Description |
|
|
20
20
|
|--------------------|--------|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
21
21
|
| source_type | string | `source` or `docs-only` | Default `source`. When `docs-only`: `source_repo` optional, `doc_urls` required |
|
|
22
|
-
| doc_urls | array | `{url, label}` objects
|
|
22
|
+
| doc_urls | array | `{url, label, source?}` objects | Documentation URLs for T3 content. Required when `source_type: "docs-only"`. Optional `source` provenance enum: `language-registry` (registry-guaranteed corpus) \| `readme-detection` \| `homepage` \| `pages-api` \| `docs-folder` |
|
|
23
23
|
| `scripts_intent` | string | `detect` / `none` / free-text | Describes whether scripts should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip scripts), or a free-text description of expected scripts (e.g., "CLI validation tools in bin/"). |
|
|
24
24
|
| `assets_intent` | string | `detect` / `none` / free-text | Describes whether assets should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip assets), or a free-text description of expected assets (e.g., "JSON schemas in schemas/"). |
|
|
25
25
|
| `target_version` | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | User-specified target version. When present, overrides auto-detection and becomes the skill's version. Recommended for docs-only skills where auto-detection is unavailable. |
|
|
@@ -10,6 +10,9 @@ detectWorkspacesProbeOrder:
|
|
|
10
10
|
detectLanguageProbeOrder:
|
|
11
11
|
- '{project-root}/_bmad/skf/shared/scripts/skf-detect-language.py'
|
|
12
12
|
- '{project-root}/src/shared/scripts/skf-detect-language.py'
|
|
13
|
+
emitBriefEnvelopeProbeOrder:
|
|
14
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
15
|
+
- '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
13
16
|
---
|
|
14
17
|
|
|
15
18
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -56,10 +59,10 @@ To analyze the target repository by resolving its location, reading its structur
|
|
|
56
59
|
|
|
57
60
|
**On API failure (non-200 from `gh api`):**
|
|
58
61
|
|
|
59
|
-
Distinguish the failure class before reporting:
|
|
60
|
-
- Auto-run `gh auth status` and capture its output. If it reports an unauthenticated state or expired token: HALT (exit code 3, `halt_reason: "gh-auth-failed"`) — "**Error:** GitHub CLI is not authenticated. `gh auth status` says: `{captured output}`. Run `gh auth login` and retry."
|
|
61
|
-
- If `gh auth status` reports authenticated but the call still failed (404/403): HALT (exit code 3, `halt_reason: "target-inaccessible"`) — "**Error:** Cannot access repository at `{url}`. The CLI is authenticated but the API returned `{status}`. Check the URL and that the account has access to private repositories if applicable."
|
|
62
|
-
- If `gh auth status` itself fails to run (binary missing): HALT (exit code 3, `halt_reason: "gh-auth-failed"`) — "**Error:** `gh` CLI not found on PATH. Install it from <https://cli.github.com> and re-run."
|
|
62
|
+
Distinguish the failure class before reporting. In headless mode, every branch below emits the error envelope per **step 5 §4b** with its stated `halt_reason` before the HALT (pass the resolved `{skill_name}`, or the `"unknown"` placeholder documented in §4b if it is not yet set):
|
|
63
|
+
- Auto-run `gh auth status` and capture its output. If it reports an unauthenticated state or expired token: emit the error envelope per **step 5 §4b** with `halt_reason: "gh-auth-failed"`, then HALT (exit code 3, `halt_reason: "gh-auth-failed"`) — "**Error:** GitHub CLI is not authenticated. `gh auth status` says: `{captured output}`. Run `gh auth login` and retry."
|
|
64
|
+
- If `gh auth status` reports authenticated but the call still failed (404/403): emit the error envelope per **step 5 §4b** with `halt_reason: "target-inaccessible"`, then HALT (exit code 3, `halt_reason: "target-inaccessible"`) — "**Error:** Cannot access repository at `{url}`. The CLI is authenticated but the API returned `{status}`. Check the URL and that the account has access to private repositories if applicable."
|
|
65
|
+
- If `gh auth status` itself fails to run (binary missing): emit the error envelope per **step 5 §4b** with `halt_reason: "gh-auth-failed"`, then HALT (exit code 3, `halt_reason: "gh-auth-failed"`) — "**Error:** `gh` CLI not found on PATH. Install it from <https://cli.github.com> and re-run."
|
|
63
66
|
|
|
64
67
|
**For local paths:**
|
|
65
68
|
- Verify the directory exists
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
nextStepFile: 'analyze-target.md'
|
|
3
3
|
ratifyTargetFile: 'confirm-brief.md'
|
|
4
4
|
forgeTierFile: '{sidecar_path}/forge-tier.yaml'
|
|
5
|
-
descriptionVoiceExamplesFile: 'assets/description-voice-examples.md'
|
|
6
5
|
headlessArgsFile: 'references/headless-args.md'
|
|
7
6
|
headlessSourceAuthorityDetectionFile: 'references/headless-source-authority-detection.md'
|
|
8
7
|
portfolioSimilarityCheckFile: 'references/portfolio-similarity-check.md'
|
|
@@ -61,6 +60,7 @@ Attempt to load `{forgeTierFile}`:
|
|
|
61
60
|
|
|
62
61
|
**If not found:**
|
|
63
62
|
- "**Cannot proceed.** forge-tier.yaml not found at `{forgeTierFile}`. Run the **setup** workflow first to configure your forge tier (Quick/Forge/Forge+/Deep)."
|
|
63
|
+
- In headless mode, emit the error envelope per **step 5 §4b** with `halt_reason: "forge-tier-missing"` (`skill_name` is not yet resolved here — use the `"unknown"` placeholder convention documented in §4b).
|
|
64
64
|
- HALT (exit code 3, `halt_reason: "forge-tier-missing"`) — do not proceed.
|
|
65
65
|
|
|
66
66
|
### 1b. Auto Mode Check
|
|
@@ -106,8 +106,12 @@ This section has four sub-flows. Execute exactly one branch — 3.1a *or* 3.2 *o
|
|
|
106
106
|
|
|
107
107
|
#### 3.1 Collect target
|
|
108
108
|
|
|
109
|
+
**Open-floor opening.** Lead with an open invitation so an expert can state everything in one breath rather than being walked through seven discrete prompts — costs almost nothing token-wise and sharply improves the conversational feel of this, the most question-heavy mode. A first-timer who pastes only a bare URL still gets the full guided sequence below, unchanged.
|
|
110
|
+
|
|
109
111
|
"**What repository or documentation do you want to create a skill for?**
|
|
110
112
|
|
|
113
|
+
Tell me everything you have — the repo or docs, what you want to skill and why, any scope or version thoughts. Or just paste a URL and we'll go from there.
|
|
114
|
+
|
|
111
115
|
Provide one of:
|
|
112
116
|
- A **GitHub URL** (e.g., `https://github.com/org/repo`)
|
|
113
117
|
- A **local path** (e.g., `/path/to/project`)
|
|
@@ -118,7 +122,7 @@ Or type `cancel` / `exit` / `[X]` to leave without writing anything.
|
|
|
118
122
|
|
|
119
123
|
**Target:**"
|
|
120
124
|
|
|
121
|
-
Wait for user response.
|
|
125
|
+
Wait for user response. **Parse the response for any of the fields the later sections collect** — `target_version` (§3b), intent (§4), scope hints (§5), source authority (§3.3), a proposed name (§6) — and pre-fill every field the user covered, holding them in workflow context. Sections §3b/§4/§5/§6/§7b then **acknowledge a pre-filled field instead of re-asking** ("I noted you're targeting v4.0.0"), and prompt only for the gaps. An expert who stated it all collapses to the §3.1 target branch plus the §7b description confirmation; a bare URL falls through to the full sequence. Then branch on the response for the target itself:
|
|
122
126
|
|
|
123
127
|
- Empty input, `cancel`, `exit`, `[X]`, `q`, or `:q` → Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Cancellation here is non-destructive — no files have been written yet by step 1. Headless mode never reaches this branch (the GATE in §8 short-circuits the interactive sub-flows).
|
|
124
128
|
- Path that resolves to an existing `skill-brief.yaml` (file path ending in `skill-brief.yaml` that exists, OR a directory containing a `skill-brief.yaml`) → §3.1a
|
|
@@ -267,6 +271,8 @@ Take your time — the more context you share, the better the brief."
|
|
|
267
271
|
|
|
268
272
|
Wait for user response. Ask follow-up questions if intent is unclear.
|
|
269
273
|
|
|
274
|
+
**Capture, don't interrupt.** If the user volunteers an out-of-scope aside while answering — "the v3 API is totally different", "we're deprecating the auth module next quarter" — do not redirect the conversation to chase it. Silently note it as a candidate `scope.notes` line (carried forward into the brief's `scope.notes` at step 3) and continue the current prompt. These unprompted asides are often the most useful scoping signal; the cost of losing them when the conversation moves on is higher than the cost of one stored line.
|
|
275
|
+
|
|
270
276
|
### 5. Capture Scope Hints
|
|
271
277
|
|
|
272
278
|
If the user mentioned scope preferences in their intent response, acknowledge them:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'confirm-brief.md'
|
|
3
|
-
scopeTemplatesFile: 'assets/scope-templates.md'
|
|
4
3
|
recommendScopeTypeProbeOrder:
|
|
5
4
|
- '{project-root}/_bmad/skf/shared/scripts/skf-recommend-scope-type.py'
|
|
6
5
|
- '{project-root}/src/shared/scripts/skf-recommend-scope-type.py'
|
|
@@ -120,6 +119,8 @@ How broadly should this skill cover the library?
|
|
|
120
119
|
|
|
121
120
|
Press Enter to accept the recommendation, or pick a different letter."
|
|
122
121
|
|
|
122
|
+
**First-timer reassurance (interactive only, never-briefed user — the §4 first-timer rail fired in step 01).** Append one line so the harder scope-type call doesn't stall a first-timer: "The recommended type is almost always right — accept it and re-scope from step 4 if the analysis surprises you." Repeat users and headless skip this line.
|
|
123
|
+
|
|
123
124
|
Wait for user selection. Empty input or just Enter accepts the recommendation; any of the five letters overrides.
|
|
124
125
|
|
|
125
126
|
### 3. Define Boundaries Based on Selection
|
|
@@ -9,7 +9,12 @@ writeSkillBriefProbeOrder:
|
|
|
9
9
|
emitBriefEnvelopeProbeOrder:
|
|
10
10
|
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
11
11
|
- '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
12
|
-
|
|
12
|
+
mergeDocUrlsProbeOrder:
|
|
13
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-merge-doc-urls.py'
|
|
14
|
+
- '{project-root}/src/shared/scripts/skf-merge-doc-urls.py'
|
|
15
|
+
detectDocsProbeOrder:
|
|
16
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
|
|
17
|
+
- '{project-root}/src/shared/scripts/skf-detect-docs.py'
|
|
13
18
|
---
|
|
14
19
|
|
|
15
20
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -69,10 +74,12 @@ Extract from the parsed brief:
|
|
|
69
74
|
|
|
70
75
|
### 2. Run Doc Detection
|
|
71
76
|
|
|
77
|
+
**Resolve `{detectDocsHelper}`** from `{detectDocsProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
78
|
+
|
|
72
79
|
Invoke doc detection to discover documentation URLs for the source repo:
|
|
73
80
|
|
|
74
81
|
```bash
|
|
75
|
-
uv run
|
|
82
|
+
uv run {detectDocsHelper} --repo-url {source_repo}
|
|
76
83
|
```
|
|
77
84
|
|
|
78
85
|
`--repo-url` is always required (the script uses it for GitHub API calls). If a local clone is also available at `{local_clone_path}`, add `--local-path {local_clone_path}` to enable docs-folder scanning in addition to API-based detection.
|
|
@@ -97,8 +104,18 @@ For each detected doc entry, create a brief `doc_urls` entry:
|
|
|
97
104
|
- `"readme_link"` → `"README Link"`
|
|
98
105
|
- `"pages_api"` → `"GitHub Pages"`
|
|
99
106
|
- `"docs_folder"` → `"Docs Folder"`
|
|
107
|
+
- `source` ← coarse provenance derived from `detected_via` (per the `skill-brief.v1.json` `doc_urls[].source` enum): `homepageUrl` → `homepage`, `readme_link` → `readme-detection`, `pages_api` → `pages-api`, `docs_folder` → `docs-folder`. This marks the entry as opportunistically detected, distinct from a registry-guaranteed corpus.
|
|
108
|
+
|
|
109
|
+
**Merge via the canonical helper.** Resolve `{mergeDocUrlsHelper}` from `{mergeDocUrlsProbeOrder}` (first existing path wins; HALT if neither exists). Pass the upstream brief's `doc_urls` as `existing` and the entries just mapped above as `detected`:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
echo '{"scope_type": "{scope_type}", "existing": {upstream brief doc_urls JSON, [] if none}, "detected": {mapped detected entries JSON}}' \
|
|
113
|
+
| uv run {mergeDocUrlsHelper}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The helper returns `{"doc_urls": [...], "suppressed": [...]}`. It deduplicates by **normalized** URL (lowercase host, strip a trailing `/index.html` and any trailing `/`), so a seeded `…/book/` and a README's `…/book/index.html` collapse to one entry; existing/corpora-seeded entries always win and keep their `source: language-registry`, so the registry-vs-detected distinction survives the merge. For a **whole-language reference** (`scope_type == "full-library"` AND ≥1 `existing` entry has `source: language-registry`) it additionally suppresses README noise on a corpus host: non-corpus path segments (`/whatsnew/`, `/contribute`, `/wiki/`) and non-primary-locale duplicates of a kept page (`/ja/master/` when `/en/master/` is kept). Ordinary skills (any other `scope_type`, or no registry corpora) pass through with dedup only — no suppression. Use the returned `doc_urls` as the brief's merged list.
|
|
100
117
|
|
|
101
|
-
|
|
118
|
+
**Log suppressed entries.** When `suppressed` is non-empty, log one line per entry — `"info: suppressed {url} ({reason})"` — so the operator can see what the whole-language noise filter dropped (never drop silently). The N==0 DEGRADED case (a whole-language repo whose registry returned no corpora) carries no `language-registry` entry, so suppression stays inactive and its README docs are kept — this is intentional (there is no canonical corpus host to filter against).
|
|
102
119
|
|
|
103
120
|
### 4. Validate Enriched Brief
|
|
104
121
|
|
|
@@ -123,7 +140,7 @@ Assemble the enriched brief context as a flat JSON object following the write-br
|
|
|
123
140
|
"scope_rationale": null,
|
|
124
141
|
"scope_tier_a_include": null,
|
|
125
142
|
"scope_amendments": null,
|
|
126
|
-
"doc_urls": [{"url": "...", "label": "..."}],
|
|
143
|
+
"doc_urls": [{"url": "...", "label": "...", "source": "..."}],
|
|
127
144
|
"scripts_intent": "{scripts_intent or null}",
|
|
128
145
|
"assets_intent": "{assets_intent or null}",
|
|
129
146
|
"source_authority": "{source_authority or null}",
|
|
@@ -75,11 +75,11 @@ Scope: {scope_type} ({N} include, {M} exclude patterns)
|
|
|
75
75
|
Docs: {doc_urls count} sources detected | "None detected"
|
|
76
76
|
Version: {version}
|
|
77
77
|
Forge Tier: {forge_tier}
|
|
78
|
-
|
|
78
|
+
Pipeline: forge-auto ({forge_tier} tier)
|
|
79
79
|
Description: "{description}"
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
Where `{N}` is the count of `scope_include` patterns and `{M}` is the count of `scope_exclude` patterns. If `doc_urls` is null or empty, display "None detected".
|
|
82
|
+
Where `{N}` is the count of `scope_include` patterns and `{M}` is the count of `scope_exclude` patterns. If `doc_urls` is null or empty, display "None detected". The `Pipeline` line names the auto pipeline and the resolved `{forge_tier}` — it carries no numeric quality target, which would be an unverified guarantee an automator might parse as fact.
|
|
83
83
|
|
|
84
84
|
### 3. Validation Gate
|
|
85
85
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
---
|
|
2
|
-
briefSchemaFile: 'assets/skill-brief-schema.md'
|
|
3
2
|
versionResolutionFile: 'references/version-resolution.md'
|
|
4
3
|
qmdRegistrationFile: 'references/qmd-collection-registration.md'
|
|
5
4
|
nextStepFile: 'health-check.md'
|
|
@@ -98,7 +97,7 @@ Assemble the brief context as a **flat** JSON object — every approved value is
|
|
|
98
97
|
"scope_rationale": null | {"recommended":"...","chosen":"...","accepted_recommendation":true|false,"heuristic":"...","reason":"...","recorded":"YYYY-MM-DD"},
|
|
99
98
|
"scope_tier_a_include": null | ["{tier-A authoring-surface patterns — from step 03 §3c capture, or hydrated on a ratify run}"],
|
|
100
99
|
"scope_amendments": null | [{"path":"...","action":"...","reason":"...","date":"YYYY-MM-DD","workflow":"..."}],
|
|
101
|
-
"doc_urls": null | [{"url": "...", "label": "..."}],
|
|
100
|
+
"doc_urls": null | [{"url": "...", "label": "...", "source": "{optional: language-registry|readme-detection|homepage|pages-api|docs-folder}"}],
|
|
102
101
|
"scripts_intent": null | "{detect|none|free-text}",
|
|
103
102
|
"assets_intent": null | "{detect|none|free-text}",
|
|
104
103
|
"source_authority": null | "{official|community|internal}",
|
|
@@ -163,7 +162,7 @@ The script derives `exit_code` deterministically from `halt_reason` (null→0, i
|
|
|
163
162
|
|
|
164
163
|
The script enforces the success/error halt_reason invariant (success requires null halt_reason; error requires non-null). The `user-cancelled` halt_reason is accepted for completeness (interactive `[X]` Cancel sites in step 1/03/04) but never appears on the headless code path.
|
|
165
164
|
|
|
166
|
-
Invocation sites (each pointed at this block, not duplicated): step 1 §1 (write-failed pre-resolution), step 1 §8 (input-missing/input-invalid GATE), step 5 §2b (overwrite-cancelled), step 5 §3 (input-invalid/write-failed from script).
|
|
165
|
+
Invocation sites (each pointed at this block, not duplicated): step 1 §1 (write-failed pre-resolution; forge-tier-missing), step 1 §8 (input-missing/input-invalid GATE), step 2 §1 (target-inaccessible/gh-auth-failed), step 5 §2b (overwrite-cancelled), step 5 §3 (input-invalid/write-failed from script). The step 1 §1 forge-tier-missing and step 2 §1 target-inaccessible/gh-auth-failed sites emit through this block too, so every headless HALT class surfaces a `SKF_BRIEF_RESULT_JSON` envelope — there are no envelope-silent failure classes.
|
|
167
166
|
|
|
168
167
|
When `{headless_mode}` is false, skip this section silently — no envelope is emitted.
|
|
169
168
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skf-campaign
|
|
3
|
+
description: Campaign orchestration — multi-library skill production with dependency tracking, file-based state, and resume. Use when the user asks to "run a campaign" or "orchestrate skills."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Campaign
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
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.
|
|
11
|
+
|
|
12
|
+
## Conventions
|
|
13
|
+
|
|
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.
|
|
16
|
+
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
|
|
17
|
+
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
18
|
+
- `{skill-name}` resolves to the skill directory's basename.
|
|
19
|
+
|
|
20
|
+
## Role
|
|
21
|
+
|
|
22
|
+
You are a campaign orchestrator operating in Ferris's Management mode. You sequence workflows, track per-skill state, enforce quality gates, and ensure every skill reaches its target tier — while the individual pipeline workflows handle the actual artifact production.
|
|
23
|
+
|
|
24
|
+
## On Activation
|
|
25
|
+
|
|
26
|
+
Run these steps once, in order, before dispatching to Mode Routing.
|
|
27
|
+
|
|
28
|
+
1. **Load config.** Read `{project-root}/_bmad/skf/config.yaml` and `{sidecar_path}/preferences.yaml` in one batched message (independent files). From config resolve `project_name`, `user_name`, `communication_language`, `document_output_language`, `skills_output_folder`, `forge_data_folder`, `sidecar_path`. From preferences resolve `headless_mode` (default false). If the config file is missing, fall back to `forge_data_folder = forge-data`.
|
|
29
|
+
|
|
30
|
+
2. **Resolve `{headless_mode}`** — true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `preferences.yaml`. Default: false.
|
|
31
|
+
|
|
32
|
+
3. **Resolve workflow customization.** Run:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The script merges three layers (scalars override, arrays append):
|
|
39
|
+
|
|
40
|
+
- `{skill-root}/customize.toml` — bundled defaults
|
|
41
|
+
- `_bmad/custom/<skill-name>.toml` under `{project-root}` — team overrides (committed)
|
|
42
|
+
- `_bmad/custom/<skill-name>.user.toml` under `{project-root}` — personal overrides (gitignored)
|
|
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:
|
|
45
|
+
|
|
46
|
+
- `{campaignWorkspacePath}` ← `workflow.campaign_workspace_path` if non-empty, else `{forge_data_folder}/_campaign`
|
|
47
|
+
- `{qualityGateHard}` / `{qualityGateSoftTarget}` / `{qualityGateSoftFallback}` ← the `quality_gate_*` scalars (defaults `zero-critical-high` / `90` / `80`)
|
|
48
|
+
- `{reportTemplatePath}` ← `workflow.report_template_path` if non-empty, else `templates/campaign-report-template.md`
|
|
49
|
+
- `{kickoffTemplatePath}` ← `workflow.kickoff_template_path` if non-empty, else `templates/kickoff-template.md`
|
|
50
|
+
- `{briefTemplatePath}` ← `workflow.brief_template_path` if non-empty, else `templates/campaign-brief-template.yaml`
|
|
51
|
+
- `{onComplete}` ← `workflow.on_complete` (empty = no-op)
|
|
52
|
+
|
|
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
|
+
|
|
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
|
+
4. **Parse CLI overrides** into the workflow context:
|
|
58
|
+
|
|
59
|
+
| Flag | Effect |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `--headless` / `-H` | Force `{headless_mode} = true` (see step 2). |
|
|
62
|
+
| `--brief <file>` | Seed step-01 targets from a `campaign-brief.yaml` instead of interactive prompts. Implies `--headless`. |
|
|
63
|
+
| `--manifest <file>` | Seed step-01 targets from a plain-text `name,repo_url,tier,pin` manifest. Implies `--headless`. |
|
|
64
|
+
| `--from <skill>` | Resume override — see Mode Routing. |
|
|
65
|
+
|
|
66
|
+
If `--brief` or `--manifest` is set, force `{headless_mode} = true` (log "headless: coerced by --brief/--manifest" if it was false).
|
|
67
|
+
|
|
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.
|
|
69
|
+
|
|
70
|
+
5. **Dispatch** per Mode Routing below.
|
|
71
|
+
|
|
72
|
+
## Workflow Rules
|
|
73
|
+
|
|
74
|
+
These rules apply to every step in this workflow:
|
|
75
|
+
|
|
76
|
+
- 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)
|
|
78
|
+
- 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
|
|
80
|
+
- 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.
|
|
83
|
+
- Always communicate in `{communication_language}`
|
|
84
|
+
- 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.
|
|
91
|
+
|
|
92
|
+
## Stages
|
|
93
|
+
|
|
94
|
+
| # | Step | File | Auto-proceed |
|
|
95
|
+
|---|------|------|--------------|
|
|
96
|
+
| 0 | Setup | references/step-01-setup.md | Yes |
|
|
97
|
+
| 1 | Strategy | references/step-02-strategy.md | Yes |
|
|
98
|
+
| 2 | Pin Validation | references/step-03-pins.md | Yes |
|
|
99
|
+
| 3 | Provenance | references/step-04-provenance.md | Yes |
|
|
100
|
+
| 4 | Skill Loop | references/step-05-skill-loop.md | Yes |
|
|
101
|
+
| 5 | Tier B Batch | references/step-06-batch.md | Yes |
|
|
102
|
+
| 6 | Capstone | references/step-07-capstone.md | Yes |
|
|
103
|
+
| 7 | Verification | references/step-08-verify.md | Yes |
|
|
104
|
+
| 8 | Refinement | references/step-09-refine.md | Yes |
|
|
105
|
+
| 9 | Export | references/step-10-export.md | No (write-gate HALT) |
|
|
106
|
+
| 10 | Maintenance | references/step-11-maintenance.md | Yes |
|
|
107
|
+
|
|
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.
|
|
109
|
+
|
|
110
|
+
## Invocation Contract
|
|
111
|
+
|
|
112
|
+
| Aspect | Detail |
|
|
113
|
+
|--------|--------|
|
|
114
|
+
| **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
|
+
| **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
|
+
|
|
139
|
+
## Result Contract on HARD HALT
|
|
140
|
+
|
|
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.
|
|
148
|
+
|
|
149
|
+
## Mode Routing
|
|
150
|
+
|
|
151
|
+
On invocation:
|
|
152
|
+
|
|
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.
|
|
154
|
+
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.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "campaign-state",
|
|
4
|
+
"description": "Schema for _campaign-state.yaml — single source of truth for campaign orchestration state",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["campaign", "skills", "dependency_graph"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"campaign": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"required": ["name", "started_at", "last_updated", "current_stage", "quality_gate", "health_findings_queue"],
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"properties": {
|
|
14
|
+
"name": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"started_at": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"format": "date-time"
|
|
20
|
+
},
|
|
21
|
+
"last_updated": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"format": "date-time"
|
|
24
|
+
},
|
|
25
|
+
"current_stage": {
|
|
26
|
+
"type": "integer",
|
|
27
|
+
"minimum": 0,
|
|
28
|
+
"maximum": 10
|
|
29
|
+
},
|
|
30
|
+
"directive_path": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
"quality_gate": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"required": ["hard", "soft_target", "soft_fallback"],
|
|
36
|
+
"additionalProperties": false,
|
|
37
|
+
"properties": {
|
|
38
|
+
"hard": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"soft_target": {
|
|
42
|
+
"type": "number"
|
|
43
|
+
},
|
|
44
|
+
"soft_fallback": {
|
|
45
|
+
"type": "number"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"health_findings_queue": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"enum": ["local", "improvement"]
|
|
52
|
+
},
|
|
53
|
+
"architecture_doc_path": {
|
|
54
|
+
"type": ["string", "null"],
|
|
55
|
+
"description": "Path to the architecture document consumed by the verify (Stage 7) and refine (Stage 8) stages. Sourced at setup; null when the campaign runs no verification."
|
|
56
|
+
},
|
|
57
|
+
"capstone": {
|
|
58
|
+
"type": ["object", "null"],
|
|
59
|
+
"additionalProperties": false,
|
|
60
|
+
"description": "Capstone (stack) outcome composed from all completed campaign skills (Tier A and Tier B). Campaign-level summary; the composed skill itself lives at skill_path.",
|
|
61
|
+
"properties": {
|
|
62
|
+
"skill_path": {
|
|
63
|
+
"type": ["string", "null"]
|
|
64
|
+
},
|
|
65
|
+
"quality_score": {
|
|
66
|
+
"type": ["number", "null"]
|
|
67
|
+
},
|
|
68
|
+
"verified": {
|
|
69
|
+
"type": ["boolean", "null"]
|
|
70
|
+
},
|
|
71
|
+
"completed_at": {
|
|
72
|
+
"type": ["string", "null"],
|
|
73
|
+
"format": "date-time"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"verification": {
|
|
78
|
+
"type": ["object", "null"],
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"description": "Stage 7 verify-stack summary. Detailed findings live in the external report at report_path.",
|
|
81
|
+
"properties": {
|
|
82
|
+
"report_path": {
|
|
83
|
+
"type": ["string", "null"]
|
|
84
|
+
},
|
|
85
|
+
"overall_verdict": {
|
|
86
|
+
"type": ["string", "null"],
|
|
87
|
+
"enum": ["Verified", "Plausible", "Risky", "Blocked", null]
|
|
88
|
+
},
|
|
89
|
+
"coverage_percentage": {
|
|
90
|
+
"type": ["number", "null"]
|
|
91
|
+
},
|
|
92
|
+
"recommendation_count": {
|
|
93
|
+
"type": ["integer", "null"]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"refinement": {
|
|
98
|
+
"type": ["object", "null"],
|
|
99
|
+
"additionalProperties": false,
|
|
100
|
+
"description": "Stage 8 refine-architecture summary. The refined document lives at refined_path.",
|
|
101
|
+
"properties": {
|
|
102
|
+
"refined_path": {
|
|
103
|
+
"type": ["string", "null"]
|
|
104
|
+
},
|
|
105
|
+
"gap_count": {
|
|
106
|
+
"type": ["integer", "null"]
|
|
107
|
+
},
|
|
108
|
+
"issue_count": {
|
|
109
|
+
"type": ["integer", "null"]
|
|
110
|
+
},
|
|
111
|
+
"improvement_count": {
|
|
112
|
+
"type": ["integer", "null"]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"skills": {
|
|
119
|
+
"type": "array",
|
|
120
|
+
"items": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"required": ["name", "status", "tier"],
|
|
123
|
+
"additionalProperties": false,
|
|
124
|
+
"properties": {
|
|
125
|
+
"name": {
|
|
126
|
+
"type": "string"
|
|
127
|
+
},
|
|
128
|
+
"status": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"enum": ["pending", "active", "completed", "failed", "skipped"]
|
|
131
|
+
},
|
|
132
|
+
"depends_on": {
|
|
133
|
+
"type": "array",
|
|
134
|
+
"items": {
|
|
135
|
+
"type": "string"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"tier": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"enum": ["A", "B"]
|
|
141
|
+
},
|
|
142
|
+
"pin": {
|
|
143
|
+
"type": ["string", "null"]
|
|
144
|
+
},
|
|
145
|
+
"brief_path": {
|
|
146
|
+
"type": ["string", "null"]
|
|
147
|
+
},
|
|
148
|
+
"skill_path": {
|
|
149
|
+
"type": ["string", "null"]
|
|
150
|
+
},
|
|
151
|
+
"quality_score": {
|
|
152
|
+
"type": ["number", "null"]
|
|
153
|
+
},
|
|
154
|
+
"workarounds_applied": {
|
|
155
|
+
"type": "array",
|
|
156
|
+
"items": {
|
|
157
|
+
"type": "string"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"started_at": {
|
|
161
|
+
"type": ["string", "null"],
|
|
162
|
+
"format": "date-time"
|
|
163
|
+
},
|
|
164
|
+
"completed_at": {
|
|
165
|
+
"type": ["string", "null"],
|
|
166
|
+
"format": "date-time"
|
|
167
|
+
},
|
|
168
|
+
"commit_sha": {
|
|
169
|
+
"type": ["string", "null"]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"dependency_graph": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"required": ["execution_order", "circular_deps_detected"],
|
|
177
|
+
"additionalProperties": false,
|
|
178
|
+
"properties": {
|
|
179
|
+
"execution_order": {
|
|
180
|
+
"type": "array",
|
|
181
|
+
"items": {
|
|
182
|
+
"type": "string"
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"circular_deps_detected": {
|
|
186
|
+
"type": "boolean"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|