dorfl 0.1.2 → 0.2.0

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.
Files changed (364) hide show
  1. package/dist/advance-drivers.d.ts +1 -1
  2. package/dist/advance-drivers.d.ts.map +1 -1
  3. package/dist/advance-drivers.js +15 -3
  4. package/dist/advance-drivers.js.map +1 -1
  5. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  6. package/dist/advance-lifecycle-template.js +76 -4
  7. package/dist/advance-lifecycle-template.js.map +1 -1
  8. package/dist/advance-treeless-publish.d.ts +23 -0
  9. package/dist/advance-treeless-publish.d.ts.map +1 -1
  10. package/dist/advance-treeless-publish.js +41 -0
  11. package/dist/advance-treeless-publish.js.map +1 -1
  12. package/dist/advance.d.ts +59 -7
  13. package/dist/advance.d.ts.map +1 -1
  14. package/dist/advance.js +389 -82
  15. package/dist/advance.js.map +1 -1
  16. package/dist/advancing-lock.d.ts +30 -2
  17. package/dist/advancing-lock.d.ts.map +1 -1
  18. package/dist/advancing-lock.js +50 -0
  19. package/dist/advancing-lock.js.map +1 -1
  20. package/dist/agent-launch.d.ts +12 -0
  21. package/dist/agent-launch.d.ts.map +1 -1
  22. package/dist/agent-launch.js +22 -12
  23. package/dist/agent-launch.js.map +1 -1
  24. package/dist/agent-stop.d.ts +40 -2
  25. package/dist/agent-stop.d.ts.map +1 -1
  26. package/dist/agent-stop.js +30 -2
  27. package/dist/agent-stop.js.map +1 -1
  28. package/dist/apply-decide.d.ts +17 -3
  29. package/dist/apply-decide.d.ts.map +1 -1
  30. package/dist/apply-decide.js +36 -7
  31. package/dist/apply-decide.js.map +1 -1
  32. package/dist/apply-merge-action.d.ts +17 -5
  33. package/dist/apply-merge-action.d.ts.map +1 -1
  34. package/dist/apply-merge-action.js +43 -8
  35. package/dist/apply-merge-action.js.map +1 -1
  36. package/dist/apply-persist.d.ts +57 -26
  37. package/dist/apply-persist.d.ts.map +1 -1
  38. package/dist/apply-persist.js +166 -37
  39. package/dist/apply-persist.js.map +1 -1
  40. package/dist/apply-stuck-action.d.ts +151 -0
  41. package/dist/apply-stuck-action.d.ts.map +1 -0
  42. package/dist/apply-stuck-action.js +125 -0
  43. package/dist/apply-stuck-action.js.map +1 -0
  44. package/dist/brand.d.ts +12 -1
  45. package/dist/brand.d.ts.map +1 -1
  46. package/dist/brand.js +2 -1
  47. package/dist/brand.js.map +1 -1
  48. package/dist/cli-spinner.d.ts +1 -1
  49. package/dist/cli-spinner.js +1 -1
  50. package/dist/cli.d.ts +10 -0
  51. package/dist/cli.d.ts.map +1 -1
  52. package/dist/cli.js +320 -63
  53. package/dist/cli.js.map +1 -1
  54. package/dist/close-job-template.js +1 -1
  55. package/dist/complete.d.ts +6 -3
  56. package/dist/complete.d.ts.map +1 -1
  57. package/dist/complete.js +91 -37
  58. package/dist/complete.js.map +1 -1
  59. package/dist/config.d.ts +55 -9
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +56 -4
  62. package/dist/config.js.map +1 -1
  63. package/dist/continue-branch.d.ts.map +1 -1
  64. package/dist/continue-branch.js +22 -0
  65. package/dist/continue-branch.js.map +1 -1
  66. package/dist/decision-engine.d.ts +38 -10
  67. package/dist/decision-engine.d.ts.map +1 -1
  68. package/dist/decision-engine.js +10 -6
  69. package/dist/decision-engine.js.map +1 -1
  70. package/dist/do-autopick.d.ts.map +1 -1
  71. package/dist/do-autopick.js +11 -1
  72. package/dist/do-autopick.js.map +1 -1
  73. package/dist/do-config.d.ts +1 -1
  74. package/dist/do.d.ts +49 -56
  75. package/dist/do.d.ts.map +1 -1
  76. package/dist/do.js +419 -30
  77. package/dist/do.js.map +1 -1
  78. package/dist/env-config.d.ts.map +1 -1
  79. package/dist/env-config.js +12 -4
  80. package/dist/env-config.js.map +1 -1
  81. package/dist/failure-cause.d.ts +3 -2
  82. package/dist/failure-cause.d.ts.map +1 -1
  83. package/dist/failure-cause.js +28 -1
  84. package/dist/failure-cause.js.map +1 -1
  85. package/dist/format.d.ts +6 -6
  86. package/dist/format.d.ts.map +1 -1
  87. package/dist/format.js +10 -27
  88. package/dist/format.js.map +1 -1
  89. package/dist/frontmatter.d.ts +16 -4
  90. package/dist/frontmatter.d.ts.map +1 -1
  91. package/dist/frontmatter.js +27 -1
  92. package/dist/frontmatter.js.map +1 -1
  93. package/dist/gc.d.ts +65 -6
  94. package/dist/gc.d.ts.map +1 -1
  95. package/dist/gc.js +126 -9
  96. package/dist/gc.js.map +1 -1
  97. package/dist/github.d.ts +14 -0
  98. package/dist/github.d.ts.map +1 -1
  99. package/dist/github.js +73 -0
  100. package/dist/github.js.map +1 -1
  101. package/dist/harness.d.ts +25 -0
  102. package/dist/harness.d.ts.map +1 -1
  103. package/dist/harness.js.map +1 -1
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +2 -1
  107. package/dist/index.js.map +1 -1
  108. package/dist/install-ci-branch-protection.d.ts +132 -38
  109. package/dist/install-ci-branch-protection.d.ts.map +1 -1
  110. package/dist/install-ci-branch-protection.js +190 -48
  111. package/dist/install-ci-branch-protection.js.map +1 -1
  112. package/dist/install-ci-core.d.ts +21 -1
  113. package/dist/install-ci-core.d.ts.map +1 -1
  114. package/dist/install-ci-core.js +10 -3
  115. package/dist/install-ci-core.js.map +1 -1
  116. package/dist/install-ci-github.d.ts +32 -0
  117. package/dist/install-ci-github.d.ts.map +1 -1
  118. package/dist/install-ci-github.js +73 -0
  119. package/dist/install-ci-github.js.map +1 -1
  120. package/dist/install-skills.d.ts +123 -0
  121. package/dist/install-skills.d.ts.map +1 -0
  122. package/dist/install-skills.js +100 -0
  123. package/dist/install-skills.js.map +1 -0
  124. package/dist/intake-trigger-template.js +1 -1
  125. package/dist/intake.d.ts +17 -4
  126. package/dist/intake.d.ts.map +1 -1
  127. package/dist/intake.js +23 -7
  128. package/dist/intake.js.map +1 -1
  129. package/dist/integration-core.d.ts +10 -7
  130. package/dist/integration-core.d.ts.map +1 -1
  131. package/dist/integration-core.js +174 -60
  132. package/dist/integration-core.js.map +1 -1
  133. package/dist/integrator.d.ts +1 -1
  134. package/dist/integrator.d.ts.map +1 -1
  135. package/dist/integrator.js +19 -3
  136. package/dist/integrator.js.map +1 -1
  137. package/dist/isolation.d.ts +2 -2
  138. package/dist/isolation.d.ts.map +1 -1
  139. package/dist/isolation.js +9 -0
  140. package/dist/isolation.js.map +1 -1
  141. package/dist/item-lock.d.ts +196 -129
  142. package/dist/item-lock.d.ts.map +1 -1
  143. package/dist/item-lock.js +329 -254
  144. package/dist/item-lock.js.map +1 -1
  145. package/dist/ledger-write.d.ts +31 -26
  146. package/dist/ledger-write.d.ts.map +1 -1
  147. package/dist/ledger-write.js +100 -122
  148. package/dist/ledger-write.js.map +1 -1
  149. package/dist/lifecycle-gather.d.ts +17 -0
  150. package/dist/lifecycle-gather.d.ts.map +1 -1
  151. package/dist/lifecycle-gather.js +7 -1
  152. package/dist/lifecycle-gather.js.map +1 -1
  153. package/dist/lifecycle-pools.d.ts +40 -3
  154. package/dist/lifecycle-pools.d.ts.map +1 -1
  155. package/dist/lifecycle-pools.js +26 -6
  156. package/dist/lifecycle-pools.js.map +1 -1
  157. package/dist/merge-question-surfacer.d.ts +15 -1
  158. package/dist/merge-question-surfacer.d.ts.map +1 -1
  159. package/dist/merge-question-surfacer.js +18 -3
  160. package/dist/merge-question-surfacer.js.map +1 -1
  161. package/dist/migrate-stuck-locks.d.ts +129 -0
  162. package/dist/migrate-stuck-locks.d.ts.map +1 -0
  163. package/dist/migrate-stuck-locks.js +355 -0
  164. package/dist/migrate-stuck-locks.js.map +1 -0
  165. package/dist/needs-attention.d.ts +293 -16
  166. package/dist/needs-attention.d.ts.map +1 -1
  167. package/dist/needs-attention.js +532 -57
  168. package/dist/needs-attention.js.map +1 -1
  169. package/dist/orphan-sidecar.d.ts +8 -4
  170. package/dist/orphan-sidecar.d.ts.map +1 -1
  171. package/dist/orphan-sidecar.js +35 -2
  172. package/dist/orphan-sidecar.js.map +1 -1
  173. package/dist/pi-harness.d.ts +16 -0
  174. package/dist/pi-harness.d.ts.map +1 -1
  175. package/dist/pi-harness.js +82 -2
  176. package/dist/pi-harness.js.map +1 -1
  177. package/dist/placement.d.ts +3 -3
  178. package/dist/placement.js +1 -1
  179. package/dist/prd-to-spec.d.ts.map +1 -1
  180. package/dist/prd-to-spec.js +9 -5
  181. package/dist/prd-to-spec.js.map +1 -1
  182. package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
  183. package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
  184. package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
  185. package/dist/protocol/TASKING-PROTOCOL.md +3 -1
  186. package/dist/protocol/WORK-CONTRACT.md +22 -18
  187. package/dist/protocol/task-template.md +1 -1
  188. package/dist/reap-branches.d.ts +12 -9
  189. package/dist/reap-branches.d.ts.map +1 -1
  190. package/dist/reap-branches.js +25 -7
  191. package/dist/reap-branches.js.map +1 -1
  192. package/dist/recover-isolated.d.ts +12 -0
  193. package/dist/recover-isolated.d.ts.map +1 -1
  194. package/dist/recover-isolated.js +5 -0
  195. package/dist/recover-isolated.js.map +1 -1
  196. package/dist/repo-config.d.ts +23 -2
  197. package/dist/repo-config.d.ts.map +1 -1
  198. package/dist/repo-config.js +53 -7
  199. package/dist/repo-config.js.map +1 -1
  200. package/dist/repo-mirror.d.ts.map +1 -1
  201. package/dist/repo-mirror.js +18 -2
  202. package/dist/repo-mirror.js.map +1 -1
  203. package/dist/review-verdict.js +1 -1
  204. package/dist/review-verdict.js.map +1 -1
  205. package/dist/run.d.ts +1 -1
  206. package/dist/run.d.ts.map +1 -1
  207. package/dist/run.js +54 -19
  208. package/dist/run.js.map +1 -1
  209. package/dist/scan.d.ts +17 -8
  210. package/dist/scan.d.ts.map +1 -1
  211. package/dist/scan.js +22 -8
  212. package/dist/scan.js.map +1 -1
  213. package/dist/select-priority.d.ts +2 -2
  214. package/dist/select-priority.js +1 -1
  215. package/dist/select-priority.js.map +1 -1
  216. package/dist/sidecar-apply.js +1 -1
  217. package/dist/sidecar-apply.js.map +1 -1
  218. package/dist/sidecar.d.ts +45 -10
  219. package/dist/sidecar.d.ts.map +1 -1
  220. package/dist/sidecar.js +82 -2
  221. package/dist/sidecar.js.map +1 -1
  222. package/dist/skills/answer-questions/SKILL.md +89 -0
  223. package/dist/skills/capture-signal/SKILL.md +52 -0
  224. package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
  225. package/dist/skills/drive-tasks/SKILL.md +218 -0
  226. package/dist/skills/from-idea/SKILL.md +83 -0
  227. package/dist/skills/merge-prs/SKILL.md +70 -0
  228. package/dist/skills/orchestrate/SKILL.md +101 -0
  229. package/dist/skills/promote/SKILL.md +35 -0
  230. package/dist/skills/review/SKILL.md +16 -0
  231. package/dist/skills/setup/SKILL.md +258 -0
  232. package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
  233. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
  234. package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
  235. package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
  236. package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
  237. package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
  238. package/dist/skills/setup/protocol/spec-template.md +71 -0
  239. package/dist/skills/setup/protocol/task-template.md +65 -0
  240. package/dist/skills/surface-questions/SKILL.md +16 -0
  241. package/dist/skills/to-spec/SKILL.md +34 -0
  242. package/dist/skills/to-task/SKILL.md +19 -0
  243. package/dist/skills/triage-observations/SKILL.md +78 -0
  244. package/dist/skills/work/SKILL.md +51 -0
  245. package/dist/slug-namespace.d.ts +5 -5
  246. package/dist/slug-namespace.js +5 -5
  247. package/dist/slug-namespace.js.map +1 -1
  248. package/dist/spec-complete.d.ts.map +1 -1
  249. package/dist/spec-complete.js +2 -3
  250. package/dist/spec-complete.js.map +1 -1
  251. package/dist/start.d.ts.map +1 -1
  252. package/dist/start.js +51 -57
  253. package/dist/start.js.map +1 -1
  254. package/dist/surface-gate.d.ts.map +1 -1
  255. package/dist/surface-gate.js +10 -3
  256. package/dist/surface-gate.js.map +1 -1
  257. package/dist/surface-persist.js +1 -1
  258. package/dist/surface-persist.js.map +1 -1
  259. package/dist/tasker-review-loop.d.ts +4 -3
  260. package/dist/tasker-review-loop.d.ts.map +1 -1
  261. package/dist/tasker-review-loop.js.map +1 -1
  262. package/dist/tasking-lock.d.ts +2 -2
  263. package/dist/tasking-lock.d.ts.map +1 -1
  264. package/dist/tasking-lock.js +53 -32
  265. package/dist/tasking-lock.js.map +1 -1
  266. package/dist/tasking.d.ts +40 -6
  267. package/dist/tasking.d.ts.map +1 -1
  268. package/dist/tasking.js +224 -12
  269. package/dist/tasking.js.map +1 -1
  270. package/dist/triage-persist.d.ts +10 -6
  271. package/dist/triage-persist.d.ts.map +1 -1
  272. package/dist/triage-persist.js +34 -4
  273. package/dist/triage-persist.js.map +1 -1
  274. package/dist/vendor/incur/agents.d.ts +58 -0
  275. package/dist/vendor/incur/agents.d.ts.map +1 -0
  276. package/dist/vendor/incur/agents.js +343 -0
  277. package/dist/vendor/incur/agents.js.map +1 -0
  278. package/dist/verify-workflow-template.js +1 -1
  279. package/dist/watch-session.d.ts +11 -3
  280. package/dist/watch-session.d.ts.map +1 -1
  281. package/dist/watch-session.js +94 -7
  282. package/dist/watch-session.js.map +1 -1
  283. package/dist/work-layout.d.ts +7 -3
  284. package/dist/work-layout.d.ts.map +1 -1
  285. package/dist/work-layout.js +6 -3
  286. package/dist/work-layout.js.map +1 -1
  287. package/dist/workspace.d.ts +1 -1
  288. package/package.json +2 -2
  289. package/src/advance-drivers.ts +16 -3
  290. package/src/advance-lifecycle-template.ts +89 -4
  291. package/src/advance-treeless-publish.ts +45 -0
  292. package/src/advance.ts +467 -87
  293. package/src/advancing-lock.ts +96 -1
  294. package/src/agent-launch.ts +37 -12
  295. package/src/agent-stop.ts +60 -2
  296. package/src/apply-decide.ts +36 -7
  297. package/src/apply-merge-action.ts +45 -7
  298. package/src/apply-persist.ts +228 -54
  299. package/src/apply-stuck-action.ts +260 -0
  300. package/src/brand.ts +14 -2
  301. package/src/cli-spinner.ts +1 -1
  302. package/src/cli.ts +401 -70
  303. package/src/close-job-template.ts +1 -1
  304. package/src/complete.ts +110 -52
  305. package/src/config.ts +107 -13
  306. package/src/continue-branch.ts +22 -0
  307. package/src/decision-engine.ts +55 -17
  308. package/src/do-autopick.ts +12 -1
  309. package/src/do-config.ts +1 -1
  310. package/src/do.ts +511 -38
  311. package/src/env-config.ts +12 -4
  312. package/src/failure-cause.ts +30 -1
  313. package/src/format.ts +10 -30
  314. package/src/frontmatter.ts +35 -5
  315. package/src/gc.ts +172 -9
  316. package/src/github.ts +78 -0
  317. package/src/harness.ts +25 -0
  318. package/src/index.ts +10 -0
  319. package/src/install-ci-branch-protection.ts +282 -57
  320. package/src/install-ci-core.ts +30 -3
  321. package/src/install-ci-github.ts +86 -0
  322. package/src/install-skills.ts +166 -0
  323. package/src/intake-trigger-template.ts +1 -1
  324. package/src/intake.ts +46 -11
  325. package/src/integration-core.ts +199 -65
  326. package/src/integrator.ts +22 -9
  327. package/src/isolation.ts +11 -2
  328. package/src/item-lock.ts +485 -341
  329. package/src/ledger-write.ts +135 -150
  330. package/src/lifecycle-gather.ts +29 -6
  331. package/src/lifecycle-pools.ts +67 -9
  332. package/src/merge-question-surfacer.ts +28 -4
  333. package/src/migrate-stuck-locks.ts +451 -0
  334. package/src/needs-attention.ts +857 -84
  335. package/src/orphan-sidecar.ts +47 -6
  336. package/src/pi-harness.ts +82 -2
  337. package/src/placement.ts +3 -3
  338. package/src/prd-to-spec.ts +13 -5
  339. package/src/reap-branches.ts +38 -14
  340. package/src/recover-isolated.ts +17 -0
  341. package/src/repo-config.ts +55 -6
  342. package/src/repo-mirror.ts +23 -1
  343. package/src/review-verdict.ts +1 -1
  344. package/src/run.ts +64 -18
  345. package/src/scan.ts +22 -8
  346. package/src/select-priority.ts +3 -3
  347. package/src/sidecar-apply.ts +1 -1
  348. package/src/sidecar.ts +138 -11
  349. package/src/slug-namespace.ts +7 -7
  350. package/src/spec-complete.ts +2 -3
  351. package/src/start.ts +53 -59
  352. package/src/surface-gate.ts +10 -3
  353. package/src/surface-persist.ts +1 -1
  354. package/src/tasker-review-loop.ts +4 -3
  355. package/src/tasking-lock.ts +52 -36
  356. package/src/tasking.ts +261 -17
  357. package/src/triage-persist.ts +56 -12
  358. package/src/vendor/incur/LICENSE +21 -0
  359. package/src/vendor/incur/README.md +19 -0
  360. package/src/vendor/incur/agents.ts +392 -0
  361. package/src/verify-workflow-template.ts +1 -1
  362. package/src/watch-session.ts +102 -7
  363. package/src/work-layout.ts +6 -3
  364. package/src/workspace.ts +1 -1
