Claude Skill

skf-brief-skill

Design a skill scope through guided discovery. Use when the user requests to "create a skill brief" or "brief a skill".

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download armelhbobdad-bmad-module-skill-forge-src_skf-brief-skill-492e73e.zip · 73 KB
Part of armelhbobdad/bmad-module-skill-forge — 15 skills

Install

skills CLI npx skills add https://github.com/armelhbobdad/bmad-module-skill-forge/tree/main/src/skf-brief-skill
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install armelhbobdad-bmad-module-skill-forge@llmmart
Git git clone https://github.com/armelhbobdad/bmad-module-skill-forge.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole armelhbobdad/bmad-module-skill-forge collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Brief Skill

Overview

Helps the user define what to skill — target repo, scope, language, inclusion/exclusion patterns — and produces a skill-brief.yaml that drives create-skill. This is the first step in the skill creation pipeline; the brief is the input contract for create-skill, which performs the actual compilation.

A good skill brief sets a tight, cohesive boundary: one capability with 3-8 primary functions, an unambiguous public API surface, and a description short enough to fit in a registry row. Briefs that try to cover several unrelated concerns (e.g. authentication and data visualization) compile into skills that no agent can route to confidently — a brief covering too much is a worse failure mode than a brief covering too little, and this workflow steers toward the smaller, sharper version when scope is unclear. Scope on cheap signals — manifests, top-level exports, intent — not full AST extraction.

Ratify path. A pre-authored skill-brief.yaml (typically from skf-analyze-source's generate-briefs step) can be ratified — reviewed and rewritten in place — instead of re-derived from scratch. Interactively, pass its path at the first prompt; headlessly, pass from_brief <path>. See the from_brief Inputs cell in references/invocation-contract.md for the full ratify contract.

Conventions

  • Bare paths (e.g. references/<name>.md) resolve from the skill root.
  • references/ holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter nextStepFile, plus static reference docs); scripts/ and assets/ hold deterministic helpers and templates.
  • {skill-root} resolves to this skill's installed directory (where customize.toml lives, if present).
  • {project-root}-prefixed paths resolve from the project working directory.
  • {skill-name} resolves to the skill directory's basename.

Role

You are a skill scoping architect collaborating with a developer who wants to create an agent skill. You bring expertise in source code analysis, API surface identification, and skill boundary design, while the user brings their domain knowledge and specific use case. Work together as equals.

Workflow Rules

