bmad-module-skill-forge 1.0.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.
Files changed (44) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +9 -7
  3. package/docs/_data/pinned.yaml +5 -2
  4. package/docs/{RELEASING.md → _internal/RELEASING.md} +87 -61
  5. package/docs/{STABILITY.md → _internal/STABILITY.md} +2 -2
  6. package/docs/bmad-synergy.md +2 -2
  7. package/docs/getting-started.md +1 -1
  8. package/docs/troubleshooting.md +1 -1
  9. package/docs/why-skf.md +5 -4
  10. package/package.json +2 -7
  11. package/release-audits/v1.0.0-launch-audit.md +313 -2
  12. package/src/knowledge/ccc-bridge.md +1 -1
  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-forge-tier-rw.py +413 -0
  17. package/src/shared/scripts/skf-merge-ccc-exclusions.py +324 -0
  18. package/src/shared/scripts/skf-preflight.py +14 -4
  19. package/src/shared/scripts/skf-qmd-classify-collections.py +212 -0
  20. package/src/shared/scripts/skf-validate-frontmatter.py +9 -3
  21. package/src/skf-audit-skill/steps-c/step-01-init.md +60 -0
  22. package/src/skf-audit-skill/steps-c/step-02-re-index.md +7 -1
  23. package/src/skf-audit-skill/steps-c/step-03-structural-diff.md +19 -1
  24. package/src/skf-audit-skill/steps-c/step-04-semantic-diff.md +10 -1
  25. package/src/skf-audit-skill/steps-c/step-05-severity-classify.md +8 -0
  26. package/src/skf-audit-skill/steps-c/step-06-report.md +8 -0
  27. package/src/skf-brief-skill/assets/skill-brief-schema.md +36 -18
  28. package/src/skf-create-skill/steps-c/step-06-validate.md +1 -1
  29. package/src/skf-drop-skill/steps-c/step-02-execute.md +1 -1
  30. package/src/skf-export-skill/assets/managed-section-format.md +1 -1
  31. package/src/skf-export-skill/steps-c/step-04-update-context.md +4 -4
  32. package/src/skf-rename-skill/steps-c/step-02-execute.md +1 -1
  33. package/src/skf-setup/SKILL.md +25 -10
  34. package/src/skf-setup/references/tier-rules.md +2 -2
  35. package/src/skf-setup/steps-c/step-01-detect-and-tier.md +58 -71
  36. package/src/skf-setup/steps-c/step-01b-ccc-index.md +49 -66
  37. package/src/skf-setup/steps-c/step-02-write-config.md +59 -86
  38. package/src/skf-setup/steps-c/step-03-auto-index.md +73 -91
  39. package/src/skf-setup/steps-c/step-04-report.md +135 -11
  40. package/src/skf-setup/steps-c/step-05-health-check.md +4 -2
  41. package/src/skf-test-skill/steps-c/step-01-init.md +4 -4
  42. package/src/skf-test-skill/steps-c/step-03-coverage-check.md +1 -1
  43. package/src/skf-update-skill/steps-c/step-02-detect-changes.md +123 -0
  44. package/tools/validate-docs-drift.js +42 -2
@@ -1,31 +1,39 @@
1
1
  ---
2
2
  nextStepFile: './step-02-write-config.md'
3
+ # Resolve `{mergeCccExclusionsHelper}` by probing `{mergeCccExclusionsProbeOrder}`
4
+ # in order (installed SKF module path first, src/ dev-checkout fallback); first
5
+ # existing path wins. HALT if neither resolves — the script owns config-value
6
+ # validation and the set-union merge into .cocoindex_code/settings.yml; no
7
+ # fallback to prose-driven validation.
8
+ mergeCccExclusionsProbeOrder:
9
+ - '{project-root}/_bmad/skf/shared/scripts/skf-merge-ccc-exclusions.py'
10
+ - '{project-root}/src/shared/scripts/skf-merge-ccc-exclusions.py'
3
11
  ---
4
12
 
13
+ <!-- Config: communicate in {communication_language}. User-visible status messages (CCC exclusion summary, indexing progress message) render in the user's language. -->
14
+
5
15
  # Step 1b: CCC Index Verification
