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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +9 -7
- package/docs/_data/pinned.yaml +5 -2
- package/docs/{RELEASING.md → _internal/RELEASING.md} +87 -61
- package/docs/{STABILITY.md → _internal/STABILITY.md} +2 -2
- package/docs/bmad-synergy.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/why-skf.md +5 -4
- package/package.json +2 -7
- package/release-audits/v1.0.0-launch-audit.md +313 -2
- package/src/knowledge/ccc-bridge.md +1 -1
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +134 -0
- package/src/shared/scripts/skf-detect-tools.py +359 -0
- package/src/shared/scripts/skf-emit-result-envelope.py +419 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +413 -0
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +324 -0
- package/src/shared/scripts/skf-preflight.py +14 -4
- package/src/shared/scripts/skf-qmd-classify-collections.py +212 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +9 -3
- package/src/skf-audit-skill/steps-c/step-01-init.md +60 -0
- package/src/skf-audit-skill/steps-c/step-02-re-index.md +7 -1
- package/src/skf-audit-skill/steps-c/step-03-structural-diff.md +19 -1
- package/src/skf-audit-skill/steps-c/step-04-semantic-diff.md +10 -1
- package/src/skf-audit-skill/steps-c/step-05-severity-classify.md +8 -0
- package/src/skf-audit-skill/steps-c/step-06-report.md +8 -0
- package/src/skf-brief-skill/assets/skill-brief-schema.md +36 -18
- package/src/skf-create-skill/steps-c/step-06-validate.md +1 -1
- package/src/skf-drop-skill/steps-c/step-02-execute.md +1 -1
- package/src/skf-export-skill/assets/managed-section-format.md +1 -1
- package/src/skf-export-skill/steps-c/step-04-update-context.md +4 -4
- package/src/skf-rename-skill/steps-c/step-02-execute.md +1 -1
- package/src/skf-setup/SKILL.md +25 -10
- package/src/skf-setup/references/tier-rules.md +2 -2
- package/src/skf-setup/steps-c/step-01-detect-and-tier.md +58 -71
- package/src/skf-setup/steps-c/step-01b-ccc-index.md +49 -66
- package/src/skf-setup/steps-c/step-02-write-config.md +59 -86
- package/src/skf-setup/steps-c/step-03-auto-index.md +73 -91
- package/src/skf-setup/steps-c/step-04-report.md +135 -11
- package/src/skf-setup/steps-c/step-05-health-check.md +4 -2
- package/src/skf-test-skill/steps-c/step-01-init.md +4 -4
- package/src/skf-test-skill/steps-c/step-03-coverage-check.md +1 -1
- package/src/skf-update-skill/steps-c/step-02-detect-changes.md +123 -0
- 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
|
-
|
|
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"`.
|
|
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.
|
|
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.
|
|
97
|
-
| `action` | string | yes | One of: `promoted` (
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
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
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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-
|
|
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
|
-
-
|
|
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 `
|
|
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
|
|
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 paths — otherwise 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`)
|
|
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. **
|
|
170
|
-
4.
|
|
171
|
-
5. If they differ, replace the prefix with the target
|
|
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 rebuild — by 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
|
|
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
|
|
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 paths — otherwise 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
|
|
package/src/skf-setup/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
- If
|
|
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
|
-
|
|
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
|
-
**
|
|
42
|
+
"**About to set up the forge.** This workflow will:
|
|
69
43
|
|
|
70
|
-
-
|
|
71
|
-
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
54
|
+
### 2. Run Detection Helper
|
|
80
55
|
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
+
From the JSON, set these context flags. Field paths are relative to the script's top-level object.
|
|
94
63
|
|
|
95
|
-
|
|
64
|
+
From `tools`:
|
|
96
65
|
|
|
97
|
-
|
|
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
|
-
|
|
78
|
+
From `tier`:
|
|
100
79
|
|
|
101
|
-
-
|
|
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
|
-
|
|
89
|
+
From `require_tier`:
|
|
104
90
|
|
|
105
|
-
-
|
|
106
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|