Claude Skill

skf-setup

Initialize forge environment, detect tools, and set capability tier (Quick/Forge/Forge+/Deep). Use when the user requests to "set up" or "initialize the forge".

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-setup-492e73e.zip · 22 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-setup
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

Setup Forge

Overview

Initializes the forge environment by detecting available tools, determining the capability tier (Quick/Forge/Forge+/Deep), and writing persistent configuration to {project-root}/_bmad/_memory/forger-sidecar/. When ccc (cocoindex-code) is available, also augments .cocoindex_code/settings.yml with SKF exclusion patterns and creates or refreshes the project's semantic-search index. On Deep tier, reconciles the QMD collection registry; whenever ccc is available, reconciles the CCC index registry as well.

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.
  • Deterministic work is delegated to shared Python helpers under src/shared/scripts/ (installed as _bmad/skf/shared/scripts/). Each step's frontmatter declares a *ProbeOrder array for the helpers it needs: run the first path in the array that exists, and halt if none resolve — the script owns that logic, with no prose fallback.
  • {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 system executor performing environment resolution. Run each step in sequence, write configuration files, and report results at completion.

Workflow Rules

  • Only load one step file at a time — never preload future steps.
  • Communicate in {communication_language}.
  • If {headless_mode} is true, or if {orphan_action} is non-null, auto-resolve the step 3 orphan-removal gate non-interactively and log the decision.

Stages

# Step File
1 Detect Tools & Set Tier references/detect-and-tier.md
1b CCC Index (only when ccc is available) references/ccc-index.md
2 Write Config references/write-config.md
3 QMD + CCC Registry Hygiene references/auto-index.md
4 Report references/report.md
5 Workflow Health Check references/health-check.md

Invocation Contract

Aspect Detail
Inputs (none)
Flags --headless / -H (skip prompts, auto-resolve gates to defaults); --require-tier=<Quick\|Forge\|Forge+\|Deep> (halt with failure if calculated tier does not satisfy the requirement); --orphan-action=<keep\|remove> (resolve the orphan-removal gate non-interactively, even outside --headless); --ccc-skip-index (skip CCC indexing; envelope ccc_index.status becomes "skipped" — the fast re-probe lane for an expert re-running only to refresh the detected tier without paying the full ccc re-index cost); --quiet (suppress the human-readable FORGE STATUS banner — pipelines and expert re-runners get the envelope only)
Gates One optional: orphaned QMD collection removal (step 3, Deep tier only; default: Keep, or whatever --orphan-action set)
Outputs forger-sidecar/forge-tier.yaml, forger-sidecar/preferences.yaml, {forge_data_folder}/; when ccc is available, .cocoindex_code/settings.yml (exclusion patterns merged) and the project ccc index
Headless All gates auto-resolve with default action when {headless_mode} is true. Under --headless or --quiet, step 4 emits a single-line SKF_SETUP_RESULT_JSON: {…} envelope as the only stdout line — the FORGE STATUS banner is suppressed. Branch on the envelope's top-level status field: success, tier_failure (require-tier miss), or blocked (any pre-report halt — the error.phase names the cause). skf-emit-result-envelope.py derives status from the payload, so pipelines never compose it from require_tier_satisfied + error. Schema in references/report.md §4.
Failure modes --require-tier not satisfied → status tier_failure, the envelope sets "require_tier_satisfied": false, and the workflow halts before step 5 (interactive runs also print a "REQUIRED TIER NOT MET" block). A write failure (forge-tier.yaml or preferences.yaml could not be written) halts step 2 with a blocked envelope whose error.phase (step 2:write-tools or step 2:init-prefs) and error name the path and reason.

On Activation

Halt contract for headless/quiet runs. Any halt below must first emit a blocked envelope when {headless_mode} or {quiet_mode} is true, before printing the human diagnostic — a pipeline observer that sees no envelope treats the run as not-completed-cleanly. Pipe {"phase":"<phase>","reason":"<reason>","path":"<path>"} to python3 <helper> emit-blocked where <helper> is the first existing path of {project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py then {project-root}/src/shared/scripts/skf-emit-result-envelope.py. The emit-blocked subcommand declares zero dependencies (no uv, no pyyaml), so it works even when uv itself is the thing that's missing.

  1. Parse invocation flags first (so every halt below knows whether to emit an envelope): {headless_mode} (true on --headless / -H), {require_tier} (--require-tier=<Quick|Forge|Forge+|Deep>, case-sensitive; null if absent or unparseable), {orphan_action} (--orphan-action=<keep|remove>; null if absent), {ccc_skip_index} (true on --ccc-skip-index), {quiet_mode} (true on --quiet).

  2. Probe uv runtime. Run uv --version. Every step invokes shared Python helpers via uv run (PEP 723 inline metadata auto-resolves pyyaml). If uv is missing, halt with phase on-activation:uv-missing and the human diagnostic:

    "Setup cannot proceed: uv is not installed. SKF helpers depend on uv to auto-resolve their Python dependencies. Install it from https://docs.astral.sh/uv/getting-started/installation/ and re-run /skf-setup."

  3. Load config from {project-root}/_bmad/skf/config.yaml and resolve project_name, output_folder, user_name, communication_language, document_output_language, skills_output_folder, forge_data_folder, sidecar_path. Halt with phase on-activation:config-missing if the file does not exist, or on-activation:config-malformed if the YAML is invalid, with the matching human diagnostic.

  4. Reconcile {headless_mode} with preferences.yaml: OR the parsed flag with headless_mode: true from the YAML.

  5. 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), and _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.

    Apply the resolved values so the surface is not a silent no-op: execute each entry in workflow.activation_steps_prepend in order now (org-wide pre-flight checks such as auth, network, or compliance); 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); stash {onCompleteCommand} ← workflow.on_complete (empty string = no-op) for references/report.md §5 to invoke at the terminal stage. After activation completes, execute each entry in workflow.activation_steps_append in order, before the first stage runs.

  6. Execute references/detect-and-tier.md.