@@ -0,0 +1,122 @@
1
+ # REVIEW-PROTOCOL
2
+
3
+ The **review discipline** the autonomous runner invokes by name on a `work/`-protocol artifact before that artifact is _trusted_ — a task before it lands/is claimed, code in a work PR against the task that specified it, a spec before tasking, a set of tasks before they land, or a captured note. The runner spawns a fresh-context agent and tells it to "run the review protocol"; that agent reads THIS doc and applies its standard.
4
+
5
+ The protocol describes how work is AUTHORED (`WORK-CONTRACT.md`, the templates), CLAIMED and BUILT (`CLAIM-PROTOCOL.md`, the Gate-1 `verify` floor), and JUDGED BEFORE LANDING (this doc). It is in-band in every set-up repo, never host-specific. (The human-facing pointer is `skills/review/SKILL.md`; the standard lives here.)
6
+
7
+ > This doc is **protocol-native**: it assumes the repo uses the `work/` contract and reviews the artifact AGAINST that contract. Every bare "WORK-CONTRACT" / "ADR-FORMAT" mention below refers to `work/protocol/<doc>` in the repo under review.
8
+
9
+ You **emit a verdict; you do not act on it** — see [Your output](#your-output). Routing the verdict (to `needsAnswers`, needs-attention, a batch file, a merge) is the caller's job. This discipline is the _assessment_, not the disposition.
10
+
11
+ ## When to use vs. not
12
+
13
+ - **Use** to review: a **task** (well-cut? claim-ready?); **code** in a work PR (does it deliver the task it claims?); a **spec** (taskable? gate axes honest?); a **note** (right bucket? actionable?); or a **set of tasks** — the whole-SET lens: **graph coherence / gaps / overlap / goal-composition** (does the dependency graph cohere, are there set-level gaps or overlapping/duplicated tasks, and do they compose into the spec/ADR goal?). The set-level checks live in lens 3 (cross-artifact composition) and lens 5 (the destination check).
14
+ - **Don't** use it to _produce_ the artifact (that's `to-spec` / `to-task` / the build agent), nor to _route_ the verdict (that's the caller — a review gate, a conductor skill, or a human). This protocol only assesses.
15
+
16
+ ## The core disciplines (what makes a review thorough, not shallow)
17
+
18
+ These are _why_ this beats a single "looks fine" pass — apply them throughout:
19
+
20
+ 1. **Run a SEQUENCE of distinct angles, not one pass.** Each lens below is a different framing. Re-running the _same_ angle converges on nothing fast; changing the angle keeps finding distinct _classes_ of defect. Stop when a full pass across the angles finds nothing NEW.
21
+ 2. **A reviewer is ADVERSARIAL.** Try to _break_ the artifact ("attack these tasks: granularity? dependency order? gate correctness? drift? a missed seam?"), don't confirm it. Self-review in the producing context rubber- stamps; review as if someone else wrote it (ideally a fresh/cold read).
22
+ 3. **Verify against what ACTUALLY LANDED, not intent or memory.** Read the real code / the committed artifact — not what you _think_ a change did. Edits silently fail; specs drift. Trust the bytes on disk.
23
+ 4. **A SECOND instance of the same finding is a SIGNAL, not noise.** "I've seen this shape before" → generalise the fix, don't patch instances one by one (this applies to the artifact's defects _and_ to your own repeated mistakes).
24
+ 5. **Defects concentrate in the TASK/SPEC more than in the code.** Agents build what they're told, correctly; the expensive bugs are an ambiguous premise, a wrong "reuse X", an assumed-but-absent seam, a stale central assumption. Spend the most scrutiny on the spec.
25
+ 6. **Flag, don't guess.** When something is genuinely unresolved, that is a `block`/`needsAnswers` finding — not a guess dressed as approval. A false "looks fine" ships wrong-but-compiling work; a flagged question costs one human glance.
26
+ 7. **Weight findings by REAL impact — do not cargo-cult the lenses.** A finding is only worth raising if acting on it changes an outcome someone would actually hit. A technically-true nit that no reader/builder/runtime will ever be bitten by is NOT a `block` (often not even worth recording). Running a lens as a checklist and reporting conformance misses ("this optional field is empty", "a list could be renumbered") as blocking is the failure mode this rule exists to stop: it buries the findings that matter under bookkeeping noise. Ask of each finding: _who hits this, and what breaks?_ No answer → drop it. The lenses find candidates; impact decides severity.
27
+
28
+ ## The lenses — apply IN ORDER, ending in the destination check
29
+
30
+ For each lens: _what it catches_ + _how to apply it (against the contract)_.
31
+
32
+ ### 1. Claim-vs-reality
33
+
34
+ Every concrete claim the artifact makes, checked against the real world.
35
+
36
+ - Task/spec: each referenced symbol, path, function signature, "reuse X" — does it exist and have the assumed shape? (Catches ghost paths, wrong module homes, "reuse X" where X is private / wrongly-shaped.)
37
+ - Code: does the diff actually do what its task/commit claims?
38
+ - Any doc: does it match what landed in `tasks/done/` and the relevant ADRs/findings?
39
+ - **Drift is a `needs-attention` / `needsAnswers` signal**, never something to paper over (WORK-CONTRACT.md). A task built on a stale premise is a `block`.
40
+
41
+ ### 2. Cleanup-vs-behaviour
42
+
43
+ Anything framed as removal / dead-code / no-op, checked for **hidden live behaviour** (e.g. a flag claimed "just cleanup" that is actually still read somewhere). If a "cleanup" changes behaviour, that's a defect or an unowned scope.
44
+
45
+ This lens also owns **acceptance-criteria conformance** for code:
46
+
47
+ - Does the code meet every acceptance criterion of its task?
48
+ - **Shared-write isolation rule (WORK-CONTRACT.md):** if the code writes to a shared/global location (a real home/config dir, a system path, a shared service, an external tool's store), do its tests ISOLATE that location (temp/scratch via the named env/config lever) AND assert the real one is UNTOUCHED? A missing isolation test is a `block` — it silently pollutes and can crash unrelated tools.
49
+
50
+ ### 3. Cross-artifact composition (contract conformance)
51
+
52
+ Do the artifacts COMPOSE, and do they obey the contract?
53
+
54
+ - **Composition:** handoffs (one task ships a stub another fills), shared helpers with no owner, two tasks editing the SAME file/command in parallel (a merge conflict waiting to happen — should carry a `blockedBy` to serialise), one task deleting another's live tooling, cross-task side-effects.
55
+ - **Wide-refactor sub-checklist** (a pervasive rename / identifier cutover split into a `rename-*` chain — complements `TASKING-PROTOCOL.md` §3a):
56
+ - **Expand-first / per-batch compilability.** For EACH batch, verify it is either **indirected-safe** (the renamed identifier is read through a key/indirection so a hard swap keeps `pnpm -r build` green in isolation) OR **expand-first** (a prior batch added the new form beside the old across the whole non-indirected surface, this batch is an additive migrate, a later contract batch removes the aliases). A linear sequence of hard-swap `rename-*` batches over NON-indirected identifiers cannot compile per-batch and must be restructured into expand → migrate → contract. (Motivation: the spec→spec identity chain shipped review-clean, yet batch 2 stopped at build time — `fm.spec` / `'spec'` were non-indirected, read at ~28 call sites, and could not compile alone.)
57
+ - **File ownership per clause.** For EACH acceptance clause of EACH batch, identify which file(s) it must change and verify THIS batch owns them; a clause whose file lives in another batch is a scope-fence violation and must be moved to the batch that owns the file. (Motivation: batch 2 carried a `do spec:` / `advance spec:` verb-dispatch clause, but the dispatcher lives in `do.ts` / `advance.ts` / `advance-drivers.ts` / `do-autopick.ts` — batch 4's files — so the clause was unsatisfiable inside batch 2's scope fence and the `do` agent correctly STOPPED.)
58
+ - **Contract conformance (assume these rules; flag violations):**
59
+ - **status = folder**, never a frontmatter field; **one file per item**; **no shared index/manifest**.
60
+ - **content-derived slug**, never a counter; **camelCase** field names (`humanOnly`, `needsAnswers`, `blockedBy`, `taskedAfter`).
61
+ - **gate axes set HONESTLY** — `humanOnly` (a human must drive this) and `needsAnswers` (open questions, listed in the body) reflect the artifact's real nature; a task's gate is decided from _building that task_, NOT inherited from its spec; a falsely-complete `needsAnswers:false` is a defect.
62
+ - **`blockedBy` / `spec` / `covers`** present and correct (`spec` required iff `covers` is set); deps reference real slugs.
63
+ - **bucket polarity** for notes: _observation_ = spotted/unverified (append-only); _finding_ = verified EXTERNAL/domain ground truth; _ADR_ = a decision WE made + why (in `docs/adr/`). A note in the wrong bucket is a finding.
64
+ - **a task's `## Prompt`** is self-contained (an agent could start from the file alone) and includes the drift-check.
65
+
66
+ ### 4. Conceptual coherence (does it fit the system's LANGUAGE?)
67
+
68
+ The artifact may be internally correct yet INCOHERENT against the concepts the system already has. This lens catches the conflation that mechanical conformance (lens 3) and claim-checking (lens 1) miss — a single concept applied at the WRONG LAYER (e.g. a policy gate placed on an explicit verb when it should gate only the autonomous selection step), an inconsistency that can otherwise survive across multiple tasks and specs.
69
+
70
+ For each concept / flag / config key / verb / status the artifact introduces or touches, ask three questions:
71
+
72
+ - **(a) Consistent meaning?** Is the term used the SAME way it is already defined elsewhere (the project's `CONTEXT.md` glossary is the source of truth, plus the ADRs, other tasks, the code)? A term that silently RE-MEANS an existing word — or means two different things in two places — is incoherent.
73
+ - **(b) Right layer?** Is the concept placed at the conceptual layer it actually belongs to? (A policy gate on the autonomous-SELECTION step vs on the explicit VERB; a knob on the loop vs on the one-shot; a check on "who invoked" when the system cannot even distinguish the invokers.) A correct mechanism at the wrong layer is incoherent.
74
+ - **(c) Duplicate / overlap?** Does it FORK an existing concept under a new name instead of reusing or renaming the one that already exists? (Two flags meaning "isolate"; a new status that is really an existing one; a second lock primitive.) If it overlaps, the artifact should reuse/rename, not add.
75
+
76
+ A concept that is coherent in ISOLATION but incoherent against the system's existing language is a `block` (or, for a task/spec not yet built, a `needsAnswers` / re-scope). Coherence is a first-class quality, not a nicety: an incoherent concept is debt that compounds silently across every artifact that later reuses the muddled term. When you spot the muddle, also check whether the GLOSSARY (`CONTEXT.md`) needs the term pinned so the next author cannot re-fork it.
77
+
78
+ ### 5. The destination check (the final, highest-value move)
79
+
80
+ _"If every task is built / the code is merged exactly as written, do we END UP WITH the system the spec/ADR describes?"_ — distinct from per-piece correctness, and the strongest signal a decomposition is trustworthy (especially with no human).
81
+
82
+ - Take the spec/ADR end-state as the target; **map every promised element to a delivering task** — a hole = an element no task delivers.
83
+ - Confirm **coverage is complete + non-duplicated** — every user story covered exactly once.
84
+ - Audit the **deletion sweep** — a new system means the OLD surface is GONE; every removal owned by exactly one task, none unowned or double-owned.
85
+ - Check for **orphans** (a task delivering something the end-state doesn't need) and that assumed-pre-existing foundations actually exist.
86
+ - Confirm **deliberate non-deliveries are flagged** as named follow-ups, not silently missing.
87
+
88
+ **`approve` must mean "provably reaches the spec/ADR goal," not "each piece looks fine."** If this lens finds a hole, it is the most important thing to `block`.
89
+
90
+ ## Your output
91
+
92
+ Emit a verdict per reviewed item — and **write nothing** (no frontmatter edits, no `git mv`, no file changes). The caller routes it.
93
+
94
+ The verdict is a single JSON object with this shape (the **emitted-shape contract**). The runtime PARSER is the source of truth for the shape; this prose mirrors what it enforces:
95
+
96
+ - `verdict` — REQUIRED, exactly `"approve"` or `"block"`. `approve` lets the artifact proceed; `block` keeps it out (the caller routes to needs-attention / `needsAnswers` / a comment).
97
+ - `findings` — REQUIRED, an array (possibly empty). Each finding is:
98
+ - `severity` — `"blocking"` (keeps the item out of "ready") or `"non-blocking"` (a nit / future improvement). Be honest about which.
99
+ - `question` — the question / defect, with enough context to act WITHOUT re-deriving it.
100
+ - `context` — OPTIONAL, the relevant excerpt, `file:line`, or reasoning.
101
+
102
+ Several caller-specific optional channels MAY ride on the same JSON object. They are OPT-IN: each caller's prompt names which ones to fill. The shape they take when present:
103
+
104
+ - `review` — a single deliberately-authored, human-readable REVIEW string the caller posts as a comment on the PR (leads with Approved/Blocked, then the lenses + the destination-check reasoning). Plain text inside the JSON string. Advisory only — never gates the verdict.
105
+ - `edits` — full-content edits to apply between passes in an improver loop: an array of `{path, content}`, where `path` is a repo-relative target (typically `work/tasks/backlog/<slug>.md`) and `content` is the FULL replacement file body. The runner writes them; the agent does no disk/git.
106
+ - `edit` — for the lone-task review only: a single in-memory full-replacement task BODY (the markdown AFTER the frontmatter), applied before the next round. No path — the task has not been emitted yet.
107
+ - `questions` — an array of strings carrying open questions for a human to answer (the non-converge sink in the lone-task review).
108
+ - `uncertainTasks` — for the tasker improver loop: specific tasks to emit `needsAnswers: true` with the questions in their bodies. Each is `{path, questions: string[]}`.
109
+ - `decompositionUnclear` — for the tasker improver loop: when the WHOLE decomposition is unsound, `{questions: string[]}` to record as the spec's needs-attention reason.
110
+
111
+ Any unrecognised field is ignored by the parser; the caller routes on `verdict`/`findings` plus the channels its prompt asked for.
112
+
113
+ **Keep the JSON parseable** (a malformed verdict strands the work). Emit defensively: emit it MINIFIED on ONE single line; do NOT use a literal double-quote `"` inside any string value (paraphrase, or use single quotes — a dropped escape on an inner `"` is the most common corruption); keep every string field SHORT and SINGLE-LINE (write `\n` literally, never embed a real newline / tab / control char); and cap the longest field (`review`) at roughly 1500 characters — say less, not more.
114
+
115
+ ### How callers route your verdict (not your job — for orientation only)
116
+
117
+ - a **review GATE** routes a `block` → set `needsAnswers: true` on the artifact (question in its body) or surface a `work/questions/<type>-<slug>.md` sidecar + `needsAnswers: true` (needs-attention; post `retire-stuck-lock-state` the lock is never left `stuck` — that state is retired); `approve` → let it land / auto-merge.
118
+ - a **conductor** (e.g. `drive-tasks`/`orchestrate`) routes a `block` → into its stuck-set / batched questions for the human; `approve` → merge / advance.
119
+
120
+ ## Scope fence
121
+
122
+ This doc is the review _protocol/discipline_ only. The review **gates** — _when_ review runs (task-time / PR-time), per-repo toggles, the model override, the `--propose` PR arbiter, auto-merge-on-approve, the role/seam wiring, the trust resolver — are NOT here; they live in the runner machinery. This protocol assumes nothing about its caller beyond "you will route my verdict."
@@ -0,0 +1,135 @@
1
+ # SURFACE-PROTOCOL
2
+
3
+ The **surface-questions discipline** the autonomous runner invokes by name on ONE `work/` item before that item can advance a lifecycle rung. The runner spawns a fresh-context agent and tells it to "run the surface protocol"; that agent reads THIS doc and applies its standard.
4
+
5
+ The protocol describes how work is AUTHORED (`WORK-CONTRACT.md`, the templates), CLAIMED and BUILT (`CLAIM-PROTOCOL.md`, the `verify` floor), JUDGED BEFORE LANDING (`REVIEW-PROTOCOL.md`) and — when judgement is genuinely open — has its OPEN QUESTIONS SURFACED for a human to answer (this doc). It is in-band in every set-up repo, never host-specific. (The human-facing pointer is `skills/surface-questions/SKILL.md`; the standard lives here.)
6
+
7
+ > This doc is **protocol-native**: it assumes the repo uses the `work/` contract and surfaces the open-judgement residue of an item AGAINST that contract. Every bare "WORK-CONTRACT" / "REVIEW-PROTOCOL" mention below refers to `work/protocol/<doc>` in the repo under work.
8
+
9
+ It is **doc-shaped, exactly like `review`**: you produce an assessment (here, a set of questions) and the **caller routes/persists it**. You never set `needsAnswers`, never write a sidecar, never `git mv`, never commit. The advance engine's surface-question rung spawns you fresh-context, takes your questions, and **ITSELF writes the sidecar (CAS-atomic)** — exactly as the review gate uses `review`. **The skill judges; the engine persists.**
10
+
11
+ ## The two laws (state them; they keep the tool honest)
12
+
13
+ 1. **GATHER-only.** Your job is to FORMULATE the open questions for the item — by composing the existing reviewing/triage judgement, not by re-deriving it. You add no new disposition of the item.
14
+ 2. **PERSIST-NEVER.** You EMIT questions and **write nothing** (no `needsAnswers` edit, no sidecar, no `git mv`, no commit) — mirroring `review`. The caller (the advance engine, or a human) routes and persists. If you are tempted to write a file, STOP: that is the engine's job (or, by hand, the `advance` verb — see [the no-runner path](#the-no-runner-path)).
15
+
16
+ **The humility rule (the heart of it):** you **surface the residue, you NEVER invent an answer.** A `default:` is a _suggested_ default offered for the human's convenience — it is a humility aid, not a decision, and it never substitutes for the human answering. Automating answer creation is rejected by design; the human is the clock. When judgement is genuinely open, that is a QUESTION — never a guess dressed as a resolution.
17
+
18
+ ## When to use vs. not
19
+
20
+ - **Use** to formulate the open questions for ONE item before it can advance a lifecycle rung — a task or spec that may carry open judgement, an untriaged observation, code in a work PR — whether you are the advance engine's surface rung or a human doing it by hand with no runner.
21
+ - **Don't** use it to PRODUCE an item (that is `to-spec` / `to-task` / the build agent), to APPLY a human's answer or advance the item (that is the engine's apply rung / the `advance` verb), or to PERSIST the questions (the engine, or the `advance` verb, owns the write). And do not use it to invent answers — there is no answer-creation here, by design.
22
+
23
+ ## What you COMPOSE (single sources — do NOT duplicate)
24
+
25
+ You are a GATHERER. You stand up the existing producers/reviewers and collect what they emit; you do not reimplement their judgement. `to-task` and `review` stay the single sources, **composed and UNCHANGED**.
26
+
27
+ 1. **`review` (`work/protocol/REVIEW-PROTOCOL.md`) — for a task / spec / code.** Run the `review` discipline; it EMITS a verdict `{verdict, findings:[{severity, question, context}]}` and writes nothing. ROUTE its **`block`** findings into your emitted questions (a blocking finding is an open question that must be answered before the item advances). A non-blocking finding is a nit — record it as an optional/low-priority question, never as a blocker. Do NOT re-derive review's lenses here; you call review and carry its findings over.
28
+ 2. **The native observation-triage question — for an observation.** An observation has no gate for `review` to assess; its native question is **"what should become of this signal?"** (resolve / promote / delete / duplicate). **This one question is DETERMINISTIC and engine-owned:** the advance engine ALWAYS surfaces it (as `q1`) for every untriaged observation, built from a fixed template, NOT from your emit. It can never be zeroed out or lost to a flake, so a record / a rationale note / a fresh-bug signal are all surfaced identically and the human always decides the disposition. **Your role for an observation is ADDITIVE:** investigate the observation's claim against current reality (code / tasks / specs / ADRs) and emit any EXTRA pointed questions the body genuinely raises (e.g. a specific open sub-question the note itself asks) — the engine appends them AFTER the deterministic triage question. If the observation raises no extra question of its own, emit an empty `questions` array: the deterministic triage question still lands, so the observation is never stuck. (You do NOT need to emit the "what becomes of this signal?" question yourself — the engine owns it; emitting it too would merely duplicate `q1`.)
29
+ 3. **The item's PRE-EXISTING open questions.** Collect what the item already carries: a `needsAnswers: true` item's `## Open questions` block, and any open question already written in the body. Carry each over verbatim as an emitted question (with its context). These are open judgement the author already named — they must surface, not be silently dropped.
30
+
31
+ For each gathered question, attach **inline CONTEXT** (the relevant excerpt / `file:line` / the reasoning — so the human need not open the source item) and, where you can honestly suggest one, an **optional suggested DEFAULT** (the humility aid — never a decision).
32
+
33
+ ## The emitted question shape (MUST match the sidecar)
34
+
35
+ The questions you emit MUST match the **sidecar entry fields**, so the engine persists them with **zero translation**. The runtime PARSER (`parseSurfaceEmit`) is the source of truth for the shape; this prose mirrors what it enforces.
36
+
37
+ Emit a single JSON object of this exact shape (no prose OUTSIDE it):
38
+
39
+ ```json
40
+ {
41
+ "item": "<type>:<slug>",
42
+ "questions": [
43
+ {
44
+ "question": "…",
45
+ "context": "…",
46
+ "default": "… (optional; omit if none)"
47
+ }
48
+ ],
49
+ "note": "… (optional free prose; your reasoning / findings live HERE)"
50
+ }
51
+ ```
52
+
53
+ - **`item`** — OPTIONAL, the namespaced identity the surface is for (orientation only; the resolver owns identity — the parser tolerates absence).
54
+ - **`questions`** — REQUIRED, an ORDERED array. An EMPTY array is VALID — the honest "no open judgement" result; absence is NOT (the parser rejects it, never a silent surface). Each entry is:
55
+ - **`question`** — REQUIRED, the question verbatim. An all-whitespace question is dropped as a placeholder.
56
+ - **`context`** — OPTIONAL, inline context so the human need not open the item (the relevant excerpt / `file:line` / reasoning).
57
+ - **`default`** — OPTIONAL, the suggested default — the humility aid; omit when you cannot honestly suggest one (never fabricate a default just to fill the field).
58
+ - **`note`** — OPTIONAL free-prose channel for your reasoning / findings (the surface counterpart of the verdict's `review` field). It is the HOME for any explanation you want to give: put it HERE, INSIDE the object, never as prose around the JSON. The engine does not persist `note` (the parser ignores it); its only purpose is to give your prose somewhere to go so the emitted object can be your final, clean, single-object output. Like `review`, keep it short and single-line (write `\n` literally, never a raw newline).
59
+
60
+ There is NO `disposition` field, and no token vocabulary to learn or pick: a sidecar entry is BINARY (no-answer | answered), and the human answers in PLAIN LANGUAGE. What to DO with the answer — mint a task, a SPEC, or an ADR; delete the source; or ask a follow-up — is the agentic apply decision (read off the human's answer + the source item), not a token the surface emits. An observation's triage question is therefore just an ordinary plain question ("what becomes of this signal?"); the human writes back in their own words, and if the answer is "throw it away", the discharge is the direct-delete path (the human, the `answer-questions` skill, or the `dorfl` delete verb removes the source + sidecar in one revertible commit), not a `delete` token.
61
+
62
+ You do NOT assign ids, `answered:`, `answer:`, or `allAnswered`. Those are the SIDECAR's machine-owned fields — the engine assigns the stable monotonic id (`q1`, `q2`, …), the human fills `answer:`, and the serialiser derives `answered:`/`allAnswered`. You emit only the three authoring fields above; the engine owns the rest. (This is precisely why you must not write the sidecar: you do not own its machine fields.)
63
+
64
+ Because the shape is the sidecar's, the engine APPENDS your questions to any existing sidecar (never overwriting an already-answered entry) and writes the whole thing in one CAS-atomic commit. You need not know any of that — you just emit the four fields.
65
+
66
+ If the item carries **no open judgement** (review approves with no blocking findings, an observation raises no extra pointed question of its own beyond the engine's deterministic triage question, nothing pre-existing) — emit the object with an **empty `questions` array** (put WHY in `note`). Surfacing no EXTRA question is a valid, honest result; do not manufacture a question to look busy. Do NOT replace the empty-array object with a prose explanation: the JSON object is always your output, even when it carries no questions.
67
+
68
+ > **Empty is VALID, absence is NOT.** This is a hard rule the runtime parser (`parseSurfaceEmit`) enforces: `"questions": []` is a valid, honest "no open judgement" result; ABSENCE of the `questions` field is rejected as an unparseable emit and strands the surface rung (the failure mode observed in `surface-rung-agent-emits-no-parseable-questions` and its 2026-07-10 recurrence on decision-record observations). When you have nothing to ask, the safe close is ALWAYS `{"questions": [], "note": "…"}` — not a prose sentence, not a skipped emit, not a dropped `questions` key.
69
+
70
+ > **For an OBSERVATION, an empty emit is never a dead end.** The advance engine ALWAYS surfaces the deterministic triage question (`q1`, engine-owned — see "What you COMPOSE" item 2), so an empty `questions` array from you simply means "no EXTRA question beyond the triage one," and the human still gets asked what should become of the observation. Your empty emit (or even a flake) is therefore non-fatal for an observation: the triage question lands regardless. (For a TASK / SPEC there is no engine-owned base question, so an empty emit there is the genuine "nothing surfaced" no-op.)
71
+
72
+ **The emitted object is your FINAL and ONLY output.** Do not narrate your process, and add no remark, summary, or sign-off before or after it — and take no further turn once you have emitted it (emitting it is how you finish). The caller reads only your LAST turn, so a trailing chatty turn AFTER the object discards the emit and strands the run. This is the same discipline Gate-2's verdict carries; the `note` field exists precisely so all your prose has a home inside the object.
73
+
74
+ ### How the caller persists your questions (NOT your job — for orientation only)
75
+
76
+ - **The advance engine's surface-question rung** spawns you fresh-context, takes your emitted questions, and writes them to the sidecar `work/questions/<type>-<slug>.md` CAS-atomically (assigning ids, appending, setting `needsAnswers: true`). The skill judges; the engine persists.
77
+ - **A human (no runner)** persists via the `advance` verb (see below), or hand-writes the documented sidecar format.
78
+
79
+ ## The no-runner path
80
+
81
+ You stay **human-invokable**. A human with no runner can invoke this discipline by hand, take the emitted questions, and persist them one of two ways:
82
+
83
+ - **Persist via the `advance` verb** — the apply/surface rung of the `advance` command (a **sibling top-level verb**, like `do` and `run`). It is `advance`, **NOT `do advance`** — `advance` is its own verb.
84
+ - **Hand-write the documented sidecar format** — write `work/questions/<type>-<slug>.md` by hand per the human-readable Markdown shape below. Because the emitted shape already matches the sidecar entry, this is a transcription, not a translation.
85
+
86
+ The hand-written sidecar shape (the SAME file is both human-readable on GitHub and machine-parseable — the machine fields hide in HTML comments that GitHub renders as nothing, the human content is real Markdown):
87
+
88
+ ```
89
+ <!-- dorfl-sidecar: item=<type>:<slug> type=<type> slug=<slug> allAnswered=false -->
90
+
91
+ ## Q1
92
+
93
+ **<the question, verbatim>**
94
+
95
+ > <inline context so the human need not open the item>
96
+
97
+ _Suggested default: <optional default; omit the whole line if none>_
98
+
99
+ <!-- q1 fields: id=q1 -->
100
+
101
+ **Your answer** (write below this line):
102
+
103
+ ## Q2
104
+
105
+ **<next question…>**
106
+
107
+
108
+ ```
109
+
110
+ Notes for the hand-writer:
111
+
112
+ - The **identity HTML comment** at the top carries `item`/`type`/`slug` and the derived `allAnswered` mirror. Set `allAnswered=false` on first write (no answers yet); the engine recomputes it on every subsequent serialise.
113
+ - Each entry opens with a `## Qn` heading (`Q1`, `Q2`, …, monotonic — never reused). The heading is BOTH the entry separator and the answer-region boundary.
114
+ - The **question is a bold line**, the **context is a Markdown blockquote** (each line prefixed `> `), the **default is one italic line** prefixed `_Suggested default: ` and closed with `_`. Omit context/default lines entirely when absent.
115
+ - The **per-entry HTML comment** carries `id=qN`. There is no `disposition=` field (the token vocabulary is retired — an entry is binary). Do NOT add an `answered=` field either — the engine derives answered-ness from the answer text and only emits the override when it disagrees with that derivation.
116
+ - The fixed marker `**Your answer** (write below this line):` is followed by an empty region; the answer is everything from the marker up to the next `## ` heading (heading-delimited so a `---` inside an answer cannot break parsing).
117
+ - The human just types prose under the answer marker — no `key:`, no escaping, no fence.
118
+
119
+ ### The optional `kind=` dispatch axis (interim primitive)
120
+
121
+ An entry MAY carry an optional `kind=<value>` machine-only dispatch axis in its per-entry HTML comment (`<value>` ∈ `merge` | `stuck` | `triage` | `spec`), read by the `advance` apply rung to route runner-ACTION questions (`merge`, `stuck`) to deterministic dispatch vs CONTENT questions (`triage`, `spec`) to the agentic `decide()` path. Absent ⇒ the plain binary content entry (every pre-`kind` sidecar parses + renders byte-identically). This is an INTERIM primitive — removable once question sidecars move to kind-based SUBFOLDERS (`work/questions/merge/`, …), where the folder ENCODES the kind and this per-entry field is redundant.
122
+
123
+ Three ratified rules govern the on-disk shape of this axis (recorded here so `packages/dorfl/src/sidecar.ts` is not the sole home):
124
+
125
+ - **Token spelling is `kind=<value>`.** No alternative spelling, no synonyms — the literal token is `kind=`, mirroring the `id=` / `answered=` house style.
126
+ - **Order within the comment: `kind=` is emitted AFTER any `answered=` token** (i.e. `<!-- qN fields: id=qN [answered=…] [kind=…] -->`). Emit order is load-bearing because the comment IS the on-disk surface — downstream diff tools see it, so the order is fixed rather than left implementation-defined.
127
+ - **Unknown `kind=` values are SILENTLY DROPPED on re-serialise** (silent-on-malformed, matching the retired `disposition=` precedent — never a throw, never a coerce). A mistyped or unknown token parses to `undefined` and is NOT echoed back on serialise. CONSEQUENCE: round-trip is NOT byte-preserving for unknown tokens. This is BY DESIGN — it keeps parse/serialise symmetric on the KNOWN grammar and stops downstream code from silently depending on opaque passthrough. Do NOT "fix" it by adding echo-through; that would reverse the decision.
128
+
129
+ **No separate write-skill is added.** Hand-writing the sidecar (or the `advance` verb) is enough. Do not invent one here.
130
+
131
+ ## Boundaries (the scope fence)
132
+
133
+ - **`to-task` / `review` stay COMPOSED and UNCHANGED.** You call them; you never modify or reimplement them. They are the single sources for tasking/reviewing judgement.
134
+ - **You formulate the questions for ONE item; you do not batch, apply, or iterate.** Batching, applying answers, and iterating are the ENGINE's job (or `orchestrate`'s, for the human batch) — NOT yours.
135
+ - **You write nothing and you invent no answer.** Both laws, restated because they are the whole point: GATHER-only, PERSIST-NEVER; surface the residue, NEVER invent an answer.
@@ -0,0 +1,124 @@
1
+ # TASKING-PROTOCOL
2
+
3
+ The **tasking discipline** the autonomous runner invokes by name on a spec in `work/specs/ready/` to decompose it into independently-grabbable, file-based work tasks (tracer-bullet vertical tasks). The runner spawns a fresh-context agent and tells it to "run the tasking protocol"; that agent reads THIS doc and applies its standard.
4
+
5
+ This is one of the runner-invoked protocol disciplines (alongside `CLAIM-PROTOCOL.md`, `REVIEW-PROTOCOL.md`, and `SURFACE-PROTOCOL.md`): the protocol describes how work is AUTHORED (`WORK-CONTRACT.md`, the templates), CLAIMED and BUILT (`CLAIM-PROTOCOL.md`, the `verify` floor), JUDGED BEFORE LANDING (`REVIEW-PROTOCOL.md`), has its OPEN QUESTIONS SURFACED (`SURFACE-PROTOCOL.md`), and — between spec and buildable task — TASKED (this doc). It is in-band in every set-up repo, never host-specific. (The human-facing pointer is `skills/to-task/SKILL.md`; the standard lives here.)
6
+
7
+ > This doc is **protocol-native**: it assumes the repo uses the `work/` contract. Every bare "WORK-CONTRACT" / "CLAIM-PROTOCOL" mention below refers to `work/protocol/<doc>` in the repo under work.
8
+
9
+ You **emit task files; you do not act on them** — see [Git protocol](#git-protocol). Committing them, moving the spec, claiming any of them is the caller's job (the runner, on the agent path; a human, on the no-runner path). This discipline is the _decomposition_, not the disposition.
10
+
11
+ ## When to use vs. not
12
+
13
+ - **Use** when tasking a `work/specs/ready/<slug>.md`, a design doc, or a plan into grabbable units for solo-with-agents (incl. parallel) work.
14
+ - **Don't** use to _write_ the spec (that's a separate step — `to-spec`) or to _claim/run_ a task (that's the runner: `dorfl claim`/`do`/`complete`, or the `drive-tasks` conductor). Don't introduce a shared index file or a status field — status is the folder (see `WORK-CONTRACT.md`).
15
+
16
+ ## Process
17
+
18
+ ### 1. Locate / confirm the source
19
+
20
+ Work from a `work/specs/ready/<slug>.md`, a design doc, or the conversation context. If the source is a path, read it fully. The `work/` folder lives **inside the target project repo** (versioned with its code).
21
+
22
+ ### 2. Explore the codebase (if not already)
23
+
24
+ Task titles and descriptions use the project's domain glossary. Respect ADRs / findings in the area you're touching.
25
+
26
+ **Check the spec against reality first (drift = a needs-attention signal).** A spec is a launch snapshot and may have DRIFTED from what has since landed (`tasks/done/`, ADRs, sibling tasks). Before tasking, verify its assumptions still hold. If it has drifted such that tasking it as-is would emit tasks built on a false premise, do NOT task it: set `needsAnswers: true` on the spec with the discrepancy in its body (or fix a small certain factual error first). See WORK-CONTRACT.md "Drift is a needs-attention signal". Never emit tasks from a stale spec.
27
+
28
+ ### 3. Draft vertical tasks
29
+
30
+ Each task is a **tracer bullet** — a thin vertical path through ALL layers end-to-end, not a horizontal cross-section of one layer.
31
+
32
+ - Each task delivers a narrow but COMPLETE path (schema → logic → API/UI → tests).
33
+ - A completed task is demoable/verifiable on its own.
34
+ - Prefer many thin tasks over few thick ones.
35
+ - Set the **two gate axes** ONLY where they apply (both default to OMITTED on most tasks): **`humanOnly: true`** = NEVER-for-agents BY NATURE (the NARROW DECIDED axis — secrets/release/security; survives even in the pool `work/tasks/ready/`); **`needsAnswers: true`** = unresolved questions block autonomous work (the DISCOVERED axis — list the questions in the task body). Omitted on either means "undeclared"; whether an agent may then auto-build is the _repo's_ `autoBuild` policy. Mark `blockedBy` for ordering. See `WORK-CONTRACT.md` for the two-axis semantics, the predicate, and the `autoBuild` precedence.
36
+ - **A task's `humanOnly` is decided from the nature of BUILDING THAT TASK — never inherited from the spec.** Evaluate each task on its own merits (does _building it_ genuinely need to be done by a human BY NATURE — secrets handling, release pipeline, hard security boundary, an AGENTS.md prohibition?), AS IF the spec's `humanOnly` field did not exist. (The two flags are disjoint — see §3b.)
37
+ - **Do NOT stamp `humanOnly` to mean "a human should REVIEW this before the agent builds it"** — that is the POSITION's job, not the flag's. The runner BIRTHS tasks STAGED in `work/tasks/backlog/` (not eligible); a human promotes the approved ones into the pool `work/tasks/ready/`. Review-first is encoded by the staging position; `humanOnly` is reserved for the rare never-by-nature case. (See WORK-CONTRACT.md "Task `humanOnly` is NARROW".)
38
+ - **Do NOT be shy about `needsAnswers` — when genuinely unsure, FLAG, don't guess.** `needsAnswers` is cheap (a human clears it in seconds) and a confidently-underspecified task is expensive (an agent builds the wrong thing, convincingly). Defects concentrate in TASKING far more than in implementation: an ambiguous premise, an unresolved design fork, a "reuse X" where X's shape is unverified, or a seam you _assume_ exists — each is a `needsAnswers` with the open question written in the body, NOT a guess dressed as a spec. The asymmetry is the whole point: a false `needsAnswers` costs one human glance; a false confidence ships wrong-but-compiling work.
39
+ - **Prefer file-orthogonal tasks to minimise merge conflicts.** `blockedBy` encodes logical ordering, but two independent tasks that edit the SAME files will conflict when the second integrates after the first. Parallel agents make this real. So: split along file/module boundaries where you can; and when two tasks are known to touch the same module, add a `blockedBy` to **serialize** them even if there's no strict logical dependency. The runner only rebases-or-surfaces conflicts (it never auto-resolves), so avoiding them at tasking time is the cheap win.
40
+
41
+ ### 3a. Wide refactors are the EXCEPTION to vertical slicing (expand → migrate → contract)
42
+
43
+ Most work slices vertically (§3). A **wide refactor** does not, and forcing it to is the failure mode this subsection exists to stop. A wide refactor is one mechanical change — rename a shared symbol or a column, retype a pervasive identifier, cut a vocabulary over — whose **blast radius** fans across the whole codebase, so a single edit breaks thousands of call sites at once and **no vertical tracer-bullet task can land green on its own**. Sequence it as **expand → migrate → contract** instead:
44
+
45
+ - **Expand** (one task): add the NEW form BESIDE the old so nothing breaks yet. Nothing is removed; the gate stays green because every existing caller still resolves.
46
+ - **Migrate** (one task PER batch, each `blockedBy` the expand task): move call sites onto the new form in batches sized by blast radius — per package, per directory, per module. Each batch is its own task and stays green because the old form still exists. Split batches file-orthogonally (§3's merge-conflict rule) so parallel agents don't collide.
47
+ - **Contract** (one task, `blockedBy` EVERY migrate batch): delete the old form once no caller remains. It cannot start until every migrate batch is done — that is what its `blockedBy` fan-in encodes.
48
+
49
+ **A KEY / folder rename task MUST ground its blast-radius claim against the actual code** — do NOT assert "every call site references the key, never a raw string" without grepping first, because when the key is a typed string literal (`as const satisfies WorkFolderKey` or the equivalent) renaming the key IS a hard TypeScript break at every call site, and those call-site literal updates MUST be scoped into the migrate batch, not deferred.
50
+
51
+ **When even the batches cannot stay green alone** (the change is so entangled that an individual batch can't pass the gate in isolation), keep the same expand → migrate → contract shape but let the batches share a common **integration point**: task each batch onto the work-branch discipline the runner already uses, and add a final **integrate-and-verify** task `blockedBy` all of them where green is promised — green is guaranteed only at that fan-in, not batch-by-batch. Prefer the plain green-batch-by-batch form; reach for the shared-integration form only when a batch genuinely cannot be made to pass alone.
52
+
53
+ The test for "is this a wide refactor?" is whether a single mechanical edit breaks the gate across many call sites at once such that no thin vertical path can be green. If yes, use this sequence; if a normal vertical slice CAN be green, it is not a wide refactor — slice it vertically (§3).
54
+
55
+ ### 3b. Spec gate vs task gate are DISJOINT + honour cross-spec `taskedAfter`
56
+
57
+ - **`humanOnly` on a spec and `humanOnly` on a task are DISJOINT — they gate different verbs and DO NOT flow into each other.**
58
+ - **Spec `humanOnly`** gates _tasking_: its ONLY effect is that an agent may not **auto-task** that spec (even where the repo's `autoTask` policy is on); a human must drive the decomposition. That is its entire meaning.
59
+ - **Task `humanOnly`** gates _building_: it is decided per task from the nature of building that task (see §3), independently.
60
+ - There is **NO inheritance, NO propagation, and NOT EVEN A HINT** from the spec flag to the task flags. A `humanOnly: true` spec can produce entirely agent-buildable tasks; an un-flagged spec can produce some `humanOnly` tasks. When setting a task's gate, ignore the spec's `humanOnly` entirely.
61
+ - Likewise **`needsAnswers`**: on a spec it blocks auto-tasking until the questions are answered; on a task it blocks auto-building. Set a task's `needsAnswers` only when _that task_ has unresolved questions (list them in its body) — not because the spec had open questions (a spec with open questions should be resolved BEFORE tasking, not task-inherited).
62
+ - (A spec's body may still _describe_ which areas are judgement-heavy — use that as ordinary domain input when reasoning about a task's own build-nature, the same as any other spec prose; it is not a flag-setting shortcut.)
63
+ - **`taskedAfter` (cross-spec order).** If this spec has `taskedAfter: [other-spec]`, those specs must already be TASKED (their tasks exist) before you task this one — so this spec's tasks can reference the real slugs of those specs' tasks in `blockedBy`. (The auto-tasker enforces this; a human may task anyway but must then know the blocker slugs.) If a needed blocker spec is not yet tasked, task it first or record the dependency and stop.
64
+
65
+ ### 4. Quiz the user — OR (no human present) do a confidence check
66
+
67
+ **If a human is present** (the normal interactive path): present the breakdown as a numbered list — Title, the two gate axes, Blocked-by, and (if the source has them) which user stories it covers. Ask: granularity right? dependencies right? merge/split any? gates correct? Iterate until approved.
68
+
69
+ **If NO human is present** (an agent auto-tasking in CI): step 4 is replaced by a **confidence check**, because there is no one to quiz. Do NOT emit guessed tasks. The source spec should already be clear (the auto-tasker only runs on a spec that is not `humanOnly` and not `needsAnswers`). If, while tasking, ANY of {granularity, dependency order, a gate, a seam} is genuinely unresolved by the spec/ADR, do not guess: either set `needsAnswers: true` (with the open questions in the body) on the specific uncertain task, or — if the whole decomposition is unclear — stop and route the spec to needs-attention with the questions, rather than emitting a wrongly-cut task. Only emit tasks you would have gotten the human to approve.
70
+
71
+ ### 5. Write the task files
72
+
73
+ For each approved task, write `work/tasks/backlog/<slug>.md` using `work/protocol/task-template.md`. Create `work/` and `work/tasks/backlog/` lazily if absent. One file per task. Use a content-derived slug, never a counter. Fill `blockedBy` with the slugs of blocking tasks, and set the **required `spec`** field to the slug of the source `work/specs/ready/<slug>.md` (so `covers` story numbers are unambiguous — see `WORK-CONTRACT.md`).
74
+
75
+ ### 6. Trim the spec to its durable framing (one-time)
76
+
77
+ The spec is a launch snapshot (see the `to-spec` skill). Now that the work is tasked, the spec's **technical detail is redundant** (it lives in the tasks) and is the part that would otherwise go stale. Do a ONE-TIME trim:
78
+
79
+ - The tasks now own _what to build_ (Implementation/Testing detail) — remove those sections from the spec.
80
+ - Any **durable rationale** worth keeping (the _why_ of a decision) is RELOCATED to an ADR (`docs/adr/<slug>.md`), not deleted.
81
+ - The spec settles to its durable framing: Problem / Solution / User Stories / Out of Scope (+ its launch-snapshot banner). Leave a one-line pointer that detail moved to tasks/ADRs.
82
+ - **Move the spec to `work/specs/tasked/`** to record that it has been tasked: `git mv work/specs/<src>/<slug>.md work/specs/tasked/<slug>.md`, where `<src>` is the spec's CURRENT non-pool resting position — `ready` on the runner/dorfl path, but `proposed` on the human-driven path (a human MAY task a spec straight from `specs/proposed/` without first promoting it to `ready/`; doing so is deliberate — staging keeps it out of the auto-tasking pool so CI cannot race the human, and a forced `proposed → ready` pre-move would re-open exactly that race). The DESTINATION is always `tasked/` regardless of source. Transforming a spec into tasks MUST move it: residence in `work/specs/tasked/` IS tasked-ness (the build-machine `tasks/done/` analogue for specs, the sole signal); a tasked spec left in `proposed/`/`ready/` both lies about its state and stays auto-taskable (CI could re-fan-out). Do NOT add a `tasked:` frontmatter marker; the folder is the source of truth. (On the dorfl path `do spec:<slug>` performs this move itself as part of its runner-owned integration commit; this manual step is for the human-driven, no-lock tasking path.)
83
+
84
+ This is a hand-off transition, not ongoing maintenance — after this single trim the spec is stable because the stale-prone part was relocated, not because it is kept in sync. (Nothing is lost: detail → tasks; rationale → ADR.)
85
+
86
+ ## Git protocol
87
+
88
+ Do NOT commit/push — leave the work for the caller to inspect/integrate. The one exception is the spec `specs/<ready|proposed>/ → specs/tasked/` relocation above, which is a `git mv` (so it is staged as a rename); leave every other new/edited file unstaged. Report the exact paths written (and the trimmed + relocated spec).
89
+
90
+ When the runner spawns you on the agent tasking path, you EDIT files only — write the task files under the STAGING folder, trim the spec — and the RUNNER owns every git-state transition (it commits the produced tasks, releases the tasking lock, and moves the spec into `work/specs/tasked/`). Do not stage, commit, push, or move any files yourself. The runner integrates the tasking transition through the shared band (`--propose` PR / `--merge` main) honouring the caller's flags.
91
+
92
+ ## The emitted task shape (mirrors `work/protocol/task-template.md`)
93
+
94
+ Each emitted task file is a markdown document with YAML frontmatter, BORN STAGED in `work/tasks/backlog/<slug>.md`. The shape's enforced source of truth is the frontmatter parser code (`parseFrontmatter`) and the templated body in `work/protocol/task-template.md`; this section DESCRIBES it in prose so the spawned agent emits files the parser and the runner read identically.
95
+
96
+ ### Required frontmatter fields
97
+
98
+ - **`title:`** — a short, human-readable title for the task (one line).
99
+ - **`slug:`** — the URL-safe content-derived slug; matches the filename `<slug>.md`. Never a counter.
100
+ - **`spec:`** — the slug of the source `work/specs/ready/<spec>.md` this task derives from. REQUIRED when `covers:` is non-empty; OMITTED only on a self-contained chore/refactor (with `covers: []`). Disambiguates `covers:` story numbers.
101
+ - **`blockedBy:`** — a YAML inline list of slugs that must reach `work/tasks/done/` first; `[]` means startable now.
102
+
103
+ ### Optional frontmatter axes (omit when undeclared)
104
+
105
+ - **`humanOnly: true`** — gate axis 1 (DECIDED, NARROW): NEVER-for-agents BY NATURE (secrets / release / security / an `AGENTS.md` prohibition). Survives even in the pool `work/tasks/ready/`. OMIT when the task is agent-buildable — "review this before the agent builds" is the POSITION's job (the task is BIRTHED in `work/tasks/backlog/`), NOT `humanOnly`'s.
106
+ - **`needsAnswers: true`** — gate axis 2 (DISCOVERED): open questions block autonomous work. List the questions under an `## Open questions` heading in the body. OMIT when the task launches fully resolved.
107
+ - **`covers:`** — an inline list of user-story numbers within `spec:` this task covers; `[]` (or omitted) means no specific story coverage.
108
+ - **`issue:`** — the GitHub issue number an `intake`-emitted task was transformed from. Carried only when the task is the direct closer for an issue (mutually exclusive with `spec:` carrying the closure via the spec).
109
+
110
+ ### Body sections
111
+
112
+ - **`## What to build`** — a concise description of the vertical task — the end-to-end behaviour (a thin path through every layer: schema → logic → API/UI → tests), NOT a layer-by-layer implementation plan. Avoid specific file paths / code snippets (they go stale).
113
+ - **`## Acceptance criteria`** — a bullet list of verifiable / demoable criteria, ending with the test-coverage line and (where applicable) the shared-write isolation rule from `WORK-CONTRACT.md`.
114
+ - **`## Blocked by`** — prose mirror of the frontmatter `blockedBy:`; `None — can start immediately.` when `blockedBy: []`.
115
+ - **`## Prompt`** — self-contained instructions to paste into a fresh agent context: an agent must be able to start from THIS FILE ALONE. State the goal, the relevant domain vocabulary, where to look (by module/concept, not brittle paths), the seams to test at, and what "done" means. Reference any constraining ADRs / findings.
116
+ - **`## Open questions`** — present iff `needsAnswers: true`; lists the unresolved questions blocking autonomous build. Stripped by the apply rung on full resolution.
117
+
118
+ ### Placement rule
119
+
120
+ The tasker ALWAYS writes emitted task files to `work/tasks/backlog/` (the STAGING folder). The pool `work/tasks/ready/` is the agent-eligible pool the runner owns the promotion into; a write outside the staging folder is dropped by the runner-deterministic placement resolver. The agent never self-places into the pool.
121
+
122
+ ## The on-disk contract
123
+
124
+ The full `work/` layout, slug rules, and frontmatter are in `work/protocol/WORK-CONTRACT.md`. The claim/lifecycle protocol these files are designed to support (consumed by the runner — `dorfl claim`/`do`/`complete`) is in `work/protocol/CLAIM-PROTOCOL.md` — read it so the files you emit are claim-ready, but this discipline does not itself claim or run tasks.