bmad-module-skill-forge 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +6 -4
  3. package/docs/_data/pinned.yaml +1 -1
  4. package/docs/bmad-synergy.md +2 -2
  5. package/docs/getting-started.md +1 -1
  6. package/docs/skill-model.md +26 -32
  7. package/docs/troubleshooting.md +13 -1
  8. package/docs/why-skf.md +5 -4
  9. package/docs/workflows.md +53 -0
  10. package/package.json +2 -2
  11. package/src/knowledge/ccc-bridge.md +1 -1
  12. package/src/shared/references/output-contract-schema.md +10 -0
  13. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +134 -0
  14. package/src/shared/scripts/skf-detect-tools.py +359 -0
  15. package/src/shared/scripts/skf-emit-result-envelope.py +419 -0
  16. package/src/shared/scripts/skf-extract-public-api.py +505 -0
  17. package/src/shared/scripts/skf-forge-tier-rw.py +413 -0
  18. package/src/shared/scripts/skf-merge-ccc-exclusions.py +324 -0
  19. package/src/shared/scripts/skf-preflight.py +14 -4
  20. package/src/shared/scripts/skf-qmd-classify-collections.py +212 -0
  21. package/src/shared/scripts/skf-render-quick-metadata.py +192 -0
  22. package/src/shared/scripts/skf-resolve-package.py +264 -0
  23. package/src/shared/scripts/skf-validate-frontmatter.py +9 -3
  24. package/src/shared/scripts/skf-validate-output.py +24 -7
  25. package/src/skf-create-skill/steps-c/step-06-validate.md +1 -1
  26. package/src/skf-quick-skill/SKILL.md +178 -10
  27. package/src/skf-quick-skill/assets/skill-template.md +5 -1
  28. package/src/skf-quick-skill/references/registry-resolution.md +2 -0
  29. package/src/skf-quick-skill/steps-c/step-01-resolve-target.md +84 -16
  30. package/src/skf-quick-skill/steps-c/step-02-ecosystem-check.md +3 -3
  31. package/src/skf-quick-skill/steps-c/step-03-quick-extract.md +86 -43
  32. package/src/skf-quick-skill/steps-c/step-04-compile.md +49 -56
  33. package/src/skf-quick-skill/steps-c/step-05-write-and-validate.md +164 -0
  34. package/src/skf-quick-skill/steps-c/{step-06-write.md → step-06-finalize.md} +15 -7
  35. package/src/skf-quick-skill/steps-c/step-07-health-check.md +5 -3
  36. package/src/skf-setup/SKILL.md +25 -10
  37. package/src/skf-setup/references/tier-rules.md +2 -2
  38. package/src/skf-setup/steps-c/step-01-detect-and-tier.md +58 -71
  39. package/src/skf-setup/steps-c/step-01b-ccc-index.md +49 -66
  40. package/src/skf-setup/steps-c/step-02-write-config.md +59 -86
  41. package/src/skf-setup/steps-c/step-03-auto-index.md +73 -91
  42. package/src/skf-setup/steps-c/step-04-report.md +135 -11
  43. package/src/skf-setup/steps-c/step-05-health-check.md +4 -2
  44. package/src/skf-test-skill/steps-c/step-01-init.md +4 -4
  45. package/src/skf-quick-skill/steps-c/step-05-validate.md +0 -193
@@ -1,73 +1,89 @@
1
1
  ---
2
2
  nextStepFile: './step-04-report.md'
3
+ # Resolve `{qmdClassifyHelper}` and `{forgeTierRwHelper}` by probing the
4
+ # corresponding `*ProbeOrder` arrays (installed SKF module path first, src/
5
+ # dev-checkout fallback); first existing path wins. HALT if neither resolves
6
+ # for the helper a section actually invokes — both scripts own classification
7
+ # / registry-cleanup contracts that have no prose fallback.
8
+ qmdClassifyProbeOrder:
9
+ - '{project-root}/_bmad/skf/shared/scripts/skf-qmd-classify-collections.py'
10
+ - '{project-root}/src/shared/scripts/skf-qmd-classify-collections.py'
11
+ forgeTierRwProbeOrder:
12
+ - '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
13
+ - '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
3
14
  ---
4
15
 
5
- # Step 3: QMD Collection Hygiene
16
+ <!-- Config: communicate in {communication_language}. The orphan-removal prompt and the headless-resolution log message render in the user's language. -->
17
+
18
+ # Step 3: QMD + CCC Registry Hygiene
6
19
 
7
20
  ## STEP GOAL:
8
21
 
