BR = Business Requirements — the finalized requirements specification, located at
br/BR.md. The skill is named after the central artifact it produces, not after a verb. Every frame and every clause traces back to that file, so "product bug ⇔ BR bug" (GH#15, 2026-07-03) is a premise of the whole line, not a slogan.1-name hub: the user describes the scope (interview / compile / slice / run / status), NOT having to remember many commands. Foundation: council 028 (thin-slice, human-in-the-loop) + council 031/032 (loop v1 with 6 mandatory conditions — winner Taleb mean-rank 1.0).
WHAT
Purpose and context
- Purpose: turn the user's raw requirements document into a disciplined product through the BR-skill → frames → loop with harness line, every frame and clause tracing back to
br/BR.md. - Trigger (when to use):
- User has a requirements document (raw/unstructured) and wants to turn it into a disciplined product.
- User says: interview / requirements interview / draft BR / slice frame / run frame / line status / ralph.
- Non-goals: does not write to
llmwiki/raw/; does not rewrite the deterministic tooling already built (frame-lint.py,loop-runner.py,build-line-status.py…); does not commit/merge on the human's behalf; does not edit UI by hand outside/br run; does not hook auto-fire.
Mental model
llmwiki/raw/ → interview (spec-filled S1–S10 + câu hỏi hỏi-bù) → compile (BR.md có clause_id + "Giả định đang gánh" + BR.clauses.json) → slice (frames có depends_on, frame-lint gác) → run (loop-runner 6 phanh, commit per-frame, sổ trace) → qc / find / status (truy ngược lỗi→frame→clause). The user only describes the SCOPE; the hub picks the mode.
Input and output contract
| Field | Required? | Meaning | |
|---|---|---|---|
| In | raw document in llmwiki/raw/ |
present (mode interview) | read only; empty → ask the user to drop the document in |
| In | br/interview/NNN-answers.md filled in |
present (mode compile) | the user's real answers by field-id |
| In | scope described by the user | present | interview · compile · slice · run · find · qc · status · sync · contract · rail |
| In | flag --proactive · --lens-fill · --ack-tier · --worktree · --dry-run |
no | optional branch (see Branches) |
| Out | br/spec-filled.md, br/interview/NNN-{questions.html,answers.md} |
mode interview | field has status + provenance |
| Out | br/BR.md, br/BR.clauses.json, br/DESIGN.md (if there is a UI) |
mode compile | check-contract green |
| Out | br/frames/*.md, br/frames/index.md, br/prompts.md |
mode slice | frame-lint green |
| Out | commit frame(<id>), br/frames/<id>.run.json, .checkpoints.jsonl |
mode run | one-line summary + medic |
| Out | br/line-status.{json,html} + llmwiki/html/line-status.html |
mode status | 3-line summary |
Rules and capabilities
- RULE-01 (MUST): The human is the final gate — DO NOT auto-commit/merge the output frame; dry-run + human approval (council 028).
- RULE-02 (MUST): Inviolable brakes — never raise max_iter or disable a guard "so it finishes." Too many brakes is cheaper than too few (council 031/032).
- RULE-03 (MUST): Prompt instructions are NOT a safety layer — scope-jail/test-hash are deterministic code (loop-runner guard 5/6), not wording in the prompt (council 5/5 seats).
- RULE-04 (MUST): Lens-fill is always
verified: false— never mixed with real answers; always shown in the "Assumptions being carried" table. - RULE-05 (MUST): Human-in-the-loop slicer in the first round — not yet full-auto (a slicer error is a correlated error, blast-radius = the whole BR).
- RULE-06 (MUST): File-first, on-demand — every artifact is a travel-able file; the skill is invoked manually, NOT an auto-fire hook (ADR-004).
- RULE-07 (MUST): HTML generated for humans to view must explain terminology + print the full path (R16).
- Capabilities: read raw documents + codebase; write artifacts under
br/; run deterministic tools (lint, loop, monitor) locally; call model adapters with narrowly scoped tools; create issues in an external tracker only after confirmation.
Failure boundaries
llmwiki/raw/empty → clarify: ask the user to add the document.br-fill.py check-contractRED → compile failed, fix the BR until it is green.frame-lint.py checkred, or a dirty working tree →/br runblocked (refuse to run).- Frame
tier: compensable/irreversiblehas no--ack-tier→ blocked (TIER-GATE). - KILL condition (e.g. diff-jail triggers ≥2 times / test-hash triggers once) → cancelled the whole run, no rescue, no raising the brakes.
- Red frame → branch under
status: blocked(partial), unrelated branches still run. - UI check without a real IMAGE FILE +
MANIFEST.json→ treat as failed (never happened).
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | the user's words | Choose the mode based on the scope the user described (1-name hub) | mode | ambiguous → ask again |
| W02 | judgment | llmwiki/raw/ + spec-template |
Mode 1 interview: extract raw into S1–S10, gap-diff, generate questions.html + answers.md | 2 file NNN | raw empty → clarify; --proactive → B01 |
| W03 | deterministic | — | STOP — user opens HTML + fills answers.md | answers filled | --lens-fill → B02 |
| W04 | judgment | spec-filled + answers | Mode 2 compile: BR.md + BR.clauses.json (+ DESIGN.md if UI exists) | BR | — |
| W05 | deterministic | BR | Gate br-fill.py check-contract |
green | red → fix BR, repeat W05 |
| W06 | judgment | BR.md | Mode 3 slice: propose slices, STOP for user to approve EACH slice, write frame per template | frames + index | — |
| W07 | deterministic | frames | Guard frame-lint.py check + br-prompts.py sync |
lint green | red → fix frame, repeat W07 |
| W08 | effect | frame | Mode 4 run: br-run.py run (tier-gate, lint, clean tree, loop-runner, commit, run_log_ref, trace ledger) |
commit frame + run log | red → block downstream branches; KILL → discard run |
| W09 | deterministic | frames + run log + BR | Mode 5 status: build-line-status.py build + 3-line summary |
line-status.html | error falls into assumed → return W02 next round |
Details of each step (source of truth for W01–W09 and all sub-modes):
Deterministic toolset (already built + selftest — DO NOT rewrite)
| Tool | Task | selftest |
|---|---|---|
skills/br/assets/spec-template.md |
standard spec set S1–S10 (reference frame for every project) | — |
skills/br/assets/frame-template.md |
DETERMINISTIC template for every frame (frontmatter + 4 body sections a human can understand) | — |
skills/br/assets/design-template.md |
reusable design template (Google design-doc frame + design system loaded from /high-end-visual-design) → copy to br/DESIGN.md |
— |
fdk/tools/frame-lint.py |
guard frame with 7 rules (schema · scope · test-first · freshness · DAG · exclusive-scope has lease · content) | frame-lint.py selftest |
harness/scripts/loop-runner.py |
loop with 6 brakes (max_iter·budget·no_progress·escalate·diff-jail·test-hash) | loop-runner.py selftest |
fdk/tools/build-line-status.py |
deterministic monitor (frame·run-log·BR → json+html, --check) |
build-line-status.py selftest |
Runtime artifacts live in br/ at the project root (not inside the skill): br/spec-filled.md,
br/interview/NNN-{questions.html,answers.md}, br/BR.md, br/BR.clauses.json, br/frames/*.md,
br/frames/index.md, br/frames/<id>.run.json, br/line-status.{json,html}.
Mode 1 — /br interview
- Read ALL files in
llmwiki/raw/(read-only — rule forbids writing to raw/). If empty → ask the user to drop in documents. - Cross-check against the standard spec set
skills/br/assets/spec-template.md: extract information from the raw map into each field S1–S10 → writebr/spec-filled.md. EACH field writestatus(filled|missing|conflict|assumed) +provenance(raw:<file>). - Gap-diff: field
missing/conflict→ generate the question set. Each field is ONE clear spec (rigor /orca-workflow): the question states WHAT the field DOES + "what counts as ENOUGH of an answer" (the field's acceptance criteria), no vague questions. Write 2 files (same ordinal NNN):br/interview/NNN-questions.html— view by section, with term explanations + sufficiency criteria for each field, print full-path (R16), dark-mode. Ask ONLY about the missing parts.br/interview/NNN-answers.md— block to fill in by field-id (e.g.## S4.2… blanks), for the user to type answers. 3b.--proactive(T-260714-01, short for/br auto) — the user wants the machine to fill in the missing parts instead of asking everything: runpython3 fdk/tools/br-fill.py fill --root .(deterministic, no model). The tool looks up registry defaults (skills/br/assets/defaults.yaml— table of 26 loop conditions + github/spec-kit convention; project overridebr/defaults.yamlwins on field collision) and prints 2 groups:- MACHINE-FILL SUGGESTIONS → paste into
NNN-answers.md, KEEPfilled_by: <source>:<refs>+verified: falseevery item AS IS (lens-fill discipline applies to every machine-fill tier). - REAL QUESTIONS (field carve-out — auth/permissions, trust boundary, money — and fields with no default): pick ≤5 questions by Impact × Uncertainty (carve-outs are ranked first, taking question slots first) and put them into questions.html; anything over the cap gets lens-filled (step 5). A carve-out NEVER receives a machine value — even if the registry has an entry, even via lens.
- STOP — tell the user to open the HTML to view + fill in answers.md.
- If the user enables
--lens-fill(or says "let an expert fill in the uncertain parts"): pick a lens withpython3 harness/scripts/council.py roster --case product --json, fill the fieldsmissingthat the user marked "uncertain" → stamp each fieldfilled_by: lens:<tên>+verified: false. Do NOT mix with the user's real answers.
Mode 2 — /br compile
- Read the filled-in
br/spec-filled.md+br/interview/*-answers.md. - Generate
br/BR.md: each clause hasclause_id(inherits field-id, e.g.S4.2) + provenance (raw|user|lens:<tên>). The top of the file is a table "Assumptions currently carrying" listing every clauselens/assumed(fail-fast: one glance shows what is being bet on).- Product has UI (S7.5): settle one NFR clause (e.g.
N01 giao-diện) POINTING tobr/DESIGN.md, do NOT copy design rules into the BR. Also copyskills/br/assets/design-template.md→br/DESIGN.mdand fill in §1/§2/§4/§6/§7 for the product (keep §3/§5 inherited). BR = must have theme/design; DESIGN.md = what the design looks like.
- Product has UI (S7.5): settle one NFR clause (e.g.
- Generate
br/BR.clauses.json:{clause_id: {provenance, assumed: bool, fields: ["S4.2", …]}}—fieldsdeclaring the S1–S10 spec fields that the clause implements (the contract checksum key); the monitor (/br status) reads this file to color assumed clauses orange. The "Assumptions currently carrying" table grouped by fill source (default/spec-kit/lens) so the reviewer sees what is being bet on and from where. - Contract checksum gate (patch G1 — council c9dc13d):
python3 fdk/tools/br-fill.py check-contract --root .— every required field S1–S10 must have a counterpart clause; RED means compile FAIL, fix the BR until it's green, there's no such thing as "compile finished but the contract has holes".
Mode 3 — /br slice
- Read
br/BR.md. PROPOSE a list of slices: each slice = {clause_ids, expected scope_code (≤3 files), scope_test, acceptance_test, depends_on}. STOP for user to approve/edit/merge/split EACH slice (human-in-the-loop — the guard against slicer correlation errors). First batch ≤ 3–5 frames.depends_onis mandatory to think about, not mandatory to have (GH#75, distill Atomic Task Graph): the chain is a GRAPH, not a list. Frame B declaresdepends_on: [frame-A]when B cannot be green if A isn't green yet (B calls an API/store/schema that A builds). If it depends on no one →depends_on: []. Declared correctly, you get three things free at mode 4: the run order is automatically right, a red frame automatically blocks the branch below it, and fixing one frame only requires rerunning its own branch. Declared carelessly (linking just so it "looks plausible"), you lose parallelism and create fake waits — only link when there's a REAL dependency.
- Write
br/frames/frame-NNN-<slug>.mdFOLLOWING THE TEMPLATEskills/br/assets/frame-template.md(schema v0 + 5 mandatory body sections: Business · Input/Output · Spec (FR/SC) · Acceptance criteria · Out of scope — written for a LATER PERSON to read and understand, frame-lint R7 enforces hard: frame_id must have a business slug, muc_tieu must not be generic, each frame has clear FR-id/SC-id, sections must not be empty) +parent_br_hash = sha256(br/BR.md). - Generate registry
br/frames/index.md(table frame_id · clause_ids · scope_code · status · run_log_ref) from the frames' frontmatter. - Gate:
python3 fdk/tools/frame-lint.py check br/frames --root .— all green before a slice counts as done (including R6 exclusive-scope: 2 frames not done must not tread on the same file).- R6 is a LEASE CONTRACT, not a land title: a frame that is done (run-log
verdict: SUCCESS) releases the right to hold the file. Thanks to that, a new frame can be connected to the starting point (main.ts,state.ts,server.ts…) that an old frame used to hold — without a lease, new code goes green but never reaches the user (walleye 2026-09-10: 4 green frames, the player sees nothing). Two unfinished frames still holding the same file is still RED — unlessdepends_onhas already arranged them in sequence (this frame is an ancestor of that one): in that case they never modify at the same time, and multiple features can be connected to the samemain.tsone after another._manifest.jsonhands the file to the last renter (the descendant). - Each feature must have a WIRING slice to the starting point, and a UI frame must be accepted by a test that goes through the real interface (e2e/route-shots), not by a unit test of the module standing alone.
- R6 is a LEASE CONTRACT, not a land title: a frame that is done (run-log
python3 fdk/tools/br-prompts.py sync— append prompt entry for new frames to the logbr/prompts.md(the user can edit it by hand immediately).
Mode 4 — /br run <frame>
Deterministic invocation (already wired — default IN-PLACE, a SINGLE working tree):
python3 fdk/tools/br-run.py run br/frames/<frame>.md --root .
# xem prompt sẽ gửi mà không chạy: thêm --print-prompt
# chạy cả hàng đợi resumable: python3 fdk/tools/br-queue.py run --queue br/queue.yaml
# sửa 1 frame → chạy lại ĐÚNG nhánh của nó, không cả dây chuyền:
# python3 fdk/tools/br-queue.py affected <frame_id> --queue br/queue.yaml --reset
# python3 fdk/tools/br-queue.py run --queue br/queue.yaml
# cần cô lập thư mục thật sự (hiếm): thêm --worktree
Queue runs by GRAPH, not by hand-written order (GH#75 — distill Atomic Task Graph, arXiv 2607.01942):
- Topo order:
br-queuereadsdepends_onof each frame and orders them automatically (if there are no dependencies, the declared order is kept — the old queue runs exactly as before). - A red frame BLOCKS the entire branch below it (
status: blocked, writeblocked_by): the input of that branch is already broken; running on only burns model turns on something that is certain to fail. Unrelated branches still run normally — one broken frame no longer blocks the entire chain. - Local repair (
affected): fix one frame → only that frame + the branch below it return topending, every other green frame stays as is. This is where it pays off: previously one broken frame in the middle of a chain forced more reruns than necessary. - NO subgraph-reuse cache (the paper has it; we deliberately omit it):
status: donealready skips green frames; adding a hash-based cache at the scale of a few dozen frames only breeds stale bugs. The paper's own limitation applies here too — the benefit remains when the graph is small and acceptance_test is deterministic; for a very large graph or a test/tool that returns erratic results, this scheduling stops being useful.br-run.pyitself: (0) TIER-GATE — a frame that declarestier: compensable|irreversiblein its frontmatter (irreversible effect: calling an external API/sending mail/writing DB) is BLOCKED until a human reruns with--ack-tier(SHEPHERD gate-before-materialize), (1) frame-lint, (2) check the working tree is clean, (3) run loop-runner RIGHT IN the current tree with revise already pointed atfdk/tools/br-revise.py(renderskills/br/assets/revise-prompt.md→claude -ptools narrowed), (4) green frame → commit to the current branch, message tagged frame_id (revert/blame marker), (5) print a 1-line summary, (6) writerun_log_refinto the frame, (7) write the marker into the TRACE LOG.checkpoints.jsonl(checkpoint-trace/SHEPHERD — the record already points to the commit, no double-commit) → view the entire chain:python3 fdk/tools/checkpoint.py list; roll back the whole pipeline to any frame by NAME:checkpoint.py rollback <frame_id>(keeps history, warns about irreversible effect tiers after the marker). Why in-place is the default (user feedback 05/07): N frames = N worktrees = N ghost folders nobody digs through; the usual human flow is open the app and see, the fix must appear immediately in the tree the app is running from. Out-of-scope control lives in the HARNESS (diff-jail revert every round + FINAL SCOPE SWEEP + test-hash + scope_clean + R6 exclusive-scope), not in folder isolation. Don't like the result →git revert <commit frame>.
Equivalent manual steps (if fine-tuning by hand is needed):
frame-lint checkthat frame is green. Check working-tree CLEAN (git status --porcelainempty) — if dirty, refuse.- Create separate git worktree from
baseline_refof the frame (jail filesystem — the first frame does NOT touch harness/hook/CI). - Run loop — commit per-frame INTO WORKTREE BRANCH (not main); human still merges manually:
Adapter revise =python3 harness/scripts/loop-runner.py run \ --verify "<acceptance_test>" --revise "<adapter claude -p>" \ --state "<scope_code>" --scope "<scope_code>" --protect "<scope_test>" \ --baseline "<baseline_ref>" --commit-on-success \ --commit-message "frame(<frame_id>): <muc_tieu> [<clause_ids>]" \ --max-iter <g> --budget-seconds <g> --no-progress-k <g> --escalate-after <g> \ --log br/frames/<id>.run.json --cwd <worktree>claude -pwith narrowly scoped tools (--allowedTools Edit,Write,Read,Grep,Glob— NO free Bash, no network), prompt built frommuc_tieu+scope_code+ latest failed verify output. Trace frame→code (prerequisite — ensure which frame created which code + no scope violation):- Run-log records
changed_files= files the frame ACTUALLY changed (≠ intended scope);scope_clean=true when every file ⊆scope_code;attempted_out_of_scope= files the frame INTENDED to write out of scope but that were reverted. - FINAL SCOPE SWEEP: on every loop stop (including PROTECT_VIOLATION), loop-runner cleanly reverts every file outside
scope_code→ worktree is ALWAYS scope-clean,changed_filesALWAYS ⊆scope_code. Full control, not just relying on prompt instructions. --commit-on-successonly commits when verdict=SUCCESS AND scope_clean; message tagged withframe_id→git blameto trace back to the exact frame.--baseline= base commit for diff computation (usually the worktree HEAD at start).
- Run-log records
Prompt & queue (run multiple frames, resumable)
- 📒 MASTER PROMPT BOOK (PRIORITY source #1, user edits by hand without needing model):
br/prompts.md— ONE file, each frame one entry## <frame_id>. Open it, find the frame, edit/ADD content freely (placeholder{{...}}stays as-is for the machine to fill in at run time).python3 fdk/tools/br-prompts.py syncautomatically adds entries for new frames, NEVER overwrites hand-edited entries (run after each/br slice);list/get <frame_id>to check. Priority order when running: prompt book > inline queue > prompt_file > default template. - Original template (seed for the book):
skills/br/assets/revise-prompt.md. Adapterfdk/tools/br-revise.pyrenders the winning source → callsclaude -p(narrowly scoped tools). See the prompt that will be sent:br-revise.py run --frame <f> --verify "<cmd>" --print. - QUEUE file (
br/queue.yaml, templateskills/br/assets/queue.example.yaml): LIST of frames to run sequentially; each entry is eitherprompt_file:(points to a template file) ORprompt:(writes text directly, still supports placeholders). Driverfdk/tools/br-queue.py run --queue br/queue.yamlruns each entry, writesstatusback after EACH entry → re-running the queue = skipdone, continue (resume).--dry-runshows which prompt each entry uses;listshows status. - Adapter
claude -pis the boundaryverified:false(BNAL) — template + queue + render are all deterministic + selftest (br-revise.py selftest,br-queue.py selftest).
- KILL conditions written BEFOREHAND (via negativa): e.g. diff-jail bites ≥2 times / test-hash bites once → discard the whole run, no rescue, no loosening the brakes.
- Loop stops → prints ONE-LINE summary (verdict · round count · diffstat · final verify command) + run
python3 fdk/tools/medic.py --ci. STOP — the reader reviews the diff in the worktree, decides for themselves whether to merge/continue/discard. - Fill in
run_log_ref+outcomeinto the frame + registry. Record lessons (including what did NOT happen — which brake has never bitten).
Mode 4b — /br find <file-hoặc-từ-khoá> — point at the fault location, get back the responsible frame/prompt
Normal-person flow: open the app → see the bug → point at that spot:
python3 fdk/tools/br-find.py "src/auth/login.py" # theo file
python3 fdk/tools/br-find.py "đăng nhập" # theo từ khoá / clause
Print out: responsible frame (matched by the ACTUAL changed file > scope_code > keyword) · BR clause · where the prompt lives (inline in queue.yaml / prompt_file / default template) · the command to re-run EXACTLY that frame after fixing the prompt. This works because R6 exclusive-scope (frame-lint) forces each file to belong to only ONE frame at a time — if a file's owner has changed via lease, the responsible frame is the last lessee (_manifest.json), while history is git blame by commit frame(<id>).
Mode 4c — /br qc [frame] — senior QC audit (4-point LLM) on the mockup/frame
Once you have a green mockup/frame, run the SENIOR EYES (expensive=LLM, called by hand — the cheap deterministic part already ran automatically after each /br run):
- Code: call
/qc-code(Skill tool →qc-code) to review the frame diff — 4 items security/performance/naming/logic + generate a reproduction testqc-*. - UI/UX: call
/qc-uiux(Skill tool →qc-uiux) audit the mockup — 4 items a11y/hierarchy/consistency/antipattern; the measurable part (contrast/tap-target/overlap/misalign/missing-label) runs through the visual-qa headless engine:node skills/visual-qa/assets/route-shots.mjs <base-url-mockup> --audit - Verdict NEEDS FIX → fix before locking the frame. Advisory — the LLM verdict is an opinion; engine measurements + tests
qc-*are what hard-gate it (already ran automatically at/br runandverify-before-commitstep 3b). No frame specified → audit the existing mockup routes.
Mode 5 — /br status
python3 fdk/tools/build-line-status.py build --root .→br/line-status.json+llmwiki/html/line-status.html.- Print a 3-line summary (overall status · most notable frame · HTML path). Page for tracing back bug→frame→clause; assumed clauses shaded orange → go back to
/br interviewnext round if the bug falls under assumed.
Mode 6 — /br sync (frame ↔ GitHub sub-issue)
Distill automazeio/ccpm: each frame → one GitHub sub-issue so the team can see progress + hand-off. Do NOT redo provenance (clause_id/manifest already go deeper than ccpm); just a thin sync layer on top of gh.
python3 fdk/tools/br-sync.py sync br/frames --root . --repo <owner/repo> --dry-run— ALWAYS dry-run first (no gh calls, no mapping writes).- Drop
--dry-runonce sure → create a sub-issue for unmapped frames, writebr/frames/issue-mapping.json. Idempotent.--epiccreates the epic issue.status/selftestis available. - Creating an issue is an action outside the repo → confirm before dropping
--dry-run.
Mode 7 — /br contract (UI Contract locks in the implementation, md+html)
Separate the CODE axis (frame) from the DISPLAY axis (screen/route). The standard for every project:
- Frame declares
ui_role: none|screen|panel|widget|form|action+ui_screen(frame-template) + section "## How the UI works". br/ui-layout.yaml:nav_style+screens[](id·title·real route·frames[]). Editing this file gathers screens WITHOUT touching frames.python3 fdk/tools/br-contract.py build br/frames --layout br/ui-layout.yaml --root .→br/UI-CONTRACT.md+.html. Table frame·what it does·ui_role·screen·route·clause·acceptance + count of frames/screens/routes + mismatch warnings.selftestis available.
Branches
| ID | Kind | Guard | Behavior | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | user_optional | --proactive (abbreviated /br auto) at interview |
br-fill.py fill proposes machine-filled (verified: false) + ≤5 real questions by Impact × Uncertainty; carve-out does not accept machine values |
exceeds ceiling → lens-fill | W03 |
| B02 | user_optional | --lens-fill or "let the expert fill in uncertain items" |
council.py roster --case product --json, fill missing fields the user marked uncertain, stamp filled_by: lens:<tên> + verified: false |
not mixed with real answers | W04 |
| B03 | conditional_required | product has UI (S7.5) | clause NFR points to br/DESIGN.md; copy design-template.md → br/DESIGN.md, fill in §1/§2/§4/§6/§7 |
no UI → skip | W05 |
| B04 | conditional_required | frame has ui_role other than none |
/design-twice before coding; acceptance_test is visual-qa route-shots.mjs --assert |
missing item ## Thiết kế (design-twice) → frame not done |
W08 |
| B05 | conditional_required | frame declares tier: compensable or irreversible |
blocks until rerun with --ack-tier |
no ack → does not run | W08 |
| B06 | capability_optional | needs real directory isolation (rare) | add --worktree, or an equivalent manual step (separate worktree from baseline_ref) |
— | W08 |
| B07 | user_optional | run multiple frames / edit one frame | br-queue.py run --queue br/queue.yaml in topo order; affected <frame_id> --reset rerun the correct branch |
red frame blocks downstream branches | W09 |
| B08 | user_optional | /br find <file-hoặc-từ-khoá> |
Mode 4b: br-find.py in the responsible frame + clause + prompt place + rerun command |
— | W08 (edit prompt then rerun) |
| B09 | user_optional | /br qc [frame] |
Mode 4c: /qc-code + /qc-uiux (advisory); verdict NEEDS FIXING → fix before locking the frame |
— | W08 |
| B10 | user_optional | /br sync |
Mode 6: br-sync.py sync --dry-run first, confirm then remove --dry-run |
not confirmed → stop at dry-run | end |
| B11 | user_optional | /br contract |
Mode 7: br-contract.py build → br/UI-CONTRACT.md + .html |
— | end |
| B12 | user_optional | /br rail off or skip |
turn off SOFT gate (R7 content · visual-qa UI · assumption-gate) via frame-lint … --soft-off; hard brake stays as is |
skip auto re-enables after 1 run | W08 |
Validation and stopping
Deterministic: frame-lint.py check (7 rules), br-fill.py check-contract, loop-runner 6 brakes + FINAL SCOPE SWEEP, medic.py --ci after each loop, each tool's selftest (frame-lint.py selftest, loop-runner.py selftest, build-line-status.py selftest, br-revise.py selftest, br-queue.py selftest). Review needed (human is the final gate): approve answers, approve EACH slice in the slice, read frame diff then decide merge/continue/discard. Mode stops at the recorded STOP points; loop stops according to brakes, no brake escalation.
Examples
- Positive: user drops 3 requested files into
llmwiki/raw/, says "draft BR" → interview generatesbr/interview/001-questions.htmlasks for the 7 missing fields → user fills them in → compiles tobr/BR.md+ check-contract green → slice proposes 4 slices, user approves →frame-lint.py check br/frames --root .green →br-run.py run br/frames/frame-001-dang-nhap.md --root .→ SUCCESS, commitframe(frame-001-dang-nhap): …, one-line summary + medic. - Boundary/failure:
/br runwhengit status --porcelainis not empty → refuse to run. Mail-sending frame declarestier: irreversible→ TIER-GATE blocks until--ack-tier. Frame-003 red → frame-004 (depends_on: [frame-003]) becomesstatus: blocked, unrelated frame-005 still runs.
Reference — Unified Stream UI + Clear Spec (distill hallmark + orca-workflow)
Two gaps of the multi-frame pipeline, patched by two distill mechanisms:
A. UI/UX unified ACROSS FRAMES (distill hallmark). Each frame runs independently through loop-runner → easy for each frame to look different, patchwork mockups. hallmark mechanism: br/DESIGN.md = LOCKED TOKEN SYSTEM, read first and overrides all choices; INVERSION rule — frames must be SIMILAR (share the system), not diversified; locked-token — every color/font/spacing points to a named token (var(--…)), inline FORBIDDEN. Applied to /br:
/br compilecopydesign-template.md→br/DESIGN.mdwith token table §4 + §4.1 (locked tokens) as the SOURCE OF TRUTH.- Frame UI in
## UI hoạt động ra saouses only DESIGN.md tokens; adding a new value = add a token to DESIGN.md then point to it, do not make one up at the frame. - Token drift is caught by
/qc-uiux(consistency) + visual-qa engine → the "stream" runs by deterministic gate, not by verbal reminders. Result: frame-01 and frame-09 look like ONE product.
B. Every frame + interview screen needs a clear SPEC (learn from /orca-workflow). Propose FR/SC durable ids + acceptance; /br carry that rigor down every link in the chain:
- Frame: frame-template has section
## Spec (FR/SC)(frame-lint R7 enforces hard) — each frame declares an FR-id (system MUST…) + SC-id (measure a PERSON), acceptance_test is the evidence for the SC, each acceptance line traces back to an FR. A frame missing clear FR/SC = not done, just like an interview missing fields. - Interview: each field S1–S10 asked must yield ONE clear spec — the question states what the field is for + the criterion for "what counts as ENOUGH of an answer" (no vague questions). Fields still
missing/assumed= spec not sufficient, shown in the "Assumptions currently carrying" table so they don't drift.
Reference — DESIGN round for UI frames (skill /design-twice) — run BEFORE coding
Mandatory for every frame ui_role≠none: before writing a single line of UI code, run /design-twice
to get 2–3 FUNDAMENTALLY DIFFERENT options, compare, pick 1, then write section ## Thiết kế (design-twice)
into the frame itself (3 options + chosen option + reasons + ideas merged from the losers).
- Why: the first idea is the easiest idea to think of, not the best idea. With no options to compare, the agent only knows how to patch the single option — each patch round spawns a new bug (Lume theme: 5 patch rounds).
- Signal to go back to the design round: currently patching for the 2nd time in the same place, or the user complains "ugly/crappy/don't like it" without pointing out a specific flaw ⇒ wrong shape, more patching is wasted effort.
- UI frame missing section
## Thiết kế (design-twice)= not done (R7 content treats it as missing content). - Only after design is done do we get to
## UI hoạt động ra sao→ code → gate/visual-qabelow.
Reference — VISUAL self-check round for UI frames (skill /visual-qa)
Principle (not a separate re-check rule): editing UI = edit the spec + code OF THAT FRAME then
/br run again. If you want "re-running the frame self-re-verifies the UI", then acceptance_test of the UI frame
must BE visual-qa — the test hits the UI, not a unit-test on the side. This is a high-leverage
point: no need to tell the agent "remember to re-check", because the frame's own verify loop already embraces the UI check.
- Frame
ui_role≠nonesets:acceptance_test: "node skills/visual-qa/assets/route-shots.mjs --base <url> --route <route> --assert --user <u> --pass <p> --out <dir>"→ exit 1 if the route is not 200/empty → frame RED until the UI renders correctly./br runagain after each fix = self re-verify. - After green, the agent
Readimage (skill/visual-qa) →FINDINGS.md(route·level·fix) → each finding is a fix to the frame's spec+code →/br runagain. Catches theme/brand/empty-state bugs unit-tests can't see; use when the MCP extension breaks on localhost. - Do not manually edit the UI outside
/br run(debt p28 05/07): manual edit = lose gate + no re-verify. All UI edits must go through the frame.
Reference — Rail — the pipeline's harness switch (/br rail on|off|skip)
"On the rails" = currently within guard scope. DEFAULT ON — auto-on for the whole session when using /br.
python3 fdk/tools/br-rail.py status --root .→ status + ALWAYS remind that it can be turned on/off.on(fully guarded) ·off(turn off SOFT gate until re-enabled) ·skip(skip soft gate EXACTLY 1 time then auto re-enable + warning).- SAFETY (invariant): off/skip ONLY turns off soft gate — R7 content · visual-qa UI · assumption-gate. HARD BRAKE (diff-jail · test-hash · scope-clean) + R1–R6 structure + security hook are NEVER turned off via this.
- Wiring: start of
/br run, readsbr-rail.py consume→ if off/skip then callsframe-lint … --soft-off(skips R7+assumption), loop-runner hard brake remains intact; PRINT Rail status + reminder line on every run.
Reference — Evidence > words (UI check rule — feedback 2026-07-13)
UI/visual check is only valid when there is REAL IMAGE FILE + MANIFEST.json listing route→image→bytes (skill /visual-qa). No file = NEVER DONE = fail, regardless of what the model says. After editing the UI you must RE-capture + re-read before green. Forbidden to "let it pass" by reasoning. (Also the /fdk rule.)