bmad-module-skill-forge 1.1.0 → 1.2.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 +6 -4
- package/docs/_data/pinned.yaml +1 -1
- package/docs/bmad-synergy.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/why-skf.md +5 -4
- package/package.json +2 -2
- package/src/knowledge/ccc-bridge.md +1 -1
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +134 -0
- package/src/shared/scripts/skf-detect-tools.py +359 -0
- package/src/shared/scripts/skf-emit-result-envelope.py +419 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +413 -0
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +324 -0
- package/src/shared/scripts/skf-preflight.py +14 -4
- package/src/shared/scripts/skf-qmd-classify-collections.py +212 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +9 -3
- package/src/skf-create-skill/steps-c/step-06-validate.md +1 -1
- package/src/skf-setup/SKILL.md +25 -10
- package/src/skf-setup/references/tier-rules.md +2 -2
- package/src/skf-setup/steps-c/step-01-detect-and-tier.md +58 -71
- package/src/skf-setup/steps-c/step-01b-ccc-index.md +49 -66
- package/src/skf-setup/steps-c/step-02-write-config.md +59 -86
- package/src/skf-setup/steps-c/step-03-auto-index.md +73 -91
- package/src/skf-setup/steps-c/step-04-report.md +135 -11
- package/src/skf-setup/steps-c/step-05-health-check.md +4 -2
- package/src/skf-test-skill/steps-c/step-01-init.md +4 -4
|
@@ -1,123 +1,96 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: './step-03-auto-index.md'
|
|
3
|
+
# Resolve `{forgeTierRwHelper}` by probing `{forgeTierRwProbeOrder}` in order
|
|
4
|
+
# (installed SKF module path first, src/ dev-checkout fallback); first existing
|
|
5
|
+
# path wins. HALT if neither resolves — the script owns the canonical
|
|
6
|
+
# forge-tier.yaml format AND the array-preservation contract that protects
|
|
7
|
+
# qmd_collections / ccc_index_registry / staleness_threshold_hours from being
|
|
8
|
+
# lost on rewrite. NEVER fall back to inline YAML emission — drift between the
|
|
9
|
+
# script and a prose-rendered template will silently corrupt downstream skills.
|
|
10
|
+
forgeTierRwProbeOrder:
|
|
11
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
|
|
12
|
+
- '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
|
|
3
13
|
---
|
|
4
14
|
|
|
15
|
+
<!-- Config: communicate in {communication_language}. Halt-on-write-failure messages render in the user's language. -->
|
|
16
|
+
|
|
5
17
|
# Step 2: Write Configuration
|
|
6
18
|
|
|
7
19
|
## STEP GOAL:
|
|
8
20
|
|
|
9
|
-
Write the detected tool availability and calculated tier to forge-tier.yaml, create preferences.yaml with defaults if it does not exist, and ensure the forge-data
|
|
21
|
+
Write the detected tool availability and calculated tier to `forge-tier.yaml` (preserving registry arrays from any existing file), create `preferences.yaml` with first-run defaults if it does not exist, and ensure the `forge-data/` directory is present. All file mutations go through `{forgeTierRwHelper}` so the format is locked, atomic, and array-preservation is guaranteed.
|
|
10
22
|
|
|
11
23
|
## Rules
|
|
12
24
|
|
|
13
25
|
- Focus only on writing configuration files and creating directories
|
|
14
26
|
- Do not re-detect tools — use results from step-01
|
|
15
|
-
-
|
|
16
|
-
- File write failures are errors — report clearly
|
|
27
|
+
- Never inline a YAML template for forge-tier.yaml or preferences.yaml — the script owns the canonical format
|
|
28
|
+
- File write failures are errors — report clearly and halt the workflow
|
|
17
29
|
|
|
18
30
|
## MANDATORY SEQUENCE
|
|
19
31
|
|
|
20
|
-
**CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
|
|
21
|
-
|
|
22
32
|
### 1. Write forge-tier.yaml
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
status: {ccc_index_result from step-01b: "fresh"|"created"|"none"|"failed"}
|
|
49
|
-
staleness_threshold_hours: 24
|
|
50
|
-
file_count: {ccc_file_count from step-01b, or ~}
|
|
51
|
-
exclude_patterns: {ccc_exclude_patterns from step-01b, or []}
|
|
52
|
-
|
|
53
|
-
# CCC index registry (tracks which source paths have been indexed for skill workflows)
|
|
54
|
-
# PRESERVE existing entries on re-runs — see Note below
|
|
55
|
-
ccc_index_registry: {preserved from existing forge-tier.yaml, or [] if first run}
|
|
56
|
-
|
|
57
|
-
# QMD collection registry (populated by create-skill, consumed by audit/update-skill)
|
|
58
|
-
# PRESERVE existing entries on re-runs — see Note below
|
|
59
|
-
qmd_collections: {preserved from existing forge-tier.yaml, or [] if first run}
|
|
34
|
+
Build the JSON payload from context flags set by step-01 and step-01b. The payload must include `tools`, `tier`, and `ccc_index`; the script handles `tier_detected_at` defaulting to "now" if absent and preserves `qmd_collections`, `ccc_index_registry`, and a user-customized `ccc_index.staleness_threshold_hours` from any existing file.
|
|
35
|
+
|
|
36
|
+
Invoke via `uv run` so the script's PEP 723 PyYAML dependency resolves automatically (this is what `docs/getting-started.md`'s uv prereq exists for). Bare `python3` would fail on a fresh interpreter with `ModuleNotFoundError`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
echo '{
|
|
40
|
+
"tools": {
|
|
41
|
+
"ast_grep": {ast_grep},
|
|
42
|
+
"gh_cli": {gh_cli},
|
|
43
|
+
"qmd": {qmd},
|
|
44
|
+
"ccc": {ccc},
|
|
45
|
+
"ccc_daemon": {ccc_daemon},
|
|
46
|
+
"security_scan": {security_scan}
|
|
47
|
+
},
|
|
48
|
+
"tier": "{calculated_tier}",
|
|
49
|
+
"ccc_index": {
|
|
50
|
+
"indexed_path": {ccc_indexed_path},
|
|
51
|
+
"last_indexed": {ccc_last_indexed},
|
|
52
|
+
"status": "{ccc_index_result}",
|
|
53
|
+
"file_count": {ccc_file_count},
|
|
54
|
+
"exclude_patterns": {ccc_exclude_patterns}
|
|
55
|
+
}
|
|
56
|
+
}' | uv run {forgeTierRwHelper} write-tools \
|
|
57
|
+
--target "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"
|
|
60
58
|
```
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
**This file is ALWAYS overwritten** on every run — it reflects current tool state.
|
|
65
|
-
|
|
66
|
-
If the write fails, report the error and halt the workflow.
|
|
60
|
+
The script atomically writes the file via temp + fsync + rename (mirrors `skf-atomic-write.py`'s crash-safety contract) and returns a JSON response with `wrote`, `preserved_arrays.qmd_collections` count, `preserved_arrays.ccc_index_registry` count, and the resolved `tier`.
|
|
67
61
|
|
|
68
|
-
|
|
62
|
+
**Parse the response and set context flags for step-04:**
|
|
69
63
|
|
|
70
|
-
|
|
64
|
+
- `{forge_tier_yaml_path}` ← `wrote`
|
|
65
|
+
- `{forge_tier_qmd_collections_count}` ← `preserved_arrays.qmd_collections`
|
|
66
|
+
- `{forge_tier_ccc_registry_count}` ← `preserved_arrays.ccc_index_registry`
|
|
71
67
|
|
|
72
|
-
**If
|
|
68
|
+
**If the script exits non-zero**: parse the stderr JSON `{"status":"error","message":...}`, set `{error: {phase: "step-02:write-tools", path: "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml", reason: <message>}}` for step-04's envelope, and halt the workflow before chaining to step-03.
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
# Ferris Sidecar: User Preferences
|
|
76
|
-
# Created by setup workflow on first run
|
|
77
|
-
# Edit this file to customize Ferris behavior
|
|
70
|
+
### 2. Initialize preferences.yaml
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
passive_context: true
|
|
72
|
+
```bash
|
|
73
|
+
uv run {forgeTierRwHelper} init-prefs \
|
|
74
|
+
--target "{project-root}/_bmad/_memory/forger-sidecar/preferences.yaml"
|
|
75
|
+
```
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
headless_mode: false
|
|
77
|
+
The script creates the file with first-run defaults (matching the prior inline template — `tier_override: ~`, `passive_context: true`, `headless_mode: false`, `compact_greeting: false`, plus reserved-for-future-use commented fields) IF the file does not exist. When the file already exists, the script refuses to overwrite (preserves user customization) and reports `wrote: false`.
|
|
87
78
|
|
|
88
|
-
|
|
89
|
-
compact_greeting: false
|
|
79
|
+
**Parse the response and set context flags for step-04:**
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
# output_language: ~
|
|
93
|
-
# skill_format_version: ~
|
|
94
|
-
# citation_style: ~
|
|
95
|
-
# confidence_display: ~
|
|
96
|
-
```
|
|
81
|
+
- `{preferences_yaml_created}` ← `wrote` (true on first run, false on re-run when the file pre-existed)
|
|
97
82
|
|
|
98
|
-
**If
|
|
83
|
+
**If the script exits non-zero**: same halt-and-report pattern as section 1, with `phase: "step-02:init-prefs"` and the matching path.
|
|
99
84
|
|
|
100
85
|
### 3. Ensure forge-data/ Directory
|
|
101
86
|
|
|
102
87
|
Check if `{forge_data_folder}` directory exists:
|
|
103
88
|
|
|
104
|
-
- If missing: create it
|
|
105
|
-
- If exists: skip silently
|
|
89
|
+
- If missing: create it. Store `{forge_data_dir_created: true}` in context.
|
|
90
|
+
- If exists: skip silently. Store `{forge_data_dir_created: false}` in context.
|
|
106
91
|
|
|
107
|
-
|
|
92
|
+
If the create fails (parent unwritable, disk full, permission denied), set `{error: {phase: "step-02:forge-data-dir", path: "{forge_data_folder}", reason: <error message>}}` for step-04's envelope and halt the workflow.
|
|
108
93
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#### Menu Handling Logic:
|
|
112
|
-
|
|
113
|
-
- After all file operations complete successfully, immediately load, read entire file, then execute {nextStepFile}
|
|
114
|
-
|
|
115
|
-
#### EXECUTION RULES:
|
|
116
|
-
|
|
117
|
-
- This is an auto-proceed step with no user choices
|
|
118
|
-
- Proceed directly to next step after configuration is written
|
|
119
|
-
|
|
120
|
-
## CRITICAL STEP COMPLETION NOTE
|
|
121
|
-
|
|
122
|
-
ONLY WHEN forge-tier.yaml has been written successfully and preferences.yaml exists (created or pre-existing) will you load and read fully `{nextStepFile}` to execute the QMD hygiene step.
|
|
94
|
+
### 4. Auto-Proceed
|
|
123
95
|
|
|
96
|
+
After forge-tier.yaml has been written successfully and preferences.yaml exists (created or pre-existing), display "**Proceeding to QMD collection hygiene...**", then load `{nextStepFile}`, read it fully, and execute it.
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- Do not
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
46
|
+
**If `{calculated_tier}` IS Deep:** Continue to section 2.
|
|
36
47
|
|
|
37
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
+
**Parse the JSON output and set context flags:**
|
|
53
71
|
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
- The QMD collection was deleted or lost
|
|
64
|
-
- Remove the stale entry from the registry
|
|
79
|
+
### 3. Handle Orphaned Collections
|
|
65
80
|
|
|
66
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
102
|
+
Track the count of successful removals as `{hygiene_orphaned_removed}`. Set `{hygiene_orphaned_kept: 0}`.
|
|
97
103
|
|
|
98
|
-
|
|
104
|
+
**If user selects K (Keep) or no orphans:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}`.
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
### 4. Stale Registry Cleanup
|
|
101
107
|
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
**Parse the JSON output and set context flags for step-04:**
|
|
141
122
|
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
+
(The script already logs each ccc removal as a WARNING line; no additional display needed.)
|
|
149
130
|
|
|
150
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
212
|
+
**When `{headless_mode}` is `false`, do NOT invoke the script** — interactive runs read the human-readable banner.
|
|
90
213
|
|
|
91
|
-
|
|
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.
|