Overview
Produces a refined architecture document: gaps filled, issues flagged and improvements suggested from the generated skills and an optional VS feasibility report. It never deletes original content, only adds annotations, subsections and suggestions between <!-- RA:BEGIN ... --> and <!-- RA:END --> marker lines; run again on a refined document, it sets its earlier annotations aside and writes one current set.
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. {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 an architecture refinement analyst in Ferris Architect mode; the user brings the architecture vision and the generated skills.
Workflow Rules
These rules apply to every step in this workflow:
- Never speculate — every gap, issue, or improvement must cite specific APIs, types, or function signatures from the generated skills
- Only load one step file at a time — never preload future steps
- Always communicate in
{communication_language} - At any interactive prompt, the inputs
cancel,exit,[X],q, or:qexit cleanly with exit code 6 (halt_reason: "user-cancelled") - If
{headless_mode}is true, auto-proceed through confirmation gates with their default action; a gate that picks its default records that decision in the run sink the moment it decides - Every HARD HALT names its exit code,
halt_reasonand phase, and in headless mode prints the envelope the Result Contract below describes - Run state lives in
{run_dir}, not in context; step 6 deletes it when the run finishes, a HALT keeps it
Stages
| # | Step | File | Auto-proceed |
|---|---|---|---|
| 1 | Initialize & Load Inputs | references/init.md | No (input gate) |
| 2 | Gap Analysis | references/gap-analysis.md | Conditional (confirm a derived scope) |
| 3 | Issue Detection | references/issue-detection.md | Yes |
| 4 | Improvements | references/improvements.md | Yes |
| 5 | Compile Refined Architecture | references/compile.md | No (review) |
| 6 | Report | references/report.md | Yes |
| 7 | Workflow Health Check | references/health-check.md | Yes |
Invocation Contract
| Aspect | Detail |
|---|---|
| Inputs | architecture_doc_path [required], vs_report_path [optional] |
| Flags | --headless / -H; --architecture-doc <path> and --vs-report-path <path> answer the step 1 prompts (--vs-report-path none refines without a report and skips the search for one); --scope-skills <names> (comma-separated skill names that replace the derived scope, so step 2 asks no scope confirmation) |
| Gates | step 1: Input Gate [use args] |
| Outputs | refined-architecture-{arch_project_name}.md at {outputFolderPath} (the document's frontmatter project_name, else config's), promoted from a draft once the step 5 review approves it; an earlier one is first renamed to refined-architecture-{arch_project_name}-{timestamp}.md. Also refine-architecture-result-{YYYYMMDD-HHmmss}.json, its -latest.json copy and, once an approved review drops a finding, .ra-dismissed-{arch_project_name}.json |
| Headless | Every gate takes its default when {headless_mode} is true: --scope-skills skips the step 2 scope confirmation, and without --vs-report-path step 1 uses the [VS] report it finds. |
Result Contract (Headless)
When {headless_mode} is true, step 6 prints one SKF_REFINE_ARCHITECTURE_RESULT_JSON: {...} line on stdout, and every HARD HALT one on stderr, built by the shared emitter: references/exit-codes.md gives its fields, the halt_reason values and the halt command.
On Activation
-
Load config from
{project-root}/_bmad/skf/config.yamland resolve:project_name,user_name,communication_language,document_output_languageskills_output_folder,forge_data_folder,output_folder,sidecar_path
-
Compute run-scoped variables:
timestamp← the output ofdate -u +%Y%m%d-%H%M%S, run once now: it names the run folder and the copy an earlier refined document is renamed to.run_dir←{project-root}/_bmad-output/.skf-run/skf-refine-architecture-{timestamp}, the run folder §5 creates
-
Resolve
{headless_mode}: true if--headlessor-Hwas passed as an argument, or ifheadless_mode: truein{sidecar_path}/preferences.yaml. Default: false. -
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-refine-architecture.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. In that case keep the reason as{customization_resolver_unavailable}for §5 to record.Bind each path scalar, taking the bundled default when the merged value is empty or absent:
{refinementRulesPath}←workflow.refinement_rules_pathif non-empty, elsereferences/refinement-rules.md(house style only: the file's first section names what a copy can change){outputFolderPath}←workflow.output_folder_pathif non-empty, else{output_folder}{onCompleteCommand}←workflow.on_completeif non-empty, else empty
Then run
workflow.activation_steps_prependnow, treatworkflow.persistent_factsas standing context for the run (file:-prefixed entries load their file/glob contents as facts: the bundled default loads everyproject-context.mdunder{project-root}; an entry prefixed!drops each earlier entry it names and loads nothing itself, so an override's"!file:{project-root}/**/project-context.md"turns that default off), then runworkflow.activation_steps_appendonce §5's pre-flight has passed, before §6 loads the first stage. -
Pre-flight: the emitter, config, write probe and run folder. In that order, so every halt can print its envelope and an empty path halts as missing config (exit 3), not as a write failure.
The emitter. Resolve
{emitEnvelopeHelper}←{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py, else{project-root}/src/shared/scripts/skf-emit-result-envelope.py(the first that exists). If neither exists, HALT (exit code 3,halt_reason: "resolution-failure") at phaseon-activation:emitterand display only: "Refine Architecture cannot run withoutskf-emit-result-envelope.py, which is not installed. Re-install SKF."The halts below come before the run folder, so in headless mode each passes its payload on stdin, with
"customization_resolver_unavailable": "<reason>"added when step 4 kept one:uv run {emitEnvelopeHelper} emit-halt --workflow skf-refine-architecture --target stderr <<'SKF_RA_HALT' {"phase": "<phase>", "reason": "<the halt message>", "halt_reason": "<halt_reason>"} SKF_RA_HALTConfig-completeness (exit 3). If
{outputFolderPath}is empty: HALT (exit code 3,halt_reason: "output-folder-unconfigured") at phaseon-activation:config: "Setoutput_folderin config.yaml, then re-run [RA]." If{forge_data_folder}is empty: HALT (exit code 3,halt_reason: "forge-folder-unconfigured") at phaseon-activation:config: "Setforge_data_folderin config.yaml, then re-run [RA]."Write probe (exit 4). Check that both paths are writable before any prompt:
for dir in "{outputFolderPath}" "{forge_data_folder}"; do mkdir -p "$dir" && \ printf 'probe' > "$dir/.skf-write-probe" && \ rm "$dir/.skf-write-probe" doneOn any non-zero exit: HALT (exit code 4,
halt_reason: "write-failed") at phaseon-activation:write-probe, with"path"set to the folder that failed.Run folder (exit 4). Create the run folder:
mkdir -p "{project-root}/_bmad-output/.skf-run" && mkdir "{run_dir}"If the command fails, HALT (exit code 4,
halt_reason: "write-failed") at phaseon-activation:run-folder: "Cannot create the run folder{run_dir}: {the first stderr line}." Once it exists and{customization_resolver_unavailable}is set, record the reason in the run sink: writecustomization_resolver_unavailable: {customization_resolver_unavailable}to{run_dir}/resolver-warning.txtwith a file write, neverecho, then runuv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/resolver-warning.txt")". -
Load, read the full file, and then execute
references/init.mdto begin the workflow.