Overview
Detects the available tools, sets the capability tier (Quick/Forge/Forge+/Deep) and writes the forge configuration to {sidecar_path}/. With ccc it also keeps .cocoindex_code/settings.yml and the semantic-search index current, and it reconciles the QMD and CCC index registries.
Conventions
- Bare paths (e.g.
references/<name>.md) resolve from the skill root. - Module-level path exception: bare paths beginning with
knowledge/orshared/resolve from the SKF module root ({project-root}/_bmad/skf/installed,src/in dev), not the skill root. - Sibling skills: a path that names another SKF skill's folder (
skf-<name>/...) resolves from the SKF module root, and that skill must be installed with this one. references/holds the workflow stages (chained by frontmatternextStepFile), the invocation contract andtier-rules.md, the tier tablereport.mdpasses to the emitter's banner.- Shared Python helpers do the deterministic work, with no prose fallback: each step's frontmatter
*ProbeOrderarray lists a helper's installed path ({project-root}/_bmad/skf/shared/scripts/<name>) then its dev path ({project-root}/src/shared/scripts/<name>). Run the first that exists; if none does, halt through the On Activation halt contract with phasestep <N>:helper-missing. {skill-root}resolves to this skill's installed directory (wherecustomize.tomllives, 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: run each step in sequence, write the configuration files, and report at completion.
Workflow Rules
- Only load one step file at a time — never preload future steps.
- Communicate in
{communication_language}. - If
{orphan_action}is non-null or{quiet_mode}is true, resolve the step 3 orphan-removal gate without prompting; step 3 records the decision for the envelope'swarnings. - When
{quiet_mode}is true, the one line setup displays is theSKF_SETUP_RESULT_JSON: {…}envelope that step 4 builds, or the blocked envelope from a halt. In a standalone run that line is the run's final message, so it is allclaude -pprints (step 4 holds it until then). - When
{quiet_mode}is true, write no assistant text at all between tool calls: no status, progress or step-transition notes, however brief. - When
{pipeline_mode}is true, the forger runs setup as one step of a pipeline: display the same envelope line, then return control to the forger, which keeps chaining. The final-message rule above is for a standalone run.
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
references/invocation-contract.md holds the flags, gate, outputs, SKF_SETUP_RESULT_JSON contract and failure modes for pipeline authors; no run loads it.
On Activation
Halt contract. Every halt in this workflow that names a phase, below and in the step files, follows this contract; the step 4 tier-miss halt names none and displays its
tier_failureenvelope instead. When{quiet_mode}is true, run<runner> <helper> emit-blocked --phase '<phase>' --reason '<reason>' --path "<path>"(<path>written with/; no--pathfor a halt without one):<helper>isskf-emit-result-envelope.py, at its installed path, else its dev path (Conventions), and<runner>isuv runin the step files. The halts in this list can fire beforeuvis proven present, so for them try the runnersuv run,python3,pythonandpy -3in that order and stop at the first that exits 0 and prints a line startingSKF_SETUP_RESULT_JSON:(the helper is stdlib-only). Display that line verbatim, nothing else. If neither path exists, or no runner exits 0 and prints that line, display the reason alone as that one line. Interactive runs display the reason as their diagnostic and emit no envelope. Text from the user or a helper never goes into the single-quoted--reason: write<message>there and pass the text on stdin with--stderr-from -and a quoted<<'SKF_TEXT'heredoc, one per runner tried, for the emitter to fold to one line and escape. Once item 6 binds{customization_resolver_unavailable}to a reason, every halt also passes--customization-resolver-unavailable "<reason>", escaping each\,",$and backtick in it with a backslash.--headlessand--quietare known at every halt,headless_modefrompreferences.yamlonly after item 5.
-
Parse invocation flags first, so every halt below knows whether to emit:
{headless_mode}(true on--headless/-H, and when{pipeline_mode}is true),{quiet_mode}(true on--quiet, the alias of--headless, and whenever{headless_mode}is true),{require_tier}and{orphan_action}(each flag's raw value exactly as given, after=or a space; an empty string when nothing, or another flag, follows it; null only when the flag is absent),{ccc_skip_index}(true on--ccc-skip-index, false otherwise).{pipeline_mode}is not a flag: it is true only when the forger invokes setup as one step of a pipeline and passes it, and false otherwise, including every direct/skf-setuprun. Step 1's detector rejects a{require_tier}that names no tier. When{orphan_action}is non-null and neitherkeepnorremove, halt with phaseon-activation:orphan-action-invalid, nopath, and reasonSetup cannot proceed: --orphan-action takes keep or remove, not <message>. Re-run with --orphan-action=keep or --orphan-action=remove., where<message>is the value as given, oran empty valuewhen there is none, passed as the halt contract says. -
Check for the config. If
{project-root}/_bmad/skf/config.yamldoes not exist, halt with phaseon-activation:config-missing,path{project-root}/_bmad/skf/config.yaml, and reasonSetup cannot proceed: the SKF config file was not found. SKF is not installed in this project: from the project root, run npx bmad-module-skill-forge install (or npx bmad-method install and add SKF), then re-run /skf-setup. -
Probe
uvruntime. Runuv --version. Ifuvis missing, halt with phaseon-activation:uv-missing, nopath, and reasonSetup cannot proceed: uv is not installed. Install it from https://docs.astral.sh/uv/getting-started/installation/ and re-run /skf-setup. -
Load config and preferences.
<preflight>isskf-preflight.py, at its installed path, else its dev path (neither: halt with phaseon-activation:helper-missing,paththe installed one, reasonSetup cannot proceed: skf-preflight.py was not found. Reinstall SKF, then re-run /skf-setup.). Runuv run "<preflight>" "{project-root}" --allow-missing-sidecar, which parses config.yaml andpreferences.yamlinto one JSON object:statusok: bind{project_name},{user_name},{communication_language}and{document_output_language}fromconfig, each folder from its absolute path ({output_folder},{skills_output_folder},{forge_data_folder}and{sidecar_path}fromconfig.<name>_resolved), and{tier_override}fromsidecar.preferences.tier_override(null when absent). Withsidecar.preferences_error, go on with the defaults and, unless{quiet_mode}is true, display that error in one line.codeCONFIG_MISSING: the item 2 halt. Any otherhard-halt, or no JSONstatus: halt with phaseon-activation:config-malformed,path{project-root}/_bmad/skf/config.yaml, and reasonSetup cannot proceed: <message>: itserrorwithout the leadingCannot initialize.(else the first line the call printed), passed as the halt contract says, never preflight's whole JSON.
-
Reconcile
{headless_mode}: OR it withderived.headless_mode(headless_mode: trueinpreferences.yaml). Then, if{headless_mode}is true, set{quiet_mode}to true. -
Resolve workflow customization. Run:
uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflowIt merges the bundled
{skill-root}/customize.tomlwith{project-root}/_bmad/custom/skf-setup.toml(team overrides, committed) and.user.toml(personal overrides, gitignored). When it exits non-zero, prints no JSON or is missing, print one line,[activation/warn] customization_resolver_unavailable: <reason>(<reason>: its first stderr line,not foundwhen the script is missing,no JSONwhen it printed none). If the resolver cannot run, read{skill-root}/customize.tomlalone and use its bundled defaults: the{project-root}/_bmad/custom/overrides do not apply to this run. Bind{customization_resolver_unavailable}← that reason (null when the resolver ran), whichreferences/report.mdand every later halt hand to the emitter; print the warning only when{quiet_mode}is false.Apply the resolved values, with no narration of your own when
{quiet_mode}is true: run eachworkflow.activation_steps_prependentry now; treat eachworkflow.persistent_factsentry as standing context (afile:entry loads its file or glob contents); bind{onCompleteCommand}←workflow.on_complete(empty: no hook) forreferences/report.md§5; then run eachworkflow.activation_steps_appendentry, before the first stage. -
Execute
references/detect-and-tier.md.