6
16
 
7
17
  ## STEP GOAL:
8
18
 
9
- If ccc is available (`{ccc: true}` from step-01), configure CCC exclusion patterns for SKF infrastructure directories, verify that the ccc index exists for the project root, and create or refresh it if needed. Store index state and exclusion patterns in context for step-02 to write into forge-tier.yaml.
19
+ If ccc is available (`{ccc: true}` from step-01), invoke `{mergeCccExclusionsHelper}` to validate config-driven SKF exclusion patterns and merge them into `.cocoindex_code/settings.yml`, then verify the project's ccc index exists and create or refresh it if needed. Store index state and exclusion-merge results in context for step-02 to write into forge-tier.yaml and for step-04 to surface in the JSON envelope.
10
20
 
11
21
  For Quick and Forge tiers, or when ccc is unavailable, skip silently and proceed.
12
22
 
13
23
  ## Rules
14
24
 
15
- - Focus only on ccc index verification and creation
25
+ - Focus only on ccc index verification, exclusion-pattern merge, and (re-)indexing
16
26
  - Do not display skip messages for Quick/Forge tiers
17
27
  - Do not fail the workflow if ccc indexing fails
18
- - Do not re-index if ccc index already exists and is fresh, unless new exclusion patterns were applied
28
+ - Never reimplement the exclusion-pattern validation in prose the script owns the PR #248 rules
19
29
 
20
30
  ## MANDATORY SEQUENCE
21
31
 
22
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
23
-
24
32
  ### 1. Check Eligibility
25
33
 
26
34
  Read `{ccc}` from step-01 context.
27
35
 
28
- **If `{ccc}` is false:** Set `{ccc_index_result: "none", ccc_indexed_path: null, ccc_last_indexed: null, ccc_exclude_patterns: []}`. Proceed directly to section 4 (Auto-Proceed) — no output, no messaging.
36
+ **If `{ccc}` is false:** Set `{ccc_index_result: "none", ccc_indexed_path: null, ccc_last_indexed: null, ccc_exclude_patterns: [], ccc_exclusion_warnings: [], settings_yml_written: false, settings_yml_patterns_added: 0}`. Proceed directly to section 4 (Auto-Proceed) — no output, no messaging.
29
37
 
30
38
  **If `{ccc}` is true:** Continue to section 2.
31
39
 
