bmad-module-skill-forge 1.3.0 → 1.4.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/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-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-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
|
@@ -171,46 +171,7 @@ scope:
|
|
|
171
171
|
|
|
172
172
|
## Human-Readable Presentation Format
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
```
|
|
177
|
-
Skill Brief: {name}
|
|
178
|
-
====================
|
|
179
|
-
|
|
180
|
-
Target: {source_repo}
|
|
181
|
-
Language: {language}
|
|
182
|
-
Forge Tier: {forge_tier}
|
|
183
|
-
Description: {description}
|
|
184
|
-
|
|
185
|
-
Scope: {scope.type}
|
|
186
|
-
Include: {scope.include patterns, one per line}
|
|
187
|
-
Exclude: {scope.exclude patterns, one per line}
|
|
188
|
-
Notes: {scope.notes}
|
|
189
|
-
|
|
190
|
-
{If source_type is "docs-only":}
|
|
191
|
-
Source Type: docs-only
|
|
192
|
-
Doc URLs:
|
|
193
|
-
{doc_urls, one per line with labels}
|
|
194
|
-
|
|
195
|
-
{If source_type is "source" AND supplemental doc_urls collected:}
|
|
196
|
-
Supplemental Docs:
|
|
197
|
-
{doc_urls, one per line with labels}
|
|
198
|
-
|
|
199
|
-
{If scripts_intent or assets_intent was explicitly set (not default "detect"):}
|
|
200
|
-
Scripts: {scripts_intent}
|
|
201
|
-
Assets: {assets_intent}
|
|
202
|
-
|
|
203
|
-
Source Authority: {source_authority}
|
|
204
|
-
|
|
205
|
-
{If target_version is set:}
|
|
206
|
-
Target Version: {target_version} (user-specified)
|
|
207
|
-
Detected Version: {detected_version or "N/A"}
|
|
208
|
-
{Else:}
|
|
209
|
-
Version: {version}
|
|
210
|
-
{End if}
|
|
211
|
-
Created: {created}
|
|
212
|
-
Created by: {created_by}
|
|
213
|
-
```
|
|
174
|
+
The runtime template lives in `steps-c/step-04-confirm-brief.md` §2 — that is the single source of truth for how the brief is rendered for user confirmation (brief-skill step-04 only; analyze-source batch generation does not render). If the rendering format needs to change, edit the step file. This asset documents the data contract; the step owns the presentation.
|
|
214
175
|
|
|
215
176
|
## Validation Rules
|
|
216
177
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Draft Checkpoint Lifecycle
|
|
2
|
+
|
|
3
|
+
The `.brief-draft.json` file at `{forge_data_folder}/{skill-name}/.brief-draft.json` is a step-01 in-flight-state checkpoint. It exists only while the workflow has progressed past §7 but not yet completed step-05 — once the final brief writes successfully, step-05 §4 removes it.
|
|
4
|
+
|
|
5
|
+
**Headless mode skips this entire lifecycle** — the run completes in a single invocation, so no resume is meaningful and no checkpoint is written.
|
|
6
|
+
|
|
7
|
+
The two halves of the lifecycle (resume on entry, write on §7 confirmation) form a pair. This file documents both so a single load covers them.
|
|
8
|
+
|
|
9
|
+
## Half 1 — Resume Check (loaded from §6 after name confirmation)
|
|
10
|
+
|
|
11
|
+
After the skill name is confirmed in §6, check for an in-progress draft at `{forge_data_folder}/{name}/.brief-draft.json`. **Only present the resume prompt** when the file exists AND no `skill-brief.yaml` sits beside it (a finished brief uses the same dir; if a finished brief exists, the draft is stale and step-05's overwrite gate is the right control point).
|
|
12
|
+
|
|
13
|
+
When the precondition is met, present:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
**An in-progress draft for `{name}` was found** (last updated: {mtime}).
|
|
17
|
+
[Y] Resume from the saved draft (jump to §8 with prior answers restored)
|
|
18
|
+
[N] Start fresh (delete the draft and re-gather)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### `[Y]` — Resume
|
|
22
|
+
|
|
23
|
+
Load the JSON and restore the captured fields: `target_repo`, `source_type`, `source_authority`, `target_version`, `doc_urls`, `intent`, `scope_hint`, `description`, `forge_tier`, `tier_source`. Then jump directly to §8 — **skip the rest of §6, all of §7, and all of §7b**.
|
|
24
|
+
|
|
25
|
+
The skip rule for §7b is load-bearing: re-running §7b would overwrite the user's previously accepted `description` with a fresh candidate synthesized from the seed material. The restored `description` is authoritative.
|
|
26
|
+
|
|
27
|
+
The user can still revise any field at step-04 §3 if a refinement is needed after the full brief is visible.
|
|
28
|
+
|
|
29
|
+
### `[N]` — Start fresh
|
|
30
|
+
|
|
31
|
+
Delete `.brief-draft.json` and continue forward to §7 — the collision check and portfolio-similarity check have already executed earlier in §6 (before the resume prompt fired) and do not repeat. §7 / §7b then proceed in their normal order.
|
|
32
|
+
|
|
33
|
+
## Half 2 — Checkpoint Write (loaded from §7 after summary confirmation)
|
|
34
|
+
|
|
35
|
+
After the user confirms the §7 summary, persist the captured state atomically. Write a single JSON object with all of:
|
|
36
|
+
|
|
37
|
+
- `target_repo`, `source_type`, `source_authority`
|
|
38
|
+
- `target_version` (if set)
|
|
39
|
+
- `doc_urls` (if collected)
|
|
40
|
+
- `intent`, `scope_hint`
|
|
41
|
+
- `description` (the §7b accepted text)
|
|
42
|
+
- `forge_tier`, `tier_source` (for diagnostics)
|
|
43
|
+
|
|
44
|
+
Atomic-write protocol: write to `.brief-draft.json.tmp` first, then `mv .brief-draft.json.tmp .brief-draft.json`. The rename is atomic on a single filesystem; a partial write never becomes visible as `.brief-draft.json`.
|
|
45
|
+
|
|
46
|
+
The file is removed by step-05 §4 after the final brief writes successfully.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Headless Argument Table
|
|
2
|
+
|
|
3
|
+
Loaded by step-01 §8 only when `{headless_mode}` is true. Canonical operator-facing documentation for the argument set consumed at step-01's GATE; the `{validateBriefInputsScript}` enforces these rules deterministically (its `KNOWN_FIELDS` set must stay in sync with this table).
|
|
4
|
+
|
|
5
|
+
| Argument | Required | Default | Notes |
|
|
6
|
+
|----------|----------|---------|-------|
|
|
7
|
+
| `target_repo` | yes | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent |
|
|
8
|
+
| `skill_name` | yes | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent; HALT (exit 2, `halt_reason: "input-invalid"`) if non-kebab |
|
|
9
|
+
| `source_type` | no | `source` | If `docs-only`, `doc_urls` becomes required |
|
|
10
|
+
| `doc_urls` | conditional | — | Required when `source_type=docs-only` (HALT exit 2, `halt_reason: "input-missing"` if empty). List of `url` or `url,label` |
|
|
11
|
+
| `source_authority` | no | detected | `official` / `community` / `internal`. When absent and `target_repo` is a GitHub URL, step-01 §8 GATE probes `gh api user` and compares its login to the URL owner — match → `official`, otherwise → `community`. Local-path or `gh api user` failure → `community`. Forced to `community` when `source_type=docs-only` |
|
|
12
|
+
| `target_version` | no | — | Auto-detected in step-02 if absent. Full X.Y.Z semver required (HALT exit 2, `halt_reason: "input-invalid"` on partial forms like `1`, `1.2`, `v2`) |
|
|
13
|
+
| `scope_hint` | no | — | Free-text steering for §5 |
|
|
14
|
+
| `language_hint` | no | — | Overrides language detection in step-02/03 |
|
|
15
|
+
| `scope_type` | no | heuristic | `full-library` / `specific-modules` / `public-api` / `component-library` / `reference-app` / `docs-only`. When absent and `source_type=source`, step-03 §2c runs five signal-driven heuristics (component-registry presence, reference-app keywords, specific-module intent, narrow public API) and uses the first match; falls back to `full-library` only if no heuristic fires. `source_type=docs-only` always short-circuits to `docs-only` |
|
|
16
|
+
| `include` | no | — | Comma-separated globs (used by step-03 §3) |
|
|
17
|
+
| `exclude` | no | — | Comma-separated globs (used by step-03 §3) |
|
|
18
|
+
| `scripts_intent` | no | `detect` | `detect` / `none` / free-text |
|
|
19
|
+
| `assets_intent` | no | `detect` | `detect` / `none` / free-text |
|
|
20
|
+
| `intent` | no | — | Free-text used to derive `description` in §7b |
|
|
21
|
+
| `force` | no | — | Overwrite existing brief without prompting (consumed in step-05 §2b) |
|
|
22
|
+
| `preset` | no | — | Name of a preset YAML file at `{sidecar_path}/brief-presets/{preset}.yaml`. Loaded at step-01 §8 GATE and merged as defaults; explicit args override preset values. Useful for repeated patterns (e.g. briefing 5 SaaS API SDKs with the same `source_authority`/`scope_type`/`scripts_intent`). The preset file is YAML containing any subset of the headless args above; unknown fields are ignored with a warning |
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Headless Source-Authority Detection
|
|
2
|
+
|
|
3
|
+
Loaded by step-01 §8 only when **all three** preconditions hold:
|
|
4
|
+
|
|
5
|
+
1. `{headless_mode}` is true
|
|
6
|
+
2. `source_authority` is absent from the validator's `normalized` output (the validator intentionally leaves it absent so detection can run here — when it was supplied in args, the supplied value wins and detection does not run)
|
|
7
|
+
3. `source_type=source` AND `target_repo` is a GitHub URL
|
|
8
|
+
|
|
9
|
+
When any precondition is unmet, skip this entire procedure: docs-only forces `community` in §3.2; local-path targets default to `community` directly; non-GitHub source URLs are not classifiable from `gh api user` and also default to `community`.
|
|
10
|
+
|
|
11
|
+
## Procedure
|
|
12
|
+
|
|
13
|
+
Probe the operator's GitHub login:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gh api user --jq .login
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Compare the result to the `owner` segment of `target_repo` (URL pattern `https://github.com/<owner>/<repo>`) — **lower-case both values before comparing**. GitHub owner matching is case-insensitive but the API preserves case in responses, so a literal-string comparison would miss the match.
|
|
20
|
+
|
|
21
|
+
| Outcome | Set | Rationale |
|
|
22
|
+
|---------|-----|-----------|
|
|
23
|
+
| login matches owner (case-insensitive) | `source_authority: "official"` | The operator is the repo's GitHub owner |
|
|
24
|
+
| login does not match | `source_authority: "community"` | The operator is a downstream consumer |
|
|
25
|
+
| `gh api user` errors (unauthenticated, network failure, missing binary) | `source_authority: "community"` (fallback) | Log `"warn: source-authority detection skipped — gh api user failed"` |
|
|
26
|
+
| Local-path target | `source_authority: "community"` (no probe) | Comparison does not apply |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Portfolio-Similarity Check
|
|
2
|
+
|
|
3
|
+
Loaded by step-01 §6 only when **all three** preconditions hold:
|
|
4
|
+
|
|
5
|
+
1. Forge tier is `Deep`
|
|
6
|
+
2. `tools.qmd` is true in `forge-tier.yaml`
|
|
7
|
+
3. The flow is interactive (the headless path skips this check entirely — it would either need to HALT on duplicates [over-aggressive for an automator] or silently log [no operator to act on it]; either choice has a side effect on the QMD index that is best avoided headlessly)
|
|
8
|
+
|
|
9
|
+
This check catches **semantic near-duplicates** that exact-name collision misses (e.g. `markdown-renderer` proposed when `marked` already exists, or `auth-gateway` when `auth-middleware` already exists). Exact-name collision is handled separately at §6 before this check runs.
|
|
10
|
+
|
|
11
|
+
## Procedure
|
|
12
|
+
|
|
13
|
+
The brief portfolio is already indexed in QMD collections — one `{skill-name}-brief` collection per existing brief, registered by step-05 §5 of every prior Deep-tier run. The qmd CLI does not support glob-style collection selection, so enumerate first then query per collection in **a single message with N parallel Bash calls**:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Enumerate brief collections (one per existing brief)
|
|
17
|
+
qmd collection list | awk '/-brief$/{print $1}'
|
|
18
|
+
|
|
19
|
+
# 2. For each collection, query the proposed name + intent text:
|
|
20
|
+
qmd query "{name} {synthesized-or-intent-text}" -c {collection-name} -n 1 --min-score 0.6
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Aggregate the top hits across all `-brief` collections; keep the 3 highest-scoring across the union. If any results come back, surface them as a heads-up — *not* a HALT:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
**Heads up — these existing briefs look semantically close to `{name}`:**
|
|
27
|
+
1. {existing-name} (similarity: {score}) — {existing-description}
|
|
28
|
+
2. {existing-name} (similarity: {score}) — {existing-description}
|
|
29
|
+
|
|
30
|
+
Continue with `{name}`, or pick a different name?
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Failure modes
|
|
34
|
+
|
|
35
|
+
On any QMD failure (binary missing, collection list empty, any per-collection query times out): log `"warn: portfolio-similarity check skipped — qmd query failed: {error}"` and continue silently — never HALT. Quick / Forge / Forge+ tiers do not run this check (qmd is Deep-tier-only per the canonical tier definition: `Deep = + ast-grep + gh + QMD` in `skf-forge-tier-rw.py`).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# QMD Collection Registration (Deep Tier)
|
|
2
|
+
|
|
3
|
+
Loaded by step-05 §5 only when forge tier is Deep AND QMD is available. Skipped silently otherwise.
|
|
4
|
+
|
|
5
|
+
Index the skill brief into a QMD collection so portfolio-level searches can find existing briefs and avoid duplicate skill creation across large monorepos.
|
|
6
|
+
|
|
7
|
+
## Collection Creation
|
|
8
|
+
|
|
9
|
+
Create a QMD collection targeting only the brief file:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
qmd collection add {forge_data_folder}/{skill-name} --name {skill-name}-brief --mask "skill-brief.yaml"
|
|
13
|
+
qmd embed
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If the collection already exists (re-briefing): remove and recreate for atomic replace:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
qmd collection remove {skill-name}-brief
|
|
20
|
+
qmd collection add {forge_data_folder}/{skill-name} --name {skill-name}-brief --mask "skill-brief.yaml"
|
|
21
|
+
qmd embed
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Embed Verification
|
|
25
|
+
|
|
26
|
+
After `qmd embed` completes, verify the collection was embedded:
|
|
27
|
+
|
|
28
|
+
- Run `qmd status` or `qmd collection list` and confirm `{skill-name}-brief` shows document count > 0
|
|
29
|
+
- If verification succeeds: proceed to registry update with no `status` field
|
|
30
|
+
- If verification fails: log warning "QMD embed verification failed for {skill-name}-brief — collection may not be searchable yet", proceed to registry update but include `status: "pending"` in the entry
|
|
31
|
+
|
|
32
|
+
## Registry Update (Delegated to Script)
|
|
33
|
+
|
|
34
|
+
Build the entry JSON and pipe it to the `register-qmd-collection` subcommand:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
echo '{
|
|
38
|
+
"name": "{skill-name}-brief",
|
|
39
|
+
"type": "brief",
|
|
40
|
+
"source_workflow": "brief-skill",
|
|
41
|
+
"skill_name": "{skill-name}",
|
|
42
|
+
"created_at": "{current ISO date}"
|
|
43
|
+
// include "status": "pending" only when embed verification failed
|
|
44
|
+
}' | uv run {forgeTierRwScript} register-qmd-collection --target {forgeTierFile}
|
|
45
|
+
```
|
|
46
|
+
|
|
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.
|
|
48
|
+
|
|
49
|
+
## Error Handling
|
|
50
|
+
|
|
51
|
+
- If `qmd embed` or `qmd collection add` fails: log the error. Do NOT fail the workflow — the brief file was already written successfully.
|
|
52
|
+
- If the `register-qmd-collection` script call fails: log the error JSON, continue. The brief is the user-visible artifact; the registry entry is a portfolio-search optimisation.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Version Resolution
|
|
2
|
+
|
|
3
|
+
Single source of truth for how brief-skill resolves the `version` field of `skill-brief.yaml`. Loaded by step-02 §4b (auto-detect, fallback path only when the language is not script-supported) and step-05 §3 (resolve & write) so both operate on the same precedence rules and invariant. Step-01 §3b references this file in prose for human-readable rationale but does not load it — that step only collects `target_version` and validates its shape with an inline regex.
|
|
4
|
+
|
|
5
|
+
**Aligned with** `assets/skill-brief-schema.md` "Version Detection" section. If you change one, change the other.
|
|
6
|
+
|
|
7
|
+
## Detection Algorithm
|
|
8
|
+
|
|
9
|
+
For the detected source language, attempt the lookups in order. Stop at the first match.
|
|
10
|
+
|
|
11
|
+
- **Python:** `pyproject.toml` `[project] version` (static) → if `dynamic = ["version"]`, check `__init__.py` for `__version__` → `_version.py` if exists → `setup.py` `version=` → `git describe --tags --abbrev=0`
|
|
12
|
+
- **JavaScript / TypeScript:** root `package.json` (`"version"`). If the 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.
|
|
13
|
+
- **Rust:** `Cargo.toml` `[package] version` (static). If `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0`.
|
|
14
|
+
- **Go:** version tag from `go.mod`, or `git describe --tags --abbrev=0`.
|
|
15
|
+
|
|
16
|
+
For remote GitHub sources, fetch version-bearing files via `gh api repos/{owner}/{repo}/contents/{file}` (decode base64). For local sources, read the file directly.
|
|
17
|
+
|
|
18
|
+
If every step fails or returns a non-semver value, the detected version is `null` — the resolver below falls back to `"1.0.0"`.
|
|
19
|
+
|
|
20
|
+
**Pre-release handling:** preserve detected pre-release tags (`1.0.0-beta.0`, `2.0.0-rc.1`) verbatim. Do not strip them.
|
|
21
|
+
|
|
22
|
+
## Precedence — Resolving the `version` Field
|
|
23
|
+
|
|
24
|
+
The brief's `version` field is resolved from three candidate sources, in priority order:
|
|
25
|
+
|
|
26
|
+
1. **`target_version`** — collected interactively in step-01 §3b or supplied as a headless argument. When present, this value wins outright. The auto-detection above still runs for informational purposes (the user sees both "Target version" and "Detected version" side-by-side at the analysis summary), but the brief's `version` field is set from `target_version`.
|
|
27
|
+
2. **Auto-detected version** — from §"Detection Algorithm" above. Used when `target_version` is absent.
|
|
28
|
+
3. **Default** — `"1.0.0"` when both of the above fail or yield a non-semver value.
|
|
29
|
+
|
|
30
|
+
## Invariant
|
|
31
|
+
|
|
32
|
+
When `target_version` is set, the written brief MUST satisfy:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
brief.target_version == brief.version
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Step-05 §3 enforces this by setting both fields to the same string when `target_version` is present. Downstream tooling (e.g. `skf-create-skill`) can distinguish "user-requested" from "auto-detected" by the presence of `target_version` without re-deriving provenance — but the values themselves are identical. Different values are a contract violation and a bug.
|
|
39
|
+
|
|
40
|
+
## Step-Level Responsibilities
|
|
41
|
+
|
|
42
|
+
| Step | Responsibility |
|
|
43
|
+
|------|----------------|
|
|
44
|
+
| 01 §3b | Collect `target_version` (interactive prompt, or headless arg). Do not auto-detect — that is step 02's job. |
|
|
45
|
+
| 02 §4b | Run the detection algorithm regardless of whether `target_version` is set. If `target_version` is set and the detected version differs, surface the disagreement to the user — but the precedence above is unchanged: `target_version` wins. |
|
|
46
|
+
| 05 §3 | Apply the precedence rules and write `version`. If `target_version` is set, also write the `target_version` field with the identical value. Enforce the invariant. |
|
|
@@ -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
|