9
- If the detected tier is Deep, verify the health of existing QMD collections by cross-referencing them against the `qmd_collections` registry in `forge-tier.yaml`. Identify orphaned collections (in QMD but not in registry) and stale registry entries (in registry but collection missing from QMD). Prompt the user before removing orphaned collections.
22
+ When the detected tier is Deep, classify live QMD collections against the `qmd_collections` registry and prompt the user before removing orphans. Whenever ccc is available (Forge+ or Deep), prune `ccc_index_registry` entries whose source paths no longer exist. All set-arithmetic and YAML mutation goes through scripts (`{qmdClassifyHelper}` and `{forgeTierRwHelper}`); the workflow only orchestrates external CLI calls (`qmd collection list`, `qmd collection remove`) and the user-prompt branch.
10
23
 
11
- For Quick and Forge tiers, skip silently and proceed (QMD is not available at those tiers). For Forge+ tier, skip QMD hygiene but the step routes correctly to the next step.
24
+ For Quick and Forge tiers, skip silently and proceed (QMD is not available; ccc registry cleanup only runs when ccc is available regardless of tier).
12
25
 
13
26
  ## Rules
14
27
 
15
- - Focus only on verifying and cleaning QMD collections (Deep tier) or graceful skip (other tiers)
16
- - Do not display negative framing for non-Deep tiers
17
- - Do not fail the workflow if QMD hygiene encounters errors
18
- - Do not create new QMD collections that belongs to create-skill
19
- - Do not silently delete collections always prompt user before removal
28
+ - Focus only on registry hygiene no new collection creation (that belongs to create-skill)
29
+ - Never reimplement the forge-namespace suffix filter in prose — the classifier owns it
30
+ - Never silently delete collections always prompt before `qmd collection remove`
31
+ - Headless runs must auto-resolve the orphan prompt to the documented default (Keep)
32
+ - Do not fail the workflow if hygiene encounters errors
20
33
 
21
34
  ## MANDATORY SEQUENCE
22
35
 
23
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
24
-
25
36
  ### 1. Check Tier
26
37
 
27
- Read `{calculated_tier}` from context.
38
+ Read `{calculated_tier}` and `{ccc}` from context (set by step-01).
28
39
 
29
- **If tier is Quick or Forge:** Set `{ccc_registry_stale_cleaned: 0}`. Proceed directly to section 6 (Auto-Proceed) — no output, no messaging.
40
+ **If `{calculated_tier}` is Quick or Forge AND `{ccc}` is false:** No registry hygiene needed. Set `{hygiene_result: "skipped", hygiene_healthy: 0, hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0, hygiene_stale_cleaned: 0, ccc_registry_stale_cleaned: 0, ccc_registry_stale_removed_paths: []}`. Proceed directly to section 5 (Auto-Proceed) — no output, no messaging.
30
41
 
31
- **If tier is Forge+:** Skip QMD hygiene (qmd is not available at Forge+). Proceed directly to section 5b (CCC Index Registry Hygiene) no QMD output, no messaging.
42
+ **If `{calculated_tier}` is Quick or Forge AND `{ccc}` is true:** No QMD work, but ccc registry needs pruning. Set QMD-related flags to defaults (`hygiene_result: "skipped"`, all hygiene_* counts = 0). Skip directly to section 4 (Stale Registry Cleanup), running it with the ccc-prune flag only.
32
43
 
33
- **If tier IS Deep:** Continue to section 2.
44
+ **If `{calculated_tier}` is Forge+:** Same as Quick/Forge with ccc — no QMD work (qmd unavailable at Forge+), but ccc registry hygiene runs.
34
45
 
35
- ### 2. Load Registry and QMD State
46
+ **If `{calculated_tier}` IS Deep:** Continue to section 2.
36
47
 
37
- Read the `qmd_collections` array from `{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml`.
48
+ ### 2. Classify Live QMD Collections vs Registry
38
49
 
39
50
  List live QMD collections:
51
+
40
52
  ```bash
41
53
  qmd collection list
42
54
  ```
43
55
 
44
- **Store both lists for cross-reference:**
45
- - `{registry_collections}` — entries from forge-tier.yaml qmd_collections
46
- - `{live_collections}` collections currently in QMD
56
+ Parse the output into a comma-separated string of collection names and store as `{live_collections}` (raw — including any foreign collections owned by other tools sharing the QMD daemon; the classifier filters them out).
57
+
58
+ **Error handling:** If `qmd collection list` fails (daemon down, daemon errors), set `{hygiene_result: "qmd_unavailable", hygiene_healthy: 0, hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0, hygiene_stale_cleaned: 0}`, log the error, and skip directly to section 4 (which will still run the ccc-prune branch if `{ccc}` is true).
47
59
 