@@ -36,81 +44,68 @@ Read existing forge-tier.yaml at `{project-root}/_bmad/_memory/forger-sidecar/fo
36
44
  Read `staleness_threshold_hours` from `ccc_index.staleness_threshold_hours` in the existing forge-tier.yaml (default: 24 if not present or not a number). Use this value for the freshness check below.
37
45
 
38
46
  Check the `ccc_index` section:
47
+
39
48
  - If `ccc_index.indexed_path` matches `{project-root}` AND `ccc_index.status` is `"fresh"` or `"created"`:
40
49
  - Check freshness: if `ccc_index.last_indexed` is within `staleness_threshold_hours` of now → index is fresh
41
50
  - Store `{ccc_index_result: "fresh", ccc_indexed_path: {project-root}, ccc_last_indexed: {existing timestamp}}`
42
- - Set `{needs_reindex: false}` — proceed to section 2b (exclusions must still be configured)
51
+ - Set `{needs_reindex: false}` — exclusions still get merged in section 3, which may force a re-index
43
52
 
44
53
  - If `ccc_index.indexed_path` matches `{project-root}` but timestamp is older than threshold:
45
- - Set `{needs_reindex: true}` — proceed to section 2b then section 3
54
+ - Set `{needs_reindex: true}`
46
55
 
47
56
  - If `ccc_index` is missing, has null values, or path doesn't match:
48
- - Set `{needs_reindex: true}` — proceed to section 2b then section 3
57
+ - Set `{needs_reindex: true}`
49
58
 
50
- ### 2b. Configure CCC Exclusions
59
+ ### 3. Merge SKF Exclusion Patterns
51
60
 
52
61
  SKF infrastructure and output directories must be excluded from the CCC index — they contain workflow instructions, build artifacts, and generated skills that pollute semantic search results with zero extraction value.
53
62
 
54
- **Build the SKF exclusion list:**
55
-
56
- 1. Use `{skills_output_folder}` and `{forge_data_folder}` from the workflow activation context (resolved in On Activation from `{project-root}/_bmad/skf/config.yaml`).
57
-
58
- 2. Assemble the exclusion patterns using `**/` prefix format (matching `.cocoindex_code/settings.yml` convention — e.g., `**/node_modules`):
59
- - `**/_bmad` — SKF framework module (workflows, agents, knowledge files)
60
- - `**/_bmad-output` — Build output artifacts
61
- - `**/.claude` — Claude Code configuration
62
- - `**/_skf-learn` — SKF learning materials
63
- - `**/{skills_output_folder}` — Generated skill files (from activation context)
64
- - `**/{forge_data_folder}` — Compilation workspace (from activation context)
65
-
66
- 3. Store `{ccc_exclude_patterns}` in context for step-02 to write into forge-tier.yaml.
63
+ Run the merge helper, forwarding the resolved config values from the workflow activation context. Invoke via `uv run` so PEP 723 inline metadata resolves the script's PyYAML dependency automatically (per `docs/getting-started.md`'s prereq list — uv exists for this exact purpose). Bare `python3` will fail on a fresh Python with `ModuleNotFoundError: No module named 'yaml'`.
67
64
 
68
- **Apply exclusions to settings.yml:**
65
+ ```bash
66
+ uv run {mergeCccExclusionsHelper} \
67
+ --project-root "{project-root}" \
68
+ --skills-output-folder "{skills_output_folder}" \
69
+ --forge-data-folder "{forge_data_folder}"
70
+ ```
69
71
 
70
- Check if `{project-root}/.cocoindex_code/settings.yml` exists. Set `{settings_yml_existed: true}` if it does, `{settings_yml_existed: false}` if not.
72
+ The script (see `src/shared/scripts/skf-merge-ccc-exclusions.py` docstring for the full schema) builds the SKF exclusion list (4 always-include hardcoded patterns + 2 conditional from validated config), applies the PR #248 validation rules to reject empty / absolute / glob-meta config values with actionable warnings, and performs an idempotent set-union merge into `{project-root}/.cocoindex_code/settings.yml`. User customizations are preserved. When the file does not exist yet (first-time setup before `ccc init`) the script creates it; when nothing new needs adding the script skips the write entirely (mtime preserved).
71
73
 
72
- If `{settings_yml_existed}` is true (from a previous `ccc init` run):
74
+ **Parse the JSON output and set context flags:**
73
75
 
74
- 1. Read `settings.yml`
75
- 2. For each pattern in `{ccc_exclude_patterns}`: if the pattern is NOT already present in `exclude_patterns`, append it and set `{exclusions_changed: true}`
76
- 3. If `{exclusions_changed}`: write the updated `settings.yml` back, set `{needs_reindex: true}` (new exclusions require re-indexing), display: "**CCC exclusions configured:** {count} SKF patterns applied to .cocoindex_code/settings.yml"
77
- 4. If no new patterns needed: display nothing (exclusions already configured)
76
+ - `{settings_yml_existed}` `settings_yml_existed`
77
+ - `{settings_yml_written}` `written`
78
+ - `{settings_yml_patterns_added}` `patterns_added`
79
+ - `{ccc_exclude_patterns}` ← derive from the script's behaviour: it always merges the 4 always-include patterns plus any of the 2 config-driven ones whose value passed validation. Step-02 needs this list to write `ccc_index.exclude_patterns` into forge-tier.yaml — read it from the script's `patterns_added_list` PLUS any already-present matching SKF patterns inferred from the input. Pragmatically: re-derive the list as `["**/_bmad", "**/_bmad-output", "**/.claude", "**/_skf-learn"]` plus `"**/{skills_output_folder}"` if no warning names `skills_output_folder` plus `"**/{forge_data_folder}"` if no warning names `forge_data_folder`.
80
+ - `{ccc_exclusion_warnings}` ← `warnings` (a list — step-04 folds them into the envelope's warnings array)
78
81
 
79
- This preserves any existing user customizations and default exclusions while ensuring SKF directories are filtered out.
82
+ **If `{settings_yml_written}` is true** (new patterns merged into settings.yml): set `{needs_reindex: true}` — new exclusions require re-indexing for the index to reflect them. Display: "**CCC exclusions configured:** {patterns_added} SKF patterns applied to .cocoindex_code/settings.yml"
80
83
 
81
- If `{settings_yml_existed}` is false: the exclusions will be applied after `ccc init` in section 3.
84
+ **If `{settings_yml_written}` is false** (idempotent re-run, all patterns already present): display nothing (exclusions already configured). Do NOT change `{needs_reindex}` it stays at whatever section 2 set it to.
82
85
 
83
86
  **Flow decision:**
84
- - If `{needs_reindex}` is true: proceed to section 3
85
- - If `{needs_reindex}` is false: proceed to section 4 (Auto-Proceed)
86
-
87
- ### 3. Create or Refresh CCC Index
88
87
 
89
- **If `{ccc_daemon}` is `"stopped"` or `"healthy"`:**
88
+ - If `{needs_reindex}` is true: proceed to section 4
89
+ - If `{needs_reindex}` is false: proceed to section 5 (Auto-Proceed)
90
90
 
91
- The `ccc index` command auto-starts the daemon when needed. Proceed with indexing below.
91
+ ### 4. Create or Refresh CCC Index
92
92
 
93
- **If `{ccc_daemon}` is `"error"`:**
93
+ **If `{ccc_daemon}` is `"stopped"` or `"healthy"`:** the `ccc index` command auto-starts the daemon when needed.
94
94
 
95
- Attempt indexing anyway — errors will be caught below.
95
+ **If `{ccc_daemon}` is `"error"`:** attempt indexing anyway — errors will be caught below.
96
96
 
97
97
  Run (CWD must be `{project-root}`):
98
+
98
99
  ```bash