These rules apply to every step in this workflow:

  • Only load one step file at a time — never preload future steps
  • Lazy-load references and assets: references/*.md and assets/*.md files are loaded inside the section that needs them, not at step entry. If a section is skipped (e.g. version-resolution.md when {extractPublicApiHelper} already returned a version, scope-templates.md for the docs-only branch that bypasses §2c), do not load that file. Each unnecessary load costs context (~5-10 KB per reference) and biases the LLM toward consulting material the current path does not need.
  • Always communicate in {communication_language} (the language for user-facing prose). Written artifact text — the description, notes, and other free-form fields persisted into skill-brief.yaml — is in {document_output_language}; per-step rules call this out where it applies (see step 5). The two values may be the same.
  • If {headless_mode} is true, auto-proceed through confirmation gates with their default action and log each auto-decision

On Activation

  1. Load config from {project-root}/_bmad/skf/config.yaml and resolve:

    • project_name, output_folder, user_name, communication_language, forge_data_folder, sidecar_path
  2. Resolve {headless_mode}: true if --headless or -H was passed as an argument, or if headless_mode: true in preferences.yaml. Default: false.

  3. Resolve workflow customization. Run:

    python3 {project-root}/_bmad/scripts/resolve_customization.py \
        --skill {skill-root} --key workflow
    

    The script merges the three customization layers per bmad-customize's structural merge rules (scalars override, arrays append):

    • {skill-root}/customize.toml — bundled defaults
    • _bmad/custom/<skill-name>.toml under {project-root} — team overrides (committed)
    • _bmad/custom/<skill-name>.user.toml under {project-root} — personal overrides (gitignored)

    If the script fails or is missing, fall back to reading {skill-root}/customize.toml directly — the bundled defaults are an empty string for each path scalar.

    Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each of the three scalars, if the merged value is empty or absent, use the bundled default:

    • {descriptionVoiceExamplesPath} ← workflow.description_voice_examples_path if non-empty, else assets/description-voice-examples.md
    • {scopeTemplatesPath} ← workflow.scope_templates_path if non-empty, else assets/scope-templates.md
    • {briefSchemaPath} ← workflow.brief_schema_path if non-empty, else assets/skill-brief-schema.md
    • {onCompleteCommand} ← workflow.on_complete if non-empty, else empty string (no-op — write-brief.md skips the hook invocation entirely)

    Stash all four as workflow-context variables. Stage files reference {descriptionVoiceExamplesPath} / {scopeTemplatesPath} / {briefSchemaPath} / {onCompleteCommand} directly — no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default; non-empty values let orgs swap in house-style copies (or wire in a pipeline hook) without forking the skill.

    Also apply the array surfaces so they are not silent no-ops: execute each entry in workflow.activation_steps_prepend in order now; treat every entry in workflow.persistent_facts as standing context for the whole run (file:-prefixed entries are paths or globs whose contents load as facts — the bundled default loads any project-context.md under {project-root}); then, after activation completes and before step 4 loads the first stage, execute each entry in workflow.activation_steps_append in order.

  4. Load, read the full file, and execute references/gather-intent.md.

Stages

# Step File Auto-proceed
1 Gather Intent references/gather-intent.md No (interactive)
1a Auto-Brief Generation (auto mode only) references/step-auto-brief.md Yes
1b Auto-Brief Validation (auto mode only) references/step-auto-validate.md No (interactive gate — headless auto-approves)
2 Analyze Target references/analyze-target.md Yes
3 Scope Definition references/scope-definition.md No (interactive)
4 Confirm Brief references/confirm-brief.md No (confirm)
5 Write Brief references/write-brief.md Yes
6 Workflow Health Check (terminal) references/health-check.md Yes

Stages 1a-1b are conditional — they replace stages 2-5 when BS is invoked with the [auto] flag via pipeline context. The routing decision is made in stage 1 (gather-intent.md §1b). In auto mode, the chain is: gather-intent.md §1 (forge tier) → §1b (auto check) → step-auto-brief.md → step-auto-validate.md → health-check.md (on [A]pprove or [E]dit) or → confirm-brief.md → write-brief.md → health-check.md (on [R]eject).

Invocation Contract

Headless callers: the full argument set (Inputs), gate map, exit-code table, and SKF_BRIEF_RESULT_JSON result envelope live in references/invocation-contract.md. Interactive runs do not need it.

Files (bmad-module-skill-forge)
  • assets
    • description-voice-examples.md 2 KB
      # Description Voice Examples
      
      Loaded by step 1 §7b only. The five examples below show the *range* of acceptable voices for the `description` field — they vary in lead, structure, and trigger phrasing on purpose. The point is to anchor the LLM to "two facts must come through (what the skill is, when to use it); everything else is voice — do not template-stamp."
      
      ## Examples
      
      > Render Markdown to HTML using the marked library. Use when the user pastes raw Markdown and wants formatted output, or asks how to convert MD files in a build pipeline.
      
      > Stripe API client for Node.js — payment intents, subscriptions, customer portal, webhooks. Use when the task involves Stripe-managed payments, subscription billing, or webhook event handling.
      
      > Charts and visualizations powered by D3.js. Use when the user asks to plot data, build interactive graphs, or wants bare D3 control instead of a React-charts abstraction.
      
      > Lint Python code with Ruff. Use when the user wants to add or configure Ruff in a Python project, debug rule selectors, or understand why a specific check fired.
      
      > Date and time arithmetic via Luxon — parsing, formatting, time zones, durations, intervals. Use when working with dates in ways that exceed `Date.toISOString()` but you don't want a full Moment.js footprint.
      
      ## Notes on Voice
      
      Each example leads differently (verb / noun / "Charts and..." / verb / noun-phrase) rather than copy-pasting a single template. Compose in that spirit using the gathered material — the target repo, the user's intent, the version if set, and any scope hints — but **do not template-stamp**.
      
      The **lead** is where the voice varies; the **trigger clause** is fixed. Every description must contain a literal `Use when` clause somewhere, because validators test for that exact phrase — `skill-check`'s `description.use_when_phrase` rule scores a description below 100 without it. Phrasings like "Triggers on…" or "Reach for this when…" read well but do not satisfy it on their own, so keep them for additional text rather than as the only trigger.
      
    • scope-templates.md 7.2 KB
      # Scope Templates Reference
      
      ## Scope Type Options
      
      Present these options to the user for selection:
      
      **[F] Full Library** — Include everything. Best for smaller, focused libraries.
      - All public exports, all modules
      - Exclude only tests, build artifacts, and internal utilities
      - *Looks like:* `marked` (single-purpose Markdown→HTML); `nanoid` (id generator); `zod` (validation library)
      
      **[M] Specific Modules** — Select which modules to include. Best for large libraries where only some parts are relevant.
      - You choose which modules/directories
      - Fine-grained control over what's in and out
      - *Looks like:* `lodash` skill scoped to just `lodash/array` and `lodash/string`; `aws-sdk` skill scoped to just S3 and DynamoDB
      
      **[P] Public API Only** — Include only the public-facing API surface. Best for libraries with a clear public/private boundary.
      - Entry points and exported interfaces only
      - Internal implementation excluded
      - *Looks like:* `stripe` (payment intents, subscriptions, webhooks — not internal HTTP plumbing); `redis` client (connection + commands, not protocol parsers)
      
      **[C] Component Library** — Optimized for UI component libraries with registries, props-based APIs, and design system variants.
      - Component registry as primary API surface (not individual exports)
      - Props interfaces as API contracts (not function signatures)
      - Auto-exclude demo/example/story files (with user confirmation)
      - Variant consolidation across design systems
      - *Looks like:* `shadcn-ui` (Button, Dialog, Form... 50+ components); Material-UI; Carbon Design System
      
      **[R] Reference App** — Whole-app pattern-reference skill. Use when the source is a working example app and the skill's value is **wiring patterns** (lifecycle, IPC, build-config, distribution) rather than a public library API.
      - Pattern surface as primary API slot (not individual exports)
      - Adoption Steps as primary workflow format (not API-call chains)
      - Tier 2 organized as `references/pattern-*.md` groupings (not per-function)
      - Export-count stats are pattern-surface proxies, not library exports
      - *Looks like:* a Tauri starter app (window setup + IPC bridge + build config); a Next.js auth example (route handlers + middleware + session storage wiring)
      
      ## Boundary Definitions by Scope Type
      
      ### Full Library Boundaries
      
      Default inclusions:
      - All source files under {main source directory}
      - All public modules: {list from analysis}
      
      Default exclusions:
      - Test files (`**/*.test.*`, `**/*.spec.*`, `**/test/`, `**/tests/`)
      - Build artifacts (`**/dist/`, `**/build/`, `**/target/`)
      - Configuration files
      - Documentation source files
      
      Prompt: "Any additional exclusions you'd like to add? Or adjustments to these defaults?"
      
      ### Specific Modules Boundaries
      
      **Phase 1 — Module selection:**
      
      Present numbered list of modules from step 02 with brief descriptions.
      Prompt: "Which modules would you like to include? (Enter numbers, comma-separated):"
      
      **Phase 2 — Granularity within selected modules:**
      
      For selected modules, ask:
      - **A)** Everything in those modules (all files)
      - **B)** Only public exports from those modules
      
      Prompt: "Any files or patterns to explicitly exclude within these modules?"
      
      ### Public API Only Boundaries
      
      **Phase 1 — Export selection:**
      
      Present numbered list of exports/entry points from step 02.
      Prompt: "Which of these would you like to include? (Enter numbers, or 'all'):"
      
      **Phase 2 — Confirm exclusions:**
      
      Exclusions will include all internal implementation files, tests, and utilities.
      Prompt: "Any additional items you'd like to include or exclude?"
      
      ### Reference App Boundaries
      
      **Phase 1 — Pattern surface intent:**
      
      Ask: "What is the authored pattern surface for this skill? List the files (or directories) the user must touch to adopt the pattern — entry points, config files, lifecycle hooks, build scripts."
      
      - Record the user's list as `scope.tier_a_include` when narrower than a broad `scope.include`. Reference-app briefs benefit strongly from `tier_a_include` because the denominator is small and precise.
      - Prompt follow-up: "Any files outside that list that should still be in scope for completeness (tests, fixtures, supporting configs)?"
      
      **Phase 2 — Scope.include and exclusions:**
      
      Set `scope.include` to the pattern-surface file list (or broader union when the author flagged supporting files). Default exclusions mirror the Full Library defaults (tests, build artifacts, docs source). Record `scope.notes` with a one-sentence description of the pattern (e.g., "Embedded Python sidecar pattern for Electron apps — lifecycle orchestration, RPC proxy, build-copy wiring").
      
      **Phase 3 — Confirmation:**
      
      Summary showing: pattern surface count, `tier_a_include` vs `include` distinction, notes. Prompt: "Does this reference-app scope look right? Adjust before continuing."
      
      ### Docs-Only Boundaries
      
      **No source code access.** Scope is defined by the `doc_urls` collected during intent gathering.
      
      - All content derived from external documentation
      - No include/exclude patterns — coverage determined by fetched documentation
      - All extractions labeled T3 (`[EXT:{url}]` citations)
      
      Prompt: "Any additional documentation URLs to include? Or URLs to exclude from the ones collected?"
      
      ### Component Library Boundaries
      
      **Phase 1 — Registry Detection:**
      
      Auto-detect or accept explicit `registry_path` from user. Scan source tree for files matching common registry patterns:
      - Files named `registry.ts`, `components.ts`, `index.ts` in `registry/`, `catalog/`, or `components/` directories
      - Arrays of objects with `{ id, name, component }` structure and 10+ entries
      - Files with `Component[]` type annotations
      
      Present detected registry candidate(s) to user for confirmation.
      Prompt: "I found what looks like a component registry at {path} ({count} entries). Is this correct? Or provide the registry path:"
      
      **Phase 2 — Demo/Example Exclusion:**
      
      Auto-detect demo directories and file patterns:
      - Directories: `demo/`, `demos/`, `stories/`, `examples/`, `__stories__/`, `storybook/`
      - Files: `*.stories.*`, `*.story.*`, `*.example.*`, `*.demo.*`
      
      Show detected patterns to the user for confirmation before applying them, rather than excluding files silently.
      Prompt: "**Auto-detected {N} demo/example files** in {M} directories. Confirm exclusion? [Y/n] Or adjust patterns:"
      
      **Phase 3 — Variant Selection (if applicable):**
      
      If multiple design system variant directories detected (e.g., `react-shadcn/`, `react-baseui/`, `react-carbon/`):
      - Present detected variants with component counts per variant
      - User selects primary variant and which variants to include
      - Record as `ui_variants` in brief
      
      Prompt: "I detected {count} design system variants: {list with counts}. Which is the primary variant? Include all? [Y/n]"
      
      **Phase 4 — Scope Confirmation:**
      
      Summary showing: component count, excluded demo count, variant summary, include/exclude patterns.
      Prompt: "Does this component library scope look right? Adjust before continuing."
      
      ## Scripts & Assets Detection (Optional Refinement)
      
      When `scripts_intent` or `assets_intent` is `detect` (default), SKF auto-detects from source directories matching: `scripts/`, `bin/`, `tools/`, `cli/` (for scripts) and `assets/`, `templates/`, `schemas/`, `configs/`, `examples/` (for assets). Detection applies to all scope types except `docs-only`.
      
    • skill-brief-schema.md 20.7 KB
      # Skill Brief Schema
      
      ## Required Fields
      
      | Field       | Type   | Constraint                                       | Description                                                                 |
      |-------------|--------|--------------------------------------------------|-----------------------------------------------------------------------------|
      | name        | string | kebab-case `[a-z0-9-]+`                          | Unique skill identifier                                                     |
      | version     | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | Auto-detect from source (see Version Detection below), fall back to `1.0.0`. **Side effect on remote sources:** `skf-create-skill` treats `version` as an **implicit** `target_version` hint when `target_version` itself is absent — it will try to resolve `{version}` or `v{version}` to a git tag before cloning and fall back to HEAD with a warning if no tag matches. See `skf-create-skill/references/source-resolution-protocols.md` → "Implicit Tag Resolution". |
      | source_repo | string | GitHub URL or local path                         | Repository or project root (optional when `source_type: "docs-only"`)       |
      | language    | string | Recognized language                              | Primary programming language                                                |
      | scope       | object | See Scope Object below                           | Boundary definition                                                         |
      | description | string | 1-3 sentences                                    | What the skill covers. Must contain a literal `Use when` clause — validators test for that exact phrase. |
      | forge_tier  | string | `Quick` / `Forge` / `Forge+` / `Deep`            | Inherited from forge-tier.yaml (Title Case)                                 |
      | created     | string | ISO date `YYYY-MM-DD`                            | Generation date                                                             |
      | created_by  | string | user_name from config                            | Who generated the brief                                                     |
      
      ## Optional Fields
      
      | Field              | Type   | Constraint                                       | Description                                                                                                                                                                                                                    |
      |--------------------|--------|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
      | source_type        | string | `source` or `docs-only`                          | Default `source`. When `docs-only`: `source_repo` optional, `doc_urls` required                                                                                                                                                |
      | doc_urls           | array  | `{url, label, source?}` objects                  | Documentation URLs for T3 content. Required when `source_type: "docs-only"`. Optional `source` provenance enum: `language-registry` (registry-guaranteed corpus) \| `readme-detection` \| `homepage` \| `pages-api` \| `docs-folder` |
      | `scripts_intent`   | string | `detect` / `none` / free-text                    | Describes whether scripts should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip scripts), or a free-text description of expected scripts (e.g., "CLI validation tools in bin/"). |
      | `assets_intent`    | string | `detect` / `none` / free-text                    | Describes whether assets should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip assets), or a free-text description of expected assets (e.g., "JSON schemas in schemas/").        |
      | `target_version`   | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | User-specified target version. When present, overrides auto-detection and becomes the skill's version. Recommended for docs-only skills where auto-detection is unavailable.                                                   |
      | `target_ref`       | string | Git ref (tag or branch)                          | Optional. Explicit git ref used verbatim as the resolved `source_ref`, bypassing version-to-tag matching. Escape hatch for monorepo crate tags whose prefix differs from the skill name (e.g. tag `livekit/v0.7.42` for skill `livekit-rust`). Remote sources only. |
      | `source_authority` | string | `official` / `community` / `internal`            | Default `community`. Set to `official` only when the skill creator is the library maintainer. Forced to `community` when `source_type: "docs-only"`.                                                                           |
      | `source_ref`       | string | Git ref (tag/branch/HEAD)                        | Resolved git ref used for source access. Set automatically during tag resolution — do not set manually.                                                                                                                        |
      | `scope.tier_a_include` | array | Glob patterns (min 1, non-empty)              | Optional. Narrower tier-A include list for a curated-subset skill — stratified monorepo packages, single-crate `specific-modules` scopes, and multi-entry exports-map packages. When present, `skf-test-skill` re-derives the coverage denominator from this list instead of the coarse `scope.include`, so the denominator reflects the authoring surface rather than incidentally-matched internal infrastructure. **It also exempts the brief from the denominator deflation guard**, so supply it only when it genuinely narrows the surface — never as an empty list, and never as a way to widen (listing files outside `scope.include` inverts its meaning). See `skf-test-skill/references/source-access-protocol.md` stratified-scope resolution. |
      
      When `source_type: "docs-only"`:
      - `source_repo` becomes optional (set to doc site URL for reference)
      - `doc_urls` must have at least one entry
      - `source_authority` is forced to `community` (T3 external documentation cannot be `official`)
      - All extracted content gets `[EXT:{url}]` citations
      
      ## Version Detection
      
      During brief creation, attempt to auto-detect the source version before defaulting to `"1.0.0"`. Check the first matching file in the source:
      
      - **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`
      - **JavaScript/TypeScript:** root `package.json` (`"version"`) → 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.
      - **Rust:** `Cargo.toml` `[package] version` (static) → if `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0`
      - **Go:** version tag from `go.mod` or `git describe --tags --abbrev=0`
      
      If the source is a remote GitHub repo, use `gh api repos/{owner}/{repo}/contents/{file}` to read the version file. If the source is local, read the file directly.
      
      If detection succeeds, use the detected version. If it fails or returns a non-semver value, fall back to `"1.0.0"`.
      
      The create-skill workflow (extract) also performs version reconciliation at extraction time — if the source version has changed since the brief was created, the extraction step warns and uses the source version.
      
      **Target version override:** When `target_version` is present in the brief, it takes precedence over auto-detection. Auto-detection still runs for informational purposes (displayed as "Detected version" alongside the user-specified "Target version"), but the `target_version` value is used as the brief's `version` field. This is particularly useful for docs-only skills (where no package manifest exists) and when the user wants to compile a skill for a specific older version.
      
      **Pre-release handling:** If the detected version contains a pre-release tag (e.g., `1.0.0-beta.0`, `2.0.0-rc.1`), preserve it as-is. Pre-release tags are valid semver and must not be stripped. When comparing versions during reconciliation, use semver-aware comparison that respects pre-release ordering.
      
      ## Scope Object Structure
      
      ```yaml
      scope:
        type: full-library | specific-modules | public-api | component-library | reference-app | docs-only
        include:
          - "src/**/*.ts"           # Glob patterns for included files/directories
        exclude:
          - "**/*.test.*"           # Glob patterns for excluded files
          - "**/node_modules/**"
        # Optional: narrower tier-A include list for stratified-scope monorepos
        # tier_a_include:
        #   - "code/core/src/manager-api/**"
        #   - "code/core/src/preview-api/**"
        notes: "Optional notes about scope decisions"
        # Optional: authoring-time scope-type rationale (written once by brief-skill 2c)
        # rationale:
        #   recommended: full-library
        #   chosen: public-api
        #   accepted_recommendation: false
        #   heuristic: narrow-public-api
        #   reason: "user overrode full-library->public-api: only documented API ships"
        #   recorded: "2026-05-18"
        # Optional: amendment log for scope decisions made during create-skill §2a,
        # update-skill §1b (auth-doc), and update-skill §1c (scope-expansion).
        # amendments:
        #   - path: "apps/docs/public/llms.txt"
        #     action: "promoted"          # "promoted" | "skipped" | "demoted-include" | "demoted-exclude"
        #     category: "auth-doc"        # "auth-doc" (default for legacy entries) | "scope-expansion"
        #     reason: "authoritative AI docs — only source for canonical install command"
        #     heuristic: "llms.txt"        # required for auth-doc; absent for scope-expansion
        #     date: "2026-04-11"
        #     workflow: "skf-create-skill"
        #   - path: "python/cocoindex/_internal/api.py"
        #     action: "promoted"
        #     category: "scope-expansion"
        #     reason: "out-of-scope new public API — drift report drift-report-20260424-212355.md"
        #     evidence: "~70 new exports flagged out-of-scope by audit"
        #     date: "2026-04-25"
        #     workflow: "skf-update-skill"
        # Additional fields when scope.type is "component-library":
        # registry_path: "path/to/registry.ts"  # Optional — auto-detected if omitted
        # ui_variants:                           # Optional — design system variants
        #   - name: "shadcnui"
        #     package: "packages/components/react-shadcn"
        # demo_patterns:                         # Optional — auto-detected if omitted
        #   - "**/demo/**"
        #   - "**/*.stories.*"
      ```
      
      ### Scope Rationale (Optional)
      
      `scope.rationale` is a single optional object recording **why the scope type was chosen at authoring time**. Unlike `scope.amendments[]` (an additive log that accumulates post-authoring decisions across workflow runs), `scope.rationale` is one decision, written once by `skf-brief-skill` step 03 §2c and revised in place on a step-4 `[R]` re-entry. It sits structurally beside `scope.amendments`, reusing the same structured / script-readable / human-auditable ethos rather than a prose decision log.
      
      **Fields:**
      
      | Field | Type | Required | Source |
      |---|---|---|---|
      | `recommended` | string (one of the six `scope.type` values) | yes | `skf-recommend-scope-type.py` → `scope_type` |
      | `chosen` | string (one of the six) | yes | final `scope.type` |
      | `accepted_recommendation` | bool | yes | `chosen == recommended` |
      | `heuristic` | string | yes | script `matched_heuristic` |
      | `reason` | string | yes | accepted → script `rationale` verbatim; overridden → user's stated reason, or `"user overrode {recommended}->{chosen}; reason not stated"` |
      | `recorded` | string (ISO date `YYYY-MM-DD`) | yes | current date — mirrors `amendments[].date` |
      
      **Who reads `scope.rationale`:**
      
      - Humans reviewing the brief — it records why the boundary was drawn the way it was.
      - `skf-update-skill` Update intent MAY later surface conflicts against it (e.g., a scope change that contradicts the original authoring decision). **Not implemented now — deferred.** The field is forward-compatible; the reader is wired later.
      
      **Backward compatibility:** `scope.rationale` is optional. Briefs without this field validate unchanged — treat missing as absent (null). Mirrors the `scope.amendments` backward-compat rule.
      
      ### Scope Amendments (Optional)
      
      `scope.amendments[]` is an additive, optional audit log of scope decisions made by workflows after the brief was first authored. Two writer paths exist today:
      
      - **Auth-doc promotions** (`category: "auth-doc"`) — `skf-create-skill` §2a and its mirror `skf-update-skill` §1b append entries when extraction discovers authoritative AI documentation files (`llms.txt`, `AGENTS.md`, etc.) that the original scope patterns excluded.
      - **Scope-expansion promotions** (`category: "scope-expansion"`) — `skf-update-skill` §1c appends entries when an audit drift report flags out-of-scope new public API paths (typically a major-version restructure where the brief's `scope.include` no longer reflects the real surface).
      
      **Entry fields:**
      
      | Field | Type | Required | Description |
      |---|---|---|---|
      | `path` | string | yes | Relative path (or glob, for `category: "scope-expansion"`) from source root to the file or tree being amended. For `promoted` actions this matches the literal entry added to `scope.include`. |
      | `action` | string | yes | One of: `promoted` (path added to `scope.include`), `skipped` (user declined promotion; decision recorded to prevent re-prompting), `excluded` (path added to `scope.exclude` — only valid with `category: "scope-expansion"`; used by gap-driven rescope to remove an internal / `#[doc(hidden)]` / out-of-scope export from the public surface), `demoted-include` (path removed from `scope.include` — only valid with `category: "scope-expansion"`), `demoted-exclude` (path removed from `scope.exclude` — only valid with `category: "scope-expansion"`). |
      | `category` | string | no | One of: `auth-doc` (default for entries without this field — the historical sole use case), `scope-expansion`. Distinguishes which workflow path wrote the entry and which writer-rules apply on re-runs. |
      | `reason` | string | yes | Human-readable sentence explaining the decision. Either user-provided at prompt time or auto-generated. |
      | `heuristic` | string | conditional | Required for `category: "auth-doc"` — the basename that matched (`llms.txt`, `AGENTS.md`, etc.). Omit for `category: "scope-expansion"`. |
      | `evidence` | string | conditional | Required for `category: "scope-expansion"` — short rationale from the source signal (e.g., a drift-report finding's evidence one-liner). Omit for `category: "auth-doc"`. |
      | `date` | string | yes | ISO date (`YYYY-MM-DD`) when the amendment was recorded. |
      | `workflow` | string | yes | Workflow name that wrote the amendment (`skf-create-skill`, `skf-update-skill`). Identifies which workflow made the decision. |
      
      **Promotion write-through:** When `action: "promoted"`, the workflow also appends the literal path to `scope.include`. This is a belt-and-suspenders design: future runs read `scope.include` during scope filtering and include the file in the filtered list automatically, so the §2a/§1b/§1c discovery loop finds no candidate and does not re-prompt. The `amendments[]` entry is the human-readable audit trail of *why* the path was added.
      
      **Skip recording:** When `action: "skipped"`, the workflow does NOT modify `scope.include` or `scope.exclude`. The amendment entry alone is enough to prevent re-prompting, because the discovery loop checks `amendments[]` before prompting.
      
      **Demotion (scope-expansion only):** `demoted-include` removes a previously-promoted path from `scope.include` — used when a prior `[P]` decision is reversed. `demoted-exclude` removes a path from `scope.exclude` — used when a previously excluded path needs to be re-evaluated. Both write the structural change and append the amendment so future runs see the rationale. Demotion is not valid for `category: "auth-doc"`: auth-doc skips already prevent re-prompting without scope mutation.
      
      **Exclusion (scope-expansion only):** `excluded` adds a path to `scope.exclude` — written by `skf-update-skill` gap-driven rescope (detect-changes §0 rule R1) when a coverage gap's remediation is removal (the export is internal, `#[doc(hidden)]`, or out of scope). The amendment is the audit trail; the `scope.exclude` write is what shrinks the source barrel, so the legitimate scope reduction is expressed in the brief rather than by editing `metadata.stats`. This keeps the reduction visible to `skf-test-skill`'s denominator-deflation check, which re-derives the barrel from `scope.include` filtered by `scope.exclude`. Not valid for `category: "auth-doc"`.
      
      **Backward compatibility:** `scope.amendments` is optional. Briefs without this field validate unchanged. Treat missing as an empty list. Existing entries without `category` are equivalent to `category: "auth-doc"` — readers must default the field when absent.
      
      **Who reads `amendments[]`:**
      
      - `skf-create-skill` §2a consults it to avoid re-prompting on decided auth-doc files.
      - `skf-update-skill` §1b (mirror of §2a) consults it for the same auth-doc reason.
      - `skf-update-skill` §1c consults it to avoid re-prompting on decided scope-expansion candidates and to honor prior `demoted-*` decisions.
      - `skf-audit-skill` may optionally report on stale promotions (promoted paths that no longer exist in source) as a future enhancement — not currently implemented.
      - Humans reading the brief see the audit trail of non-obvious scope decisions.
      
      **Who writes `amendments[]`:**
      
      - `skf-create-skill` §2a (Discovered Authoritative Files Protocol) — `category: "auth-doc"`
      - `skf-update-skill` §1b (mirror of §2a applied during change detection) — `category: "auth-doc"`
      - `skf-update-skill` §1c (Major-Version Scope Reconciliation) — `category: "scope-expansion"`
      - `skf-update-skill` gap-driven rescope (detect-changes §0 rule R1) — `category: "scope-expansion"`, `action: "excluded"`
      - Manual edits by the brief author are permitted but should include all required fields above (and `category` when the entry is not an auth-doc decision).
      
      ## YAML Template
      
      ```yaml
      ---
      name: "{skill-name}"
      version: "{detected-version or 1.0.0}"  # Auto-detect from source, fall back to 1.0.0
      source_type: "source"                    # "source" (default) or "docs-only"
      source_repo: "{github-url-or-local-path}"
      language: "{detected-language}"
      description: "{brief-description}"
      forge_tier: "{Quick|Forge|Forge+|Deep}"
      created: "{date}"
      created_by: "{user_name}"
      scope:
        type: "{full-library|specific-modules|public-api|component-library|reference-app|docs-only}"
        include:
          - "{pattern}"
        exclude:
          - "{pattern}"
        notes: "{optional-scope-notes}"
      # target_version: "X.Y.Z"       # Optional: overrides auto-detection when specified
      # target_ref: "livekit/v0.7.42" # Optional: explicit git ref, used verbatim (monorepo crate tags)
      # source_ref: "v0.5.0"          # Auto-resolved — do not set manually
      # Optional: documentation URLs for T3 content (required when source_type: "docs-only")
      # doc_urls:
      #   - url: "https://docs.example.com/api"
      #     label: "API Reference"
      # scripts_intent: detect         # Optional: detect | none | description
      # assets_intent: detect          # Optional: detect | none | description
      # source_authority: community    # Optional: official | community | internal
      ---
      ```
      
      ## Human-Readable Presentation Format
      
      The runtime template lives in `references/confirm-brief.md` §2 — that is the single source of truth for how the brief is rendered for user confirmation (brief-skill step 4 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.
      
      ## Validation Rules
      
      1. `name` must be unique within {forge_data_folder}
      2. `source_repo` must be accessible (gh api for GitHub, path exists for local)
      3. `language` must be a recognized programming language
      4. `scope.type` must be one of the six defined types
      5. `scope.include` must have at least one pattern (exception: `docs-only` scope, where include patterns are optional since no source code is available)
      6. `forge_tier` must be one of: Quick, Forge, Forge+, Deep (Title Case, must match the tier from forge-tier.yaml, or default to Quick)
      7. When `source_type: "docs-only"`: `doc_urls` must have >= 1 entry, `source_repo` becomes optional
      8. Each `doc_urls` entry must have a valid `url` field
      
  • references
    • analyze-target.md 21.2 KB
      ---
      nextStepFile: 'scope-definition.md'
      versionResolutionFile: 'references/version-resolution.md'
      extractPublicApiProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-extract-public-api.py'
        - '{project-root}/src/shared/scripts/skf-extract-public-api.py'
      detectWorkspacesProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-detect-workspaces.py'
        - '{project-root}/src/shared/scripts/skf-detect-workspaces.py'
      detectLanguageProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-detect-language.py'
        - '{project-root}/src/shared/scripts/skf-detect-language.py'
      emitBriefEnvelopeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
        - '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 2: Analyze Target
      
      ## Rules
      
      - Do not make scoping decisions or recommendations
      - Do not hallucinate or guess about repository contents
      
      ## Sequence
      
      ### 1. Resolve Target Location
      
      **For GitHub URLs:**
      
      **Resolve the analysis ref first.** `{analysis_ref}` is the git ref every GitHub-API fetch in this step (tree, manifests, contents) reads from — resolve it before fetching anything so the analyzed structure matches the version being skilled:
      - If neither `target_ref` nor `target_version` was set in step 01: `{analysis_ref}` = `HEAD` (default branch). This is the common case — skip straight to the probes below with no extra call.
      - If `target_ref` is set (an explicit ref the user stated verbatim, highest priority): use it directly as `{analysis_ref}` — no tag lookup.
      - If `target_version` is set: resolve it to a tag via `gh api repos/{owner}/{repo}/git/refs/tags` (paginate if the repo has many tags), matching in priority order — exact `{target_version}`, then `v{target_version}`. This mirrors the clone-path tag matching in `skf-create-skill/references/source-resolution-protocols.md` (see that file for the full monorepo-tag priority if the simple forms miss). On a single match, set `{analysis_ref}` to that tag. On **multiple matches**, present them and ask which to use (headless: take the exact match, else the `v`-prefixed one). On **zero matches**, warn `"No git tag matches version {target_version}; analyzing the default branch (HEAD) instead — structure and exports may not match the pinned version."`, set `{analysis_ref}` = `HEAD`, and record the fallback in the §5 analysis summary.
      
      - Issue both probes in **one message with two parallel Bash calls** — they are independent:
        - `gh api repos/{owner}/{repo}` (verify repo exists)
        - `gh api repos/{owner}/{repo}/git/trees/{analysis_ref}?recursive=1` (fetch file tree at the resolved ref)
      - 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.
      
      **Truncation detection:** After receiving the tree response, check the `truncated` field in the JSON output. If `truncated: true`:
      - Display: "Note: GitHub API returned a truncated tree response ({count} items). Full analysis may require a local clone."
      - Record in analysis summary: "Tree listing is partial — some files may not appear in the analysis."
      - For very large repos (>1000 files in tree response): offer a recovery path instead of just warning. Interactive — present:
        ```
        Tree is truncated. How would you like to proceed?
          [L] Clone locally and re-analyze (slower but complete)
          [P] Proceed with the partial tree (faster, may miss exports under deeper paths)
        ```
        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.
      
      **On API failure (non-200 from `gh api`):**
      
      Distinguish the failure class before reporting. In headless mode, every branch below emits the error envelope per **step 5 §4b** with its stated `halt_reason` before the HALT (pass the resolved `{skill_name}`, or the `"unknown"` placeholder documented in §4b if it is not yet set):
      - Auto-run `gh auth status` and capture its output. If it reports an unauthenticated state or expired token: emit the error envelope per **step 5 §4b** with `halt_reason: "gh-auth-failed"`, then 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."
      - If `gh auth status` reports authenticated but the call still failed (404/403): emit the error envelope per **step 5 §4b** with `halt_reason: "target-inaccessible"`, then 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."
      - If `gh auth status` itself fails to run (binary missing): emit the error envelope per **step 5 §4b** with `halt_reason: "gh-auth-failed"`, then 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."
      
      **For local paths:**
      - Verify the directory exists
      - List the directory tree
      - If the path does not exist: HALT (exit code 3, `halt_reason: "target-inaccessible"`) — "**Error:** Directory not found at {path}. Verify the path is correct." In headless mode, emit the error envelope per **step 5 §4b** with `halt_reason: "target-inaccessible"` before the HALT (pass the resolved `{skill_name}`, or the `"unknown"` placeholder documented in §4b if it is not yet set), matching the GitHub-target failure branches above so a missing local path surfaces the same `SKF_BRIEF_RESULT_JSON` failure class.
      
      Display: "**Resolving target...**"
      
      ### 1b. Detect Monorepo / Workspace Layout
      
      **Resolve `{detectWorkspacesHelper}`** from `{detectWorkspacesProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Delegate workspace detection to `{detectWorkspacesHelper}` 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:
      
      ```bash
      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>"}}' | \
        uv run {detectWorkspacesHelper}
      ```
      
      - **`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/{analysis_ref}?recursive=1` response; for local: the equivalent listing).
      - **`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}?ref={analysis_ref}` 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.
      
      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.
      
      **If `is_monorepo: false`** — skip this section silently and continue to §2.
      
      **If `is_monorepo: true`** — present the discovered workspaces and prompt:
      
      ```
      This looks like a monorepo ({manifest_kind}) with these workspaces:
        1. {workspaces[0].name} ({workspaces[0].path})
        2. {workspaces[1].name} ({workspaces[1].path})
        ...
      Which one should the skill cover? Pick a number, or type 'all' to scope at the repo root.
      ```
      
      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.
      
      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"`.
      
      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.
      
      **`cross-ecosystem workspace ignored` warning:** when a root workspace manifest from a different language ecosystem co-exists with the surfaced one (e.g. a root `Cargo.toml [workspace]` alongside a pnpm workspace), the script surfaces only the higher-priority kind and emits this warning naming the ignored kind and its member count. The ignored ecosystem's workspaces are **not** in `workspaces[]`, so the numbered menu above will not list them. When this warning is present, tell the operator both ecosystems exist and ask which the skill should cover; if they pick the ignored ecosystem, scope §2-§4b at its root (or the relevant member) rather than the surfaced workspace, and carry the ignored kind into §3 (see the `workspace_signal` note there).
      
      ### 2. Read Repository Structure
      
      List the top-level directory structure:
      
      "**Repository Structure:**
      ```
      {repo-name}/
      ├── {top-level files}
      ├── {top-level directories}/
      │   └── ...
      └── ...
      ```
      **Total:** {file count} files, {directory count} directories"
      
      ### 3. Detect Primary Language
      
      **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Delegate the rule walk to `{detectLanguageHelper}` instead of evaluating manifest presence and extension frequency in prose:
      
      ```bash
      echo '{"tree": [<flat list of repo-relative file paths from §1>], "workspace_signal": "<§1b manifest_kind, or omit when null>"}' | uv run {detectLanguageHelper}
      ```
      
      Pass the §1b `manifest_kind` as `workspace_signal` (omit the key when it is `null` / not a monorepo). This gives the workspace root precedence: for a `cargo-workspace` or `python-multi-package` root, the script returns the root language (rust/python) instead of being misled into `typescript` by a nested `package.json` + `tsconfig.json` in a non-workspace subdirectory (e.g. a `docs/` or `website/` site). JS-family workspace kinds (`npm-workspaces`/`pnpm-workspaces`/`lerna`) carry no override — their root `package.json` resolves js/ts normally.
      
      **When §1b surfaced a `cross-ecosystem workspace ignored` warning and the operator chose the ignored ecosystem:** pass that ignored kind as `workspace_signal` (not the surfaced kind), so a co-located `cargo-workspace`/`python-multi-package` root resolves to rust/python instead of being pinned to the surfaced ecosystem's language by the workspace that won detection priority.
      
      The script returns `{language, confidence, detection_source, fallback_to_extension_frequency}` after walking the documented rule table (the `workspace_signal` precedence above first, then manifest presence — 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:
      
      "**Detected language:** {language}
      **Confidence:** {confidence}
      **Detection source:** {detection_source}"
      
      **Headless language override.** If `language_hint` was supplied as a headless argument, use it as the confirmed `{language}` (overriding the detected value) and carry it forward to §4 and step 03. The detector still runs so the "Detected language" line reflects what the source signals, but the explicit hint wins and the step 03 §4 low-confidence override does not fire. When `language_hint` is absent, carry the detected `{language}` forward.
      
      If `confidence` is `low` (or `unknown` is returned for `language`) and no `language_hint` was supplied: flag for user override in step 03 §4.
      
      ### 4. List Top-Level Modules and Exports
      
      **Resolve `{extractPublicApiHelper}`** from `{extractPublicApiProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Identify the public API surface. **Delegate the parsing to `{extractPublicApiHelper}` 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.
      
      **Script-supported languages** (use the script): `js`, `ts`, `javascript`, `typescript`, `python`, `rust`, `go`, `java`, `kotlin`.
      
      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).
      
      #### 4.1 Procedure — script-supported languages
      
      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}?ref={analysis_ref}` 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. Carrying `{analysis_ref}` through here is what keeps the analyzed exports/version aligned with the pinned tag rather than HEAD. For local sources read directly (also parallelisable, but local reads are fast enough that serial Read tool calls are acceptable).
      
         | Language | Manifest | Entry points (mode=quick) |
         |----------|----------|--------------------------|
         | 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 |
         | python | `pyproject.toml` (or `setup.py` / `setup.cfg` if no `pyproject.toml`) | top-level `__init__.py` of the package, plus `_version.py` if present |
         | rust | `Cargo.toml` (`[package]` — workspace root if `version = { workspace = true }`) | `src/lib.rs` |
         | go | `go.mod` | top-level `*.go` exporting the package surface |
         | java | `pom.xml` | (manifest alone is sufficient for the modules listing) |
         | kotlin | `build.gradle` / `build.gradle.kts` | (manifest alone) |
      
      2. Build a JSON payload matching the script contract:
      
         ```json
         {
           "language": "<one of the supported values>",
           "manifest": {"path": "<relative path>", "content": "<file contents>"},
           "entries":  [{"path": "<relative path>", "content": "<file contents>"}, ...],
           "mode":     "quick"
         }
         ```
      
      3. Invoke the script and parse its JSON stdout:
      
         ```bash
         echo '<payload-json>' | uv run {extractPublicApiHelper} --mode quick
         ```
      
         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.
      
      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.
      
      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).
      
      #### 4.2 Procedure — fallback (not script-supported)
      
      Languages outside the script coverage (Ruby / C# / Swift / etc.) take this path. The §4.1 fall-through on script error also lands here.
      
      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.
      
      #### 4.3 Output format (both paths)
      
      "**Top-Level Modules/Directories:**
      {numbered list of modules with brief description of each}
      
      **Detected Exports/Entry Points:**
      {numbered list of public-facing items found — from script output when available, ad-hoc inspection otherwise}"
      
      #### 4.4 Semantic Signals (Forge+/Deep with ccc only)
      
      **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."
      
      If `tools.ccc` is true in forge-tier.yaml, supplement the module listing with a semantic discovery pass:
      
      **CCC Semantic Discovery:**
      - **Claude Code:** Use `/ccc search "{repo_name} public API exports modules"` from `{source_path}` — the query is variadic, so a trailing path is swallowed into the search string rather than selecting a project
      - **Cursor:** Use `ccc` MCP server `search` tool with query `"{repo_name} public API exports modules"` and path `{source_path}`
      - **CLI fallback:** `cd {source_path} && ccc search --limit 10 "{repo_name} public API exports modules"` — `ccc search` reads the index in the current working directory and has no project-selector flag (`--path` is a file-path glob filter *within* the index)
      
      See `knowledge/tool-resolution.md` for full bridge-to-tool mapping.
      
      If results are returned, display:
      
      "**Semantic Signals (ccc):**
      {numbered list of file:snippet pairs from CCC results — top 5 most relevant}"
      
      This supplements — never replaces — the explicit module list above. CCC may surface non-obvious entry points (dynamically constructed exports, re-export chains) that static directory analysis misses.
      
      If CCC is unavailable or returns no results: skip this subsection silently.
      
      ### 4b. Detect Source Version
      
      **When the language was script-supported (§4 took the script path):** the `version` field returned by `{extractPublicApiHelper}` 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.
      
      **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).
      
      Surface the result regardless of which path produced it:
      
      **If `target_version` was provided in step 01:**
      - Display: "**Target version:** {target_version} (user-specified)"
      
      Display: "**Detected version:** {version or 'Not detected — will default to 1.0.0'}"
      
      {If target_version was provided AND auto-detected version differs:}
      "**Note:** Detected version ({detected_version}) differs from your target version ({target_version}). Using target version (per `references/version-resolution.md` precedence rules)."
      
      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.
      
      ### 5. Report Analysis Summary
      
      Present the complete analysis:
      
      "**Analysis Complete**
      
      ---
      
      **Target:** {repo URL or path}
      **Language:** {detected language} ({confidence})
      **Structure:** {file count} files across {directory count} directories
      
      **Key Modules ({count}):**
      {bulleted list of modules}
      
      **Public Exports/Entry Points ({count}):**
      {bulleted list of exports}
      
      **Notable Files:**
      - README: {found/not found}
      - Tests: {found/not found — location}
      - Docs: {found/not found — location}
      - Config: {list of config files found}
      - Version: {detected version or "Not detected — defaulting to 1.0.0"}
      {If the target was a GitHub URL:}
      - Analysis ref: {analysis_ref} {append " (resolved from target_version {target_version})" when a tag was matched, or " (no tag matched {target_version} — analyzed default branch)" on the zero-match fallback}
      
      Store `{analysis_ref}` in workflow context — step 03 (`scope-definition.md`) reuses it for any further `contents/` fetches so scope analysis reads the same ref as this step.
      
      ---
      
      {If language confidence is low:}
      **Note:** Language detection confidence is low. You'll be able to override this in the next step.
      
      Moving to scope definition where you'll choose what to include and exclude."
      
      ### 6. Auto-Proceed to Scope Definition
      
      Display: "**Proceeding to scope definition...**
      
      Review the analysis above. If anything looks wrong, let me know now — otherwise I'll proceed to scope definition."
      
      Pause briefly for user input. If the user provides corrections or asks questions, address them and re-present any updated analysis findings. Then proceed.
      
      #### Menu Handling Logic:
      
      - After analysis report is presented to user and any corrections addressed, load, read entire file, then execute {nextStepFile}
      
      #### Execution rules:
      
      - This is a soft auto-proceed step — present the pause prompt, wait briefly for user input
      - If user provides corrections: address them, then proceed
      - If no user input after a brief pause: proceed directly to step 03
      
      
    • confirm-brief.md 7.5 KB
      ---
      nextStepFile: 'write-brief.md'
      reviseStepFile: 'scope-definition.md'
      advancedElicitationSkill: '/bmad-advanced-elicitation'
      partyModeSkill: '/bmad-party-mode'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 4: Confirm Brief
      
      ## Rules
      
      - Do not proceed without explicit user approval (P2 confirmation gate)
      
      ## Sequence
      
      ### 1. Assemble Complete Brief
      
      Use the values already accepted in steps 01-03 directly — do not re-load `{briefSchemaPath}` here. The 18 fields below are all in conversation; the schema is only consulted in §4 if an inline adjustment needs a specific field's validation rule cited.
      
      **Ratify run (`ratify_mode: true`):** steps 2-3 were skipped (interactive `[R]` at gather-intent §3.1a, or the headless §8 GATE `from_brief` route), so there is no fresh steps 01-03 output to compile. Use the brief context variables **hydrated from the parsed brief** at step 1 in place of that output — the hydrated variable names match the field references below one-for-one. `detected_version` is absent on this path; rely on the hydrated `version` (step 5 pins it via `version_resolved`).
      
      Compile all gathered data from steps 01-03 into the complete brief:
      
      - **name:** {skill name from step 01}
      - **version:** {auto-detected from source, or "1.0.0" if not found — see schema for detection rules}
      - **target_version:** {target_version from step 01, if set}
      - **source_repo:** {target repo from step 01}
      - **language:** {detected/confirmed language from steps 02-03}
      - **description:** {derived from user intent in step 01}
      - **forge_tier:** {tier from step 01}
      - **created:** {current date}
      - **created_by:** {user_name from config}
      - **scope.type:** {scope type from step 03}
      - **scope.include:** {include patterns from step 03}
      - **scope.exclude:** {exclude patterns from step 03}
      - **scope.notes:** {any scope notes from step 03}
      - **scope.tier_a_include:** {tier-A authoring-surface patterns from step 03 §3c — omit if unset}
      - **scope.rationale:** {recommended -> chosen, reason — from step 03}
      - **source_type:** {source or docs-only, from step 01}
      - **doc_urls:** {collected documentation URLs with labels, from steps 01/03 — include if source_type is "docs-only" or supplemental URLs were collected}
      - **scripts_intent:** {detect/none/description from step 03, or "detect" if not explicitly set}
      - **assets_intent:** {detect/none/description from step 03, or "detect" if not explicitly set}
      - **source_authority:** {official/community/internal from step 01 — default "community"}
      
      ### 2. Present Brief for Review
      
      Using the format below:
      
      "**Review the complete skill brief before I write it.**
      
      ---
      
      ```
      Skill Brief: {name}
      ====================
      
      Target:      {source_repo}
      Language:    {language}
      Forge Tier:  {forge_tier} — {tier_gloss}
      Description: {description}
      
      Scope: {scope.type}
        Include: {scope.include patterns, one per line}
        Exclude: {scope.exclude patterns, one per line}
        Notes:   {scope.notes}
        Tier-A:  {scope.tier_a_include patterns, one per line — omit this line entirely if scope.tier_a_include unset}
        Rationale: {chosen} chosen over {recommended} — {reason}
                   {omit this line entirely if scope.rationale absent}
      
      {If source_type is "docs-only":}
      Source Type: docs-only
      Doc URLs:
        {doc_urls, one per line with labels}
      
      {If source_type is "source" AND supplemental doc_urls collected:}
      Supplemental Docs:
        {doc_urls, one per line with labels}
      
      Scripts:    {scripts_intent} — {scripts_gloss}
      Assets:     {assets_intent} — {assets_gloss}
      
      Source Authority: {source_authority}
      
      {If target_version is set:}
      Target Version: {target_version} (user-specified)
      Detected Version: {detected_version or "N/A"}
      {Else:}
      Version:    {version}
      
      Created:    {created}
      Created by: {created_by}
      ```
      
      ---
      
      Glosses (substitute the matching one-liner for `{tier_gloss}`, `{scripts_gloss}`, `{assets_gloss}` above so the user can decode each value at a glance):
      
      - **Forge tier glosses** —
        - `Quick`: text-only extraction; AST and semantic discovery off
        - `Forge`: AST-grep on; semantic and re-ranking off
        - `Forge+`: AST-grep + ccc semantic discovery; re-ranking off
        - `Deep`: full pipeline — AST + ccc + qmd portfolio search + LLM re-ranking
      
      - **`scripts_intent` / `assets_intent` glosses** —
        - `detect`: SKF will scan source for the standard `scripts/`/`bin/`/`tools/`/`cli/` (or `assets/`/`templates/`/`schemas/`/`configs/`) directories during create-skill and decide automatically
        - `none`: no script/asset packaging — create-skill will skip the detection pass
        - free-text (anything else): a description of what to package; create-skill treats it as the user's spec
      
      (For `docs-only` and `public-api` scope types the scripts/assets prompt is skipped in step 3 §5b — the values default to `detect` but the create-skill detection pass also no-ops for these scope types, so the gloss just clarifies that the recorded value will not actually fire any scan.)"
      
      ### 3. Highlight Items Needing Attention
      
      Flag any fields that may need review:
      
      {If language was overridden or low confidence:}
      "**Note:** Language was {auto-detected / manually overridden}."
      
      "**Description:** synthesized and confirmed in step 1 §7b. This is the text agents read when deciding whether to route to your skill — refine here if you want to tighten it now that the full brief is visible."
      
      {If forge tier was defaulted:}
      "**Note:** Forge tier defaulted to Quick (no forge-tier.yaml found)."
      
      {If any scope patterns seem broad or narrow:}
      "**Note:** {specific observation about scope breadth}."
      
      {If target_version is set AND detected_version exists AND they differ:}
      "**Note:** Target version ({target_version}) differs from detected source version ({detected_version}). The target version will be used for compilation."
      
      "**This is your last chance to make changes before writing the file.**
      
      You can:
      - Adjust any field by telling me what to change
      - Revise scope boundaries by selecting [R]
      - Proceed to write by selecting [C]"
      
      ### 4. Handle Inline Adjustments
      
      If the user requests changes to specific fields (name, description, version, etc.):
      - If the adjustment requires explaining a field's validation rule or allowed values, load `{briefSchemaPath}` now (otherwise skip the read — the common path does not need it)
      - Make the adjustment
      - Re-present the updated brief
      - Return to the menu
      
      ### 5. Present MENU OPTIONS
      
      Display: **Select an Option:** [R] Revise Scope [A] Advanced Elicitation [P] Party Mode [C] Approve and Write [X] Cancel and exit
      
      #### Menu Handling Logic:
      
      - IF R: Load, read entire file, then execute {reviseStepFile} to re-enter scope definition
      - IF A: Invoke {advancedElicitationSkill}, and when finished redisplay the menu
      - IF P: Invoke {partyModeSkill}, and when finished redisplay the menu
      - IF C: Load, read entire file, then execute {nextStepFile}
      - IF X: Treat as user-cancellation. Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Cancellation here is non-destructive — step 5 has not run, no skill-brief.yaml file exists yet. `[X]` is interactive-only; the headless GATE never reaches this branch.
      - IF Any other comments or queries: help user respond, apply any field adjustments, re-present brief if changed, then [Redisplay Menu Options](#5-present-menu-options)
      
      #### Execution rules:
      
      - **GATE [default: C]** — If `{headless_mode}`: auto-proceed with [C] Confirm, log: "headless: auto-confirm brief"
      - After other menu items execution, return to this menu
      - User can chat, request field changes, or ask questions — always respond and then redisplay menu
      
      
    • draft-checkpoint.md 3.7 KB
      # Draft Checkpoint Lifecycle
      
      The `.brief-draft.json` file at `{forge_data_folder}/{skill-name}/.brief-draft.json` is a step 1 in-flight-state checkpoint. It exists only while the workflow has progressed past §7 but not yet completed step 5 — once the final brief writes successfully, step 5 §4 removes it.
      
      **Headless mode skips this entire lifecycle** — the run completes in a single invocation, so no resume is meaningful and no checkpoint is written.
      
      The two halves of the lifecycle (resume after the target is confirmed in §3, write on §7 confirmation) form a pair. This file documents both so a single load covers them.
      
      ## Half 1 — Resume Check (loaded from §3 after the target is confirmed)
      
      Keyed on the confirmed **target**, not the derived skill name, so the offer can fire right after §3 — before the returning user re-answers version (§3b), intent (§4), or scope (§5), which is exactly the state a draft restores. The caller (gather-intent §3) has already globbed `{forge_data_folder}/*/.brief-draft.json`, kept only drafts whose `target_repo` equals the confirmed target (or whose `doc_urls` contain it, for docs-only) with no `skill-brief.yaml` beside them, and selected the most-recently-modified survivor. Its directory basename is the candidate skill `name`. Present the resume prompt for that draft.
      
      When a live draft is found, present:
      
      ```
      **An in-progress draft for `{name}` was found** (last updated: {mtime}).
        [Y] Resume from the saved draft (jump to §8 with prior answers restored)
        [N] Start fresh (ignore this draft and keep gathering)
      ```
      
      ### `[Y]` — Resume
      
      Restore the candidate `name` (the matched draft's directory basename), then 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 §3b, §4, §5, §6, §7, and §7b** — so the version, intent, scope, and description the draft already holds are never re-gathered.
      
      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. §6 is skipped too, so the restored `name` is used as-is — it already cleared the collision and portfolio-similarity checks in the session that wrote the draft.
      
      The user can still revise any field at step 4 §3 if a refinement is needed after the full brief is visible.
      
      ### `[N]` — Start fresh
      
      Leave the draft in place and continue forward to §3b — the normal gather flow (§3b version, §4 intent, §5 scope, §6 name) resumes, and the §6 collision / portfolio-similarity checks run in their usual place. Do not delete the draft here: the skill name has not been chosen yet, so there is nothing to key a deletion on. If the user lands on the same name, step 5's atomic write overwrites the stale draft; otherwise it stays a harmless orphan that the resume check offers again on a future run targeting the same repo.
      
      ## Half 2 — Checkpoint Write (loaded from §7 after summary confirmation)
      
      After the user confirms the §7 summary, persist the captured state atomically. Write a single JSON object with all of:
      
      - `target_repo`, `source_type`, `source_authority`
      - `target_version` (if set)
      - `doc_urls` (if collected)
      - `intent`, `scope_hint`
      - `description` (the §7b accepted text)
      - `forge_tier`, `tier_source` (for diagnostics)
      
      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`.
      
      The file is removed by step 5 §4 after the final brief writes successfully.
      
    • gather-intent.md 35.8 KB
      ---
      nextStepFile: 'analyze-target.md'
      ratifyTargetFile: 'confirm-brief.md'
      forgeTierFile: '{sidecar_path}/forge-tier.yaml'
      headlessArgsFile: 'references/headless-args.md'
      headlessSourceAuthorityDetectionFile: 'references/headless-source-authority-detection.md'
      portfolioSimilarityCheckFile: 'references/portfolio-similarity-check.md'
      draftCheckpointFile: 'references/draft-checkpoint.md'
      validateBriefInputsProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-inputs.py'
        - '{project-root}/src/shared/scripts/skf-validate-brief-inputs.py'
      validateBriefSchemaProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
        - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
      emitBriefEnvelopeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
        - '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 1: Gather Intent
      
      ## Rules
      
      - Focus only on gathering intent — do not analyze the repo yet (Step 02)
      - Do not examine source code or list exports in this step
      - Open-ended discovery facilitation — collect target repo, user intent, scope hints, skill name
      - All user-facing output in `{communication_language}`
      
      ## Sequence
      
      ### 1. Discover Forge Tier
      
      **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 5's atomic write — by then the user has invested 5–15 minutes. Run a single-byte write-and-remove probe:
      
      ```bash
      mkdir -p "{forge_data_folder}" && \
        printf 'probe' > "{forge_data_folder}/.skf-write-probe" && \
        rm "{forge_data_folder}/.skf-write-probe"
      ```
      
      `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 5 §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.
      
      Attempt to load `{forgeTierFile}`:
      
      **If found:**
      - Read the tier level (quick, forge, forge+, or deep)
      - Note available tools for scoping guidance later
      
      **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.
      
      **If found but the YAML cannot be parsed (corrupted or truncated):**
      - 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)."
      - Continue with `tier = "Quick"` and `tools = {}` — do not HALT. Record `tier_source: "fallback-corrupted-config"` for later diagnostics.
      
      **If not found:**
      - "**Cannot proceed.** forge-tier.yaml not found at `{forgeTierFile}`. Run the **setup** workflow first to configure your forge tier (Quick/Forge/Forge+/Deep)."
      - In headless mode, emit the error envelope per **step 5 §4b** with `halt_reason: "forge-tier-missing"` (`skill_name` is not yet resolved here — use the `"unknown"` placeholder convention documented in §4b).
      - HALT (exit code 3, `halt_reason: "forge-tier-missing"`) — do not proceed.
      
      ### 1b. Auto Mode Check
      
      **Check for `[auto]` flag:** If `[auto]` was passed as a bracket modifier in the pipeline context (e.g., `BS[auto]`), set `{auto_mode}` = true.
      
      **IF `{auto_mode}` is true:**
      
      1. **Load upstream brief path:** Read `brief_path` from the pipeline data context (passed by the forger from AN's `SKF_ANALYZE_RESULT_JSON` `brief_paths[]`). If `brief_path` is not available, HARD HALT with exit code 2 (`input-missing`): "**Auto mode requires `brief_path` in pipeline context — AN must run before BS[auto].**"
      2. **Load source repo:** Read `source_repo` from the pipeline data context (the target repo URL or path, forwarded by the forger). If not available, attempt to extract it from the upstream brief at `brief_path`.
      3. "**Auto mode activated — bypassing interactive brief workflow.**"
      4. **Route to auto-brief:** Load, read fully, then execute `references/step-auto-brief.md`, and hand off there — do not fall through to §2 or any subsequent section of this file.
      
      **IF `{auto_mode}` is NOT true:**
      Continue to §2 as normal — the entire interactive flow below is unchanged.
      
      ### 2. Welcome and Explain
      
      "**Welcome to Brief Skill — the skill scoping workflow.**
      
      **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.
      
      I'll help you define exactly what to skill and produce a `skill-brief.yaml` that drives the create-skill compilation workflow.
      
      {If tier override was applied:}
      **Your forge tier:** {override tier} (overridden from {original tier}) — {tier_gloss}
      {Else:}
      **Your forge tier:** {detected tier} — {tier_gloss}
      
      (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.)
      
      Let's get started."
      
      ### 3. Gather Target Repository
      
      This section has four sub-flows. Execute exactly one branch — 3.1a *or* 3.2 *or* 3.3 — based on the user's response in 3.1, then end with the shared confirmation (3.1a is terminal for §3 and jumps directly to confirm-brief.md). Do not mix branches.
      
      #### 3.1 Collect target
      
      **Open-floor opening.** Lead with an open invitation so an expert can state everything in one breath rather than being walked through seven discrete prompts — costs almost nothing token-wise and sharply improves the conversational feel of this, the most question-heavy mode. A first-timer who pastes only a bare URL still gets the full guided sequence below, unchanged.
      
      "**What repository or documentation do you want to create a skill for?**
      
      Tell me everything you have — the repo or docs, what you want to skill and why, any scope or version thoughts. Or just paste a URL and we'll go from there.
      
      Provide one of:
      - A **GitHub URL** (e.g., `https://github.com/org/repo`)
      - A **local path** (e.g., `/path/to/project`)
      - **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)
      - A **path to an existing `skill-brief.yaml`** (file path or a directory containing one) — use this to ratify a brief produced by another workflow (e.g. `skf-analyze-source`) without re-deriving fields
      
      Or type `cancel` / `exit` / `[X]` to leave without writing anything.
      
      **Target:**"
      
      Wait for user response. **Parse the response for any of the fields the later sections collect** — `target_version` (§3b), intent (§4), scope hints (§5), source authority (§3.3), a proposed name (§6) — and pre-fill every field the user covered, holding them in workflow context. Sections §3b/§4/§5/§6/§7b then **acknowledge a pre-filled field instead of re-asking** ("I noted you're targeting v4.0.0"), and prompt only for the gaps. An expert who stated it all collapses to the §3.1 target branch plus the §7b description confirmation; a bare URL falls through to the full sequence. Then branch on the response for the target itself:
      
      - Empty input, `cancel`, `exit`, `[X]`, `q`, or `:q` → Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Cancellation here is non-destructive — no files have been written yet by step 1. Headless mode never reaches this branch (the GATE in §8 short-circuits the interactive sub-flows).
      - Path that resolves to an existing `skill-brief.yaml` (file path ending in `skill-brief.yaml` that exists, OR a directory containing a `skill-brief.yaml`) → §3.1a
      - Documentation URLs only (no source location) → §3.2
      - GitHub URL or local filesystem path → §3.3
      - Any other free-form question (e.g. "what is this?", "show me an example", "how does SKF work?") → answer briefly, re-display the prompt
      
      #### 3.1a Branch — Ratify existing brief
      
      This branch handles the AN→BS handoff: another workflow (typically `skf-analyze-source`) has already produced a `skill-brief.yaml`, and the user wants to review and confirm it without re-running gather-intent / analyze-target / scope-definition. **This §3.1a path is reached only interactively** — it is entered by typing a brief path at the §3.1 prompt, which headless mode never does. The headless equivalent is the §8 GATE `from_brief` route: it consumes a `from_brief` argument, runs the same schema validation, sets the same `ratify_mode`, hydrates from the same parsed payload, and jumps to step 4 exactly as `[R]` does below — see §8. Keep the two paths' hydration in sync.
      
      Resolve the path:
      
      - If the user's input ends in `skill-brief.yaml` and points at an existing file → that is the brief path.
      - Otherwise (input was a directory) → the brief path is `<input>/skill-brief.yaml`.
      
      **Validate the brief against the schema** before presenting it. Resolve `{validateBriefSchemaHelper}` from `{validateBriefSchemaProbeOrder}` (first existing path wins; HALT if no candidate exists), then:
      
      ```bash
      uv run {validateBriefSchemaHelper} <resolved-brief-path>
      ```
      
      The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}`. Apply the result:
      
      - **`valid: false`** — surface the `errors[]` messages and the `halt_reason` to the user, then re-display the §3.1 prompt for a corrected path (or another target altogether). Do not HALT — the user may simply have pointed at the wrong file. Example: `"**Brief at `{path}` is invalid:** {first error message}. Pick a different brief, or supply a repo / docs URL instead."`
      - **`valid: true`** — proceed with the parsed `brief` payload.
      
      Surface any non-empty `warnings[]` as a single grouped line (`"**Brief validation warnings:** {joined warnings}"`), then present the ratify menu:
      
      ```
      **Existing brief detected at `{path}`.**
      
      - **Name:** {brief.name}
      - **Target:** {brief.source_repo}
      - **Description:** "{brief.description}"
      - **Created:** {brief.created} by {brief.created_by}
      - **Scope:** {brief.scope.type}
      
      Pick one:
        [R] Ratify — review in step 4 and write (overwriting this file once approved)
        [F] Start fresh — discard this brief and re-prompt for a target
        [X] Cancel and exit
      ```
      
      Wait for user response. Branch:
      
      - **[R] Ratify** — Confirm overwrite up front: store `ratify_mode: true` and `ratify_source_path: <resolved-brief-path>` in workflow context. Hydrate the brief context variables from the parsed `brief` payload so step 4 has the same field set it normally derives from steps 1-3:
        - `name` ← `brief.name`; `version` ← `brief.version`; `target_version` ← `brief.target_version`
        - `target_ref` ← `brief.target_ref`; `source_ref` ← `brief.source_ref` (optional git refs; preserve when present)
        - `source_repo` ← `brief.source_repo`; `source_type` ← `brief.source_type`; `source_authority` ← `brief.source_authority`; `doc_urls` ← `brief.doc_urls`
        - `language` ← `brief.language`; `description` ← `brief.description`; `forge_tier` ← `brief.forge_tier`
        - `created` ← `brief.created`; `created_by` ← `brief.created_by`
        - `scope.type` / `scope.include` / `scope.exclude` / `scope.tier_a_include` / `scope.notes` / `scope.rationale` / `scope.amendments` ← `brief.scope.*` (preserve `tier_a_include` and the `amendments` log verbatim — do not re-derive or drop them)
        - `scripts_intent` ← `brief.scripts_intent`; `assets_intent` ← `brief.assets_intent`
      
        Then load, read entirely, and execute `{ratifyTargetFile}` — bypassing §3.1b/§3.2/§3.3, §3b, §4, §5, §6, §7, §7b, and §8 entirely. Skip step 2 (analyze-target) and step 3 (scope-definition) — both would re-derive fields already on disk. The forward chain resumes at step 4 (confirm-brief) where the user gets the standard review pass and can still adjust fields inline via §4.
      
      - **[F] Start fresh** — discard the loaded brief and re-display §3.1 above (the user is now at the same point as if they had typed nothing).
      - **[X] Cancel** — Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Non-destructive.
      - **Any other input** — treat as a fresh §3.1 response and re-evaluate the routing branches above (a typed GitHub URL after seeing the menu means "I changed my mind, brief this repo instead").
      
      #### 3.2 Branch — Documentation URLs (docs-only)
      
      - Set `source_type: "docs-only"` in the brief data
      - Collect one or more doc URLs with optional labels
      - 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:
        - On 2xx/3xx: silently accept.
        - 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.
      - Set `source_authority: "community"` (forced for docs-only — T3 external documentation; the §3.3 source-authority prompt is skipped)
      - Note: `source_repo` becomes optional (can be set to the main doc site URL for reference)
      
      Skip §3.3 and continue at "Confirm the target" below.
      
      #### 3.3 Branch — Source (GitHub URL or local path)
      
      - Set `source_type: "source"` (default)
      - **Pre-validate the target before continuing.** Issue these probes in a single message with parallel Bash calls:
        - **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.
        - **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 2 §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.
        - **GitHub URL, in parallel:** `gh auth status` — if it reports unauthenticated or the binary is missing, warn `"GitHub CLI not authenticated; step 2 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 2 §1's failure-class triage.)
        - **Local path:** verify the directory exists (`test -d {path}`). If not, warn `"Local path {path} does not exist."` and re-prompt.
      - Optionally ask: "Are there any documentation URLs you'd like to include for supplemental context? (These will be fetched as T3 external references.)"
      - If yes: collect doc URLs into `doc_urls`
      
      **Source authority (this branch only — docs-only forces `community` in §3.2):**
      
      **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.
      
      "**Are you the maintainer of this library, or creating a community skill?**"
      - If maintainer: set `source_authority: "official"`
      - If community user: set `source_authority: "community"` (default)
      - If internal/proprietary: set `source_authority: "internal"`
      
      Default to `"community"` if user does not specify or skips.
      
      ---
      
      Confirm the target.
      
      **Draft-resume check (interactive only).** Now that the target is confirmed — and *before* the version prompt (§3b), intent (§4), or scope (§5) spend the user's time — offer to resume an in-progress draft that already covers this exact target, so a returning user re-types nothing. Keying on the target (not the not-yet-derived skill name) is what lets the offer fire this early. When the flow is interactive:
      
      1. **Cheap pre-filter** — list any draft file that mentions the confirmed target at all:
      
         ```bash
         grep -lF "{target}" "{forge_data_folder}"/*/.brief-draft.json 2>/dev/null
         ```
      
         `{target}` is the repo URL, local path, or primary doc URL just entered. No output → no draft; skip straight to §3b.
      
      2. **Confirm each candidate on the target *field*, not free text.** Read the candidate draft's JSON and keep it only if its `target_repo` equals the confirmed target (source targets) or the target appears in its `doc_urls` (docs-only) — this rejects a draft that merely mentions the URL in its `intent` or `description`. Drop any candidate that has a finished `skill-brief.yaml` in the same directory (that brief is done; step 5's overwrite gate owns it).
      
      3. If one or more live drafts survive, load `{draftCheckpointFile}` and follow Half 1 (Resume Check) against the most-recently-modified survivor; its directory basename is the candidate skill name. On `[Y]` resume, Half 1 restores every gathered answer and jumps straight to §8 — **§3b, §4, §5, §6, §7, and §7b are all skipped**. Otherwise (headless, no surviving draft, or every match already has a finished brief beside it) skip the load and continue to §3b.
      
      ### 3b. Gather Target Version
      
      This step only collects `target_version` and validates its shape with the regex below — auto-detection runs in step 2 and precedence/invariant resolution lands in step 5's writer script. The canonical precedence rules live in `references/version-resolution.md`; load it from step 2 / step 5 only when the relevant section needs it.
      
      **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).
      
      "**Are you targeting a specific version of this library?**
      (Leave blank to auto-detect from source)"
      
      {If source_type is "docs-only":}
      "Since this is a docs-only skill with no source code, specifying the version is recommended — otherwise it defaults to 1.0.0."
      
      Wait for user response.
      
      **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 2 auto-detection.
      **If blank:** Proceed without `target_version` — version will be auto-detected in step 02.
      
      {If target_version was set AND doc_urls are being collected (either docs-only primary or supplemental):}
      
      "**You're targeting version {target_version}. Do these documentation URLs correspond to that version?** [Y/N]"
      
      - **If Y:** Proceed.
      - **If N:** "Provide the correct documentation URLs for version {target_version}." Re-collect doc_urls.
      
      ### 4. Gather User Intent
      
      **First-timer rail (interactive only).** Before the intent prompt, check whether `{forge_data_folder}/` contains any prior briefs:
      
      ```bash
      find "{forge_data_folder}" -maxdepth 2 -name "skill-brief.yaml" -print -quit
      ```
      
      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:
      
      "**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.)"
      
      On `[Y]`: load `{descriptionVoiceExamplesPath}` 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.
      
      "**What's your intent for this skill?**
      
      Help me understand:
      - **What** specifically do you want to skill from this repo?
      - **Why** — what's the use case? How will an AI agent use this skill?
      - **Any initial thoughts** on scope? (Full library? Specific modules? Public API only?)
      
      Take your time — the more context you share, the better the brief."
      
      Wait for user response. Ask follow-up questions if intent is unclear.
      
      **Capture, don't interrupt.** If the user volunteers an out-of-scope aside while answering — "the v3 API is totally different", "we're deprecating the auth module next quarter" — do not redirect the conversation to chase it. Silently note it as a candidate `scope.notes` line (carried forward into the brief's `scope.notes` at step 3) and continue the current prompt. These unprompted asides are often the most useful scoping signal; the cost of losing them when the conversation moves on is higher than the cost of one stored line.
      
      ### 5. Capture Scope Hints
      
      If the user mentioned scope preferences in their intent response, acknowledge them:
      
      "**I noted these scope hints from your response:**
      - {list any scope hints mentioned}
      
      We'll refine these after analyzing the repo structure in the next step."
      
      If no scope hints were mentioned, that's fine — skip this acknowledgment.
      
      ### 6. Derive Skill Name
      
      Based on the target repo and intent, propose a skill name:
      
      "**Suggested skill name:** `{derived-name}` (kebab-case)
      
      This will be used for the output directory and file naming. Want to use this name or suggest something different?"
      
      Wait for confirmation or alternative.
      
      **Collision check (interactive and headless):** before locking the name, check whether `{forge_data_folder}/{name}/skill-brief.yaml` already exists. If it does:
      
      - 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:
        1. `{name}-v{N}` where `N` is the smallest positive integer that doesn't collide (e.g. `{name}-v2`, `{name}-v3`) — always applies
        2. `{name}-{target_version}` if `target_version` is set and the suffix wouldn't collide (e.g. `marked-1.2.3`)
        3. `{name}-{source_authority}` if `source_authority` is not `community` (e.g. `marked-internal` for an internal fork)
      
        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:
      
        ```
        **Heads up — a brief for `{name}` already exists at `{path}`.**
      
        Suggested alternates (none collide):
          [1] {alternate-1}
          {if a second alternate was produced:} [2] {alternate-2}
          {if a third alternate was produced:} [3] {alternate-3}
      
        Pick a number to use that name, type a different name, or press Enter to keep `{name}` and let step 5 §2b handle the overwrite prompt.
        ```
      
        On a numbered choice, replace `{name}` with the chosen alternate. On Enter, fall through to step 5's overwrite gate. On any other input, treat as a new candidate name and re-run the collision check against it.
      
      - Headless: log `"warn: skill name '{name}' collides with existing brief at {path}"` and proceed; the existing-brief overwrite policy in step 5 §2b is the canonical gate (HALT with `overwrite-cancelled` unless `force` was supplied).
      
      **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.
      
      (The resume-a-draft offer for a returning user fires earlier, right after the target is confirmed in §3 — see the §3 "Draft-resume check" — so the intent, scope, and description a draft holds are spared *before* they get re-gathered here.)
      
      ### 7. Summarize Gathered Intent
      
      "**Here's what I've captured:**
      
      - **Target:** {repo URL or path}
      - **Intent:** {user's intent summary}
      - **Scope hints:** {any hints, or "None — we'll define scope after analysis"}
      - **Skill name:** {confirmed name}
      - **Source type:** {source or docs-only}
      - **Source authority:** {official/community/internal}
      {If target_version set:}
      - **Target version:** {target_version} (user-specified)
      {If doc_urls collected:}
      - **Doc URLs:** {count} supplemental documentation URLs
      - **Forge tier:** {tier}
      
      Ready to analyze the target repository?"
      
      **Draft checkpoint.** When the flow is interactive, load `{draftCheckpointFile}` (or reuse it if already loaded for the §3 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.
      
      ### 7b. Synthesize Skill Description
      
      The schema's `description` field is 1-3 sentences and surfaces in skill registries — it must exist by the time step 4 presents the brief. Synthesize it explicitly here, while the user's intent is fresh, instead of letting it fall out implicitly later.
      
      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.
      
      Load `{descriptionVoiceExamplesPath}` for the five voice examples (range of acceptable leads and structures) 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.
      
      **Whatever lead you choose, the description must contain a literal `Use when` clause somewhere** — validators test for that exact phrase, so alternatives like "Triggers on…" or "Reach for this when…" do not satisfy it on their own. The clause need not lead: a descriptive opener followed by `Use when …` is both good voice and validator-clean.
      
      Present:
      
      "**Proposed skill description:**
      
      > {synthesized description}
      
      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. A specific 'use when…' trigger helps agents match real user requests; generic descriptions blend in and get skipped. Edit, replace, or accept as-is."
      
      Wait for user confirmation or alternative.
      
      **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:
      
      "**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?"
      
      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.
      
      Store the accepted text as the brief's `description` field. The same field is re-presented in step 4 §3 for a final review pass — refinements there flow back to this value.
      
      **Headless:** if the `intent` argument was supplied, load `{descriptionVoiceExamplesPath}` 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:
      
      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 `{descriptionVoiceExamplesPath}` 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 2 §1; this lightweight `--jq .description` call only retrieves the one field.)
      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."`
      
      ### 8. Present MENU OPTIONS
      
      Display: "**Select:** [C] Continue to Target Analysis · [X] Cancel and exit"
      
      #### Menu Handling Logic:
      
      - IF C: Load, read entire file, then execute {nextStepFile}
      - 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 1.
      - IF Any other: Help user, then [Redisplay Menu Options](#8-present-menu-options)
      
      #### Execution rules:
      
      - **Resolve `{validateBriefInputsHelper}`** from `{validateBriefInputsProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      - **GATE [default: use args]** — If `{headless_mode}`, consume pre-supplied arguments and auto-proceed. The full argument set (required/optional, defaults, halt codes, enum values) is documented in `{headlessArgsFile}` — load it now if you need to look up a specific argument. Validation is delegated to `{validateBriefInputsHelper}`; the table is the canonical operator-facing documentation, the script enforces it.
      
        **Preset merge (before validation).** Skip this merge entirely when a `from_brief` argument is present — presets seed a *derived* brief and have no meaning on the ratify route below. Otherwise: 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).
      
        **Delegate validation to `{validateBriefInputsHelper}`** instead of reasoning through the table rules in prose:
      
        ```bash
        echo '<headless-args-as-json>' | uv run {validateBriefInputsHelper}
        ```
      
        The script returns a JSON envelope: `{valid, errors[], warnings[], normalized, halt_reason}`. Apply the result deterministically:
      
        - **`valid: false`** — emit the error envelope per **step 5 §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.
        - **`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.
      
        The script's `KNOWN_FIELDS` set must stay in sync with the table in `{headlessArgsFile}`.
      
        **Ratify route — `from_brief` present.** After a `valid: true` result, branch on `normalized.from_brief`. When it is set, this run ratifies a pre-authored brief instead of deriving one — the headless mirror of the interactive §3.1a `[R]` branch. Take this route *before* the source-authority detection and analyze-target routing below (both belong to the derive path and do not apply here):
      
        1. **Resolve the brief path.** If `normalized.from_brief` ends in `skill-brief.yaml`, that is the path; otherwise treat it as a directory and use `<from_brief>/skill-brief.yaml`.
        2. **Schema-validate.** Resolve `{validateBriefSchemaHelper}` from `{validateBriefSchemaProbeOrder}` (first existing path wins; HALT if no candidate exists), then run `uv run {validateBriefSchemaHelper} <resolved-brief-path>`. The script returns `{valid, errors[], warnings[], halt_reason, brief}`. Apply it — and note that, unlike the interactive §3.1a branch (which re-prompts because the operator might have a corrected path to offer), headless has no second chance, so an unusable brief is terminal:
           - **`valid: false`** with `halt_reason: "brief-missing"` (path absent / unreadable) — emit the error envelope per **step 5 §4b** with `halt_reason: "input-missing"`, surface `errors[]` to the operator log, HALT (exit 2).
           - **`valid: false`** with any other `halt_reason` (`brief-malformed` / `brief-invalid`) — emit the error envelope with `halt_reason: "input-invalid"`, surface `errors[]`, HALT (exit 2).
           - **`valid: true`** — surface any non-empty `warnings[]` to the operator log and proceed with the parsed `brief` payload.
        3. **Hydrate and route.** Store `ratify_mode: true` and `ratify_source_path: <resolved-brief-path>` in workflow context, then hydrate the brief context variables from the parsed `brief` payload exactly as the §3.1a `[R]` branch does (the identical field-mapping list: `name`/`version`/`target_version`, `target_ref`/`source_ref`, `source_repo`/`source_type`/`source_authority`/`doc_urls`, `language`/`description`/`forge_tier`, `created`/`created_by`, `scope.type`/`scope.include`/`scope.exclude`/`scope.tier_a_include`/`scope.notes`/`scope.rationale`/`scope.amendments`, `scripts_intent`/`assets_intent` — preserving `target_ref`/`source_ref`/`tier_a_include`/`amendments` verbatim). Load, read entirely, and execute `{ratifyTargetFile}` — bypassing step 2 (analyze-target) and step 3 (scope-definition), both of which would re-derive fields already on disk. The forward chain resumes at step 4 (confirm-brief), which auto-confirms `[C]` under headless and proceeds to step 5's write (the step 5 §2b ratify branch auto-overwrites in place). Do **not** run the source-authority detection or the `[C] → {nextStepFile}` routing below — they belong to the derive path.
      
        **Headless source-authority detection (derive route only — no `from_brief`).** 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.
      
      
    • headless-args.md 4 KB
      # Headless Argument Table
      
      Loaded by step 1 §8 only when `{headless_mode}` is true. Canonical operator-facing documentation for the argument set consumed at step 1's GATE; the `{validateBriefInputsHelper}` enforces these rules deterministically (its `KNOWN_FIELDS` set must stay in sync with this table).
      
      | Argument | Required | Default | Notes |
      |----------|----------|---------|-------|
      | `target_repo` | yes¹ | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent. ¹Not required when `from_brief` is supplied — the ratify route derives the target from the brief and ignores `target_repo` (with a warning) if also passed |
      | `skill_name` | yes¹ | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent; HALT (exit 2, `halt_reason: "input-invalid"`) if non-kebab. ¹Not required when `from_brief` is supplied — the ratify route derives the name from the brief and ignores `skill_name` (with a warning) if also passed |
      | `from_brief` | no | — | Path to a pre-authored `skill-brief.yaml` (a file, or a directory containing one) to **ratify** instead of deriving a brief. When present it is the source of truth and routes the step 1 §8 GATE to the headless ratify path — the mirror of the interactive §3.1a `[R]` branch: schema-validate the brief → skip analyze-target / scope-definition (no re-derivation) → write through the canonical writer, overwriting in place (no `force` needed). `target_repo` / `skill_name` become optional and are ignored if also passed. HALT (exit 2, `halt_reason: "input-missing"`) if the value is empty or the resolved path does not exist; HALT (exit 2, `halt_reason: "input-invalid"`) if the brief fails schema validation |
      | `source_type` | no | `source` | If `docs-only`, `doc_urls` becomes required |
      | `doc_urls` | conditional | — | Required when `source_type=docs-only` (HALT exit 2, `halt_reason: "input-missing"` if empty). List of `url` or `url,label` |
      | `source_authority` | no | detected | `official` / `community` / `internal`. When absent and `target_repo` is a GitHub URL, step 1 §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` |
      | `target_version` | no | — | Auto-detected in step 2 if absent. Full X.Y.Z semver required (HALT exit 2, `halt_reason: "input-invalid"` on partial forms like `1`, `1.2`, `v2`) |
      | `scope_hint` | no | — | Free-text steering for §5 |
      | `language_hint` | no | — | Overrides language detection — consumed at step 2 §3: the detector still runs for the informational Detected-language line, but the hint becomes the confirmed language and the step 3 §4 low-confidence override does not fire |
      | `scope_type` | no | heuristic | `full-library` / `specific-modules` / `public-api` / `component-library` / `reference-app` / `docs-only`. When absent and `source_type=source`, step 3 §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` |
      | `include` | no | — | Comma-separated globs (used by step 3 §3) |
      | `exclude` | no | — | Comma-separated globs (used by step 3 §3) |
      | `scripts_intent` | no | `detect` | `detect` / `none` / free-text |
      | `assets_intent` | no | `detect` | `detect` / `none` / free-text |
      | `intent` | no | — | Free-text used to derive `description` in §7b |
      | `force` | no | — | Overwrite existing brief without prompting (consumed in step 5 §2b) |
      | `preset` | no | — | Name of a preset YAML file at `{sidecar_path}/brief-presets/{preset}.yaml`. Loaded at step 1 §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 |
      
    • headless-source-authority-detection.md 1.6 KB
      # Headless Source-Authority Detection
      
      Loaded by step 1 §8 only when **all three** preconditions hold:
      
      1. `{headless_mode}` is true
      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)
      3. `source_type=source` AND `target_repo` is a GitHub URL
      
      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`.
      
      ## Procedure
      
      Probe the operator's GitHub login:
      
      ```bash
      gh api user --jq .login
      ```
      
      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.
      
      | Outcome | Set | Rationale |
      |---------|-----|-----------|
      | login matches owner (case-insensitive) | `source_authority: "official"` | The operator is the repo's GitHub owner |
      | login does not match | `source_authority: "community"` | The operator is a downstream consumer |
      | `gh api user` errors (unauthenticated, network failure, missing binary) | `source_authority: "community"` (fallback) | Log `"warn: source-authority detection skipped — gh api user failed"` |
      | Local-path target | `source_authority: "community"` (no probe) | Comparison does not apply |
      
    • health-check.md 710 B
      ---
      # `shared/health-check.md` resolves relative to the SKF module root
      # (`{project-root}/_bmad/skf/` when installed, `{project-root}/src/`
      # during development), NOT relative to this step file.
      nextStepFile: 'shared/health-check.md'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 6: Workflow Health Check
      
      This is the terminal step of brief-skill. Load `{nextStepFile}`, read it fully, then execute it — do nothing else here (no user-facing reports, file writes, or result contracts; those were step 5). After `{nextStepFile}` returns control, the brief-skill workflow is fully complete: do not re-enter step 5 or step 6, load any further step file, or loop back into the workflow.
      
    • invocation-contract.md 5.8 KB
      # Invocation Contract — skf-brief-skill
      
      Full argument set, gate map, exit codes, and headless result envelope for `skf-brief-skill`. Interactive callers can ignore this file; headless automators and pipeline integrators consult it. This is the canonical statement of the `from_brief` ratify contract — the source of truth for both the interactive (`gather-intent.md` §3.1a) and headless (step 1 §8 GATE) ratify paths.
      
      ## Invocation Contract
      
      | Aspect | Detail |
      |--------|--------|
      | **Inputs** | `target_repo` [required], `skill_name` [required], `scope_hint` [optional], `language_hint` [optional], `target_version` [optional], `source_authority` [optional: official/community/internal, default community], `source_type` [optional: source/docs-only, default source], `doc_urls` [optional: list of `url[,label]` for source_type=docs-only or supplemental], `scope_type` [optional: full-library/specific-modules/public-api/component-library/reference-app/docs-only], `include` [optional: comma-separated globs], `exclude` [optional: comma-separated globs], `scripts_intent` [optional: detect/none/free-text, default detect], `assets_intent` [optional: detect/none/free-text, default detect], `intent` [optional: free-text used to derive description], `force` [optional: overwrite existing brief without prompting], `from_brief` [optional: path to a pre-authored `skill-brief.yaml` to *ratify* — when supplied it is the source of truth, `target_repo`/`skill_name` become optional/derived-from-brief, and the run mirrors the interactive §3.1a ratify path: schema-validate, skip analyze-target/scope-definition, write through the canonical writer in place], `[auto]` [optional: bracket modifier passed via pipeline context — when present, BS loads the upstream brief from `brief_path` in pipeline data, enriches it with doc detection, and writes through the canonical writer; requires `brief_path` from AN's `SKF_ANALYZE_RESULT_JSON`] |
      | **Gates** | step 1: Input Gate [use args] | step 3: Confirm Gate [C] | step 4: Confirm Gate [C] |
      | **Outputs** | `skill-brief.yaml` at `{forge_data_folder}/{skill-name}/skill-brief.yaml`; final `SKF_BRIEF_RESULT_JSON` line on stdout when `{headless_mode}` is true |
      | **Headless** | All gates auto-resolve with heuristic-driven or default action when `{headless_mode}` is true; pre-supplied inputs consumed at the gates that would otherwise prompt; absent `source_authority` and `scope_type` are resolved by signal-driven detection (see `references/headless-args.md`); existing briefs are preserved unless `--force` was supplied (HALT with `overwrite-cancelled` otherwise); supplying `from_brief <path>` instead routes the step 1 GATE to the ratify path described in the `from_brief` Inputs cell (schema-validate, skip analyze/scope, write in place — no `--force` needed) rather than deriving a new brief |
      | **Transient-failure retry** | This workflow does **not** auto-retry network or subprocess failures. A failed `gh` fetch (analyze-target.md §1, portfolio-similarity-check.md), QMD probe, or extraction script is logged and surfaced in the final result envelope as a warning, but the workflow continues with whatever signal it has. Headless pipelines that want retry semantics should wrap the invocation at their orchestrator level (e.g. CI re-runner on non-zero exit). Rationale: brief-skill is read-mostly with one terminal write (the YAML at step 5); a partial-signal retry has more failure modes than just re-running the whole workflow, which is cheap. |
      | **Exit codes** | See "Exit Codes" below |
      
      ## Exit Codes
      
      Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
      
      | Code | Meaning              | Raised by                                                                                  |
      | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
      | 0    | success              | step 6 (terminal)                                                                         |
      | 2    | input-missing / input-invalid | step 1 GATE — required headless arg absent (`target_repo`, `skill_name`, or `doc_urls` when `source_type=docs-only`) → `input-missing`; enum violation, malformed semver, non-kebab `skill_name`, or step 5 brief-context schema validation failure → `input-invalid` |
      | 3    | resolution-failure   | step 1 §1 (`forge-tier.yaml` missing); step 2 §1 (target inaccessible / `gh auth` fails) |
      | 4    | write-failure        | step 1 §1 pre-flight write probe (data folder unwritable: read-only mount, disk full, permissions denied); step 5 §4 (write to `{forge_data_folder}/{skill-name}/skill-brief.yaml` failed) |
      | 5    | overwrite-cancelled  | step 5 §2 (existing brief, `force` not supplied)                                          |
      | 6    | user-cancelled       | any interactive menu in step 1/03/04 (user selected `[X]` Cancel and exit)                |
      
      ## Result Contract (Headless)
      
      When `{headless_mode}` is true, step 5 emits a single-line JSON envelope on **stdout** before chaining to step 6, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
      
      ```
      SKF_BRIEF_RESULT_JSON: {"status":"success|error","brief_path":"…|null","skill_name":"…","version":"…|null","language":"…|null","scope_type":"…|null","exit_code":0,"halt_reason":null,"mode":"auto|null"}
      ```
      
      `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"forge-tier-missing"`, `"target-inaccessible"`, `"gh-auth-failed"`, `"write-failed"`, `"overwrite-cancelled"`, `"user-cancelled"`. `exit_code` matches the table above. `mode` is `"auto"` when BS was invoked with the `[auto]` flag (pipeline auto-brief generation), `null` otherwise (interactive or headless-without-auto).
      
    • portfolio-similarity-check.md 3.1 KB
      # Portfolio-Similarity Check
      
      Loaded by step 1 §6 only when **all three** preconditions hold:
      
      1. Forge tier is `Deep`
      2. `tools.qmd` is true in `forge-tier.yaml`
      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)
      
      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.
      
      ## Procedure
      
      The brief portfolio is already indexed in QMD collections — one `{skill-name}-brief` collection per existing brief, registered by step 5 §5 of every prior Deep-tier run. The qmd CLI does not support glob-style collection selection, so enumerate first (capping the sweep), then query the capped set per collection in **bounded parallel batches (≈4 concurrent Bash calls at a time)** — each `qmd query` cold-starts an embedding model, so issuing all of them at once on a large portfolio thrashes memory and stalls:
      
      ```bash
      # 1. Enumerate brief collections (one per existing brief), capping the sweep.
      #    Match the first whitespace field, not the whole line: `qmd collection list`
      #    rows end in a `(qmd://name/)` URI suffix, so a line-anchored `/-brief$/`
      #    matches nothing and the check silently no-ops. The cap bounds wall-time and
      #    concurrent model loads as the portfolio grows; `qmd collection list` has no
      #    guaranteed recency order, so this caps total count, not "newest N".
      qmd collection list | awk '$1 ~ /-brief$/ {print $1}' | head -n 12
      
      # 2. For each capped collection, query the proposed name + intent text.
      #    `timeout` bounds each cold model start so a slow qmd degrades instead of
      #    hanging; a non-zero exit (124 = timed out) falls through to the
      #    "times out → warn and continue" failure-mode branch below.
      timeout 20 qmd query "{name} {synthesized-or-intent-text}" -c {collection-name} -n 1 --min-score 0.6
      ```
      
      Aggregate the top hits across the swept `-brief` collections; keep the 3 highest-scoring across the union. If any results come back, surface them as a heads-up — *not* a HALT:
      
      ```
      **Heads up — these existing briefs look semantically close to `{name}`:**
        1. {existing-name} (similarity: {score})  — {existing-description}
        2. {existing-name} (similarity: {score})  — {existing-description}
      
      Continue with `{name}`, or pick a different name?
      ```
      
      ## Failure modes
      
      On any QMD failure (binary missing, collection list empty, any per-collection query times out — `timeout` returns exit code 124): log `"warn: portfolio-similarity check skipped — qmd query failed: {error}"` and continue silently — never HALT. A timed-out or failed query drops only that collection from the aggregate; the remaining hits still surface. 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`).
      
    • qmd-collection-registration.md 2.3 KB
      # QMD Collection Registration (Deep Tier)
      
      Loaded by step 5 §5 only when forge tier is Deep AND QMD is available. Skipped silently otherwise.
      
      Index the skill brief into a QMD collection so portfolio-level searches can find existing briefs and avoid duplicate skill creation across large monorepos.
      
      ## Collection Creation
      
      Create a QMD collection targeting only the brief file:
      
      ```bash
      qmd collection add {forge_data_folder}/{skill-name} --name {skill-name}-brief --mask "skill-brief.yaml"
      qmd embed
      ```
      
      If the collection already exists (re-briefing): remove and recreate for atomic replace:
      
      ```bash
      qmd collection remove {skill-name}-brief
      qmd collection add {forge_data_folder}/{skill-name} --name {skill-name}-brief --mask "skill-brief.yaml"
      qmd embed
      ```
      
      ## Embed Verification
      
      After `qmd embed` completes, verify the collection was embedded:
      
      - Run `qmd status` or `qmd collection list` and confirm `{skill-name}-brief` shows document count > 0
      - If verification succeeds: proceed to registry update with no `status` field
      - 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
      
      ## Registry Update (Delegated to Script)
      
      Build the entry JSON and pipe it to the `register-qmd-collection` subcommand:
      
      ```bash
      echo '{
        "name": "{skill-name}-brief",
        "type": "brief",
        "source_workflow": "brief-skill",
        "skill_name": "{skill-name}",
        "created_at": "{current ISO date}"
        // include "status": "pending" only when embed verification failed
      }' | uv run {forgeTierRwHelper} register-qmd-collection --target {forgeTierFile}
      ```
      
      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.
      
      ## Error Handling
      
      - If `qmd embed` or `qmd collection add` fails: log the error. Do NOT fail the workflow — the brief file was already written successfully.
      - 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.
      
    • scope-definition.md 16 KB
      ---
      nextStepFile: 'confirm-brief.md'
      recommendScopeTypeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-recommend-scope-type.py'
        - '{project-root}/src/shared/scripts/skf-recommend-scope-type.py'
      advancedElicitationSkill: '/bmad-advanced-elicitation'
      partyModeSkill: '/bmad-party-mode'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 3: Scope Definition
      
      ## Rules
      
      - Do not make scope decisions unilaterally — user drives all scope choices
      - Produce: scope type, include patterns, exclude patterns
      - **Re-entry from step 4 [R] revise:** prior selections (`scope.type`, `scope.include`, `scope.exclude`, `scope.notes`, `scope.tier_a_include`, `scope.rationale`, `scripts_intent`, `assets_intent`, supplemental `doc_urls`) are preserved as the current state. Re-present them at each section as the existing answer; the user only re-confirms or overrides. Do not reset to the §2c template menu unless the user explicitly asks to start scope over. When `scope.rationale` is preserved and the user changes `chosen` (the scope type) on this pass, recompute `accepted_recommendation` (`chosen == recommended`) and refresh `reason` and `recorded` per the §2c capture rules — revise in place, do not append.
      
      ## Sequence
      
      ### 1. Present Scope Context
      
      "**Let's define the scope for your skill.**
      
      Based on the analysis, here's what we're working with:
      
      - **Target:** {repo}
      - **Language:** {detected language}
      - **Modules found:** {count} — {list names}
      - **Your intent:** {user intent from step 01}
      {If scope hints from step 01:}
      - **Your initial scope hints:** {hints}"
      
      ### 2. Handle Docs-Only Mode (if applicable)
      
      **If `source_type: "docs-only"`:**
      
      "**Docs-only mode — scope is defined by documentation pages.**
      
      You've provided these documentation URLs:
      {numbered list of doc_urls with labels}
      
      Which pages should be included in the skill? (Enter numbers, or 'all')
      Any additional documentation URLs to add?"
      
      Wait for confirmation. Then skip to section 5 (Summarize Scope Decisions) with:
      - `scope.type: "docs-only"`
      - `scope.include`: confirmed doc URLs
      - `scope.notes: "Generated from external documentation. All content is T3 confidence."`
      
      **If `source_type: "source"` (default):** Continue to scope templates below.
      
      ### 2b. Confirm Supplemental Documentation (if doc_urls collected)
      
      **If `source_type: "source"` AND supplemental `doc_urls` were collected in step 01:**
      
      "**Supplemental documentation URLs:**
      {numbered list of collected doc_urls with labels}
      
      These will be included as T3 external references in the skill brief.
      Add, remove, or confirm these URLs."
      
      Wait for confirmation. Record any changes to `doc_urls`.
      
      HEAD-check the URLs in parallel — issue all N `curl -sI --max-time 5 {url}` calls in a **single message with N parallel Bash calls**, then process the responses together. On a 4xx/5xx, DNS failure, or timeout per URL, warn `"Could not reach {url} — {status or error}."` and offer the same correct/keep choice as step 1 §3. The check is best-effort — never HALT on a failed HEAD — but the failure must surface here so it is not discovered downstream during compilation.
      
      **On re-entry from step 4 [R]:** if `doc_urls` is byte-identical to the list that was probed on the previous pass through this subsection AND the prior per-URL probe results are still recoverable from conversation context, skip the parallel HEAD-check and reuse those results. Re-running the probes when the list has not changed wastes round-trips and can flap on transient failures. Any addition, removal, or edit to a URL invalidates the cache — re-probe the entire updated set. If the prior results are not recoverable (long session, compaction, etc.), re-probe — never cache-hit on a list whose results you cannot cite.
      
      **If no supplemental doc_urls were collected:** Skip this subsection.
      
      **Scope guidance for first-time users:** A well-scoped skill covers one cohesive capability with 3-8 primary functions. If the scope includes unrelated concerns (e.g., authentication AND data visualization), suggest splitting into separate briefs. If the scope is too narrow (single utility function), suggest expanding to the surrounding capability surface.
      
      ### 2c. Offer Scope Templates
      
      Load `{scopeTemplatesPath}` for the scope type options ([F], [M], [P], [C], [R]) and their descriptions.
      
      **Recommend a scope type — don't present the five options as equal weight.** SKILL.md states this workflow "steers toward the smaller, sharper version when scope is unclear" — surface that opinion at decision time. Use the analysis from step 2 and the user's intent from step 1 to pick the best-fit recommendation, then present the menu with that option marked as the suggested default.
      
      **Resolve `{recommendScopeTypeHelper}`** from `{recommendScopeTypeProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      **Delegate the recommendation to `{recommendScopeTypeHelper}`** instead of walking the heuristic ladder in prose. The script is the single source of truth for the five-rule ladder (component-registry → reference-app keywords → specific-modules naming/count → narrow-public-api → default full-library) plus the docs-only short-circuit. Both the interactive recommendation and the §6 headless GATE invoke the same script — same inputs, same outputs, no drift.
      
      **Fetch registry-file contents before building the payload.** Step-02 §4.1 fetches `package.json` plus the entry-point files but does not fetch `registry.ts` / `components.ts` — the deep-match branch of the component-registry rule needs those contents. Scan the tree for any of `registry.ts` / `registry.tsx` / `components.ts` / `components.tsx` (any depth). For each match, fetch its contents in **one message with N parallel Bash calls** (`gh api repos/{owner}/{repo}/contents/{path}?ref={analysis_ref}` for GitHub — `{analysis_ref}` is the ref resolved in step 02 §1, defaulting to `HEAD`; file reads for local), then base64-decode the responses together. Skip the fetch if the tree contains no registry files.
      
      Build the payload and invoke:
      
      ```bash
      echo '{
        "intent": "<combined intent + scope_hint text from step 1>",
        "module_count": <count from step 2 §4.3>,
        "export_count": <count from step 2 §4.3>,
        "tree": [<flat list of repo-relative file paths from step 2 §1>],
        "entry_files": [{"path": "<registry path>", "content": "<contents>"}, ...],
        "source_type": "source",
        "mode": "interactive"
      }' | uv run {recommendScopeTypeHelper}
      ```
      
      `entry_files` carries the registry contents fetched above; omit when no registry files exist in the tree. `mode: "interactive"` activates the content-inspection branch of the component-registry rule (10+ entries or `Component[]` annotation); the headless GATE in §6 uses `mode: "headless"` which falls back to presence-only matching. `source_type: "docs-only"` short-circuits to `docs-only` regardless of the other signals.
      
      The script returns `{scope_type, matched_heuristic, signals, rationale}`. Use `rationale` directly — it already names the specific signals that fired.
      
      **Persist the rationale — do not discard it.** Hold `scope_type` as `rationale.recommended` and `matched_heuristic` as `rationale.heuristic` in conversation state. After the user's §2c selection: if they accept the recommendation, set `rationale.chosen = recommended`, `accepted_recommendation = true`, `reason = <script rationale verbatim>`. If they override, set `chosen = <selected type>`, `accepted_recommendation = false`, and ask one line — *"In a sentence, why {chosen} over the recommended {recommended}?"* — storing the answer as `reason` (or `"user overrode {recommended}->{chosen}; reason not stated"` if skipped). Set `recorded = {current ISO date}`. This object becomes `scope.rationale`.
      
      Present:
      
      "**Recommended scope type: [{letter}] {Name}** — {rationale from the script}.
      
      How broadly should this skill cover the library?
      
      {full menu from `{scopeTemplatesPath}` with the recommended letter marked, e.g. '[F] Full Library', '[M] Specific Modules', '[P] Public API Only ← recommended', '[C] Component Library', '[R] Reference App'}
      
      Press Enter to accept the recommendation, or pick a different letter."
      
      **First-timer reassurance (interactive only, never-briefed user — the §4 first-timer rail fired in step 01).** Append one line so the harder scope-type call doesn't stall a first-timer: "The recommended type is almost always right — accept it and re-scope from step 4 if the analysis surprises you." Repeat users and headless skip this line.
      
      Wait for user selection. Empty input or just Enter accepts the recommendation; any of the five letters overrides.
      
      ### 3. Define Boundaries Based on Selection
      
      Using the boundary definitions from `{scopeTemplatesPath}`, present the appropriate flow for the user's selected scope type ([F], [M], [P], [C], or [R]). Follow each type's prompts and wait for user input at each phase before proceeding.
      
      ### 3b. Monorepo Subpackage Convention
      
      **Applies only when step 02 §1b selected a workspace (`monorepo_workspace` is set).** A subpackage skill documents one package inside a larger repository, so the source and scope fields follow a fixed convention. Express them wrong and `skf-create-skill` cannot resolve the scope globs against the cloned source:
      
      - **`source_repo` stays the repository URL**, never the subpackage. `skf-create-skill` clones the whole repo at the pinned ref and then roots extraction at the subpackage, so the repo URL is what it clones.
      - **`scope.include` / `scope.exclude` globs are repo-root-relative and subpackage-prefixed.** Step 02 rebased its analysis against `monorepo_workspace`, but the emitted globs must still carry the workspace prefix (e.g. `packages/sdk/src/**`, not `src/**`) because they resolve against the repo root, not the subpackage root.
      - **Record the subpackage layout in `scope.notes`:** the subpackage root (the `monorepo_workspace` path), the published package name and version, the resolved git ref, and the local-clone directory. This is the only field that maps the repo-URL `source_repo` to the actual skilled subpackage — downstream workflows and re-forges read it to reconstruct the source layout.
      
      Carry the `monorepo_workspace` path forward from step 02 §1b into the `scope.notes` you draft here rather than recomputing it.
      
      ### 3c. Tier-A Authoring Surface (coarse-glob monorepo subsets)
      
      **Applies when a monorepo subpackage's `scope.include` uses coarse directory globs (`packages/foo/src/**`, `bin/**`) rather than an explicit file list.** Coarse globs also sweep in internal-only files (build scripts, state-store impls, generated config) that the package's public entry barrel never re-exports. `skf-create-skill` scores coverage against the *authoring* surface, and `skf-test-skill` re-derives that surface from the brief to guard against a deflated coverage denominator — so when the coarse-glob union is much larger than the documented export count and the brief names no narrower surface, the test gate inflates the denominator and an otherwise-complete skill scores as if it had large coverage gaps.
      
      Head this off by capturing the authoring surface as **`scope.tier_a_include`**: the concrete source files whose named exports the package's public entry barrel (`index.ts`, `lib.rs`, `__init__.py`) actually re-exports. Derive the candidate list by tracing the entry barrel's re-export targets (the entry-point file was fetched in step 02), present it for the user to confirm or adjust, and store the confirmed list as `scope.tier_a_include`. List the definition files, not the umbrella barrel itself — a barrel re-exports the whole package, so including it widens the surface instead of narrowing it.
      
      `scope.tier_a_include` does not change what gets extracted (that still follows `scope.include` / `scope.exclude`); it only pins the coverage denominator so the create-side and test-side counts agree without a mid-test hand-edit. Leave it unset when `scope.include` is already an explicit file list, or when the target is not a monorepo subset — there the coarse-glob union and the authoring surface coincide and no narrowing is needed.
      
      ### 4. Handle Language Override
      
      {If language detection confidence was low from step 02:}
      
      "**Language confirmation needed.**
      
      The analysis detected **{language}** with low confidence. Is this correct, or should we set a different primary language?"
      
      Wait for confirmation or override.
      
      **Headless:** `language_hint`, when supplied, already set the language at step 02 §3; otherwise accept the detected language and continue. This confirmation prompt is interactive-only, so a headless run never stalls here.
      
      ### 5. Summarize Scope Decisions
      
      "**Scope Summary:**
      
      **Type:** {Full Library / Specific Modules / Public API / Component Library / Reference App}
      
      **Include:**
      {bulleted list of include patterns}
      
      **Exclude:**
      {bulleted list of exclude patterns}
      
      **Language:** {confirmed language}
      
      {If any scope notes:}
      **Notes:** {scope notes}
      
      Does this look right? You can adjust before we continue."
      
      Wait for confirmation. Make adjustments if requested.
      
      ### 5b. Scripts & Assets Intent (Optional)
      
      **Only ask when `scope.type` is `full-library`, `specific-modules`, `component-library`, or `reference-app` (skip for `public-api` and `docs-only`). Reference apps routinely ship wiring scripts and build-config assets — prompt for them.**
      
      "Does this library include executable scripts (CLI tools, validation scripts, setup helpers) or static assets (config templates, JSON schemas, example configs) that should be packaged with the skill?"
      
      - **[D] Auto-detect** from source (default) — SKF will scan for `scripts/`, `bin/`, `assets/`, `templates/`, `schemas/` directories
      - **[N] None expected** — skip script/asset detection
      - Or describe what you expect (free text)
      
      Record the response as `scripts_intent` and `assets_intent` in the brief. Default to `detect` if user does not respond or skips.
      
      ### 6. Present MENU OPTIONS
      
      Display: **Select an Option:** [A] Advanced Elicitation [P] Party Mode [C] Continue to Brief Confirmation [X] Cancel and exit
      
      #### Menu Handling Logic:
      
      - IF A: Invoke {advancedElicitationSkill}, and when finished redisplay the menu
      - IF P: Invoke {partyModeSkill}, and when finished redisplay the menu
      - IF C: Load, read entire file, then execute {nextStepFile}
      - IF X: Treat as user-cancellation. 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. `[X]` is interactive-only; the headless GATE never reaches this branch.
      - IF Any other comments or queries: help user respond then [Redisplay Menu Options](#6-present-menu-options)
      
      #### Execution rules:
      
      - **GATE [default: C]** — If `{headless_mode}`: consume the headless inputs from step 1 in priority order:
        - If `scope_type` was supplied, use it (must match one of the six valid types) and skip the §2c template menu.
        - Otherwise auto-select via `{recommendScopeTypeHelper}` — invoke the script with the **same payload shape** documented in §2c but with `mode: "headless"` (presence-only matching for the component-registry rule, since `entry_files` may not be available without an interactive context). Use the returned `scope_type` and log `"headless: scope_type={value} from heuristic={matched_heuristic}"`. The script's docs-only short-circuit handles `source_type=docs-only` automatically.
        - If `include`/`exclude` were supplied, use them verbatim (split on comma) instead of running the boundary prompts in §3.
        - If `scripts_intent`/`assets_intent` were supplied, record them and skip §5b; otherwise default to `detect`.
        - Set `scope.rationale`: `recommended`/`heuristic` from the script (or `recommended = scope_type` arg, `heuristic = "user-supplied-arg"` when `scope_type` was passed); `chosen = <resolved type>`; `accepted_recommendation = (no scope_type arg)`; `reason = "<script rationale>"` (auto path) or `"headless: scope_type supplied as argument"` (arg path); `recorded = {date}`. No prompt — headless never asks "why".
        - Log: `"headless: scope_type={value} include={n} exclude={n} scripts_intent={value} assets_intent={value}"`.
      - After other menu items execution, return to this menu
      - User can chat or ask questions — always respond and then redisplay menu
      
      
    • step-auto-brief.md 10.5 KB
      ---
      nextStepFile: 'step-auto-validate.md'
      validateBriefSchemaProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
        - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
      writeSkillBriefProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
        - '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
      emitBriefEnvelopeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
        - '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
      mergeDocUrlsProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-merge-doc-urls.py'
        - '{project-root}/src/shared/scripts/skf-merge-doc-urls.py'
      detectDocsProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
        - '{project-root}/src/shared/scripts/skf-detect-docs.py'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 1a: Auto-Brief Generation
      
      ## STEP GOAL:
      
      To enrich an upstream skill brief (produced by AN auto-scope) with documentation URLs discovered via `skf-detect-docs.py`, validate the enriched brief, and write it through the canonical writer. Envelope emission is deferred to step-auto-validate.md, which presents the brief for user approval before continuing. This step replaces the interactive gather-intent → analyze-target → scope-definition → confirm-brief → write-brief chain when `[auto]` mode is active.
      
      ## Rules
      
      - Auto-proceed step — no user interaction required
      - This step is conditional — only loaded when `[auto]` flag is present in the pipeline context
      - Must produce the same output artifact as the interactive chain: a validated `skill-brief.yaml`
      - Doc detection is best-effort — failures do not halt the pipeline
      - Do NOT re-derive scope fields from the upstream brief — AN already set them correctly
      - Do NOT render YAML or JSON envelopes in the LLM — delegate to deterministic scripts
      
      ## MANDATORY SEQUENCE
      
      ### 1. Load Upstream Brief
      
      Read the upstream brief path from `{brief_path}` (passed by the forger from AN's `SKF_ANALYZE_RESULT_JSON` `brief_paths[]`).
      
      **IF `{brief_path}` is not set or the file does not exist:**
      - HARD HALT with exit code 2 (`input-missing`): "**Auto-brief requires an upstream brief — `brief_path` is missing or the file does not exist at `{brief_path}`.**"
      - Emit error envelope per §6 with `halt_reason: "input-missing"`.
      
      **Resolve `{validateBriefSchemaHelper}`** from `{validateBriefSchemaProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Validate the upstream brief against the schema:
      
      ```bash
      uv run {validateBriefSchemaHelper} {brief_path}
      ```
      
      The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}`.
      
      - **`valid: false`** — the upstream brief is malformed. HARD HALT with exit code 2 (`input-invalid`): "**Upstream brief at `{brief_path}` is invalid: {first error message}.**" Emit error envelope per §6 with `halt_reason: "input-invalid"`.
      - **`valid: true`** — proceed with the parsed `brief` payload. Surface any non-empty `warnings[]` to the log.
      
      Extract from the parsed brief:
      - `skill_name` ← `brief.name`
      - `version` ← `brief.version`
      - `source_repo` ← `brief.source_repo`
      - `language` ← `brief.language`
      - `scope_type` ← `brief.scope.type`
      - `forge_tier` ← `brief.forge_tier`
      - `description` ← `brief.description`
      - `created` ← `brief.created`
      - `created_by` ← `brief.created_by`
      - All scope fields: `scope.include`, `scope.exclude`, `scope.notes`, `scope.rationale`, `scope.amendments`, `scope.tier_a_include`
      - Optional fields: `source_type`, `source_authority`, `doc_urls`, `target_version`, `target_ref`, `source_ref`, `scripts_intent`, `assets_intent`
      
      **Docs-only check:** If `source_type` is `docs-only` in the parsed brief, skip §2 (Run Doc Detection) and §3 (Enrich Brief with Detected Docs) — the doc URL is already in the brief's `doc_urls`. Log: "Docs-only brief — skipping repo-based doc detection. Doc URLs provided by upstream." Proceed directly to §4 (Validate Enriched Brief). All brief fields (`source_type`, `source_authority`, `doc_urls`, `scope_type`) must pass through unmodified.
      
      ### 2. Run Doc Detection
      
      **Resolve `{detectDocsHelper}`** from `{detectDocsProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Invoke doc detection to discover documentation URLs for the source repo:
      
      ```bash
      uv run {detectDocsHelper} --repo-url {source_repo}
      ```
      
      `--repo-url` is always required (the script uses it for GitHub API calls). If a local clone is also available at `{local_clone_path}`, add `--local-path {local_clone_path}` to enable docs-folder scanning in addition to API-based detection.
      
      **Handle exit codes:**
      
      - **Exit 0 (found docs):** Parse the JSON output array. Each entry has `{url, detected_via, content_hash, content_type}`. Proceed to §3 with the detected docs.
      - **Exit 1 (none found):** Log: "No external documentation found — brief generated from source analysis only." Proceed to §4 with no doc enrichment.
      - **Exit 2 (error):** Log warning: "Doc detection failed — proceeding without doc enrichment." Do NOT halt — doc enrichment is best-effort. Proceed to §4 with no doc enrichment.
      
      ### 3. Enrich Brief with Detected Docs
      
      For each detected doc entry, create a brief `doc_urls` entry:
      
      - `url` ← `url` (direct copy)
      - `label` ← derive from `content_type` if available:
        - `"api-docs"` → `"API Documentation"`
        - `"guide"` → `"Guide"`
        - `"reference"` → `"Reference"`
        - Otherwise derive from `detected_via`:
          - `"homepageUrl"` → `"Homepage"`
          - `"readme_link"` → `"README Link"`
          - `"pages_api"` → `"GitHub Pages"`
          - `"docs_folder"` → `"Docs Folder"`
      - `source` ← coarse provenance derived from `detected_via` (per the `skill-brief.v1.json` `doc_urls[].source` enum): `homepageUrl` → `homepage`, `readme_link` → `readme-detection`, `pages_api` → `pages-api`, `docs_folder` → `docs-folder`. This marks the entry as opportunistically detected, distinct from a registry-guaranteed corpus.
      
      **Merge via the canonical helper.** Resolve `{mergeDocUrlsHelper}` from `{mergeDocUrlsProbeOrder}` (first existing path wins; HALT if neither exists). Pass the upstream brief's `doc_urls` as `existing` and the entries just mapped above as `detected`:
      
      ```bash
      echo '{"scope_type": "{scope_type}", "existing": {upstream brief doc_urls JSON, [] if none}, "detected": {mapped detected entries JSON}}' \
        | uv run {mergeDocUrlsHelper}
      ```
      
      The helper returns `{"doc_urls": [...], "suppressed": [...]}`. It deduplicates by **normalized** URL (lowercase host, strip a trailing `/index.html` and any trailing `/`), so a seeded `…/book/` and a README's `…/book/index.html` collapse to one entry; existing/corpora-seeded entries always win and keep their `source: language-registry`, so the registry-vs-detected distinction survives the merge. For a **whole-language reference** (`scope_type == "full-library"` AND ≥1 `existing` entry has `source: language-registry`) it additionally suppresses README noise on a corpus host: non-corpus path segments (`/whatsnew/`, `/contribute`, `/wiki/`) and non-primary-locale duplicates of a kept page (`/ja/master/` when `/en/master/` is kept). Ordinary skills (any other `scope_type`, or no registry corpora) pass through with dedup only — no suppression. Use the returned `doc_urls` as the brief's merged list.
      
      **Log suppressed entries.** When `suppressed` is non-empty, log one line per entry — `"info: suppressed {url} ({reason})"` — so the operator can see what the whole-language noise filter dropped (never drop silently). The N==0 DEGRADED case (a whole-language repo whose registry returned no corpora) carries no `language-registry` entry, so suppression stays inactive and its README docs are kept — this is intentional (there is no canonical corpus host to filter against).
      
      ### 4. Validate Enriched Brief
      
      Assemble the enriched brief context as a flat JSON object following the write-brief §3 contract:
      
      ```json
      {
        "name":             "{skill_name}",
        "target_version":   "{target_version or null}",
        "detected_version": null,
        "source_type":      "{source_type or 'source'}",
        "source_repo":      "{source_repo}",
        "language":         "{language}",
        "description":      "{description}",
        "forge_tier":       "{forge_tier}",
        "created":          "{created}",
        "created_by":       "{created_by}",
        "scope_type":       "{scope_type}",
        "scope_include":    ["{scope.include patterns}"],
        "scope_exclude":    ["{scope.exclude patterns}"],
        "scope_notes":      "{scope.notes or ''}",
        "scope_rationale":  null,
        "scope_tier_a_include": null,
        "scope_amendments":     null,
        "doc_urls":         [{"url": "...", "label": "...", "source": "..."}],
        "scripts_intent":   "{scripts_intent or null}",
        "assets_intent":    "{assets_intent or null}",
        "source_authority": "{source_authority or null}",
        "target_ref":       "{target_ref or null}",
        "source_ref":       "{source_ref or null}",
        "version_resolved": "{version}"
      }
      ```
      
      The `version_resolved` key pins the output to the upstream brief's version — without it, the writer's precedence logic falls through to `1.0.0` since `target_version` and `detected_version` are both null on the auto path.
      
      ### 5. Write Enriched Brief
      
      **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Write the enriched brief through the canonical writer:
      
      ```bash
      echo '<context-json>' | uv run {writeSkillBriefHelper} write --target {forge_data_folder}/{skill_name}/skill-brief.yaml --from-flat
      ```
      
      **On script failure (non-zero exit):**
      - Exit 1 (validation/invariant): Emit error envelope per §6 with `halt_reason: "input-invalid"`, then HARD HALT.
      - Exit 2 (I/O failure): Emit error envelope per §6 with `halt_reason: "write-failed"`, then HARD HALT.
      
      **On success:** Capture `brief_path` and `version` from the response envelope for step-auto-validate's envelope emission.
      
      ### 6. Error Envelope (Canonical)
      
      Every HARD HALT in this step emits the error envelope on stderr:
      
      **Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      ```bash
      echo '{"status":"error","skill_name":"{skill_name or unknown}","halt_reason":"{reason}","mode":"auto"}' | \
        uv run {emitBriefEnvelopeHelper} emit --target stderr
      ```
      
      ### 7. Chain to Auto-Validate
      
      Load, read fully, then execute {nextStepFile} to present the auto-brief validation gate, where the user can approve, edit, or reject the brief before the pipeline continues. Do this only after the enriched brief has been written and validated.
      
    • step-auto-validate.md 9.5 KB
      ---
      nextStepFile: 'health-check.md'
      rejectTargetFile: 'confirm-brief.md'
      validateBriefSchemaProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
        - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
      writeSkillBriefProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
        - '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
      emitBriefEnvelopeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
        - '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 1b: Auto-Brief Validation
      
      ## STEP GOAL:
      
      To present the user with a concise summary of the auto-generated brief and offer three actions — approve, edit, or reject — before the pipeline continues. On approve or edit, the result envelope is emitted and the pipeline chains to the health check. On reject, the pipeline falls back to the interactive brief review cycle with pre-populated fields.
      
      ## Rules
      
      - This step is conditional — only loaded from step-auto-brief.md when `[auto]` mode is active
      - The brief MUST already exist on disk (written by step-auto-brief §5) before this step runs
      - Do NOT render YAML or JSON envelopes in the LLM — delegate to deterministic scripts
      - Do NOT modify confirm-brief.md or write-brief.md — the [R]eject path reuses them as-is
      - The 10-line summary is always displayed, even in headless mode, for logging transparency
      
      ## MANDATORY SEQUENCE
      
      ### 1. Load Auto-Brief
      
      Read the brief from `{forge_data_folder}/{skill_name}/skill-brief.yaml` (written by step-auto-brief §5).
      
      **Resolve `{validateBriefSchemaHelper}`** from `{validateBriefSchemaProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Validate the brief against the schema:
      
      ```bash
      uv run {validateBriefSchemaHelper} {forge_data_folder}/{skill_name}/skill-brief.yaml
      ```
      
      The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}`.
      
      - **`valid: false`** — HARD HALT with exit code 2 (`input-invalid`): "**Auto-brief at `{forge_data_folder}/{skill_name}/skill-brief.yaml` is invalid: {first error message}.**" Emit error envelope per §7 with `halt_reason: "input-invalid"`.
      - **`valid: true`** — proceed with the parsed `brief` payload. Surface any non-empty `warnings[]` to the log.
      
      **IF the file does not exist:**
      - HARD HALT with exit code 2 (`input-missing`): "**Auto-brief not found at `{forge_data_folder}/{skill_name}/skill-brief.yaml` — step-auto-brief must write the brief before this step runs.**" Emit error envelope per §7 with `halt_reason: "input-missing"`.
      
      Extract from the parsed brief:
      - `skill_name` ← `brief.name`
      - `version` ← `brief.version`
      - `source_repo` ← `brief.source_repo`
      - `language` ← `brief.language`
      - `scope_type` ← `brief.scope.type`
      - `scope_include` ← `brief.scope.include`
      - `scope_exclude` ← `brief.scope.exclude`
      - `forge_tier` ← `brief.forge_tier`
      - `description` ← `brief.description`
      - `doc_urls` ← `brief.doc_urls`
      
      ### 2. Present 10-Line Summary
      
      Render a concise summary from the brief fields for rapid scanning:
      
      ```
      Auto-Brief Summary: {skill_name}
      ─────────────────────────────────
      Source:       {source_repo}
      Language:     {language}
      Scope:        {scope_type} ({N} include, {M} exclude patterns)
      Docs:         {doc_urls count} sources detected | "None detected"
      Version:      {version}
      Forge Tier:   {forge_tier}
      Pipeline:     forge-auto ({forge_tier} tier)
      Description:  "{description}"
      ```
      
      Where `{N}` is the count of `scope_include` patterns and `{M}` is the count of `scope_exclude` patterns. If `doc_urls` is null or empty, display "None detected". The `Pipeline` line names the auto pipeline and the resolved `{forge_tier}` — it carries no numeric quality target, which would be an unverified guarantee an automator might parse as fact.
      
      ### 3. Validation Gate
      
      **GATE: [A]pprove** — Present `[A]pprove` / `[E]dit` / `[R]eject` to user.
      
      "**Review the auto-generated brief above.**
      
      Select an action:
        [A] Approve — accept the brief as-is and continue the pipeline
        [E] Edit — modify specific fields before continuing
        [R] Reject — fall back to interactive brief review with pre-populated fields"
      
      If `{headless_mode}`: auto-proceed with `[A]pprove`, log: "headless: auto-approve auto-brief".
      
      Wait for user response. Branch on the response:
      
      - `[A]` or `approve` → §4 ([A]pprove path)
      - `[E]` or `edit` → §5 ([E]dit path)
      - `[R]` or `reject` → §6 ([R]eject path)
      - Any other input → answer briefly, re-display the menu
      
      ### 4. [A]pprove Path
      
      **Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      Emit the `SKF_BRIEF_RESULT_JSON` envelope with `mode: "auto"`:
      
      ```bash
      echo '{"status":"success","brief_path":"{brief_path}","skill_name":"{skill_name}","version":"{version}","language":"{language}","scope_type":"{scope_type}","halt_reason":null,"mode":"auto"}' | \
        uv run {emitBriefEnvelopeHelper} emit
      ```
      
      Where `{brief_path}` is `{forge_data_folder}/{skill_name}/skill-brief.yaml`.
      
      Chain to {nextStepFile} (health-check.md) — load, read fully, then execute.
      
      ### 5. [E]dit Path
      
      Present each brief field with its current value and accept natural language modification requests.
      
      "**Editable fields:**
      
      1. **Name:** {skill_name}
      2. **Source:** {source_repo}
      3. **Language:** {language}
      4. **Scope type:** {scope_type}
      5. **Include patterns:** {scope_include}
      6. **Exclude patterns:** {scope_exclude}
      7. **Description:** {description}
      8. **Doc URLs:** {doc_urls or "None"}
      9. **Version:** {version}
      10. **Forge tier:** {forge_tier}
      
      Tell me what to change (e.g. 'change scope type to public-api', 'add doc URL https://...')."
      
      Wait for user response. Apply changes to the brief context.
      
      **Re-write the modified brief** through the canonical writer:
      
      **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins.
      
      Assemble the modified brief context as a flat JSON object (same format as step-auto-brief §4):
      
      ```bash
      echo '<modified-flat-json>' | uv run {writeSkillBriefHelper} write --target {forge_data_folder}/{skill_name}/skill-brief.yaml --from-flat
      ```
      
      The canonical writer validates the brief internally — on non-zero exit, surface the error and re-prompt for corrections. The edit loop allows multiple modifications — each write re-validates before accepting.
      
      **Re-validate the written brief** against the schema to confirm correctness:
      
      **Resolve `{validateBriefSchemaHelper}`** from `{validateBriefSchemaProbeOrder}`; first existing path wins.
      
      ```bash
      uv run {validateBriefSchemaHelper} {forge_data_folder}/{skill_name}/skill-brief.yaml
      ```
      
      If validation fails (the writer missed a constraint), surface the error and re-prompt for corrections.
      
      Re-present the 10-line summary (§2 format) with updated values so the user can verify the change.
      
      "Updated brief written. **Select:** [A] Approve and continue · [E] Edit more · [R] Reject"
      
      - `[A]` → emit envelope per §4, chain to {nextStepFile}
      - `[E]` → repeat §5 edit loop
      - `[R]` → §6 ([R]eject path)
      
      ### 6. [R]eject Path
      
      "**Falling back to interactive brief — fields pre-populated from auto-detection.**"
      
      Hydrate brief context variables from the auto-brief on disk, using the same field mapping as the ratify path in gather-intent §3.1a:
      
      - `name` ← `brief.name`; `version` ← `brief.version`; `target_version` ← `brief.target_version`
      - `target_ref` ← `brief.target_ref`; `source_ref` ← `brief.source_ref` (optional git refs; preserve when present)
      - `source_repo` ← `brief.source_repo`; `source_type` ← `brief.source_type`; `source_authority` ← `brief.source_authority`; `doc_urls` ← `brief.doc_urls`
      - `language` ← `brief.language`; `description` ← `brief.description`; `forge_tier` ← `brief.forge_tier`
      - `created` ← `brief.created`; `created_by` ← `brief.created_by`
      - `scope.type` / `scope.include` / `scope.exclude` / `scope.tier_a_include` / `scope.notes` / `scope.rationale` / `scope.amendments` ← `brief.scope.*` (preserve `tier_a_include` and the `amendments` log verbatim — do not re-derive or drop them)
      - `scripts_intent` ← `brief.scripts_intent`; `assets_intent` ← `brief.assets_intent`
      
      Set `ratify_mode: true` and `ratify_source_path: {forge_data_folder}/{skill_name}/skill-brief.yaml` in workflow context.
      
      Chain to {rejectTargetFile} (confirm-brief.md) — load, read fully, then execute. The user gets the full interactive review experience: view, adjust fields inline, revise scope via [R], or approve via [C] → write-brief.md → health-check.md.
      
      The interactive chain's write-brief.md handles its own envelope emission with `mode: null` (interactive), which is correct since the user explicitly chose to leave auto mode.
      
      ### 7. Error Envelope (Canonical)
      
      Every HARD HALT in this step emits the error envelope on stderr:
      
      **Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins.
      
      ```bash
      echo '{"status":"error","skill_name":"{skill_name or unknown}","halt_reason":"{reason}","mode":"auto"}' | \
        uv run {emitBriefEnvelopeHelper} emit --target stderr
      ```
      
      ### 8. Chain
      
      Load, read fully, then execute the appropriate next step file — only after the user has made their choice and the corresponding action has been taken (envelope emitted or context hydrated):
      - [A]pprove or [E]dit (after final approve): {nextStepFile} (health-check.md)
      - [R]eject: {rejectTargetFile} (confirm-brief.md)
      
    • version-resolution.md 4.1 KB
      # Version Resolution
      
      Single source of truth for how brief-skill resolves the `version` field of `skill-brief.yaml`. Loaded by step 2 §4b (auto-detect, fallback path only when the language is not script-supported) and step 5 §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.
      
      **Aligned with** `assets/skill-brief-schema.md` "Version Detection" section. If you change one, change the other.
      
      ## Detection Algorithm
      
      For the detected source language, attempt the lookups in order. Stop at the first match.
      
      - **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`
      - **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.
      - **Rust:** `Cargo.toml` `[package] version` (static). If `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0`.
      - **Go:** version tag from `go.mod`, or `git describe --tags --abbrev=0`.
      
      For remote GitHub sources, fetch version-bearing files via `gh api repos/{owner}/{repo}/contents/{file}?ref={analysis_ref}` (decode base64) — `{analysis_ref}` is the ref resolved in step 02 §1, defaulting to `HEAD` when no `target_ref`/`target_version` was pinned; reading at the pinned ref keeps the "Detected version" consistent with the version being skilled. For local sources, read the file directly.
      
      If every step fails or returns a non-semver value, the detected version is `null` — the resolver below falls back to `"1.0.0"`.
      
      **Pre-release handling:** preserve detected pre-release tags (`1.0.0-beta.0`, `2.0.0-rc.1`) verbatim. Do not strip them.
      
      ## Precedence — Resolving the `version` Field
      
      The brief's `version` field is resolved from three candidate sources, in priority order:
      
      1. **`target_version`** — collected interactively in step 1 §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`.
      2. **Auto-detected version** — from §"Detection Algorithm" above. Used when `target_version` is absent.
      3. **Default** — `"1.0.0"` when both of the above fail or yield a non-semver value.
      
      ## Invariant
      
      When `target_version` is set, the written brief must satisfy:
      
      ```
      brief.target_version == brief.version
      ```
      
      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.
      
      ## Step-Level Responsibilities
      
      | Step | Responsibility |
      |------|----------------|
      | 01 §3b | Collect `target_version` (interactive prompt, or headless arg). Do not auto-detect — that is step 02's job. |
      | 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. |
      | 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. |
      
    • write-brief.md 14.3 KB
      ---
      versionResolutionFile: 'references/version-resolution.md'
      qmdRegistrationFile: 'references/qmd-collection-registration.md'
      nextStepFile: 'health-check.md'
      writeSkillBriefProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
        - '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
      emitBriefEnvelopeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
        - '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
      forgeTierRwProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
        - '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
      forgeTierFile: '{sidecar_path}/forge-tier.yaml'
      ---
      
      <!-- Config: communicate in {communication_language}. -->
      
      # Step 5: Write Brief
      
      ## Rules
      
      - Focus only on writing the file — all decisions have been made
      - Do not change any field values without user request — the brief was already approved
      - Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing success summary is NOT the terminal step
      - All user-facing output in `{communication_language}`; written artifact (`description`, `notes`) in `{document_output_language}`
      - **Determinism delegation:** YAML rendering, version-precedence, atomic write, the headless result envelope, and the QMD-collection registry mutation are all delegated to shared SKF scripts. The LLM's job in this step is to assemble inputs, branch on script results, and surface user-facing prose — not to render YAML, JSON envelopes, or YAML-mutation diffs in the model.
      
      ## Sequence
      
      ### 1. Reference the Schema (LLM context only)
      
      **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      `{briefSchemaPath}` and `{versionResolutionFile}` document the brief contract for human readers. The deterministic enforcement of that contract lives in `{writeSkillBriefHelper}` and its JSON Schema artifact at `src/shared/scripts/schemas/skill-brief.v1.json`. Load `{briefSchemaPath}` only if you need to explain a specific field to the user during inline adjustments — otherwise skip the read; the script is the source of truth.
      
      ### 2. Resolve Output Path
      
      Resolve the target write path:
      - Primary: `{forge_data_folder}/{skill-name}/skill-brief.yaml`
      - Fallback (when `{forge_data_folder}` is not set or doesn't exist): `{output_folder}/forge-data/{skill-name}/skill-brief.yaml` and inform user "**Note:** forge_data_folder not configured. Writing to {output_folder}/forge-data/{skill-name}/ instead."
      
      The script's atomic-write helper creates parent directories as needed (`mkdir -p`) — no separate mkdir call required.
      
      ### 2b. Existing Brief — Overwrite Policy
      
      Before writing, check whether the resolved target path already exists.
      
      **Ratify path (`ratify_mode: true` in workflow context):**
      
      The overwrite was already authorized when ratify mode was entered — interactively at step 1 §3.1a (`[R] Ratify` against the same file, then reviewed and approved at step 4), or headlessly at the step 1 §8 GATE `from_brief` route (the operator pointed the run at a brief to ratify). Either way, skip the interactive prompt below; log a single-line `brief-skill: ratify-mode auto-overwriting existing brief at {path}` and proceed to §3. **This ratify branch takes precedence over both the interactive and headless branches below** — when `ratify_mode` is set, neither of those runs. In particular, a headless ratify (`from_brief`) auto-overwrites the brief in place without requiring `force`; `force` governs only the derive route, where overwriting a pre-existing brief is a genuine clobber the operator must opt into.
      
      **Interactive (`{headless_mode}` is false, `ratify_mode` not set):**
      
      If the file exists, present:
      
      "**An existing brief was found at `{path}`.**
      Overwrite it with the brief you just approved? [Y/N]"
      
      - **[Y]** Overwrite — proceed to §3.
      - **[N]** Cancel — emit a single-line stderr log `brief-skill: overwrite-cancelled at {path}` and HALT with exit code 5 (do not chain to step 6; the run produced no new artifact).
      
      **Headless (`{headless_mode}` is true):**
      
      If the file exists:
      
      - If `force` was supplied as a headless argument: log `"headless: force-overwriting existing brief at {path}"` and proceed to §3.
      - Otherwise: emit the error envelope per §4b with `halt_reason: "overwrite-cancelled"`, then HALT with exit code 5.
      
      If the file does not exist, proceed normally.
      
      ### 3. Write the Brief
      
      Assemble the brief context as a **flat** JSON object — every approved value is a top-level key, scope is split across four `scope_*` keys instead of nested, and every optional field is passed as `null` when not set rather than conditionally omitted. This eliminates the "decide what to omit" cognitive load that previously made this the most expensive HALT-typo site in the workflow:
      
      ```json
      {
        "name":             "{approved skill name}",
        "target_version":   "{target_version from step 01, or null}",
        "detected_version": "{auto-detected version from step 02, or null}",
        "source_type":      "{source or docs-only}",
        "source_repo":      "{approved source repo or doc site URL}",
        "language":         "{approved language}",
        "description":      "{approved description}",
        "forge_tier":       "{Quick|Forge|Forge+|Deep}",
        "created":          "{current ISO date YYYY-MM-DD}",
        "created_by":       "{user_name}",
        "scope_type":       "{approved scope type}",
        "scope_include":    ["{approved include patterns}"],
        "scope_exclude":    ["{approved exclude patterns}"],
        "scope_notes":      "{approved scope notes or empty string}",
        "scope_rationale":  null | {"recommended":"...","chosen":"...","accepted_recommendation":true|false,"heuristic":"...","reason":"...","recorded":"YYYY-MM-DD"},
        "scope_tier_a_include": null | ["{tier-A authoring-surface patterns — from step 03 §3c capture, or hydrated on a ratify run}"],
        "scope_amendments":     null | [{"path":"...","action":"...","reason":"...","date":"YYYY-MM-DD","workflow":"..."}],
        "doc_urls":         null | [{"url": "...", "label": "...", "source": "{optional: language-registry|readme-detection|homepage|pages-api|docs-folder}"}],
        "scripts_intent":   null | "{detect|none|free-text}",
        "assets_intent":    null | "{detect|none|free-text}",
        "source_authority": null | "{official|community|internal}",
        "target_ref":       null | "{explicit git ref — ratify only}",
        "source_ref":       null | "{resolved git ref — ratify only}"
      }
      ```
      
      **Ratify mode (`ratify_mode: true`):** this path never ran step 2, so the version was not re-derived — it was hydrated from the upstream brief at step 1 §3.1a (interactive) or the §8 GATE `from_brief` route (headless). Add a `version_resolved` key set to that hydrated `version`; the writer's precedence checks `version_resolved` first, so this pins the output to the brief's authored version. **Without it**, `target_version` and `detected_version` are both null on a ratify run and the writer falls through to the `1.0.0` default, silently discarding the upstream version. Keep `target_version` set to the brief's `target_version` (null if it had none) so the writer's `target_version == version` invariant still holds. Likewise carry `target_ref`/`source_ref` and `scope_tier_a_include`/`scope_amendments` from the hydrated brief (all null on a derive run) so the writer round-trips the monorepo git ref, the stratified tier-A surface, and the amendment audit log instead of dropping them.
      
      Pipe it into the writer script with the `--from-flat` flag:
      
      ```bash
      echo '<context-json>' | uv run {writeSkillBriefHelper} write --target {resolved-target-path} --from-flat
      ```
      
      The script translates flat → nested internally, drops the null optional fields, and runs the same schema validation and atomic write as before — pass every key always, the writer decides what reaches the YAML.
      
      The script:
      - Validates the context against `src/shared/scripts/schemas/skill-brief.v1.json`
      - Applies the version-precedence rule from `{versionResolutionFile}`
      - Enforces the `target_version == version` invariant (refuses to write a brief that violates it)
      - Renders YAML in canonical key order (byte-stable across runs)
      - Atomically writes the file via temp + fsync + rename (no half-written file ever visible)
      - Emits a JSON success envelope on stdout: `{"status":"ok","brief_path":"…","version":"…","bytes":…,"warnings":[…]}`
      
      **On script failure (non-zero exit):**
      - Exit 1 (validation/invariant): The error JSON on stderr names the offending field. This indicates a context-assembly bug, not a user error — surface the message to the user, log it, then HALT.
        - Interactive: **HALT** — display the error JSON's `message` field.
        - Headless: emit the error envelope per §4b with `halt_reason: "input-invalid"`, then `exit 2`.
      - Exit 2 (I/O failure): The atomic write failed (target unwritable, disk full, etc.).
        - Interactive: **HALT** — "**Error:** Failed to write skill-brief.yaml. Check that the directory is writable and try again."
        - Headless: emit the error envelope per §4b with `halt_reason: "write-failed"`, then `exit 4`.
      
      **On success:** capture `brief_path` and `version` from the response envelope — both are needed for §4b and §6.
      
      **Draft cleanup.** After a successful write, remove `{forge_data_folder}/{skill-name}/.brief-draft.json` if it exists (`rm -f` — silent on absent). The draft was a step 1 §7 checkpoint covering the in-flight workflow window; once the brief is written it is no longer meaningful. In headless mode this rm is a no-op (drafts are only written interactively).
      
      ### 4b. Headless Result Envelope (Canonical)
      
      **Resolve `{emitBriefEnvelopeHelper}`** from `{emitBriefEnvelopeProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      This section is the canonical envelope-emission reference for the workflow. Every headless emission — the success terminal here and every HARD HALT in step 1/02/05 — uses this contract. Remote sites point here instead of restating it.
      
      **Success (this call site only — emitted from §3 directly):**
      
      ```bash
      echo '{"status":"success","brief_path":"<from §3 response>","skill_name":"<name>","version":"<from §3 response>","language":"<language>","scope_type":"<scope.type>","halt_reason":null}' | \
        uv run {emitBriefEnvelopeHelper} emit
      ```
      
      **Error (used by every HARD HALT site):**
      
      ```bash
      echo '{"status":"error","skill_name":"<name>","halt_reason":"<reason>"}' | \
        uv run {emitBriefEnvelopeHelper} emit --target stderr
      ```
      
      When the HALT fires before `skill_name` has been resolved (step 1 §1 pre-flight write probe, step 1 §8 input-missing on a malformed args bundle), pass the partially-gathered value or the literal `"unknown"` — the script accepts any non-empty string at this position.
      
      The script derives `exit_code` deterministically from `halt_reason` (null→0, input-missing/input-invalid→2, forge-tier-missing/target-inaccessible/gh-auth-failed→3, write-failed→4, overwrite-cancelled→5, user-cancelled→6 [interactive-only — headless never raises this]), validates against `src/shared/scripts/schemas/skf-brief-result-envelope.v1.json`, and prints the prefixed `SKF_BRIEF_RESULT_JSON: {…}` line.
      
      The script enforces the success/error halt_reason invariant (success requires null halt_reason; error requires non-null). The `user-cancelled` halt_reason is accepted for completeness (interactive `[X]` Cancel sites in step 1/03/04) but never appears on the headless code path.
      
      Invocation sites (each pointed at this block, not duplicated): step 1 §1 (write-failed pre-resolution; forge-tier-missing), step 1 §8 (input-missing/input-invalid GATE), step 2 §1 (target-inaccessible/gh-auth-failed), step 5 §2b (overwrite-cancelled), step 5 §3 (input-invalid/write-failed from script). The step 1 §1 forge-tier-missing and step 2 §1 target-inaccessible/gh-auth-failed sites emit through this block too, so every headless HALT class surfaces a `SKF_BRIEF_RESULT_JSON` envelope — there are no envelope-silent failure classes.
      
      When `{headless_mode}` is false, skip this section silently — no envelope is emitted.
      
      ### 5. QMD Collection Registration (Deep Tier Only)
      
      **Resolve `{forgeTierRwHelper}`** from `{forgeTierRwProbeOrder}`; first existing path wins. HALT if no candidate exists.
      
      **IF forge tier is Deep AND QMD tool is available:** load `{qmdRegistrationFile}` and follow the procedure there to index the brief into a QMD collection and update the forge-tier registry.
      
      **IF forge tier is NOT Deep OR QMD is not available:** skip this section silently — do not load `{qmdRegistrationFile}`. No messaging.
      
      ### 6. Display Success Summary
      
      "**Skill brief written successfully.**
      
      ---
      
      **File:** `{brief_path from §3 response}`
      **Skill:** {name}
      **Language:** {language}
      **Scope:** {scope type}
      **Forge Tier:** {forge tier}
      
      ---
      
      ## Next Steps
      
      Your skill brief is ready. To compile the actual skill from this brief, run:
      
      **create-skill** — Reads your skill-brief.yaml and compiles a complete SKILL.md with AST-backed analysis.
      
      After compilation, you can:
      - **test-skill** — Validate the compiled skill
      - **export-skill** — Package the skill for distribution
      
      ---
      
      **Brief-skill workflow complete.**"
      
      ### 6b. On-Complete Hook (pipeline integration)
      
      If `{onCompleteCommand}` is non-empty (resolved at SKILL.md On Activation §3 from `workflow.on_complete`), invoke it now — after the brief has been written (§3) and the result contract finalized (§4b) — as:
      
      ```bash
      {onCompleteCommand} --result-path={brief_path}
      ```
      
      where `{brief_path}` is the absolute path captured from the §3 response envelope (the freshly written `skill-brief.yaml`, the stable artifact a downstream consumer chains from).
      
      - On success: log to `workflow_warnings[]` as informational only if the hook emitted stderr (`on_complete hook stderr: …`); otherwise no entry.
      - On non-zero exit / process error: log to `workflow_warnings[]` (`on_complete hook failed (exit {code}): {stderr_snippet}`).
      - **Never fail the workflow on hook errors** — the hook is for pipeline integration (chaining into create-skill, Slack, dashboards, CI), not for gating brief production.
      
      When `{onCompleteCommand}` is empty (bundled default), skip this section entirely — no hook is invoked.
      
      ### 7. Chain to Health Check
      
      Once the brief file has been written and the success summary displayed, load, read the full file, and execute `{nextStepFile}`. The health-check step is the true terminal step — do not stop here even though the summary reads as final.
      
  • customize.toml 2.2 KB
    # DO NOT EDIT -- overwritten on every update.
    #
    # Workflow customization surface for skf-brief-skill.
    # Team overrides:     _bmad/custom/skf-brief-skill.toml (under {project-root})
    # Personal overrides: _bmad/custom/skf-brief-skill.user.toml (under {project-root})
    
    [workflow]
    
    # --- Configurable below. Overrides merge per BMad structural rules: ---
    #   scalars: override wins • arrays (persistent_facts, activation_steps_*): append
    #   arrays-of-tables with `code`/`id`: replace matching items, append new ones.
    
    # Steps to run before the standard activation (uv probe, config load).
    # Overrides append. Use for org-wide pre-flight checks (auth, network,
    # compliance) that must precede any brief authoring work.
    
    activation_steps_prepend = []
    
    # Steps to run after activation but before the first stage executes.
    # Overrides append. Use for context loads or banner customization that
    # should run once activation completes successfully.
    
    activation_steps_append = []
    
    # Persistent facts the workflow keeps in mind for the whole run
    # (house style, naming conventions, description-voice guardrails).
    # Overrides append.
    #
    # Each entry is either:
    #   - a literal sentence, e.g. "Skill briefs must cite an authoritative source."
    #   - a file reference prefixed with `file:`, e.g.
    #     "file:{project-root}/docs/brief-style.md" (globs supported; file
    #     contents are loaded and treated as facts).
    
    persistent_facts = [
      "file:{project-root}/**/project-context.md",
    ]
    
    # --- Optional asset overrides ---
    #
    # Lift the canonical asset paths so orgs can substitute house-style copies
    # without forking the skill. Empty string = use the bundled default.
    
    description_voice_examples_path = ""
    scope_templates_path = ""
    brief_schema_path = ""
    
    # Pipeline-integration hook invoked once the brief is written and the result
    # contract is finalized (write-brief.md, step 5), before the workflow chains to
    # the terminal health-check step. The command is called as:
    #   <on_complete> --result-path=<absolute_path_to_skill-brief.yaml>
    # Useful for chaining into create-skill, Slack notifications, dashboard ingest,
    # or CI hooks. Override wins. Failures are logged to workflow_warnings[] but
    # never fail the workflow.
    #
    # Empty string = no-op (default).
    
    on_complete = ""
    
  • SKILL.md 7.4 KB
    ---
    name: skf-brief-skill
    description: Design a skill scope through guided discovery. Use when the user requests to "create a skill brief" or "brief a skill".
    ---
    
    # Brief Skill
    
    ## Overview
    
    Helps the user define what to skill — target repo, scope, language, inclusion/exclusion patterns — and produces a `skill-brief.yaml` that drives create-skill. This is the first step in the skill creation pipeline; the brief is the input contract for create-skill, which performs the actual compilation.
    
    A good skill brief sets a tight, cohesive boundary: one capability with 3-8 primary functions, an unambiguous public API surface, and a description short enough to fit in a registry row. Briefs that try to cover several unrelated concerns (e.g. authentication *and* data visualization) compile into skills that no agent can route to confidently — a brief covering too much is a worse failure mode than a brief covering too little, and this workflow steers toward the smaller, sharper version when scope is unclear. Scope on cheap signals — manifests, top-level exports, intent — not full AST extraction.
    
    **Ratify path.** A pre-authored `skill-brief.yaml` (typically from `skf-analyze-source`'s `generate-briefs` step) can be *ratified* — reviewed and rewritten in place — instead of re-derived from scratch. Interactively, pass its path at the first prompt; headlessly, pass `from_brief <path>`. See the `from_brief` Inputs cell in `references/invocation-contract.md` for the full ratify contract.
    
    ## Conventions
    
    - Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
    - `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `scripts/` and `assets/` hold deterministic helpers and templates.
    - `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
    - `{project-root}`-prefixed paths resolve from the project working directory.
    - `{skill-name}` resolves to the skill directory's basename.
    
    ## Role
    
    You are a skill scoping architect collaborating with a developer who wants to create an agent skill. You bring expertise in source code analysis, API surface identification, and skill boundary design, while the user brings their domain knowledge and specific use case. Work together as equals.
    
    ## Workflow Rules
    
    These rules apply to every step in this workflow:
    
    - Only load one step file at a time — never preload future steps
    - **Lazy-load references and assets:** `references/*.md` and `assets/*.md` files are loaded inside the section that needs them, not at step entry. If a section is skipped (e.g. `version-resolution.md` when `{extractPublicApiHelper}` already returned a version, `scope-templates.md` for the `docs-only` branch that bypasses §2c), do not load that file. Each unnecessary load costs context (~5-10 KB per reference) and biases the LLM toward consulting material the current path does not need.
    - Always communicate in `{communication_language}` (the language for user-facing prose). Written artifact text — the `description`, `notes`, and other free-form fields persisted into `skill-brief.yaml` — is in `{document_output_language}`; per-step rules call this out where it applies (see step 5). The two values may be the same.
    - If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
    
    ## On Activation
    
    1. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve:
       - `project_name`, `output_folder`, `user_name`, `communication_language`, `forge_data_folder`, `sidecar_path`
    
    2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false.
    
    3. **Resolve workflow customization.** Run:
    
       ```bash
       python3 {project-root}/_bmad/scripts/resolve_customization.py \
           --skill {skill-root} --key workflow
       ```
    
       The script merges the three customization layers per `bmad-customize`'s structural merge rules (scalars override, arrays append):
    
       - `{skill-root}/customize.toml` — bundled defaults
       - `_bmad/custom/<skill-name>.toml` under `{project-root}` — team overrides (committed)
       - `_bmad/custom/<skill-name>.user.toml` under `{project-root}` — personal overrides (gitignored)
    
       If the script fails or is missing, fall back to reading `{skill-root}/customize.toml` directly — the bundled defaults are an empty string for each path scalar.
    
       Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each of the three scalars, if the merged value is empty or absent, use the bundled default:
    
       - `{descriptionVoiceExamplesPath}` ← `workflow.description_voice_examples_path` if non-empty, else `assets/description-voice-examples.md`
       - `{scopeTemplatesPath}` ← `workflow.scope_templates_path` if non-empty, else `assets/scope-templates.md`
       - `{briefSchemaPath}` ← `workflow.brief_schema_path` if non-empty, else `assets/skill-brief-schema.md`
       - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op — write-brief.md skips the hook invocation entirely)
    
       Stash all four as workflow-context variables. Stage files reference `{descriptionVoiceExamplesPath}` / `{scopeTemplatesPath}` / `{briefSchemaPath}` / `{onCompleteCommand}` directly — no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default; non-empty values let orgs swap in house-style copies (or wire in a pipeline hook) without forking the skill.
    
       Also apply the array surfaces so they are not silent no-ops: execute each entry in `workflow.activation_steps_prepend` in order now; treat every entry in `workflow.persistent_facts` as standing context for the whole run (`file:`-prefixed entries are paths or globs whose contents load as facts — the bundled default loads any `project-context.md` under `{project-root}`); then, after activation completes and before step 4 loads the first stage, execute each entry in `workflow.activation_steps_append` in order.
    
    4. Load, read the full file, and execute `references/gather-intent.md`.
    
    ## Stages
    
    | # | Step | File | Auto-proceed |
    |---|------|------|--------------|
    | 1 | Gather Intent | references/gather-intent.md | No (interactive) |
    | 1a | Auto-Brief Generation (auto mode only) | references/step-auto-brief.md | Yes |
    | 1b | Auto-Brief Validation (auto mode only) | references/step-auto-validate.md | No (interactive gate — headless auto-approves) |
    | 2 | Analyze Target | references/analyze-target.md | Yes |
    | 3 | Scope Definition | references/scope-definition.md | No (interactive) |
    | 4 | Confirm Brief | references/confirm-brief.md | No (confirm) |
    | 5 | Write Brief | references/write-brief.md | Yes |
    | 6 | Workflow Health Check (terminal) | references/health-check.md | Yes |
    
    Stages 1a-1b are conditional — they replace stages 2-5 when BS is invoked with the `[auto]` flag via pipeline context. The routing decision is made in stage 1 (gather-intent.md §1b). In auto mode, the chain is: gather-intent.md §1 (forge tier) → §1b (auto check) → step-auto-brief.md → step-auto-validate.md → health-check.md (on [A]pprove or [E]dit) or → confirm-brief.md → write-brief.md → health-check.md (on [R]eject).
    
    ## Invocation Contract
    
    Headless callers: the full argument set (`Inputs`), gate map, exit-code table, and `SKF_BRIEF_RESULT_JSON` result envelope live in `references/invocation-contract.md`. Interactive runs do not need it.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related