48
- **Error handling:** If `qmd collection list` fails, log the error, store `{hygiene_result: "qmd_unavailable"}`, and proceed to section 6.
60
+ Run the classifier. Invoke via `uv run` so the script's PEP 723 PyYAML dependency resolves automatically (`docs/getting-started.md` documents uv as the runtime prereq for exactly this); bare `python3` would `ModuleNotFoundError` on a fresh interpreter.
61
+
62
+ ```bash
63
+ uv run {qmdClassifyHelper} \
64
+ --live-names "{live_collections}" \
65
+ --registry-from-yaml "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"
66
+ ```
49
67
 
50
- ### 3. Cross-Reference and Classify
68
+ The script (see `src/shared/scripts/skf-qmd-classify-collections.py` docstring for the full schema) applies the forge-namespace suffix filter (`-brief | -temporal | -docs | -extraction`) to `{live_collections}` before classifying — collections owned by unrelated tools are silently excluded from the orphan / healthy / stale sets and counted under `foreign_filtered_count` for telemetry only. This is the PR #244 incident protection: collections like Hindsight memory banks that happen to live in the same QMD daemon never enter any classification that could lead to data loss.
51
69
 
52
- Compare the two lists:
70
+ **Parse the JSON output and set context flags:**
53
71
 
54
- **Healthy** collection exists in both registry AND QMD:
55
- - Mark as verified
56
- - No action needed
72
+ - `{hygiene_healthy}` `len(healthy)`
73
+ - `{orphaned_collections}` `orphaned` (the list — used in section 3)
74
+ - `{stale_collections}` `stale` (the list — used in section 4)
75
+ - `{foreign_filtered_count}` ← `foreign_filtered_count`
57
76
 
58
- **Orphaned** collection exists in QMD but NOT in registry:
59
- - These may be leftover from prior auto-indexing or manual indexing
60
- - Flag for user-prompted removal
77
+ Set `{hygiene_result: "completed"}`.
61
78
 
62
- **Stale** entry exists in registry but collection is missing from QMD:
63
- - The QMD collection was deleted or lost
64
- - Remove the stale entry from the registry
79
+ ### 3. Handle Orphaned Collections
65
80
 
66
- ### 4. Handle Orphaned Collections
81
+ **If `{orphaned_collections}` is empty:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0}` and skip to section 4.
67
82
 
68
- **If orphaned collections found:**
83
+ **Headless gate.** If `{headless_mode}` is true, auto-resolve to the default action **Keep** without prompting: log `"Auto-decision (headless): kept {len(orphaned_collections)} orphaned forge collection(s)"`, set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}`, and skip to section 4. This matches the workflow contract (`Headless: All gates auto-resolve with default action when {headless_mode} is true`) declared in the Invocation Contract.
84
+
85
+ **If `{headless_mode}` is false**, display to the user:
69
86
 
70
- Display to user:
71
87
  "**QMD Hygiene: Found {count} orphaned collection(s) not tracked in the forge registry:**
72
88
 
73
89
  {list orphaned collection names}
@@ -75,77 +91,43 @@ Display to user:
75
91
  These collections exist in QMD but are not managed by any skill workflow. They may be from a previous auto-index run or manual creation.
76
92
 
77
93
  **[R]emove** orphaned collections — clean up QMD
78
- **[K]eep** orphaned collections — leave them as-is"
94
+ **[K]eep** orphaned collections — leave them as-is (default)"
95
+
96
+ **If user selects R (Remove):** For each name in `{orphaned_collections}`:
79
97
 
80
- **If user selects R (Remove):**
81
- For each orphaned collection:
82
98
  ```bash
83
- qmd collection remove {collection_name}
99
+ qmd collection remove <name>
84
100
  ```
85
- Log each removal.
86
-
87
- **If user selects K (Keep):**
88
- Skip removal. Log that orphaned collections were kept.
89
-
90
- **If no orphaned collections:** Skip this section silently.
91
-
92
- ### 5. Handle Stale Registry Entries
93
-
94
- **If stale registry entries found:**
95
101
 
96
- Remove stale entries from the `qmd_collections` array in forge-tier.yaml.
102
+ Track the count of successful removals as `{hygiene_orphaned_removed}`. Set `{hygiene_orphaned_kept: 0}`.
97
103
 
