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
@@ -116,7 +116,9 @@ Read the `qmd_collections` registry from `{sidecar_path}/forge-tier.yaml`.
116
116
 
117
117
  Find the collection entry matching the current skill: look for an entry where `skill_name` matches the current skill being audited AND `type` is `"extraction"`.
118
118
 
119
- **If a matching extraction collection is found:**
119
+ Three collection states must be handled distinctly (same branching as step-04 §2 — keep them in sync):
120
+
121
+ **If a matching extraction collection is found and populated** (pre-query probe via `qmd ls {collection_name}` or equivalent returns one or more files):
120
122
  Query qmd_bridge against the `{skill_name}-extraction` collection for temporal context on each extracted export:
121
123
  - When was this export first added?
122
124
  - Has it been modified recently?
@@ -125,6 +127,10 @@ Query qmd_bridge against the `{skill_name}-extraction` collection for temporal c
125
127
 
126
128
  Append temporal metadata to each export in the snapshot.
127
129
 
130
+ **If a matching extraction collection is found but empty** (pre-query probe reports `Files: 0 (updated never)` or an empty listing):
131
+ Log: "QMD collection `{collection_name}` is registered but empty. Run `qmd update` to (re-)index `{collection.path}`, then re-audit. Temporal enrichment skipped for this run."
132
+ Continue without T2 enrichment — the unpopulated collection is a setup gap, not an extraction failure. Step-04 will fall through to its direct-content fallback for semantic diff.
133
+
128
134
  **If no matching collection found in registry:**
129
135
  Log: "No QMD extraction collection found for {skill_name}. Temporal enrichment skipped. Re-run [CS] Create Skill to generate the collection."
130
136
  Continue without T2 enrichment — this is not an error.
@@ -29,10 +29,20 @@ Load both datasets:
29
29
  **Current (from Step 02 extraction):**
30
30
  - Export list with names, types, signatures, file paths, line numbers
31
31
 
32
+ **Canonicalize extractor methodology differences before matching.** The extractor used by `skf-create-skill` at baseline time and the re-extractor used by step-02 can differ in cosmetic detail (quote style, module qualification, re-export resolution). Without normalization, those cosmetic differences surface as false-positive "Changed" and "Removed" entries even when the source commit has not moved. Apply these transforms to both sets symmetrically:
33
+
34
+ - **Quote style on string defaults.** Normalize string-literal defaults in signatures to a single style — e.g., `kind: str = "Hnsw"` ↔ `kind: str = 'Hnsw'`. Pick one canonical form and apply to both sides.
35
+ - **Module qualification of stdlib helpers.** Strip module prefixes on well-known stdlib helpers when the unqualified form is importable at the call site: `dataclasses.field(...)` → `field(...)`, `typing.Optional[...]` → `Optional[...]`, `typing.List[...]` → `List[...]`. Do not collapse user-defined namespaces.
36
+ - **Public-API re-export resolution.** When `{source_root}/**/__init__.py` re-exports an internal symbol under a different public name (`from .internal import _Impl as Public`, or via `__all__`), resolve both sides to the public name before key-matching — otherwise a renamed re-export in the current scan shows up as "Removed `_Impl`" + "Added `Public`" instead of matching the baseline entry. Build the re-export map once per package by walking `__init__.py` files.
37
+
38
+ Record the set of transforms actually applied in workflow context — step-06 surfaces them in the Provenance section so a reviewer can tell which differences the diff collapsed and which were real.
39
+
32
40
  Normalize both sets for comparison:
33
- - Match by export name (primary key)
41
+ - Match by canonicalized export name (primary key)
34
42
  - Group by file for location-aware comparison
35
43
 
44
+ > **Longer-term fix.** The principled remedy is to persist `skf-create-skill`'s ast-grep ruleset to `{forge_version}/extraction-rules.yaml` at create time and have step-02 replay that exact ruleset. When the file is present, step-02 extraction becomes reproducible against the baseline and the canonicalization pass above becomes a no-op. Until then, normalization is the salvage remediation for provenance maps that predate extractor pinning.
45
+
36
46
  ### 2. Detect Added Exports
37
47
 
38
48
  **Launch subprocess (Pattern 4 — parallel execution):** In Claude Code, use multiple parallel Agent tool calls. In CLI, use `xargs -P` or equivalent.
@@ -106,6 +116,14 @@ If `{is_stack_skill}` is true:
106
116
 
107
117
  ### 5. Compile Structural Drift Section
108
118
 
119
+ **Rollup for high-volume uniform findings.** When ≥ 10 findings in the same table share one root cause (deleted source file, renamed module, entire package tree removed), you MAY collapse them into one row per root cause. Rollup rows replace the per-symbol `Export`/`Signature` columns with `Count` and `Representative symbols` (up to 3 names, `…` if more). Rollup applies to **Added Exports**, **Removed Exports**, and **Script/Asset Drift** tables — **not** to Changed Exports, which are heterogeneous by construction (signature changes and cross-file changes are inspected per-finding). Record which groupings were collapsed in workflow context for reviewer traceability.
120
+
121
+ **Rollup row form (Added / Removed Exports):**
122
+
123
+ | Root Cause | Count | Representative symbols | Location | Confidence |
124
+ |------------|-------|------------------------|----------|------------|
125
+ | {deleted/renamed path or similar} | {N} | `{sym1}`, `{sym2}`, `{sym3}`, … | {root-cause path} | {T1/T1-low} |
126
+
109
127
  Append to {outputFile}:
110
128
 
