wingfoil 0.2.2 → 0.3.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 (454) hide show
  1. package/README.md +83 -43
  2. package/dist/agent/discovery.d.ts +72 -0
  3. package/dist/agent/discovery.d.ts.map +1 -0
  4. package/dist/agent/discovery.js +210 -0
  5. package/dist/agent/discovery.js.map +1 -0
  6. package/dist/agent/execute.d.ts +206 -0
  7. package/dist/agent/execute.d.ts.map +1 -0
  8. package/dist/agent/execute.js +581 -0
  9. package/dist/agent/execute.js.map +1 -0
  10. package/dist/agent/index.d.ts +23 -0
  11. package/dist/agent/index.d.ts.map +1 -0
  12. package/dist/agent/index.js +85 -0
  13. package/dist/agent/index.js.map +1 -0
  14. package/dist/agent/launch.d.ts +133 -0
  15. package/dist/agent/launch.d.ts.map +1 -0
  16. package/dist/agent/launch.js +380 -0
  17. package/dist/agent/launch.js.map +1 -0
  18. package/dist/agent/manifest.d.ts +30 -0
  19. package/dist/agent/manifest.d.ts.map +1 -0
  20. package/dist/agent/manifest.js +93 -0
  21. package/dist/agent/manifest.js.map +1 -0
  22. package/dist/agent/placeholders.d.ts +49 -0
  23. package/dist/agent/placeholders.d.ts.map +1 -0
  24. package/dist/agent/placeholders.js +177 -0
  25. package/dist/agent/placeholders.js.map +1 -0
  26. package/dist/agent/run-log.d.ts +254 -0
  27. package/dist/agent/run-log.d.ts.map +1 -0
  28. package/dist/agent/run-log.js +612 -0
  29. package/dist/agent/run-log.js.map +1 -0
  30. package/dist/agent/schema.d.ts +100 -0
  31. package/dist/agent/schema.d.ts.map +1 -0
  32. package/dist/agent/schema.js +89 -0
  33. package/dist/agent/schema.js.map +1 -0
  34. package/dist/agent/step.d.ts +41 -0
  35. package/dist/agent/step.d.ts.map +1 -0
  36. package/dist/agent/step.js +95 -0
  37. package/dist/agent/step.js.map +1 -0
  38. package/dist/build-info.json +4 -0
  39. package/dist/cli/error.d.ts +11 -2
  40. package/dist/cli/error.d.ts.map +1 -1
  41. package/dist/cli/error.js +27 -6
  42. package/dist/cli/error.js.map +1 -1
  43. package/dist/cli/exit.d.ts +33 -3
  44. package/dist/cli/exit.d.ts.map +1 -1
  45. package/dist/cli/exit.js +54 -3
  46. package/dist/cli/exit.js.map +1 -1
  47. package/dist/cli/index.d.ts +1 -1
  48. package/dist/cli/index.d.ts.map +1 -1
  49. package/dist/cli/index.js +2 -1
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/cli/init-command.d.ts.map +1 -1
  52. package/dist/cli/init-command.js +4 -2
  53. package/dist/cli/init-command.js.map +1 -1
  54. package/dist/cli/mcp-command.d.ts +13 -4
  55. package/dist/cli/mcp-command.d.ts.map +1 -1
  56. package/dist/cli/mcp-command.js +25 -8
  57. package/dist/cli/mcp-command.js.map +1 -1
  58. package/dist/cli/output.d.ts +12 -4
  59. package/dist/cli/output.d.ts.map +1 -1
  60. package/dist/cli/output.js +15 -4
  61. package/dist/cli/output.js.map +1 -1
  62. package/dist/cli/program.d.ts +1 -1
  63. package/dist/cli/program.d.ts.map +1 -1
  64. package/dist/cli/program.js +162 -40
  65. package/dist/cli/program.js.map +1 -1
  66. package/dist/cli/registrar.d.ts +1 -1
  67. package/dist/cli/registrar.d.ts.map +1 -1
  68. package/dist/cli/registrar.js +67 -6
  69. package/dist/cli/registrar.js.map +1 -1
  70. package/dist/cli/suggest.d.ts +20 -0
  71. package/dist/cli/suggest.d.ts.map +1 -0
  72. package/dist/cli/suggest.js +49 -0
  73. package/dist/cli/suggest.js.map +1 -0
  74. package/dist/cli/warning.d.ts +26 -0
  75. package/dist/cli/warning.d.ts.map +1 -0
  76. package/dist/cli/warning.js +66 -0
  77. package/dist/cli/warning.js.map +1 -0
  78. package/dist/cli.js +11 -2
  79. package/dist/cli.js.map +1 -1
  80. package/dist/core/agent-execute.d.ts +44 -0
  81. package/dist/core/agent-execute.d.ts.map +1 -0
  82. package/dist/core/agent-execute.js +71 -0
  83. package/dist/core/agent-execute.js.map +1 -0
  84. package/dist/core/agent-list.d.ts +95 -0
  85. package/dist/core/agent-list.d.ts.map +1 -0
  86. package/dist/core/agent-list.js +207 -0
  87. package/dist/core/agent-list.js.map +1 -0
  88. package/dist/core/agent-show.d.ts +44 -0
  89. package/dist/core/agent-show.d.ts.map +1 -0
  90. package/dist/core/agent-show.js +188 -0
  91. package/dist/core/agent-show.js.map +1 -0
  92. package/dist/core/approval-authority.d.ts +17 -14
  93. package/dist/core/approval-authority.d.ts.map +1 -1
  94. package/dist/core/approval-authority.js +9 -9
  95. package/dist/core/approval-authority.js.map +1 -1
  96. package/dist/core/builtin-asset.d.ts +18 -15
  97. package/dist/core/builtin-asset.d.ts.map +1 -1
  98. package/dist/core/builtin-asset.js +17 -14
  99. package/dist/core/builtin-asset.js.map +1 -1
  100. package/dist/core/builtin-integrity.d.ts +12 -2
  101. package/dist/core/builtin-integrity.d.ts.map +1 -1
  102. package/dist/core/builtin-integrity.js +85 -9
  103. package/dist/core/builtin-integrity.js.map +1 -1
  104. package/dist/core/confinement.d.ts +3 -1
  105. package/dist/core/confinement.d.ts.map +1 -1
  106. package/dist/core/confinement.js +7 -4
  107. package/dist/core/confinement.js.map +1 -1
  108. package/dist/core/context.d.ts +150 -98
  109. package/dist/core/context.d.ts.map +1 -1
  110. package/dist/core/context.js +602 -66
  111. package/dist/core/context.js.map +1 -1
  112. package/dist/core/directive-assign.d.ts +33 -7
  113. package/dist/core/directive-assign.d.ts.map +1 -1
  114. package/dist/core/directive-assign.js +67 -27
  115. package/dist/core/directive-assign.js.map +1 -1
  116. package/dist/core/directive-scope.d.ts +36 -0
  117. package/dist/core/directive-scope.d.ts.map +1 -0
  118. package/dist/core/directive-scope.js +34 -0
  119. package/dist/core/directive-scope.js.map +1 -0
  120. package/dist/core/directives-list.d.ts +13 -3
  121. package/dist/core/directives-list.d.ts.map +1 -1
  122. package/dist/core/directives-list.js +18 -6
  123. package/dist/core/directives-list.js.map +1 -1
  124. package/dist/core/dry-run.d.ts +33 -0
  125. package/dist/core/dry-run.d.ts.map +1 -0
  126. package/dist/core/dry-run.js +45 -0
  127. package/dist/core/dry-run.js.map +1 -0
  128. package/dist/core/element-ref.d.ts +23 -0
  129. package/dist/core/element-ref.d.ts.map +1 -0
  130. package/dist/core/element-ref.js +24 -0
  131. package/dist/core/element-ref.js.map +1 -0
  132. package/dist/core/error-details.d.ts +20 -0
  133. package/dist/core/error-details.d.ts.map +1 -0
  134. package/dist/core/error-details.js +65 -0
  135. package/dist/core/error-details.js.map +1 -0
  136. package/dist/core/exit-code.d.ts.map +1 -1
  137. package/dist/core/exit-code.js +6 -7
  138. package/dist/core/exit-code.js.map +1 -1
  139. package/dist/core/git-identity.d.ts +46 -11
  140. package/dist/core/git-identity.d.ts.map +1 -1
  141. package/dist/core/git-identity.js +140 -27
  142. package/dist/core/git-identity.js.map +1 -1
  143. package/dist/core/index.d.ts +184 -10
  144. package/dist/core/index.d.ts.map +1 -1
  145. package/dist/core/index.js +1045 -185
  146. package/dist/core/index.js.map +1 -1
  147. package/dist/core/init.d.ts +45 -5
  148. package/dist/core/init.d.ts.map +1 -1
  149. package/dist/core/init.js +134 -24
  150. package/dist/core/init.js.map +1 -1
  151. package/dist/core/loaders.d.ts +170 -14
  152. package/dist/core/loaders.d.ts.map +1 -1
  153. package/dist/core/loaders.js +570 -89
  154. package/dist/core/loaders.js.map +1 -1
  155. package/dist/core/memory-add-link.d.ts +28 -0
  156. package/dist/core/memory-add-link.d.ts.map +1 -0
  157. package/dist/core/memory-add-link.js +67 -0
  158. package/dist/core/memory-add-link.js.map +1 -0
  159. package/dist/core/memory-add-type.d.ts +25 -1
  160. package/dist/core/memory-add-type.d.ts.map +1 -1
  161. package/dist/core/memory-add-type.js +59 -1
  162. package/dist/core/memory-add-type.js.map +1 -1
  163. package/dist/core/memory-amend.d.ts +80 -0
  164. package/dist/core/memory-amend.d.ts.map +1 -0
  165. package/dist/core/memory-amend.js +160 -0
  166. package/dist/core/memory-amend.js.map +1 -0
  167. package/dist/core/memory-supersede.d.ts +28 -0
  168. package/dist/core/memory-supersede.d.ts.map +1 -0
  169. package/dist/core/memory-supersede.js +79 -0
  170. package/dist/core/memory-supersede.js.map +1 -0
  171. package/dist/core/memory-transition.d.ts +190 -30
  172. package/dist/core/memory-transition.d.ts.map +1 -1
  173. package/dist/core/memory-transition.js +480 -59
  174. package/dist/core/memory-transition.js.map +1 -1
  175. package/dist/core/registry.d.ts +82 -10
  176. package/dist/core/registry.d.ts.map +1 -1
  177. package/dist/core/registry.js +50 -0
  178. package/dist/core/registry.js.map +1 -1
  179. package/dist/core/relevance.d.ts +74 -12
  180. package/dist/core/relevance.d.ts.map +1 -1
  181. package/dist/core/relevance.js +45 -27
  182. package/dist/core/relevance.js.map +1 -1
  183. package/dist/core/require-reason.d.ts.map +1 -1
  184. package/dist/core/require-reason.js +6 -5
  185. package/dist/core/require-reason.js.map +1 -1
  186. package/dist/core/required-fields.d.ts +28 -0
  187. package/dist/core/required-fields.d.ts.map +1 -0
  188. package/dist/core/required-fields.js +60 -0
  189. package/dist/core/required-fields.js.map +1 -0
  190. package/dist/core/revision.d.ts +82 -0
  191. package/dist/core/revision.d.ts.map +1 -0
  192. package/dist/core/revision.js +144 -0
  193. package/dist/core/revision.js.map +1 -0
  194. package/dist/core/types.d.ts +28 -2
  195. package/dist/core/types.d.ts.map +1 -1
  196. package/dist/core/types.js +12 -3
  197. package/dist/core/types.js.map +1 -1
  198. package/dist/core/workflow-core-checks.d.ts +34 -0
  199. package/dist/core/workflow-core-checks.d.ts.map +1 -0
  200. package/dist/core/workflow-core-checks.js +403 -0
  201. package/dist/core/workflow-core-checks.js.map +1 -0
  202. package/dist/core/workflow-create-remove.d.ts +65 -0
  203. package/dist/core/workflow-create-remove.d.ts.map +1 -0
  204. package/dist/core/workflow-create-remove.js +277 -0
  205. package/dist/core/workflow-create-remove.js.map +1 -0
  206. package/dist/core/workflow-deduction.d.ts +48 -0
  207. package/dist/core/workflow-deduction.d.ts.map +1 -0
  208. package/dist/core/workflow-deduction.js +523 -0
  209. package/dist/core/workflow-deduction.js.map +1 -0
  210. package/dist/core/workflow-diagnostics.d.ts +83 -0
  211. package/dist/core/workflow-diagnostics.d.ts.map +1 -0
  212. package/dist/core/workflow-diagnostics.js +375 -0
  213. package/dist/core/workflow-diagnostics.js.map +1 -0
  214. package/dist/core/workflow-exit-state.d.ts +125 -0
  215. package/dist/core/workflow-exit-state.d.ts.map +1 -0
  216. package/dist/core/workflow-exit-state.js +243 -0
  217. package/dist/core/workflow-exit-state.js.map +1 -0
  218. package/dist/core/workflow-finalize.d.ts +37 -0
  219. package/dist/core/workflow-finalize.d.ts.map +1 -0
  220. package/dist/core/workflow-finalize.js +153 -0
  221. package/dist/core/workflow-finalize.js.map +1 -0
  222. package/dist/core/workflow-list-show.d.ts +176 -0
  223. package/dist/core/workflow-list-show.d.ts.map +1 -0
  224. package/dist/core/workflow-list-show.js +268 -0
  225. package/dist/core/workflow-list-show.js.map +1 -0
  226. package/dist/core/workflow-next.d.ts +268 -0
  227. package/dist/core/workflow-next.d.ts.map +1 -0
  228. package/dist/core/workflow-next.js +503 -0
  229. package/dist/core/workflow-next.js.map +1 -0
  230. package/dist/core/workflow-registry.d.ts +52 -0
  231. package/dist/core/workflow-registry.d.ts.map +1 -0
  232. package/dist/core/workflow-registry.js +158 -0
  233. package/dist/core/workflow-registry.js.map +1 -0
  234. package/dist/core/workflow-start-end.d.ts +71 -0
  235. package/dist/core/workflow-start-end.d.ts.map +1 -0
  236. package/dist/core/workflow-start-end.js +298 -0
  237. package/dist/core/workflow-start-end.js.map +1 -0
  238. package/dist/core/workflow-status.d.ts +83 -0
  239. package/dist/core/workflow-status.d.ts.map +1 -0
  240. package/dist/core/workflow-status.js +125 -0
  241. package/dist/core/workflow-status.js.map +1 -0
  242. package/dist/core/write-guard.d.ts +51 -8
  243. package/dist/core/write-guard.d.ts.map +1 -1
  244. package/dist/core/write-guard.js +139 -9
  245. package/dist/core/write-guard.js.map +1 -1
  246. package/dist/directives/index.d.ts +3 -2
  247. package/dist/directives/index.d.ts.map +1 -1
  248. package/dist/directives/index.js +5 -3
  249. package/dist/directives/index.js.map +1 -1
  250. package/dist/directives/schema.d.ts +34 -29
  251. package/dist/directives/schema.d.ts.map +1 -1
  252. package/dist/directives/schema.js +35 -29
  253. package/dist/directives/schema.js.map +1 -1
  254. package/dist/directives/version.d.ts +28 -0
  255. package/dist/directives/version.d.ts.map +1 -0
  256. package/dist/directives/version.js +51 -0
  257. package/dist/directives/version.js.map +1 -0
  258. package/dist/dna/edit.d.ts +7 -6
  259. package/dist/dna/edit.d.ts.map +1 -1
  260. package/dist/dna/edit.js +96 -41
  261. package/dist/dna/edit.js.map +1 -1
  262. package/dist/dna/mutate.js +18 -3
  263. package/dist/dna/mutate.js.map +1 -1
  264. package/dist/dna/path.d.ts +2 -2
  265. package/dist/dna/roles.d.ts +1 -1
  266. package/dist/dna/schema.d.ts +61 -3
  267. package/dist/dna/schema.d.ts.map +1 -1
  268. package/dist/dna/schema.js +121 -6
  269. package/dist/dna/schema.js.map +1 -1
  270. package/dist/mcp/dna-resource.d.ts.map +1 -1
  271. package/dist/mcp/dna-resource.js +4 -4
  272. package/dist/mcp/dna-resource.js.map +1 -1
  273. package/dist/mcp/index.d.ts +8 -4
  274. package/dist/mcp/index.d.ts.map +1 -1
  275. package/dist/mcp/index.js +13 -2
  276. package/dist/mcp/index.js.map +1 -1
  277. package/dist/mcp/memory-resource.d.ts.map +1 -1
  278. package/dist/mcp/memory-resource.js +30 -19
  279. package/dist/mcp/memory-resource.js.map +1 -1
  280. package/dist/mcp/prompt.d.ts +61 -24
  281. package/dist/mcp/prompt.d.ts.map +1 -1
  282. package/dist/mcp/prompt.js +176 -47
  283. package/dist/mcp/prompt.js.map +1 -1
  284. package/dist/mcp/read-only.d.ts +39 -0
  285. package/dist/mcp/read-only.d.ts.map +1 -1
  286. package/dist/mcp/read-only.js +61 -0
  287. package/dist/mcp/read-only.js.map +1 -1
  288. package/dist/mcp/registrar.d.ts +10 -0
  289. package/dist/mcp/registrar.d.ts.map +1 -1
  290. package/dist/mcp/registrar.js +48 -12
  291. package/dist/mcp/registrar.js.map +1 -1
  292. package/dist/mcp/server.d.ts +21 -14
  293. package/dist/mcp/server.d.ts.map +1 -1
  294. package/dist/mcp/server.js +41 -15
  295. package/dist/mcp/server.js.map +1 -1
  296. package/dist/mcp/session-resource.d.ts +13 -0
  297. package/dist/mcp/session-resource.d.ts.map +1 -0
  298. package/dist/mcp/session-resource.js +65 -0
  299. package/dist/mcp/session-resource.js.map +1 -0
  300. package/dist/mcp/workflow-resource.d.ts +18 -0
  301. package/dist/mcp/workflow-resource.d.ts.map +1 -1
  302. package/dist/mcp/workflow-resource.js +37 -8
  303. package/dist/mcp/workflow-resource.js.map +1 -1
  304. package/dist/memory/add.d.ts +110 -24
  305. package/dist/memory/add.d.ts.map +1 -1
  306. package/dist/memory/add.js +251 -64
  307. package/dist/memory/add.js.map +1 -1
  308. package/dist/memory/audit.d.ts +152 -39
  309. package/dist/memory/audit.d.ts.map +1 -1
  310. package/dist/memory/audit.js +257 -103
  311. package/dist/memory/audit.js.map +1 -1
  312. package/dist/memory/commit-message.d.ts +60 -38
  313. package/dist/memory/commit-message.d.ts.map +1 -1
  314. package/dist/memory/commit-message.js +167 -21
  315. package/dist/memory/commit-message.js.map +1 -1
  316. package/dist/memory/entry.d.ts.map +1 -1
  317. package/dist/memory/entry.js +1 -2
  318. package/dist/memory/entry.js.map +1 -1
  319. package/dist/memory/frontmatter-edit.d.ts +16 -4
  320. package/dist/memory/frontmatter-edit.d.ts.map +1 -1
  321. package/dist/memory/frontmatter-edit.js +13 -1
  322. package/dist/memory/frontmatter-edit.js.map +1 -1
  323. package/dist/memory/git-log.d.ts +18 -6
  324. package/dist/memory/git-log.d.ts.map +1 -1
  325. package/dist/memory/git-log.js +40 -13
  326. package/dist/memory/git-log.js.map +1 -1
  327. package/dist/memory/history.d.ts +73 -12
  328. package/dist/memory/history.d.ts.map +1 -1
  329. package/dist/memory/history.js +128 -44
  330. package/dist/memory/history.js.map +1 -1
  331. package/dist/memory/index.d.ts +13 -11
  332. package/dist/memory/index.d.ts.map +1 -1
  333. package/dist/memory/index.js +34 -3
  334. package/dist/memory/index.js.map +1 -1
  335. package/dist/memory/query.d.ts +161 -19
  336. package/dist/memory/query.d.ts.map +1 -1
  337. package/dist/memory/query.js +435 -55
  338. package/dist/memory/query.js.map +1 -1
  339. package/dist/memory/schema.d.ts +52 -3
  340. package/dist/memory/schema.d.ts.map +1 -1
  341. package/dist/memory/schema.js +132 -8
  342. package/dist/memory/schema.js.map +1 -1
  343. package/dist/memory/state-machine.d.ts +65 -10
  344. package/dist/memory/state-machine.d.ts.map +1 -1
  345. package/dist/memory/state-machine.js +134 -46
  346. package/dist/memory/state-machine.js.map +1 -1
  347. package/dist/memory/submit.d.ts +52 -3
  348. package/dist/memory/submit.d.ts.map +1 -1
  349. package/dist/memory/submit.js +118 -11
  350. package/dist/memory/submit.js.map +1 -1
  351. package/dist/storage/build-stamp.d.ts +18 -0
  352. package/dist/storage/build-stamp.d.ts.map +1 -0
  353. package/dist/storage/build-stamp.js +68 -0
  354. package/dist/storage/build-stamp.js.map +1 -0
  355. package/dist/storage/builtin-adapters.d.ts +32 -0
  356. package/dist/storage/builtin-adapters.d.ts.map +1 -0
  357. package/dist/storage/builtin-adapters.js +100 -0
  358. package/dist/storage/builtin-adapters.js.map +1 -0
  359. package/dist/storage/builtin-directives.d.ts +0 -29
  360. package/dist/storage/builtin-directives.d.ts.map +1 -1
  361. package/dist/storage/builtin-directives.js +9 -4
  362. package/dist/storage/builtin-directives.js.map +1 -1
  363. package/dist/storage/commit.d.ts +310 -3
  364. package/dist/storage/commit.d.ts.map +1 -1
  365. package/dist/storage/commit.js +807 -33
  366. package/dist/storage/commit.js.map +1 -1
  367. package/dist/storage/document.d.ts +6 -0
  368. package/dist/storage/document.d.ts.map +1 -1
  369. package/dist/storage/document.js +10 -1
  370. package/dist/storage/document.js.map +1 -1
  371. package/dist/storage/dry-run.d.ts +65 -0
  372. package/dist/storage/dry-run.d.ts.map +1 -0
  373. package/dist/storage/dry-run.js +150 -0
  374. package/dist/storage/dry-run.js.map +1 -0
  375. package/dist/storage/errors.d.ts +26 -0
  376. package/dist/storage/errors.d.ts.map +1 -1
  377. package/dist/storage/errors.js +27 -1
  378. package/dist/storage/errors.js.map +1 -1
  379. package/dist/storage/frontmatter.d.ts +1 -1
  380. package/dist/storage/frontmatter.js +1 -1
  381. package/dist/storage/git-read.d.ts +64 -0
  382. package/dist/storage/git-read.d.ts.map +1 -0
  383. package/dist/storage/git-read.js +107 -0
  384. package/dist/storage/git-read.js.map +1 -0
  385. package/dist/storage/index.d.ts +13 -5
  386. package/dist/storage/index.d.ts.map +1 -1
  387. package/dist/storage/index.js +39 -2
  388. package/dist/storage/index.js.map +1 -1
  389. package/dist/storage/layout.d.ts +2 -2
  390. package/dist/storage/layout.d.ts.map +1 -1
  391. package/dist/storage/layout.js +2 -7
  392. package/dist/storage/layout.js.map +1 -1
  393. package/dist/storage/memory-path.d.ts +18 -5
  394. package/dist/storage/memory-path.d.ts.map +1 -1
  395. package/dist/storage/memory-path.js +41 -5
  396. package/dist/storage/memory-path.js.map +1 -1
  397. package/dist/storage/snapshot.js +1 -1
  398. package/dist/storage/templates.d.ts +33 -6
  399. package/dist/storage/templates.d.ts.map +1 -1
  400. package/dist/storage/templates.js +170 -16
  401. package/dist/storage/templates.js.map +1 -1
  402. package/dist/validation/config-version.d.ts +19 -0
  403. package/dist/validation/config-version.d.ts.map +1 -0
  404. package/dist/validation/config-version.js +53 -0
  405. package/dist/validation/config-version.js.map +1 -0
  406. package/dist/validation/diagnostic.d.ts +40 -0
  407. package/dist/validation/diagnostic.d.ts.map +1 -0
  408. package/dist/validation/diagnostic.js +41 -0
  409. package/dist/validation/diagnostic.js.map +1 -0
  410. package/dist/validation/format.d.ts +74 -0
  411. package/dist/validation/format.d.ts.map +1 -0
  412. package/dist/validation/format.js +104 -0
  413. package/dist/validation/format.js.map +1 -0
  414. package/dist/validation/id.d.ts +14 -0
  415. package/dist/validation/id.d.ts.map +1 -1
  416. package/dist/validation/id.js +35 -11
  417. package/dist/validation/id.js.map +1 -1
  418. package/dist/validation/identity.d.ts +40 -0
  419. package/dist/validation/identity.d.ts.map +1 -0
  420. package/dist/validation/identity.js +78 -0
  421. package/dist/validation/identity.js.map +1 -0
  422. package/dist/validation/index.d.ts +9 -3
  423. package/dist/validation/index.d.ts.map +1 -1
  424. package/dist/validation/index.js +30 -1
  425. package/dist/validation/index.js.map +1 -1
  426. package/dist/validation/secret-scan.d.ts +3 -2
  427. package/dist/validation/secret-scan.d.ts.map +1 -1
  428. package/dist/validation/secret-scan.js +32 -11
  429. package/dist/validation/secret-scan.js.map +1 -1
  430. package/dist/validation/warning.d.ts +30 -16
  431. package/dist/validation/warning.d.ts.map +1 -1
  432. package/dist/validation/warning.js +74 -7
  433. package/dist/validation/warning.js.map +1 -1
  434. package/dist/workflow/bindings.d.ts +152 -0
  435. package/dist/workflow/bindings.d.ts.map +1 -0
  436. package/dist/workflow/bindings.js +249 -0
  437. package/dist/workflow/bindings.js.map +1 -0
  438. package/dist/workflow/create.d.ts +13 -0
  439. package/dist/workflow/create.d.ts.map +1 -0
  440. package/dist/workflow/create.js +41 -0
  441. package/dist/workflow/create.js.map +1 -0
  442. package/dist/workflow/deduce.d.ts +400 -0
  443. package/dist/workflow/deduce.d.ts.map +1 -0
  444. package/dist/workflow/deduce.js +1109 -0
  445. package/dist/workflow/deduce.js.map +1 -0
  446. package/dist/workflow/manifest-edit.d.ts +19 -0
  447. package/dist/workflow/manifest-edit.d.ts.map +1 -0
  448. package/dist/workflow/manifest-edit.js +124 -0
  449. package/dist/workflow/manifest-edit.js.map +1 -0
  450. package/dist/workflow/schema.d.ts +158 -19
  451. package/dist/workflow/schema.d.ts.map +1 -1
  452. package/dist/workflow/schema.js +163 -15
  453. package/dist/workflow/schema.js.map +1 -1
  454. package/package.json +18 -8