98
- Display: "**Cleaned {count} stale registry entry/entries** (collection no longer exists in QMD)."
104
+ **If user selects K (Keep) or no orphans:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}`.
99
105
 
100
- Update forge-tier.yaml with the cleaned registry.
106
+ ### 4. Stale Registry Cleanup
101
107
 
102
- **If no stale entries:** Skip this section silently.
108
+ This section ALWAYS runs when reachable — it handles both `qmd_collections` stale entries (Deep tier) and `ccc_index_registry` stale entries (whenever ccc is true). The script's flags are mutually independent.
103
109
 
104
- ### 5b. CCC Index Registry Hygiene (Forge+ and Deep with ccc)
110
+ Build the invocation. Always include `--target` for the forge-tier.yaml path. Include `--qmd-live-names "{live_collections}"` ONLY when section 2 ran successfully (i.e. `{hygiene_result}` is `"completed"`); omit the flag entirely otherwise so the script skips QMD cleanup. Include `--prune-missing-ccc-paths` ONLY when `{ccc}` is true; omit it otherwise.
105
111
 
106
- **IF `tools.ccc` is true in forge-tier.yaml (regardless of whether QMD hygiene ran):**
107
-
108
- Read the `ccc_index_registry` array from forge-tier.yaml.
109
-
110
- For each entry, verify the indexed path still exists on disk:
111
-
112
- **Healthy** — path exists: no action needed.
113
-
114
- **Stale** — path does not exist (source directory removed or moved):
115
- - Remove the stale entry from `ccc_index_registry`
116
- - Log: "Removed stale CCC index registry entry: {path} (path no longer exists)"
117
-
118
- Update forge-tier.yaml with the cleaned registry.
119
-
120
- Store `{ccc_registry_stale_cleaned: count}` in context for step-04 reporting.
121
-
122
- **IF `tools.ccc` is false:** Skip this section silently.
123
-
124
- ### 6. Store Hygiene Results and Auto-Proceed
125
-
126
- Store in context for step-04 reporting:
127
- ```
128
- {hygiene_result: "completed"|"skipped"|"qmd_unavailable"}
129
- {hygiene_healthy: count}
130
- {hygiene_orphaned_removed: count}
131
- {hygiene_orphaned_kept: count}
132
- {hygiene_stale_cleaned: count}
133
- {ccc_registry_stale_cleaned: count}
112
+ ```bash
113
+ uv run {forgeTierRwHelper} clean-stale \
114
+ --target "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml" \
115
+ [--qmd-live-names "{live_collections}"] \
116
+ [--prune-missing-ccc-paths]
134
117
  ```
135
118
 
136
- "**Proceeding to forge status report...**"
137
-
138
- #### Menu Handling Logic:
119
+ The script reads the registry, computes set-difference operations (qmd: registry − live; ccc: filter where `path` does not exist on disk), and atomically rewrites forge-tier.yaml only when something actually changed (mtime preserved on idempotent re-runs). The CI ephemeral-mount caveat for ccc-registry pruning is logged in the script's WARNING message (per PR #248).
139
120
 
140
- - After hygiene completes (or is skipped), immediately load, read entire file, then execute {nextStepFile}
121
+ **Parse the JSON output and set context flags for step-04:**
141
122
 
142
- #### EXECUTION RULES:
123
+ - `{hygiene_stale_cleaned}` ← `len(qmd_removed)`
124
+ - `{ccc_registry_stale_cleaned}` ← `len(ccc_removed)`
125
+ - `{ccc_registry_stale_removed_paths}` ← `ccc_removed` (the list — step-04 folds individual paths into envelope warnings)
143
126
 
144
- - This step has one optional user interaction (orphan removal prompt)
145
- - If no orphans found, this is an auto-proceed step
146
- - Proceed directly to next step after hygiene or skip
127
+ If `{hygiene_stale_cleaned}` > 0, display: "**Cleaned {hygiene_stale_cleaned} stale QMD registry entry/entries** (collection no longer exists in QMD)."
147
128
 
148
- ## CRITICAL STEP COMPLETION NOTE
129
+ (The script already logs each ccc removal as a WARNING line; no additional display needed.)
149
130
 
150
- ONLY WHEN the hygiene check has been performed (or skipped for non-Deep tiers) will you load and read fully `{nextStepFile}` to execute the report step.
131
+ ### 5. Auto-Proceed
151
132
 
133
+ After hygiene completes (or is skipped for non-Deep tiers without ccc), display "**Proceeding to forge status report...**", then load `{nextStepFile}`, read it fully, and execute it.
@@ -1,31 +1,47 @@
1
1
  ---
2
2
  tierRulesData: 'references/tier-rules.md'