111
129
  ```markdown
@@ -47,7 +47,16 @@ Continue to section 2.
47
47
  ### 2. Query Original Knowledge Context
48
48
 
49
49
  Launch a subprocess (Pattern 3 — data operations) that:
50
- 1. Read the `qmd_collections` registry from `{sidecar_path}/forge-tier.yaml`. Find the entry where `skill_name` matches `{skill_name}` AND `type` is `"extraction"`. Use the `name` field from that entry as the collection to query. If no matching entry exists, log: "No QMD extraction collection found for {skill_name}. Semantic diff skipped." → Auto-proceed to {nextStepFile}.
50
+ 1. Read the `qmd_collections` registry from `{sidecar_path}/forge-tier.yaml`. Find the entry where `skill_name` matches `{skill_name}` AND `type` is `"extraction"`. Three cases must be handled distinctly collapsing them into "found vs. not found" silently degrades semantic diff when a collection is registered but never indexed.
51
+
52
+ - **Registry entry missing.** Log: "No QMD extraction collection found for `{skill_name}`. Semantic diff skipped." → Auto-proceed to {nextStepFile}.
53
+ - **Registry entry present but collection empty.** Run a pre-query probe — `qmd ls {collection_name}` (CLI) or the equivalent MCP call. If it reports zero files (`Files: 0 (updated never)` or an empty listing), the collection is registered but has never been indexed. Do **not** proceed to querying — queries will return nothing and the step would silently degrade.
54
+ - Log: "QMD collection `{collection_name}` is registered but empty. Run `qmd update` to (re-)index `{collection.path}`, then re-audit for full Deep-tier semantic coverage."
55
+ - Fall through to the **direct-content fallback** below instead of skipping outright.
56
+ - **Registry entry present and populated.** Use the `name` field from the registry entry as the collection to query. Proceed to bullet 2.
57
+
58
+ **Direct-content fallback** (used when the collection is registered but empty): load `SKILL.md` and `references/*.md` from the audited skill, then spot-check each documented export against the current source tree under `{source_root}` using the Deep-tier AST tooling this step already requires (ast_bridge; see step-02 §1 "Deep tier"). This fallback is reachable only from Deep tier — §1 short-circuits Quick/Forge/Forge+ before §2 runs, so AST tooling is guaranteed available here. Record findings with confidence label `T1-low-fallback` rather than T2 — this is direct content inspection, not QMD-backed semantic analysis. The step's output schema is otherwise unchanged; set `qmd_collection = null` in the Semantic Drift header and annotate: "Semantic diff ran in direct-content fallback mode — QMD collection was registered but empty."
59
+
51
60
  2. Queries for knowledge context around each export documented in the skill
52
61
  3. Retrieves: usage patterns, conventions, architectural context, dependency relationships
53
62
  4. Returns structured findings to parent
@@ -85,6 +85,14 @@ Apply scoring rules from {severityRulesFile}:
85
85
 
86
86
  ### 5. Compile Severity Classification Section
87
87
 
88
+ **Rollup inherits from step-03.** If step-03 §5 collapsed ≥ 10 same-kind findings into a single rollup row (deleted source file, renamed module, entire package tree removed), carry that rollup through to the matching severity table as one row — do not re-expand it here. Keep the existing 6-column severity table shape; the rollup encodes root cause, count, and representative symbols **inline in the `Finding` cell** rather than adding new columns, so rollup and per-item rows render cleanly in one table. Changed-signature and cross-file findings remain per-row; they were not eligible for rollup in step-03 and are not eligible here.
89
+
90
+ **Rollup row form (any severity table):**
91
+
92
+ | # | Finding | Type | Detail | Location | Confidence |
93
+ |---|---------|------|--------|----------|------------|
94
+ | N | {root cause} (×{Count}; rep: `{sym1}`, `{sym2}`, `{sym3}`, …) | {structural/semantic} | {shared detail} | {root-cause path} | {T1/T2} |
95
+
88
96
  Append to {outputFile}:
89
97
 
90
98
  ```markdown
@@ -77,6 +77,11 @@ Append to {outputFile}:
77
77
  {IF any CRITICAL or HIGH findings:}
78
78
  **Recommended:** Run `[US] Update Skill` workflow to apply priority remediations automatically.
79
79
 
80
+ {IF `audit_ref != baseline_ref` (source version bump detected in step-01 §5b):}
81
+ **Version preservation (non-destructive).** `update-skill` preserves the prior version at `{skill_group}/{baseline_version}/` unchanged and writes the new skill to `{skill_group}/{audit_version}/` (see `skf-update-skill/steps-c/step-04-merge.md` §6b, which creates the new version directory and leaves the previous one on disk). The `active` symlink at `{skill_group}/active` repoints to the new version (see `skf-update-skill/steps-c/step-06-write.md` §5b). On the next export, the prior version's export-manifest entry transitions to `status: archived` — files retained for rollback (see `skf-export-skill/steps-c/step-04-update-context.md`). Do **not** recommend `skf-drop-skill` + `skf-create-skill` for a version bump — that destroys the prior version's artifacts.
82
+
83
+ **Surface new entry points for the brief gate.** If the audit observed new top-level modules, renamed package trees, or new public entry points (new `__init__.py`, `index.ts`, `lib.rs`, or equivalent) that were not in the brief's original scope, call them out here. `update-skill` step-02 §1b detects new candidate files via heuristic and prompts `[P]romote` / `[S]kip` / `[U]pdate-brief`; surfacing them in advance makes that gate faster to resolve, or lets the user refine scope via `skf-brief-skill` before running update-skill.
84
+
80
85
  {IF only MEDIUM or LOW findings:}
81
86
  **Optional:** Minor drift detected. Manual updates sufficient, or run `[US] Update Skill` for automated remediation.
82
87
 
@@ -102,6 +107,9 @@ Append to {outputFile}:
102
107
  | **Provenance Map** | {provenance_map_path} |
103
108
  | **Provenance Age** | {days} days |
104
109
  | **Mode** | {normal / degraded} |
110
+ | **Baseline Ref / Commit** | `{baseline_ref}` @ `{baseline_commit_short}` |
111
+ | **Audit Ref / Commit** | `{audit_ref}` @ `{audit_commit_short}` ({audit_ref_source}) |
112
+ | **Upstream Latest** | `{latest_tag or remote_head or "(not fetched)"}` |
105
113
 
106
114
  **Confidence Legend:**
107
115
  - **T1:** AST extraction — high reliability, structural truth
@@ -67,14 +67,23 @@ scope:
67
67
  # - "code/core/src/manager-api/**"
68
68
  # - "code/core/src/preview-api/**"
69
69
  notes: "Optional notes about scope decisions"
70
- # Optional: amendment log for scope decisions made during create-skill §2a
70
+ # Optional: amendment log for scope decisions made during create-skill §2a,
71
+ # update-skill §1b (auth-doc), and update-skill §1c (scope-expansion).
71
72
  # amendments:
72
73
  # - path: "apps/docs/public/llms.txt"
73
- # action: "promoted" # "promoted" | "skipped"
74
+ # action: "promoted" # "promoted" | "skipped" | "demoted-include" | "demoted-exclude"
75
+ # category: "auth-doc" # "auth-doc" (default for legacy entries) | "scope-expansion"
74
76
  # reason: "authoritative AI docs — only source for canonical install command"
75
- # heuristic: "llms.txt"
77
+ # heuristic: "llms.txt" # required for auth-doc; absent for scope-expansion
76
78
  # date: "2026-04-11"
77
79
  # workflow: "skf-create-skill"
80
+ # - path: "python/cocoindex/_internal/api.py"
81
+ # action: "promoted"
82
+ # category: "scope-expansion"
83
+ # reason: "out-of-scope new public API — drift report drift-report-20260424-212355.md"
84
+ # evidence: "~70 new exports flagged out-of-scope by audit"
85
+ # date: "2026-04-25"
86
+ # workflow: "skf-update-skill"
78
87
  # Additional fields when scope.type is "component-library":
79
88
  # registry_path: "path/to/registry.ts" # Optional — auto-detected if omitted
80
89
  # ui_variants: # Optional — design system variants
@@ -87,37 +96,46 @@ scope:
87
96
 
88
97
  ### Scope Amendments (Optional)
89
98
 
90
- `scope.amendments[]` is an additive, optional audit log of scope decisions made by workflows after the brief was first authored. Its primary writer is `skf-create-skill` §2a (Discovered Authoritative Files Protocol), which appends entries when extraction discovers authoritative AI documentation files (`llms.txt`, `AGENTS.md`, etc.) that the original scope patterns excluded.
99
+ `scope.amendments[]` is an additive, optional audit log of scope decisions made by workflows after the brief was first authored. Two writer paths exist today:
100
+
101
+ - **Auth-doc promotions** (`category: "auth-doc"`) — `skf-create-skill` §2a and its mirror `skf-update-skill` §1b append entries when extraction discovers authoritative AI documentation files (`llms.txt`, `AGENTS.md`, etc.) that the original scope patterns excluded.
102
+ - **Scope-expansion promotions** (`category: "scope-expansion"`) — `skf-update-skill` §1c appends entries when an audit drift report flags out-of-scope new public API paths (typically a major-version restructure where the brief's `scope.include` no longer reflects the real surface).
91
103
 
92
104
  **Entry fields:**
93
105
 
94
106
  | Field | Type | Required | Description |
95
107
  |---|---|---|---|
96
- | `path` | string | yes | Relative path from source root to the file being amended. Matches the literal path added to `scope.include` (for `promoted` actions). |
97
- | `action` | string | yes | One of: `promoted` (file added to scope with a literal `scope.include` entry), `skipped` (user declined promotion; file remains out of scope, decision recorded to prevent re-prompting). |
98
- | `reason` | string | yes | Human-readable sentence explaining the decision. Either user-provided at prompt time or auto-generated ("authoritative AI docs matched heuristic {basename}"). |
99
- | `heuristic` | string | yes | The basename heuristic that matched (`llms.txt`, `AGENTS.md`, etc.) so future audits can verify the file still matches its original classification. |
108
+ | `path` | string | yes | Relative path (or glob, for `category: "scope-expansion"`) from source root to the file or tree being amended. For `promoted` actions this matches the literal entry added to `scope.include`. |
109
+ | `action` | string | yes | One of: `promoted` (path added to `scope.include`), `skipped` (user declined promotion; decision recorded to prevent re-prompting), `demoted-include` (path removed from `scope.include` — only valid with `category: "scope-expansion"`), `demoted-exclude` (path removed from `scope.exclude` — only valid with `category: "scope-expansion"`). |
110
+ | `category` | string | no | One of: `auth-doc` (default for entries without this field — the historical sole use case), `scope-expansion`. Distinguishes which workflow path wrote the entry and which writer-rules apply on re-runs. |
111
+ | `reason` | string | yes | Human-readable sentence explaining the decision. Either user-provided at prompt time or auto-generated. |
112
+ | `heuristic` | string | conditional | Required for `category: "auth-doc"` — the basename that matched (`llms.txt`, `AGENTS.md`, etc.). Omit for `category: "scope-expansion"`. |
113
+ | `evidence` | string | conditional | Required for `category: "scope-expansion"` — short rationale from the source signal (e.g., a drift-report finding's evidence one-liner). Omit for `category: "auth-doc"`. |
100
114
  | `date` | string | yes | ISO date (`YYYY-MM-DD`) when the amendment was recorded. |
101
- | `workflow` | string | yes | Workflow name that wrote the amendment (`skf-create-skill`, `skf-update-skill`). Identifies which workflow's §2a-equivalent made the decision. |
115
+ | `workflow` | string | yes | Workflow name that wrote the amendment (`skf-create-skill`, `skf-update-skill`). Identifies which workflow made the decision. |
116
+
117
+ **Promotion write-through:** When `action: "promoted"`, the workflow also appends the literal path to `scope.include`. This is a belt-and-suspenders design: future runs read `scope.include` during scope filtering and include the file in the filtered list automatically, so the §2a/§1b/§1c discovery loop finds no candidate and does not re-prompt. The `amendments[]` entry is the human-readable audit trail of *why* the path was added.
102
118
 
103
- **Promotion write-through:** When `action: "promoted"`, the workflow also appends the literal path to `scope.include`. This is a belt-and-suspenders design: future `skf-create-skill` runs read `scope.include` during §2 and include the file in the filtered list automatically, so §2a finds no candidate and does not re-prompt. The `amendments[]` entry is the human-readable audit trail of *why* the path was added.
119
+ **Skip recording:** When `action: "skipped"`, the workflow does NOT modify `scope.include` or `scope.exclude`. The amendment entry alone is enough to prevent re-prompting, because the discovery loop checks `amendments[]` before prompting.
104
120
 
105
- **Skip recording:** When `action: "skipped"`, the workflow does NOT modify `scope.include` or `scope.exclude`. The amendment entry alone is enough to prevent re-prompting, because §2a checks `amendments[]` before prompting.
121
+ **Demotion (scope-expansion only):** `demoted-include` removes a previously-promoted path from `scope.include` used when a prior `[P]` decision is reversed. `demoted-exclude` removes a path from `scope.exclude` used when a previously excluded path needs to be re-evaluated. Both write the structural change and append the amendment so future runs see the rationale. Demotion is not valid for `category: "auth-doc"`: auth-doc skips already prevent re-prompting without scope mutation.
106
122
 
107
- **Backward compatibility:** `scope.amendments` is optional. Briefs without this field validate unchanged. Treat missing as an empty list.
123
+ **Backward compatibility:** `scope.amendments` is optional. Briefs without this field validate unchanged. Treat missing as an empty list. Existing entries without `category` are equivalent to `category: "auth-doc"` — readers must default the field when absent.
108
124
 
109
125
  **Who reads `amendments[]`:**
110
126
 
111
- - `skf-create-skill` §2a consults it to avoid re-prompting on decided files.
112
- - `skf-update-skill` §1b (mirror of §2a) consults it for the same reason.
113
- - `skf-audit-skill` may optionally report on stale promotions (promoted files that no longer exist in source) as a future enhancement — not currently implemented.
127
+ - `skf-create-skill` §2a consults it to avoid re-prompting on decided auth-doc files.
128
+ - `skf-update-skill` §1b (mirror of §2a) consults it for the same auth-doc reason.
129
+ - `skf-update-skill` §1c consults it to avoid re-prompting on decided scope-expansion candidates and to honor prior `demoted-*` decisions.
130
+ - `skf-audit-skill` may optionally report on stale promotions (promoted paths that no longer exist in source) as a future enhancement — not currently implemented.
114
131
  - Humans reading the brief see the audit trail of non-obvious scope decisions.
115
132
 
116
133
  **Who writes `amendments[]`:**
117
134
 
118
- - `skf-create-skill` §2a (Discovered Authoritative Files Protocol)
119
- - `skf-update-skill` §1b (mirror of §2a applied during change detection)
120
- - Manual edits by the brief author are permitted but should include all required fields above.
135
+ - `skf-create-skill` §2a (Discovered Authoritative Files Protocol) — `category: "auth-doc"`
136
+ - `skf-update-skill` §1b (mirror of §2a applied during change detection) — `category: "auth-doc"`
137
+ - `skf-update-skill` §1c (Major-Version Scope Reconciliation) `category: "scope-expansion"`
138
+ - Manual edits by the brief author are permitted but should include all required fields above (and `category` when the entry is not an auth-doc decision).
121
139
 
122
140
  ## YAML Template
123
141
 
@@ -40,7 +40,7 @@ To prevent this, any tool invocation that may touch SKILL.md must run inside the
40
40
  2. **Execute.** Run the tool as specified in its section.
41
41
  3. **Verify.** After the tool completes, re-read the on-disk SKILL.md and compare its frontmatter `description` against `guarded_description` as **token streams**: split each string on whitespace (`str.split()` — any run of spaces/tabs/newlines collapses) and compare the resulting lists element-by-element. This catches content divergence (missing words, replaced phrases, truncation, angle-bracket re-introduction) while ignoring cosmetic whitespace changes that a tool may apply (trailing newline, re-wrapped quoted strings). Do NOT use a normalized-string equality — a tool that rewrites `"foo bar"` to `"foo bar"` (collapsed inner run) would trip a naive normalization even though no semantic content changed, and a tool that swapped one word would slip past a looser fuzzy match. Token-stream comparison is the sweet spot.
42
42
  4. **Restore on divergence.** If the post-tool description differs from `guarded_description` in any way other than whitespace normalization, write `guarded_description` back to the on-disk SKILL.md frontmatter and update the in-context copy to match. Record `description_guard_restored: true` with the tool name in context for the evidence report.
43
- 5. **Re-validate restored description.** After a restore, run `python3 {project-root}/src/shared/scripts/skf-validate-frontmatter.py <staging-skill-dir>/SKILL.md` against the on-disk file to confirm the restored description still satisfies the frontmatter contract (length limits, forbidden tokens, required fields). Capture `schema_revalidation_result` in context. If the validator exits non-zero OR reports failure for the `description` field: flip the Schema result back to `FAIL` in the evidence report (overriding any prior PASS/WARN from §2), record `description_guard_revalidation: FAIL` with the validator's diagnostic message, and continue — do not halt (step-09 health-check and result contract still need to run so the failure is surfaced through the normal artifact path).
43
+ 5. **Re-validate restored description.** After a restore, run `uv run {project-root}/src/shared/scripts/skf-validate-frontmatter.py <staging-skill-dir>/SKILL.md` against the on-disk file to confirm the restored description still satisfies the frontmatter contract (length limits, forbidden tokens, required fields). The script declares pyyaml in its PEP 723 inline metadata; `uv run` resolves it automatically (per `docs/getting-started.md`'s uv prereq), while bare `python3` would `ModuleNotFoundError` on a fresh interpreter. Capture `schema_revalidation_result` in context. If the validator exits non-zero OR reports failure for the `description` field: flip the Schema result back to `FAIL` in the evidence report (overriding any prior PASS/WARN from §2), record `description_guard_revalidation: FAIL` with the validator's diagnostic message, and continue — do not halt (step-09 health-check and result contract still need to run so the failure is surfaced through the normal artifact path).
44
44
 
45
45
  **What counts as divergence:**
46
46
 
@@ -95,7 +95,7 @@ For each entry in `target_context_files`:
95
95
 
96
96
  5. **Rewrite root paths for the current context file** using the generic rewrite algorithm from export-skill step-04 section 4d:
97
97
 
98
- For each snippet, parse the `root:` line (`root: {prefix}{skill-name}/`), strip the trailing `{skill-name}/` to extract the current prefix, and replace it with the current entry's `skill_root` if different. **Override guard:** If `snippet_skill_root_override` is set in config.yaml AND the extracted prefix equals the override value, skip the rewrite leave the root path untouched. See `skf-export-skill/steps-c/step-04-update-context.md` §4d for full semantics.
98
+ For each snippet, parse the `root:` line (`root: {prefix}{skill-name}/`), strip the trailing `{skill-name}/` to extract the current prefix, and replace it with the **effective target prefix** if different. The effective target prefix is `snippet_skill_root_override` when that key is set in config.yaml applied uniformly to every snippet so the managed section references the real on-disk location and never mixes override and per-IDE pathsotherwise the current entry's `skill_root`. See `skf-export-skill/steps-c/step-04-update-context.md` §4d for full semantics.
99
99
 
100
100
  6. **Sort skills alphabetically by name.** Count totals (skills, stack skills).
101
101
 
@@ -71,7 +71,7 @@ All other IDEs use AGENTS.md as the context file, each with its own skill direct
71
71
  **`snippet_skill_root_override` (optional):** Authoring repos where all skills live under one shared on-disk directory (e.g. `skills/`) that does not match any per-IDE skill root may set `snippet_skill_root_override: skills/` in `config.yaml`. When set:
72
72
 
73
73
  - `export-skill/step-03` §2.7 uses the override as `{skill_root}` for snippet generation instead of the IDE-mapped value
74
- - `export-skill/step-04` §4d (and its equivalents in `drop-skill/step-02` and `rename-skill/step-02`) skip the prefix rewrite for any snippet whose current prefix already equals the override — the managed section will reference the on-disk location instead of a per-IDE directory that does not exist
74
+ - `export-skill/step-04` §4d (and its equivalents in `drop-skill/step-02` and `rename-skill/step-02`) treat the override as the effective target prefix for the managed-section rebuild: snippets whose prefix already matches the override pass through unchanged, and any other prefix including per-IDE prefixes carried by sibling snippets that were exported before the override was adopted, and legacy `skills/` drafts — is rewritten to the override so the rebuilt managed section uniformly references the real on-disk location
75
75
 
76
76
  Consuming projects (the common case) omit the field and keep the default IDE-mapping behavior. The override is a narrow escape hatch for repos that author skills into `{skills_output_folder}` and never duplicate them into `.claude/skills/`-style directories.
77
77
 
@@ -166,11 +166,11 @@ For each snippet being included in the managed section:
166
166
 
167
167
  1. Read the `root:` value from the snippet's first line — it has the form `root: {prefix}{skill-name}/`
168
168
  2. Extract the current prefix by stripping the trailing `{skill-name}/` from the root value
169
- 3. **Override guard:** If `snippet_skill_root_override` is set in config.yaml AND the extracted prefix equals the override value, skip the rewrite for this snippetleave the root path untouched. This is the authoring-repo escape hatch: when the override is set, any snippet that already matches the override prefix is treated as ground truth and must not be rewritten to a per-IDE path that does not exist on disk.
170
- 4. Otherwise, compare the extracted prefix against the current target's `skill_root`
171
- 5. If they differ, replace the prefix with the target's `skill_root`, preserving the skill name
169
+ 3. **Resolve the effective target prefix:** If `snippet_skill_root_override` is set in config.yaml, use the override value as the effective target prefix for this rebuildby the override contract every skill in the repo lives under that single shared on-disk directory, so the managed section must reference the override for *every* snippet (not just the ones that already match it). Otherwise, use the current target's `skill_root` as the effective target prefix.
170
+ 4. Compare the extracted prefix against the effective target prefix
171
+ 5. If they differ, replace the prefix with the effective target prefix, preserving the skill name. When the override is set, this rewrites stale per-IDE prefixes carried by sibling snippets that were exported before the override was adopted, so the rebuilt managed section uniformly references the real on-disk location instead of mixing override and per-IDE paths.
172
172
  6. Example: if snippet has `root: .claude/skills/my-lib/` but target skill_root is `.windsurf/skills/`, rewrite to `root: .windsurf/skills/my-lib/`
173
- 7. Example: if snippet has legacy `root: skills/my-lib/` and target skill_root is `.github/skills/`, rewrite to `root: .github/skills/my-lib/` **unless** `snippet_skill_root_override: skills/` is set, in which case the `skills/` prefix is preserved
173
+ 7. Example: if `snippet_skill_root_override: skills/` is set, the effective target prefix is `skills/` regardless of the IDE mapping. A snippet with `root: skills/my-lib/` passes through unchanged; a sibling snippet with `root: .claude/skills/my-other/` (carried over from a pre-override export) is rewritten to `root: skills/my-other/`.
174
174
 
175
175
  This algorithm handles any IDE's skill root path — including future IDEs — without enumerating known prefixes. The legacy `skills/` prefix (no leading dot) may appear in draft snippets generated by create-skill/quick-skill before export.
176
176
 
@@ -199,7 +199,7 @@ For each entry in `target_context_files`:
199
199
 
200
200
  4. **Rewrite root paths for the current context file** using the generic rewrite algorithm from export-skill step-04 section 4d:
201
201
 
202
- For each snippet, parse the `root:` line (`root: {prefix}{skill-name}/`), strip the trailing `{skill-name}/` to extract the current prefix, and replace it with the current entry's `skill_root` if different. **Override guard:** If `snippet_skill_root_override` is set in config.yaml AND the extracted prefix equals the override value, skip the rewrite leave the root path untouched. See `skf-export-skill/steps-c/step-04-update-context.md` §4d for full semantics.
202
+ For each snippet, parse the `root:` line (`root: {prefix}{skill-name}/`), strip the trailing `{skill-name}/` to extract the current prefix, and replace it with the **effective target prefix** if different. The effective target prefix is `snippet_skill_root_override` when that key is set in config.yaml applied uniformly to every snippet so the managed section references the real on-disk location and never mixes override and per-IDE pathsotherwise the current entry's `skill_root`. See `skf-export-skill/steps-c/step-04-update-context.md` §4d for full semantics.
203
203
 
204
204
  5. **Sort skills alphabetically by name.** Count totals (skills, stack skills).
205
205
 
@@ -7,7 +7,7 @@ description: Initialize forge environment, detect tools, and set capability tier
7
7
 
8
8
  ## Overview
9
9
 
10
- Initializes the forge environment by detecting available tools, determining the capability tier (Quick/Forge/Forge+/Deep), writing persistent configuration, and optionally indexing the project for deep search. This is a fully autonomous workflow — no user interaction is required during execution.
10
+ Initializes the forge environment by detecting available tools, determining the capability tier (Quick/Forge/Forge+/Deep), and writing persistent configuration to `{project-root}/_bmad/_memory/forger-sidecar/`. When `ccc` (cocoindex-code) is available, also augments `.cocoindex_code/settings.yml` with SKF exclusion patterns and creates or refreshes the project's semantic-search index. On Deep tier, reconciles the QMD collection registry; whenever ccc is available, reconciles the CCC index registry as well. The workflow is autonomous with one optional gate orphaned QMD collection removal in step 3 (Deep tier only; default action: Keep) — which auto-resolves to the default when `{headless_mode}` is true.
11
11
 
12
12
  ## Role
13
13
 
@@ -17,7 +17,7 @@ You are a system executor performing environment resolution. Run each step in se
17
17
 
18
18
  These rules apply to every step in this workflow:
19
19
 
20
- - Fully autonomous — all steps auto-proceed with no user interaction until the final report
20
+ - Autonomous with one optional gate (step 3 orphan-removal prompt; default: Keep) — all other steps auto-proceed with no user interaction until the final report
21
21
  - Read each step file completely before taking any action
22
22
  - Follow the mandatory sequence in each step exactly — do not skip, reorder, or optimize
23
23
  - Only load one step file at a time — never preload future steps
@@ -29,9 +29,9 @@ These rules apply to every step in this workflow:
29
29
  | # | Step | File | Auto-proceed |
30
30
  |---|------|------|--------------|
31
31
  | 1 | Detect Tools & Set Tier | steps-c/step-01-detect-and-tier.md | Yes |
32
- | 1b | CCC Index | steps-c/step-01b-ccc-index.md | Yes |
32
+ | 1b | CCC Index (only when ccc is available) | steps-c/step-01b-ccc-index.md | Yes |
33
33
  | 2 | Write Config | steps-c/step-02-write-config.md | Yes |
34
- | 3 | QMD Hygiene | steps-c/step-03-auto-index.md | Yes |
34
+ | 3 | QMD + CCC Registry Hygiene | steps-c/step-03-auto-index.md | Yes |
35
35
  | 4 | Report | steps-c/step-04-report.md | Yes |
36
36
  | 5 | Workflow Health Check | steps-c/step-05-health-check.md | Yes |
37
37
 
@@ -39,17 +39,32 @@ These rules apply to every step in this workflow:
39
39
 
40
40
  | Aspect | Detail |
41
41
  |--------|--------|
42
- | **Inputs** | (none — fully autonomous) |
42
+ | **Inputs** | (none) |
43
+ | **Flags** | `--headless` / `-H` (skip prompts, auto-resolve gates to defaults); `--require-tier=<Quick\|Forge\|Forge+\|Deep>` (halt with failure if calculated tier does not satisfy the requirement) |
43
44
  | **Gates** | One optional: orphaned QMD collection removal (step 3, Deep tier only; default: Keep) |
44
- | **Outputs** | forge-tier.yaml, preferences.yaml, forge-data directories |
45
- | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
45
+ | **Outputs** | `forger-sidecar/forge-tier.yaml`, `forger-sidecar/preferences.yaml`, `{forge_data_folder}/`; when ccc is available, `.cocoindex_code/settings.yml` (exclusion patterns merged) and the project ccc index |
46
+ | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. step-04 emits a single-line `SKF_SETUP_RESULT_JSON: {…}` envelope after the human-readable banner so pipelines can parse the outcome without reading forge-tier.yaml. Schema documented in `steps-c/step-04-report.md` §4. |
47
+ | **Failure modes** | `--require-tier` not satisfied → step-04 prints a "REQUIRED TIER NOT MET" block, the JSON envelope sets `"require_tier_satisfied": false`, and the workflow halts before step-05. |
48
+ | **Exit codes** | The workflow runs as an LLM-driven sequence rather than a CLI process, so "exit code" describes the agent's terminal state for a calling pipeline that reads `SKF_SETUP_RESULT_JSON`. **0 — success** (no JSON `error` field, all writes completed, `require_tier_satisfied` is `true` or `null`). **1 — required-tier failure** (`require_tier_satisfied` is `false`; envelope still emitted with full state for diagnosis). **2 — write failure** (forge-tier.yaml or preferences.yaml could not be written; envelope `error` field names the path and reason). Pipelines should branch on the JSON `require_tier_satisfied` and `error` fields rather than process exit codes. |
46
49
 
47
50
  ## On Activation
48
51
 
49
- 1. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve:
52
+ 1. **Probe `uv` runtime.** Run `uv --version`. Every step in this workflow invokes shared Python helpers via `uv run` (PEP 723 inline metadata is what auto-resolves `pyyaml` for the helpers that need it — see `docs/getting-started.md`). If `uv` is missing, halt now with a single cohesive diagnostic rather than letting five separate steps each fail with `uv: command not found`:
53
+
54
+ "**Setup cannot proceed: `uv` is not installed.** SKF helpers depend on `uv` to auto-resolve their Python dependencies. Install it from <https://docs.astral.sh/uv/getting-started/installation/> and re-run `/skf-setup`. (See the Prerequisites section in <https://docs.astral.sh/uv/getting-started/installation/> or the SKF docs at `docs/getting-started.md` for details.)"
55
+
56
+ 2. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve:
50
57
  - `project_name` (from installer-generated config.yaml, not module.yaml), `output_folder`, `user_name`, `communication_language`, `document_output_language`
51
58
  - `skills_output_folder`, `forge_data_folder`, `sidecar_path`
52
59
 
53
- 2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false.
60
+ **If the file does not exist**, halt with a single cohesive "wrong directory" diagnostic rather than failing later with a generic file-read error from a downstream step:
61
+
62
+ "**Setup cannot proceed: `{project-root}/_bmad/skf/config.yaml` was not found.** This directory does not appear to be an SKF-initialised project. From the project root, run `npx bmad-module-skill-forge install` (or `npx bmad-method install` and add SKF as a custom module — see `docs/getting-started.md`), then re-run `/skf-setup`. If you ARE in the right project but the file was deleted, restore it from version control or re-run the SKF installer."
63
+
64
+ **If the file exists but is malformed YAML**, halt and surface the parser error along with the file path so the user can fix it directly: "**Setup cannot proceed: `{project-root}/_bmad/skf/config.yaml` is not valid YAML.** Parser error: {error_message}. Open the file and repair the YAML, or restore from version control."
65
+
66
+ 3. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false.
67
+
68
+ 4. **Resolve `{require_tier}`**: parse `--require-tier=<value>` from the invocation arguments. Accept exactly `Quick`, `Forge`, `Forge+`, or `Deep` (case-sensitive). If absent or unparseable, leave as null (no tier requirement).
54
69
 
55
- 3. Load, read the full file, and then execute `./steps-c/step-01-detect-and-tier.md` to begin the workflow.
70
+ 5. Load, read the full file, and then execute `./steps-c/step-01-detect-and-tier.md` to begin the workflow.
@@ -7,9 +7,9 @@
7
7
  | ast-grep | `ast-grep --version` | Returns version string without error |
8
8
  | gh | `gh --version` | Returns version string without error |
9
9
  | qmd | `qmd status` | Returns status indicating initialized and operational |
10
- | ccc | Step A: `ccc --help` Step B: `ccc doctor` | Step A: exits 0 (binary exists). Step B: daemon running, version string, model check OK |
10
+ | ccc | Step A: `ccc --help` Step B: `ccc doctor` | Step A: exits 0 AND help output identifies the binary as cocoindex-code. Step B: daemon healthy. See `steps-c/step-01-detect-and-tier.md` §7 for the full identity-marker procedure. |
11
11
 
12
- **Important:** Use verification commands, not existence checks (`which`, `command -v`). A tool must be functional, not just present on PATH. For daemon-based tools (ccc), verify both binary existence and daemon health.
12
+ **Important:** Use verification commands, not existence checks (`which`, `command -v`). A tool must be functional, not just present on PATH. For daemon-based tools (ccc), verify both binary identity and daemon health — a binary with the right name but the wrong implementation is a false positive, not a tool.
13
13
 
14
14
  ## Tier Calculation
15
15
 
@@ -1,111 +1,98 @@
1
1
  ---
2
2
  nextStepFile: './step-01b-ccc-index.md'
3
- tierRulesData: 'references/tier-rules.md'
3
+ # Resolve `{detectToolsHelper}` by probing `{detectToolsProbeOrder}` in order
4
+ # (installed SKF module path first, src/ dev-checkout fallback); first existing
5
+ # path wins. HALT if neither resolves — the script is the source of truth for
6
+ # tool detection and tier calculation; no fallback to prose-driven probes.
7
+ detectToolsProbeOrder:
8
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-tools.py'
9
+ - '{project-root}/src/shared/scripts/skf-detect-tools.py'
4
10
  ---
5
11
 
12
+ <!-- Config: communicate in {communication_language}. The first-run preamble below is user-visible — render it in the user's language. -->
13
+
6
14
  # Step 1: Detect Tools and Determine Tier
7
15
 
8
16
  ## STEP GOAL:
9
17
 
10
- Verify availability of the four forge tools (ast-grep, gh, qmd, ccc), read any existing configuration for re-run comparison, check for tier override, and calculate the capability tier.
18
+ Verify availability of the four forge tools (ast-grep, gh, qmd, ccc), read any existing configuration for re-run comparison, check for tier override, and calculate the capability tier — all via `{detectToolsHelper}` so the deterministic work is done once, by a tested script, never by the LLM.
11
19
 
12
20
  ## Rules
13
21
 
14
22
  - Focus only on tool detection and tier calculation — do not write any files (Step 02)
15
- - Do not skip any tool checkall 4 must be verified
16
- - Tool command failures are not errors — they indicate unavailability
23
+ - Never reimplement tool probes or the tier rules in prose the script is authoritative
24
+ - Tool command failures are not errors — they indicate unavailability (the script swallows them)
17
25
 
18
26
  ## MANDATORY SEQUENCE
19
27
 
20
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
21
-
22
- ### 1. Load Tier Rules
23
-
24
- Load and read {tierRulesData} for the tool detection commands and tier calculation logic.
25
-
26
- ### 2. Check for Existing Configuration (Re-run Detection)
28
+ ### 1. Check for Existing Configuration (Re-run Detection)
27
29
 
28
30
  **Read existing forge-tier.yaml** at `{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml`:
29
- - If exists: store the current `tier` value as `{previous_tier}` and `tier_detected_at` as `{previous_detection_date}`
30
- - If not found: set `{previous_tier}` to null (first run)
31
+
32
+ - If exists: store the current `tier` value as `{previous_tier}`, `tier_detected_at` as `{previous_detection_date}`, and the `tools` map as `{previous_tools}` (for the tool-set delta detection step-04 needs to surface newly-installed tools on same-tier re-runs).
33
+ - If not found: set `{previous_tier}` to null and `{previous_tools}` to an empty map (first run).
31
34
 
32
35
  **Read existing preferences.yaml** at `{project-root}/_bmad/_memory/forger-sidecar/preferences.yaml`:
36
+
33
37
  - If exists: check for `tier_override` value
34
38
  - If not found: set `{tier_override}` to null
35
39
 
36
- ### 3. Verify Tool: ast-grep
37
-
38
- Run: `ast-grep --version`
39
-
40
- - If succeeds: record `{ast_grep: true}` and store version string
41
- - If fails (command not found or error): record `{ast_grep: false}`
42
-
43
- ### 4. Verify Tool: gh
44
-
45
- Run: `gh --version`
46
-
47
- - If succeeds: record `{gh_cli: true}` and store version string
48
- - If fails: record `{gh_cli: false}`
49
-
50
- ### 5. Verify Tool: qmd
51
-
52
- Run: `qmd status`
53
-
54
- - If succeeds and indicates operational: record `{qmd: true}`
55
- - If fails or indicates not initialized: record `{qmd: false}`
56
-
57
- ### 6. Check Optional: Security Scan (SNYK_TOKEN)
58
-
59
- Check if the `SNYK_TOKEN` environment variable is set:
60
-
61
- - If `SNYK_TOKEN` is non-empty: record `{security_scan: true}`
62
- - If `SNYK_TOKEN` is empty or unset: record `{security_scan: false}`
63
-
64
- This is informational only — security scan availability does NOT affect the tier level. It is recorded in forge-tier.yaml so that create-skill's validation step can report actionable guidance when security scanning is unavailable.
65
-
66
- ### 7. Verify Tool: ccc (cocoindex-code)
40
+ **First-run preamble** when `{previous_tier}` is null AND `{headless_mode}` is `false`, display this preamble before continuing so the user knows what is about to happen and can abort cleanly with Esc / Ctrl+C before any writes:
67
41
 
68
- **Step A Binary existence:** Run `ccc --help`
42
+ "**About to set up the forge.** This workflow will:
69
43
 
70
- - If exits 0: binary confirmed. Continue to Step B.
71
- - If fails (command not found or error): record `{ccc: false}`. Skip Step B.
44
+ - Detect available tools (ast-grep, gh, qmd, ccc) read-only probes only
45
+ - Write `{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml` (capability tier + tool state)
46
+ - Write `{project-root}/_bmad/_memory/forger-sidecar/preferences.yaml` (first-run defaults)
47
+ - Create `{forge_data_folder}/` if missing
48
+ - When ccc is available: augment `{project-root}/.cocoindex_code/settings.yml` with SKF exclusion patterns, then create or refresh the project ccc index
72
49
 
73
- **Step BDaemon health:** Run `ccc doctor`
50
+ **About tiers:** SKF picks one of four tiers (Quick / Forge / Forge+ / Deep) based on which tools are installed. **All four are fully usable** higher tiers add power, they don't fix gaps. If you're new and only have a base Python install, Quick tier is the right starting point and the report at the end will show you exactly which tools to install if you want to climb later.
74
51
 
75
- - If daemon is running and model check OK: record `{ccc: true, ccc_daemon: "healthy"}` and store version string from output
76
- - If daemon is not running: record `{ccc: true, ccc_daemon: "stopped"}` — binary available, daemon needs starting. Step-01b will handle this.
77
- - If error or timeout: record `{ccc: true, ccc_daemon: "error"}` — binary works but daemon has issues.
52
+ Press Esc or Ctrl+C now if this isn't the right project no files have been written yet."
78
53
 
79
- ccc availability gates the Forge+ tier and enhances Deep tier when present.
54
+ ### 2. Run Detection Helper
80
55
 
81
- ### 8. Calculate Tier
56
+ Build the Bash invocation. Start with `uv run {detectToolsHelper}` — `uv run` honors the script's PEP 723 inline-metadata dependency declarations (resolved automatically on first call; cached thereafter), which is why uv is documented as a runtime prerequisite in `docs/getting-started.md`. Do NOT call the script via bare `python3` — `python3` ignores PEP 723 metadata, and on a fresh interpreter without the declared deps installed system-wide the call fails with `ModuleNotFoundError`. If `{tier_override}` is non-null, append `--tier-override "{tier_override}"` (the script handles invalid values by flagging them — do NOT pre-validate). If `{require_tier}` (resolved in On Activation) is non-null, append `--require-tier "{require_tier}"`. Then execute.
82
57
 
83
- **If `{tier_override}` is set and valid (Quick, Forge, Forge+, or Deep):**
84
- - Use `{tier_override}` as `{calculated_tier}`
85
- - Note that override is active for the report step
58
+ The script (see `src/shared/scripts/skf-detect-tools.py` docstring for the full `DETECT_OUTPUT_SCHEMA`) probes ast-grep / gh / qmd / ccc concurrently with two-step verification for qmd and ccc (binary-identity check + daemon-health check, including the `CocoIndex Code` identity-marker substring check that rejects PATH-shadowing aliases). It applies the 4-rule tier table, performs the tier-override sanity check (override is honored but flagged unsafe when underlying tools are missing), and evaluates `--require-tier` using a tool-prerequisite check (Deep does NOT subsume Forge+ Deep does not require ccc). Output is one JSON document on stdout.
86
59
 
87
- **If no override, apply tier rules from {tierRulesData} in order — the first matching rule wins. Do not continue checking once a match is found:**
88
- - `{ast_grep}` AND `{gh_cli}` AND `{qmd}` all true → **Deep**
89
- - `{ast_grep}` AND `{ccc}` both true, but NOT (`{gh_cli}` AND `{qmd}`) → **Forge+**
90
- - `{ast_grep}` true (regardless of ccc/gh/qmd) → **Forge**
91
- - Otherwise → **Quick**
60
+ ### 3. Parse Output and Set Context Flags
92
61
 
93
- **If `{tier_override}` is set but invalid:** ignore it, use detected tier, flag for warning in report.
62
+ From the JSON, set these context flags. Field paths are relative to the script's top-level object.
94
63
 
95
- ### 9. Auto-Proceed
64
+ From `tools`:
96
65
 
97
- "**Proceeding to CCC index check...**"
66
+ - `{ast_grep}` `tools.ast_grep.available`
67
+ - `{ast_grep_version}` ← `tools.ast_grep.version`
68
+ - `{gh_cli}` ← `tools.gh_cli.available`
69
+ - `{gh_cli_version}` ← `tools.gh_cli.version`
70
+ - `{qmd}` ← `tools.qmd.available`
71
+ - `{qmd_status}` ← `tools.qmd.status` (`"absent" | "daemon_stopped" | "healthy"` — drives the climb-hint distinction in step-04)
72
+ - `{qmd_version}` ← `tools.qmd.version`
73
+ - `{ccc}` ← `tools.ccc.available`
74
+ - `{ccc_daemon}` ← `tools.ccc.daemon` (`"healthy" | "stopped" | "error" | null`)
75
+ - `{ccc_version}` ← `tools.ccc.version`
76
+ - `{security_scan}` ← `tools.security_scan.available` (informational only — never affects tier)
98
77
 
99
- #### Menu Handling Logic:
78
+ From `tier`:
100
79
 
101
- - After tier calculation is complete, immediately load, read entire file, then execute {nextStepFile}
80
+ - `{calculated_tier}` ← `tier.calculated` the tier downstream steps act on
81
+ - `{detected_tier}` ← `tier.detected` — what would have been chosen without override
82
+ - `{tier_override_active}` ← `tier.override_applied`
83
+ - `{tier_override_invalid}` ← `tier.override_invalid`
84
+ - `{tier_override_invalid_value}` ← `tier.override_invalid_value`
85
+ - `{tier_override_invalid_suggestion}` ← `tier.override_invalid_suggestion` (closest valid tier name from a fuzzy match — `null` when no candidate cleared the cutoff or when override is valid; consumed by step-04's invalid-override note as a "did you mean ...?" hint)
86
+ - `{tier_override_unsafe}` ← `tier.override_unsafe`
87
+ - `{tier_override_unsafe_missing}` ← `tier.override_unsafe_missing` (a list — step-04 joins with `", "` for display)
102
88
 
103
- #### EXECUTION RULES:
89
+ From `require_tier`:
104
90
 
105
- - This is an auto-proceed step with no user choices
106
- - Proceed directly to next step after detection and calculation
91
+ - `{require_tier_satisfied}` `require_tier.satisfied` (`true | false | null`; null when `--require-tier` was not set)
92
+ - `{require_tier_failure_missing_tools}` `require_tier.missing_tools` (a list)
107
93
 
108
- ## CRITICAL STEP COMPLETION NOTE
94
+ **The script is the source of truth.** Every tier-rules edge case (override-honored-but-unsafe, Deep-doesn't-subsume-Forge+, qmd-binary-vs-daemon distinction, ccc-identity-marker rejection of foreign binaries) is locked into the test suite at `test/test-skf-detect-tools.py`. Substituting prose-driven logic for the script's output here will cause subtle re-run regressions that the prompt's prose is no longer detailed enough to catch.
109
95
 
110
- ONLY WHEN all 4 core tools have been verified, optional security scan checked, and the tier calculated will you load and read fully `{nextStepFile}` to execute the CCC index check step.
96
+ ### 4. Auto-Proceed
111
97
 
98
+ After context flags are populated, display "**Proceeding to CCC index check...**", then load `{nextStepFile}`, read it fully, and execute it.