@@ -14,7 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.CORE_MODULES = exports.dnaEntryFieldOfOption = exports.dnaEntryOptionName = exports.DNA_ENTRY_OPTION_PREFIX = exports.verifyBuiltinTemplates = exports.verifyCommittedPaths = exports.undeclaredCommittedPaths = exports.requireUnmodifiedTargets = exports.requireUnmodifiedTarget = exports.requireAbsentTarget = exports.committedScopeError = exports.CONFIG_WRITE_CONTRACT = exports.resolveAddType = exports.verifyCommittedScope = exports.prepareMemoryTransition = exports.commitMemoryTransition = exports.NO_RELEVANT_MEMORY_NOTE = exports.filterRelevantMemoryDocuments = exports.DEFAULT_CONTEXT_LIMITS = exports.WINGFOIL_ALREADY_INITIALIZED = exports.initWingfoilProject = exports.initWingfoilStorage = exports.UNASSIGNED_ASSIGNMENT = exports.GLOBAL_ASSIGNMENT = exports.loadDirectiveListing = exports.buildDirectiveListing = exports.selectDirectivesById = exports.resolveRoleDirectives = exports.assembleExecutionContext = exports.loadWorkflowsYaml = exports.loadRolesYamlAtHead = exports.loadRolesYaml = exports.loadMemoryYamlAtHead = exports.loadMemoryYaml = exports.loadDnaYamlAtHead = exports.loadDnaYaml = exports.loadDirectivesAtHead = exports.loadDirectives = exports.ROLES_YAML_PATH = exports.MEMORY_YAML_PATH = exports.DNA_YAML_PATH = exports.DIRECTIVES_DIR_PATH = exports.MODULE_NAME = void 0;
17
+ exports.workflowStatusAtHead = exports.statusWorkflow = exports.renderStatusConsole = exports.W_APPROVAL_ROLE_WITHOUT_AUTHORITY = exports.routeApproval = exports.approvalDiagnostics = exports.WORKFLOWS_MANIFEST_PATH = exports.workflowExistsMessage = exports.renderRemoveConsole = exports.renderCreateConsole = exports.NO_DECLARATION_MESSAGE = exports.KIND_CONFLICT_MESSAGE = exports.renderStartConsole = exports.renderEndConsole = exports.NO_PLAN_TYPE_MESSAGE = exports.NO_ACTIVE_WORKFLOW_MESSAGE = exports.workflowFinalizeAtHead = exports.renderFinalizeConsole = exports.phaseRecordTrailers = exports.NO_ACTIVE_WORKFLOW_TO_FINALIZE = exports.workflowNextAtHead = exports.renderNextConsole = exports.nextWorkflow = exports.buildStep = exports.loadWorkflowRegistryAtRev = exports.loadWorkflowRegistryAtHead = exports.loadWorkflowRegistry = exports.RevisionError = exports.resolveRevision = exports.isWellFormedRevision = exports.loadWorkflowsYamlAtRev = exports.loadWorkflowsYaml = exports.loadRolesYamlAtRev = exports.loadRolesYamlAtHead = exports.loadRolesYaml = exports.loadMemoryYamlAtRev = exports.loadMemoryYamlAtHead = exports.loadMemoryYaml = exports.loadDnaYamlAtRev = exports.loadDnaYamlAtHead = exports.loadDnaYaml = exports.loadDirectivesAtRev = exports.loadDirectivesAtHead = exports.loadDirectives = exports.ROLES_YAML_PATH = exports.MEMORY_YAML_PATH = exports.DNA_YAML_PATH = exports.DIRECTIVES_DIR_PATH = exports.ConfigFileMissingError = exports.MODULE_NAME = void 0;
18
+ exports.requireUnmodifiedTargets = exports.requireUnmodifiedTarget = exports.requireNoDivergentStage = exports.requireInspectableTarget = exports.requireAbsentTarget = exports.committedScopeError = exports.CONFIG_WRITE_CONTRACT = exports.resolveAddType = exports.verifyCommittedScope = exports.commitMemoryTransition = exports.beginMemoryTransition = exports.selectRelevantMemoryDocuments = exports.NO_RELEVANT_MEMORY_NOTE = exports.filterRelevantMemoryDocuments = exports.DEFAULT_CONTEXT_LIMITS = exports.WINGFOIL_NOT_INITIALIZED = exports.WINGFOIL_ALREADY_INITIALIZED = exports.requireInitializedProject = exports.initWingfoilProject = exports.initWingfoilStorage = exports.runAsDryRun = exports.DRY_RUN_FLAG = exports.UNASSIGNED_ASSIGNMENT = exports.GLOBAL_ASSIGNMENT = exports.loadDirectiveListing = exports.buildDirectiveListing = exports.WrittenTimestamp = exports.validateExecutionContext = exports.serializeExecutionContext = exports.selectDirectivesById = exports.resolveRoleDirectives = exports.CONTEXT_PAYLOAD_FORMAT = exports.assembleExecutionContext = exports.parseElementRef = exports.malformedElementRefMessage = exports.W_UNRESOLVED_TOKEN = exports.W_UNCOMMITTED_INPUTS = exports.W_MEMORY_INVALID_STATE = exports.W_INSTANCE_WORKFLOW_UNKNOWN = exports.resolveInstanceRef = exports.NO_ITERATION_NOTE = exports.deduceWorkflowState = exports.selectWorkflowInstance = exports.readDeductionSnapshotAtHead = exports.deduceWorkflowStateAtHead = exports.workflowExitStates = exports.machineStates = exports.iterationStartState = exports.workflowCoreDiagnostics = exports.W_WORKFLOW_CHECKS_NOT_RUN = void 0;
19
+ exports.CORE_MODULES = exports.dnaEntryFieldOfOption = exports.dnaEntryOptionName = exports.DNA_ENTRY_OPTION_PREFIX = exports.DNA_REWRITE_PLANNED_WARNING = exports.DNA_REWRITE_WARNING = exports.verifyBuiltinTemplates = exports.verifyCommittedPaths = exports.undeclaredCommittedPaths = void 0;
20
+ exports.coreErrorOf = coreErrorOf;
21
+ exports.loadDnaRoleSet = loadDnaRoleSet;
22
+ exports.dnaRewriteConflict = dnaRewriteConflict;
18
23
  /**
19
24
  * `core` module — shared domain logic; single behavior behind both the CLI and MCP surfaces
20
25
  * (REQ-SYS-05). Exposes one loader per Project pillar (task-004-decoupled-pillars, REQ-SYS-02):
@@ -43,24 +48,42 @@ const storage_1 = require("../storage");
43
48
  // (same as `src/memory/entry.ts`): `memory add` needs the CONFINED target path before it writes, to
44
49
  // run task-092's absence guard on it.
45
50
  const memory_path_1 = require("../storage/memory-path");
51
+ const errors_1 = require("../storage/errors");
46
52
  const memory_1 = require("../memory");
47
53
  const loaders_1 = require("./loaders");
54
+ const workflow_list_show_1 = require("./workflow-list-show");
55
+ const workflow_next_1 = require("./workflow-next");
56
+ const workflow_start_end_1 = require("./workflow-start-end");
57
+ const workflow_finalize_1 = require("./workflow-finalize");
58
+ const workflow_status_1 = require("./workflow-status");
59
+ const workflow_create_remove_1 = require("./workflow-create-remove");
48
60
  const directives_list_1 = require("./directives-list");
49
61
  const context_1 = require("./context");
50
62
  const directive_assign_1 = require("./directive-assign");
51
63
  const git_identity_1 = require("./git-identity");
64
+ const required_fields_1 = require("./required-fields");
65
+ const init_1 = require("./init");
52
66
  const builtin_asset_1 = require("./builtin-asset");
53
67
  const confinement_1 = require("./confinement");
54
68
  const approval_authority_1 = require("./approval-authority");
55
69
  const require_reason_1 = require("./require-reason");
56
70
  const memory_transition_1 = require("./memory-transition");
71
+ const memory_supersede_1 = require("./memory-supersede");
72
+ const memory_amend_1 = require("./memory-amend");
57
73
  const memory_add_type_1 = require("./memory-add-type");
74
+ const memory_add_link_1 = require("./memory-add-link");
58
75
  const write_guard_1 = require("./write-guard");
76
+ const agent_execute_1 = require("./agent-execute");
77
+ const agent_list_1 = require("./agent-list");
78
+ const agent_show_1 = require("./agent-show");
59
79
  const usage_error_1 = require("./usage-error");
80
+ const registry_1 = require("./registry");
60
81
  const types_1 = require("./types");
61
82
  /** This module's `dna.yaml` name (`core`) — the stable identifier surfaces and tests key it by. */
62
83
  exports.MODULE_NAME = 'core';
63
84
  var loaders_2 = require("./loaders");
85
+ // A missing working-tree configuration file, named repository-relative (task-179, `bug-245`).
86
+ Object.defineProperty(exports, "ConfigFileMissingError", { enumerable: true, get: function () { return loaders_2.ConfigFileMissingError; } });
64
87
  Object.defineProperty(exports, "DIRECTIVES_DIR_PATH", { enumerable: true, get: function () { return loaders_2.DIRECTIVES_DIR_PATH; } });
65
88
  Object.defineProperty(exports, "DNA_YAML_PATH", { enumerable: true, get: function () { return loaders_2.DNA_YAML_PATH; } });
66
89
  Object.defineProperty(exports, "MEMORY_YAML_PATH", { enumerable: true, get: function () { return loaders_2.MEMORY_YAML_PATH; } });