3
3
  nextStepFile: './step-05-health-check.md'
4
+ # Resolve `{emitEnvelopeHelper}` by probing `{emitEnvelopeProbeOrder}` in order
5
+ # (installed SKF module path first, src/ dev-checkout fallback); first existing
6
+ # path wins. HALT if neither resolves when the headless envelope must be emitted
7
+ # — the script is the source of truth for the SKF_SETUP_RESULT_JSON contract;
8
+ # never inline-render the envelope from prose (LLM schema drift is the bug
9
+ # this script exists to prevent).
10
+ emitEnvelopeProbeOrder:
11
+ - '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
12
+ - '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
4
13
  ---
5
14
 
15
+ <!-- Config: communicate in {communication_language}; emit user-visible report text (FORGE STATUS banner, climb hint, REQUIRED TIER NOT MET block, breadcrumb) in {document_output_language}. The JSON envelope from section 4 is a machine contract — its keys and enum values stay English regardless. -->
16
+
6
17
  # Step 4: Forge Status Report
7
18
 
8
19
  ## STEP GOAL:
9
20
 
10
- Display the forge status report with positive capability framing and report tier changes on re-run.
21
+ Display the forge status report with positive capability framing, surface tier changes and tool-set deltas on re-runs, prominently flag a required-tier miss, and (when headless) emit the schema-locked `SKF_SETUP_RESULT_JSON` envelope via `{emitEnvelopeHelper}`.
11
22
 
12
23
  ## Rules
13
24
 
14
- - Focus only on displaying the status report
25
+ - Focus only on display + envelope emission
15
26
  - Do not use negative framing ("missing", "lacking", "unavailable")
16
27
  - Do not list tools that are not available
17
28
  - Use tier capability descriptions from tier-rules.md
29
+ - Never inline-render the envelope JSON — the script owns the schema; drift breaks pipelines
18
30
  - Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing status report is NOT the terminal step
19
31
 
20
- ## MANDATORY SEQUENCE
32
+ ## Headless Mode Display Rule
33
+
34
+ When `{headless_mode}` is `true`, sections 2 (the human-readable banner) and 3 (the REQUIRED TIER NOT MET human prose block) are **skipped entirely**. The single line of stdout in headless mode is the `SKF_SETUP_RESULT_JSON: {…}` envelope from section 4 — every signal the human banner would have surfaced is already in the envelope (`tier`, `previous_tier`, `tier_changed`, `tools`, `tools_added`, `tools_removed`, `files_written`, `tier_override_*`, `require_tier_satisfied`, `warnings`, `error`). Parent skills and CI pipelines consume one parseable line; they should not be forced to scroll past 30 lines of ASCII-art they cannot use.
35
+
36
+ When `{headless_mode}` is `false`, sections 2 and 3 display normally and section 4 is skipped (interactive runs read the human banner; envelope emission would just be log noise).
21
37
 
22
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
38
+ ## MANDATORY SEQUENCE
23
39
 
24
40
  ### 1. Load Capability Descriptions
25
41
 
26
- Load and read {tierRulesData} for the tier capability descriptions and re-run messages.
42
+ Load and read {tierRulesData} for the tier capability descriptions and re-run messages. Needed by section 2; safe to load unconditionally — the data load itself is silent.
27
43
 
28
- ### 2. Display Forge Status Report
44
+ ### 2. Display Forge Status Report (skip when `{headless_mode}` is true)
29
45
 
30
46
  **Format the report as follows:**
31
47
 
@@ -39,6 +55,17 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
39
55
 
40
56
  Tools Detected:
41
57
  {for each tool that is available, show: tool name — version}