99
100
  ccc init
100
101
  ```
101
102
 
102
- **If init fails** (project may already be initialized): continue — this is not an error.
103
-
104
- **Apply SKF exclusion patterns (if not already applied in section 2b):**
103
+ **If init fails** (project may already be initialized): continue — this is not an error. Note: when `{settings_yml_existed}` was false and `ccc init` just created the settings.yml, the merge in section 3 ran BEFORE ccc init. The script handles "no settings.yml exists" by creating one with just the SKF exclusions, which `ccc init` will then either preserve (if it merges) or overwrite (in which case the next workflow run re-merges). Either way the SKF exclusions end up in the file by the time `ccc index` runs the second time.
105
104
 
106
- If `{settings_yml_existed}` is false (first-time setup — `ccc init` just created it), apply exclusions now:
107
-
108
- 1. Read `{project-root}/.cocoindex_code/settings.yml` (created by `ccc init`)
109
- 2. For each pattern in `{ccc_exclude_patterns}`: if the pattern is NOT already present in `exclude_patterns`, append it
110
- 3. Write the updated `settings.yml` back
111
- 4. Display: "**CCC exclusions configured:** {count} SKF patterns applied to .cocoindex_code/settings.yml"
105
+ Before invoking `ccc index`, display: "**Building semantic index this can take several minutes on large codebases (1000+ files). Run `ccc status` in another terminal to monitor progress.**" so the user does not assume the workflow has hung during the long-running call.
112
106
 
113
107
  Then run:
108
+
114
109
  ```bash
115
110
  ccc index