@@ -70,17 +93,98 @@ Object.defineProperty(exports, "loadDirectives", { enumerable: true, get: functi
70
93
  // task-090 kept `loadDnaYamlAtHead` private while that rule was undecided; it is decided now, and a
71
94
  // facility the ruling tells the next implementer to use has to be reachable (task-091, D3).
72
95
  Object.defineProperty(exports, "loadDirectivesAtHead", { enumerable: true, get: function () { return loaders_2.loadDirectivesAtHead; } });
96
+ // The same readers at any commit (task-137, `spec-012` §2 `stateRef`); each `…AtHead` is its
97
+ // `…AtRev` at `'HEAD'`.
98
+ Object.defineProperty(exports, "loadDirectivesAtRev", { enumerable: true, get: function () { return loaders_2.loadDirectivesAtRev; } });
73
99
  Object.defineProperty(exports, "loadDnaYaml", { enumerable: true, get: function () { return loaders_2.loadDnaYaml; } });
74
100
  Object.defineProperty(exports, "loadDnaYamlAtHead", { enumerable: true, get: function () { return loaders_2.loadDnaYamlAtHead; } });
101
+ Object.defineProperty(exports, "loadDnaYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadDnaYamlAtRev; } });
75
102
  Object.defineProperty(exports, "loadMemoryYaml", { enumerable: true, get: function () { return loaders_2.loadMemoryYaml; } });
76
103
  Object.defineProperty(exports, "loadMemoryYamlAtHead", { enumerable: true, get: function () { return loaders_2.loadMemoryYamlAtHead; } });
104
+ Object.defineProperty(exports, "loadMemoryYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadMemoryYamlAtRev; } });
77
105
  Object.defineProperty(exports, "loadRolesYaml", { enumerable: true, get: function () { return loaders_2.loadRolesYaml; } });
78
106
  Object.defineProperty(exports, "loadRolesYamlAtHead", { enumerable: true, get: function () { return loaders_2.loadRolesYamlAtHead; } });
107
+ Object.defineProperty(exports, "loadRolesYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadRolesYamlAtRev; } });
79
108
  Object.defineProperty(exports, "loadWorkflowsYaml", { enumerable: true, get: function () { return loaders_2.loadWorkflowsYaml; } });
109
+ Object.defineProperty(exports, "loadWorkflowsYamlAtRev", { enumerable: true, get: function () { return loaders_2.loadWorkflowsYamlAtRev; } });
110
+ var revision_1 = require("./revision");
111
+ Object.defineProperty(exports, "isWellFormedRevision", { enumerable: true, get: function () { return revision_1.isWellFormedRevision; } });
112
+ Object.defineProperty(exports, "resolveRevision", { enumerable: true, get: function () { return revision_1.resolveRevision; } });
113
+ Object.defineProperty(exports, "RevisionError", { enumerable: true, get: function () { return revision_1.RevisionError; } });
114
+ // task-194: the workflow registry with spec-003's core checks, and the exit-state computation
115
+ // (spec-017 §4.4) task-198's deduction reuses.
116
+ var workflow_registry_1 = require("./workflow-registry");
117
+ Object.defineProperty(exports, "loadWorkflowRegistry", { enumerable: true, get: function () { return workflow_registry_1.loadWorkflowRegistry; } });
118
+ Object.defineProperty(exports, "loadWorkflowRegistryAtHead", { enumerable: true, get: function () { return workflow_registry_1.loadWorkflowRegistryAtHead; } });
119
+ Object.defineProperty(exports, "loadWorkflowRegistryAtRev", { enumerable: true, get: function () { return workflow_registry_1.loadWorkflowRegistryAtRev; } });
120
+ // task-216: `workflow next` at `HEAD` (`spec-017` §6, §7.3, §8 `Step` / `NextResult`); the operation is
121
+ // `CORE_MODULES`' `workflowNext`, and the step builder is what `agent execute --next` (task-235) and `status`
122
+ // (task-225) reuse. `workflowNextAtHead` is also what the MCP Resource `wingfoil://workflows/-/next` serves (task-239).
123
+ var workflow_next_2 = require("./workflow-next");
124
+ Object.defineProperty(exports, "buildStep", { enumerable: true, get: function () { return workflow_next_2.buildStep; } });
125
+ Object.defineProperty(exports, "nextWorkflow", { enumerable: true, get: function () { return workflow_next_2.nextWorkflow; } });
126
+ Object.defineProperty(exports, "renderNextConsole", { enumerable: true, get: function () { return workflow_next_2.renderNextConsole; } });
127
+ Object.defineProperty(exports, "workflowNextAtHead", { enumerable: true, get: function () { return workflow_next_2.workflowNextAtHead; } });
128
+ // task-226: `workflow finalize` (`spec-017` §7.9, §8 `FinalizeResult`); the operation is `CORE_MODULES`'
129
+ // `workflowFinalize`.
130
+ var workflow_finalize_2 = require("./workflow-finalize");
131
+ Object.defineProperty(exports, "NO_ACTIVE_WORKFLOW_TO_FINALIZE", { enumerable: true, get: function () { return workflow_finalize_2.NO_ACTIVE_WORKFLOW_TO_FINALIZE; } });
132
+ Object.defineProperty(exports, "phaseRecordTrailers", { enumerable: true, get: function () { return workflow_finalize_2.phaseRecordTrailers; } });
133
+ Object.defineProperty(exports, "renderFinalizeConsole", { enumerable: true, get: function () { return workflow_finalize_2.renderFinalizeConsole; } });
134
+ Object.defineProperty(exports, "workflowFinalizeAtHead", { enumerable: true, get: function () { return workflow_finalize_2.workflowFinalizeAtHead; } });
135
+ // task-217: `CORE_MODULES`' `workflowStart` / `workflowEnd` (`spec-017` §7.1–§7.2) and their values.
136
+ var workflow_start_end_2 = require("./workflow-start-end");
137
+ Object.defineProperty(exports, "NO_ACTIVE_WORKFLOW_MESSAGE", { enumerable: true, get: function () { return workflow_start_end_2.NO_ACTIVE_WORKFLOW_MESSAGE; } });
138
+ Object.defineProperty(exports, "NO_PLAN_TYPE_MESSAGE", { enumerable: true, get: function () { return workflow_start_end_2.NO_PLAN_TYPE_MESSAGE; } });
139
+ Object.defineProperty(exports, "renderEndConsole", { enumerable: true, get: function () { return workflow_start_end_2.renderEndConsole; } });
140
+ Object.defineProperty(exports, "renderStartConsole", { enumerable: true, get: function () { return workflow_start_end_2.renderStartConsole; } });
141
+ var workflow_create_remove_2 = require("./workflow-create-remove");
142
+ Object.defineProperty(exports, "KIND_CONFLICT_MESSAGE", { enumerable: true, get: function () { return workflow_create_remove_2.KIND_CONFLICT_MESSAGE; } });
143
+ Object.defineProperty(exports, "NO_DECLARATION_MESSAGE", { enumerable: true, get: function () { return workflow_create_remove_2.NO_DECLARATION_MESSAGE; } });
144
+ Object.defineProperty(exports, "renderCreateConsole", { enumerable: true, get: function () { return workflow_create_remove_2.renderCreateConsole; } });
145
+ Object.defineProperty(exports, "renderRemoveConsole", { enumerable: true, get: function () { return workflow_create_remove_2.renderRemoveConsole; } });
146
+ Object.defineProperty(exports, "workflowExistsMessage", { enumerable: true, get: function () { return workflow_create_remove_2.workflowExistsMessage; } });
147
+ Object.defineProperty(exports, "WORKFLOWS_MANIFEST_PATH", { enumerable: true, get: function () { return workflow_create_remove_2.WORKFLOWS_MANIFEST_PATH; } });
148
+ // task-225: `workflow status` at `HEAD` (`spec-017` §5.1–§5.4, §7.4, §8 `StatusResult`); approval detection and
149
+ // routing live in the step builder above, so `next` reports them too. The operation is `CORE_MODULES`' `workflowStatus`.
150
+ var workflow_next_3 = require("./workflow-next");
151
+ Object.defineProperty(exports, "approvalDiagnostics", { enumerable: true, get: function () { return workflow_next_3.approvalDiagnostics; } });
152
+ Object.defineProperty(exports, "routeApproval", { enumerable: true, get: function () { return workflow_next_3.routeApproval; } });
153
+ Object.defineProperty(exports, "W_APPROVAL_ROLE_WITHOUT_AUTHORITY", { enumerable: true, get: function () { return workflow_next_3.W_APPROVAL_ROLE_WITHOUT_AUTHORITY; } });
154
+ var workflow_status_2 = require("./workflow-status");
155
+ Object.defineProperty(exports, "renderStatusConsole", { enumerable: true, get: function () { return workflow_status_2.renderStatusConsole; } });
156
+ Object.defineProperty(exports, "statusWorkflow", { enumerable: true, get: function () { return workflow_status_2.statusWorkflow; } });
157
+ Object.defineProperty(exports, "workflowStatusAtHead", { enumerable: true, get: function () { return workflow_status_2.workflowStatusAtHead; } });
158
+ var workflow_core_checks_1 = require("./workflow-core-checks");
159
+ Object.defineProperty(exports, "W_WORKFLOW_CHECKS_NOT_RUN", { enumerable: true, get: function () { return workflow_core_checks_1.W_WORKFLOW_CHECKS_NOT_RUN; } });
160
+ Object.defineProperty(exports, "workflowCoreDiagnostics", { enumerable: true, get: function () { return workflow_core_checks_1.workflowCoreDiagnostics; } });
161
+ var workflow_exit_state_1 = require("./workflow-exit-state");
162
+ Object.defineProperty(exports, "iterationStartState", { enumerable: true, get: function () { return workflow_exit_state_1.iterationStartState; } });
163
+ Object.defineProperty(exports, "machineStates", { enumerable: true, get: function () { return workflow_exit_state_1.machineStates; } });
164
+ Object.defineProperty(exports, "workflowExitStates", { enumerable: true, get: function () { return workflow_exit_state_1.workflowExitStates; } });
165
+ var workflow_deduction_1 = require("./workflow-deduction");
166
+ Object.defineProperty(exports, "deduceWorkflowStateAtHead", { enumerable: true, get: function () { return workflow_deduction_1.deduceWorkflowStateAtHead; } });
167
+ Object.defineProperty(exports, "readDeductionSnapshotAtHead", { enumerable: true, get: function () { return workflow_deduction_1.readDeductionSnapshotAtHead; } });
168
+ Object.defineProperty(exports, "selectWorkflowInstance", { enumerable: true, get: function () { return workflow_deduction_1.selectWorkflowInstance; } });
169
+ var deduce_1 = require("../workflow/deduce");
170
+ Object.defineProperty(exports, "deduceWorkflowState", { enumerable: true, get: function () { return deduce_1.deduceWorkflowState; } });
171
+ Object.defineProperty(exports, "NO_ITERATION_NOTE", { enumerable: true, get: function () { return deduce_1.NO_ITERATION_NOTE; } });
172
+ Object.defineProperty(exports, "resolveInstanceRef", { enumerable: true, get: function () { return deduce_1.resolveInstanceRef; } });
173
+ Object.defineProperty(exports, "W_INSTANCE_WORKFLOW_UNKNOWN", { enumerable: true, get: function () { return deduce_1.W_INSTANCE_WORKFLOW_UNKNOWN; } });
174
+ Object.defineProperty(exports, "W_MEMORY_INVALID_STATE", { enumerable: true, get: function () { return deduce_1.W_MEMORY_INVALID_STATE; } });
175
+ Object.defineProperty(exports, "W_UNCOMMITTED_INPUTS", { enumerable: true, get: function () { return deduce_1.W_UNCOMMITTED_INPUTS; } });
176
+ Object.defineProperty(exports, "W_UNRESOLVED_TOKEN", { enumerable: true, get: function () { return deduce_1.W_UNRESOLVED_TOKEN; } });
177
+ var element_ref_1 = require("./element-ref");
178
+ Object.defineProperty(exports, "malformedElementRefMessage", { enumerable: true, get: function () { return element_ref_1.malformedElementRefMessage; } });
179
+ Object.defineProperty(exports, "parseElementRef", { enumerable: true, get: function () { return element_ref_1.parseElementRef; } });
80
180
  var context_2 = require("./context");
81
181
  Object.defineProperty(exports, "assembleExecutionContext", { enumerable: true, get: function () { return context_2.assembleExecutionContext; } });
182
+ Object.defineProperty(exports, "CONTEXT_PAYLOAD_FORMAT", { enumerable: true, get: function () { return context_2.CONTEXT_PAYLOAD_FORMAT; } });
82
183
  Object.defineProperty(exports, "resolveRoleDirectives", { enumerable: true, get: function () { return context_2.resolveRoleDirectives; } });
83
184
  Object.defineProperty(exports, "selectDirectivesById", { enumerable: true, get: function () { return context_2.selectDirectivesById; } });
185
+ Object.defineProperty(exports, "serializeExecutionContext", { enumerable: true, get: function () { return context_2.serializeExecutionContext; } });
186
+ Object.defineProperty(exports, "validateExecutionContext", { enumerable: true, get: function () { return context_2.validateExecutionContext; } });
187
+ Object.defineProperty(exports, "WrittenTimestamp", { enumerable: true, get: function () { return context_2.WrittenTimestamp; } });
84
188
  var directives_list_2 = require("./directives-list");
85
189
  Object.defineProperty(exports, "buildDirectiveListing", { enumerable: true, get: function () { return directives_list_2.buildDirectiveListing; } });
86
190
  Object.defineProperty(exports, "loadDirectiveListing", { enumerable: true, get: function () { return directives_list_2.loadDirectiveListing; } });
@@ -95,17 +199,25 @@ __exportStar(require("./require-reason"), exports);
95
199
  __exportStar(require("./builtin-asset"), exports);
96
200
  __exportStar(require("./confinement"), exports);
97
201
  __exportStar(require("./usage-error"), exports);
98
- var init_1 = require("./init");
99
- Object.defineProperty(exports, "initWingfoilStorage", { enumerable: true, get: function () { return init_1.initWingfoilStorage; } });
100
- Object.defineProperty(exports, "initWingfoilProject", { enumerable: true, get: function () { return init_1.initWingfoilProject; } });
101
- Object.defineProperty(exports, "WINGFOIL_ALREADY_INITIALIZED", { enumerable: true, get: function () { return init_1.WINGFOIL_ALREADY_INITIALIZED; } });
202
+ var dry_run_1 = require("./dry-run");
203
+ Object.defineProperty(exports, "DRY_RUN_FLAG", { enumerable: true, get: function () { return dry_run_1.DRY_RUN_FLAG; } });
204
+ Object.defineProperty(exports, "runAsDryRun", { enumerable: true, get: function () { return dry_run_1.runAsDryRun; } });
205
+ var init_2 = require("./init");
206
+ Object.defineProperty(exports, "initWingfoilStorage", { enumerable: true, get: function () { return init_2.initWingfoilStorage; } });
207
+ Object.defineProperty(exports, "initWingfoilProject", { enumerable: true, get: function () { return init_2.initWingfoilProject; } });
208
+ Object.defineProperty(exports, "requireInitializedProject", { enumerable: true, get: function () { return init_2.requireInitializedProject; } });
209
+ Object.defineProperty(exports, "WINGFOIL_ALREADY_INITIALIZED", { enumerable: true, get: function () { return init_2.WINGFOIL_ALREADY_INITIALIZED; } });
210
+ Object.defineProperty(exports, "WINGFOIL_NOT_INITIALIZED", { enumerable: true, get: function () { return init_2.WINGFOIL_NOT_INITIALIZED; } });
102
211
  var relevance_1 = require("./relevance");
103
212
  Object.defineProperty(exports, "DEFAULT_CONTEXT_LIMITS", { enumerable: true, get: function () { return relevance_1.DEFAULT_CONTEXT_LIMITS; } });
104
213
  Object.defineProperty(exports, "filterRelevantMemoryDocuments", { enumerable: true, get: function () { return relevance_1.filterRelevantMemoryDocuments; } });
105
214
  Object.defineProperty(exports, "NO_RELEVANT_MEMORY_NOTE", { enumerable: true, get: function () { return relevance_1.NO_RELEVANT_MEMORY_NOTE; } });
215
+ Object.defineProperty(exports, "selectRelevantMemoryDocuments", { enumerable: true, get: function () { return relevance_1.selectRelevantMemoryDocuments; } });
216
+ // `prepareMemoryTransition` is deliberately not re-exported: outside `./memory-transition` a transition
217
+ // starts at `beginMemoryTransition`, which runs the identity pre-flight first (task-132 review).
106
218
  var memory_transition_2 = require("./memory-transition");
219
+ Object.defineProperty(exports, "beginMemoryTransition", { enumerable: true, get: function () { return memory_transition_2.beginMemoryTransition; } });
107
220
  Object.defineProperty(exports, "commitMemoryTransition", { enumerable: true, get: function () { return memory_transition_2.commitMemoryTransition; } });
108
- Object.defineProperty(exports, "prepareMemoryTransition", { enumerable: true, get: function () { return memory_transition_2.prepareMemoryTransition; } });
109
221
  Object.defineProperty(exports, "verifyCommittedScope", { enumerable: true, get: function () { return memory_transition_2.verifyCommittedScope; } });
110
222
  var memory_add_type_2 = require("./memory-add-type");
111
223
  Object.defineProperty(exports, "resolveAddType", { enumerable: true, get: function () { return memory_add_type_2.resolveAddType; } });
@@ -113,6 +225,8 @@ var write_guard_2 = require("./write-guard");
113
225
  Object.defineProperty(exports, "CONFIG_WRITE_CONTRACT", { enumerable: true, get: function () { return write_guard_2.CONFIG_WRITE_CONTRACT; } });
114
226
  Object.defineProperty(exports, "committedScopeError", { enumerable: true, get: function () { return write_guard_2.committedScopeError; } });
115
227
  Object.defineProperty(exports, "requireAbsentTarget", { enumerable: true, get: function () { return write_guard_2.requireAbsentTarget; } });
228
+ Object.defineProperty(exports, "requireInspectableTarget", { enumerable: true, get: function () { return write_guard_2.requireInspectableTarget; } });
229
+ Object.defineProperty(exports, "requireNoDivergentStage", { enumerable: true, get: function () { return write_guard_2.requireNoDivergentStage; } });
116
230
  Object.defineProperty(exports, "requireUnmodifiedTarget", { enumerable: true, get: function () { return write_guard_2.requireUnmodifiedTarget; } });
117
231
  Object.defineProperty(exports, "requireUnmodifiedTargets", { enumerable: true, get: function () { return write_guard_2.requireUnmodifiedTargets; } });
118
232
  Object.defineProperty(exports, "undeclaredCommittedPaths", { enumerable: true, get: function () { return write_guard_2.undeclaredCommittedPaths; } });
@@ -124,31 +238,71 @@ Object.defineProperty(exports, "verifyBuiltinTemplates", { enumerable: true, get
124
238
  * failures onto a `CoreResult` (spec-006 §2): a `ValidationError` from the shared two-pass pipeline
125
239
  * becomes `VALIDATION`, a missing file (Node's `ENOENT`) becomes `NOT_FOUND`; anything else
126
240
  * propagates as a genuine thrown exception (a programmer bug, not a domain failure — spec-006 §2's
127
- * "no function throws for *expected* domain failures" implies unexpected ones still may). Factored
128
- * out of `wrapReadOnly` so both `dnaShowFn` (task-026) and `pathsFn` (task-028) reuse the exact
129
- * same mapping for their own richer, argument-aware bodies instead of duplicating it.
241
+ * "no function throws for *expected* domain failures" implies unexpected ones still may). Every
242
+ * read-only query (`dnaShowFn`, `pathsFn`, `workflowListFn`, …) maps its loader's failures through it,
243
+ * so the mapping is written once. (The `wrapReadOnly` adapter it was factored out of had one caller
244
+ * left, `workflowList`, until task-204 gave that operation a flag and was removed with it.)
130
245
  */
131
246
  function loadOrError(loader) {
132
247
  try {
133
248
  return (0, types_1.coreOk)(loader());
134
249
  }
135
250
  catch (error) {
136
- if (error instanceof validation_1.ValidationError) {
137
- return (0, types_1.coreErr)({ code: 'VALIDATION', message: error.message, details: { issues: error.issues } });
138
- }
139
- const errno = error;
140
- if (errno && errno.code === 'ENOENT') {
141
- return (0, types_1.coreErr)({ code: 'NOT_FOUND', message: errno.message });
142
- }
143
- throw error;
251
+ const refusal = coreErrorOf(error);
252
+ if (refusal === null)
253
+ throw error;
254
+ return (0, types_1.coreErr)(refusal);
144
255
  }
145
256
  }
146
- /** Adapt a synchronous, throwing pillar loader into a `CoreFn` taking just `{ root }` (spec-006 §2). */
147
- function wrapReadOnly(loader) {
148
- return async (params) => {
149
- const { root } = params;
150
- return loadOrError(() => loader(root));
151
- };
257
+ /**
258
+ * The expected failure a pillar loader throws, as the {@link CoreError} a read-only query returns for
259
+ * it, or `null` when `error` is not one (a programmer bug, which the caller re-throws). This is
260
+ * {@link loadOrError}'s mapping on its own, for a surface that calls a loader directly rather than
261
+ * through a `CoreOperation` — the MCP server's Resource and Prompt handlers (task-174, `bug-184`), so a
262
+ * refusal there carries the same details (`./error-details.ts`) the CLI prints:
263
+ *
264
+ * - a loader that collects spec-003 diagnostics (task-136): the first error is the reason, and the whole
265
+ * ordered array rides `details` under spec-003's own name for it, `diagnostics`;
266
+ * - a `ValidationError` from the shared two-pass pipeline: `details.issues`;
267
+ * - a missing file (`ENOENT`): `NOT_FOUND`, with the system message.
268
+ */
269
+ function coreErrorOf(error) {
270
+ if (error instanceof validation_1.DiagnosticsError) {
271
+ return { code: 'VALIDATION', message: error.message, details: { diagnostics: error.diagnostics } };
272
+ }
273
+ if (error instanceof validation_1.ValidationError) {
274
+ return { code: 'VALIDATION', message: error.message, details: { issues: error.issues } };
275
+ }
276
+ const errno = error;
277
+ if (errno && errno.code === 'ENOENT') {
278
+ return { code: 'NOT_FOUND', message: errno.message };
279
+ }
280
+ return null;
281
+ }
282
+ /**
283
+ * The DNA role set — `dna.yaml` `team.roles[].name`, in declaration order — read once, by `wingfoil
284
+ * mcp`'s pre-flight, for the Prompts channel to serve for the server's whole life (`dl-049` (b),
285
+ * spec-004 §3.1 "derived from DNA at server start", spec-014 §1; task-174).
286
+ *
287
+ * A root with no `.wingfoil/` is refused with the shared not-initialized message
288
+ * ({@link requireInitializedProject}, task-143) before anything is read, so the refusal names no path
289
+ * (`bug-035`); an unreadable or invalid `dna.yaml` is refused as every read-only query refuses it.
290
+ */
291
+ function loadDnaRoleSet(root) {
292
+ return loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root).team.roles.map(({ name }) => name));
293
+ }
294
+ /**
295
+ * {@link loadOrError} for a read that needs the project's configuration: a root with no `.wingfoil/` is
296
+ * refused first with the shared not-initialized message ({@link requireInitializedProject}, task-143),
297
+ * which names no path; otherwise the loader runs and its failures map as `loadOrError` maps them — a
298
+ * missing file as `ConfigFileMissingError`'s repository-relative message (task-179, `bug-198`,
299
+ * `bug-245`).
300
+ */
301
+ function loadConfigOrError(root, loader) {
302
+ const initialized = (0, init_1.requireInitializedProject)(root);
303
+ if (!initialized.ok)
304
+ return initialized;
305
+ return loadOrError(loader);
152
306
  }
153
307
  /**
154
308
  * `paths` `CoreOperation.fn` (P2.5, spec-002-dna-yaml-schema's `Paths` node) —
@@ -169,7 +323,7 @@ function wrapReadOnly(loader) {
169
323
  */
170
324
  const pathsFn = async (params) => {
171
325
  const { root, positional: category } = params;
172
- const loaded = loadOrError(() => (0, loaders_1.loadDnaYaml)(root));
326
+ const loaded = loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root));
173
327
  if (!loaded.ok)
174
328
  return loaded;
175
329
  if (category === undefined)
@@ -196,7 +350,7 @@ const pathsFn = async (params) => {
196
350
  */
197
351
  const dnaShowFn = async (params) => {
198
352
  const { root, positional: section } = params;
199
- const loaded = loadOrError(() => (0, loaders_1.loadDnaYaml)(root));
353
+ const loaded = loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root));
200
354
  if (!loaded.ok || section === undefined)
201
355
  return loaded;
202
356
  const key = set_1.DNA_KEY_ALIASES[section] ?? section;
@@ -206,6 +360,25 @@ const dnaShowFn = async (params) => {
206
360
  }
207
361
  return (0, types_1.coreOk)(dna[key]);
208
362
  };
363
+ /**
364
+ * The refusal of a DNA write the in-place editor cannot express, when `--force` was not given
365
+ * (task-193, approver ruling R20/Q9 of `release-planning-rel-v0.3-plan`: the same rule as
366
+ * `dl-062` for `roles.yaml`, task-169's `rolesRewriteConflict`). `CONFLICT`, exit 1, nothing
367
+ * written. Pinned in `spec-008` §6.
368
+ */
369
+ function dnaRewriteConflict(field) {
370
+ return `dna.yaml cannot be updated in place; edit ${field} by hand, or pass --force to rewrite the whole file`;
371
+ }
372
+ /**
373
+ * The warning a `--force`d whole-file rewrite of `dna.yaml` carries on its success (task-193), on
374
+ * task-169's success-warning channel (`CoreResult.warnings`). It names what the `js-yaml` `dump` of
375
+ * the parsed document does not keep — the list `ROLES_REWRITE_WARNING` gives for `roles.yaml`,
376
+ * since the two files go through the same `dump`. Pinned in `spec-008` §6.
377
+ */
378
+ exports.DNA_REWRITE_WARNING = 'dna.yaml was rewritten as a whole file (--force): comments are not kept, and neither are quoting, flow style, ' +
379
+ 'blank lines, line endings or number formatting (1.0 becomes 1)';
380
+ /** {@link DNA_REWRITE_WARNING} as a `--dry-run` words it: the rewrite is planned, not done (task-210 review). */
381
+ exports.DNA_REWRITE_PLANNED_WARNING = exports.DNA_REWRITE_WARNING.replace('dna.yaml was rewritten', 'dna.yaml would be rewritten');
209
382
  /**
210
383
  * The shared body of every DNA mutation (`dna set`, and `dna add|remove|update` since
211
384
  * task-093-dna-mutation-surface-add-remove-update) — the mutating-op template `dna set` established
@@ -240,13 +413,20 @@ const dnaShowFn = async (params) => {
240
413
  * in-place textual edit, so every comment survives — including the inline `[SPEC]`/`[AUTHORING]`
241
414
  * field-provenance annotations a whole-file re-serialization deletes
242
415
  * (bug-004-dna-set-strips-yaml-comments, task-063). When no provably-minimal edit exists it returns
243
- * `undefined` and this FALLS BACK to `dump(dna, { lineWidth: -1 })` — correct, but comment-stripping.
244
- * Both paths are deterministic (REQ-SYS-07).
416
+ * `undefined`, and the only other candidate is the whole-file `dump(dna, { lineWidth: -1 })` —
417
+ * correct, but it keeps no comment. Both are deterministic (REQ-SYS-07). Since task-193 (`bug-019`,
418
+ * `bug-126`; ruling R20/Q9, as `dl-062`) that rewrite is **refused** (`CONFLICT`, exit 1,
419
+ * {@link dnaRewriteConflict}) unless `force` authorizes it, and a forced one succeeds with
420
+ * {@link DNA_REWRITE_WARNING}. The refusal comes after step 7, so a write the schema would refuse
421
+ * anyway is reported as the `VALIDATION` it is, not as something `--force` could fix. `force`
422
+ * authorizes the rewrite and does not demand it: an in-place edit is still made in place, unwarned.
245
423
  * 7. **Re-validate the written bytes** against `DnaYaml` (spec-002) BEFORE persisting — re-parsing the
246
424
  * serialized form, not the in-memory object, so the check honours YAML's own scalar coercion and
247
- * validates the exact bytes about to be written (`dna set version 2` writes `version: '2'` and
248
- * still fails `z.number()` on read-back). It is also what enforces the cross-field rules the verbs
249
- * do not duplicate: a member naming a role absent from `team.roles` fails here (REQ-SYS-08,
425
+ * validates the exact bytes about to be written: `--value` is a string, so
426
+ * `wingfoil dna set version --value 2` serializes `'2'` and fails `z.number()` on read-back — refused
427
+ * at exit 1, `expected number, received string`, nothing written (measured with that command on a
428
+ * fresh `init --template scrum` repository, task-184, bug-096). It is also what enforces the
429
+ * cross-field rules the verbs do not duplicate: a member naming a role absent from `team.roles` fails here (REQ-SYS-08,
250
430
  * `Team.superRefine`), which is why `dl-081` records that referential integrity needs no new check.
251
431
  * 8. **Persist + commit** through the single storage primitives (task-018) — `writeDocument` then
252
432
  * `commitPaths` on the ONE scoped path `.wingfoil/dna.yaml`; the returned sha rides
@@ -257,14 +437,20 @@ const dnaShowFn = async (params) => {
257
437
  * with the step-3 guard in place no argument can reach it, but a repository-local `pre-commit`
258
438
  * hook that rewrites and re-stages the file still can.
259
439
  */
260
- async function runDnaMutation(root, request, subject = dnaCommitSubject(request), scalarOnly = false) {
440
+ async function runDnaMutation(root, request, subject, scalarOnly, force) {
261
441
  const identity = (0, git_identity_1.requireGitIdentity)(root);
262
442
  if (!identity.ok)
263
443
  return identity;
444
+ // REQ-SEC-06 / bug-121 (task-172): `writeDocument` follows a symlinked `dna.yaml`, so a file that
445
+ // leads outside the project, or is itself a link, is refused before any read or write. First, so a
446
+ // path that leaves the project is reported as leaving it (`requireConfinedTarget`'s ordering rule).
447
+ const confined = (0, confinement_1.requireConfinedWriteTarget)(root, loaders_1.DNA_YAML_PATH, 'write');
448
+ if (!confined.ok)
449
+ return confined;
264
450
  const unmodified = (0, write_guard_1.requireUnmodifiedTarget)(root, loaders_1.DNA_YAML_PATH);
265
451
  if (!unmodified.ok)
266
452
  return unmodified;
267
- const loaded = loadOrError(() => (0, loaders_1.loadDnaYaml)(root));
453
+ const loaded = loadConfigOrError(root, () => (0, loaders_1.loadDnaYaml)(root));
268
454
  if (!loaded.ok)
269
455
  return loaded;
270
456
  if (scalarOnly) {
@@ -283,21 +469,33 @@ async function runDnaMutation(root, request, subject = dnaCommitSubject(request)
283
469
  return (0, types_1.coreErr)({ code: 'VALIDATION', message: applied.message });
284
470
  const dnaPath = (0, path_1.join)(root, loaders_1.DNA_YAML_PATH);
285
471
  const current = (0, storage_1.readDocument)(dnaPath);
286
- const serialized = (0, edit_1.applyDnaEditInText)(current, applied.edit, applied.dna) ?? (0, js_yaml_1.dump)(applied.dna, { lineWidth: -1 });
287
- const parsed = schema_1.DnaYaml.safeParse((0, validation_1.parseYaml)(serialized, dnaPath));
472
+ const inPlace = (0, edit_1.applyDnaEditInText)(current, applied.edit, applied.dna);
473
+ const serialized = inPlace ?? (0, js_yaml_1.dump)(applied.dna, { lineWidth: -1 });
474
+ // Labelled repository-relative, as every working-tree loader labels it (task-179, `bug-245`).
475
+ const parsed = schema_1.DnaYaml.safeParse((0, validation_1.parseYaml)(serialized, loaders_1.DNA_YAML_PATH));
288
476
  if (!parsed.success) {
289
- const validationError = (0, validation_1.toValidationError)(parsed.error, dnaPath);
477
+ const validationError = (0, validation_1.toValidationError)(parsed.error, loaders_1.DNA_YAML_PATH);
290
478
  return (0, types_1.coreErr)({ code: 'VALIDATION', message: validationError.message, details: { issues: validationError.issues } });
291
479
  }
292
480
  const outcome = { key: request.field, value: request.value };
293
481
  if (current === serialized)
294
482
  return (0, types_1.coreOk)(outcome);
295
- (0, storage_1.writeDocument)(dnaPath, serialized);
296
- const sha = (0, storage_1.commitPaths)(root, [loaders_1.DNA_YAML_PATH], subject);
483
+ const warnings = [];
484
+ const plannedWarnings = [];
485
+ if (inPlace === undefined) {
486
+ if (!force)
487
+ return (0, types_1.coreErr)({ code: 'CONFLICT', message: dnaRewriteConflict(request.field) });
488
+ warnings.push(exports.DNA_REWRITE_WARNING);
489
+ plannedWarnings.push(exports.DNA_REWRITE_PLANNED_WARNING);
490
+ }
491
+ const sha = (0, storage_1.writeAndCommit)(root, [{ path: loaders_1.DNA_YAML_PATH, content: serialized }], subject, {
492
+ warnings,
493
+ plannedWarnings,
494
+ });
297
495
  const leaked = (0, write_guard_1.committedScopeError)(root, sha, loaders_1.DNA_YAML_PATH, serialized);
298
496
  if (leaked)
299
497
  return leaked;
300
- return (0, types_1.coreOk)(outcome, { sha, message: subject });
498
+ return (0, types_1.coreOk)(outcome, { sha, message: subject }, warnings);
301
499
  }
302
500
  /**
303
501
  * The one-line commit subject a DNA mutation writes: `wf(dna): {verb} {field} [{value}]`, extending
@@ -313,39 +511,40 @@ function dnaCommitSubject(request) {
313
511
  * agree on where their target comes from (`dl-082-cli-parameter-shape`: a positional carries the
314
512
  * identity of the target).
315
513
  *
316
- * Three usage errors, in the order they are checked, and the order is load-bearing:
514
+ * Two usage errors, in the order they are checked:
317
515
  *
318
- * 1. **Missing** — nothing to act on.
516
+ * 1. **Missing** — nothing to act on: {@link missingOperandReason}'s `missing required argument: <path>`,
517
+ * the wording every command uses (task-179, `bug-168`). The CLI registrar refuses it first, with the
518
+ * usage hint; this is the same refusal for a caller that reaches core without the registrar.
319
519
  * 2. **Unparseable** — whatever `splitDnaPath` (`src/dna/set.ts`) refuses, reported with **its own**
320
520
  * message rather than a flat one, because the three cases are not the same complaint: an empty
321
521
  * segment (the BDD's `..language`) is malformed, an unterminated quote is a typo in the
322
522
  * delimiters, and a `"` no delimiter can account for means the *name* has no spelling at all
323
- * (`dl-083-dotted-entry-names-in-paths`, task-099). Checked BEFORE the extra-positional rule below
324
- * so that `dna set ..language python`, which is `P2.1-dna-set.feature`'s third scenario verbatim,
325
- * keeps reporting `invalid key path: '..language'` rather than the migration hint. A stale
326
- * invocation that is also malformed is malformed first.
327
- * 3. **Extra positionals** — the old `dna set <key> <value>` spelling, and any slip of the same
328
- * shape on the three new verbs. It gets a named message rather than being ignored, because
329
- * `dl-082` is a breaking change to a shipped command and silently dropping the second word would
330
- * make `dna set project.license MIT` look like it worked (`--value` absent, the write refused for
331
- * a reason that names neither the second word nor the new grammar).
523
+ * (`dl-083-dotted-entry-names-in-paths`, task-099).
332
524
  *
333
- * All three are exit `2` (`UsageError` → `exitCodeForThrow`, `spec-005-cli-command-contract` §1).
525
+ * A surplus operand — the old `dna set <key> <value>` spelling — is not checked here: since task-179
526
+ * (`bug-180`) the registrar refuses it before the project root is resolved, as for every command, with
527
+ * the `the value travels in --value` migration hint the four verbs declare ({@link DNA_SURPLUS_HINT}).
528
+ *
529
+ * Both are exit `2` (`UsageError` → `exitCodeForThrow`, `spec-005-cli-command-contract` §1).
334
530
  */
335
- function dnaPathPositional(verb, positionals) {
531
+ function dnaPathPositional(positionals) {
336
532
  const path = positionals?.[0];
337
533
  if (path === undefined) {
338
- throw new usage_error_1.UsageError(`missing required argument: wingfoil dna ${verb} <path> --value <value>`);
534
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('path'));
339
535
  }
340
536
  const split = (0, set_1.splitDnaPath)(path);
341
537
  if (!split.ok) {
342
538
  throw new usage_error_1.UsageError(split.message);
343
539
  }
344
- if ((positionals?.length ?? 0) > 1) {
345
- throw new usage_error_1.UsageError(`wingfoil dna ${verb} takes one positional <path>; the value travels in --value (got ${positionals.length} positionals)`);
346
- }
347
540
  return path;
348
541
  }
542
+ /**
543
+ * What the surplus-operand refusal of the four DNA path verbs adds (`CorePositional.surplusHint`): the
544
+ * value moved out of a second positional into `--value` (`dl-082`, task-093), so a stale
545
+ * `dna set <key> <value>` is told where it goes.
546
+ */
547
+ const DNA_SURPLUS_HINT = 'the value travels in --value';
349
548
  /**
350
549
  * `dna set <path> --value <v>` `CoreOperation.fn` (P2.1) — kept, and still the scalar spelling
351
550
  * (`spec-002`, `spec-005` §4). Since task-093 it is **`update` restricted to a single value**: the
@@ -364,8 +563,8 @@ function dnaPathPositional(verb, positionals) {
364
563
  * write the field.
365
564
  */
366
565
  const dnaSetFn = async (params) => {
367
- const { root, positionals, options } = params;
368
- const keyPath = dnaPathPositional('set', positionals);
566
+ const { root, positionals, options, force } = params;
567
+ const keyPath = dnaPathPositional(positionals);
369
568
  const value = options?.value;
370
569
  if (value === undefined) {
371
570
  throw new usage_error_1.UsageError('missing required argument: --value');
@@ -374,11 +573,11 @@ const dnaSetFn = async (params) => {
374
573
  // reader (and `git log --grep`) sees the command that was run, not the verb it delegates to. The
375
574
  // scalar-only restriction, `dl-080`(B)'s dirty-target guard (task-092) and the committed-tree
376
575
  // post-condition all live in the shared pipeline now, on the one document it loads.
377
- return runDnaMutation(root, { verb: 'update', field: keyPath, value }, `wf(dna): set ${keyPath}`, true);
576
+ return runDnaMutation(root, { verb: 'update', field: keyPath, value }, `wf(dna): set ${keyPath}`, true, force === true);
378
577
  };
379
578
  /** Split a verb's invocation into the positional `<path>`, `--value` and the per-entry `--entry-<field>` values. */
380
579
  function dnaMutationRequest(verb, positionals, options) {
381
- const field = dnaPathPositional(verb, positionals);
580
+ const field = dnaPathPositional(positionals);
382
581
  if (verb === 'add' && options?.value === undefined)
383
582
  throw new usage_error_1.UsageError('missing required argument: --value');
384
583
  // Every remaining option is an entry field, carried under its `--entry-<field>` name (see
@@ -404,18 +603,21 @@ function dnaMutationRequest(verb, positionals, options) {
404
603
  }
405
604
  /** `dna add` — create an entry, or append to a list (`dl-081` option (E); spec-006 §3, Tool `dna.add`). */
406
605
  const dnaAddFn = async (params) => {
407
- const { root, positionals, options } = params;
408
- return runDnaMutation(root, dnaMutationRequest('add', positionals, options));
606
+ const { root, positionals, options, force } = params;
607
+ const request = dnaMutationRequest('add', positionals, options);
608
+ return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
409
609
  };
410
610
  /** `dna remove` — drop an entry, a value from a list, or an optional field (Tool `dna.remove`). */
411
611
  const dnaRemoveFn = async (params) => {
412
- const { root, positionals, options } = params;
413
- return runDnaMutation(root, dnaMutationRequest('remove', positionals, options));
612
+ const { root, positionals, options, force } = params;
613
+ const request = dnaMutationRequest('remove', positionals, options);
614
+ return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
414
615
  };
415
616
  /** `dna update` — change a value, a list, or the fields of one entry (Tool `dna.update`). */
416
617
  const dnaUpdateFn = async (params) => {
417
- const { root, positionals, options } = params;
418
- return runDnaMutation(root, dnaMutationRequest('update', positionals, options));
618
+ const { root, positionals, options, force } = params;
619
+ const request = dnaMutationRequest('update', positionals, options);
620
+ return runDnaMutation(root, request, dnaCommitSubject(request), false, force === true);
419
621
  };
420
622
  /**
421
623
  * The `--entry-<field>` namespace, re-exported from the DNA pillar (`src/dna/path.ts`), which owns it
@@ -449,6 +651,14 @@ const DNA_SET_VALUE_OPTION = {
449
651
  required: true,
450
652
  description: 'the new value for <path> (scalar fields only — use dna update for collections and lists)',
451
653
  };
654
+ /**
655
+ * `--force` on the four DNA write verbs (task-193, ruling R20/Q9): the opt-in to the whole-file rewrite
656
+ * the in-place editor would otherwise refuse. A per-command flag, listed in `spec-008` §12.
657
+ */
658
+ const DNA_FORCE_FLAG = {
659
+ name: 'force',
660
+ description: 'rewrite the whole dna.yaml when it cannot be edited in place (comments and formatting are not kept)',
661
+ };
452
662
  const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
453
663
  name: (0, path_2.dnaEntryOptionName)(field),
454
664
  description: `'${field}' of the entry <path> names, where that collection declares it`,
@@ -478,18 +688,26 @@ function singleOption(options, name) {
478
688
  * none, so no call path can reach the decision with a working-tree registry. An unknown type is a
479
689
  * domain `NOT_FOUND` (exit 1) with the exact P1.3 message
480
690
  * `unknown memory type '<t>' (not defined in memory.yaml)`, returned BEFORE any write.
691
+ * With `--workflow <ref> [--step <key>]` (task-227, `spec-017` §7.10), the step the element is linked to
692
+ * is then resolved from the deduction at `HEAD` ({@link resolveAddLink}); a refusal (`NOT_FOUND`,
693
+ * `CONFLICT`, `VALIDATION`) is returned before the id is allocated, and the add commit carries the
694
+ * `WingFoil-Instance` / `WingFoil-Step` trailers. `--step` without `--workflow` is a usage error (exit 2).
481
695
  * 4. **Generate the id** deterministically from the type's committed `id_pattern` (task-002's
482
696
  * `generateId`, REQ-SYS-07), in `spec-001`'s fixed order (task-110, `dl-107` S2): first every
483
697
  * frontmatter/context token from `--set` (a `--set` name the committed `id_pattern` and `path` do
484
698
  * not contain is refused, exit 1; a token with no value names the option that supplies it), then
485
- * the `{slug}` from the title, and — only for a `{n}`-token pattern — a sequence counter over the
486
- * type's directory, matched against the already-materialized pattern (`src/memory/add.ts`; no wall-clock/random). That
487
- * counter still reads the WORKING TREE: it is `bug-087` (`release: v0.3`), a different read in
488
- * this verb and deliberately not task-095's. What changed is only that the directory it counts in
489
- * is now derived from the committed `path` pattern.
490
- * 5. **Fill the committed scaffold's bytes** with only the `id`/`status: draft`/`--title`/`--tags`
699
+ * the `{slug}` from the title, and — only for a `{n}`-token pattern — a sequence counter matched
700
+ * against the already-materialized pattern (`src/memory/add.ts`; no wall-clock/random): the
701
+ * highest number any local branch, remote-tracking ref, `HEAD` or the working tree holds, across
702
+ * every folder the committed `path` pattern can resolve to, plus one (task-128, `dl-101` §2 (a),
703
+ * `bug-087`, `bug-162`). That read is wider than `HEAD` on purpose — it can only raise the number —
704
+ * and is the declared baseline `command-baseline` records for it.
705
+ * 5. **Fill the committed scaffold's bytes** with only the `id`/`status`/`--title`/`--tags`
491
706
  * skeleton (P1.3; spec-010-memory-frontmatter-schema) plus the `--set` fields (a context token only
492
- * where the scaffold declares it), then **write + commit** through task-022's
707
+ * where the scaffold declares it). `status` is the head of the type's committed machine
708
+ * (`spec-001`; `bug-214`, task-180), and a WIP limit declared on that state is checked
709
+ * (`requireWipSlot`, `CONFLICT`, exit 1), its holders counted at `HEAD` and on every local and
710
+ * remote-tracking branch (`dl-156` Q1 (a), task-274). Then **write + commit** through task-022's
493
711
  * confined `writeMemoryEntry` (REQ-SEC-06 refuse-before-write + one scoped commit
494
712
  * `wf(<type>): add <id>`); the returned sha rides `CoreResult.commit`.
495
713
  *
@@ -499,7 +717,7 @@ function singleOption(options, name) {
499
717
  * only exit-2 path and are thrown before this try.
500
718
  */
501
719
  const memoryAddFn = async (params) => {
502
- const { root, options } = params;
720
+ const { root, options, fields: extraFields } = params;
503
721
  const type = singleOption(options, 'type');
504
722
  if (type === undefined)
505
723
  throw new usage_error_1.UsageError('missing required argument: --type');
@@ -510,6 +728,11 @@ const memoryAddFn = async (params) => {
510
728
  const set = (0, memory_1.parseSetOptions)(options?.set);
511
729
  if (!set.ok)
512
730
  throw new usage_error_1.UsageError(set.message);
731
+ // task-227 (`spec-017` §7.10): `--step` picks a step of the instance `--workflow` names.
732
+ const workflowRef = singleOption(options, 'workflow');
733
+ const stepKey = singleOption(options, 'step');
734
+ if (stepKey !== undefined && workflowRef === undefined)
735
+ throw new usage_error_1.UsageError('missing required argument: --workflow');
513
736
  const identity = (0, git_identity_1.requireGitIdentity)(root);
514
737
  if (!identity.ok)
515
738
  return identity;
@@ -519,7 +742,7 @@ const memoryAddFn = async (params) => {
519
742
  const resolved = (0, memory_add_type_1.resolveAddType)(root, type);
520
743
  if (!resolved.ok)
521
744
  return resolved;
522
- const { pathPattern, idPattern, scaffold } = resolved.value;
745
+ const { pathPattern, idPattern, scaffold, initialState, memoryYaml: committedMemoryYaml } = resolved.value;
523
746
  const unknown = (0, memory_1.unknownSetNames)(set.values, idPattern, pathPattern);
524
747
  if (unknown.length > 0) {
525
748
  return (0, types_1.coreErr)({
@@ -530,20 +753,41 @@ const memoryAddFn = async (params) => {
530
753
  });
531
754
  }
532
755
  try {
533
- // spec-001's order: field/context tokens → {slug} → {n}, so the counter sees the materialized prefix.
534
- const materialized = (0, memory_1.expandFieldTokens)(idPattern, set.values);
756
+ // task-227: the step the element is linked to, decided at HEAD before the id is allocated, so every
757
+ // refusal precedes the write (and a dry run refuses the same way). A `spec-003` error at HEAD maps as
758
+ // `workflow next` maps it; a git failure is a `StorageError`, mapped below.
759
+ let link;
760
+ if (workflowRef !== undefined) {
761
+ const resolvedLink = loadOrError(() => (0, memory_add_link_1.resolveAddLink)(root, workflowRef, stepKey, type));
762
+ const linked = resolvedLink.ok ? resolvedLink.value : resolvedLink;
763
+ if (!linked.ok)
764
+ return linked;
765
+ link = linked.value;
766
+ }
767
+ const trailers = link === undefined ? [] : (0, memory_add_link_1.addLinkTrailers)(link);
768
+ // spec-001's order: {date} → {author} → field/context tokens → {slug} → {n}, so the counter sees
769
+ // the materialized prefix. {date} is read ONCE, from the author date git records for this add's
770
+ // commit, and the commit is pinned to it below, as it is to the author {author} slugs (task-163).
771
+ const date = (0, validation_1.patternTokens)(idPattern).includes('date') ? (0, memory_1.readAuthorDate)(root, identity.value) : undefined;
772
+ const materialized = (0, memory_1.expandFieldTokens)(idPattern, set.values, { date, authorName: identity.value.name });
535
773
  const sequence = (0, memory_1.hasNumericToken)(materialized)
536
- ? (0, memory_1.nextSequenceNumber)((0, memory_1.resolveTypeDirectory)(root, pathPattern, set.values), materialized)
774
+ ? (0, memory_1.nextSequenceNumber)(root, pathPattern, materialized)
537
775
  : 0;
538
776
  const id = (0, validation_1.generateId)(materialized, { slug: (0, memory_1.slugifyTitle)(title), n: sequence });
539
- const content = (0, memory_1.renderAddDocument)(scaffold, { id, title, tags, fields: (0, memory_1.writtenFields)(scaffold, set.values) });
777
+ const extra = Object.keys(extraFields ?? {})
778
+ .sort()
779
+ .map((name) => [name, extraFields[name]]);
780
+ const content = (0, memory_1.renderAddDocument)(scaffold, { id, title, tags, fields: [...(0, memory_1.writtenFields)(scaffold, set.values), ...extra], status: initialState });
540
781
  const pathValues = { ...set.values, id };
541
- const message = `wf(${type}): add ${id}`;
782
+ const subject = `wf(${type}): add ${id}`;
783
+ // What the operation reports is the message it records, the build signature aside (`commitPaths`).
784
+ const message = trailers.length === 0 ? subject : `${subject}\n\n${trailers.join('\n')}`;
542
785
  // dl-080 (B) / bug-078, AC3: this verb CREATES, so the rule is absence rather than cleanliness.
543
- // `nextSequenceNumber` counts the WORKING TREE, so a tree that disagrees with `HEAD` about how
544
- // many elements exist can generate an id landing on an occupied path — and the write below is
545
- // unconditional, which turns `wf(<type>): add <id>` into a commit that overwrites (or deletes
546
- // lines from) an existing element. The path is resolved here rather than taken from
786
+ // Since task-128 a `{n}` id is above every number any baseline holds, but a slug-only id
787
+ // (`note-{slug}`) still lands on an occupied path whenever the title repeats, and a `{n}` id can
788
+ // land on a git-ignored file (no baseline the counter reads holds it). The write below
789
+ // is unconditional, which would turn `wf(<type>): add <id>` into a commit that overwrites (or
790
+ // deletes lines from) an existing element. The path is resolved here rather than taken from
547
791
  // `writeMemoryEntry`'s return value because the guard must run BEFORE the write; the resolution
548
792
  // is pure, so doing it twice is free of side effects and keeps that throwing storage primitive's
549
793
  // contract untouched. A confinement escape still throws `StorageError` from this same call.
@@ -551,19 +795,40 @@ const memoryAddFn = async (params) => {
551
795
  const absent = (0, write_guard_1.requireAbsentTarget)(root, targetPath);
552
796
  if (!absent.ok)
553
797
  return absent;
554
- const { path, sha } = (0, memory_1.writeMemoryEntry)(root, pathPattern, pathValues, content, message);
798
+ // `add` is the verb that enters the initial state, so a WIP limit declared there holds here too
799
+ // (task-180, `dl-110` P3 (a)), counted at `HEAD` — the commit the type was resolved at — and on
800
+ // every local and remote-tracking branch (`dl-156` Q1 (a), task-274).
801
+ const slot = (0, memory_transition_1.requireWipSlot)(root, 'HEAD', committedMemoryYaml, type, initialState);
802
+ if (!slot.ok)
803
+ return slot;
804
+ const warnings = slot.value.map(validation_1.formatDiagnostic);
805
+ const { path, sha } = (0, memory_1.writeMemoryEntry)(root, pathPattern, pathValues, content, subject, {
806
+ author: identity.value,
807
+ warnings,
808
+ trailers,
809
+ // `@`: git reads a bare `<seconds> <offset>` as a timestamp only from 9 digits of seconds up
810
+ // (`0 +0000` is "invalid date format"); `@<seconds> <offset>` is a timestamp at any width.
811
+ ...(date === undefined ? {} : { env: { GIT_AUTHOR_DATE: `@${date}` } }),
812
+ });
555
813
  const leaked = (0, write_guard_1.committedScopeError)(root, sha, targetPath, content);
556
814
  if (leaked)
557
815
  return leaked;
558
- return (0, types_1.coreOk)({ id, path: (0, path_1.relative)(root, path) }, { sha, message });
816
+ return (0, types_1.coreOk)({ id, path: (0, path_1.relative)(root, path) }, { sha, message }, warnings);
559
817
  }
560
818
  catch (error) {
561
819
  if (error instanceof storage_1.StorageError) {
562
- return (0, types_1.coreErr)({ code: 'IO', message: error.message });
820
+ // One rule, one code (task-130, `bug-123`, `spec-005` §3): a confinement refusal is `VALIDATION`
821
+ // here as it is from the transition verbs' guard — the request named a target REQ-SEC-06
822
+ // forbids, or one outside its type's folder (task-281, `bug-325`, approver ruling D1: exit 1).
823
+ // Every other storage failure (a git read, an unresolved path token) stays `IO`.
824
+ const confinement = error.code === errors_1.E_PATH_ESCAPES_ROOT || error.code === errors_1.E_PATH_OUTSIDE_TYPE_FOLDER || error.code === errors_1.E_TARGET_IS_SYMLINK;
825
+ return (0, types_1.coreErr)({ code: confinement ? 'VALIDATION' : 'IO', message: error.message });
563
826
  }
564
827
  if (error instanceof validation_1.ValidationError) {
565
- const reason = error.issues.length > 0 ? error.issues.map((issue) => issue.message).join('; ') : error.message;
566
- return (0, types_1.coreErr)({ code: 'VALIDATION', message: reason });
828
+ // Every `ValidationError` src/ builds carries at least one issue (task-189: each construction
829
+ // site passes a literal issue, is guarded by `issues.length > 0`, or maps a failed Zod parse),
830
+ // so the issues ARE the reason; there is no issue-less fallback to choose.
831
+ return (0, types_1.coreErr)({ code: 'VALIDATION', message: error.issues.map((issue) => issue.message).join('; ') });
567
832
  }
568
833
  throw error;
569
834
  }
@@ -613,22 +878,26 @@ const memorySearchFn = async (params) => {
613
878
  if (positional !== undefined)
614
879
  (0, memory_1.validateSearchQuery)(positional);
615
880
  const query = positional ?? '';
616
- const loaded = loadOrError(() => (0, loaders_1.loadMemoryYaml)(root));
881
+ const loaded = loadConfigOrError(root, () => (0, loaders_1.loadMemoryYaml)(root));
617
882
  if (!loaded.ok)
618
883
  return loaded;
619
884
  const tag = options?.tag;
620
885
  const type = options?.type;
621
886
  const status = options?.status;
887
+ const diagnostics = [];
622
888
  const scanned = (0, memory_1.searchMemoryDocuments)(root, loaded.value, query, {
623
889
  ...(tag !== undefined ? { tag } : {}),
624
890
  ...((0, memory_1.isArchivedStatus)(status) ? { includeArchived: true } : {}),
891
+ onDiagnostic: (diagnostic) => diagnostics.push(diagnostic),
625
892
  });
626
893
  const matches = scanned
627
894
  .filter((match) => (type === undefined || match.type === type) && (status === undefined || match.status === status))
628
895
  .map(({ path, id, title, type: docType, status: docStatus, tags }) => ({ path, id, title, type: docType, status: docStatus, tags }));
896
+ // task-171 (`bug-031`): a file the scan could not read is a warning, never the search's failure.
897
+ const warnings = diagnostics.map(validation_1.formatDiagnostic);
629
898
  return matches.length === 0
630
- ? (0, types_1.coreOk)({ query, matches, message: NO_MEMORY_SEARCH_MATCHES_MESSAGE })
631
- : (0, types_1.coreOk)({ query, matches });
899
+ ? (0, types_1.coreOk)({ query, matches, message: NO_MEMORY_SEARCH_MATCHES_MESSAGE }, undefined, warnings)
900
+ : (0, types_1.coreOk)({ query, matches }, undefined, warnings);
632
901
  };
633
902
  /**
634
903
  * `memory history` `CoreOperation.fn` (P1.10, `mutates: false`; spec-006-core-domain-api §3 memory
@@ -645,7 +914,9 @@ const memorySearchFn = async (params) => {
645
914
  * 3. **Resolve the id to a document** with task-009's {@link findMemoryDocumentById} — an EXACT
646
915
  * frontmatter-`id` match over the deterministically-sorted document set, never a substring (that
647
916
  * is `memory search`'s job). No match is a domain `NOT_FOUND` (exit 1) with the exact P1.10 message
648
- * `document not found: <id>`, returned rather than thrown. Archived documents are deliberately NOT
917
+ * `document not found: <id>`, returned rather than thrown. A document written in a `format` newer
918
+ * than this build reads is refused, `VALIDATION` (exit 1) with the `dl-149` `E_INVALID_FORMAT` text
919
+ * (task-257, `bug-241`); one the lookup passes on its way is a warning. Archived documents are deliberately NOT
649
920
  * excluded: REQ-STATE-06 scopes its exclusion to default *search* results, and explicitly
650
921
  * guarantees an archived element remains "present on disk and in git history" — which is precisely
651
922
  * what this command reads.
@@ -657,20 +928,49 @@ const memorySearchFn = async (params) => {
657
928
  *
658
929
  * A document that exists on disk but has never been committed yields `entries: []` — correctly, not
659
930
  * as an error: per ADR-007 git history IS the audit trail, so an uncommitted document has none yet.
931
+ * A git read that fails — a tree git cannot read, an answer past the read buffer — is a `CoreError`
932
+ * `IO` (exit `1`) carrying git's message, never an empty trail (task-142, `bug-072`).
660
933
  */
661
934
  const memoryHistoryFn = async (params) => {
662
935
  const { root, positional: id } = params;
663
936
  if (id === undefined || id.trim().length === 0) {
664
- throw new usage_error_1.UsageError('missing required argument: memory history <id>');
937
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
665
938
  }
666
- const loaded = loadOrError(() => (0, loaders_1.loadMemoryYaml)(root));
939
+ const loaded = loadConfigOrError(root, () => (0, loaders_1.loadMemoryYaml)(root));
667
940
  if (!loaded.ok)
668
941
  return loaded;
669
- const found = (0, memory_1.findMemoryDocumentById)(root, loaded.value, id);
942
+ const diagnostics = [];
943
+ let found;
944
+ try {
945
+ found = (0, memory_1.findMemoryDocumentById)(root, loaded.value, id, { onDiagnostic: (diagnostic) => diagnostics.push(diagnostic) });
946
+ }
947
+ catch (error) {
948
+ // The `dl-149` refusal of the element the lookup names (task-257, `bug-241`): a document written in
949
+ // a newer format is not read by today's rules. Any other failure (an unreadable directory) is not
950
+ // that refusal and propagates as it did (review F2).
951
+ if (!(0, validation_1.isNewerFormatError)(error))
952
+ throw error;
953
+ return (0, types_1.coreErr)({
954
+ code: 'VALIDATION',
955
+ message: `cannot read the history of ${id}: its document is written in a newer format: ${error.message}`,
956
+ details: { issues: error.issues },
957
+ });
958
+ }
670
959
  if (!found) {
671
960
  return (0, types_1.coreErr)({ code: 'NOT_FOUND', message: `document not found: ${id}` });
672
961
  }
673
- const entries = (0, memory_1.reconstructMemoryTransitions)(root, found.path).map((transition) => ({
962
+ let transitions;
963
+ try {
964
+ transitions = (0, memory_1.reconstructMemoryTransitions)(root, found.path);
965
+ }
966
+ catch (error) {
967
+ // A git read that failed is `IO`, never an empty trail (task-142, `bug-072`): the walk throws
968
+ // rather than answering "no history" for a history it could not read. Since task-171 the tolerant
969
+ // reconstruction throws nothing else (a revision that does not parse is an entry, `bug-188`), so
970
+ // what reaches here is a `StorageError`.
971
+ return (0, types_1.coreErr)({ code: 'IO', message: error.message });
972
+ }
973
+ const entries = transitions.map((transition) => ({
674
974
  sha: transition.sha,
675
975
  author: `${transition.authorName} <${transition.authorEmail}>`,
676
976
  timestamp: transition.date,
@@ -682,8 +982,17 @@ const memoryHistoryFn = async (params) => {
682
982
  : null,
683
983
  reason: transition.reason,
684
984
  subject: transition.subject,
985
+ wingfoil: transition.wingfoil,
986
+ ...(transition.unreadable !== undefined ? { unreadable: transition.unreadable } : {}),
685
987
  }));
686
- return (0, types_1.coreOk)({ id, path: found.path, entries });
988
+ // task-171: the documents the lookup could not read (`bug-031`), then each revision of this one whose
989
+ // frontmatter does not parse (`bug-188`), named with its commit.
990
+ for (const transition of transitions) {
991
+ if (transition.unreadable !== undefined) {
992
+ diagnostics.push((0, memory_1.memoryUnreadableDiagnostic)(found.path, `${(0, memory_1.firstLineOf)(transition.unreadable)} (at ${transition.sha})`));
993
+ }
994
+ }
995
+ return (0, types_1.coreOk)({ id, path: found.path, entries }, undefined, diagnostics.map(validation_1.formatDiagnostic));
687
996
  };
688
997
  /**
689
998
  * `memory submit` `CoreOperation.fn` (P1.6, `mutates: true`; spec-006-core-domain-api §3). Submit is
@@ -692,16 +1001,22 @@ const memoryHistoryFn = async (params) => {
692
1001
  *
693
1002
  * 1. **`<id>`** absent or blank → `UsageError` (exit 2), as `memory history` — before the identity
694
1003
  * check, so the exit code does not depend on the machine (task-125, `bug-172`).
695
- * 2. **`requireGitIdentity`** (REQ-SEC-01) — exit 1.
696
- * 3. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` committed at `HEAD` itself
697
- * (task-091, `dl-080` (B)); no committed machine, an unreadable one, not found, unknown type,
1004
+ * 2. **`requireGitIdentity`** (REQ-SEC-01) — exit 1. Steps 2 and 3 are {@link beginMemoryTransition},
1005
+ * the preamble every transition verb shares (task-132, `bug-142`; `spec-006` §7).
1006
+ * 3. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` and the document committed at
1007
+ * `HEAD` itself (task-091, `dl-080` (B); task-247, `bug-187`); no committed machine, an unreadable one, not found, unknown type,
698
1008
  * invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
699
1009
  * carries the pinned `illegal transition <from> -> <to> for type '<type>'` (`dl-032`, P1.6 sc.2).
700
1010
  * 5. **Required fields** (spec-010 validation rules) — `title` and every `template.frontmatter.required`
701
1011
  * field must be non-empty, else `VALIDATION` `missing required field on submit: <fields>` (exit 1).
1012
+ * A field the type lists in `template.frontmatter.not_applicable_allowed` may hold
1013
+ * `"n/a — <reason>"`; a not-applicable value anywhere else, or one with no reason, is `VALIDATION`
1014
+ * `not-applicable value on submit: …` naming the field (`dl-124`, task-168, {@link requireRequiredFields}).
702
1015
  * 6. **Edit + commit** — `status` set to the target and `rejection_reason` removed (spec-010 field-write
703
1016
  * ownership; every other byte kept), then one commit scoped to that file, subject
704
- * `wf(<type>): submit <id>` with no bracket and no body (spec-004 §4.3). The rendered document is
1017
+ * `wf(<type>): submit <id>` with no bracket (spec-004 §4.3, `dl-054`) and, when the author's edits
1018
+ * ride along, one body line `Carries content: …` naming them (`describeSubmitContent`; task-209,
1019
+ * `dl-106` W1 (a), `spec-008` §2); a pure transition has no body. The rendered document is
705
1020
  * re-parsed first (`commitMemoryTransition`'s post-condition: `status` is the target, no
706
1021
  * `rejection_reason`, nothing else changed); a failure is `VALIDATION` (exit 1) with nothing written. No `--reason`: spec-008 §2
707
1022
  * requires it only on the approval gates (`dl-027`).
@@ -709,29 +1024,39 @@ const memoryHistoryFn = async (params) => {
709
1024
  const memorySubmitFn = async (params) => {
710
1025
  const { root, positional: id } = params;
711
1026
  if (id === undefined || id.trim().length === 0) {
712
- throw new usage_error_1.UsageError('missing required argument: memory submit <id>');
1027
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
713
1028
  }
714
- const identity = (0, git_identity_1.requireGitIdentity)(root);
715
- if (!identity.ok)
716
- return identity;
717
- const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'submit');
1029
+ const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'submit');
718
1030
  if (!prepared.ok)
719
1031
  return prepared;
720
1032
  const { type, path, frontmatter, content, from, to } = prepared.value;
721
- const required = prepared.value.memoryYaml.types[type]?.template?.frontmatter.required ?? [];
722
- const missing = (0, memory_1.missingRequiredFields)(frontmatter, required);
723
- if (missing.length > 0) {
724
- return (0, types_1.coreErr)({ code: 'VALIDATION', message: `missing required field on submit: ${missing.join(', ')}` });
725
- }
726
- const message = (0, memory_1.formatMemoryCommitMessage)({ type, op: 'submit', ids: [id] });
1033
+ const fieldsFilled = (0, required_fields_1.requireRequiredFields)(prepared.value.memoryYaml, type, frontmatter, 'submit');
1034
+ if (!fieldsFilled.ok)
1035
+ return fieldsFilled;
1036
+ // The subject stays plain (`dl-054`, ruling R20); the body declares the content the commit carries
1037
+ // beyond the state move, measured against the document committed at the sha the transition was
1038
+ // decided at (task-209, `dl-106` W1 (a)). A pure transition gets no body line.
727
1039
  const rendered = (0, memory_1.renderSubmitDocument)(content, to);
1040
+ const committedContent = (0, storage_1.readPathAtRev)(root, prepared.value.sha, path);
1041
+ const described = (0, memory_1.describeSubmitContent)(committedContent, content, to);
1042
+ // `describeSubmitContent` compares line-ending-normalized text; whether the line endings change the
1043
+ // blob is git's answer, after its own filters (`core.autocrlf`, `.gitattributes`; review F1). It is
1044
+ // asked whatever else changed (re-review 1): against the rendered committed copy when nothing else
1045
+ // did, otherwise against this rendering written in the committed document's line endings.
1046
+ let lineEndingsChanged = false;
1047
+ if (committedContent !== null) {
1048
+ const baseline = described.length === 0 ? (0, memory_1.renderSubmitDocument)(committedContent, to) : (0, memory_1.withLineEndingsOf)(rendered, committedContent);
1049
+ lineEndingsChanged = !(0, storage_1.storesAsBlob)(root, path, rendered, baseline);
1050
+ }
1051
+ const carries = lineEndingsChanged ? [...described, memory_1.LINE_ENDINGS_ITEM] : described;
1052
+ const message = (0, memory_1.formatMemoryCommitMessage)({ type, op: 'submit', ids: [id], carries });
728
1053
  // `carries-content`: submit is the ONE verb entitled to bring the author's body and required fields
729
1054
  // into its commit (spec-010's field-write ownership row), so it is not guarded against a modified
730
1055
  // working tree and its commit need not differ from HEAD~1 by `status` alone (task-088, bug-076 AC4).
731
1056
  const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, { [memory_1.REJECTION_REASON_FIELD]: undefined }, 'carries-content');
732
1057
  if (!committed.ok)
733
1058
  return committed;
734
- return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message });
1059
+ return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message }, prepared.value.warnings);
735
1060
  };
736
1061
  /**
737
1062
  * `memory approve` `CoreOperation.fn` (P1.7, `mutates: true`; spec-006-core-domain-api §3). Approve is
@@ -744,18 +1069,20 @@ const memorySubmitFn = async (params) => {
744
1069
  * 2. **`requireReason`** (REQ-SEC-04, task-041) → `UsageError` `missing required argument: --reason`
745
1070
  * (exit 2, P1.7 sc.2). Placed before any file is read, so an omitted reason touches nothing. Both
746
1071
  * usage checks precede the identity check (task-125, `bug-172`).
747
- * 3. **`requireGitIdentity`** (REQ-SEC-01) — exit 1. It is also the identity the `Approver:` line and
748
- * the commit's own author record, so the two can never name different principals.
749
- * 4. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` committed at `HEAD` itself
750
- * (task-091, `dl-080` (B)); no committed machine, an unreadable one, not found, unknown type,
1072
+ * 3. **`requireGitIdentity`** (REQ-SEC-01) — exit 1. Steps 3 and 4 are {@link beginMemoryTransition}
1073
+ * (task-132, `bug-142`; `spec-006` §7), which reads the identity ONCE: step 6 authorizes it, the
1074
+ * `Approver:` line names it, and the commit is authored as it (`--author`), so the three can never
1075
+ * name different principals (`bug-149`).
1076
+ * 4. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` and the document committed at
1077
+ * `HEAD` itself (task-091, `dl-080` (B); task-247, `bug-187`); no committed machine, an unreadable one, not found, unknown type,
751
1078
  * invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
752
1079
  * carries the pinned `illegal transition <from> -> <to> for type '<type>'` (`dl-032`), whose `<to>`
753
- * is `approve`'s own next legal edge (`dl-053`).
1080
+ * is `(none)`: `approve` reaches nothing from `<from>` (task-181, `bug-165`).
754
1081
  * 6. **`requireApprovalAuthority`** (REQ-SEC-03, task-040) — exit 1 with
755
1082
  * `user not authorized to approve type '<type>'` (P1.7 sc.3). It runs after step 5 because its
756
1083
  * message interpolates the document's type, which is only knowable once the document is located,
757
- * and `findMemoryDocumentById` is a full scan of the registered content roots — resolving the type
758
- * twice would double the cost of every approve. Nothing is weakened by the order: the authority
1084
+ * and `findMemoryDocumentByIdAtRev` at `HEAD` is a full scan of the registered content roots —
1085
+ * resolving the type twice would double the cost of every approve. Nothing is weakened by the order: the authority
759
1086
  * *decision* depends on nothing step 5 computes (adr-006 fixes one uniform `approver` role, keyed
760
1087
  * on the git identity), and step 5 writes nothing. It reads the roles from the **committed**
761
1088
  * `.wingfoil/dna.yaml` and resolves that file itself (task-090, `bug-079`) — which is why no
@@ -764,29 +1091,40 @@ const memorySubmitFn = async (params) => {
764
1091
  * 7. **Edit + commit** — `status` set to the target and **nothing else** (spec-010 field-write
765
1092
  * ownership: approve changes only `status`; approver and reason live in the commit message). One
766
1093
  * commit scoped to that file, subject `wf(<type>): approve <id> [<from> → <to>]` with the mandatory
767
- * `Approver:` / `Reason:` body (CLAUDE.md §5.1; `dl-054` confines the bracket to the
1094
+ * `Approver:` / `Reason:` body (AGENTS.md §5.1; `dl-054` confines the bracket to the
768
1095
  * approver-gated verbs, which is why `memory submit` has none). `commitMemoryTransition` re-parses
769
1096
  * the rendered frontmatter first and refuses (exit 1, nothing written) unless `status` is the
770
1097
  * target and no other field's value moved — which is exactly spec-010's "only `status`" rule, so
771
1098
  * no extra `expected` entry is needed here.
1099
+ * 8. **The `supersedes:` trigger** (task-162, `dl-065` Q1.1; {@link prepareSupersede}). When the target
1100
+ * is the `waiting` state whose forward edge leads to `superseded` (`adr` `accepted`, `tech-spec`
1101
+ * `approved`) and the document's committed `supersedes:` names an element, that element is
1102
+ * resolved at the same `HEAD` sha, and refused (exit `1`) when it is missing, of another type, not
1103
+ * in that state, or not writable as a status change. This runs after step 6 and before step 7's
1104
+ * write, and both documents pass `checkMemoryTransition` before either is written, so a refusal
1105
+ * leaves the repository as it was. The approve commit comes first; then a second commit, scoped
1106
+ * to the superseded document, `wf(<type>): finalize <A> [<state> → superseded]`, with a `Reason:`
1107
+ * citing the approve sha and no `Approver:` line (`spec-008` §2). The result reports both; its
1108
+ * `commit` is the approve's.
772
1109
  */
773
1110
  const memoryApproveFn = async (params) => {
774
1111
  const { root, positional: id, options } = params;
775
1112
  if (id === undefined || id.trim().length === 0) {
776
- throw new usage_error_1.UsageError('missing required argument: memory approve <id>');
1113
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
777
1114
  }
778
1115
  const reason = (0, require_reason_1.requireReason)(options);
779
- const identity = (0, git_identity_1.requireGitIdentity)(root);
780
- if (!identity.ok)
781
- return identity;
782
- const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'approve');
1116
+ const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'approve');
783
1117
  if (!prepared.ok)
784
1118
  return prepared;
785
1119
  const { type, path, content, from, to } = prepared.value;
786
- const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type);
1120
+ const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
787
1121
  if (!authorized.ok)
788
1122
  return authorized;
789
- const { name, email } = (0, git_identity_1.readGitIdentity)(root);
1123
+ const supersede = (0, memory_supersede_1.prepareSupersede)(root, prepared.value);
1124
+ if (!supersede.ok)
1125
+ return supersede;
1126
+ const superseded = supersede.value;
1127
+ const { name, email } = prepared.value.identity;
790
1128
  const message = (0, memory_1.formatMemoryCommitMessage)({
791
1129
  type,
792
1130
  op: 'approve',
@@ -795,25 +1133,89 @@ const memoryApproveFn = async (params) => {
795
1133
  approver: { name, email, role: approval_authority_1.APPROVER_ROLE },
796
1134
  reason,
797
1135
  });
798
- const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, (0, memory_1.setFrontmatterField)(content, 'status', to), message);
1136
+ const rendered = (0, memory_1.setFrontmatterField)(content, 'status', to);
1137
+ let renderedSuperseded = '';
1138
+ if (superseded !== null) {
1139
+ // Both documents are checked before either is written, so a refusal of the second leaves the
1140
+ // first unwritten too (task-162). The approved one is checked again by its own commit below.
1141
+ renderedSuperseded = (0, memory_1.setFrontmatterField)(superseded.content, 'status', superseded.to);
1142
+ const checkedApproved = (0, memory_transition_1.checkMemoryTransition)(root, prepared.value, rendered);
1143
+ if (!checkedApproved.ok)
1144
+ return checkedApproved;
1145
+ const checkedSuperseded = (0, memory_transition_1.checkMemoryTransition)(root, superseded, renderedSuperseded);
1146
+ if (!checkedSuperseded.ok) {
1147
+ return (0, types_1.coreErr)({
1148
+ ...checkedSuperseded.error,
1149
+ message: `cannot approve ${id}: its supersedes: field names ${superseded.id}, which cannot be superseded: ${checkedSuperseded.error.message}`,
1150
+ });
1151
+ }
1152
+ }
1153
+ // Both lookups read the same HEAD sha in path order; a file unreadable to both is named once. Known
1154
+ // before the first commit, so a dry run, which stops there, reports them too (task-210 review, F4).
1155
+ const warnings = superseded === null ? prepared.value.warnings : [...new Set([...prepared.value.warnings, ...superseded.warnings])];
1156
+ const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, {}, 'declared-fields-only', warnings);
799
1157
  if (!committed.ok)
800
1158
  return committed;
801
- return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message });
1159
+ if (superseded === null)
1160
+ return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message }, warnings);
1161
+ const finalizeMessage = (0, memory_1.formatMemoryCommitMessage)({
1162
+ type,
1163
+ op: 'finalize',
1164
+ ids: [superseded.id],
1165
+ transition: { from: superseded.from, to: superseded.to },
1166
+ reason: (0, memory_supersede_1.supersedeReason)(id, committed.value),
1167
+ });
1168
+ let finalized;
1169
+ try {
1170
+ finalized = (0, memory_transition_1.commitMemoryTransition)(root, superseded, renderedSuperseded, finalizeMessage);
1171
+ }
1172
+ catch (error) {
1173
+ // A refused commit is worded from its parts, without the primitive's "the working tree and the index
1174
+ // are as they were": the next statement makes the working tree differ on purpose (task-210 review,
1175
+ // F5). Any other error keeps `String`'s `name: message`.
1176
+ finalized = (0, types_1.coreErr)({ code: 'IO', message: (0, storage_1.commitFailureSummary)(error) });
1177
+ // The commit primitive put the file back when git refused the commit (task-210, `bug-217`); the
1178
+ // recovery below commits the file as it stands, so write the finalized status into it again — the
1179
+ // one write here meant to outlive a failed commit, because the approve has already happened.
1180
+ (0, storage_1.writeDocument)((0, path_1.join)(root, superseded.path), renderedSuperseded);
1181
+ }
1182
+ if (!finalized.ok) {
1183
+ // Reachable only when git itself fails between the two commits (a hook, a full disk): every
1184
+ // refusal was checked above. The approve stays — rewriting a commit behind the user's back is
1185
+ // worse (dl-035) — so say exactly what is left to do.
1186
+ return (0, types_1.coreErr)({
1187
+ ...finalized.error,
1188
+ message: `${id} was approved in ${committed.value}, but the commit moving ${superseded.id} to ${superseded.to} failed: ` +
1189
+ `${finalized.error.message.trim()}. Its status is written in the working tree (${superseded.path}). ` +
1190
+ `Complete the pair by committing that file alone, from the repository root:\n\n` +
1191
+ // `--cleanup=whitespace`, as `commitPaths` passes it (bug-051, task-192 review): under the
1192
+ // operator's `commit.cleanup=strip` a reason line opening with `#` would otherwise be lost.
1193
+ `git commit --only --cleanup=whitespace -F - -- ${superseded.path} <<'EOF'\n${finalizeMessage}\nEOF`,
1194
+ });
1195
+ }
1196
+ return (0, types_1.coreOk)({
1197
+ id,
1198
+ path,
1199
+ from,
1200
+ to,
1201
+ superseded: { id: superseded.id, path: superseded.path, from: superseded.from, to: superseded.to },
1202
+ }, { sha: committed.value, message }, warnings);
802
1203
  };
803
1204
  /**
804
1205
  * `memory reject` `CoreOperation.fn` (P1.8, `mutates: true`; spec-006-core-domain-api §3) — the FIRST
805
1206
  * approver-gated verb, so the first operation that enforces REQ-SEC-03 (role authority) and REQ-SEC-04
806
1207
  * (mandatory reason) and the first to emit a commit with a `[from → to]` subject bracket and an
807
- * `Approver:`/`Reason:` body (`dl-054-submit-commit-subject-bracket`, CLAUDE.md §5.1). Order, every
1208
+ * `Approver:`/`Reason:` body (`dl-054-submit-commit-subject-bracket`, AGENTS.md §5.1). Order, every
808
1209
  * refusal before the single write:
809
1210
  *
810
1211
  * 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`.
811
1212
  * 2. **`requireReason`** (REQ-SEC-04, task-041-mandatory-reason-on-verbs) → `UsageError`
812
1213
  * `missing required argument: --reason` (exit `2`, P1.8 sc.3). Both usage checks precede the
813
1214
  * identity check (task-125, `bug-172`).
814
- * 3. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`.
815
- * 4. **{@link prepareMemoryTransition}** with op `reject`, against the `memory.yaml` committed at
816
- * `HEAD` (task-091, `dl-080` (B)) — no committed machine, an unreadable one, not found,
1215
+ * 3. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`. Steps 3 and 4 are {@link beginMemoryTransition}
1216
+ * (task-132, `bug-142`; `spec-006` §7).
1217
+ * 4. **{@link prepareMemoryTransition}** with op `reject`, against the `memory.yaml` and the document
1218
+ * committed at `HEAD` (task-091, `dl-080` (B); task-247, `bug-187`) — no committed machine, an unreadable one, not found,
817
1219
  * unknown type, invalid state, or an illegal transition (the document is in no `gates`
818
1220
  * state), each a `CoreResult.error` at exit `1` carrying `dl-032`'s pinned contract message. The
819
1221
  * target is the type's `gates.<from>.reject` value, taken verbatim (`spec-001`), so it need not be
@@ -831,27 +1233,24 @@ const memoryApproveFn = async (params) => {
831
1233
  * document and refuses (`VALIDATION`, exit `1`, nothing written) unless `status` AND
832
1234
  * `rejection_reason` hold their expected values and no other field moved.
833
1235
  *
834
- * The `Approver:` identity is the live git identity at `root` — the same one step 5 checked and the
835
- * same one git records as the commit author, so the body line can never disagree with the attribution
836
- * (adr-001/adr-006) — under the `APPROVER_ROLE` the authority was exercised as.
1236
+ * The `Approver:` identity is the one step 3 resolved — the same value step 5 checked and the commit
1237
+ * is authored as (`--author`, task-132, `bug-149`), so the body line can never disagree with the
1238
+ * attribution (adr-001/adr-006) — under the `APPROVER_ROLE` the authority was exercised as.
837
1239
  */
838
1240
  const memoryRejectFn = async (params) => {
839
1241
  const { root, positional: id, options } = params;
840
1242
  if (id === undefined || id.trim().length === 0) {
841
- throw new usage_error_1.UsageError('missing required argument: memory reject <id>');
1243
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
842
1244
  }
843
1245
  const reason = (0, require_reason_1.requireReason)(options);
844
- const identity = (0, git_identity_1.requireGitIdentity)(root);
845
- if (!identity.ok)
846
- return identity;
847
- const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'reject');
1246
+ const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'reject');
848
1247
  if (!prepared.ok)
849
1248
  return prepared;
850
1249
  const { type, path, from, to, content } = prepared.value;
851
- const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type);
1250
+ const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
852
1251
  if (!authorized.ok)
853
1252
  return authorized;
854
- const { name, email } = (0, git_identity_1.readGitIdentity)(root);
1253
+ const { name, email } = prepared.value.identity;
855
1254
  const message = (0, memory_1.formatMemoryCommitMessage)({
856
1255
  type,
857
1256
  op: 'reject',
@@ -864,7 +1263,7 @@ const memoryRejectFn = async (params) => {
864
1263
  const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message, { [memory_1.REJECTION_REASON_FIELD]: reason });
865
1264
  if (!committed.ok)
866
1265
  return committed;
867
- return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message });
1266
+ return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message }, prepared.value.warnings);
868
1267
  };
869
1268
  /**
870
1269
  * `memory deprecate` `CoreFn` (P1.9, `mutates: true`; spec-006-core-domain-api §3) — the retire verb.
@@ -875,17 +1274,19 @@ const memoryRejectFn = async (params) => {
875
1274
  * from a literal here — so this one function retires a `task` sitting in a `waiting` state, an
876
1275
  * `accepted` `adr`, a terminal `decision-log`, and a type that falls back to `defaults.states`, with
877
1276
  * no per-type branch. (`adr`/`tech-spec`'s `superseded` is NOT this verb's target: it is a `waiting`
878
- * edge fired by a later element's `supersedes:` field — see `SUPERSEDED_STATE` in
879
- * `src/memory/state-machine.ts` and this task's Execution Notes, decision D1.)
1277
+ * edge fired by a later element's `supersedes:` field when that element is approved — see
1278
+ * `SUPERSEDED_STATE` in `src/memory/state-machine.ts`, `./memory-supersede.ts` (task-162) and
1279
+ * task-048's Execution Notes, decision D1. A `superseded` element can still be deprecated.)
880
1280
  *
881
1281
  * Order, every refusal before the single write:
882
1282
  *
883
1283
  * 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`/`memory reject`; so is a
884
- * `--reason` that is given but blank or trailer-shaped (`optionalReason`). Both precede the
885
- * identity check (task-125, `bug-172`).
886
- * 2. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`.
887
- * 3. **{@link prepareMemoryTransition}** with op `deprecate`, against the `memory.yaml` committed at
888
- * `HEAD` (task-091, `dl-080` (B)) — no committed machine, an unreadable one, not found,
1284
+ * `--reason` that is given but blank, carrying a control character other than tab or newline, or
1285
+ * trailer-shaped (`optionalReason`). Both precede the identity check (task-125, `bug-172`).
1286
+ * 2. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`. Steps 2 and 3 are {@link beginMemoryTransition}
1287
+ * (task-132, `bug-142`; `spec-006` §7).
1288
+ * 3. **{@link prepareMemoryTransition}** with op `deprecate`, against the `memory.yaml` and the
1289
+ * document committed at `HEAD` (task-091, `dl-080` (B); task-247, `bug-187`) — no committed machine, an unreadable one, not found,
889
1290
  * unknown type, or a `status` that is not a state of the type, each a
890
1291
  * `CoreResult.error` (exit `1`). There is no illegal-transition branch: the wildcard edge is legal
891
1292
  * from every state.
@@ -910,16 +1311,14 @@ const memoryRejectFn = async (params) => {
910
1311
  const memoryDeprecateFn = async (params) => {
911
1312
  const { root, positional: id, options } = params;
912
1313
  if (id === undefined || id.trim().length === 0) {
913
- throw new usage_error_1.UsageError('missing required argument: memory deprecate <id>');
1314
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
914
1315
  }
915
1316
  // `--reason` is optional here (`dl-027`), but a reason that IS given must be recordable in the
916
- // trailer — blank or trailer-shaped is a usage error at exit 2, before anything is read or written
917
- // (`dl-067` clauses 1 and 4; `bug-042` F2/F3, whose amendment makes THIS verb the exploitable one).
1317
+ // trailer — blank, carrying a control character other than tab or newline, or trailer-shaped is a
1318
+ // usage error at exit 2, before anything is read or written (`dl-067` clauses 1 and 4 as amended;
1319
+ // `bug-042` F2/F3, whose amendment makes THIS verb the exploitable one).
918
1320
  const reason = (0, require_reason_1.optionalReason)(options);
919
- const identity = (0, git_identity_1.requireGitIdentity)(root);
920
- if (!identity.ok)
921
- return identity;
922
- const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'deprecate');
1321
+ const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'deprecate');
923
1322
  if (!prepared.ok)
924
1323
  return prepared;
925
1324
  const { type, path, from, to, content } = prepared.value;
@@ -931,7 +1330,132 @@ const memoryDeprecateFn = async (params) => {
931
1330
  const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message);
932
1331
  if (!committed.ok)
933
1332
  return committed;
934
- return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message });
1333
+ return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message }, prepared.value.warnings);
1334
+ };
1335
+ /**
1336
+ * `memory park` `CoreOperation.fn` (task-180, `mutates: true`; `dl-110` P1 (a)) — return a started
1337
+ * element to an earlier state along its type's declared `returns` edge (`spec-001`), e.g. a `task` from
1338
+ * `in-progress` back to `backlog`. Returning is not rejecting (`dl-110` Rationale): the commit says the
1339
+ * work is not being done now, not that it was wrong, so `memory history` keeps the two apart. Order,
1340
+ * every refusal before the single write (`spec-006` §7):
1341
+ *
1342
+ * 1. **`<id>`** absent or blank, then **`requireReason`** (`dl-067`) → `UsageError` (exit `2`), before
1343
+ * the identity is read (task-125, `bug-172`). The reason is mandatory: a park records why.
1344
+ * 2. **{@link beginMemoryTransition}** with op `park` — identity (REQ-SEC-01), then the document and
1345
+ * its edge at `HEAD` (`dl-080`): a state with no `returns` edge is `INVALID_TRANSITION` with the
1346
+ * `dl-032` contract message (exit `1`). A WIP limit declared on the target state does not refuse a
1347
+ * park: a return along a `returns` edge takes no slot check (`dl-156` Q2 (i), task-274).
1348
+ * 3. **Edit + commit** — `status` set to the target and nothing else (`spec-010` field-write ownership,
1349
+ * enforced by `commitMemoryTransition`'s post-condition), one commit scoped to that file:
1350
+ * `wf(<type>): park <id> [<from> → <to>]` with a `Reason:` block (`spec-008` §2).
1351
+ *
1352
+ * Deliberately absent: no `Approver:` line and no authority check. A park is a scheduling decision, not
1353
+ * an approval gate; `dl-110` names no approver for it. What parking does to the work around the element
1354
+ * under `dev-loop` (the task's worktree, its bugs' `sync_state`) is `dl-110` P2, owned by the workflow,
1355
+ * not by this verb.
1356
+ */
1357
+ const memoryParkFn = async (params) => {
1358
+ const { root, positional: id, options } = params;
1359
+ if (id === undefined || id.trim().length === 0) {
1360
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
1361
+ }
1362
+ const reason = (0, require_reason_1.requireReason)(options);
1363
+ const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'park');
1364
+ if (!prepared.ok)
1365
+ return prepared;
1366
+ const { type, path, from, to, content } = prepared.value;
1367
+ const message = (0, memory_1.formatMemoryCommitMessage)({ type, op: 'park', ids: [id], transition: { from, to }, reason });
1368
+ const rendered = (0, memory_1.setFrontmatterField)(content, 'status', to);
1369
+ const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, rendered, message);
1370
+ if (!committed.ok)
1371
+ return committed;
1372
+ return (0, types_1.coreOk)({ id, path, from, to, reason }, { sha: committed.value, message }, prepared.value.warnings);
1373
+ };
1374
+ /**
1375
+ * `memory amend` `CoreOperation.fn` (task-127, `mutates: true`; `dl-108` A1 (a), A2 (i), A3). It
1376
+ * records the author's uncommitted edit of one document as an amendment: one commit, that file only,
1377
+ * `status` untouched, under an approver's name. It is the verb for a correction to an element no
1378
+ * transition verb can move (an `approved` tech-spec, a `ready` decision-log). Order, every refusal
1379
+ * before the single write:
1380
+ *
1381
+ * 1. **`<id>`** absent or blank, then **`requireReason`** (`dl-067`) → `UsageError` (exit `2`), before
1382
+ * anything is read. Both precede the identity check (task-125, `bug-172`), as on `approve`.
1383
+ * 2. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`. Step 2 and the lookup in step 3 are
1384
+ * {@link beginMemoryTransition} (task-132, `bug-142`; `spec-006` §7); the identity it returns is
1385
+ * the one step 4 authorizes, the `Approver:` line names and the commit is authored as.
1386
+ * 3. **{@link prepareMemoryTransition}** with op `amend` — the document located at `HEAD`, its type and
1387
+ * state resolved against the `memory.yaml` committed there (`dl-080` (B); task-247, `bug-187`), its
1388
+ * content taken from the working tree; its target is its own
1389
+ * state, so no illegal-transition refusal exists for this verb. Then
1390
+ * {@link requireConfinedWriteTarget} (REQ-SEC-06): a document outside the project, or one that is
1391
+ * itself a symbolic link, is refused before any question about its content.
1392
+ * 4. **{@link requireApprovalAuthority}** (REQ-SEC-03, `dl-108` A2 (i)) — the same check and message
1393
+ * as `approve`, from the committed `dna.yaml`; exit `1`. It runs as soon as the type is known
1394
+ * (step 3), the same place `approve` runs it, so a caller without authority learns nothing about
1395
+ * the edit (task-127 review F6).
1396
+ * 5. **{@link requireAmendableType}** — the committed entry must declare `amendable: true`
1397
+ * (`dl-108` A3, `spec-001`); exit `1`.
1398
+ * Then **{@link requireReadableScaffold}**: the type's scaffold committed at `HEAD`, which step 6
1399
+ * reads, must be one `memory add` would read — a newer or malformed `format` is refused with
1400
+ * `memory add`'s message (`dl-149`; task-257, `bug-243`); exit `1`.
1401
+ * 6. **{@link requireAmendableEdit}** — the document must be committed at `HEAD`, carry a change, and
1402
+ * leave every field the type's {@link amendReservedFields} lists as committed (`spec-010` §
1403
+ * Field-write ownership: `release` only where the committed scaffold declares it, task-170);
1404
+ * exit `1`, naming the field. Then {@link requireRequiredFieldsKept}: past the initial
1405
+ * state, `title` and the type's required fields must stay non-empty (`spec-010` § Validation
1406
+ * rules); exit `1`, naming the field.
1407
+ * 7. **Commit** — {@link commitMemoryTransition} under `carries-content`, the scope `memory submit`
1408
+ * uses: the working-tree bytes are the content of record, so they are written back unchanged and
1409
+ * committed as that one path. `commitPaths` stages only that path, so other modified or staged
1410
+ * files stay where they are (`bug-027`); the post-condition checks that the commit holds only that
1411
+ * path and that `status` is the unchanged state (`bug-076`). Subject
1412
+ * `wf(<type>): amend <id> [<s> → <s>]` with the `Approver:` / `Reason:` body (`spec-008` §2).
1413
+ */
1414
+ const memoryAmendFn = async (params) => {
1415
+ const { root, positional: id, options } = params;
1416
+ if (id === undefined || id.trim().length === 0) {
1417
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('id'));
1418
+ }
1419
+ const reason = (0, require_reason_1.requireReason)(options);
1420
+ const prepared = (0, memory_transition_1.beginMemoryTransition)(root, id, 'amend');
1421
+ if (!prepared.ok)
1422
+ return prepared;
1423
+ const { memoryYaml, type, path, content, frontmatter, from, to } = prepared.value;
1424
+ // Before any question about the edit: is this file the project's to write at all (REQ-SEC-06,
1425
+ // `bug-117`/`bug-120`)? The other verbs ask it first inside `commitMemoryTransition`; `amend` asks
1426
+ // it here too, because its own checks read the document against `HEAD`, and a symlinked document
1427
+ // would otherwise be refused for a field change it never made.
1428
+ const confined = (0, confinement_1.requireConfinedWriteTarget)(root, path, 'write');
1429
+ if (!confined.ok)
1430
+ return confined;
1431
+ const authorized = (0, approval_authority_1.requireApprovalAuthority)(root, type, prepared.value.identity);
1432
+ if (!authorized.ok)
1433
+ return authorized;
1434
+ const amendable = (0, memory_amend_1.requireAmendableType)(memoryYaml, type);
1435
+ if (!amendable.ok)
1436
+ return amendable;
1437
+ const scaffold = (0, memory_amend_1.requireReadableScaffold)(root, memoryYaml, type);
1438
+ if (!scaffold.ok)
1439
+ return scaffold;
1440
+ const edit = (0, memory_amend_1.requireAmendableEdit)(root, id, path, content, (0, memory_amend_1.amendReservedFields)(root, memoryYaml, type));
1441
+ if (!edit.ok)
1442
+ return edit;
1443
+ const filled = (0, memory_amend_1.requireRequiredFieldsKept)(memoryYaml, type, from, frontmatter);
1444
+ if (!filled.ok)
1445
+ return filled;
1446
+ const { name, email } = prepared.value.identity;
1447
+ const message = (0, memory_1.formatMemoryCommitMessage)({
1448
+ type,
1449
+ op: 'amend',
1450
+ ids: [id],
1451
+ transition: { from, to },
1452
+ approver: { name, email, role: approval_authority_1.APPROVER_ROLE },
1453
+ reason,
1454
+ });
1455
+ const committed = (0, memory_transition_1.commitMemoryTransition)(root, prepared.value, content, message, {}, 'carries-content');
1456
+ if (!committed.ok)
1457
+ return committed;
1458
+ return (0, types_1.coreOk)({ id, path, from, to }, { sha: committed.value, message }, prepared.value.warnings);
935
1459
  };
936
1460
  /**
937
1461
  * `directive create` `CoreOperation.fn` (P3.1, `mutates: true` — the FIRST Directives-pillar mutation
@@ -976,6 +1500,12 @@ const directiveCreateFn = async (params) => {
976
1500
  // correct absolute path on Windows; the root-relative form is what `commitPaths` stages.
977
1501
  const relativePath = `.wingfoil/directives/custom/${name}.md`;
978
1502
  const absolutePath = (0, path_1.join)(root, relativePath);
1503
+ // REQ-SEC-06 / bug-121 (task-172): a DANGLING link at the path reads as free to `documentExists`
1504
+ // (`existsSync` follows it), and `writeDocument` would create the file wherever it points. Refused
1505
+ // before the existence check, so a path that leaves the project is reported as leaving it.
1506
+ const confined = (0, confinement_1.requireConfinedWriteTarget)(root, relativePath, 'write');
1507
+ if (!confined.ok)
1508
+ return confined;
979
1509
  if ((0, storage_1.documentExists)(absolutePath)) {
980
1510
  return (0, types_1.coreErr)({ code: 'CONFLICT', message: `directive already exists: ${name}` });
981
1511
  }
@@ -986,9 +1516,8 @@ const directiveCreateFn = async (params) => {
986
1516
  if (!unmodified.ok)
987
1517
  return unmodified;
988
1518
  const content = (0, create_1.renderCustomDirective)(name);
989
- (0, storage_1.writeDocument)(absolutePath, content);
990
1519
  const message = `wf(directive): create ${name}`;
991
- const sha = (0, storage_1.commitPaths)(root, [relativePath], message);
1520
+ const sha = (0, storage_1.writeAndCommit)(root, [{ path: relativePath, content }], message);
992
1521
  const leaked = (0, write_guard_1.committedScopeError)(root, sha, relativePath, content);
993
1522
  if (leaked)
994
1523
  return leaked;
@@ -1019,10 +1548,12 @@ const directiveCreateFn = async (params) => {
1019
1548
  * never sorting) through the comment-preserving `roles.yaml` writer and makes ONE commit,
1020
1549
  * `wf(directive): assign <id1>, <id2> to <role>`, staging only `.wingfoil/roles.yaml`. A request
1021
1550
  * whose ids are all already bound is a success with no write and no commit (P3.7 Sc.2 "Binding is
1022
- * idempotent"); a partially overlapping one appends only what is missing.
1551
+ * idempotent"); a partially overlapping one appends only what is missing. A file the in-place
1552
+ * editor cannot edit is refused (`CONFLICT`, exit 1) unless `--force` authorizes the whole-file
1553
+ * rewrite, whose success then carries a warning the CLI prints on stderr (task-169, `dl-062`).
1023
1554
  */
1024
1555
  const directiveAssignFn = async (params) => {
1025
- const { root, options } = params;
1556
+ const { root, options, force } = params;
1026
1557
  const directive = options?.directive;
1027
1558
  if (directive === undefined)
1028
1559
  throw new usage_error_1.UsageError('missing required argument: --directive');
@@ -1039,10 +1570,12 @@ const directiveAssignFn = async (params) => {
1039
1570
  if (invalid)
1040
1571
  return (0, types_1.coreErr)(invalid);
1041
1572
  const message = `wf(directive): assign ${directives.join(', ')} to ${role}`;
1042
- const updated = (0, directive_assign_1.updateRoleAssignments)(root, role, (current) => (0, roles_edit_1.withAssignedDirectives)(current, directives), message);
1573
+ const updated = (0, directive_assign_1.updateRoleAssignments)(root, role, (current) => (0, roles_edit_1.withAssignedDirectives)(current, directives), message, {
1574
+ force: force === true,
1575
+ });
1043
1576
  if (!updated.ok)
1044
1577
  return updated;
1045
- return (0, types_1.coreOk)({ directives, role, assignments: updated.value.assignments }, updated.commit);
1578
+ return (0, types_1.coreOk)({ directives, role, assignments: updated.value.assignments }, updated.commit, updated.warnings);
1046
1579
  };
1047
1580
  /**
1048
1581
  * `directive remove` `CoreOperation.fn` (P3.3, `mutates: true`; spec-006 §3 row `directiveRemove`,
@@ -1053,7 +1586,7 @@ const directiveAssignFn = async (params) => {
1053
1586
  * strictly **before** anything on disk changes:
1054
1587
  *
1055
1588
  * 1. `<name>` presence — a missing or blank positional is a `UsageError` → exit **2**
1056
- * (`missing required argument: directive remove <name>`, the `memory submit <id>` precedent).
1589
+ * (`missing required argument: <name>`, the one missing-operand wording, task-179).
1057
1590
  * 2. `requireGitIdentity` pre-flight (REQ-SEC-01), after the usage check (task-125, `bug-172`).
1058
1591
  * 3. **Resolve the name to a real file**, never to a constructed path. `loadDirectives` reads every
1059
1592
  * installed directive and {@link selectDirectivesById} picks the winner for that id under
@@ -1103,17 +1636,25 @@ const directiveAssignFn = async (params) => {
1103
1636
  const directiveRemoveFn = async (params) => {
1104
1637
  const { root, positional: name } = params;
1105
1638
  if (name === undefined || name.trim().length === 0) {
1106
- throw new usage_error_1.UsageError('missing required argument: directive remove <name>');
1639
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('name'));
1107
1640
  }
1108
1641
  const identity = (0, git_identity_1.requireGitIdentity)(root);
1109
1642
  if (!identity.ok)
1110
1643
  return identity;
1111
- const directiveFiles = loadOrError(() => (0, loaders_1.loadDirectives)(root));
1112
- if (!directiveFiles.ok)
1113
- return directiveFiles;
1114
- const target = (0, context_1.selectDirectivesById)(directiveFiles.value, new Set([name])).byId.get(name);
1644
+ const inventory = loadOrError(() => (0, loaders_1.loadDirectiveInventory)(root));
1645
+ if (!inventory.ok)
1646
+ return inventory;
1647
+ const target = (0, context_1.selectDirectivesById)(inventory.value.files, new Set([name])).byId.get(name);
1115
1648
  if (target === undefined) {
1116
- return (0, types_1.coreErr)({ code: 'NOT_FOUND', message: `unknown directive: ${name}` });
1649
+ // task-143: the entry asked for may be one the loader skipped (a dangling link, an unreadable
1650
+ // file), so its warnings ride this refusal's `details` — the channel both surfaces render
1651
+ // (task-130) — rather than a stderr line from core, which `--format json` cannot carry.
1652
+ const { warnings } = inventory.value;
1653
+ return (0, types_1.coreErr)({
1654
+ code: 'NOT_FOUND',
1655
+ message: `unknown directive: ${name}`,
1656
+ ...(warnings.length > 0 ? { details: { issues: warnings.map((detail) => ({ detail })) } } : {}),
1657
+ });
1117
1658
  }
1118
1659
  const custom = (0, builtin_asset_1.requireCustomAsset)('directive', target.path);
1119
1660
  if (!custom.ok)
@@ -1140,9 +1681,8 @@ const directiveRemoveFn = async (params) => {
1140
1681
  const unmodified = (0, write_guard_1.requireUnmodifiedTarget)(root, relativePath);
1141
1682
  if (!unmodified.ok)
1142
1683
  return unmodified;
1143
- (0, storage_1.removeDocument)((0, path_1.join)(root, relativePath));
1144
1684
  const message = `wf(directive): remove ${name}`;
1145
- const sha = (0, storage_1.commitPaths)(root, [relativePath], message);
1685
+ const sha = (0, storage_1.writeAndCommit)(root, [{ path: relativePath, content: null }], message);
1146
1686
  const leaked = (0, write_guard_1.committedScopeError)(root, sha, relativePath, null);
1147
1687
  if (leaked)
1148
1688
  return leaked;
@@ -1161,8 +1701,86 @@ const directiveRemoveFn = async (params) => {
1161
1701
  */
1162
1702
  const directivesListFn = async (params) => {
1163
1703
  const { root, options } = params;
1704
+ // task-143 (bug-154): with no `.wingfoil/` the loaders read nothing and the listing came back empty,
1705
+ // exit 0 — under `--role`, with a "no directives assigned" warning nothing was read to establish.
1706
+ const initialized = (0, init_1.requireInitializedProject)(root);
1707
+ if (!initialized.ok)
1708
+ return initialized;
1164
1709
  return loadOrError(() => (0, directives_list_1.loadDirectiveListing)(root, options?.role));
1165
1710
  };
1711
+ /** `workflowList` `CoreOperation.fn` (P4.6, `spec-017` §7.5): {@link workflowListAtHead}, refusals mapped. */
1712
+ const workflowListFn = async (params) => {
1713
+ const { root, all } = params;
1714
+ return loadOrError(() => (0, workflow_list_show_1.workflowListAtHead)(root, all === true));
1715
+ };
1716
+ /**
1717
+ * `workflowShow` `CoreOperation.fn` (P4.7, `spec-017` §7.6): {@link workflowShowAtHead}, refusals mapped; a
1718
+ * missing `<ref>` is the one missing-operand usage error (exit `2`) for a caller that bypasses the registrar.
1719
+ */
1720
+ const workflowShowFn = async (params) => {
1721
+ const { root, positional } = params;
1722
+ if (positional === undefined)
1723
+ throw new usage_error_1.UsageError((0, registry_1.missingOperandReason)('ref'));
1724
+ const result = loadOrError(() => (0, workflow_list_show_1.workflowShowAtHead)(root, positional));
1725
+ return result.ok ? result.value : result;
1726
+ };
1727
+ /**
1728
+ * `workflowNext` `CoreOperation.fn` (P4.4, `spec-017` §7.3): {@link workflowNextAtHead}, refusals mapped — a
1729
+ * `spec-003` error at `HEAD` is `VALIDATION` (exit 1), a `<ref>` naming no open instance `NOT_FOUND` (exit 1);
1730
+ * every deduced outcome exits 0.
1731
+ */
1732
+ const workflowNextFn = async (params) => {
1733
+ const { root, positional, options } = params;
1734
+ const result = loadOrError(() => (0, workflow_next_1.workflowNextAtHead)(root, positional, options?.['assigned-to']));
1735
+ return result.ok ? result.value : result;
1736
+ };
1737
+ /**
1738
+ * `workflowFinalize` `CoreOperation.fn` (`spec-017` §7.9, ruling R11): {@link workflowFinalizeAtHead}, its
1739
+ * refusals returned (`CONFLICT`, `NOT_FOUND`, `VALIDATION`: exit 1) or thrown as a `UsageError` (an
1740
+ * unrecordable or missing `--reason`: exit 2); a `spec-003` error at `HEAD` is `VALIDATION`, exit 1.
1741
+ */
1742
+ const workflowFinalizeFn = async (params) => {
1743
+ const { root, positional, options } = params;
1744
+ const step = options?.['step'];
1745
+ const result = loadOrError(() => (0, workflow_finalize_1.workflowFinalizeAtHead)(root, { ...(positional === undefined ? {} : { ref: positional }), ...(step === undefined ? {} : { step }), ...(options === undefined ? {} : { options }) }));
1746
+ return result.ok ? result.value : result;
1747
+ };
1748
+ /**
1749
+ * `workflowStart` / `workflowEnd` `CoreOperation.fn`s (P4.2, P4.3; `spec-017` §7.1–§7.2; task-217): the two
1750
+ * operations of `./workflow-start-end`, with `start` writing through this registry's own `memoryAdd` and
1751
+ * `memorySubmit`. A `spec-003` error at `HEAD` (or a token value outside the id class) is `VALIDATION`,
1752
+ * exit 1, as every workflow operation maps it ({@link coreErrorOf}).
1753
+ */
1754
+ async function refusalOf(run) {
1755
+ try {
1756
+ return await run();
1757
+ }
1758
+ catch (error) {
1759
+ const refusal = coreErrorOf(error);
1760
+ if (refusal === null)
1761
+ throw error;
1762
+ return (0, types_1.coreErr)(refusal);
1763
+ }
1764
+ }
1765
+ const workflowStartFn = (params) => refusalOf(() => (0, workflow_start_end_1.workflowStart)(params, { add: memoryAddFn, submit: memorySubmitFn }));
1766
+ const workflowEndFn = (params) => refusalOf(() => (0, workflow_start_end_1.workflowEnd)(params));
1767
+ /**
1768
+ * `workflowCreate` / `workflowRemove` `CoreOperation.fn`s (P4.8, P4.9; `spec-017` §7.7–§7.8; task-211): the two
1769
+ * operations of `./workflow-create-remove`. A `spec-003` error at `HEAD` is `VALIDATION`, exit 1
1770
+ * ({@link coreErrorOf}); the argument errors are thrown `UsageError`s (exit 2).
1771
+ */
1772
+ const workflowCreateFn = (params) => refusalOf(() => (0, workflow_create_remove_1.workflowCreate)(params));
1773
+ const workflowRemoveFn = (params) => refusalOf(() => (0, workflow_create_remove_1.workflowRemove)(params));
1774
+ /**
1775
+ * `workflowStatus` `CoreOperation.fn` (P4.5, `spec-017` §7.4): {@link workflowStatusAtHead}, refusals mapped — a
1776
+ * `spec-003` error at `HEAD` is `VALIDATION` (exit 1), a `<ref>` naming no open instance `NOT_FOUND` (exit 1);
1777
+ * every deduced outcome exits 0, a routing error included (BDD P4.14 sc. 3).
1778
+ */
1779
+ const workflowStatusFn = async (params) => {
1780
+ const { root, positional } = params;
1781
+ const result = loadOrError(() => (0, workflow_status_1.workflowStatusAtHead)(root, positional));
1782
+ return result.ok ? result.value : result;
1783
+ };
1166
1784
  /**
1167
1785
  * The production `CoreModule` registry (spec-006 §2, §4). `src/cli`'s command registrar and
1168
1786
  * `src/mcp`'s Tool/Resource registrar both import this exact array — see spec-006 §4.1: "no
@@ -1191,6 +1809,72 @@ const directivesListFn = async (params) => {
1191
1809
  * Tool, or the diff fails.
1192
1810
  */
1193
1811
  exports.CORE_MODULES = [
1812
+ // task-177 (`spec-016` §1): the `agent` module, registered under the name its operations carry
1813
+ // (`agentExecute`, `agentList`, `agentShow`, spec-016 §8). `agentShow` is the first (task-220);
1814
+ // `agentExecute` follows (task-218 the pre-launch half, task-228 the launch), and `agentList`
1815
+ // (task-240) completes the v0.3 set. `src/agent` holds the adapter manifest, the run log and the pipeline.
1816
+ {
1817
+ name: 'agent',
1818
+ description: 'launch an agent on an element, and read recorded agent runs',
1819
+ operations: {
1820
+ // task-218 (`spec-016` §3): `mutates: true` — its one write is the run record's commit (§4.4,
1821
+ // task-228), so it takes `--dry-run`, which prints the launch plan. CLI only in v0.3: the
1822
+ // production server does not run `registerCoreModules`, and the `agent.execute` Tool is v0.4
1823
+ // (spec-016 §7, §8). Reads `HEAD` (spec-008 §11).
1824
+ agentExecute: {
1825
+ name: 'agentExecute',
1826
+ mutates: true,
1827
+ description: 'launch an agent CLI through its adapter on one Memory element, with its context assembled and checked first',
1828
+ // task-235 (spec-016 §3.1, ruling R16): the step forms. `--workflow` and `--step` imply `--next`.
1829
+ flags: [{ name: 'next', description: "take the workflow, phase, element and role from the active workflow's next step" }],
1830
+ options: [
1831
+ { name: 'workflow', valueName: 'ref', description: 'take the step from this workflow (its most recent open instance) or open instance id; implies --next' },
1832
+ { name: 'step', valueName: 'key', description: 'take this step of the frontier, e.g. dev-loop.red@task:task-042-login-form; implies --next' },
1833
+ { name: 'element', valueName: 'type:id', description: "the element the agent works on, e.g. task:task-042-login-form; with a step, it overrides the step's element" },
1834
+ { name: 'role', valueName: 'role', description: "the role the agent runs as, a dna.yaml team.roles name (default: the step's role; with no step, developer, with a warning)" },
1835
+ { name: 'agent', valueName: 'name', description: 'the team.agents entry to launch (default: the first with an adapter that executes the role)' },
1836
+ ],
1837
+ example: 'wingfoil agent execute --next',
1838
+ fn: agent_execute_1.agentExecuteFn,
1839
+ // spec-016 §3.4 (task-228): the agent owns stdout, so the recorded run is reported on stderr.
1840
+ renderToStderr: agent_execute_1.renderAgentExecuteStderr,
1841
+ },
1842
+ // task-240 (`spec-016` §5): read-only, and a declared `HEAD` read (§5.1, `spec-006` §6 item 6): the
1843
+ // run log and the workflow deduction at one commit. Its console rendering is §5's sections
1844
+ // (`renderConsole`); each diagnostic is also a success warning, the console's `warning:` line.
1845
+ // `--active` is v0.4 and not declared, so it is refused as an unknown option (exit 2). CLI only
1846
+ // in v0.3 (spec-016 §7, §8).
1847
+ agentList: {
1848
+ name: 'agentList',
1849
+ mutates: false,
1850
+ description: 'list the recorded agent runs and the workflow steps waiting for an agent, as committed at HEAD',
1851
+ flags: [
1852
+ { name: 'past', description: 'list the recorded runs (the run log at HEAD); without --past or --waiting, both sections' },
1853
+ { name: 'waiting', description: "list the open workflows' ready steps whose role an agent executes; without --past or --waiting, both sections" },
1854
+ ],
1855
+ options: [
1856
+ { name: 'element', valueName: 'type:id', description: 'keep the runs and steps of this element, e.g. task:task-042-login-form' },
1857
+ { name: 'phase', valueName: 'name', description: 'keep the runs and steps of this workflow phase, e.g. red' },
1858
+ ],
1859
+ example: 'wingfoil agent list --past --element task:task-042-login-form',
1860
+ fn: agent_list_1.agentListFn,
1861
+ renderConsole: agent_list_1.renderAgentListConsole,
1862
+ },
1863
+ // task-220 (`spec-016` §6): read-only, and a declared `HEAD` read (§5.1, `spec-006` §6 item 6).
1864
+ // Its console rendering is the `key: value` lines §6 defines, not the indented JSON every other
1865
+ // command prints until P5.1.4 (`renderConsole`, spec-008 §2). Not served over MCP before v0.4
1866
+ // (spec-016 §7): the production server does not run `registerCoreModules`.
1867
+ agentShow: {
1868
+ name: 'agentShow',
1869
+ mutates: false,
1870
+ description: 'print one recorded agent run and the commit that added it',
1871
+ positional: { name: 'run-id', required: true, description: 'the run id, <element-id>/<phase>/<n>' },
1872
+ example: 'wingfoil agent show task-042-login-form/red/1',
1873
+ fn: agent_show_1.agentShowFn,
1874
+ renderConsole: agent_show_1.renderAgentShowConsole,
1875
+ },
1876
+ },
1877
+ },
1194
1878
  {
1195
1879
  name: 'dna',
1196
1880
  description: "read and change dna.yaml, the project's structural map",
@@ -1206,9 +1890,10 @@ exports.CORE_MODULES = [
1206
1890
  name: 'dnaAdd',
1207
1891
  mutates: true,
1208
1892
  description: 'add an entry to a collection, or values to a list',
1209
- positional: { name: 'path', required: true, description: 'the collection (e.g. team.members) or list (e.g. paths.sources) to add to' },
1893
+ positional: { name: 'path', required: true, description: 'the collection (e.g. team.members) or list (e.g. paths.sources) to add to', surplusHint: DNA_SURPLUS_HINT },
1210
1894
  options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
1211
1895
  example: 'wingfoil dna add team.members --value "Ada Lovelace" --entry-email ada@example.com --entry-roles approver',
1896
+ flags: [DNA_FORCE_FLAG],
1212
1897
  fn: dnaAddFn,
1213
1898
  },
1214
1899
  // `remove` declares no entry-field options: it takes what to drop, never what to write.
@@ -1216,9 +1901,10 @@ exports.CORE_MODULES = [
1216
1901
  name: 'dnaRemove',
1217
1902
  mutates: true,
1218
1903
  description: 'remove a collection entry, or values from a list',
1219
- positional: { name: 'path', required: true, description: 'the entry to remove (<collection>.<name>), or the list to remove the --value values from' },
1904
+ positional: { name: 'path', required: true, description: 'the entry to remove (<collection>.<name>), or the list to remove the --value values from', surplusHint: DNA_SURPLUS_HINT },
1220
1905
  options: [DNA_VALUE_OPTION],
1221
1906
  example: 'wingfoil dna remove paths.docs --value README.md',
1907
+ flags: [DNA_FORCE_FLAG],
1222
1908
  fn: dnaRemoveFn,
1223
1909
  },
1224
1910
  // The FIRST `mutates: true` operation in production (spec-006 §3 dna table) — by construction an
@@ -1229,9 +1915,10 @@ exports.CORE_MODULES = [
1229
1915
  name: 'dnaSet',
1230
1916
  mutates: true,
1231
1917
  description: 'set one scalar field',
1232
- positional: { name: 'path', required: true, description: 'the dotted path of the scalar field, e.g. project.name (double-quote a segment that contains a dot)' },
1918
+ positional: { name: 'path', required: true, description: 'the dotted path of the scalar field, e.g. project.name (double-quote a segment that contains a dot)', surplusHint: DNA_SURPLUS_HINT },
1233
1919
  options: [DNA_SET_VALUE_OPTION],
1234
1920
  example: 'wingfoil dna set project.name --value "My Project"',
1921
+ flags: [DNA_FORCE_FLAG],
1235
1922
  fn: dnaSetFn,
1236
1923
  },
1237
1924
  dnaShow: {
@@ -1246,9 +1933,10 @@ exports.CORE_MODULES = [
1246
1933
  name: 'dnaUpdate',
1247
1934
  mutates: true,
1248
1935
  description: 'change fields of an existing collection entry',
1249
- positional: { name: 'path', required: true, description: 'the entry (<collection>.<name>), or one of its fields (<collection>.<name>.<field>) with --value' },
1936
+ positional: { name: 'path', required: true, description: 'the entry (<collection>.<name>), or one of its fields (<collection>.<name>.<field>) with --value', surplusHint: DNA_SURPLUS_HINT },
1250
1937
  options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
1251
1938
  example: 'wingfoil dna update modules.api --entry-description "Public HTTP API"',
1939
+ flags: [DNA_FORCE_FLAG],
1252
1940
  fn: dnaUpdateFn,
1253
1941
  },
1254
1942
  },
@@ -1266,7 +1954,7 @@ exports.CORE_MODULES = [
1266
1954
  memoryAdd: {
1267
1955
  name: 'memoryAdd',
1268
1956
  mutates: true,
1269
- description: "create a document in its type's initial state (draft) from the type's template",
1957
+ description: "create a document in its type's initial state (the first state of its sequence; draft by default) from the type's template",
1270
1958
  options: [
1271
1959
  { name: 'type', required: true, valueName: 'type', description: 'the Memory type, as the committed memory.yaml declares it' },
1272
1960
  { name: 'title', required: true, valueName: 'title', description: 'the document title; also the source of the {slug} token' },
@@ -1280,6 +1968,18 @@ exports.CORE_MODULES = [
1280
1968
  valueName: 'name=value',
1281
1969
  description: 'fill the id_pattern/path token {name} and write the frontmatter field of that name (repeatable)',
1282
1970
  },
1971
+ // task-227 (`spec-017` §7.10, ruling R16; `spec-008` §12): link the added element to a workflow step,
1972
+ // through the `WingFoil-Instance` / `WingFoil-Step` trailers of the add commit (§4.8).
1973
+ {
1974
+ name: 'workflow',
1975
+ valueName: 'ref',
1976
+ description: 'link the document to a step of this open workflow instance (an instance id, or a workflow name)',
1977
+ },
1978
+ {
1979
+ name: 'step',
1980
+ valueName: 'key',
1981
+ description: 'the frontier step to link to, when several of the instance add this type (needs --workflow)',
1982
+ },
1283
1983
  ],
1284
1984
  example: 'wingfoil memory add --type task --title "My first task" --tags demo,quickstart',
1285
1985
  fn: memoryAddFn,
@@ -1366,6 +2066,39 @@ exports.CORE_MODULES = [
1366
2066
  example: 'wingfoil memory deprecate dl-001-use-postgresql --reason "Superseded by the hosted-DB decision."',
1367
2067
  fn: memoryDeprecateFn,
1368
2068
  },
2069
+ // task-180 (`dl-110` P1 (a)) — `mutates: true`: CLI `wingfoil memory park <id> --reason <text>` + MCP
2070
+ // Tool `memory.park`.
2071
+ // `--reason` is `required` (metadata; `memoryParkFn` enforces it via `requireReason`). The id
2072
+ // rides the bare `positional` seam (spec-008 §7).
2073
+ memoryPark: {
2074
+ name: 'memoryPark',
2075
+ mutates: true,
2076
+ description: "return a started document to an earlier state along its type's declared returns edge (for a task, in-progress → backlog)",
2077
+ positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
2078
+ options: [{ name: 'reason', required: true, valueName: 'text', description: "why the work stops for now, recorded as the commit's Reason:" }],
2079
+ example: 'wingfoil memory park task-001-my-first-task --reason "Blocked on the schema decision; back to the backlog."',
2080
+ fn: memoryParkFn,
2081
+ },
2082
+ // task-127 (`dl-108`) — `mutates: true`: CLI `wingfoil memory amend <id> --reason <text>` + MCP
2083
+ // Tool `memory.amend`. Approver-gated like `memoryApprove`, so `--reason` is `required`
2084
+ // (metadata; `memoryAmendFn` enforces it via `requireReason`). The id rides the bare
2085
+ // `positional` seam (spec-008 §7), which also gives it task-129's surplus-operand refusal.
2086
+ memoryAmend: {
2087
+ name: 'memoryAmend',
2088
+ mutates: true,
2089
+ description: 'record an uncommitted correction to a document as an amendment, leaving its state unchanged',
2090
+ positional: { name: 'id', required: true, description: 'the document id, e.g. spec-001-storage-layout' },
2091
+ options: [
2092
+ {
2093
+ name: 'reason',
2094
+ required: true,
2095
+ valueName: 'text',
2096
+ description: "why the document is corrected, recorded as the commit's Reason:; needs the approver role",
2097
+ },
2098
+ ],
2099
+ example: 'wingfoil memory amend spec-001-storage-layout --reason "Later measurements corrected the §2 figures."',
2100
+ fn: memoryAmendFn,
2101
+ },
1369
2102
  },
1370
2103
  },
1371
2104
  {
@@ -1392,7 +2125,7 @@ exports.CORE_MODULES = [
1392
2125
  name: 'paths',
1393
2126
  mutates: false,
1394
2127
  description: 'print the resource paths declared in dna.yaml paths:',
1395
- positional: { name: 'category', description: 'sources, tests, docs, config or governance (omit it for the whole map)' },
2128
+ positional: { name: 'category', description: 'sources, tests, docs, config, governance, runs or health (omit it for the whole map)' },
1396
2129
  flags: [{ name: 'list', description: 'accepted for the planned drill-down view; it does not change the output yet' }],
1397
2130
  example: 'wingfoil paths sources',
1398
2131
  fn: pathsFn,
@@ -1401,14 +2134,138 @@ exports.CORE_MODULES = [
1401
2134
  },
1402
2135
  {
1403
2136
  name: 'workflow',
1404
- description: 'read the workflows the project declares (there is no workflow engine yet)',
2137
+ description: 'start and end workflows, read, create and remove the workflows the project declares, and record the steps nothing else completes (no step is executed yet)',
1405
2138
  operations: {
2139
+ // task-204 (`spec-017` §7.5–§7.6, §8): both read `HEAD`, a declared exception (§1.1, ruling R15;
2140
+ // `spec-006` §6 item 6), from the snapshot and deduction `workflow next` / `status` answer from. A
2141
+ // `spec-003` error at `HEAD` (loader or core check, task-194) is `VALIDATION`, exit 1, with every
2142
+ // diagnostic in `details`; warnings ride the payload's `diagnostics`. The MCP Resources
2143
+ // `wingfoil://workflows…` are not these operations and keep the working tree (spec-017 §9).
1406
2144
  workflowList: {
1407
2145
  name: 'workflowList',
1408
2146
  mutates: false,
1409
- description: 'print the workflow manifest (workflows.yaml) and every workflow it includes, with their phases',
1410
- example: 'wingfoil workflow list',
1411
- fn: wrapReadOnly(loaders_1.loadWorkflowsYaml),
2147
+ description: 'list the workflows you can run now, as committed at HEAD',
2148
+ flags: [{ name: 'all', description: 'list every workflow, the includable ones no open workflow has reached as well' }],
2149
+ example: 'wingfoil workflow list --all',
2150
+ fn: workflowListFn,
2151
+ },
2152
+ // task-216 (`spec-017` §7.3): reads `HEAD`, gating (§1.1 item 1): its first step is what `agent execute
2153
+ // --next` launches on. Exit 0 for every deduced outcome; 1 for a `spec-003` error or an unknown `<ref>`.
2154
+ // Console: the step's key, trail, role, scope, actions with bindings, directive ids and any "human
2155
+ // needed" line (§8). The MCP Resource `wingfoil://workflows/-/next` serves the same payload (task-239,
2156
+ // `src/mcp/workflow-resource.ts`).
2157
+ workflowNext: {
2158
+ name: 'workflowNext',
2159
+ mutates: false,
2160
+ description: 'print the next step of an open workflow: its verb, role, element, directives and bindings, as committed at HEAD',
2161
+ positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: the active instance)' },
2162
+ options: [{ name: 'assigned-to', valueName: 'who', description: "keep the steps whose role 'me', a member name or email, or a role name holds" }],
2163
+ example: 'wingfoil workflow next --assigned-to me',
2164
+ fn: workflowNextFn,
2165
+ renderConsole: workflow_next_1.renderNextConsole,
2166
+ },
2167
+ // task-217 (`spec-017` §7.1–§7.2): both decide at `HEAD` (§1.1 item 1, gating). `start` records the
2168
+ // instance as a `plan` element in two commits (`wf(plan): add`, `wf(plan): submit`); `end` closes it
2169
+ // with `wf(plan): finalize <id> [active → done]`. The MCP Tools `workflow.start` / `workflow.end` are
2170
+ // v0.4 (P5.2.3); the production server serves no Tool.
2171
+ workflowStart: {
2172
+ name: 'workflowStart',
2173
+ mutates: true,
2174
+ description: 'start a workflow: record it as an active plan element and print its first step',
2175
+ positional: { name: 'name', required: true, description: 'the startable workflow to start' },
2176
+ options: [
2177
+ { name: 'element', valueName: 'type:id', description: 'the element the workflow runs on, when it declares one (default: the active workflow\'s element of that type)' },
2178
+ { name: 'title', valueName: 'title', description: "the plan's title (default: '<workflow> — <phase>')" },
2179
+ {
2180
+ name: 'set',
2181
+ repeatable: true,
2182
+ valueName: 'name=value',
2183
+ description: "override a derived plan token: phase=<v> (default: the element's id, else 1) or scope=<v> (default: the workflow name)",
2184
+ },
2185
+ ],
2186
+ example: 'wingfoil workflow start release-cycle --element release:minor-v1',
2187
+ fn: workflowStartFn,
2188
+ renderConsole: workflow_start_end_1.renderStartConsole,
2189
+ },
2190
+ workflowEnd: {
2191
+ name: 'workflowEnd',
2192
+ mutates: true,
2193
+ description: 'end a complete workflow: move its plan element to done; the previous open workflow becomes active',
2194
+ positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: the active instance)' },
2195
+ example: 'wingfoil workflow end',
2196
+ fn: workflowEndFn,
2197
+ renderConsole: workflow_start_end_1.renderEndConsole,
2198
+ },
2199
+ // task-226 (`spec-017` §7.9, ruling R11): records a checkpoint, an `awaits` or an approval no element
2200
+ // carries as one commit with no file change, `workflow: finalize <instance> <key>`, carrying the
2201
+ // phase-record trailers. Gates on the deduction and `dna.yaml` at `HEAD` (§11). The MCP Tool
2202
+ // `workflow.finalize` is v0.4 (P5.2.3): the production server serves no Tool (§9).
2203
+ workflowFinalize: {
2204
+ name: 'workflowFinalize',
2205
+ mutates: true,
2206
+ description: 'record that a step of an open workflow is complete, as an empty commit with the phase-record trailers',
2207
+ positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: the active instance)' },
2208
+ options: [
2209
+ { name: 'step', valueName: 'key', description: 'the frontier step to record, by its key (default: the next step)' },
2210
+ { name: 'reason', valueName: 'text', description: 'why; required on a phase that declares approval:, recorded with an Approver: line' },
2211
+ ],
2212
+ example: 'wingfoil workflow finalize --step dev-loop.red@task:task-130',
2213
+ fn: workflowFinalizeFn,
2214
+ renderConsole: workflow_finalize_1.renderFinalizeConsole,
2215
+ },
2216
+ // task-225 (`spec-017` §7.4, §5.1–§5.4): reads `HEAD`, declared (§1.1, ruling R15), from the deduction `next`
2217
+ // answers from. Every open instance with its phases, frontier and late elements, the pending approvals routed
2218
+ // to `dna.yaml` members, and the ungated elements; the console view's "human needed" lines are the v0.3
2219
+ // notification channel (§5.3). The MCP Resource `wingfoil://workflows/-/status` serves the same payload
2220
+ // (task-239, `src/mcp/workflow-resource.ts`).
2221
+ workflowStatus: {
2222
+ name: 'workflowStatus',
2223
+ mutates: false,
2224
+ description: 'print every open workflow, its pending approvals and who they are routed to, and the elements waiting at a gate, as committed at HEAD',
2225
+ positional: { name: 'ref', description: 'a workflow name or an open workflow instance id (default: every open instance)' },
2226
+ example: 'wingfoil workflow status',
2227
+ fn: workflowStatusFn,
2228
+ renderConsole: workflow_status_1.renderStatusConsole,
2229
+ },
2230
+ workflowShow: {
2231
+ name: 'workflowShow',
2232
+ mutates: false,
2233
+ description: 'print one workflow resolved, its included workflows nested under their phases, as committed at HEAD',
2234
+ positional: { name: 'ref', required: true, description: 'a workflow name, or the id of an open workflow instance' },
2235
+ example: 'wingfoil workflow show release-cycle',
2236
+ fn: workflowShowFn,
2237
+ },
2238
+ // task-211 (`spec-017` §7.7–§7.8, REQ-SEC-07): both decide at `HEAD` (§11) and write one commit with the
2239
+ // workflow file and `workflows.yaml` (`wf(workflow): create|remove <name>`), after loading the registry the
2240
+ // commit would leave. `create`'s one filesystem read is its target's existence (`dl-086`); `remove` refuses
2241
+ // a built-in, a workflow another one includes (`dl-030`; the referrer source is `include:` alone, `dl-066`
2242
+ // option 1) and one with an open instance. The MCP Tools `workflow.create` / `workflow.remove` are v0.4.
2243
+ workflowCreate: {
2244
+ name: 'workflowCreate',
2245
+ mutates: true,
2246
+ description: 'create a custom workflow file with one phase and include it in workflows.yaml',
2247
+ positional: { name: 'name', required: true, description: 'the new workflow, written to .wingfoil/workflows/custom/<name>.yaml' },
2248
+ options: [
2249
+ { name: 'kind', valueName: 'main|sub', description: 'main: startable; sub: includable (or declare --startable / --includable instead)' },
2250
+ { name: 'element', valueName: 'type', description: 'the Memory type the workflow runs on' },
2251
+ { name: 'description', valueName: 'text', description: "the workflow's description" },
2252
+ ],
2253
+ flags: [
2254
+ { name: 'startable', description: 'the workflow can be started on its own (instead of --kind)' },
2255
+ { name: 'includable', description: "a phase can include the workflow (instead of --kind)" },
2256
+ ],
2257
+ example: 'wingfoil workflow create arch-review --kind main',
2258
+ fn: workflowCreateFn,
2259
+ renderConsole: workflow_create_remove_1.renderCreateConsole,
2260
+ },
2261
+ workflowRemove: {
2262
+ name: 'workflowRemove',
2263
+ mutates: true,
2264
+ description: 'delete a custom workflow and its workflows.yaml entry, unless another workflow includes it or an open workflow runs it',
2265
+ positional: { name: 'ref', required: true, description: 'a workflow name, or the id of an open workflow instance (built-in workflows cannot be removed)' },
2266
+ example: 'wingfoil workflow remove arch-review',
2267
+ fn: workflowRemoveFn,
2268
+ renderConsole: workflow_create_remove_1.renderRemoveConsole,
1412
2269
  },
1413
2270
  },
1414
2271
  },
@@ -1452,6 +2309,9 @@ exports.CORE_MODULES = [
1452
2309
  { name: 'directive', required: true, valueName: 'name[,name...]', description: 'the directive(s) to assign, comma-separated' },
1453
2310
  { name: 'role', required: true, valueName: 'role', description: 'the role to assign them to, as the committed dna.yaml declares it' },
1454
2311
  ],
2312
+ // task-169 (`dl-062` Q1 option 3): the opt-in to the whole-file rewrite the in-place editor
2313
+ // would otherwise refuse. A per-command flag, documented in spec-008 §12, not §2.
2314
+ flags: [{ name: 'force', description: 'rewrite the whole roles.yaml when it cannot be edited in place (comments and formatting are not kept)' }],
1455
2315
  example: 'wingfoil directive assign --directive api-style --role developer',
1456
2316
  fn: directiveAssignFn,
1457
2317
  },