58
+ {if no tools are available: (none yet — see "Climb to next tier" below)}
59
+
60
+ {if calculated_tier is not Deep:}
61
+ Climb to next tier:
62
+ {if not tools.ast_grep: - Install ast-grep (https://ast-grep.github.io) — unlocks AST-backed code analysis (Forge tier)}
63
+ {if tools.ast_grep and not tools.ccc: - Install cocoindex-code (https://github.com/cocoindex-io/cocoindex-code) — adds semantic-guided precision compilation (Forge+ tier)}
64
+ {if tools.ast_grep and not tools.gh_cli: - Install GitHub CLI (https://cli.github.com) — required for Deep tier (cross-repository synthesis)}
65
+ {if tools.ast_grep and not tools.qmd and qmd_status is "absent": - Install qmd (https://github.com/tobi/qmd) — required for Deep tier (knowledge search)}
66
+ {if tools.ast_grep and not tools.qmd and qmd_status is "daemon_stopped": - Start the qmd daemon (already installed) — run `qmd start` (or your distribution's qmd service command) to unlock Deep tier (knowledge search)}
67
+ {if tools.ccc and ccc_daemon is "error": - The ccc daemon is reporting errors — run `ccc doctor` to diagnose. CCC index will fail until resolved (mirrors the qmd daemon-stopped pattern above for parity)}
68
+ {end if}
42
69
 
43
70
  {if hygiene_result is "completed":}
44
71
  QMD Registry:
@@ -63,30 +90,127 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
63
90
  {if ccc_index_result is "failed": indexing failed — semantic discovery unavailable this session}
64
91
  {end if}
65
92
 
93
+ Files written this run:
94
+ - forge-tier.yaml — {project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml
95
+ {if preferences_yaml_created is true:}
96
+ - preferences.yaml — {project-root}/_bmad/_memory/forger-sidecar/preferences.yaml (first-run defaults)
97
+ {end if}
98
+ {if forge_data_dir_created is true:}
99
+ - {forge_data_folder}/ (directory created)
100
+ {end if}
101
+ {if settings_yml_written is true:}
102
+ - .cocoindex_code/settings.yml — {project-root}/.cocoindex_code/settings.yml ({settings_yml_patterns_added} SKF exclusion pattern(s) merged)
103
+ {end if}
104
+ {if ccc_index_result is "created":}
105
+ - .cocoindex_code/ ccc index — {ccc_file_count} files indexed
106
+ {end if}
107
+
66
108
  {if tier_override is active:}
67
109
  Note: Tier override active (set in preferences.yaml)
68
110
 
111
+ {if tier_override_invalid is true:}
112
+ Note: tier_override value "{tier_override_invalid_value}" in preferences.yaml is not valid.
113
+ {if tier_override_invalid_suggestion is non-null: Did you mean "{tier_override_invalid_suggestion}"?}
114
+ Valid values are case-sensitive: Quick, Forge, Forge+, Deep. Using detected tier {calculated_tier}.
115
+
116
+ {if tier_override_unsafe is true:}
117
+ Warning: tier_override is forcing {calculated_tier} but the underlying tool prerequisites are not satisfied.
118
+ Missing: {tier_override_unsafe_missing}. The override is honored, but downstream skills that
119
+ rely on the missing tool(s) will fail at runtime. Install the missing tool(s) or remove
120
+ the override from preferences.yaml.
121
+
69
122
  {if re-run with tier change:}
70
123
  {appropriate upgrade/downgrade message from tier-rules.md}
71
124
 
72
- {if re-run with same tier:}
125
+ {if re-run with same tier and {tools_added} is empty and {tools_removed} is empty:}
73
126
  {same-tier message from tier-rules.md}
74
127
 
128
+ {if re-run with same tier and ({tools_added} or {tools_removed} is non-empty):}
129
+ Tier unchanged: {calculated_tier}.
130
+ {if {tools_added} non-empty:} Newly detected: {comma-separated tool names from tools_added}{if ccc was added and tier is Deep: " — ccc enhances Deep tier transparently."}
131
+ {if {tools_removed} non-empty:} No longer detected: {comma-separated tool names from tools_removed} — re-install to restore those capabilities.
132
+
75
133
  ═══════════════════════════════════════
76
134
  Forge ready. {calculated_tier} tier active.
77
135
  ═══════════════════════════════════════
136
+
137
+ {if {headless_mode} is false:}
138
+ Next: try [BS] Brief Skill to scope your first compilation target, or [QS] Quick Skill for a fast template-driven path. Already have a skill? [AS] Audit Skill drift-checks an existing skill against current sources.
78
139
  ```
79
140
 
141
+ The same-tier-with-tool-deltas branch reads `{tools_added}` and `{tools_removed}` directly from step-01's context (set from `skf-detect-tools.py` output relative to `{previous_tools}`). No LLM-side recomputation.
142
+
80
143
  **Tool display rules:**
144
+
81
145
  - Only show tools that ARE available with their version strings
82
146
  - Do NOT list unavailable tools
83
147
  - Do NOT show a "missing" column or section
84
148
 
85
- ### 3. Chain to Health Check
149
+ ### 3. Display Required-Tier Failure Block (when applicable; skip when `{headless_mode}` is true)
150
+
151
+ If `{require_tier_satisfied}` is `false` AND `{headless_mode}` is `false`, display this block immediately after the status report. In headless mode the failure surfaces via the envelope's `require_tier_satisfied: false` and the synthesized `warnings` entry — the human prose block would be log noise that pipelines have to scroll past.
152
+
153
+ When the block does fire (interactive run with require-tier failure):
154
+
155
+ ```
156
+ ═══════════════════════════════════════
157
+ REQUIRED TIER NOT MET
158
+ ═══════════════════════════════════════
159
+
160
+ Required: {require_tier}
161
+ Detected: {calculated_tier}
162
+ Missing: {require_tier_failure_missing_tools}
163
+
164
+ Install the missing tool(s) and re-run, or relax `--require-tier`.
165
+ See "Climb to next tier" above for install URLs.
166
+ ═══════════════════════════════════════
167
+ ```
168
+
169
+ This block exists to make pipeline failures visible without the operator parsing the JSON envelope.
170
+
171
+ ### 4. Emit Headless JSON Envelope
172
+
173
+ When `{headless_mode}` is `true`, build the context payload from this step's accumulated flags and forward it to `{emitEnvelopeHelper}` on stdin. Invoke via `uv run` so the script's PEP 723 dependency declarations are honored (the canonical runtime invocation per `docs/getting-started.md`'s uv prereq — bare `python3` skips the metadata block and breaks on a fresh interpreter). The script computes derived fields (`tools_added`, `tools_removed`, `tier_changed`, `warnings`), validates the assembled envelope against the JSON Schema at `src/shared/scripts/schemas/skf-setup-result-envelope.v1.json`, and emits the single prefixed line `SKF_SETUP_RESULT_JSON: {…}` on stdout.
174
+
175
+ ```bash
176
+ echo '{
177
+ "tier": "{calculated_tier}",
178
+ "previous_tier": {previous_tier_or_null},
179
+ "tools": {tools_dict_from_step_01_or_skf_detect_tools_output},
180
+ "previous_tools": {previous_tools_or_null},
181
+ "config_path": "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml",
182
+ "ccc_index": {
183
+ "status": "{ccc_index_result}",
184
+ "indexed_path": {ccc_indexed_path_or_null},
185
+ "file_count": {ccc_file_count_or_null}
186
+ },
187
+ "files_written": {
188
+ "forge-tier.yaml": true,
189
+ "preferences.yaml": {preferences_yaml_created},
190
+ "settings.yml": {settings_yml_written},
191
+ "ccc_index": {ccc_index_result_was_created}
192
+ },
193
+ "tier_override_active": {tier_override_active},
194
+ "tier_override_invalid": {tier_override_invalid},
195
+ "tier_override_invalid_value": {tier_override_invalid_value_or_null},
196
+ "tier_override_unsafe": {tier_override_unsafe},
197
+ "tier_override_unsafe_missing": {tier_override_unsafe_missing_list},
198
+ "require_tier_satisfied": {require_tier_satisfied_or_null},
199
+ "require_tier_failure_missing": {require_tier_failure_missing_tools_list},
200
+ "qmd_status": "{qmd_status}",
201
+ "ccc_exclusion_warnings": {ccc_exclusion_warnings_list},
202
+ "ccc_registry_stale_removed": {ccc_registry_stale_removed_paths_list},
203
+ "ccc_indexing_failed_reason": {ccc_indexing_failed_reason_or_null},
204
+ "error": {error_object_or_null}
205
+ }' | uv run {emitEnvelopeHelper} emit
206
+ ```
207
+
208
+ The script's documented context-payload shape (see `src/shared/scripts/skf-emit-result-envelope.py` docstring) tolerates two `tools` shapes — bare booleans OR `skf-detect-tools.py`'s `{key: {available: bool, ...}}` output — so either step-01's normalized booleans OR the raw detect-tools output forwarded as-is will produce the correct envelope.
86
209
 
87
- ONLY WHEN the forge status report has been displayed will you then load, read the full file, and execute `{nextStepFile}`. The health-check step is the true terminal step do not stop here even though the report reads as final.
210
+ **If the script exits non-zero:** the assembled envelope failed schema validation, which means a context flag from an earlier step is malformed. Surface the error to stderr and continue (the human-readable banner is already displayed; missing JSON envelope on a headless run is a degraded but non-fatal state — the pipeline observer will see no envelope and treat that as "agent did not complete cleanly").
88
211
 
89
- ## CRITICAL STEP COMPLETION NOTE
212
+ **When `{headless_mode}` is `false`, do NOT invoke the script** — interactive runs read the human-readable banner.
90
213
 
91
- This step chains to the local health-check step (`{nextStepFile}`), which in turn delegates to `shared/health-check.md`. After the health check completes, the setup workflow is fully done.
214
+ ### 5. Chain to Health Check
92
215
 
216
+ After the forge status report (and any failure block + JSON envelope) has been displayed, load `{nextStepFile}`, read it fully, and execute it — UNLESS `{require_tier_satisfied}` is `false`, in which case halt the workflow here without chaining to step-05. The health-check step is the true terminal step on success — do not stop after the report on a passing run even though it reads as final. step-05 in turn delegates to `shared/health-check.md`; after that returns, the setup workflow is fully done.
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  # `shared/health-check.md` resolves relative to the SKF module root
3
- # (`_bmad/skf/` when installed, `src/` during development), NOT relative
4
- # to this step file.
3
+ # (`{project-root}/_bmad/skf/` when installed, `{project-root}/src/` during
4
+ # development), NOT relative to this step file.
5
5
  nextStepFile: 'shared/health-check.md'
6
6
  ---
7
7
 
8
+ <!-- Config: communicate in {communication_language}. This is a delegation-only step (no user-visible output of its own); shared/health-check.md inherits the language directive on load. -->
9
+
8
10
  # Step 5: Workflow Health Check
9
11
 
10
12
  ## STEP GOAL:
@@ -91,10 +91,10 @@ SKF module (`skf init`) or run from a development checkout with src/ present.
91
91
 
92
92
  Do not proceed. No partial test report is written.
93
93
 
94
- **3b. Run the validator (30s timeout — deterministic validator should finish in <1s; the cap only guards against runaway python):**
94
+ **3b. Run the validator (30s timeout — deterministic validator should finish in <1s; the cap only guards against runaway python).** Invoke via `uv run` so the script's PEP 723 PyYAML dependency resolves automatically; bare `python3` would fail with `ModuleNotFoundError: No module named 'yaml'` on a fresh interpreter (`docs/getting-started.md` documents uv as the runtime prereq for exactly this).
95
95
 
96
96
  ```bash
97
- timeout 30s python3 {frontmatterScript} {resolved_skill_package}/SKILL.md --skill-dir-name {skill_name}
97
+ timeout 30s uv run {frontmatterScript} {resolved_skill_package}/SKILL.md --skill-dir-name {skill_name}
98
98
  ```
99
99
 
100
100
  If the command trips the 30s wall-clock (exit code `124`), set
@@ -116,7 +116,7 @@ Parse the JSON output. Per B2, treat each `status` value explicitly:
116
116
  This skill will fail `npx skills add` and `npx skill-check check`. {If warn:} Consider fixing frontmatter before proceeding (run `npx skill-check check <skill-dir> --fix` to auto-fix deterministic issues). {If fail:} test-skill cannot proceed — halt and repair frontmatter, then re-run.
117
117
  ```
118
118
 
119
- **3c. Python runtime probe.** Before the first invocation, confirm `python3` is on `$PATH` (`command -v python3`). If missing, set `analysis_confidence: degraded` in workflow context and carry a **score cap** into step-05: `capped_score = threshold - 1` → forces auto-FAIL until the runtime is restored. Record the reason in evidence-report and the test report frontmatter (`analysisConfidence: degraded`, `toolingStatus: python3-missing`).
119
+ **3c. Python runtime probe.** Before the first invocation, confirm both `python3` AND `uv` are on `$PATH` (`command -v python3` and `command -v uv`). Both are required: `uv run` shells through to `python3` under the hood AND honors the script's PEP 723 PyYAML dependency declaration that bare `python3` ignores. If either is missing, set `analysis_confidence: degraded` in workflow context and carry a **score cap** into step-05: `capped_score = threshold - 1` → forces auto-FAIL until the runtime is restored. Record the reason in evidence-report and the test report frontmatter (`analysisConfidence: degraded`, `toolingStatus: python3-missing` or `uv-missing` as appropriate). `uv` is a documented runtime prerequisite — see `docs/getting-started.md` for install instructions.
120
120
 
121
121
  ### 4. Load Forge Tier State
122
122
 
@@ -199,7 +199,7 @@ testResult: ''
199
199
  score: ''
200
200
  threshold: ''
201
201
  analysisConfidence: '{full|degraded}'
202
- toolingStatus: '{ok|python3-missing|frontmatter-validator-missing|frontmatter-validator-timeout}'
202
+ toolingStatus: '{ok|python3-missing|uv-missing|frontmatter-validator-missing|frontmatter-validator-timeout}'
203
203
  workspaceDrift: '{not-checked|ok|overridden}'
204
204
  testDate: '{run_id timestamp ISO-8601 UTC}'
205
205
  stepsCompleted: ['step-01-init']