116
111
  ```
@@ -118,29 +113,17 @@ ccc index
118
113
  **Note:** `ccc index` can take several minutes on large codebases (1000+ files). Run with an extended timeout or in background mode. Use `ccc status` to verify completion — check that `Chunks` and `Files` counts are non-zero.
119
114
 
120
115
  **If succeeds:**
116
+
121
117
  - Run `ccc status` to get file count
122
118
  - Store `{ccc_index_result: "created", ccc_indexed_path: {project-root}, ccc_last_indexed: {current ISO timestamp}, ccc_file_count: {count from ccc status}}`
123
119
  - Display: "**CCC index created.** {ccc_file_count} files indexed for semantic discovery."
124
120
 
125
121
  **If fails:**
126
- - Store `{ccc_index_result: "failed", ccc_indexed_path: null, ccc_last_indexed: null}`
122
+
123
+ - Store `{ccc_index_result: "failed", ccc_indexed_path: null, ccc_last_indexed: null, ccc_indexing_failed_reason: {error}}` (the failed-reason flag flows into step-04's envelope warnings)
127
124
  - Display: "CCC indexing failed: {error}. Extraction will use direct AST scanning — semantic pre-ranking unavailable this session."
128
125
  - Continue — this is NOT a workflow error
129
126
 
130
- ### 4. Auto-Proceed
131
-
132
- "**Proceeding to write configuration...**"
133
-
134
- #### Menu Handling Logic:
135
-
136
- - After ccc index check completes (or is skipped), immediately load, read entire file, then execute {nextStepFile}
137
-
138
- #### EXECUTION RULES:
139
-
140
- - This is an auto-proceed step with no user choices
141
- - Proceed directly to next step after ccc index verification
142
-
143
- ## CRITICAL STEP COMPLETION NOTE
144
-
145
- ONLY WHEN ccc index verification is complete (or step is skipped for ccc unavailable) will you load and read fully `{nextStepFile}` to execute the configuration write step.
127
+ ### 5. Auto-Proceed
146
128
 
129
+ After ccc index verification is complete (or skipped because ccc is unavailable), display "**Proceeding to write configuration...**", then load `{nextStepFile}`, read it fully, and execute it.
@@ -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/ directory is present.
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
- - Do not overwrite existing preferences.yaml
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
- Write to `{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml`:
25
-
26
- ```yaml
27
- # Ferris Sidecar: Forge Tier State
28
- # Written by setup workflow
29
-
30
- # Tool availability (detected during [SF] Setup Forge)
31
- tools:
32
- ast_grep: {true/false from detection}
33
- gh_cli: {true/false from detection}
34
- qmd: {true/false from detection}
35
- ccc: {true/false from detection}
36
- ccc_daemon: {ccc_daemon from step-01 if available: "healthy"|"stopped"|"error", or ~}
37
- security_scan: {true/false — true when SNYK_TOKEN is set}
38
-
39
- # Capability tier (derived from tool availability)
40
- # Quick = no tools | Forge = + ast-grep | Forge+ = + ast-grep + ccc | Deep = + ast-grep + gh + QMD
41
- tier: {calculated_tier}
42
- tier_detected_at: {current ISO timestamp}
43
-
44
- # CCC semantic index state (managed by setup step-01b and extraction workflows)
45
- ccc_index:
46
- indexed_path: {ccc_indexed_path from step-01b, or ~}
47
- last_indexed: {ccc_last_indexed from step-01b, or ~}
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
- **Note on re-runs:** The `qmd_collections`, `ccc_index_registry` arrays, and `staleness_threshold_hours` value must be preserved across re-runs. Before overwriting forge-tier.yaml, read these existing values and re-inject them into the new write. These values are populated by create-skill workflows or customized by users and must not be reset. Note: `exclude_patterns` is NOT preserved it is always written fresh from `{ccc_exclude_patterns}` computed by step-01b.
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
- ### 2. Handle preferences.yaml
62
+ **Parse the response and set context flags for step-04:**
69
63
 
70
- Check if `{project-root}/_bmad/_memory/forger-sidecar/preferences.yaml` exists:
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 it does NOT exist (first run):** Create with defaults:
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
- ```yaml
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
- # Override detected tier (set to Quick, Forge, Forge+, or Deep to force a tier)
80
- tier_override: ~
81
-
82
- # Passive context injection (set to false to skip snippet generation and CLAUDE.md updates during export)
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
- # Headless mode (set to true to skip confirmation gates in all workflows)
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
- # Compact greeting (set to true to skip the full capabilities table on session start)
89
- compact_greeting: false
79
+ **Parse the response and set context flags for step-04:**
90
80
 
91
- # Reserved for future use these fields are not yet consumed by any workflow step
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 it DOES exist:** Do not modify. Preserve entirely.
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
- ### 4. Auto-Proceed
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
- "**Proceeding to QMD collection hygiene...**"
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
- # 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.