Asking the specification
A finished feature isn't only working software โ it's a written specification sitting right
next to the code. The /query-specs command reads that spec and answers questions about it in
plain language. Here are two real questions about how pawn promotion works for Black โ the answers are
reproduced verbatim.
I'll check the specs for how pawn promotion is handled for Black.
Searched promot|promo
Yes โ promotion is implemented for Black. When Stockfish (playing Black) advances a pawn to the final rank, the app applies the promotion the engine chose: the pawn is replaced by whatever piece Stockfish promoted it to (queen, rook, bishop, or knight).
This is the one case where promotion actually works today, precisely because the engine decides for Black โ there's nothing for a human to pick, so the app just carries out the engine's choice.
In plain terms:
- A Black pawn reaching the last rank in Stockfish's reply is promoted automatically, to the piece the engine selected (rule B-6a).
- The counterpart โ White-side (the human player's) promotion โ is not implemented, and neither is a manual "choose your promotion piece" prompt; both are explicitly deferred (Out-of-scope note, building on F0002's general deferral of promotion).
- Note the asymmetry: because Stockfish always names the promotion piece, Black promotion needs no chooser and works end to end; a human pushing a White pawn to the last rank has no way to promote yet.
Feature: F0003 โ play-with-stockfish โ F0003.md โ rule B-6a (and the Full pawn promotion item in Out of scope), extending F0002 โ move-a-piece, which first deferred promotion.
Good question โ let me split the two parts, because the premise is half right.
Yes, it happens on the fisher-server side โ but the piece is chosen by Stockfish, not by fisher-server.
Rule B-6a is a fisher-server action in the sense that the fisher-server is the one that actually replaces the pawn on its stored board. But it does not decide which piece to promote to. That choice comes entirely from the Stockfish engine: when the engine plays a promoting move for Black, its reply already names the promotion piece (e.g. "promote to a queen"), and fisher-server simply reads that instruction and carries it out on the board.
In plain terms:
- Stockfish returns Black's move, and if it's a promotion the move itself carries the chosen piece (queen, rook, bishop, or knight) โ the engine picks it (rule B-5, the move parsing).
- fisher-server then applies that: it removes the pawn from the board and puts the engine's chosen piece in its place (rule B-6a).
- So there's no independent "piece choice" made by fisher-server, and no chooser shown to anyone โ fisher-server is just the executor of the engine's decision. (A human/White-side promotion, where someone would need to pick, is still out of scope.)
Feature: F0003 โ play-with-stockfish โ F0003.md โ rule B-6a (with rule B-5 for where the engine's promotion choice comes from).
Down to the code lines
The spec captures the intent; sometimes you want the exact lines that carry it out. Ask in plain English and the assistant walks straight to the Rust that implements the rule โ file, line number, and the code itself.
Searched promo|from_promo|Piece::black|nth\(4\)
Here are the exact Rust lines that interpret Stockfish's reply and carry out the promotion (rule B-6a). It happens in three steps across two files:
1. Read the promotion letter from the engine's UCI move โ parse_uci takes the
optional 5th character of the bestmove string (e.g. a2a1q โ q) and turns it
into a piece kind:
-
fisher-server/src/engine/mod.rs:145
let promo = s.chars().nth(4).and_then(Kind::from_promo);(stored on the parsed move at fisher-server/src/engine/mod.rs:134 โ
pub promo: Option<Kind>)
2. Translate that letter into a piece kind โ Kind::from_promo maps
q/r/b/n โ Queen/Rook/Bishop/Knight:
- fisher-server/src/game/piece.rs:31 โ
pub fn from_promo(c: char) -> Option<Kind>
3. Replace the pawn on the board โ apply_black_move puts the promoted
Black piece on the target square instead of the pawn:
-
fisher-server/src/engine/mod.rs:156
if let Some(kind) = uci.promo { // The promoted piece is always Black (this module only plays Black). next[uci.to.row][uci.to.col] = Some(Piece::black(kind)); }where
Piece::black(kind)(defined at fisher-server/src/game/piece.rs:76) builds the Black piece of that kind.
In short: the choice of piece is decoded from Stockfish's move at
mod.rs:145 (via Kind::from_promo), and the board is actually updated at
mod.rs:156โ158. The behaviour is pinned by the unit test
apply_black_move_records_capture_and_promotion
(fisher-server/src/engine/mod.rs:492), which asserts a a2a1q
reply promotes to a black queen.
/query-specs
reads the written rules, and a plain code question walks straight to the lines that implement them โ
spec and code kept in sync, so both stay trustworthy and their answers agree.
Why AI SDD works
Four episodes in, the pattern is clear. Spec-Driven Development isn't just a nice way to start a feature โ it changes what the whole project is. The specification stops being documentation that rots and becomes the thing everything else is measured against.
- The specs are the single source of truth. A powerful methodology where the written specification โ not the code, not someone's memory โ is the reference everything else answers to.
- The specs cover more than the business logic. They describe the application and its integration and unit tests, so behaviour and its proof are specified together, before a line is written.
- Within a well-defined architecture and clear code norms, verification is easy. When the shape of the system and its conventions are fixed up front, checking that the code matches the spec is straightforward rather than a matter of opinion.
- Logs become a proof of execution. Every step leaves a traceable line behind, so a problem in production can be retro-injected into the system to replay what happened and pinpoint the reason for the failure.
- The whole system becomes a knowledge base. Spec, tests, code and logs all describe the same truth โ so the entire project can be queried in plain language with an LLM, exactly as this episode showed.