bmad-module-skill-forge 1.5.0 → 1.6.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/docs/_data/pinned.yaml +1 -1
- package/package.json +1 -1
- package/src/shared/scripts/skf-description-guard.py +33 -80
- package/src/shared/scripts/skf-detect-tools.py +50 -8
- package/src/shared/scripts/skf-manifest-ops.py +22 -6
- package/src/skf-analyze-source/references/continue.md +3 -1
- package/src/skf-analyze-source/references/identify-units.md +8 -4
- package/src/skf-analyze-source/references/init.md +6 -4
- package/src/skf-analyze-source/references/scan-project.md +6 -2
- package/src/skf-audit-skill/references/init.md +9 -3
- package/src/skf-audit-skill/references/structural-diff.md +9 -3
- package/src/skf-brief-skill/SKILL.md +3 -1
- package/src/skf-brief-skill/references/analyze-target.md +22 -10
- package/src/skf-brief-skill/references/gather-intent.md +71 -6
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +1 -1
- package/src/skf-brief-skill/references/scope-definition.md +8 -4
- package/src/skf-brief-skill/references/write-brief.md +24 -8
- package/src/skf-create-skill/assets/compile-assembly-rules.md +1 -1
- package/src/skf-create-skill/assets/skill-sections.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +2 -0
- package/src/skf-create-skill/references/validate.md +2 -2
- package/src/skf-create-stack-skill/references/detect-integrations.md +7 -2
- package/src/skf-create-stack-skill/references/detect-manifests.md +6 -2
- package/src/skf-create-stack-skill/references/parallel-extract.md +6 -2
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +6 -1
- package/src/skf-export-skill/references/summary.md +4 -4
- package/src/skf-export-skill/references/update-context.md +2 -2
- package/src/skf-test-skill/SKILL.md +1 -1
- package/src/skf-test-skill/references/coherence-check.md +14 -7
- package/src/skf-update-skill/references/init.md +8 -1
- package/src/skf-verify-stack/references/coverage.md +6 -2
- package/src/skf-verify-stack/references/init.md +6 -2
- package/src/skf-verify-stack/references/integrations.md +6 -2
- package/src/skf-verify-stack/references/report.md +6 -2
- package/src/skf-verify-stack/references/requirements.md +7 -3
- package/src/skf-verify-stack/references/synthesize.md +6 -2
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'analyze-target.md'
|
|
3
|
+
ratifyTargetFile: 'confirm-brief.md'
|
|
3
4
|
forgeTierFile: '{sidecar_path}/forge-tier.yaml'
|
|
4
5
|
descriptionVoiceExamplesFile: 'assets/description-voice-examples.md'
|
|
5
6
|
headlessArgsFile: 'references/headless-args.md'
|
|
6
7
|
headlessSourceAuthorityDetectionFile: 'references/headless-source-authority-detection.md'
|
|
7
8
|
portfolioSimilarityCheckFile: 'references/portfolio-similarity-check.md'
|
|
8
9
|
draftCheckpointFile: 'references/draft-checkpoint.md'
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
validateBriefInputsProbeOrder:
|
|
11
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-inputs.py'
|
|
12
|
+
- '{project-root}/src/shared/scripts/skf-validate-brief-inputs.py'
|
|
13
|
+
validateBriefSchemaProbeOrder:
|
|
14
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
|
|
15
|
+
- '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
|
|
16
|
+
emitBriefEnvelopeProbeOrder:
|
|
17
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
18
|
+
- '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
11
19
|
---
|
|
12
20
|
|
|
13
21
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -80,7 +88,7 @@ Let's get started."
|
|
|
80
88
|
|
|
81
89
|
### 3. Gather Target Repository
|
|
82
90
|
|
|
83
|
-
This section has
|
|
91
|
+
This section has four sub-flows. Execute exactly one branch — 3.1a *or* 3.2 *or* 3.3 — based on the user's response in 3.1, then end with the shared confirmation (3.1a is terminal for §3 and jumps directly to confirm-brief.md). Do not mix branches.
|
|
84
92
|
|
|
85
93
|
#### 3.1 Collect target
|
|
86
94
|
|
|
@@ -90,6 +98,7 @@ Provide one of:
|
|
|
90
98
|
- A **GitHub URL** (e.g., `https://github.com/org/repo`)
|
|
91
99
|
- A **local path** (e.g., `/path/to/project`)
|
|
92
100
|
- **Documentation URLs** for a docs-only skill (e.g., `https://docs.stripe.com/api`) — use this when no source code is available (SaaS, closed-source)
|
|
101
|
+
- A **path to an existing `skill-brief.yaml`** (file path or a directory containing one) — use this to ratify a brief produced by another workflow (e.g. `skf-analyze-source`) without re-deriving fields
|
|
93
102
|
|
|
94
103
|
Or type `cancel` / `exit` / `[X]` to leave without writing anything.
|
|
95
104
|
|
|
@@ -98,10 +107,64 @@ Or type `cancel` / `exit` / `[X]` to leave without writing anything.
|
|
|
98
107
|
Wait for user response. Branch on the response:
|
|
99
108
|
|
|
100
109
|
- Empty input, `cancel`, `exit`, `[X]`, `q`, or `:q` → Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Cancellation here is non-destructive — no files have been written yet by step 1. Headless mode never reaches this branch (the GATE in §8 short-circuits the interactive sub-flows).
|
|
110
|
+
- Path that resolves to an existing `skill-brief.yaml` (file path ending in `skill-brief.yaml` that exists, OR a directory containing a `skill-brief.yaml`) → §3.1a
|
|
101
111
|
- Documentation URLs only (no source location) → §3.2
|
|
102
112
|
- GitHub URL or local filesystem path → §3.3
|
|
103
113
|
- Any other free-form question (e.g. "what is this?", "show me an example", "how does SKF work?") → answer briefly, re-display the prompt
|
|
104
114
|
|
|
115
|
+
#### 3.1a Branch — Ratify existing brief
|
|
116
|
+
|
|
117
|
+
This branch handles the AN→BS handoff: another workflow (typically `skf-analyze-source`) has already produced a `skill-brief.yaml`, and the user wants to review and confirm it without re-running gather-intent / analyze-target / scope-definition. **This branch is interactive-only** — headless mode reaches step 1 via §8's GATE with `target_repo`/`skill_name` args, not via this §3.1a path. (A headless ratify equivalent could be added later via a `from_brief` argument; out of scope here.)
|
|
118
|
+
|
|
119
|
+
Resolve the path:
|
|
120
|
+
|
|
121
|
+
- If the user's input ends in `skill-brief.yaml` and points at an existing file → that is the brief path.
|
|
122
|
+
- Otherwise (input was a directory) → the brief path is `<input>/skill-brief.yaml`.
|
|
123
|
+
|
|
124
|
+
**Validate the brief against the schema** before presenting it. Resolve `{validateBriefSchemaHelper}` from `{validateBriefSchemaProbeOrder}` (first existing path wins; HALT if no candidate exists), then:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv run {validateBriefSchemaHelper} <resolved-brief-path>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}`. Apply the result:
|
|
131
|
+
|
|
132
|
+
- **`valid: false`** — surface the `errors[]` messages and the `halt_reason` to the user, then re-display the §3.1 prompt for a corrected path (or another target altogether). Do not HALT — the user may simply have pointed at the wrong file. Example: `"**Brief at `{path}` is invalid:** {first error message}. Pick a different brief, or supply a repo / docs URL instead."`
|
|
133
|
+
- **`valid: true`** — proceed with the parsed `brief` payload.
|
|
134
|
+
|
|
135
|
+
Surface any non-empty `warnings[]` as a single grouped line (`"**Brief validation warnings:** {joined warnings}"`), then present the ratify menu:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
**Existing brief detected at `{path}`.**
|
|
139
|
+
|
|
140
|
+
- **Name:** {brief.name}
|
|
141
|
+
- **Target:** {brief.source_repo}
|
|
142
|
+
- **Description:** "{brief.description}"
|
|
143
|
+
- **Created:** {brief.created} by {brief.created_by}
|
|
144
|
+
- **Scope:** {brief.scope.type}
|
|
145
|
+
|
|
146
|
+
Pick one:
|
|
147
|
+
[R] Ratify — review in step 4 and write (overwriting this file once approved)
|
|
148
|
+
[F] Start fresh — discard this brief and re-prompt for a target
|
|
149
|
+
[X] Cancel and exit
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Wait for user response. Branch:
|
|
153
|
+
|
|
154
|
+
- **[R] Ratify** — Confirm overwrite up front: store `ratify_mode: true` and `ratify_source_path: <resolved-brief-path>` in workflow context. Hydrate the brief context variables from the parsed `brief` payload so step 4 has the same field set it normally derives from steps 1-3:
|
|
155
|
+
- `name` ← `brief.name`; `version` ← `brief.version`; `target_version` ← `brief.target_version`
|
|
156
|
+
- `source_repo` ← `brief.source_repo`; `source_type` ← `brief.source_type`; `source_authority` ← `brief.source_authority`; `doc_urls` ← `brief.doc_urls`
|
|
157
|
+
- `language` ← `brief.language`; `description` ← `brief.description`; `forge_tier` ← `brief.forge_tier`
|
|
158
|
+
- `created` ← `brief.created`; `created_by` ← `brief.created_by`
|
|
159
|
+
- `scope.type` / `scope.include` / `scope.exclude` / `scope.notes` / `scope.rationale` ← `brief.scope.*`
|
|
160
|
+
- `scripts_intent` ← `brief.scripts_intent`; `assets_intent` ← `brief.assets_intent`
|
|
161
|
+
|
|
162
|
+
Then load, read entirely, and execute `{ratifyTargetFile}` — bypassing §3.1b/§3.2/§3.3, §3b, §4, §5, §6, §7, §7b, and §8 entirely. Skip step 2 (analyze-target) and step 3 (scope-definition) — both would re-derive fields already on disk. The forward chain resumes at step 4 (confirm-brief) where the user gets the standard review pass and can still adjust fields inline via §4.
|
|
163
|
+
|
|
164
|
+
- **[F] Start fresh** — discard the loaded brief and re-display §3.1 above (the user is now at the same point as if they had typed nothing).
|
|
165
|
+
- **[X] Cancel** — Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Non-destructive.
|
|
166
|
+
- **Any other input** — treat as a fresh §3.1 response and re-evaluate the routing branches above (a typed GitHub URL after seeing the menu means "I changed my mind, brief this repo instead").
|
|
167
|
+
|
|
105
168
|
#### 3.2 Branch — Documentation URLs (docs-only)
|
|
106
169
|
|
|
107
170
|
- Set `source_type: "docs-only"` in the brief data
|
|
@@ -302,14 +365,16 @@ Display: "**Select:** [C] Continue to Target Analysis · [X] Cancel and exit"
|
|
|
302
365
|
#### EXECUTION RULES:
|
|
303
366
|
|
|
304
367
|
- ALWAYS halt and wait for user input after presenting menu
|
|
305
|
-
- **
|
|
368
|
+
- **Resolve `{validateBriefInputsHelper}`** from `{validateBriefInputsProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
369
|
+
|
|
370
|
+
- **GATE [default: use args]** — If `{headless_mode}`, consume pre-supplied arguments and auto-proceed. The full argument set (required/optional, defaults, halt codes, enum values) is documented in `{headlessArgsFile}` — load it now if you need to look up a specific argument. Validation is delegated to `{validateBriefInputsHelper}`; the table is the canonical operator-facing documentation, the script enforces it.
|
|
306
371
|
|
|
307
372
|
**Preset merge (before validation).** If the headless args include a `preset` field, load `{sidecar_path}/brief-presets/{preset}.yaml` and merge its contents as defaults — explicit args override preset values, key by key. The preset file is YAML; if it does not exist, log `"warn: preset '{name}' not found at {path} — proceeding without preset"` and continue (do not HALT). If it parses but contains unknown fields, log per-field warnings and pass through unchanged (the validator's KNOWN_FIELDS check will catch any that survive). Drop the `preset` key itself from the merged dict before passing to the validator (it is consumed at this level and is not a brief field).
|
|
308
373
|
|
|
309
|
-
**Delegate validation to `{
|
|
374
|
+
**Delegate validation to `{validateBriefInputsHelper}`** instead of reasoning through the table rules in prose:
|
|
310
375
|
|
|
311
376
|
```bash
|
|
312
|
-
echo '<headless-args-as-json>' | uv run {
|
|
377
|
+
echo '<headless-args-as-json>' | uv run {validateBriefInputsHelper}
|
|
313
378
|
```
|
|
314
379
|
|
|
315
380
|
The script returns a JSON envelope: `{valid, errors[], warnings[], normalized, halt_reason}`. Apply the result deterministically:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Headless Argument Table
|
|
2
2
|
|
|
3
|
-
Loaded by step 1 §8 only when `{headless_mode}` is true. Canonical operator-facing documentation for the argument set consumed at step 1's GATE; the `{
|
|
3
|
+
Loaded by step 1 §8 only when `{headless_mode}` is true. Canonical operator-facing documentation for the argument set consumed at step 1's GATE; the `{validateBriefInputsHelper}` enforces these rules deterministically (its `KNOWN_FIELDS` set must stay in sync with this table).
|
|
4
4
|
|
|
5
5
|
| Argument | Required | Default | Notes |
|
|
6
6
|
|----------|----------|---------|-------|
|
|
@@ -41,7 +41,7 @@ echo '{
|
|
|
41
41
|
"skill_name": "{skill-name}",
|
|
42
42
|
"created_at": "{current ISO date}"
|
|
43
43
|
// include "status": "pending" only when embed verification failed
|
|
44
|
-
}' | uv run {
|
|
44
|
+
}' | uv run {forgeTierRwHelper} register-qmd-collection --target {forgeTierFile}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
The script handles the upsert deterministically (replace existing entry with same `name`, else append) and preserves all other forge-tier state (tools, tier, ccc_index, ccc_index_registry, other qmd_collections entries) — no need to reason about YAML re-rendering or section comments.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'confirm-brief.md'
|
|
3
3
|
scopeTemplatesFile: 'assets/scope-templates.md'
|
|
4
|
-
|
|
4
|
+
recommendScopeTypeProbeOrder:
|
|
5
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-recommend-scope-type.py'
|
|
6
|
+
- '{project-root}/src/shared/scripts/skf-recommend-scope-type.py'
|
|
5
7
|
advancedElicitationSkill: '/bmad-advanced-elicitation'
|
|
6
8
|
partyModeSkill: '/bmad-party-mode'
|
|
7
9
|
---
|
|
@@ -82,7 +84,9 @@ Load `{scopeTemplatesPath}` for the scope type options ([F], [M], [P], [C], [R])
|
|
|
82
84
|
|
|
83
85
|
**Recommend a scope type — don't present the five options as equal weight.** SKILL.md states this workflow "steers toward the smaller, sharper version when scope is unclear" — surface that opinion at decision time. Use the analysis from step 2 and the user's intent from step 1 to pick the best-fit recommendation, then present the menu with that option marked as the suggested default.
|
|
84
86
|
|
|
85
|
-
**
|
|
87
|
+
**Resolve `{recommendScopeTypeHelper}`** from `{recommendScopeTypeProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
88
|
+
|
|
89
|
+
**Delegate the recommendation to `{recommendScopeTypeHelper}`** instead of walking the heuristic ladder in prose. The script is the single source of truth for the five-rule ladder (component-registry → reference-app keywords → specific-modules naming/count → narrow-public-api → default full-library) plus the docs-only short-circuit. Both the interactive recommendation and the §6 headless GATE invoke the same script — same inputs, same outputs, no drift.
|
|
86
90
|
|
|
87
91
|
**Fetch registry-file contents before building the payload.** Step-02 §4.1 fetches `package.json` plus the entry-point files but does not fetch `registry.ts` / `components.ts` — the deep-match branch of the component-registry rule needs those contents. Scan the tree for any of `registry.ts` / `registry.tsx` / `components.ts` / `components.tsx` (any depth). For each match, fetch its contents in **one message with N parallel Bash calls** (`gh api repos/{owner}/{repo}/contents/{path}` for GitHub, file reads for local), then base64-decode the responses together. Skip the fetch if the tree contains no registry files.
|
|
88
92
|
|
|
@@ -97,7 +101,7 @@ echo '{
|
|
|
97
101
|
"entry_files": [{"path": "<registry path>", "content": "<contents>"}, ...],
|
|
98
102
|
"source_type": "source",
|
|
99
103
|
"mode": "interactive"
|
|
100
|
-
}' | uv run {
|
|
104
|
+
}' | uv run {recommendScopeTypeHelper}
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
`entry_files` carries the registry contents fetched above; omit when no registry files exist in the tree. `mode: "interactive"` activates the content-inspection branch of the component-registry rule (10+ entries or `Component[]` annotation); the headless GATE in §6 uses `mode: "headless"` which falls back to presence-only matching. `source_type: "docs-only"` short-circuits to `docs-only` regardless of the other signals.
|
|
@@ -182,7 +186,7 @@ Display: **Select an Option:** [A] Advanced Elicitation [P] Party Mode [C] Conti
|
|
|
182
186
|
- ALWAYS halt and wait for user input after presenting menu
|
|
183
187
|
- **GATE [default: C]** — If `{headless_mode}`: consume the headless inputs from step 1 in priority order:
|
|
184
188
|
- If `scope_type` was supplied, use it (must match one of the six valid types) and skip the §2c template menu.
|
|
185
|
-
- Otherwise auto-select via `{
|
|
189
|
+
- Otherwise auto-select via `{recommendScopeTypeHelper}` — invoke the script with the **same payload shape** documented in §2c but with `mode: "headless"` (presence-only matching for the component-registry rule, since `entry_files` may not be available without an interactive context). Use the returned `scope_type` and log `"headless: scope_type={value} from heuristic={matched_heuristic}"`. The script's docs-only short-circuit handles `source_type=docs-only` automatically.
|
|
186
190
|
- If `include`/`exclude` were supplied, use them verbatim (split on comma) instead of running the boundary prompts in §3.
|
|
187
191
|
- If `scripts_intent`/`assets_intent` were supplied, record them and skip §5b; otherwise default to `detect`.
|
|
188
192
|
- Set `scope.rationale`: `recommended`/`heuristic` from the script (or `recommended = scope_type` arg, `heuristic = "user-supplied-arg"` when `scope_type` was passed); `chosen = <resolved type>`; `accepted_recommendation = (no scope_type arg)`; `reason = "<script rationale>"` (auto path) or `"headless: scope_type supplied as argument"` (arg path); `recorded = {date}`. No prompt — headless never asks "why".
|
|
@@ -3,9 +3,15 @@ briefSchemaFile: 'assets/skill-brief-schema.md'
|
|
|
3
3
|
versionResolutionFile: 'references/version-resolution.md'
|
|
4
4
|
qmdRegistrationFile: 'references/qmd-collection-registration.md'
|
|
5
5
|
nextStepFile: 'health-check.md'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
writeSkillBriefProbeOrder:
|
|
7
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
|
|
8
|
+
- '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
|
|
9
|
+
emitBriefEnvelopeProbeOrder:
|
|
10
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
11
|
+
- '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
12
|
+
forgeTierRwProbeOrder:
|
|
13
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
|
|
14
|
+
- '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
|
|
9
15
|
forgeTierFile: '{sidecar_path}/forge-tier.yaml'
|
|
10
16
|
---
|
|
11
17
|
|
|
@@ -30,7 +36,9 @@ To generate the complete skill-brief.yaml from the approved brief data and write
|
|
|
30
36
|
|
|
31
37
|
### 1. Reference the Schema (LLM context only)
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
**Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
40
|
+
|
|
41
|
+
`{briefSchemaPath}` and `{versionResolutionFile}` document the brief contract for human readers. The deterministic enforcement of that contract lives in `{writeSkillBriefHelper}` and its JSON Schema artifact at `src/shared/scripts/schemas/skill-brief.v1.json`. Load `{briefSchemaPath}` only if you need to explain a specific field to the user during inline adjustments — otherwise skip the read; the script is the source of truth.
|
|
34
42
|
|
|
35
43
|
### 2. Resolve Output Path
|
|
36
44
|
|
|
@@ -44,7 +52,11 @@ The script's atomic-write helper creates parent directories as needed (`mkdir -p
|
|
|
44
52
|
|
|
45
53
|
Before writing, check whether the resolved target path already exists.
|
|
46
54
|
|
|
47
|
-
**
|
|
55
|
+
**Ratify path (`ratify_mode: true` in workflow context):**
|
|
56
|
+
|
|
57
|
+
The user already confirmed overwrite up-front at step 1 §3.1a when they chose `[R] Ratify` against the same file. Skip the interactive prompt below; log a single-line `brief-skill: ratify-mode auto-overwriting existing brief at {path}` and proceed to §3. Resuming the prompt would be friction — the user has by now also reviewed and approved the brief at step 4. This auto-accept does not apply to headless mode (handled below) nor to the standard interactive path (also below).
|
|
58
|
+
|
|
59
|
+
**Interactive (`{headless_mode}` is false, `ratify_mode` not set):**
|
|
48
60
|
|
|
49
61
|
If the file exists, present:
|
|
50
62
|
|
|
@@ -94,7 +106,7 @@ Assemble the brief context as a **flat** JSON object — every approved value is
|
|
|
94
106
|
Pipe it into the writer script with the `--from-flat` flag:
|
|
95
107
|
|
|
96
108
|
```bash
|
|
97
|
-
echo '<context-json>' | uv run {
|
|
109
|
+
echo '<context-json>' | uv run {writeSkillBriefHelper} write --target {resolved-target-path} --from-flat
|
|
98
110
|
```
|
|
99
111
|
|
|
100
112
|
The script translates flat → nested internally, drops the null optional fields, and runs the same schema validation and atomic write as before — pass every key always, the writer decides what reaches the YAML.
|
|
@@ -121,20 +133,22 @@ The script:
|
|
|
121
133
|
|
|
122
134
|
### 4b. Headless Result Envelope (Canonical)
|
|
123
135
|
|
|
136
|
+
**Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
137
|
+
|
|
124
138
|
This section is the canonical envelope-emission reference for the workflow. Every headless emission — the success terminal here and every HARD HALT in step 1/02/05 — uses this contract. Remote sites point here instead of restating it.
|
|
125
139
|
|
|
126
140
|
**Success (this call site only — emitted from §3 directly):**
|
|
127
141
|
|
|
128
142
|
```bash
|
|
129
143
|
echo '{"status":"success","brief_path":"<from §3 response>","skill_name":"<name>","version":"<from §3 response>","language":"<language>","scope_type":"<scope.type>","halt_reason":null}' | \
|
|
130
|
-
uv run {
|
|
144
|
+
uv run {emitBriefEnvelopeHelper} emit
|
|
131
145
|
```
|
|
132
146
|
|
|
133
147
|
**Error (used by every HARD HALT site):**
|
|
134
148
|
|
|
135
149
|
```bash
|
|
136
150
|
echo '{"status":"error","skill_name":"<name>","halt_reason":"<reason>"}' | \
|
|
137
|
-
uv run {
|
|
151
|
+
uv run {emitBriefEnvelopeHelper} emit --target stderr
|
|
138
152
|
```
|
|
139
153
|
|
|
140
154
|
When the HALT fires before `skill_name` has been resolved (step 1 §1 pre-flight write probe, step 1 §8 input-missing on a malformed args bundle), pass the partially-gathered value or the literal `"unknown"` — the script accepts any non-empty string at this position.
|
|
@@ -149,6 +163,8 @@ When `{headless_mode}` is false, skip this section silently — no envelope is e
|
|
|
149
163
|
|
|
150
164
|
### 5. QMD Collection Registration (Deep Tier Only)
|
|
151
165
|
|
|
166
|
+
**Resolve `{forgeTierRwHelper}`** from `{forgeTierRwProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
167
|
+
|
|
152
168
|
**IF forge tier is Deep AND QMD tool is available:** load `{qmdRegistrationFile}` and follow the procedure there to index the brief into a QMD collection and update the forge-tier registry.
|
|
153
169
|
|
|
154
170
|
**IF forge tier is NOT Deep OR QMD is not available:** skip this section silently — do not load `{qmdRegistrationFile}`. No messaging.
|
|
@@ -15,7 +15,7 @@ description: >
|
|
|
15
15
|
**Frontmatter rules:**
|
|
16
16
|
|
|
17
17
|
- `name`: lowercase alphanumeric + hyphens only, must match the skill output directory name. Prefer gerund form (`processing-pdfs`, `analyzing-spreadsheets`) for clarity.
|
|
18
|
-
- `description`: non-empty, max 1024 chars, optimized for agent discovery. **MUST use third-person voice** ("Processes Excel files..." not "I can help you..." or "You can use this to..."). Inconsistent point-of-view causes discovery problems since the description is injected into the system prompt.
|
|
18
|
+
- `description`: non-empty, max 1024 chars, optimized for agent discovery. **MUST use third-person voice** ("Processes Excel files..." not "I can help you..." or "You can use this to...") AND **MUST include a trigger phrase** so agents know when to match — one of `Use when …`, `Triggers on …`, or `Reach for this when …`. When using `Use when `, follow with a **gerund** (`Use when building/processing/analyzing X…`) or a noun-phrase clause (`Use when the user requests to "X"`); never a bare indicative verb like `builds`/`processes`, because `skill-check --fix` will prepend a literal `Use when ` to a description missing the trigger phrase, producing ungrammatical output (`Use when builds X…`). Inconsistent point-of-view or a missing trigger phrase causes discovery problems since the description is injected into the system prompt. See `skf-brief-skill/assets/description-voice-examples.md` for the canonical voice palette.
|
|
19
19
|
- **`description` must NOT contain angle brackets** — neither standalone placeholders like `<name>`, `<component>`, `<path>`, nor inline generics like `` `Array<T>` `` or `` `Meta<typeof X>` ``. Both `skill-check`'s `description_field` validator and `tessl`'s deterministic description check parse the frontmatter description as a raw string and reject any `<` or `>`, regardless of markdown context (backticks do NOT protect content here). A rejected description fails the review with 0% score. When the natural phrasing would use angle brackets:
|
|
20
20
|
- Prefer the curly-brace form in prose: `{name}`, `{component-id}`, `{path}` — readable and tessl-safe.
|
|
21
21
|
- Uppercase placeholders are also acceptable: `NAME`, `COMPONENT_ID`.
|
|
@@ -18,7 +18,7 @@ description: >
|
|
|
18
18
|
**Frontmatter rules (agentskills.io specification):**
|
|
19
19
|
|
|
20
20
|
- `name`: 1-64 characters, lowercase alphanumeric + hyphens only, must match parent directory name. Prefer gerund form (`processing-pdfs`, `analyzing-spreadsheets`) for clarity; noun phrases and action-oriented forms are acceptable alternatives.
|
|
21
|
-
- `description`: 1-1024 characters, trigger-optimized for agent matching. MUST use third-person voice ("Processes..." not "I can..." or "You can...").
|
|
21
|
+
- `description`: 1-1024 characters, trigger-optimized for agent matching. MUST use third-person voice ("Processes..." not "I can..." or "You can...") AND include a trigger phrase ("Use when ...", "Triggers on ...", "Reach for this when ..."). When using "Use when ", follow with a gerund (`Use when building/processing/analyzing X…`) or noun-phrase clause — never a bare indicative verb, since `skill-check --fix` will prepend a literal "Use when " to descriptions missing the phrase. See `skf-brief-skill/assets/description-voice-examples.md`.
|
|
22
22
|
- Only 6 fields permitted: `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools`
|
|
23
23
|
- `version` and `author` belong in metadata.json, NOT in frontmatter
|
|
24
24
|
|
|
@@ -400,6 +400,8 @@ When using ast-grep for extraction, be aware of these documented limitations:
|
|
|
400
400
|
|
|
401
401
|
7. **Python class patterns with bases/colon return zero (ast-grep 0.42.x):** The patterns `class $NAME($$$BASES)` and `class $NAME($$$BASES):` return zero matches on real Python sources with ast-grep 0.42.0, even on files containing dozens of subclassed public classes. `find_code_by_rule` also rejects the bare inline rule without `kind` as `Rule must specify a set of AST kinds to match. Try adding \`kind\` rule.` **Workaround:** Use the minimal `class $NAME` pattern with `kind: class_definition` (YAML) or `ast-grep run -p 'class $NAME' -l python --json=stream` (CLI), then post-filter names via the `^[^_]` regex. The `^[^_]` constraint enforces the "public" filter since ast-grep's base-match rule is what's broken, not the name-match rule. See the Python — public classes recipe above.
|
|
402
402
|
|
|
403
|
+
8. **Rust `pub fn` any-pattern returns zero; bare `pub fn $NAME` over-captures (ast-grep 0.42.x):** The `rust-public-functions` recipe's `any:` of `pub fn $NAME($$$PARAMS) -> $RET` / `pub fn $NAME($$$PARAMS)` returns "No matches found" on real Rust sources with ast-grep 0.42.2, even on crates containing 200+ public functions. Dropping to the bare `pub fn $NAME` pattern matches, but over-captures restricted-visibility functions such as `pub(crate) fn` / `pub(super) fn`, which are **not** public API. **Workaround:** Prefer a visibility-constrained source grep — `rg '^\s*pub fn ' <src>` filtered to exclude lines beginning `pub(` — cross-checked against the AN-verified public surface, at T1-low confidence. Never silently accept zero results for Rust public functions, and never treat a bare `pub fn $NAME` match set as the public API without stripping `pub(...)`-restricted items. See the Rust — public functions recipe above.
|
|
404
|
+
|
|
403
405
|
### Component Library Demo/Example Auto-Exclusion
|
|
404
406
|
|
|
405
407
|
When `scope.type: "component-library"`, auto-detect and propose demo/example exclusions before extraction begins. **User confirmation is required before applying** — some `examples/` directories contain API-level code.
|
|
@@ -61,11 +61,11 @@ Run the external skill-check tool against the compiled skill staging directory.
|
|
|
61
61
|
**Flag probe (run once, cache the result for §4 and §5 re-invocations):**
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
npx skill-check --help 2>/dev/null | grep -- --no-security-scan
|
|
64
|
+
npx skill-check check --help 2>/dev/null | grep -- --no-security-scan
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
- If the probe matches `--no-security-scan`: set `{security_scan_flag} = "--no-security-scan"`.
|
|
68
|
-
- Else run a second probe — `npx skill-check --help 2>/dev/null | grep -- --skip-security` — and if it matches, set `{security_scan_flag} = "--skip-security"`.
|
|
68
|
+
- Else run a second probe — `npx skill-check check --help 2>/dev/null | grep -- --skip-security` — and if it matches, set `{security_scan_flag} = "--skip-security"`.
|
|
69
69
|
- If neither flag exists: set `{security_scan_flag} = ""` (empty) AND set `{skill_check_flag_fallback} = true`. Skip §2 and §4 automated flows entirely — fall through to §3 manual frontmatter validation. Record in evidence-report: `skill_check_flag_probe: neither --no-security-scan nor --skip-security supported by installed skill-check; validation performed manually`.
|
|
70
70
|
|
|
71
71
|
**If a security-scan-disable flag was resolved (probe succeeded):**
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
nextStepFile: 'compile-stack.md'
|
|
3
3
|
integrationPatterns: 'references/integration-patterns.md'
|
|
4
4
|
composeModeRules: 'references/compose-mode-rules.md'
|
|
5
|
-
|
|
5
|
+
pairIntersectProbeOrder:
|
|
6
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-pair-intersect.py'
|
|
7
|
+
- '{project-root}/src/shared/scripts/skf-pair-intersect.py'
|
|
6
8
|
---
|
|
7
9
|
|
|
8
10
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -35,8 +37,11 @@ From `confirmed_dependencies`, conceptually you have N*(N-1)/2 unordered pairs.
|
|
|
35
37
|
]
|
|
36
38
|
```
|
|
37
39
|
2. **Invoke the script** via stdin (or a temp file under `{forge_data_folder}/` if stdin piping is unavailable):
|
|
40
|
+
|
|
41
|
+
**Resolve `{pairIntersectHelper}`** from `{pairIntersectProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
42
|
+
|
|
38
43
|
```bash
|
|
39
|
-
uv run {
|
|
44
|
+
uv run {pairIntersectHelper} intersect --libraries -
|
|
40
45
|
```
|
|
41
46
|
piping the libraries JSON on stdin. The script emits:
|
|
42
47
|
```json
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'rank-and-confirm.md'
|
|
3
3
|
manifestPatterns: 'references/manifest-patterns.md'
|
|
4
|
-
|
|
4
|
+
scanManifestsProbeOrder:
|
|
5
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-scan-manifests.py'
|
|
6
|
+
- '{project-root}/src/shared/scripts/skf-scan-manifests.py'
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -90,8 +92,10 @@ Store the explicit list as `raw_dependencies` and skip to [Display Detection Sum
|
|
|
90
92
|
|
|
91
93
|
Invoke the deterministic manifest scanner — it walks the project root, parses every recognised manifest, dedupes the production dep set, and flags monorepo layout:
|
|
92
94
|
|
|
95
|
+
**Resolve `{scanManifestsHelper}`** from `{scanManifestsProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
96
|
+
|
|
93
97
|
```bash
|
|
94
|
-
uv run {
|
|
98
|
+
uv run {scanManifestsHelper} scan {scan_root}
|
|
95
99
|
```
|
|
96
100
|
|
|
97
101
|
Where `{scan_root}` is the project root path. Load `{manifestPatterns}` for the ecosystem reference table that documents supported filenames, dependency keys, and normalisation rules; the script implements exactly that table (npm/pnpm/yarn, python pip/poetry/pdm, rust cargo, go modules, java/kotlin maven + gradle, ruby bundler, composer, swift package manager). Exclusion patterns (`node_modules/`, `.venv/`, `vendor/`, `dist/`, `build/`, `target/`, `.git/`, hidden dirs) are applied internally.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'detect-integrations.md'
|
|
3
|
-
|
|
3
|
+
enumerateStackSkillsProbeOrder:
|
|
4
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-enumerate-stack-skills.py'
|
|
5
|
+
- '{project-root}/src/shared/scripts/skf-enumerate-stack-skills.py'
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -33,8 +35,10 @@ Use `skill_package_path` (stored in step 2 and optionally refreshed above) direc
|
|
|
33
35
|
|
|
34
36
|
**Exports resolution order (H1) — script-driven:** Do NOT walk per-skill `metadata.json` → `references/` → SKILL.md by hand. Invoke the helper once at step entry to compute the full inventory for every confirmed skill in one deterministic call:
|
|
35
37
|
|
|
38
|
+
**Resolve `{enumerateStackSkillsHelper}`** from `{enumerateStackSkillsProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
39
|
+
|
|
36
40
|
```bash
|
|
37
|
-
uv run {
|
|
41
|
+
uv run {enumerateStackSkillsHelper} enumerate {skills_output_folder}
|
|
38
42
|
```
|
|
39
43
|
|
|
40
44
|
The script emits JSON of the form:
|
|
@@ -48,8 +48,9 @@ Emit a single warning (once, not per snippet) and present resolution options bef
|
|
|
48
48
|
> - **(a) Set override** — add `snippet_skill_root_override: {observed_prefix}` to `config.yaml`. Snippets keep their on-disk prefix; the managed section references the real location.
|
|
49
49
|
> - **(b) Proceed with IDE mapping** — step 4 will rewrite every snippet's root path to the IDE's skill_root. Use this only if the IDE's skill directory actually contains the skill files.
|
|
50
50
|
> - **(c) Cancel** — abort export and investigate.
|
|
51
|
+
> - **(d) Use observed prefix for this run only** — set the effective snippet root to the single observed on-disk prefix for this export run, without writing to `config.yaml`. The managed section references the real on-disk location. (Only offered when exactly one prefix was observed.)
|
|
51
52
|
>
|
|
52
|
-
> If multiple distinct prefixes were observed, the snippets disagree with each other — investigate before choosing (a).
|
|
53
|
+
> If multiple distinct prefixes were observed, the snippets disagree with each other — investigate before choosing (a) or (d).
|
|
53
54
|
|
|
54
55
|
Wait for user choice.
|
|
55
56
|
|
|
@@ -69,6 +70,10 @@ Continue to §2. Step 4's root-rewrite algorithm will rewrite every snippet's pr
|
|
|
69
70
|
|
|
70
71
|
HALT. Exit code 6, `halt_reason: "user-cancelled"`. No files written.
|
|
71
72
|
|
|
73
|
+
### (d) Use observed prefix for this run only
|
|
74
|
+
|
|
75
|
+
**Only available when `observed_prefixes` contains exactly one value.** Set the effective snippet root for this run to that single observed prefix — do NOT mutate `config.yaml`. Step 4's root-rewrite algorithm uses this value as the reference instead of `target_context_files[0].skill_root`, so snippets keep their on-disk prefix and the managed section resolves to the real location. Print a one-line hint: "Persist `snippet_skill_root_override: {observed_prefix}` in config.yaml to skip this prompt on future exports." Continue to §2.
|
|
76
|
+
|
|
72
77
|
## No-mismatch fast path
|
|
73
78
|
|
|
74
79
|
**If all observed prefixes match the reference `skill_root` (or no existing snippets were found):** no warning, no gate. Return control to §1b and proceed silently to §2.
|
|
@@ -89,18 +89,18 @@ Your skill is generated from an external dependency for local use.
|
|
|
89
89
|
|
|
90
90
|
### 3. Use It In This Project
|
|
91
91
|
|
|
92
|
-
Always show this section (regardless of `source_authority`) so the author can install the skill they just forged into the current project and try it immediately
|
|
92
|
+
Always show this section (regardless of `source_authority`) so the author can install the skill they just forged into the current project and try it immediately.
|
|
93
|
+
|
|
94
|
+
**Rendering rule for the command below — must be obeyed:** substitute `{resolved_skill_package}` with the actual resolved path before printing (do not leave the placeholder for the user to replace). The substituted path must be either absolute (starts with `/`) or `./`-prefixed. A bare relative path like `skills/foo/1.0.0/foo` is parsed by the `skills` CLI as `https://github.com/skills/foo.git` (org/repo GitHub lookup) and will fail with an authentication error. If `{resolved_skill_package}` resolves to a relative path, prefix it with `./` before emitting.
|
|
93
95
|
|
|
94
96
|
"### Use It In This Project
|
|
95
97
|
|
|
96
|
-
To install this skill into the current project right now:
|
|
98
|
+
To install this skill into the current project right now, run:
|
|
97
99
|
|
|
98
100
|
```
|
|
99
101
|
npx skills add {resolved_skill_package}
|
|
100
102
|
```
|
|
101
103
|
|
|
102
|
-
Replace `{resolved_skill_package}` with the absolute path to the package folder shown above (from step 2), e.g. `{skills_output_folder}/{skill-name}/{version}/{skill-name}/`.
|
|
103
|
-
|
|
104
104
|
For other source formats (registry name, git URL, tarball, etc.), see the **Installation → Source Formats** section at <https://www.npmjs.com/package/skills>.
|
|
105
105
|
|
|
106
106
|
If the installed skill's commands are not available in the same IDE session, reload plugins (e.g. for Claude Code users, run `/reload-plugins`) or restart your AI editor. Next time you will see the command `/{skill-name}` (e.g. `/{skill-name}`)."
|
|
@@ -335,10 +335,10 @@ On success per file, report: "**{target-file} updated successfully.** Verified b
|
|
|
335
335
|
5. Write the updated manifest atomically via `{manifestOpsHelper}` after all skills in the batch have been applied. For each skill / version pair, invoke:
|
|
336
336
|
|
|
337
337
|
```bash
|
|
338
|
-
python3 {manifestOpsHelper} {skills_output_folder} set {skill-name} {version}
|
|
338
|
+
python3 {manifestOpsHelper} {skills_output_folder} set {skill-name} {version} --ides {ides_written}
|
|
339
339
|
```
|
|
340
340
|
|
|
341
|
-
The helper handles v2-schema validation, v1→v2 migration, and `platforms`→`ides` rename internally — no in-prompt JSON manipulation needed. Each `set` invocation
|
|
341
|
+
`{ides_written}` is the comma-joined sorted IDE set computed in step 3. The helper handles v2-schema validation, v1→v2 migration, and `platforms`→`ides` rename internally — no in-prompt JSON manipulation needed. Each `set` invocation unions the supplied `--ides` into the version entry's existing `ides` (deduplicated, sorted) server-side and refreshes `last_exported`. After all skills have been set, re-read the manifest via `{manifestOpsHelper} {skills_output_folder} read` to confirm the final state matches expectations.
|
|
342
342
|
|
|
343
343
|
**Dry-run mode:** Do NOT update the manifest. Display: "**[DRY RUN] Export manifest would be updated for {skill-name-list} — ides: {ides_written}.**" (list every skill in `skill_batch`)
|
|
344
344
|
|
|
@@ -51,7 +51,7 @@ These rules apply to every step in this workflow:
|
|
|
51
51
|
|--------|--------|
|
|
52
52
|
| **Inputs** | skill_name [required]; optional flags: `--allow-workspace-drift`, `--no-discovery` (skip §4b Discovery Testing), `--no-health-check` (skip §7 health-check dispatch), `--tier=<Quick\|Forge\|Forge+\|Deep>` (bypass forge-tier.yaml sidecar requirement), `--threshold=<N>` (override pass threshold; CLI wins over `workflow.default_threshold` scalar) |
|
|
53
53
|
| **Gates** | step 6: Confirm Gate [C] |
|
|
54
|
-
| **Outputs** | test-report-{skill_name}.md with completeness score and result (PASS/FAIL); per-run `skf-test-skill-result-{run_id}.json` and `skf-test-skill-result-latest.json` written atomically under `{forge_version}/` |
|
|
54
|
+
| **Outputs** | per-run `test-report-{skill_name}-{run_id}.md` with completeness score and result (PASS/FAIL); per-run `skf-test-skill-result-{run_id}.json` and `skf-test-skill-result-latest.json` written atomically under `{forge_version}/` — downstream consumers (export-skill, update-skill `--from-test-report`) glob `test-report-{skill_name}-*.md` and pick newest by parsed ISO timestamp |
|
|
55
55
|
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
|
|
56
56
|
| **Exit codes** | See "Exit Codes" below |
|
|
57
57
|
|
|
@@ -4,7 +4,9 @@ outputFile: '{forge_version}/test-report-{skill_name}-{run_id}.md'
|
|
|
4
4
|
outputFormatsFile: 'assets/output-section-formats.md'
|
|
5
5
|
scoringRulesFile: 'references/scoring-rules.md'
|
|
6
6
|
migrationSectionRules: 'references/migration-section-rules.md'
|
|
7
|
-
|
|
7
|
+
scanSkillMdStructureProbeOrder:
|
|
8
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-scan-skill-md-structure.py'
|
|
9
|
+
- '{project-root}/src/shared/scripts/skf-scan-skill-md-structure.py'
|
|
8
10
|
---
|
|
9
11
|
|
|
10
12
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -26,11 +28,13 @@ Read `testMode` from `{outputFile}` frontmatter.
|
|
|
26
28
|
|
|
27
29
|
Perform the following explicit checks (no hand-waving — most use a single deterministic script; severity assignments are binding; do not relax them).
|
|
28
30
|
|
|
29
|
-
**
|
|
31
|
+
**Resolve `{scanSkillMdStructureHelper}`** from `{scanSkillMdStructureProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
32
|
+
|
|
33
|
+
**2.0 Run the structural scan.** Invoke `{scanSkillMdStructureHelper}` twice and parse the JSON outputs. These results back §§2.1, 2.2, 2.3, and 2.6 — do not re-implement those checks with grep/sed/awk loops.
|
|
30
34
|
|
|
31
35
|
```bash
|
|
32
|
-
uv run {
|
|
33
|
-
uv run {
|
|
36
|
+
uv run {scanSkillMdStructureHelper} scan {skill-md} --required-sections
|
|
37
|
+
uv run {scanSkillMdStructureHelper} scan {skill-md}
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
The first call returns `{ description: {satisfied, matched_synonym, tried[]}, usage: {...}, api_surface: {...} }`. The second returns `{ unbalanced_fences, fence_count, bare_opening_fences[{line,text}], table_drift[{line,section,expected_cols,actual_cols,row}] }`. Hold both JSON blobs for the checks below.
|
|
@@ -47,9 +51,12 @@ The script matches case-insensitively and tolerates `##`/`###` heading levels. S
|
|
|
47
51
|
|
|
48
52
|
**2.3 Language tags on opening fences.** Read `bare_opening_fences[]` from the second JSON blob. The script already runs the stateful open/close scan — closing fences are never reported. For each entry, emit a **Medium severity** finding: `naive-coherence — opening code fence at line {entry.line} missing language tag`.
|
|
49
53
|
|
|
50
|
-
**2.4 Exports cross-used in
|
|
51
|
-
-
|
|
52
|
-
- **
|
|
54
|
+
**2.4 Exports cross-used in a usage-family section.** For each function name reported in the step 3 subagent inventory (`exports[].name` where `kind == "function"` or `kind == "method"`):
|
|
55
|
+
- Determine the usage-family search scope:
|
|
56
|
+
- **Single-body skill** (no `references/` directory, or `## Full*` sections carry real content): the span from §2.1's `matched_synonym` anchor to the next `^## ` anchor.
|
|
57
|
+
- **Split-body skill** (a `references/` directory exists alongside SKILL.md AND the SKILL.md `## Full*` sections are stubs/pointers): the union of EVERY usage-family heading present in SKILL.md (`Usage`/`Examples`/`How to use`/`Quickstart`/`Quick Start`/`Getting Started`/`Common Workflows`/`Key API Summary`), each from its anchor to the next `^## ` anchor, PLUS the full text of every file under `references/`.
|
|
58
|
+
- `grep -c "{export.name}"` across that scope and sum the counts.
|
|
59
|
+
- **Zero occurrences across the entire scope → High severity** finding: `naive-coherence — exported {kind} \`{name}\` is not referenced in any usage-family section or reference file`. This catches the "documented but unused" failure mode that trivially fails discovery testing. A method referenced in any usage-family section OR any `references/` file satisfies the check.
|
|
53
60
|
|
|
54
61
|
**2.5 Async/sync consistency.** For every export with `async` in its description prose (grep for `\basync\b` in the description segment), check the corresponding code example segment for `await` / `async` keywords:
|
|
55
62
|
- Description says async + example shows no `await` → **High severity** finding: `naive-coherence — \`{name}\` described as async but example lacks \`await\``
|
|
@@ -42,7 +42,14 @@ Provide either:
|
|
|
42
42
|
Resolve the path to an absolute skill folder location.
|
|
43
43
|
|
|
44
44
|
**If `--from-test-report` was provided (or user references a test report):**
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
`skf-test-skill` writes timestamped test-report filenames (`test-report-{skill_name}-{ISO-TIMESTAMP}-{HASH}.md`) — there is no exact-name `test-report-{skill_name}.md` on disk. Locate the most recent report by glob, mirroring `skf-export-skill/references/load-skill.md §4b`:
|
|
47
|
+
|
|
48
|
+
1. Glob `{forge_data_folder}/{skill_name}/{active_version}/test-report-{skill_name}-*.md` (i.e. `{forge_version}/test-report-{skill_name}-*.md`). Sort matches descending by the parsed ISO-timestamp segment in the filename (`YYYYMMDDTHHMMSSZ` between the skill name and the hash — `sort -r` on the filename works because the timestamp is the first variable component). Take the first match.
|
|
49
|
+
2. If the versioned glob returns nothing, fall back to the same glob at the flat path `{forge_data_folder}/{skill_name}/test-report-{skill_name}-*.md`. Pick the newest by parsed timestamp.
|
|
50
|
+
3. If neither glob returns anything, look for the stable companion `skf-test-skill-result-latest.json` in the same two directories (versioned first, then flat). Read the report path from `outputs[]` per the canonical contract documented at `shared/references/output-contract-schema.md` (resolved by skf-test-skill step 6 §4c) and load that file.
|
|
51
|
+
|
|
52
|
+
If a report is located, set `test_report_path` in context to the resolved absolute path and set `update_mode: gap-driven`. Surface the actual file picked in the message (e.g. `test-report-{skill_name}-20260507T050917Z-487606-9b2f.md`) so an operator can navigate to the report from the log. If all three lookups fail, warn and continue with normal source drift mode.
|
|
46
53
|
|
|
47
54
|
**If `--allow-workspace-drift` was provided:** set `allow_workspace_drift: true` in workflow context. This flag is consumed by step 3 §0.a's pre-flight drift guard (gap-driven mode only) and has no effect in normal source-drift mode.
|
|
48
55
|
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
nextStepFile: 'integrations.md'
|
|
3
3
|
coveragePatternsData: '{coveragePatternsPath}'
|
|
4
4
|
feasibilitySchemaRef: 'src/shared/references/feasibility-report-schema.md'
|
|
5
|
-
|
|
5
|
+
atomicWriteProbeOrder:
|
|
6
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
|
|
7
|
+
- '{project-root}/src/shared/scripts/skf-atomic-write.py'
|
|
6
8
|
outputFile: '{outputFolderPath}/feasibility-report-{project_slug}-{timestamp}.md'
|
|
7
9
|
outputFileLatest: '{outputFolderPath}/feasibility-report-{project_slug}-latest.md'
|
|
8
10
|
---
|
|
@@ -107,13 +109,15 @@ Extra and Orphan skills are informational only. They do not affect the coverage
|
|
|
107
109
|
|
|
108
110
|
### 6. Append to Report
|
|
109
111
|
|
|
112
|
+
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
113
|
+
|
|
110
114
|
Write the **Coverage Analysis** section to `{outputFile}` (see `{feasibilitySchemaRef}` — section headings are fixed and ordered: `## Executive Summary`, `## Coverage Analysis`, `## Integration Verdicts`, `## Recommendations`, `## Evidence Sources`):
|
|
111
115
|
- Include the full coverage table
|
|
112
116
|
- Include coverage percentage
|
|
113
117
|
- Include missing skill recommendations
|
|
114
118
|
- Include the Extra (unreferenced) and Orphan (source_repo unresolvable) subdivisions from section 4
|
|
115
119
|
- Update frontmatter: append `'coverage'` to `stepsCompleted`; set `coveragePercentage` (integer 0..100)
|
|
116
|
-
- Pipe the updated full content through `python3 {
|
|
120
|
+
- Pipe the updated full content through `python3 {atomicWriteHelper} write --target {outputFile}` and again with `--target {outputFileLatest}`
|
|
117
121
|
|
|
118
122
|
### 7. Auto-Proceed to Next Step
|
|
119
123
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'coverage.md'
|
|
3
3
|
feasibilitySchemaRef: 'src/shared/references/feasibility-report-schema.md'
|
|
4
|
-
|
|
4
|
+
atomicWriteProbeOrder:
|
|
5
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
|
|
6
|
+
- '{project-root}/src/shared/scripts/skf-atomic-write.py'
|
|
5
7
|
# {outputFile} and {outputFileLatest} resolve from the activation-stored
|
|
6
8
|
# {project_slug}, {timestamp}, and {outputFolderPath} variables (set in
|
|
7
9
|
# SKILL.md On Activation §2 + §4). The activation-stored values are
|
|
@@ -109,6 +111,8 @@ Each helper-emitted entry includes: `skill_name`, `version`, `language`, `confid
|
|
|
109
111
|
|
|
110
112
|
### 4. Create Feasibility Report
|
|
111
113
|
|
|
114
|
+
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
115
|
+
|
|
112
116
|
This skill is the PRODUCER of the feasibility report schema defined in `{feasibilitySchemaRef}`. All outputs MUST conform to that schema — in particular: `schemaVersion: "1.0"`, the defined verdict token set (`Verified|Plausible|Risky|Blocked`; overall `FEASIBLE|CONDITIONALLY_FEASIBLE|NOT_FEASIBLE`), the filename pattern, and the section-heading order.
|
|
113
117
|
|
|
114
118
|
**Filename variables** (already computed at activation — do not re-derive):
|
|
@@ -137,7 +141,7 @@ This skill is the PRODUCER of the feasibility report schema defined in `{feasibi
|
|
|
137
141
|
- `skillsAnalyzed: {count}`
|
|
138
142
|
- `stepsCompleted: ['init']`
|
|
139
143
|
|
|
140
|
-
**Atomic write:** Pipe the staged content through `python3 {
|
|
144
|
+
**Atomic write:** Pipe the staged content through `python3 {atomicWriteHelper} write --target {outputFile}` and then again with `--target {outputFileLatest}`. Both writes use the same staged content. Do NOT use `rm`+rewrite; do NOT create a symlink for the `-latest` copy.
|
|
141
145
|
|
|
142
146
|
On any non-zero exit from either write: HALT (exit code 4, `halt_reason: "write-failed"`) and emit the error envelope per SKILL.md "Result Contract (Headless)" with `report_path: null`, `report_latest_path: null`, `overall_verdict: null`.
|
|
143
147
|
|