Files (bmad-module-skill-forge)
  • references
    • auto-index.md 8.6 KB
      ---
      nextStepFile: 'report.md'
      # `{qmdClassifyHelper}` and `{forgeTierRwHelper}` = first existing path in
      # their `*ProbeOrder` arrays; halt if neither exists for a helper a section
      # actually invokes. Both scripts own classification / registry-cleanup
      # contracts with no prose fallback.
      qmdClassifyProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-qmd-classify-collections.py'
        - '{project-root}/src/shared/scripts/skf-qmd-classify-collections.py'
      forgeTierRwProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
        - '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
      ---
      
      <!-- Config: communicate in {communication_language}. The orphan-removal prompt and the headless-resolution log message render in the user's language. -->
      
      # Step 3: QMD + CCC Registry Hygiene
      
      ## STEP GOAL:
      
      When the detected tier is Deep, classify live QMD collections against the `qmd_collections` registry and prompt the user before removing orphans. Whenever ccc is available (Forge+ or Deep), prune `ccc_index_registry` entries whose source paths no longer exist. All set-arithmetic and YAML mutation goes through scripts (`{qmdClassifyHelper}` and `{forgeTierRwHelper}`); the workflow only orchestrates external CLI calls (`qmd collection list`, `qmd collection remove`) and the user-prompt branch.
      
      For Quick and Forge tiers, skip silently and proceed (QMD is not available; ccc registry cleanup only runs when ccc is available regardless of tier).
      
      ## Rules
      
      - Focus only on registry hygiene — no new collection creation (that belongs to create-skill)
      - Never reimplement the forge-namespace suffix filter in prose — the classifier owns it
      - Never silently delete collections — always prompt before `qmd collection remove`
      - Headless runs must auto-resolve the orphan prompt to the documented default (Keep)
      - Do not fail the workflow if hygiene encounters errors
      
      ## MANDATORY SEQUENCE
      
      ### 1. Check Tier
      
      Read `{calculated_tier}` and `{ccc}` from context (set by step 1).
      
      Default `{orphan_auto_resolution}` to null at the top of this step; only the non-interactive branch in section 3 overrides it.
      
      **If `{calculated_tier}` is Quick or Forge AND `{ccc}` is false:** No registry hygiene needed. Set `{hygiene_result: "skipped", hygiene_healthy: 0, hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0, hygiene_stale_cleaned: 0, ccc_registry_stale_cleaned: 0, ccc_registry_stale_removed_paths: []}`. Proceed directly to section 5 (Auto-Proceed) — no output, no messaging.
      
      **If `{calculated_tier}` is Quick or Forge AND `{ccc}` is true:** No QMD work, but ccc registry needs pruning. Set QMD-related flags to defaults (`hygiene_result: "skipped"`, all hygiene_* counts = 0). Skip directly to section 4 (Stale Registry Cleanup), running it with the ccc-prune flag only.
      
      **If `{calculated_tier}` is Forge+:** Same as Quick/Forge with ccc — no QMD work (qmd unavailable at Forge+), but ccc registry hygiene runs.
      
      **If `{calculated_tier}` is Deep:** Continue to section 2.
      
      ### 2. Classify Live QMD Collections vs Registry
      
      Run the classifier — it owns the `qmd collection list` invocation and stdout parsing. Invoke via `uv run`:
      
      ```bash
      uv run {qmdClassifyHelper} \
          --registry-from-yaml "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"
      ```
      
      The script (see `src/shared/scripts/skf-qmd-classify-collections.py` docstring for the full schema) invokes `qmd collection list` itself, applies the forge-namespace suffix filter (`-brief | -temporal | -docs | -extraction`) before classifying, and exits non-zero with an error message on stderr if the daemon is down. Collections owned by unrelated tools are silently excluded from the orphan / healthy / stale sets and counted under `foreign_filtered_count` for telemetry only — foreign collections never enter any classification that could lead to data loss.
      
      **Error handling:** If the script exits non-zero, set `{hygiene_result: "qmd_unavailable", hygiene_healthy: 0, hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0, hygiene_stale_cleaned: 0}` and skip directly to section 4 (which will still run the ccc-prune branch if `{ccc}` is true).
      
      **Parse the JSON output and set context flags:**
      
      - `{hygiene_healthy}` ← `len(healthy)`
      - `{orphaned_collections}` ← `orphaned` (the list — used in section 3)
      - `{stale_collections}` ← `stale` (the list — used in section 4)
      - `{live_collections}` ← comma-join of `live_names` (used in §4's clean-stale invocation; the script owns the raw set, the prompt only forwards it)
      - `{foreign_filtered_count}` ← `foreign_filtered_count`
      
      Set `{hygiene_result: "completed"}`.
      
      ### 3. Handle Orphaned Collections
      
      **If `{orphaned_collections}` is empty:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0}` and skip to section 4.
      
      **Non-interactive resolution.** If `{orphan_action}` is non-null, resolve the gate without prompting using that value: log `"Auto-decision (--orphan-action={value}): kept|removed {len(orphaned_collections)} orphaned forge collection(s)"` and set `{orphan_auto_resolution: {action: "{orphan_action}", count: len(orphaned_collections), source: "orphan-action-flag"}}` so step 4 can fold it into the envelope warnings (the audit trail matters most when `remove` deletes collections headlessly). If `{orphan_action}` is `"keep"`, set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}` and skip to section 4. If `"remove"`, fall through to the removal block below (still no user prompt). Independently, if `{headless_mode}` is true and `{orphan_action}` is null, auto-resolve to the default **Keep** with the equivalent log line, set `{orphan_auto_resolution: {action: "keep", count: len(orphaned_collections), source: "headless-default"}}`, and skip to section 4. The two paths compose: `--orphan-action` overrides the headless default; `--headless` alone falls back to the default Keep. (On an interactive resolution — section 3's prompt below — leave `{orphan_auto_resolution}` null; the human chose, so there is no auto-decision to audit.)
      
      **If `{headless_mode}` is false**, display to the user:
      
      "**QMD Hygiene: Found {count} orphaned collection(s) not tracked in the forge registry:**
      
      {list orphaned collection names}
      
      These collections exist in QMD but are not managed by any skill workflow. They may be from a previous auto-index run or manual creation.
      
      **[R]emove** orphaned collections — clean up QMD
      **[K]eep** orphaned collections — leave them as-is (default)"
      
      **If user selects R (Remove):** For each name in `{orphaned_collections}`:
      
      ```bash
      qmd collection remove <name>
      ```
      
      Track the count of successful removals as `{hygiene_orphaned_removed}`. Set `{hygiene_orphaned_kept: 0}`.
      
      **If user selects K (Keep) or no orphans:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}`.
      
      ### 4. Stale Registry Cleanup
      
      This section runs whenever reachable — it handles both `qmd_collections` stale entries (Deep tier) and `ccc_index_registry` stale entries (whenever ccc is true). The script's flags are mutually independent.
      
      Build the invocation. Always include `--target` for the forge-tier.yaml path. Include `--qmd-live-names "{live_collections}"` ONLY when section 2 ran successfully (i.e. `{hygiene_result}` is `"completed"`); omit the flag entirely otherwise so the script skips QMD cleanup. Include `--prune-missing-ccc-paths` ONLY when `{ccc}` is true; omit it otherwise.
      
      ```bash
      uv run {forgeTierRwHelper} clean-stale \
          --target "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml" \
          [--qmd-live-names "{live_collections}"]  \
          [--prune-missing-ccc-paths]
      ```
      
      The script reads the registry, computes set-difference operations (qmd: registry − live; ccc: filter where `path` does not exist on disk), and atomically rewrites forge-tier.yaml only when something actually changed (mtime preserved on idempotent re-runs). The CI ephemeral-mount caveat for ccc-registry pruning is logged in the script's warning message.
      
      **Parse the JSON output and set context flags for step 4:**
      
      - `{hygiene_stale_cleaned}` ← `len(qmd_removed)`
      - `{ccc_registry_stale_cleaned}` ← `len(ccc_removed)`
      - `{ccc_registry_stale_removed_paths}` ← `ccc_removed` (the list — step 4 folds individual paths into envelope warnings)
      
      If `{hygiene_stale_cleaned}` > 0, display: "**Cleaned {hygiene_stale_cleaned} stale QMD registry entry/entries** (collection no longer exists in QMD)."
      
      (The script already logs each ccc removal as a WARNING line; no additional display needed.)
      
      ### 5. Auto-Proceed
      
      After hygiene completes (or is skipped for non-Deep tiers without ccc), display "**Proceeding to forge status report...**", then load `{nextStepFile}`, read it fully, and execute it.
      
    • ccc-index.md 8.4 KB
      ---
      nextStepFile: 'write-config.md'
      # `{mergeCccExclusionsHelper}` = first existing path in
      # `{mergeCccExclusionsProbeOrder}`; halt if neither exists. The script owns
      # config-value validation and the set-union merge into
      # .cocoindex_code/settings.yml — no prose fallback.
      mergeCccExclusionsProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-merge-ccc-exclusions.py'
        - '{project-root}/src/shared/scripts/skf-merge-ccc-exclusions.py'
      ---
      
      <!-- Config: communicate in {communication_language}. User-visible status messages (CCC exclusion summary, indexing progress message) render in the user's language. -->
      
      # Step 1b: CCC Index Verification
      
      ## STEP GOAL:
      
      If ccc is available (`{ccc: true}` from step 1), invoke `{mergeCccExclusionsHelper}` to validate config-driven SKF exclusion patterns and merge them into `.cocoindex_code/settings.yml`, then verify the project's ccc index exists and create or refresh it if needed. Store index state and exclusion-merge results in context for step 2 to write into forge-tier.yaml and for step 4 to surface in the JSON envelope.
      
      For Quick and Forge tiers, or when ccc is unavailable, skip silently and proceed.
      
      ## Rules
      
      - Focus only on ccc index verification, exclusion-pattern merge, and (re-)indexing
      - Do not display skip messages for Quick/Forge tiers
      - Do not fail the workflow if ccc indexing fails
      - The script owns exclusion-pattern validation — do not reimplement it in prose
      - Every branch that leaves this step binds all four of `ccc_index_result`, `ccc_indexed_path`, `ccc_last_indexed`, and `ccc_file_count` — step 2 interpolates each bare into the `write-tools` JSON payload, so an unbound flag would emit its literal placeholder and fail the forge-tier.yaml write
      
      ## MANDATORY SEQUENCE
      
      ### 1. Check Eligibility
      
      Read `{ccc}` and `{ccc_skip_index}` from context.
      
      **If `{ccc}` is false:** Set `{ccc_index_result: "none", ccc_indexed_path: null, ccc_last_indexed: null, ccc_file_count: null, ccc_exclude_patterns: [], ccc_exclusion_warnings: [], settings_yml_written: false, settings_yml_patterns_added: 0}`. Proceed directly to section 5 (Auto-Proceed) — no output, no messaging.
      
      **If `{ccc}` is true AND `{ccc_skip_index}` is true:** Run the exclusion-merge (section 3) so settings.yml stays current, then set `{ccc_index_result: "skipped", ccc_indexed_path: null, ccc_last_indexed: null, ccc_file_count: null}` and proceed to section 5 (Auto-Proceed) — do not run `ccc init` or `ccc index`. The envelope's `ccc_index.status` will be `"skipped"` so pipelines that plan an out-of-band re-index can distinguish "the operator opted out" from "indexing failed".
      
      **If `{ccc}` is true AND `{ccc_skip_index}` is false:** Continue to section 2.
      
      ### 2. Check Existing Index State
      
      Consume prior CCC state from stage 1's detector output (`prior.*` context flags) — no YAML re-parse and no timestamp math here. The detector already read forge-tier.yaml and computed the freshness verdict `{ccc_index_fresh}` against `{project-root}` and the current time (same-project path match AND `"fresh"`/`"created"` status AND `last_indexed` within the staleness threshold, defaulting to 24 hours).
      
      Decide `{needs_reindex}` and `{ccc_index_result}` from `{ccc_index_fresh}`:
      
      - If `{ccc_index_fresh}` is true → index is fresh. Set `{needs_reindex: false}`, `{ccc_index_result: "fresh", ccc_indexed_path: {project-root}, ccc_last_indexed: {previous_ccc_last_indexed}, ccc_file_count: {previous_ccc_file_count}}` (the count carried over from the prior forge-tier.yaml by step 1 — nothing re-counts on this path). Exclusions still merge in section 3 (which may force a re-index).
      - If `{ccc_index_fresh}` is false (path mismatch, non-fresh status, stale timestamp, or any required prior CCC field null) → `{needs_reindex: true}`.
      
      ### 3. Merge SKF Exclusion Patterns
      
      SKF infrastructure and output directories must be excluded from the CCC index — they contain workflow instructions, build artifacts, and generated skills that pollute semantic search results with zero extraction value.
      
      Forward `skills_output_folder` and `forge_data_folder` from `{project-root}/_bmad/skf/config.yaml` **verbatim** — the script resolves `{project-root}/...` template strings and rejects absolute paths / placeholders / glob meta-chars internally. The step does no string surgery; that work moved into the helper. Invoke via `uv run`.
      
      ```bash
      uv run {mergeCccExclusionsHelper} \
          --project-root "{project-root}" \
          --skills-output-folder "{skills_output_folder}" \
          --forge-data-folder "{forge_data_folder}"
      ```
      
      The script (see `src/shared/scripts/skf-merge-ccc-exclusions.py` docstring for the full schema) builds the SKF exclusion list (4 always-include hardcoded patterns + 2 conditional from validated config), applies the validation rules to reject empty / absolute / glob-meta config values with actionable warnings, and performs an idempotent set-union merge into `{project-root}/.cocoindex_code/settings.yml`. User customizations are preserved. When the file does not exist yet (first-time setup before `ccc init`) the script creates it; when nothing new needs adding the script skips the write entirely (mtime preserved).
      
      **Parse the JSON output and set context flags:**
      
      - `{settings_yml_existed}` ← `settings_yml_existed`
      - `{settings_yml_written}` ← `written`
      - `{settings_yml_patterns_added}` ← `patterns_added`
      - `{ccc_exclude_patterns}` ← `effective_patterns` (the script returns the final, sorted, deduplicated SKF pattern set after validation — consume verbatim; do not re-derive in prose).
      - `{ccc_exclusion_warnings}` ← `warnings` (a list — step 4 folds them into the envelope's warnings array)
      
      **If `{settings_yml_written}` is true** (new patterns merged into settings.yml): set `{needs_reindex: true}` — new exclusions require re-indexing for the index to reflect them. Display: "**CCC exclusions configured:** {patterns_added} SKF patterns applied to .cocoindex_code/settings.yml"
      
      **If `{settings_yml_written}` is false** (idempotent re-run, all patterns already present): display nothing (exclusions already configured). Do NOT change `{needs_reindex}` — it stays at whatever section 2 set it to.
      
      **Flow decision:**
      
      - If `{needs_reindex}` is true: proceed to section 4
      - If `{needs_reindex}` is false: proceed to section 5 (Auto-Proceed)
      
      ### 4. Create or Refresh CCC Index
      
      **If `{ccc_daemon}` is `"stopped"` or `"healthy"`:** the `ccc index` command auto-starts the daemon when needed.
      
      **If `{ccc_daemon}` is `"error"`:** attempt indexing anyway — errors will be caught below.
      
      Run (CWD must be `{project-root}`):
      
      ```bash
      ccc init
      ```
      
      **If init fails** (project may already be initialized): continue — this is not an error. Note: when `{settings_yml_existed}` was false and `ccc init` just created the settings.yml, the merge in section 3 ran BEFORE ccc init. The script handles "no settings.yml exists" by creating one with just the SKF exclusions, which `ccc init` will then either preserve (if it merges) or overwrite (in which case the next workflow run re-merges). Either way the SKF exclusions end up in the file by the time `ccc index` runs the second time.
      
      Before invoking `ccc index`, display: "**Building semantic index — this can take several minutes on large codebases (1000+ files). Run `ccc status` in another terminal to monitor progress.**" so the user does not assume the workflow has hung during the long-running call.
      
      Then run:
      
      ```bash
      ccc index
      ```
      
      **Note:** `ccc index` can take several minutes on large codebases (1000+ files). Run with an extended timeout or in background mode. Use `ccc status` to verify completion — check that `Chunks` and `Files` counts are non-zero.
      
      **If succeeds:**
      
      - Run `ccc status` to get file count
      - Store `{ccc_index_result: "created", ccc_indexed_path: {project-root}, ccc_last_indexed: {current ISO timestamp}, ccc_file_count: {count from ccc status}}`
      - Display: "**CCC index created.** {ccc_file_count} files indexed for semantic discovery."
      
      **If fails:**
      
      - Store `{ccc_index_result: "failed", ccc_indexed_path: null, ccc_last_indexed: null, ccc_file_count: null, ccc_indexing_failed_reason: {error}}` (the failed-reason flag flows into step 4's envelope warnings)
      - Display: "CCC indexing failed: {error}. Extraction will use direct AST scanning — semantic pre-ranking unavailable this session."
      - Continue — this is not a workflow error
      
      ### 5. Auto-Proceed
      
      After ccc index verification is complete (or skipped because ccc is unavailable), display "**Proceeding to write configuration...**", then load `{nextStepFile}`, read it fully, and execute it.
      
    • detect-and-tier.md 8.2 KB
      ---
      nextStepFile: 'ccc-index.md'
      # `{detectToolsHelper}` = first existing path in `{detectToolsProbeOrder}`;
      # halt if neither exists. This script is the source of truth for tool
      # detection and tier calculation — no prose-driven probes.
      detectToolsProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-detect-tools.py'
        - '{project-root}/src/shared/scripts/skf-detect-tools.py'
      ---
      
      <!-- Config: communicate in {communication_language}. The first-run preamble below is user-visible — render it in the user's language. -->
      
      # Step 1: Detect Tools and Determine Tier
      
      ## STEP GOAL:
      
      Verify availability of the four forge tools (ast-grep, gh, qmd, ccc), read any existing configuration for re-run comparison, check for tier override, and calculate the capability tier — all via `{detectToolsHelper}` so the deterministic work is done once, by a tested script, never by the LLM.
      
      ## Rules
      
      - Focus only on tool detection and tier calculation — do not write any files (Step 02)
      - Never reimplement tool probes or the tier rules in prose — the script is authoritative
      - Tool command failures are not errors — they indicate unavailability (the script swallows them)
      
      ## MANDATORY SEQUENCE
      
      ### 1. Check for Existing Configuration (Re-run Detection)
      
      Prior state lives in two files; the detector helper (§2) reads forge-tier.yaml itself when invoked with `--prior-state-from`, so this section only handles preferences.yaml directly and the first-run preamble.
      
      **Read existing preferences.yaml** at `{project-root}/_bmad/_memory/forger-sidecar/preferences.yaml`:
      
      - If exists: check for `tier_override` value
      - If not found: set `{tier_override}` to null
      
      **First-run preamble** — when the prior-state read (§2 below, `prior.previous_tier`) returns null AND `{headless_mode}` is `false`, display this preamble before continuing so the user knows what is about to happen and can abort cleanly with Esc / Ctrl+C before any writes. (When `{headless_mode}` is true, skip the preamble entirely — pipelines never need it.)
      
      "**About to set up the forge.** This workflow will:
      
      - Detect available tools (ast-grep, gh, qmd, ccc) — read-only probes only
      - Write `{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml` (capability tier + tool state)
      - Write `{project-root}/_bmad/_memory/forger-sidecar/preferences.yaml` (first-run defaults)
      - Create `{forge_data_folder}/` if missing
      - When ccc is available: augment `{project-root}/.cocoindex_code/settings.yml` with SKF exclusion patterns, then create or refresh the project ccc index
      
      **About tiers:** SKF picks one of four tiers (Quick / Forge / Forge+ / Deep) based on which tools are installed. **All four are fully usable** — higher tiers add power, they don't fix gaps. If you're new and only have a base Python install, Quick tier is the right starting point and the report at the end will show you exactly which tools to install if you want to climb later.
      
      Press Esc or Ctrl+C now if this isn't the right project — no files have been written yet."
      
      **Re-run notice** — when the prior-state read (§2 below, `prior.previous_tier`) returns a non-null value AND `{headless_mode}` is `false`, display this notice instead of the first-run preamble, before continuing, so a user who re-ran in the wrong/sibling repo can abort before any config rewrite or re-index. (When `{headless_mode}` is true, skip it — pipelines re-run intentionally.)
      
      "**Forge already set up here:** {previous_tier} tier, detected {previous_detection_date}. Re-running re-probes the tools and refreshes config/index in this project. To refresh the tier without paying the ccc re-index cost, re-run with `--ccc-skip-index`. Press Esc or Ctrl+C now if this isn't the project you meant — nothing has been rewritten yet."
      
      ### 2. Run Detection Helper
      
      Build the Bash invocation: `uv run {detectToolsHelper} --project-root "{project-root}" --prior-state-from "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"`. If `{tier_override}` is non-null, append `--tier-override "{tier_override}"`. If `{require_tier}` is non-null, append `--require-tier "{require_tier}"`. Then execute. (`--project-root` lets the script compute the CCC-index freshness verdict — `prior.ccc_index_fresh` — so step 1b branches on a boolean instead of doing timestamp math.)
      
      The script (see `src/shared/scripts/skf-detect-tools.py` docstring for the full `DETECT_OUTPUT_SCHEMA`) probes ast-grep / gh / qmd / ccc concurrently with two-step verification for qmd and ccc (binary-identity check + daemon-health check, including the `CocoIndex Code` identity-marker substring check that rejects PATH-shadowing aliases). It applies the 4-rule tier table, performs the tier-override sanity check (override is honored but flagged unsafe when underlying tools are missing), and evaluates `--require-tier` using a tool-prerequisite check (Deep does not subsume Forge+ — Deep does not require ccc). Output is one JSON document on stdout.
      
      ### 3. Parse Output and Set Context Flags
      
      From the JSON, set these context flags. Field paths are relative to the script's top-level object.
      
      From `tools`:
      
      - `{ast_grep}` ← `tools.ast_grep.available`
      - `{ast_grep_version}` ← `tools.ast_grep.version`
      - `{gh_cli}` ← `tools.gh_cli.available`
      - `{gh_cli_version}` ← `tools.gh_cli.version`
      - `{qmd}` ← `tools.qmd.available`
      - `{qmd_status}` ← `tools.qmd.status` (`"absent" | "daemon_stopped" | "healthy"` — drives the climb-hint distinction in step 4)
      - `{qmd_version}` ← `tools.qmd.version`
      - `{ccc}` ← `tools.ccc.available`
      - `{ccc_daemon}` ← `tools.ccc.daemon` (`"healthy" | "stopped" | "error" | null`)
      - `{ccc_version}` ← `tools.ccc.version`
      - `{security_scan}` ← `tools.security_scan.available` (informational only — never affects tier)
      
      From `tier`:
      
      - `{calculated_tier}` ← `tier.calculated` — the tier downstream steps act on
      - `{detected_tier}` ← `tier.detected` — what would have been chosen without override
      - `{tier_override_active}` ← `tier.override_applied`
      - `{tier_override_invalid}` ← `tier.override_invalid`
      - `{tier_override_invalid_value}` ← `tier.override_invalid_value`
      - `{tier_override_invalid_suggestion}` ← `tier.override_invalid_suggestion` (closest valid tier name from a fuzzy match — `null` when no candidate cleared the cutoff or when override is valid; consumed by step 4's invalid-override note as a "did you mean ...?" hint)
      - `{tier_override_unsafe}` ← `tier.override_unsafe`
      - `{tier_override_unsafe_missing}` ← `tier.override_unsafe_missing` (a list — step 4 joins with `", "` for display)
      
      From `require_tier`:
      
      - `{require_tier_satisfied}` ← `require_tier.satisfied` (`true | false | null`; null when `--require-tier` was not set)
      - `{require_tier_failure_missing_tools}` ← `require_tier.missing_tools` (a list)
      
      From `prior` (populated by `--prior-state-from`; all fields null/empty on first run):
      
      - `{previous_tier}` ← `prior.previous_tier`
      - `{previous_detection_date}` ← `prior.previous_detection_date`
      - `{previous_tools}` ← `prior.previous_tools`
      - `{previous_ccc_index_status}` ← `prior.previous_ccc_index_status`
      - `{previous_ccc_indexed_path}` ← `prior.previous_ccc_indexed_path`
      - `{previous_ccc_last_indexed}` ← `prior.previous_ccc_last_indexed`
      - `{previous_ccc_staleness_threshold_hours}` ← `prior.previous_ccc_staleness_threshold_hours`
      - `{previous_ccc_file_count}` ← `prior.previous_ccc_file_count` (integer or null; step 1b carries it forward on the fresh-index path so `forge-tier.yaml` keeps its `file_count` across re-runs that do not re-index)
      - `{ccc_index_fresh}` ← `prior.ccc_index_fresh` (boolean; the script's deterministic freshness verdict — prior index covers this project AND status was fresh/created AND `last_indexed` is within the staleness threshold of now. Step 1b branches on this directly instead of doing timestamp math.)
      
      From `deltas` (computed by the script from current tools + prior; eliminates LLM-side set arithmetic in the report banner):
      
      - `{tools_added}` ← `deltas.tools_added` (the list)
      - `{tools_removed}` ← `deltas.tools_removed` (the list)
      - `{tier_changed}` ← `deltas.tier_changed` (boolean)
      
      ### 4. Auto-Proceed
      
      After context flags are populated, display "**Proceeding to CCC index check...**", then load `{nextStepFile}`, read it fully, and execute it.
      
    • health-check.md 1017 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}. This is a delegation-only step (no user-visible output of its own); shared/health-check.md inherits the language directive on load. -->
      
      # Step 5: Workflow Health Check
      
      ## STEP GOAL:
      
      Chain to the shared workflow self-improvement health check at `{nextStepFile}`. This is the terminal step of setup — after the shared health check completes, the workflow is fully done.
      
      ## Rules
      
      - No user-facing reports, file writes, or result contracts in this step — those belong in step 4
      - Delegate directly to `{nextStepFile}` with no additional commentary
      - Do not attempt any other action between loading this step and executing `{nextStepFile}`
      
      ## MANDATORY SEQUENCE
      
      Load `{nextStepFile}`, read it fully, then execute it.
      
    • report.md 12.6 KB
      ---
      tierRulesData: 'references/tier-rules.md'
      nextStepFile: 'health-check.md'
      # `{emitEnvelopeHelper}` = first existing path in `{emitEnvelopeProbeOrder}`;
      # halt if neither exists when the headless envelope must be emitted. The script
      # is the source of truth for the SKF_SETUP_RESULT_JSON contract — do not
      # inline-render the envelope from prose (LLM schema drift is the bug this
      # script exists to prevent).
      emitEnvelopeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
        - '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
      ---
      
      <!-- Config: communicate in {communication_language}; emit user-visible report text (FORGE STATUS banner, climb hint, REQUIRED TIER NOT MET block, breadcrumb) in {document_output_language}. The JSON envelope from section 4 is a machine contract — its keys and enum values stay English regardless. -->
      
      # Step 4: Forge Status Report
      
      ## STEP GOAL:
      
      Display the forge status report with positive capability framing, surface tier changes and tool-set deltas on re-runs, prominently flag a required-tier miss, and (when headless) emit the schema-locked `SKF_SETUP_RESULT_JSON` envelope via `{emitEnvelopeHelper}`.
      
      ## Rules
      
      - Focus only on display + envelope emission
      - Do not use negative framing ("missing", "lacking", "unavailable")
      - Do not list tools that are not available
      - Use tier capability descriptions from tier-rules.md
      - Never inline-render the envelope JSON — the script owns the schema; drift breaks pipelines
      - Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing status report is not the terminal step
      
      ## MANDATORY SEQUENCE
      
      ### 1. Load Capability Descriptions
      
      Load and read {tierRulesData} for the tier capability descriptions and re-run messages. Needed by section 2.
      
      ### 2. Display Forge Status Report (skip when `{headless_mode}` or `{quiet_mode}` is true)
      
      **Format the report as follows:**
      
      ```
      ═══════════════════════════════════════
        FORGE STATUS
      ═══════════════════════════════════════
      
        Tier:  {calculated_tier}
        {tier capability description from tier-rules.md}
      
        Tools Detected:
        {for each tool that is available, show: tool name — version. ccc exposes no version string, so for ccc show its daemon health instead: ccc — daemon {ccc_daemon} (e.g. "ccc — daemon healthy")}
        {if no tools are available: (none yet — see "Climb to next tier" below)}
      
        {if calculated_tier is not Deep:}
        Climb to next tier:
        {if not tools.ast_grep: - Install ast-grep (https://ast-grep.github.io) — unlocks AST-backed code analysis (Forge tier)}
        {if tools.ast_grep and not tools.ccc: - Install cocoindex-code (https://github.com/cocoindex-io/cocoindex-code) — adds semantic-guided precision compilation (Forge+ tier)}
        {if tools.ast_grep and not tools.gh_cli: - Install GitHub CLI (https://cli.github.com) — required for Deep tier (cross-repository synthesis)}
        {if tools.ast_grep and not tools.qmd and qmd_status is "absent": - Install qmd (https://github.com/tobi/qmd) — required for Deep tier (knowledge search)}
        {if tools.ast_grep and not tools.qmd and qmd_status is "daemon_stopped": - Start the qmd daemon (already installed) — run `qmd start` (or your distribution's qmd service command) to unlock Deep tier (knowledge search)}
        {if tools.ccc and ccc_daemon is "error": - The ccc daemon is reporting errors — run `ccc doctor` to diagnose. CCC index will fail until resolved}
        {end if}
      
        {if hygiene_result is "completed":}
        QMD Registry:
        {hygiene_healthy} collection(s) healthy
        {if hygiene_orphaned_removed > 0: {hygiene_orphaned_removed} orphaned collection(s) removed}
        {if hygiene_orphaned_kept > 0: {hygiene_orphaned_kept} orphaned collection(s) kept}
        {if hygiene_stale_cleaned > 0: {hygiene_stale_cleaned} stale QMD registry entry/entries cleaned}
        {end if}
      
        {if ccc_registry_stale_cleaned > 0:}
        CCC Registry: {ccc_registry_stale_cleaned} stale entry/entries cleaned
        {end if}
      
        {if hygiene_result is "completed" and hygiene_healthy is 0:}
        QMD Registry: empty — collections are created automatically when you run /skf-create-skill.
        {end if}
      
        {if hygiene_result is "qmd_unavailable":}
        QMD Registry: skipped (qmd unavailable — if the daemon is stopped, `qmd start` restores it).
        {end if}
      
        {if tools.ccc is true:}
        CCC Index:
        {if ccc_index_result is "fresh": up to date — semantic discovery ready}
        {if ccc_index_result is "created": indexed this run — semantic discovery ready}
        {if ccc_index_result is "skipped": skipped (--ccc-skip-index) — run `/skf-setup` without --ccc-skip-index to build the index when you're ready}
        {if ccc_index_result is "failed": indexing failed — semantic discovery unavailable this session}
        {end if}
      
        Files written this run:
        - forge-tier.yaml — {project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml
        {if preferences_yaml_created is true:}
        - preferences.yaml — {project-root}/_bmad/_memory/forger-sidecar/preferences.yaml (first-run defaults)
        {end if}
        - {forge_data_folder}/ (directory ensured)
        {if settings_yml_written is true:}
        - .cocoindex_code/settings.yml — {project-root}/.cocoindex_code/settings.yml ({settings_yml_patterns_added} SKF exclusion pattern(s) merged)
        {end if}
        {if ccc_index_result is "created":}
        - .cocoindex_code/ ccc index — {ccc_file_count} files indexed
        {end if}
      
      {if tier_override is active:}
        Note: Tier override active (set in preferences.yaml)
      
      {if tier_override_invalid is true:}
        Note: tier_override value "{tier_override_invalid_value}" in preferences.yaml is not valid.
              {if tier_override_invalid_suggestion is non-null: Did you mean "{tier_override_invalid_suggestion}"?}
              Valid values are case-sensitive: Quick, Forge, Forge+, Deep. Using detected tier {calculated_tier}.
      
      {if tier_override_unsafe is true:}
        Warning: tier_override is forcing {calculated_tier} but the underlying tool prerequisites are not satisfied.
                 Missing: {tier_override_unsafe_missing}. The override is honored, but downstream skills that
                 rely on the missing tool(s) will fail at runtime. Install the missing tool(s) or remove
                 the override from preferences.yaml.
      
      {if {previous_tier} is null:}
        Initial detection — {calculated_tier} tier established.
      
      {if {tier_changed} is true:}
        {appropriate upgrade/downgrade message from tier-rules.md}
      
      {if {tier_changed} is false and {tools_added} is empty and {tools_removed} is empty and {previous_tier} is non-null:}
        {same-tier message from tier-rules.md}
        {if preferences_yaml_created is false and (ccc_index_result is "fresh" or ccc_index_result is "none" or ccc_index_result is "skipped"): Nothing changed — your preferences were left untouched and the index was already current. You're good.}
      
      {if {tier_changed} is false and ({tools_added} or {tools_removed} is non-empty) and {previous_tier} is non-null:}
        Tier unchanged: {calculated_tier}.
        {if {tools_added} non-empty:} Newly detected: {comma-separated tool names from tools_added}{if ccc was added and tier is Deep: " — ccc enhances Deep tier transparently."}
        {if {tools_removed} non-empty:} No longer detected: {comma-separated tool names from tools_removed} — re-install to restore those capabilities.
      
      ═══════════════════════════════════════
        Forge ready. {calculated_tier} tier active.
      ═══════════════════════════════════════
      
      {if {headless_mode} is false:}
        Next: the fastest start is `@Ferris forge-auto <repo-or-doc-url>` — one command auto-scopes, briefs, compiles, tests at a 90% quality gate, and exports a verified skill with zero configuration. Prefer to scope by hand? `/skf-brief-skill` scopes your first compilation target, or `/skf-quick-skill` is a fast template-driven path. Already have a skill? `/skf-audit-skill` drift-checks an existing skill against current sources.
      ```
      
      All re-run-delta context flags (`{tools_added}`, `{tools_removed}`, `{tier_changed}`) come from the detector's `deltas` block bound in stage 1 — no LLM-side recomputation, no set arithmetic in prose.
      
      ### 3. Display Required-Tier Failure Block (when applicable; skip when `{headless_mode}` or `{quiet_mode}` is true)
      
      If `{require_tier_satisfied}` is `false`, display this block immediately after the status report (the heading gate already handles the headless/quiet skip).
      
      When the block does fire (interactive run with require-tier failure):
      
      ```
      ═══════════════════════════════════════
        REQUIRED TIER NOT MET
      ═══════════════════════════════════════
      
        Required:  {require_tier}
        Detected:  {calculated_tier}
        Missing:   {require_tier_failure_missing_tools}
      
        Install the missing tool(s) and re-run, or relax `--require-tier`.
        See "Climb to next tier" above for install URLs.
      ═══════════════════════════════════════
      ```
      
      ### 4. Emit Headless JSON Envelope
      
      When `{headless_mode}` is `true` OR `{quiet_mode}` is `true`, build the context payload from this step's accumulated flags and forward it to `{emitEnvelopeHelper}` on stdin. Invoke via `uv run`. The script computes derived fields (`tools_added`, `tools_removed`, `tier_changed`, `warnings`), validates the assembled envelope against the JSON Schema at `src/shared/scripts/schemas/skf-setup-result-envelope.v1.json`, and emits the single prefixed line `SKF_SETUP_RESULT_JSON: {…}` on stdout.
      
      ```bash
      echo '{
        "tier": "{calculated_tier}",
        "previous_tier": {previous_tier_or_null},
        "tools": {tools_dict_from_step_01_or_skf_detect_tools_output},
        "previous_tools": {previous_tools_or_null},
        "config_path": "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml",
        "ccc_index": {
          "status": "{ccc_index_result}",
          "indexed_path": {ccc_indexed_path_or_null},
          "file_count": {ccc_file_count_or_null}
        },
        "files_written": {
          "forge-tier.yaml": true,
          "preferences.yaml": {preferences_yaml_created},
          "settings.yml": {settings_yml_written},
          "ccc_index": {ccc_index_result_was_created}
        },
        "tier_override_active": {tier_override_active},
        "tier_override_invalid": {tier_override_invalid},
        "tier_override_invalid_value": {tier_override_invalid_value_or_null},
        "tier_override_unsafe": {tier_override_unsafe},
        "tier_override_unsafe_missing": {tier_override_unsafe_missing_list},
        "require_tier_satisfied": {require_tier_satisfied_or_null},
        "require_tier_failure_missing": {require_tier_failure_missing_tools_list},
        "qmd_status": "{qmd_status}",
        "ccc_exclusion_warnings": {ccc_exclusion_warnings_list},
        "ccc_registry_stale_removed": {ccc_registry_stale_removed_paths_list},
        "ccc_indexing_failed_reason": {ccc_indexing_failed_reason_or_null},
        "orphan_auto_resolution": {orphan_auto_resolution_or_null},
        "error": {error_object_or_null}
      }' | uv run {emitEnvelopeHelper} emit
      ```
      
      The script's documented context-payload shape (see `src/shared/scripts/skf-emit-result-envelope.py` docstring) tolerates two `tools` shapes — bare booleans OR `skf-detect-tools.py`'s `{key: {available: bool, ...}}` output — so either step 1's normalized booleans OR the raw detect-tools output forwarded as-is will produce the correct envelope.
      
      **If the script exits non-zero:** the assembled envelope failed schema validation, which means a context flag from an earlier step is malformed. Surface the error to stderr and continue (missing JSON envelope on a headless run is a degraded but non-fatal state — the pipeline observer will see no envelope and treat that as "agent did not complete cleanly").
      
      ### 5. Chain to Health Check
      
      After the forge status report (and any failure block + JSON envelope) has been displayed:
      
      - If `{require_tier_satisfied}` is `false`, halt the workflow here without chaining to step 5. The tier miss is terminal; `{onCompleteCommand}` does not fire on a failed run.
      - Otherwise the forge is fully configured. If `{onCompleteCommand}` (resolved from `workflow.on_complete` at activation) is non-empty, execute it now — this is the workflow's terminal skill-specific action (e.g. trigger the first index build or notify an onboarding channel); in headless, log the action. Then load `{nextStepFile}`, read it fully, and execute it.
      
      The health-check step is the true terminal step on success — do not stop after the report on a passing run even though it reads as final. Step 5 in turn delegates to `shared/health-check.md`; after that returns, the setup workflow is fully done.
      
    • tier-rules.md 1.7 KB
      # Tier Report Copy
      
      The user-facing report strings the Step 4 report (`report.md`) consumes. Tool-detection probes and tier-calculation rules are owned by `skf-detect-tools.py` (see `references/detect-and-tier.md` §2) — this file holds only display copy, never detection or tier logic.
      
      ## Tier Capability Descriptions
      
      Use these for positive-framing in the report step. Describe what the tier GIVES, never what it lacks.
      
      ### Quick Tier
      "Quick tier active. You have fast, template-driven skill generation with package-name resolution. Perfect for getting started quickly."
      
      ### Forge Tier
      "Forge tier active. You have AST-backed structural code analysis with line-level citations, plus template-driven generation. Every skill instruction traces to verified source code."
      
      ### Forge+ Tier
      "Forge+ tier active. Semantic-guided precision compilation — cocoindex-code maps the codebase semantically before AST extraction runs. Every skill begins with a ranked discovery pass that surfaces the most relevant source regions, then AST-backed verification gives each export its line-level citation."
      
      ### Deep Tier
      "Deep tier active. Full capability unlocked — AST-backed code analysis, GitHub repository exploration, and QMD knowledge search with cross-repository synthesis. Maximum provenance and intelligence."
      
      ## Re-run Tier Change Messages
      
      ### Upgrade
      "Tier upgraded from {previous} to {current}. {newly available tool(s)} now detected — expanded capabilities unlocked."
      
      ### Downgrade
      "Tier changed from {previous} to {current}. {tool} no longer detected. Run the tool's installation to restore capabilities."
      
      ### Same
      "Tier unchanged: {current}. All previously detected tools confirmed."
      
    • write-config.md 5.2 KB
      ---
      nextStepFile: 'auto-index.md'
      # `{forgeTierRwHelper}` = first existing path in `{forgeTierRwProbeOrder}`;
      # halt if neither exists. The script owns the canonical forge-tier.yaml format
      # and the array-preservation contract that protects qmd_collections /
      # ccc_index_registry / staleness_threshold_hours from being lost on rewrite.
      # Do not fall back to inline YAML emission — a prose-rendered template drifts
      # from the script and silently corrupts downstream skills.
      forgeTierRwProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
        - '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
      # Resolve `{emitEnvelopeHelper}` for the headless/quiet blocked-envelope emit
      # on write failures (the regular envelope assembly happens in step 4, but
      # write halts never reach step 4 — see section 1's blocked-emit protocol).
      emitEnvelopeProbeOrder:
        - '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
        - '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
      ---
      
      <!-- Config: communicate in {communication_language}. Halt-on-write-failure messages render in the user's language. -->
      
      # Step 2: Write Configuration
      
      ## STEP GOAL:
      
      Write the detected tool availability and calculated tier to `forge-tier.yaml` (preserving registry arrays from any existing file), create `preferences.yaml` with first-run defaults if it does not exist, and ensure the `forge-data/` directory is present. All file mutations go through `{forgeTierRwHelper}` so the format is locked, atomic, and array-preservation is guaranteed.
      
      ## Rules
      
      - Focus only on writing configuration files and creating directories
      - Do not re-detect tools — use results from step 1
      - Never inline a YAML template for forge-tier.yaml or preferences.yaml — the script owns the canonical format
      - File write failures are errors — report clearly and halt the workflow
      
      ## MANDATORY SEQUENCE
      
      ### 1. Write forge-tier.yaml
      
      Build the JSON payload from context flags set by step 1 and step 1b. The payload must include `tools`, `tier`, and `ccc_index`; the script handles `tier_detected_at` defaulting to "now" if absent and preserves `qmd_collections`, `ccc_index_registry`, and a user-customized `ccc_index.staleness_threshold_hours` from any existing file.
      
      Invoke via `uv run`.
      
      ```bash
      echo '{
        "tools": {
          "ast_grep": {ast_grep},
          "gh_cli": {gh_cli},
          "qmd": {qmd},
          "ccc": {ccc},
          "ccc_daemon": {ccc_daemon},
          "security_scan": {security_scan}
        },
        "tier": "{calculated_tier}",
        "ccc_index": {
          "indexed_path": {ccc_indexed_path},
          "last_indexed": {ccc_last_indexed},
          "status": "{ccc_index_result}",
          "file_count": {ccc_file_count},
          "exclude_patterns": {ccc_exclude_patterns}
        }
      }' | uv run {forgeTierRwHelper} write-tools \
             --target "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"
      ```
      
      The script atomically writes the file via temp + fsync + rename (crash-safe) and returns a JSON response with `wrote`, `preserved_arrays.qmd_collections` count, `preserved_arrays.ccc_index_registry` count, and the resolved `tier`.
      
      **Parse the response and set context flags for step 4:**
      
      - `{forge_tier_yaml_path}` ← `wrote`
      - `{forge_tier_qmd_collections_count}` ← `preserved_arrays.qmd_collections`
      - `{forge_tier_ccc_registry_count}` ← `preserved_arrays.ccc_index_registry`
      
      **If the script exits non-zero**: parse the stderr JSON `{"status":"error","message":...}`, set `{error: {phase: "step 2:write-tools", path: "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml", reason: <message>}}`, and halt the workflow before chaining to step 3. When `{headless_mode}` or `{quiet_mode}` is true, first pipe the same `{phase, reason, path}` payload to `python3 {emitEnvelopeHelper} emit-blocked` so the pipeline sees the blocked envelope before the halt (see `references/report.md` frontmatter for the helper probe order; the subcommand declares zero dependencies).
      
      ### 2. Initialize preferences.yaml
      
      ```bash
      uv run {forgeTierRwHelper} init-prefs \
          --target "{project-root}/_bmad/_memory/forger-sidecar/preferences.yaml"
      ```
      
      The script creates the file with first-run defaults (`tier_override: ~`, `passive_context: true`, `headless_mode: false`, `compact_greeting: false`) if the file does not exist. When the file already exists, the script refuses to overwrite (preserves user customization) and reports `wrote: false`.
      
      **Parse the response and set context flags for step 4:**
      
      - `{preferences_yaml_created}` ← `wrote` (true on first run, false on re-run when the file pre-existed)
      
      **If the script exits non-zero**: same halt-and-blocked-envelope-emit pattern as section 1, with `phase: "step 2:init-prefs"` and the matching path.
      
      ### 3. Ensure forge-data/ Directory
      
      Run `mkdir -p {forge_data_folder}`. The `-p` flag is idempotent (creates parents, exits 0 if the directory already exists), so the prompt does no existence-check reasoning. On non-zero exit, halt with the same blocked-envelope-emit pattern as section 1, using `phase: "step 2:forge-data-dir"` and the matching path.
      
      ### 4. Auto-Proceed
      
      After forge-tier.yaml has been written successfully and preferences.yaml exists (created or pre-existing), display "**Proceeding to QMD collection hygiene...**", then load `{nextStepFile}`, read it fully, and execute it.
      
  • customize.toml 1.7 KB
    # DO NOT EDIT -- overwritten on every update.
    #
    # Workflow customization surface for skf-setup.
    # Team overrides:     _bmad/custom/skf-setup.toml (under {project-root})
    # Personal overrides: _bmad/custom/skf-setup.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 forge setup 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
    # (standards, compliance constraints, stylistic guardrails). Overrides
    # append.
    #
    # Each entry is either:
    #   - a literal sentence, e.g. "Forge writes must be reproducible in CI."
    #   - a file reference prefixed with `file:`, e.g.
    #     "file:{project-root}/docs/forge-policy.md" (globs supported; file
    #     contents are loaded and treated as facts).
    
    persistent_facts = []
    
    # Instruction run once the forge is fully configured — after the status report
    # (step 4) emits, before chaining to the shared health check. Override wins. Use
    # for a post-setup action an org wants after the forge is configured — e.g.
    # trigger the first index build or notify an onboarding channel. Empty string =
    # no terminal hook.
    
    on_complete = ""
    
  • SKILL.md 7.8 KB
    ---
    name: skf-setup
    description: Initialize forge environment, detect tools, and set capability tier (Quick/Forge/Forge+/Deep). Use when the user requests to "set up" or "initialize the forge".
    ---
    
    # Setup Forge
    
    ## Overview
    
    Initializes the forge environment by detecting available tools, determining the capability tier (Quick/Forge/Forge+/Deep), and writing persistent configuration to `{project-root}/_bmad/_memory/forger-sidecar/`. When `ccc` (cocoindex-code) is available, also augments `.cocoindex_code/settings.yml` with SKF exclusion patterns and creates or refreshes the project's semantic-search index. On Deep tier, reconciles the QMD collection registry; whenever ccc is available, reconciles the CCC index registry as well.
    
    ## 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.
    - Deterministic work is delegated to shared Python helpers under `src/shared/scripts/` (installed as `_bmad/skf/shared/scripts/`). Each step's frontmatter declares a `*ProbeOrder` array for the helpers it needs: run the first path in the array that exists, and halt if none resolve — the script owns that logic, with no prose fallback.
    - `{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 system executor performing environment resolution. Run each step in sequence, write configuration files, and report results at completion.
    
    ## Workflow Rules
    
    - Only load one step file at a time — never preload future steps.
    - Communicate in `{communication_language}`.
    - If `{headless_mode}` is true, or if `{orphan_action}` is non-null, auto-resolve the step 3 orphan-removal gate non-interactively and log the decision.
    
    ## Stages
    
    | # | Step | File |
    |---|------|------|
    | 1 | Detect Tools & Set Tier | references/detect-and-tier.md |
    | 1b | CCC Index (only when ccc is available) | references/ccc-index.md |
    | 2 | Write Config | references/write-config.md |
    | 3 | QMD + CCC Registry Hygiene | references/auto-index.md |
    | 4 | Report | references/report.md |
    | 5 | Workflow Health Check | references/health-check.md |
    
    ## Invocation Contract
    
    | Aspect | Detail |
    |--------|--------|
    | **Inputs** | (none) |
    | **Flags** | `--headless` / `-H` (skip prompts, auto-resolve gates to defaults); `--require-tier=<Quick\|Forge\|Forge+\|Deep>` (halt with failure if calculated tier does not satisfy the requirement); `--orphan-action=<keep\|remove>` (resolve the orphan-removal gate non-interactively, even outside `--headless`); `--ccc-skip-index` (skip CCC indexing; envelope `ccc_index.status` becomes `"skipped"` — the fast re-probe lane for an expert re-running only to refresh the detected tier without paying the full ccc re-index cost); `--quiet` (suppress the human-readable FORGE STATUS banner — pipelines and expert re-runners get the envelope only) |
    | **Gates** | One optional: orphaned QMD collection removal (step 3, Deep tier only; default: Keep, or whatever `--orphan-action` set) |
    | **Outputs** | `forger-sidecar/forge-tier.yaml`, `forger-sidecar/preferences.yaml`, `{forge_data_folder}/`; when ccc is available, `.cocoindex_code/settings.yml` (exclusion patterns merged) and the project ccc index |
    | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Under `--headless` or `--quiet`, step 4 emits a single-line `SKF_SETUP_RESULT_JSON: {…}` envelope as the only stdout line — the FORGE STATUS banner is suppressed. Branch on the envelope's top-level `status` field: `success`, `tier_failure` (require-tier miss), or `blocked` (any pre-report halt — the `error.phase` names the cause). `skf-emit-result-envelope.py` derives `status` from the payload, so pipelines never compose it from `require_tier_satisfied` + `error`. Schema in `references/report.md` §4. |
    | **Failure modes** | `--require-tier` not satisfied → status `tier_failure`, the envelope sets `"require_tier_satisfied": false`, and the workflow halts before step 5 (interactive runs also print a "REQUIRED TIER NOT MET" block). A write failure (forge-tier.yaml or preferences.yaml could not be written) halts step 2 with a `blocked` envelope whose `error.phase` (`step 2:write-tools` or `step 2:init-prefs`) and `error` name the path and reason. |
    
    ## On Activation
    
    > **Halt contract for headless/quiet runs.** Any halt below must first emit a blocked envelope when `{headless_mode}` or `{quiet_mode}` is true, before printing the human diagnostic — a pipeline observer that sees no envelope treats the run as not-completed-cleanly. Pipe `{"phase":"<phase>","reason":"<reason>","path":"<path>"}` to `python3 <helper> emit-blocked` where `<helper>` is the first existing path of `{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py` then `{project-root}/src/shared/scripts/skf-emit-result-envelope.py`. The `emit-blocked` subcommand declares zero dependencies (no `uv`, no `pyyaml`), so it works even when `uv` itself is the thing that's missing.
    
    1. **Parse invocation flags first** (so every halt below knows whether to emit an envelope): `{headless_mode}` (true on `--headless` / `-H`), `{require_tier}` (`--require-tier=<Quick|Forge|Forge+|Deep>`, case-sensitive; null if absent or unparseable), `{orphan_action}` (`--orphan-action=<keep|remove>`; null if absent), `{ccc_skip_index}` (true on `--ccc-skip-index`), `{quiet_mode}` (true on `--quiet`).
    
    2. **Probe `uv` runtime.** Run `uv --version`. Every step invokes shared Python helpers via `uv run` (PEP 723 inline metadata auto-resolves `pyyaml`). If `uv` is missing, halt with phase `on-activation:uv-missing` and the human diagnostic:
    
       "**Setup cannot proceed: `uv` is not installed.** SKF helpers depend on `uv` to auto-resolve their Python dependencies. Install it from <https://docs.astral.sh/uv/getting-started/installation/> and re-run `/skf-setup`."
    
    3. **Load config** from `{project-root}/_bmad/skf/config.yaml` and resolve `project_name`, `output_folder`, `user_name`, `communication_language`, `document_output_language`, `skills_output_folder`, `forge_data_folder`, `sidecar_path`. Halt with phase `on-activation:config-missing` if the file does not exist, or `on-activation:config-malformed` if the YAML is invalid, with the matching human diagnostic.
    
    4. **Reconcile `{headless_mode}`** with `preferences.yaml`: OR the parsed flag with `headless_mode: true` from the YAML.
    
    5. **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), and `_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.
    
       Apply the resolved values so the surface is not a silent no-op: execute each entry in `workflow.activation_steps_prepend` in order now (org-wide pre-flight checks such as auth, network, or compliance); 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); stash `{onCompleteCommand}` ← `workflow.on_complete` (empty string = no-op) for `references/report.md` §5 to invoke at the terminal stage. After activation completes, execute each entry in `workflow.activation_steps_append` in order, before the first stage runs.
    
    6. Execute `references/detect-and-tier.md`.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related