@open-tomato/rafa 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/SKILL.md ADDED
@@ -0,0 +1,547 @@
1
+ ---
2
+ name: dev-planner
3
+ description: "Use when writing or parsing a PLAN-<stub>.md for the rafa loop — block syntax, task lines, and report format."
4
+ prevents: "plan tasks the loop's parser silently skips because they are shaped wrong"
5
+ signal: silent
6
+ when_to_use: "You are generating a plan document for the rafa loop, or reading one the loop will parse. Prevents: plan tasks the loop's parser silently skips because they are shaped wrong"
7
+ tags:
8
+ - dev
9
+ - planner
10
+ - typescript
11
+ stack:
12
+ - typescript
13
+ ---
14
+
15
+ # dev-planner — Structured Plan Format Specification
16
+
17
+ This skill specifies the format of plan documents produced for feature development work and consumed by the rafa agent loop (`src/rafa.ts`). It does not define agent behavior, personas, or workflow — those belong in agent profiles.
18
+
19
+ This file is the single source of that format. `rafa plan` inlines it, without its frontmatter, into the plan-generation prompt (`src/plan-prompt.md`), and the build copies it into `dist/` beside that prompt, so an installed rafa hands the format to projects that carry no dev-planner skill of their own. A rule of the format changes here; the prompt restates none of them.
20
+
21
+ ---
22
+
23
+ ## Files produced by a planning session
24
+
25
+ | File | Purpose |
26
+ | --- | --- |
27
+ | `<plan.dir>/PLAN-<stub>.md` | Full task checklist with structured blocks and technical context |
28
+ | `<plan.dir>/PREREQUISITES-<stub>.md` | Non-automatable setup steps required before the plan can run (only when any exist) |
29
+
30
+ `plan.dir` is `.rafa/plans` unless the project's `.rafa/config.yaml` names another directory. `rafa plan --spec=<spec>.md` generates both files (optionally `--stub=<name>`; the stub defaults to the spec's basename) and writes them into `plan.dir`, creating the directory when it is missing. It reads the spec from the project root, or from `specs.dir` (`.rafa/specs` by default) when the root holds no such file, and refuses to run when `<plan.dir>/PLAN-<stub>.md` already exists.
31
+
32
+ Execute a plan with `rafa start --plan=.rafa/plans/PLAN-<stub>.md`. With no `--plan`, `rafa start` runs `PLAN.md` in `plan.dir`, and falls back to a hand-written `PLAN.md` at the project root only when `plan.dir` holds no `PLAN.md`.
33
+
34
+ ---
35
+
36
+ ## Structured plan format (parser contract)
37
+
38
+ * Every task is a flat `- [ ]` checklist line. The loop's parser only reads
39
+ lines starting with `- [ ]` / `- [BLOCKED]` — anything else (headings,
40
+ prose, code blocks) is invisible to it, so never encode a task any other
41
+ way, and never nest tasks.
42
+ * Group tasks under `# Stage: {name}` headings (top-level `#`). Stage
43
+ headings are visual separators only; the checklist must read as one flat
44
+ sequence across stages.
45
+ * One atomic action per task; no compound tasks joined by "and". Tasks must
46
+ be independently completable in the order listed. A module-sized
47
+ deliverable (see Task sizing below) counts as one atomic action.
48
+ * Use imperative, specific wording ("Add Zod schema for `CreateJobRequest`",
49
+ not "Handle input validation").
50
+ * Avoid the words "current", "previous" and "next" in task text — the loop
51
+ injects task text into agent prompts, and relative references confuse the
52
+ agent about what is already done.
53
+ * Keep the plan focused on tasks and technical context only — no behavioral
54
+ instructions, opinions, or questions for follow-up. Link to relevant
55
+ `.claude/skills/` files where they clarify a task.
56
+ * Add a test task at the end of each stage at minimum; for larger stages,
57
+ test in smaller increments. Negative tests before positive tests; unit
58
+ tests before integration tests; test tasks live in the stage they cover.
59
+ Stage-end test tasks cover cross-module/integration behavior — a module's
60
+ own unit tests ride inside its module task (Task sizing below).
61
+
62
+ ### Plan layout
63
+
64
+ A plan document consists of:
65
+
66
+ 1. A `# Plan:` heading
67
+ 2. Optional structured blocks (`rafa:*` fenced code blocks)
68
+ 3. An optional `## Description` of technical context and background
69
+ 4. One or more `# Stage:` sections with tasks
70
+
71
+ ### Structured blocks
72
+
73
+ A plan document may contain fenced blocks of the form `` ```rafa:*``` `` that
74
+ carry metadata and context. Three kinds are read in phase 0:
75
+
76
+ * `rafa:plan` — Header fields in YAML format: `stub`, `issue` (optional),
77
+ `spec` (optional). The stub identifies the plan and defaults to the spec's
78
+ basename or "untitled". The loop uses the spec path to link the plan to the
79
+ change that prompted it.
80
+ * `rafa:context` — Markdown prose injected into the prompt of EVERY task under
81
+ this plan.
82
+ * `rafa:stage-context` — Markdown prose injected only into tasks under the
83
+ nearest `# Stage:` heading above it, providing stage-specific background.
84
+
85
+ Unknown `rafa:*` block kinds are retained and ignored — they do not cause
86
+ parse failures. This rule mirrors the declaration extras rule: an
87
+ unrecognized key in a task line does not prevent the line from being
88
+ dispatched.
89
+
90
+ A `rafa:stage-context` block belongs to the NEAREST `# Stage:` heading above
91
+ it. Blocks before the first stage heading have no effect.
92
+
93
+ #### Block strip rule
94
+
95
+ Every `rafa:*` block is stripped from the task text before it is quoted
96
+ to the agent, the operator log, or the commit message. The fenced block
97
+ syntax is a planning annotation, never an instruction.
98
+
99
+ ### Example
100
+
101
+ Every line is indented by two spaces, because the loop's checklist parser is
102
+ line-anchored and fence-blind: a bare `- [ ] ` at column 0 inside prose is
103
+ dispatched as a task. Measured on a draft plan, an unindented example line
104
+ became the dispatcher's answer for task one. Every example in this skill that
105
+ holds a checklist line is indented the same way; a task line in a real plan
106
+ starts at column 0.
107
+
108
+ ````markdown
109
+ # Plan: Feature Title
110
+
111
+ ```rafa:plan
112
+ stub: rafa-123-my-feature
113
+ issue: 123
114
+ spec: .rafa/specs/rafa-123-my-feature.md
115
+ ```
116
+
117
+ ```rafa:context
118
+ Context prose injected into every task.
119
+ ```
120
+
121
+ ## Description
122
+
123
+ Technical context and background. No behavioral instructions.
124
+
125
+ # Stage: schema
126
+
127
+ ```rafa:stage-context
128
+ Prose specific to the schema stage.
129
+ ```
130
+
131
+ - [ ] Add the Zod schema for `CreateJobRequest` with its unit tests {agent=loop-implementer}
132
+ - [ ] Add a test posting a malformed `CreateJobRequest` to the jobs route {agent=tdd-guide}
133
+ ````
134
+
135
+ ---
136
+
137
+ ## Structured blocks (`rafa:*` fenced code blocks)
138
+
139
+ The parser contract above names the kinds a plan carries and the rule that strips them. This section details each kind, and the report block an agent writes back.
140
+
141
+ ### `rafa:plan`
142
+
143
+ Header metadata for the entire plan. Fields are YAML key-value pairs.
144
+
145
+ ````markdown
146
+ ```rafa:plan
147
+ stub: rafa-123-my-feature
148
+ issue: 123
149
+ spec: .rafa/specs/rafa-123-my-feature.md
150
+ ```
151
+ ````
152
+
153
+ Recognized fields:
154
+ - `stub` — The plan identifier (string, following the `rafa-<n>-<slug>` format). Used to organize findings and dedupe rows across runs.
155
+ - `issue` — Optional GitHub issue number (integer, e.g., `123`). Links the work back to the `open-tomato/rafa` board.
156
+ - `spec` — Optional path to the specification document that guided the plan.
157
+ - `release` — Optional release level for the version bump this plan's pull request ships: `patch`, `minor`, `major` or `none`. Anything else is reported as `unusable-field` and read as if unset. Left out, the level is the highest among the change notes the plan's tasks stored, and `none` when they stored none.
158
+
159
+ Unknown keys are retained and ignored by the loop; they do not cause parsing to fail.
160
+
161
+ #### Naming conventions
162
+
163
+ All rafa work follows a consistent naming scheme across specifications, plans, branches, and pull requests. The board is GitHub Issues on `open-tomato/rafa`; every spec has one issue labelled `type:spec`, whose ID appears as `rafa-<n>`.
164
+
165
+ | Artifact | Pattern | Example |
166
+ | --- | --- | --- |
167
+ | Specification file | `.rafa/specs/rafa-<n>-<slug>.md` | `.rafa/specs/rafa-20-pr-commands.md` |
168
+ | Plan stub and directory | `rafa-<n>-<slug>` | `rafa-20-pr-commands` |
169
+ | `issue:` field in `rafa:plan` | `<n>` (number only) | `issue: 20` |
170
+ | Git branch | `feat/rafa-<n>-<slug>` | `feat/rafa-20-pr-commands` |
171
+ | Pull request title | `rafa-<n>: <title>` | `rafa-20: Add pull-request commands` |
172
+ | Pull request body | `Closes #<n>` | `Closes #20` |
173
+
174
+ The slug summarizes what the user gets, using two to four words joined by hyphens. The order of work lives in ONE place: the pinned "Roadmap" issue on the `open-tomato/rafa` board.
175
+
176
+ ### `rafa:context`
177
+
178
+ Prose that the loop injects into the context of **every task** in the plan. Appears between the plan-wide description and the task prompt.
179
+
180
+ ````markdown
181
+ ```rafa:context
182
+ This plan rewrites the authentication layer. See auth-design.md.
183
+ All tasks share the test fixtures in tests/auth-fixtures.ts.
184
+ ```
185
+ ````
186
+
187
+ One `rafa:context` block per plan. Appears at the top level (not inside a stage).
188
+
189
+ ### `rafa:stage-context`
190
+
191
+ Prose that the loop injects into the context of **only the tasks of one stage**: the stage whose `# Stage:` heading is the nearest above the block.
192
+
193
+ ````markdown
194
+ # Stage: Database Schema
195
+
196
+ ```rafa:stage-context
197
+ All schema changes go in migrations/ under a new timestamped file.
198
+ Run tests/schema-integration.test.ts after each migration.
199
+ ```
200
+
201
+ - [ ] Add the users table migration {agent=loop-implementer}
202
+ - [ ] Add the posts table migration {agent=loop-implementer}
203
+ ````
204
+
205
+ One `rafa:stage-context` block per stage (optional). The block binds to the nearest `# Stage:` heading above it whether it sits before that stage's tasks or after them, so place it straight after the heading, where a reader looks for it. When one stage holds two, the first is read and each later one is reported as `duplicate-block`. A block above every `# Stage:` heading belongs to no stage: the parser reports it as `orphan-stage-context` and does not read it.
206
+
207
+ ### `rafa:report` (agent output only)
208
+
209
+ Agents return a structured report block at the end of their output. The loop parses this to record findings, blockers, and other metadata.
210
+
211
+ ````markdown
212
+ ```rafa:report
213
+ status: done
214
+ feedback: |
215
+ Implemented the store interface with both NDJSON and SQLite backends.
216
+ All parity tests pass against the sibling's session logs.
217
+ findings:
218
+ - trigger: "when appending to an empty NDJSON store"
219
+ kind: gotcha
220
+ what: "file descriptor left open on failed writes"
221
+ cause: "resource cleanup not in finally block"
222
+ resolution: "wrap append in try/finally"
223
+ artifact: "EMFILE: too many open files"
224
+ signal: loud
225
+ - trigger: "SQLite schema migration timing"
226
+ kind: pattern
227
+ what: "migrations run on every store init, not just schema changes"
228
+ cause: "versioning check too coarse"
229
+ resolution: "check both version and table existence"
230
+ artifact: null
231
+ signal: silent
232
+ skills_used: [git-workflow, sqlite-patterns]
233
+ blockers: []
234
+ out_of_scope_bugs:
235
+ - what: "bun:sqlite connection pooling"
236
+ artifact: "SQLITE_MISUSE"
237
+ security: false
238
+ ```
239
+ ````
240
+
241
+ Report fields:
242
+
243
+ | Field | Type | Description |
244
+ | --- | --- | --- |
245
+ | `status` | string | The session's claim for its task: `done` or `blocked`. Required. `blocked` marks the task `[BLOCKED]` once its work is committed, and stops the run |
246
+ | `feedback` | string | One block of prose describing what was done and how it went |
247
+ | `findings` | list of objects | Findings discovered during the task (see below) |
248
+ | `skills_used` | list of strings | Names of skills referenced or applied |
249
+ | `blockers` | list of objects | What blocked the task. Any entry marks the task `[BLOCKED]` whatever `status` says, so write `[]` when nothing did |
250
+ | `out_of_scope_bugs` | list of objects | Bugs found that are outside this task's scope |
251
+ | `changes` | list of objects | The changelog for this task's own diff, one entry per user-visible change (see below). An absent list is empty with no issue; a task whose diff a user would notice nothing of writes one entry at level `none` |
252
+
253
+ Finding entry fields:
254
+
255
+ | Field | Type | Required | Description |
256
+ | --- | --- | --- | --- |
257
+ | `trigger` | string | yes | When/how the finding surfaced (e.g., "when running bun test under Docker") |
258
+ | `kind` | string | yes | Category: `gotcha`, `pattern`, `location` or `skill-suggestion` |
259
+ | `what` | string | yes | The finding itself |
260
+ | `cause` | string | no | Root cause or explanation |
261
+ | `resolution` | string | no | How to fix or work around it |
262
+ | `artifact` | string | no | Error message, file path, or code snippet that signals the finding |
263
+ | `signal` | string | yes | `loud` (it surfaced as a failure) or `silent` (it passed while wrong) |
264
+
265
+ Change note entry fields:
266
+
267
+ | Field | Type | Required | Description |
268
+ | --- | --- | --- | --- |
269
+ | `level` | string | yes | How much of a release the diff is worth: `patch`, `minor`, `major` or `none` |
270
+ | `area` | string | no | The changelog section the line groups under (e.g., `cli`, `store`) |
271
+ | `summary` | string | yes | One line a user of the project would understand, quoted |
272
+
273
+ Blocker/bug entry fields:
274
+
275
+ | Field | Type | Required | Description |
276
+ | --- | --- | --- | --- |
277
+ | `what` | string | yes | Description of the blocker or bug |
278
+ | `artifact` | string | no | Error message or diagnostic output |
279
+ | `security` | boolean | yes (bugs only) | Whether this is a security issue; never defaulted when missing |
280
+
281
+ ---
282
+
283
+ ## Block retention and stripping
284
+
285
+ The block strip rule means:
286
+
287
+ - A block planted in a task line does not appear in the dispatched prompt header
288
+ - A block in a stage description does not appear in the operator log line
289
+ - A block anywhere in the plan does not appear in a commit subject or commit body
290
+
291
+ The strip happens where the loop reads the next task, `findNextTask` in `src/utils/tracker.ts`, and it follows fences the way a renderer shows them (`src/plan/blocks.ts`):
292
+
293
+ - Only a closed, top-level `rafa:*` block hides its lines. Every other fence is transparent, so a `- [ ] ` line at column 0 inside a `markdown` or `text` illustration, or inside a `rafa:` fence nested in one, is dispatched as a task. A plan that illustrates the format indents its example lines.
294
+ - A `rafa:` fence that is never closed hides nothing, because taking the rest of the document as its body would end the plan with its tasks unrun. `parsePlan` reports it as `unclosed-block`, and each open task line after it as `task-in-block`.
295
+ - A caller that builds its own `TaskInfo`, such as a Tracker port implementation, bypasses the strip.
296
+ - An example that shows a `rafa:*` fence inside a larger fence needs the larger one to be longer, four backticks as in this skill: inside a three-backtick fence the first inner closing fence closes the outer one, and every later `rafa:` fence becomes a real block to any CommonMark reader.
297
+
298
+ ---
299
+
300
+ ## Task declarations (routing)
301
+
302
+ * A task line may carry a trailing declaration naming what the loop should
303
+ dispatch it with. Give one to every task.
304
+
305
+ ```text
306
+ - [ ] Add the Zod schema for `CreateJobRequest` {agent=loop-implementer}
307
+ - [ ] Rewrite the gates page {agent=doc-updater}
308
+ - [ ] Sweep the changed set {tools=Read,Grep,Glob model=haiku effort=low}
309
+ ```
310
+
311
+ * The block is the LAST brace group on the line, anchored at end of line,
312
+ holding no nested braces and at least one recognised key, and it is
313
+ never the whole task text. Two spaces separate it from the text.
314
+ Anything failing one of those rules stays task text — a task ending on
315
+ a code span carrying `{ "a": 1 }` is not a declaration. The one thing
316
+ that may follow the block is the loop's own: on a blocked task's
317
+ tracker line it writes `<!-- blocked: <text> -->` after the block, and
318
+ takes that comment off before the block is read. The plan itself never
319
+ holds one.
320
+ * Its tokens are space-separated `key=value` pairs, of the recognised keys
321
+ in the table below; an unrecognised key is kept for telemetry and maps
322
+ to no flag.
323
+
324
+ | Key | Value | Passed as | Beside `agent=` |
325
+ | --- | --- | --- | --- |
326
+ | `agent` | an agent name, the file stem of a definition under `.claude/agents/` | `--agent` | — |
327
+ | `model` | an alias (`opus`, `sonnet`, `haiku`, `fable`), or a full name such as `claude-opus-5` | `--model` | never passed: the definition names its model |
328
+ | `effort` | `low`, `medium`, `high`, `xhigh` or `max` | `--effort` | passed, unless the definition declares an `effort` of its own in its frontmatter |
329
+ | `budget` | US dollars above zero, as a plain decimal of at most six digits each side of the point, such as `0.50` | `--max-budget-usd` | always passed: a definition supplies no budget |
330
+ | `tools` | tool names, comma-separated with no spaces | `--tools` | never passed: the definition names its tool set |
331
+ | `skills` | skill names, comma-separated with no spaces | nothing: no flag carries a skill | kept on the record beside the agent |
332
+
333
+ * `agent=<name>` outranks `model` and `tools`, and `effort` only when its
334
+ definition declares one, as the last column says. The outranked keys
335
+ are still recorded, and the loop's routing line names them. Nothing
336
+ outranks `budget`.
337
+ * A session that ends on its budget marks its task `[BLOCKED]` with
338
+ `budget exceeded` as the blocker text, and the run stops there, as it
339
+ does on every blocked task. A budget of `0.01` ended a session before it
340
+ answered `Say hello` (Claude Code 2.1.268), so size a budget for the
341
+ whole session.
342
+ * A value the loop cannot use maps to no flag rather than failing the
343
+ task, so a misspelled level costs the routing silently. Spell each one
344
+ from the table above.
345
+ * The loop strips the block before the task text reaches the agent's
346
+ prompt, the operator log and the commit message: a declaration is a
347
+ planning annotation, never an instruction.
348
+ * Pick the agent by the task's SHAPE — not its subject or its verb —
349
+ from the `### Task shape to agent` table in `context/workflow.md`.
350
+ Prefer a row whose third column names a tracked
351
+ `.claude/agents/<name>.md` file, since a `user-level` row does not
352
+ travel with a fresh clone. Where no row fits, declare the granular
353
+ keys instead of an agent.
354
+
355
+ There is no default agent. A task with no declaration passes no routing flag and runs at the loop's defaults, as does one whose every value failed to parse.
356
+
357
+ The recognised keys are the `DECLARATION_KEYS` of `src/utils/declaration.ts`, whose `MODEL_ALIASES` and `EFFORT_LEVELS` hold the aliases and levels in the table, whose `parseBudgetUsd` reads a budget and whose `parseSkillList` reads a skill list; that module is the authority for this section, so change the two together.
358
+
359
+ ### `skills=` declaration
360
+
361
+ `skills` is a recognised key that maps to no flag: there is no CLI flag for a skill, so the names are parsed, kept on the dispatch record and stripped from the task text with the rest of the block. It stands on its own — `{skills=bun-testing}` is a declaration with no other key beside it — and a value that is not a comma-separated list of skill names is recorded as an issue rather than passed on.
362
+
363
+ ```markdown
364
+ - [ ] Port vitest tests to bun:test {agent=build-error-resolver skills=bun-testing,vitest-migration}
365
+ ```
366
+
367
+ ---
368
+
369
+ ## Task sizing (session economics)
370
+
371
+ * One task = one full agent session, so task COUNT is the plan's
372
+ wall-clock. Target **≤80 tasks**; exceed it only when the spec genuinely
373
+ enumerates more independent deliverables, and say so in the Description.
374
+ * A module ships as ONE task: implementation + its TSDoc + its colocated
375
+ unit tests together. Never split creation from documentation, or from
376
+ the tests that cover only that module.
377
+ * Documentation-only updates fold into the task whose change they document
378
+ (the same-commit doc law already requires the pairing).
379
+ * When several tasks wire into one file, give one of them the file's split
380
+ before the file nears the 800-line cap. No single "wire X into
381
+ `src/start.ts`" task grows it enough for its own diff to flag, and phase
382
+ 0's six took `src/start.ts` from 798 lines to 960 with no task owning the
383
+ split; only a whole-change-set review caught it.
384
+ * Keep as SEPARATE tasks: cross-cutting verification (fan-out gates,
385
+ invariant sweeps), live-seam runs, migrations, and close-out — these
386
+ preserve resumability where a halt is most likely.
387
+ * The RUNNER owns the push, the pull request, the merge with the base,
388
+ the wait for CI, and the version bump and changelog entry. After the last
389
+ task it runs a wrap-up session that promotes findings, compacts
390
+ `progress.txt`, merges `origin/main`, commits, pushes and opens (or
391
+ updates) the PR — and then polls that PR's checks, spending repair sessions
392
+ on a red or conflicting result. So a plan must NOT carry a task that opens
393
+ a PR, resolves a merge conflict, waits on CI, compacts `progress.txt`,
394
+ or writes a version bump or changelog entry. Two openers race: measured,
395
+ one run cut a second branch and opened a second PR for a single plan. A
396
+ close-out task SHOULD still take the mergeability reading (`git merge-tree
397
+ --write-tree origin/main HEAD`) and assemble the body material — the gate
398
+ captures, the test plan, the recorded debt — into the plan's close-out
399
+ notes for that wrap-up session to use.
400
+
401
+ ---
402
+
403
+ ## Three task-context injection modes
404
+
405
+ The loop supports three modes of injecting task context, configured at plan start via `--inject=` or in `.rafa/config.yaml`:
406
+
407
+ | Mode | Contents | Use case |
408
+ | --- | --- | --- |
409
+ | `full` | Whole plan (title, description, all stages, all tasks) | Wrap-up session; full context for retrospective |
410
+ | `stage` | Plan context, stage context, stage's checklist with earlier tasks shown as done, stage list | Task session; focused scope |
411
+ | `task` | Plan context, stage context, single task line | Minimal task focus (not used in phase 0) |
412
+
413
+ The wrap-up session always receives `full` regardless of the configured mode.
414
+
415
+ ---
416
+
417
+ ## PREREQUISITES.md format
418
+
419
+ `<plan.dir>/PREREQUISITES-<stub>.md` lists the setup a plan needs that no task can put in place. It is parsed: `src/preflight/prerequisites-md.ts` reads it and merges its items into the configured `prerequisites` for the plan beside it, `<plan.dir>/PLAN-<stub>.md`, and for no other plan.
420
+
421
+ ```markdown
422
+ # Prerequisites
423
+
424
+ ## Toolchain [auto]
425
+ - [ ] {Tool} installed: `{tool} --version`
426
+ - [ ] {Service name} answering on port {n}: `curl -sf http://localhost:{n}/health`
427
+ - [ ] {VAR_NAME} set, obtained from {where}: `test -n "${VAR_NAME}"`
428
+
429
+ ## Credentials [human]
430
+ - [ ] {Credential description}
431
+
432
+ ## Initial setup [start]
433
+ - [ ] {Setup step that applies only to first dispatch}: `{command to verify}`
434
+
435
+ ## Operator steps after the plan merges [human]
436
+ - [ ] {Step the operator takes once the plan has merged}
437
+ ```
438
+
439
+ How the file is read:
440
+ - An item is a line opening with `- [ ] `, continued by the indented lines directly below it that open no list item of their own. A ticked `- [x]` item and a `- [BLOCKED]` item are skipped, and nothing inside a fenced code block is read.
441
+ - An item's tag is the `[auto]`, `[human]`, or `[start]` written right after its box. Without one it takes its section's tag: a heading carrying `[auto]` tags its section `auto`, one carrying `[human]` or naming `manual`, `human`, `sign-off` or `team` tags it `human`, and one carrying `[start]` tags it `start`. A heading's tag reaches the deeper headings under it and ends at the next heading of its own level or shallower. An item under no tagged heading is `human`.
442
+ - An `auto` item's first backticked span is its probe. The item merges as a required prerequisite, the tier whose failure halts a run.
443
+ - A `human` item, and an `auto` item with no backticked span, is a reminder: named to the operator, never probed, and never halting a run.
444
+ - A `start` item's first backticked span is its probe. The item is probed on first dispatch only (skipped on resume), and failure halts a run on first dispatch.
445
+
446
+ Rules:
447
+ - Only include prerequisites that are genuinely non-automatable (installed services, external credentials, manual env var setup).
448
+ - Tag an item `auto` only when a shell command proves it, and write that command as the item's first backticked span, with nothing quoted before it. The command asks for no input and changes nothing.
449
+ - Tag an item `start` only when a shell command proves it and it should only be checked on the first dispatch of a plan (not on resume). Write the command as the item's first backticked span, with nothing quoted before it.
450
+ - Tag every step for after the plan merges `human`, as the heading above does, so no run ever executes one as a probe.
451
+ - Do not duplicate steps already documented in the repo's contributor docs (README, CONTRIBUTING, and the like).
452
+ - For example: do not mark `bun install` as a prerequisite if it is already documented as a required step for all development work.
453
+ - Link to the prerequisites file from the plan.
454
+
455
+ ---
456
+
457
+ ## Format validation
458
+
459
+ The loop parses plan files through `src/plan/parse.ts`:
460
+
461
+ - **Block parsing** (`src/plan/blocks.ts`): Reads fenced `rafa:*` blocks. Tolerates documents with no blocks. Retains unknown kinds.
462
+ - **Plan parsing** (`src/plan/parse.ts`): Builds the plan model over blocks and the checklist grammar. Each stage captures the tasks under it and the first `rafa:stage-context` block between its heading and the next `# Stage:` heading.
463
+ - **Declaration parsing** (`src/utils/declaration.ts`): Takes a trailing `{...}` block off a task line and reads its `key=value` entries. The block counts only when one of its keys is a recognized key (see Task declarations).
464
+ - **Report parsing** (`src/report/parse.ts`): Reads the last `rafa:report` block from agent output and parses its YAML body. Answers an explicit absence record if no block is present.
465
+
466
+ The parser is liberal with unknown input: unknown block kinds are kept, unknown declaration keys are kept, unknown YAML keys are kept. A report yields no report only when its last `rafa:report` block is missing, never closed, not valid YAML, or not a mapping. A field that is missing, or holds a value it cannot take (a `findings` that is not a list, a `signal` other than `loud` or `silent`), is reported as an issue and the rest of the report is still read. Quote every string value in a report: an unquoted value that opens with a backtick, `@`, `%` or `[` makes the whole block unreadable, and a `#` after a space starts a comment that silently cuts the value short.
467
+
468
+ Line numbers count two ways. A block's `span` (`readRafaBlocks`) and a `PlanIssue.line` count from one; `TaskInfo.lineNum` and the stage and task `lineNum` of the plan model count from zero, so compare `lineNum + 1` against either.
469
+
470
+ The report rules in this skill restate `src/report/parse.ts`, which is the authority: change the two together.
471
+
472
+ ---
473
+
474
+ ## Examples
475
+
476
+ Each module task below carries its own unit tests, and each stage closes on a test task over what its modules do together.
477
+
478
+ ### Minimal plan with no structured blocks
479
+
480
+ ```markdown
481
+ # Plan: Add API logging
482
+
483
+ ## Description
484
+
485
+ We need request/response logging on all endpoints.
486
+
487
+ # Stage: Core logger
488
+
489
+ - [ ] Add the logger utility at src/lib/logger.ts with its unit tests {agent=loop-implementer effort=low}
490
+ - [ ] Wire the logger into the request handler middleware {agent=loop-implementer effort=medium}
491
+ - [ ] Add an integration test holding one request and its response to the logged format {agent=tdd-guide effort=medium}
492
+ ```
493
+
494
+ ### Plan with full structured blocks and multiple stages
495
+
496
+ ````markdown
497
+ # Plan: Refactor authentication layer
498
+
499
+ ```rafa:plan
500
+ stub: rafa-456-refactor-auth
501
+ issue: 456
502
+ spec: .rafa/specs/rafa-456-refactor-auth.md
503
+ ```
504
+
505
+ ```rafa:context
506
+ All changes live under src/auth/. Test fixtures in tests/auth-fixtures.ts.
507
+ See docs/AUTH_ARCHITECTURE.md for the domain model.
508
+ ```
509
+
510
+ ## Description
511
+
512
+ The current auth system couples user identity to session state. This refactor separates concerns into three modules: identity verification, session management, and role-based access control.
513
+
514
+ # Stage: Identity layer
515
+
516
+ ```rafa:stage-context
517
+ Identity is stateless. Each module imports its own verification logic, never reaching back to sessions.
518
+ ```
519
+
520
+ - [ ] Add the cryptographic identity module at src/auth/identity.ts with its unit tests {agent=loop-implementer effort=high skills=crypto}
521
+ - [ ] Add identity integration tests verifying a signature across a key rotation {agent=tdd-guide effort=medium}
522
+
523
+ # Stage: Session management
524
+
525
+ ```rafa:stage-context
526
+ Sessions are keyed by session ID, never by user. One session can hold multiple identities (e.g., impersonation for debugging).
527
+ ```
528
+
529
+ - [ ] Rewrite the session store to decouple it from identity, moving its unit tests to the new store API {agent=loop-implementer effort=high}
530
+ - [ ] Add session integration tests holding two identities in one session {agent=tdd-guide effort=medium}
531
+
532
+ # Stage: Access control
533
+
534
+ - [ ] Add role-based access control gating at src/auth/rbac.ts with its unit tests {agent=loop-implementer effort=medium skills=rbac}
535
+ - [ ] Add RBAC integration tests across the identity and session modules {agent=tdd-guide effort=high}
536
+ ````
537
+
538
+ ---
539
+
540
+ ## Hand-written vs. generated plans
541
+
542
+ Both follow the same structured format. The difference is:
543
+
544
+ - **Hand-written plans** (by a planner agent): Prose in the description, stage contexts, and task granularity reflect careful thought about the work.
545
+ - **Generated plans** (by `rafa plan`): Produced from a spec document; structure is the same.
546
+
547
+ A hand-written plan read by `src/plan/parse.ts` produces the same data model as a generated one. This is the property the structured format enforces.