bmad-module-skill-forge 1.3.0 → 1.4.1
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/docs/workflows.md +34 -15
- package/package.json +2 -2
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +58 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +77 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +44 -0
- package/src/shared/scripts/skf-detect-language.py +277 -0
- package/src/shared/scripts/skf-detect-workspaces.py +427 -0
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +257 -0
- package/src/shared/scripts/skf-extract-public-api.py +29 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +73 -0
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +14 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +369 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +293 -0
- package/src/shared/scripts/skf-write-skill-brief.py +509 -0
- package/src/skf-audit-skill/steps-c/step-01-init.md +49 -3
- package/src/skf-audit-skill/steps-c/step-03-structural-diff.md +6 -5
- package/src/skf-brief-skill/SKILL.md +41 -12
- package/src/skf-brief-skill/assets/description-voice-examples.md +19 -0
- package/src/skf-brief-skill/assets/scope-templates.md +5 -0
- package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -40
- package/src/skf-brief-skill/references/draft-checkpoint.md +46 -0
- package/src/skf-brief-skill/references/headless-args.md +22 -0
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +26 -0
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +35 -0
- package/src/skf-brief-skill/references/qmd-collection-registration.md +52 -0
- package/src/skf-brief-skill/references/version-resolution.md +46 -0
- package/src/skf-brief-skill/steps-c/step-01-gather-intent.md +164 -14
- package/src/skf-brief-skill/steps-c/step-02-analyze-target.md +118 -50
- package/src/skf-brief-skill/steps-c/step-03-scope-definition.md +48 -5
- package/src/skf-brief-skill/steps-c/step-04-confirm-brief.md +33 -19
- package/src/skf-brief-skill/steps-c/step-05-write-brief.md +93 -97
- package/src/skf-brief-skill/steps-c/step-06-health-check.md +11 -2
- package/src/skf-create-skill/steps-c/step-07-generate-artifacts.md +9 -1
- package/src/skf-export-skill/steps-c/step-01-load-skill.md +11 -3
- package/src/skf-export-skill/steps-c/step-03-generate-snippet.md +8 -2
- package/src/skf-export-skill/steps-c/step-04-update-context.md +29 -0
- package/src/skf-export-skill/steps-c/step-06-summary.md +12 -0
- package/src/skf-setup/steps-c/step-01b-ccc-index.md +10 -3
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: './step-02-analyze-target.md'
|
|
3
3
|
forgeTierFile: '{sidecar_path}/forge-tier.yaml'
|
|
4
|
+
descriptionVoiceExamplesFile: 'assets/description-voice-examples.md'
|
|
5
|
+
headlessArgsFile: 'references/headless-args.md'
|
|
6
|
+
headlessSourceAuthorityDetectionFile: 'references/headless-source-authority-detection.md'
|
|
7
|
+
portfolioSimilarityCheckFile: 'references/portfolio-similarity-check.md'
|
|
8
|
+
draftCheckpointFile: 'references/draft-checkpoint.md'
|
|
9
|
+
validateBriefInputsScript: '{project-root}/src/shared/scripts/skf-validate-brief-inputs.py'
|
|
10
|
+
emitBriefEnvelopeScript: '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
4
11
|
---
|
|
5
12
|
|
|
6
13
|
# Step 1: Gather Intent
|
|
@@ -14,6 +21,7 @@ To initialize the brief-skill workflow by discovering the forge tier configurati
|
|
|
14
21
|
- Focus only on gathering intent — do not analyze the repo yet (Step 02)
|
|
15
22
|
- Do not examine source code or list exports in this step
|
|
16
23
|
- Open-ended discovery facilitation — collect target repo, user intent, scope hints, skill name
|
|
24
|
+
- All user-facing output in `{communication_language}`
|
|
17
25
|
|
|
18
26
|
## MANDATORY SEQUENCE
|
|
19
27
|
|
|
@@ -21,6 +29,16 @@ To initialize the brief-skill workflow by discovering the forge tier configurati
|
|
|
21
29
|
|
|
22
30
|
### 1. Discover Forge Tier
|
|
23
31
|
|
|
32
|
+
**Pre-flight write probe.** Before any conversational state accumulates, verify `{forge_data_folder}` is writable. A read-only mount, full disk, or permissions-denied path otherwise only surfaces at step-05's atomic write — by then the user has invested 5–15 minutes. Run a single-byte write-and-remove probe:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
mkdir -p "{forge_data_folder}" && \
|
|
36
|
+
printf 'probe' > "{forge_data_folder}/.skf-write-probe" && \
|
|
37
|
+
rm "{forge_data_folder}/.skf-write-probe"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`mkdir -p` succeeds on a pre-existing read-only mount, but the `printf > file` redirect actually attempts a write — that catches read-only, disk-full, and permissions-denied uniformly. **On any non-zero exit:** HALT (exit code 4, `halt_reason: "write-failed"`) — `"**Error:** {forge_data_folder} is not writable: {captured stderr}. Verify the path exists, the mount is writable, and there is free disk space, then re-run."` In headless mode, emit the error envelope per **step-05 §4b** with `halt_reason: "write-failed"` (skill_name is not yet resolved here — use the placeholder convention documented in §4b). On success, continue silently to the forge-tier load below.
|
|
41
|
+
|
|
24
42
|
Attempt to load `{forgeTierFile}`:
|
|
25
43
|
|
|
26
44
|
**If found:**
|
|
@@ -29,9 +47,13 @@ Attempt to load `{forgeTierFile}`:
|
|
|
29
47
|
|
|
30
48
|
**Apply tier override:** Read `{sidecar_path}/preferences.yaml`. If `tier_override` is set and is a valid tier value (Quick, Forge, Forge+, or Deep), use it instead of the detected tier.
|
|
31
49
|
|
|
50
|
+
**If found but the YAML cannot be parsed (corrupted or truncated):**
|
|
51
|
+
- Display: "**Cannot read forge-tier.yaml** at `{forgeTierFile}` — the file exists but failed to parse: `{parser error message}`. The setup workflow can rewrite it cleanly. Until then, the brief workflow falls back to **Quick** tier (no extra tools assumed)."
|
|
52
|
+
- Continue with `tier = "Quick"` and `tools = {}` — do not HALT. Record `tier_source: "fallback-corrupted-config"` for later diagnostics.
|
|
53
|
+
|
|
32
54
|
**If not found:**
|
|
33
|
-
- "**Cannot proceed.** forge-tier.yaml not found at `{forgeTierFile}`.
|
|
34
|
-
- HALT — do not proceed.
|
|
55
|
+
- "**Cannot proceed.** forge-tier.yaml not found at `{forgeTierFile}`. Run the **setup** workflow first to configure your forge tier (Quick/Forge/Forge+/Deep)."
|
|
56
|
+
- HALT (exit code 3, `halt_reason: "forge-tier-missing"`) — do not proceed.
|
|
35
57
|
|
|
36
58
|
### 2. Welcome and Explain
|
|
37
59
|
|
|
@@ -46,14 +68,22 @@ We'll work through this together:
|
|
|
46
68
|
4. **Finally:** Confirm and write the brief
|
|
47
69
|
|
|
48
70
|
{If tier override was applied:}
|
|
49
|
-
**Your forge tier:** {override tier} (overridden from {original tier}) —
|
|
71
|
+
**Your forge tier:** {override tier} (overridden from {original tier}) — {tier_gloss}
|
|
50
72
|
{Else:}
|
|
51
|
-
**Your forge tier:** {detected tier} —
|
|
73
|
+
**Your forge tier:** {detected tier} — {tier_gloss}
|
|
74
|
+
|
|
75
|
+
(Substitute `{tier_gloss}` with the matching one-liner so the user knows what the tier label means: `Quick` → "text-only extraction"; `Forge` → "AST-grep on, semantic discovery off"; `Forge+` → "AST-grep + ccc semantic discovery"; `Deep` → "full pipeline — AST + ccc + qmd portfolio search + LLM re-ranking". The tier sets the ceiling for what the downstream create-skill workflow can do; you can re-run setup later to change it.)
|
|
76
|
+
|
|
77
|
+
**Wanted something different?** This workflow *creates* a new brief — a YAML scoping document for a skill that doesn't yet exist. If you meant to compile an existing brief into a skill (`/skf-create-skill`), package one for distribution (`/skf-export-skill`), or just ask SKF a question, type `cancel` at any prompt and run that workflow instead.
|
|
52
78
|
|
|
53
79
|
Let's get started."
|
|
54
80
|
|
|
55
81
|
### 3. Gather Target Repository
|
|
56
82
|
|
|
83
|
+
This section has three sub-flows. Execute exactly one branch — 3.2 *or* 3.3 — based on the user's response in 3.1, then end with the shared confirmation. Do not mix branches.
|
|
84
|
+
|
|
85
|
+
#### 3.1 Collect target
|
|
86
|
+
|
|
57
87
|
"**What repository or documentation do you want to create a skill for?**
|
|
58
88
|
|
|
59
89
|
Provide one of:
|
|
@@ -61,33 +91,61 @@ Provide one of:
|
|
|
61
91
|
- A **local path** (e.g., `/path/to/project`)
|
|
62
92
|
- **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)
|
|
63
93
|
|
|
94
|
+
Or type `cancel` / `exit` / `[X]` to leave without writing anything.
|
|
95
|
+
|
|
64
96
|
**Target:**"
|
|
65
97
|
|
|
66
|
-
Wait for user response.
|
|
98
|
+
Wait for user response. Branch on the response:
|
|
99
|
+
|
|
100
|
+
- 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-01. Headless mode never reaches this branch (the GATE in §8 short-circuits the interactive sub-flows).
|
|
101
|
+
- Documentation URLs only (no source location) → §3.2
|
|
102
|
+
- GitHub URL or local filesystem path → §3.3
|
|
103
|
+
- 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
|
+
|
|
105
|
+
#### 3.2 Branch — Documentation URLs (docs-only)
|
|
67
106
|
|
|
68
|
-
**If user provides documentation URLs (not a repo):**
|
|
69
107
|
- Set `source_type: "docs-only"` in the brief data
|
|
70
108
|
- Collect one or more doc URLs with optional labels
|
|
71
|
-
-
|
|
109
|
+
- HEAD-check the collected URLs in parallel — do not loop sequentially. Issue all N `curl -sI {url}` (or equivalent) calls in a **single message with N parallel Bash calls**, then process the responses together. Each call must use a 5-second timeout (`curl -sI --max-time 5 {url}`) to bound worst-case wall-time on hung hosts. Per response:
|
|
110
|
+
- On 2xx/3xx: silently accept.
|
|
111
|
+
- On 4xx/5xx, DNS failure, or timeout: warn `"Could not reach {url} — {status or error}. Confirm the URL is correct, or proceed anyway."` Interactive: re-prompt for a corrected URL or `[K] Keep anyway`. Headless: keep the URL and log the warning — the brief still records it but the failure is now visible at brief-creation time instead of materializing hours later in skf-create-skill.
|
|
112
|
+
- Set `source_authority: "community"` (forced for docs-only — T3 external documentation; the §3.3 source-authority prompt is skipped)
|
|
72
113
|
- Note: `source_repo` becomes optional (can be set to the main doc site URL for reference)
|
|
73
114
|
|
|
74
|
-
|
|
115
|
+
Skip §3.3 and continue at "Confirm the target" below.
|
|
116
|
+
|
|
117
|
+
#### 3.3 Branch — Source (GitHub URL or local path)
|
|
118
|
+
|
|
75
119
|
- Set `source_type: "source"` (default)
|
|
120
|
+
- **Pre-validate the target before continuing.** Issue these probes in a single message with parallel Bash calls:
|
|
121
|
+
- **GitHub URL:** `curl -sI --max-time 5 {url}`. On a 4xx (typically 404 for a typo'd repo or org), warn `"GitHub returned {status} for {url} — confirm the URL is correct."` and re-prompt. On 2xx, accept.
|
|
122
|
+
- **GitHub URL, in parallel:** `gh api repos/{owner}/{repo} --jq .name` (5-second timeout via `gh api --hostname github.com --method GET ... ` or just rely on default). On 403/404, warn `"GitHub API returned {status} for {owner}/{repo} — the repo may be private or your token may not have access. Step-02 will HALT here if this is not resolved. Continue anyway, or fix and re-prompt?"` and offer `[K] Keep anyway` / re-prompt for a corrected URL. Do not HALT — the canonical HALT still happens in step-02 §1, but surfacing access failures at URL-entry time prevents 5+ minutes of intent investment getting lost. On any other error (network failure, missing binary), log silently and let `gh auth status` below catch it. On 2xx, accept silently.
|
|
123
|
+
- **GitHub URL, in parallel:** `gh auth status` — if it reports unauthenticated or the binary is missing, warn `"GitHub CLI not authenticated; step-02 will HALT when it tries to fetch the tree. Run 'gh auth login' before continuing, or supply a local clone path instead."` (Do not HALT here — let the user choose to fix or proceed; the canonical HALT still happens in step-02 §1's failure-class triage.)
|
|
124
|
+
- **Local path:** verify the directory exists (`test -d {path}`). If not, warn `"Local path {path} does not exist."` and re-prompt.
|
|
76
125
|
- Optionally ask: "Are there any documentation URLs you'd like to include for supplemental context? (These will be fetched as T3 external references.)"
|
|
77
126
|
- If yes: collect doc URLs into `doc_urls`
|
|
78
127
|
|
|
79
|
-
**Source authority (
|
|
128
|
+
**Source authority (this branch only — docs-only forces `community` in §3.2):**
|
|
129
|
+
|
|
130
|
+
**Interactive only** — skip this prompt entirely when `{headless_mode}` is true; the GATE in §8 resolves source_authority headlessly via the detection branch documented there.
|
|
131
|
+
|
|
80
132
|
"**Are you the maintainer of this library, or creating a community skill?**"
|
|
81
133
|
- If maintainer: set `source_authority: "official"`
|
|
82
134
|
- If community user: set `source_authority: "community"` (default)
|
|
83
135
|
- If internal/proprietary: set `source_authority: "internal"`
|
|
84
136
|
|
|
85
|
-
Default to `"community"` if user does not specify or skips.
|
|
137
|
+
Default to `"community"` if user does not specify or skips.
|
|
138
|
+
|
|
139
|
+
---
|
|
86
140
|
|
|
87
141
|
Confirm the target.
|
|
88
142
|
|
|
89
143
|
### 3b. Gather Target Version
|
|
90
144
|
|
|
145
|
+
This step only collects `target_version` and validates its shape with the regex below — auto-detection runs in step-02 and precedence/invariant resolution lands in step-05's writer script. The canonical precedence rules live in `references/version-resolution.md`; load it from step-02 / step-05 only when the relevant section needs it.
|
|
146
|
+
|
|
147
|
+
**Headless:** if `target_version` was supplied as an argument, store it and skip the interactive prompt below. If `doc_urls` were also supplied, treat the version-vs-doc-URL confirmation prompt as auto-confirmed (Y).
|
|
148
|
+
|
|
91
149
|
"**Are you targeting a specific version of this library?**
|
|
92
150
|
(Leave blank to auto-detect from source)"
|
|
93
151
|
|
|
@@ -96,7 +154,7 @@ Confirm the target.
|
|
|
96
154
|
|
|
97
155
|
Wait for user response.
|
|
98
156
|
|
|
99
|
-
**If user provides a version:**
|
|
157
|
+
**If user provides a version:** Validate the shape against `^v?\d+\.\d+\.\d+([.\-+][0-9A-Za-z][0-9A-Za-z.\-+]*)?$` (full X.Y.Z form, with optional `v` prefix and pre-release / build suffix; CalVer like `2024.04.01` accepted; partial forms like `1`, `1.2`, `v2`, `latest` rejected). On a match, store as `target_version` and set `version` to this value. On a non-match, warn `"'{value}' doesn't look like semver — write the explicit triple (e.g. 1.0.0). Fix it now or skip auto-detection?"` and re-prompt for a corrected value or blank to fall through to step-02 auto-detection.
|
|
100
158
|
**If blank:** Proceed without `target_version` — version will be auto-detected in step 02.
|
|
101
159
|
|
|
102
160
|
{If target_version was set AND doc_urls are being collected (either docs-only primary or supplemental):}
|
|
@@ -104,10 +162,22 @@ Wait for user response.
|
|
|
104
162
|
"**You're targeting version {target_version}. Do these documentation URLs correspond to that version?** [Y/N]"
|
|
105
163
|
|
|
106
164
|
- **If Y:** Proceed.
|
|
107
|
-
- **If N:** "
|
|
165
|
+
- **If N:** "Provide the correct documentation URLs for version {target_version}." Re-collect doc_urls.
|
|
108
166
|
|
|
109
167
|
### 4. Gather User Intent
|
|
110
168
|
|
|
169
|
+
**First-timer rail (interactive only).** Before the intent prompt, check whether `{forge_data_folder}/` contains any prior briefs:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
find "{forge_data_folder}" -maxdepth 2 -name "skill-brief.yaml" -print -quit
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
If the command produces any output, skip this rail silently — repeat users don't need the warm-up. If it produces no output (the user has never produced a brief), ask:
|
|
176
|
+
|
|
177
|
+
"**Want to see a few example descriptions first?** [Y/N] (Helpful if this is your first time — I'll show the voices we use so you have an anchor for what 'good intent' produces.)"
|
|
178
|
+
|
|
179
|
+
On `[Y]`: load `{descriptionVoiceExamplesFile}` and present the five examples verbatim with a one-line preface (`"Each example shows a different voice — yours doesn't have to match any specific one."`). On `[N]` or empty: proceed silently.
|
|
180
|
+
|
|
111
181
|
"**What's your intent for this skill?**
|
|
112
182
|
|
|
113
183
|
Help me understand:
|
|
@@ -140,6 +210,34 @@ This will be used for the output directory and file naming. Want to use this nam
|
|
|
140
210
|
|
|
141
211
|
Wait for confirmation or alternative.
|
|
142
212
|
|
|
213
|
+
**Collision check (interactive and headless):** before locking the name, check whether `{forge_data_folder}/{name}/skill-brief.yaml` already exists. If it does:
|
|
214
|
+
|
|
215
|
+
- Interactive: generate 1–3 non-colliding candidate alternates by scanning sibling directories under `{forge_data_folder}/`. Apply each rule that fires; skip rules whose precondition isn't met:
|
|
216
|
+
1. `{name}-v{N}` where `N` is the smallest positive integer that doesn't collide (e.g. `{name}-v2`, `{name}-v3`) — always applies
|
|
217
|
+
2. `{name}-{target_version}` if `target_version` is set and the suffix wouldn't collide (e.g. `marked-1.2.3`)
|
|
218
|
+
3. `{name}-{source_authority}` if `source_authority` is not `community` (e.g. `marked-internal` for an internal fork)
|
|
219
|
+
|
|
220
|
+
Number the surviving alternates `[1] [2] [3]…` in the order produced (1 alternate for a community-authority brief with no `target_version`; 2–3 otherwise). Then present:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
**Heads up — a brief for `{name}` already exists at `{path}`.**
|
|
224
|
+
|
|
225
|
+
Suggested alternates (none collide):
|
|
226
|
+
[1] {alternate-1}
|
|
227
|
+
{if a second alternate was produced:} [2] {alternate-2}
|
|
228
|
+
{if a third alternate was produced:} [3] {alternate-3}
|
|
229
|
+
|
|
230
|
+
Pick a number to use that name, type a different name, or press Enter to keep `{name}` and let step-05 §2b handle the overwrite prompt.
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
On a numbered choice, replace `{name}` with the chosen alternate. On Enter, fall through to step-05's overwrite gate. On any other input, treat as a new candidate name and re-run the collision check against it.
|
|
234
|
+
|
|
235
|
+
- Headless: log `"warn: skill name '{name}' collides with existing brief at {path}"` and proceed; the existing-brief overwrite policy in step-05 §2b is the canonical gate (HALT with `overwrite-cancelled` unless `force` was supplied).
|
|
236
|
+
|
|
237
|
+
**Portfolio-similarity check.** When the flow is interactive AND forge tier is `Deep` AND `tools.qmd` is true in `forge-tier.yaml`, load `{portfolioSimilarityCheckFile}` and follow the procedure there to catch semantic near-duplicates that exact-name collision misses. Otherwise (headless, or tier below Deep, or qmd unavailable) skip the load — the check does not run.
|
|
238
|
+
|
|
239
|
+
**Draft-resume check.** When the flow is interactive AND `{forge_data_folder}/{name}/.brief-draft.json` exists AND no `skill-brief.yaml` sits beside it, load `{draftCheckpointFile}` and follow Half 1 (Resume Check). On `[Y]` resume, the procedure jumps directly to §8 with prior answers restored — **the rest of §6, all of §7, and all of §7b are skipped**. Otherwise (headless, or no draft file, or a finished brief sits beside the draft) skip the load and continue with §6 normally.
|
|
240
|
+
|
|
143
241
|
### 7. Summarize Gathered Intent
|
|
144
242
|
|
|
145
243
|
"**Here's what I've captured:**
|
|
@@ -158,19 +256,71 @@ Wait for confirmation or alternative.
|
|
|
158
256
|
|
|
159
257
|
Ready to analyze the target repository?"
|
|
160
258
|
|
|
259
|
+
**Draft checkpoint.** When the flow is interactive, load `{draftCheckpointFile}` (or reuse it if already loaded for the §6 resume check) and follow Half 2 (Checkpoint Write) to persist the captured state atomically. Headless mode skips this — the run completes in a single invocation, no resume is meaningful.
|
|
260
|
+
|
|
261
|
+
### 7b. Synthesize Skill Description
|
|
262
|
+
|
|
263
|
+
The schema's `description` field is 1-3 sentences and surfaces in skill registries — it must exist by the time step-04 presents the brief. Synthesize it explicitly here, while the user's intent is fresh, instead of letting it fall out implicitly later.
|
|
264
|
+
|
|
265
|
+
Compose a candidate 1-3 sentence description from the gathered material. **Write like a human library maintainer would** — what does an agent get from this skill, and when should it route here? Two facts must come through (what the skill is, when to use it); everything else is voice. Resist filling in the same skeleton every time.
|
|
266
|
+
|
|
267
|
+
Load `{descriptionVoiceExamplesFile}` for the five voice examples (range of acceptable leads, structures, and trigger phrasings) and the "do not template-stamp" guidance, then compose in that spirit. The asset documents what "in that spirit" means; the gathered material to draw on is the target repo, the user's intent, the version if set, and any scope hints.
|
|
268
|
+
|
|
269
|
+
Present:
|
|
270
|
+
|
|
271
|
+
"**Proposed skill description:**
|
|
272
|
+
|
|
273
|
+
> {synthesized description}
|
|
274
|
+
|
|
275
|
+
This is the text agents read when deciding whether to route to your skill — it sits in the registry row alongside dozens of other skills. Specific triggers ('use when…', 'reach for this when…') help agents match real user requests; generic descriptions blend in and get skipped. Edit, replace, or accept as-is."
|
|
276
|
+
|
|
277
|
+
Wait for user confirmation or alternative.
|
|
278
|
+
|
|
279
|
+
**Soft sentence-count check (interactive only).** Before storing the accepted text, count terminal sentence punctuation (`.`, `!`, `?` followed by whitespace or end-of-string) — abbreviations like `e.g.` will inflate the count slightly but the check is a soft nudge, not a HALT. If the count exceeds 3, present:
|
|
280
|
+
|
|
281
|
+
"**Heads up — that description reads as ~{N} sentences.** The conventional norm is 1-3 (it surfaces in registry rows alongside other skills, where length crowds out the trigger phrase). Tighten now, or accept as-is?"
|
|
282
|
+
|
|
283
|
+
On `tighten` or a fresh edit: re-prompt for the description. On `accept` or any non-edit response: store the accepted text and proceed. Counts of 1-3 store silently.
|
|
284
|
+
|
|
285
|
+
Store the accepted text as the brief's `description` field. The same field is re-presented in step-04 §3 for a final review pass — refinements there flow back to this value.
|
|
286
|
+
|
|
287
|
+
**Headless:** if the `intent` argument was supplied, load `{descriptionVoiceExamplesFile}` and run the same synthesis against it (in `{document_output_language}`), then store the result. If `intent` was not supplied, fall back in priority order:
|
|
288
|
+
|
|
289
|
+
1. **GitHub repo description** — when `target_repo` is a GitHub URL, fetch `gh api repos/{owner}/{repo} --jq .description` (5-second timeout). If a non-empty description comes back, load `{descriptionVoiceExamplesFile}` and synthesize using the GitHub description as the seed in place of `intent`. Write the synthesized description in `{document_output_language}` regardless of the seed's language (the seed may be in any language; the output's language is dictated by the workflow's document-output configuration). Log `"info: description seeded from GitHub repo description"`. (The full `gh api repos` response is fetched again in step-02 §1; this lightweight `--jq .description` call only retrieves the one field.)
|
|
290
|
+
2. **Generic stub** — when no GitHub description is available (local-path target, GitHub repo with empty description, or `gh api` fails): derive from `target_repo` + `skill_name` (`"Use the {skill_name} skill to work with code or content from {target_repo}."`) — the generic fallback does not need the asset — and log `"warn: description synthesized without intent or repo description — narrow registry text."`
|
|
291
|
+
|
|
161
292
|
### 8. Present MENU OPTIONS
|
|
162
293
|
|
|
163
|
-
Display: "**Select:** [C] Continue to Target Analysis"
|
|
294
|
+
Display: "**Select:** [C] Continue to Target Analysis · [X] Cancel and exit"
|
|
164
295
|
|
|
165
296
|
#### Menu Handling Logic:
|
|
166
297
|
|
|
167
298
|
- IF C: Load, read entire file, then execute {nextStepFile}
|
|
299
|
+
- IF X: Treat as user-cancellation. Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). When `{headless_mode}` is true the GATE auto-proceeds and never reaches this branch — `[X]` is interactive-only. Cancellation here is non-destructive: no files have been written yet by step-01.
|
|
168
300
|
- IF Any other: Help user, then [Redisplay Menu Options](#8-present-menu-options)
|
|
169
301
|
|
|
170
302
|
#### EXECUTION RULES:
|
|
171
303
|
|
|
172
304
|
- ALWAYS halt and wait for user input after presenting menu
|
|
173
|
-
- **GATE [default: use args]** — If `{headless_mode}
|
|
305
|
+
- **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 `{validateBriefInputsScript}`; the table is the canonical operator-facing documentation, the script enforces it.
|
|
306
|
+
|
|
307
|
+
**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
|
+
|
|
309
|
+
**Delegate validation to `{validateBriefInputsScript}`** instead of reasoning through the table rules in prose:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
echo '<headless-args-as-json>' | uv run {validateBriefInputsScript}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The script returns a JSON envelope: `{valid, errors[], warnings[], normalized, halt_reason}`. Apply the result deterministically:
|
|
316
|
+
|
|
317
|
+
- **`valid: false`** — emit the error envelope per **step-05 §4b** with the script's `halt_reason` (`"input-missing"` for absent required args / docs-only without doc_urls; `"input-invalid"` for enum violations, malformed semver, malformed kebab-case skill_name). Surface `errors[]` to the operator log so the failure is debuggable. HALT.
|
|
318
|
+
- **`valid: true`** — consume the `normalized` object as the source of truth (it has defaults applied per the table). Surface `warnings[]` to the operator log but do not HALT. Auto-proceed.
|
|
319
|
+
|
|
320
|
+
The script's `KNOWN_FIELDS` set must stay in sync with the table in `{headlessArgsFile}`.
|
|
321
|
+
|
|
322
|
+
**Headless source-authority detection.** After consuming `normalized`, if `source_authority` is absent AND `source_type=source` AND `target_repo` is a GitHub URL, load `{headlessSourceAuthorityDetectionFile}` and follow the procedure there. Otherwise (precondition unmet, value already supplied, docs-only, or local-path) skip the load — `community` is the implicit default for the unmet branches.
|
|
323
|
+
|
|
174
324
|
- ONLY proceed to next step when user selects 'C'
|
|
175
325
|
|
|
176
326
|
## CRITICAL STEP COMPLETION NOTE
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: './step-03-scope-definition.md'
|
|
3
|
+
versionResolutionFile: 'references/version-resolution.md'
|
|
4
|
+
extractPublicApiScript: '{project-root}/src/shared/scripts/skf-extract-public-api.py'
|
|
5
|
+
detectWorkspacesScript: '{project-root}/src/shared/scripts/skf-detect-workspaces.py'
|
|
6
|
+
detectLanguageScript: '{project-root}/src/shared/scripts/skf-detect-language.py'
|
|
3
7
|
---
|
|
4
8
|
|
|
5
9
|
# Step 2: Analyze Target
|
|
@@ -13,6 +17,7 @@ To analyze the target repository by resolving its location, reading its structur
|
|
|
13
17
|
- Focus only on analysis — do not define scope yet (Step 03)
|
|
14
18
|
- Do not make scoping decisions or recommendations
|
|
15
19
|
- Do not hallucinate or guess about repository contents
|
|
20
|
+
- All user-facing output in `{communication_language}`
|
|
16
21
|
|
|
17
22
|
## MANDATORY SEQUENCE
|
|
18
23
|
|
|
@@ -21,23 +26,68 @@ To analyze the target repository by resolving its location, reading its structur
|
|
|
21
26
|
### 1. Resolve Target Location
|
|
22
27
|
|
|
23
28
|
**For GitHub URLs:**
|
|
24
|
-
-
|
|
25
|
-
-
|
|
29
|
+
- Issue both probes in **one message with two parallel Bash calls** — they are independent:
|
|
30
|
+
- `gh api repos/{owner}/{repo}` (verify repo exists)
|
|
31
|
+
- `gh api repos/{owner}/{repo}/git/trees/HEAD?recursive=1` (fetch file tree)
|
|
32
|
+
- If the repo-existence probe fails, fall through to the failure-class triage below; the tree response from the parallel call is discarded in that case.
|
|
26
33
|
|
|
27
34
|
**Truncation detection:** After receiving the tree response, check the `truncated` field in the JSON output. If `truncated: true`:
|
|
28
35
|
- Display: "Note: GitHub API returned a truncated tree response ({count} items). Full analysis may require a local clone."
|
|
29
36
|
- Record in analysis summary: "Tree listing is partial — some files may not appear in the analysis."
|
|
30
|
-
- For very large repos (>1000 files in tree response):
|
|
37
|
+
- For very large repos (>1000 files in tree response): offer a recovery path instead of just warning. Interactive — present:
|
|
38
|
+
```
|
|
39
|
+
Tree is truncated. How would you like to proceed?
|
|
40
|
+
[L] Clone locally and re-analyze (slower but complete)
|
|
41
|
+
[P] Proceed with the partial tree (faster, may miss exports under deeper paths)
|
|
42
|
+
```
|
|
43
|
+
On `[L]`: shallow-clone (`git clone --depth 1 {url} {tmp_dir}`), restart this section against the local path, and remove `{tmp_dir}` after the analysis summary in §5. On `[P]` (or under headless): record `tree_truncated: true` in the analysis summary and continue without HALT.
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
**On API failure (non-200 from `gh api`):**
|
|
46
|
+
|
|
47
|
+
Distinguish the failure class before reporting:
|
|
48
|
+
- Auto-run `gh auth status` and capture its output. If it reports an unauthenticated state or expired token: HALT (exit code 3, `halt_reason: "gh-auth-failed"`) — "**Error:** GitHub CLI is not authenticated. `gh auth status` says: `{captured output}`. Run `gh auth login` and retry."
|
|
49
|
+
- If `gh auth status` reports authenticated but the call still failed (404/403): HALT (exit code 3, `halt_reason: "target-inaccessible"`) — "**Error:** Cannot access repository at `{url}`. The CLI is authenticated but the API returned `{status}`. Check the URL and that the account has access to private repositories if applicable."
|
|
50
|
+
- If `gh auth status` itself fails to run (binary missing): HALT (exit code 3, `halt_reason: "gh-auth-failed"`) — "**Error:** `gh` CLI not found on PATH. Install it from <https://cli.github.com> and re-run."
|
|
33
51
|
|
|
34
52
|
**For local paths:**
|
|
35
53
|
- Verify the directory exists
|
|
36
54
|
- List the directory tree
|
|
37
|
-
- If path doesn't exist: **HALT** — "**Error:** Directory not found at {path}.
|
|
55
|
+
- If path doesn't exist: **HALT** — "**Error:** Directory not found at {path}. Verify the path is correct."
|
|
38
56
|
|
|
39
57
|
Display: "**Resolving target...**"
|
|
40
58
|
|
|
59
|
+
### 1b. Detect Monorepo / Workspace Layout
|
|
60
|
+
|
|
61
|
+
Delegate workspace detection to `{detectWorkspacesScript}` instead of reasoning through manifest rules in prose. Build a payload from the tree fetched in §1 plus the small set of root manifests the detector needs, then invoke the script:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
echo '{"tree": [<flat list of repo-relative file paths>], "manifests": {"package.json": "<raw text>", "Cargo.toml": "<raw text>", "pnpm-workspace.yaml": "<raw text>", "lerna.json": "<raw text>"}}' | \
|
|
65
|
+
uv run {detectWorkspacesScript}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **`tree`** — pass the flat list of repo-relative file paths already fetched in §1 (for GitHub: the `path` values from the `gh api .../git/trees/HEAD?recursive=1` response; for local: the equivalent listing).
|
|
69
|
+
- **`manifests`** — only the root manifests need contents; child-workspace manifests are looked up from the tree by the script. Include any of `package.json`, `Cargo.toml`, `pnpm-workspace.yaml`, `lerna.json` that appears at the repo root. Fetch them in **one message with N parallel Bash calls** (`gh api .../contents/{path}` for GitHub, file reads for local), then base64-decode together. Per-workspace manifest contents (e.g. `packages/foo/package.json`) are optional — including them populates the workspace `name` field with the manifest's declared package name; omitting them falls back to the directory basename.
|
|
70
|
+
|
|
71
|
+
The script returns a JSON envelope: `{is_monorepo, manifest_kind, workspaces[], warnings[]}`. Apply the result deterministically — see `src/shared/scripts/schemas/workspace-detection.v1.json` for the full contract.
|
|
72
|
+
|
|
73
|
+
**If `is_monorepo: false`** — skip this section silently and continue to §2.
|
|
74
|
+
|
|
75
|
+
**If `is_monorepo: true`** — present the discovered workspaces and prompt:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
This looks like a monorepo ({manifest_kind}) with these workspaces:
|
|
79
|
+
1. {workspaces[0].name} ({workspaces[0].path})
|
|
80
|
+
2. {workspaces[1].name} ({workspaces[1].path})
|
|
81
|
+
...
|
|
82
|
+
Which one should the skill cover? Pick a number, type 'all' to scope at the repo root, or 'list' to keep listing more.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Interactive: wait for the user choice. On a numbered choice, store `monorepo_workspace: {path}` and rebase §2-§4b against that path. On `'all'`, leave `monorepo_workspace` unset and proceed at the repo root with a note in the analysis summary that scope is unfiltered.
|
|
86
|
+
|
|
87
|
+
Headless: if the input contract supplied an `include` glob that begins with one of the workspace paths, auto-select that workspace (log `"headless: auto-selected workspace {name} from include glob"`). Otherwise default to repo root and log `"warn: monorepo detected ({manifest_kind}) but no workspace pre-selected — analyzing at repo root"`.
|
|
88
|
+
|
|
89
|
+
Surface any non-empty `warnings[]` from the script to the operator log so a malformed root manifest is debuggable; the workflow does not HALT — falling back to repo-root analysis is always safe.
|
|
90
|
+
|
|
41
91
|
### 2. Read Repository Structure
|
|
42
92
|
|
|
43
93
|
List the top-level directory structure:
|
|
@@ -54,53 +104,79 @@ List the top-level directory structure:
|
|
|
54
104
|
|
|
55
105
|
### 3. Detect Primary Language
|
|
56
106
|
|
|
57
|
-
|
|
107
|
+
Delegate the rule walk to `{detectLanguageScript}` instead of evaluating manifest presence and extension frequency in prose:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
echo '{"tree": [<flat list of repo-relative file paths from §1>]}' | uv run {detectLanguageScript}
|
|
111
|
+
```
|
|
58
112
|
|
|
59
|
-
|
|
60
|
-
1. `package.json` → JavaScript/TypeScript
|
|
61
|
-
2. `tsconfig.json` → TypeScript
|
|
62
|
-
3. `Cargo.toml` → Rust
|
|
63
|
-
4. `pyproject.toml` or `setup.py` or `setup.cfg` → Python
|
|
64
|
-
5. `go.mod` → Go
|
|
65
|
-
6. `pom.xml` or `build.gradle` → Java
|
|
66
|
-
7. `*.csproj` or `*.sln` → C#
|
|
67
|
-
8. `Gemfile` → Ruby
|
|
68
|
-
9. File extension frequency analysis as fallback
|
|
113
|
+
The script returns `{language, confidence, detection_source, fallback_to_extension_frequency}` after walking the documented rule table (manifest presence first — package.json with tsconfig.json disambiguation, Cargo.toml, pyproject.toml/setup.py/setup.cfg, go.mod, pom.xml, build.gradle.kts, build.gradle Groovy with Java/Kotlin disambiguation, *.csproj/*.sln, Gemfile — then extension-frequency fallback over recognized source extensions). Use the returned values directly:
|
|
69
114
|
|
|
70
115
|
"**Detected language:** {language}
|
|
71
|
-
**Confidence:** {
|
|
72
|
-
**Detection source:** {
|
|
116
|
+
**Confidence:** {confidence}
|
|
117
|
+
**Detection source:** {detection_source}"
|
|
73
118
|
|
|
74
|
-
If confidence is low or
|
|
119
|
+
If `confidence` is `low` (or `unknown` is returned for `language`): flag for user override in step 03 §4.
|
|
75
120
|
|
|
76
121
|
### 4. List Top-Level Modules and Exports
|
|
77
122
|
|
|
78
|
-
|
|
123
|
+
Identify the public API surface. **Delegate the parsing to `{extractPublicApiScript}` whenever the detected language is supported** — the script is the single source of truth for manifest parsing, export discovery, and version detection across the whole SKF pipeline. Hand-rolling these in prose creates drift seams the LLM cannot fully close.
|
|
79
124
|
|
|
80
|
-
**
|
|
81
|
-
- Check `package.json` for `main`, `exports`, `module` fields
|
|
82
|
-
- Look for `index.ts`/`index.js` in `src/`
|
|
83
|
-
- List directories under `src/` as potential modules
|
|
125
|
+
**Script-supported languages** (use the script): `js`, `ts`, `javascript`, `typescript`, `python`, `rust`, `go`, `java`, `kotlin`.
|
|
84
126
|
|
|
85
|
-
|
|
86
|
-
- Check `__init__.py` files for public exports
|
|
87
|
-
- List top-level packages under the source directory
|
|
127
|
+
This section runs exactly one of §4.1 (script path) or §4.2 (fallback path) based on the detected language, then always emits §4.3 (output format) and conditionally §4.4 (semantic signals).
|
|
88
128
|
|
|
89
|
-
|
|
90
|
-
- Check `lib.rs` for `pub mod` declarations
|
|
91
|
-
- List modules from `src/` directory
|
|
129
|
+
#### 4.1 Procedure — script-supported languages
|
|
92
130
|
|
|
93
|
-
**For
|
|
94
|
-
|
|
95
|
-
|
|
131
|
+
1. Read the relevant files into memory (no parsing yet — just collect content). For GitHub sources, issue **all N `gh api repos/{owner}/{repo}/contents/{file}` calls in a single message with N parallel Bash calls** (one per manifest + each entry point), then base64-decode the responses together — these are 2-4 independent fetches per typical run. For local sources read directly (also parallelisable, but local reads are fast enough that serial Read tool calls are acceptable).
|
|
132
|
+
|
|
133
|
+
| Language | Manifest | Entry points (mode=quick) |
|
|
134
|
+
|----------|----------|--------------------------|
|
|
135
|
+
| js / ts / javascript / typescript | `package.json` (root, or primary workspace package per `references/version-resolution.md`) | `index.{ts,js}` and/or `src/index.{ts,js}` if present |
|
|
136
|
+
| python | `pyproject.toml` (or `setup.py` / `setup.cfg` if no `pyproject.toml`) | top-level `__init__.py` of the package, plus `_version.py` if present |
|
|
137
|
+
| rust | `Cargo.toml` (`[package]` — workspace root if `version = { workspace = true }`) | `src/lib.rs` |
|
|
138
|
+
| go | `go.mod` | top-level `*.go` exporting the package surface |
|
|
139
|
+
| java | `pom.xml` | (manifest alone is sufficient for the modules listing) |
|
|
140
|
+
| kotlin | `build.gradle` / `build.gradle.kts` | (manifest alone) |
|
|
141
|
+
|
|
142
|
+
2. Build a JSON payload matching the script contract:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"language": "<one of the supported values>",
|
|
147
|
+
"manifest": {"path": "<relative path>", "content": "<file contents>"},
|
|
148
|
+
"entries": [{"path": "<relative path>", "content": "<file contents>"}, ...],
|
|
149
|
+
"mode": "quick"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
3. Invoke the script and parse its JSON stdout:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
echo '<payload-json>' | uv run {extractPublicApiScript} --language <lang> --mode quick
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
On a non-zero exit (codes 1 or 2 per the script's docstring), capture stderr, log it, and fall through to §4.2 (the prose-fallback path) — never HALT just because the script choked on an unusual manifest.
|
|
160
|
+
|
|
161
|
+
4. Render the returned `package_name`, `exports` (each entry's `name`/`type`/`source_file`), `dependencies`, and any `warnings` to the user. The script also returns `version` — feed that into §4b instead of re-deriving.
|
|
162
|
+
|
|
163
|
+
5. The script does not enumerate directories under `src/`. The LLM still lists those as "Top-Level Modules/Directories" so the user sees structural context (Maven and Gradle are the exception — for those, the script returns a `modules` array which IS the list).
|
|
164
|
+
|
|
165
|
+
#### 4.2 Procedure — fallback (not script-supported)
|
|
166
|
+
|
|
167
|
+
Languages outside the script coverage (Ruby / C# / Swift / etc.) take this path. The §4.1 fall-through on script error also lands here.
|
|
168
|
+
|
|
169
|
+
Fall back to ad-hoc inspection — `Gemfile` / `*.csproj` / `*.sln` / `Package.swift` / file extension frequency. List top-level source directories as potential modules and note any obvious entry points. Flag the limitation in the analysis summary so the user knows scoping is on coarser signals.
|
|
170
|
+
|
|
171
|
+
#### 4.3 Output format (both paths)
|
|
96
172
|
|
|
97
173
|
"**Top-Level Modules/Directories:**
|
|
98
174
|
{numbered list of modules with brief description of each}
|
|
99
175
|
|
|
100
176
|
**Detected Exports/Entry Points:**
|
|
101
|
-
{numbered list of public-facing items found}"
|
|
177
|
+
{numbered list of public-facing items found — from script output when available, ad-hoc inspection otherwise}"
|
|
102
178
|
|
|
103
|
-
|
|
179
|
+
#### 4.4 Semantic Signals (Forge+/Deep with ccc only)
|
|
104
180
|
|
|
105
181
|
**Remote source guard:** If the target source was resolved via GitHub API (remote URL, not a local file path), skip this CCC subsection — CCC requires a local source index and cannot operate on remote-only sources. Note: "CCC semantic discovery skipped — target is remote. CCC discovery will run automatically during create-skill after the source is cloned."
|
|
106
182
|
|
|
@@ -124,29 +200,21 @@ If CCC is unavailable or returns no results: skip this subsection silently.
|
|
|
124
200
|
|
|
125
201
|
### 4b. Detect Source Version
|
|
126
202
|
|
|
127
|
-
**
|
|
128
|
-
- Display: "**Target version:** {target_version} (user-specified)"
|
|
129
|
-
- Still run auto-detection below for informational purposes.
|
|
203
|
+
**When the language was script-supported (§4 took the script path):** the `version` field returned by `{extractPublicApiScript}` IS the detected version — do not re-derive it and do not load `{versionResolutionFile}`. The script already implements the language-specific lookups documented in that reference, so loading the reference here only burns context.
|
|
130
204
|
|
|
131
|
-
|
|
205
|
+
**When the language was not script-supported:** load `{versionResolutionFile}` and follow the prose Detection Algorithm directly (Ruby / C# / Swift / etc. fall outside the script's coverage).
|
|
132
206
|
|
|
133
|
-
|
|
134
|
-
**For JavaScript/TypeScript:** Check root `package.json` `"version"` field → if root has `"private": true` with a `"workspaces"` array or lacks a `"version"` field, fall back to a primary workspace package's `package.json` (e.g., `code/core/package.json`, or the first matching `packages/*/package.json`). For GitHub sources, prefer `gh api repos/{owner}/{repo}/releases/latest` → `tag_name` when a non-pre-release tag exists, over a default-branch pre-release. Treat a version containing `-alpha`, `-beta`, `-rc`, `-next`, or `-canary` as a pre-release.
|
|
135
|
-
**For Rust:** Check `Cargo.toml` `[package] version` (static) → if `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0`
|
|
136
|
-
**For Go:** Check `go.mod` or `git describe --tags --abbrev=0`
|
|
207
|
+
Surface the result regardless of which path produced it:
|
|
137
208
|
|
|
138
|
-
**
|
|
139
|
-
**
|
|
209
|
+
**If `target_version` was provided in step 01:**
|
|
210
|
+
- Display: "**Target version:** {target_version} (user-specified)"
|
|
140
211
|
|
|
141
212
|
Display: "**Detected version:** {version or 'Not detected — will default to 1.0.0'}"
|
|
142
213
|
|
|
143
214
|
{If target_version was provided AND auto-detected version differs:}
|
|
144
|
-
"**Note:** Detected version ({detected_version}) differs from your target version ({target_version}). Using target version."
|
|
145
|
-
|
|
146
|
-
{If target_version was provided:}
|
|
147
|
-
Store `target_version` as the brief's `version` field (overrides auto-detection).
|
|
215
|
+
"**Note:** Detected version ({detected_version}) differs from your target version ({target_version}). Using target version (per `references/version-resolution.md` precedence rules)."
|
|
148
216
|
|
|
149
|
-
If detection fails or returns a non-semver value: note that version will default to `"1.0.0"` and the user can override in step 04.
|
|
217
|
+
If detection fails or returns a non-semver value: note that version will default to `"1.0.0"` and the user can override in step 04. The actual write happens in step 05.
|
|
150
218
|
|
|
151
219
|
### 5. Report Analysis Summary
|
|
152
220
|
|