@ybotok/loam 0.2.0-alpha.7 → 0.2.0-alpha.9

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 (648) hide show
  1. package/CHANGELOG.md +1315 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +83 -7
  8. package/SCHEMA.md +569 -98
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +33 -11
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +21 -25
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/seed.js +6 -9
  118. package/dist/commands/seed/seed.js.map +1 -1
  119. package/dist/commands/show/show.js +3 -7
  120. package/dist/commands/show/show.js.map +1 -1
  121. package/dist/commands/status/print.js +13 -4
  122. package/dist/commands/status/print.js.map +1 -1
  123. package/dist/commands/status/status.js +3 -7
  124. package/dist/commands/status/status.js.map +1 -1
  125. package/dist/commands/steps/steps.js +3 -7
  126. package/dist/commands/steps/steps.js.map +1 -1
  127. package/dist/commands/subsystem/edit.js +3 -5
  128. package/dist/commands/subsystem/edit.js.map +1 -1
  129. package/dist/commands/subsystem/move.js +5 -8
  130. package/dist/commands/subsystem/move.js.map +1 -1
  131. package/dist/commands/subsystem/query.js +2 -4
  132. package/dist/commands/subsystem/query.js.map +1 -1
  133. package/dist/commands/subsystem/subsystem.js +4 -7
  134. package/dist/commands/subsystem/subsystem.js.map +1 -1
  135. package/dist/commands/subsystem/sync.js +14 -41
  136. package/dist/commands/subsystem/sync.js.map +1 -1
  137. package/dist/commands/subsystem/txn/txn.js +17 -38
  138. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  139. package/dist/commands/subsystem/txn/views.js +32 -5
  140. package/dist/commands/subsystem/txn/views.js.map +1 -1
  141. package/dist/commands/tasks/render.js +116 -0
  142. package/dist/commands/tasks/render.js.map +1 -0
  143. package/dist/commands/tasks/tasks.js +113 -0
  144. package/dist/commands/tasks/tasks.js.map +1 -0
  145. package/dist/commands/unarchive/manifest.js +6 -9
  146. package/dist/commands/unarchive/manifest.js.map +1 -1
  147. package/dist/commands/unarchive/restore.js +17 -28
  148. package/dist/commands/unarchive/restore.js.map +1 -1
  149. package/dist/commands/unarchive/unarchive.js +22 -30
  150. package/dist/commands/unarchive/unarchive.js.map +1 -1
  151. package/dist/commands/validate/arch-coverage.js +6 -15
  152. package/dist/commands/validate/arch-coverage.js.map +1 -1
  153. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  154. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  155. package/dist/commands/validate/checks/requirements.js +18 -8
  156. package/dist/commands/validate/checks/requirements.js.map +1 -1
  157. package/dist/commands/validate/checks/sources.js +6 -2
  158. package/dist/commands/validate/checks/sources.js.map +1 -1
  159. package/dist/commands/validate/feature.js +23 -19
  160. package/dist/commands/validate/feature.js.map +1 -1
  161. package/dist/commands/validate/fleet/landscape.js +23 -23
  162. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  163. package/dist/commands/validate/fleet/map/attest.js +6 -10
  164. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  165. package/dist/commands/validate/fleet/map/bindings.js +1 -3
  166. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  167. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  168. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  169. package/dist/commands/validate/fleet/obligations.js +1 -5
  170. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  171. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  172. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  173. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  174. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  175. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  176. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  177. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  178. package/dist/commands/validate/fleet/views/root-documents.js +8 -28
  179. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  180. package/dist/commands/validate/links/corpus.js +1 -4
  181. package/dist/commands/validate/links/corpus.js.map +1 -1
  182. package/dist/commands/validate/links/glossary.js.map +1 -1
  183. package/dist/commands/validate/report.js +5 -4
  184. package/dist/commands/validate/report.js.map +1 -1
  185. package/dist/commands/validate/scope.js +113 -0
  186. package/dist/commands/validate/scope.js.map +1 -0
  187. package/dist/commands/validate/service/api.js +16 -104
  188. package/dist/commands/validate/service/api.js.map +1 -1
  189. package/dist/commands/validate/service/contracts/generated.js +134 -0
  190. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  191. package/dist/commands/validate/service/events/events.js +1 -1
  192. package/dist/commands/validate/service/events/events.js.map +1 -1
  193. package/dist/commands/validate/service/events/message-producers.js +1 -1
  194. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  195. package/dist/commands/validate/service/evidence-pins.js +56 -10
  196. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  197. package/dist/commands/validate/service/service.js +16 -26
  198. package/dist/commands/validate/service/service.js.map +1 -1
  199. package/dist/commands/validate/service/specs.js +31 -38
  200. package/dist/commands/validate/service/specs.js.map +1 -1
  201. package/dist/commands/validate/service/spine.js +8 -11
  202. package/dist/commands/validate/service/spine.js.map +1 -1
  203. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  204. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  205. package/dist/commands/validate/validate.js +44 -26
  206. package/dist/commands/validate/validate.js.map +1 -1
  207. package/dist/commands/verify/cross/diff.js +2 -6
  208. package/dist/commands/verify/cross/diff.js.map +1 -1
  209. package/dist/commands/verify/evidence/contract.js +1 -1
  210. package/dist/commands/verify/evidence/contract.js.map +1 -1
  211. package/dist/commands/verify/evidence/read.js +12 -3
  212. package/dist/commands/verify/evidence/read.js.map +1 -1
  213. package/dist/commands/verify/frozen.js +45 -23
  214. package/dist/commands/verify/frozen.js.map +1 -1
  215. package/dist/commands/verify/record.js +69 -17
  216. package/dist/commands/verify/record.js.map +1 -1
  217. package/dist/commands/verify/report.js +27 -44
  218. package/dist/commands/verify/report.js.map +1 -1
  219. package/dist/commands/verify/results.js +112 -30
  220. package/dist/commands/verify/results.js.map +1 -1
  221. package/dist/commands/verify/verify.js +54 -51
  222. package/dist/commands/verify/verify.js.map +1 -1
  223. package/dist/commands/vouch/pack/pack.js +2 -2
  224. package/dist/commands/vouch/pack/pack.js.map +1 -1
  225. package/dist/commands/vouch/pack/print.js +1 -6
  226. package/dist/commands/vouch/pack/print.js.map +1 -1
  227. package/dist/commands/vouch/pack/sections.js +1 -2
  228. package/dist/commands/vouch/pack/sections.js.map +1 -1
  229. package/dist/commands/vouch/run.js +26 -81
  230. package/dist/commands/vouch/run.js.map +1 -1
  231. package/dist/commands/vouch/sample/plan.js +20 -34
  232. package/dist/commands/vouch/sample/plan.js.map +1 -1
  233. package/dist/commands/vouch/vet/verify.js +39 -7
  234. package/dist/commands/vouch/vet/verify.js.map +1 -1
  235. package/dist/commands/vouch/vouch.js +10 -17
  236. package/dist/commands/vouch/vouch.js.map +1 -1
  237. package/dist/core/agent/agents-md/command-map.js +56 -101
  238. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  239. package/dist/core/agent/agents-md/map/explain.js +4 -3
  240. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  241. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  242. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  243. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  244. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  245. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  246. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  247. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  248. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  249. package/dist/core/agent/agents-md/map/status.js +9 -3
  250. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  251. package/dist/core/agent/agents-md/map/subsystem.js +4 -3
  252. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  253. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  254. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  255. package/dist/core/agent/agents-md/refusals.js +32 -18
  256. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  257. package/dist/core/agent/agents-md.js +9 -3
  258. package/dist/core/agent/agents-md.js.map +1 -1
  259. package/dist/core/agent/agents-stamp.js +10 -0
  260. package/dist/core/agent/agents-stamp.js.map +1 -1
  261. package/dist/core/agent/scaffold.js +10 -15
  262. package/dist/core/agent/scaffold.js.map +1 -1
  263. package/dist/core/agent/tools/dialects.js +1 -0
  264. package/dist/core/agent/tools/dialects.js.map +1 -1
  265. package/dist/core/agent/tools/registry.js +41 -76
  266. package/dist/core/agent/tools/registry.js.map +1 -1
  267. package/dist/core/agent/workflows/adopt.js +5 -3
  268. package/dist/core/agent/workflows/adopt.js.map +1 -1
  269. package/dist/core/agent/workflows/check.js +20 -16
  270. package/dist/core/agent/workflows/check.js.map +1 -1
  271. package/dist/core/agent/workflows/closing.js +54 -21
  272. package/dist/core/agent/workflows/closing.js.map +1 -1
  273. package/dist/core/agent/workflows/feature.js +79 -11
  274. package/dist/core/agent/workflows/feature.js.map +1 -1
  275. package/dist/core/agent/workflows/implement.js +91 -13
  276. package/dist/core/agent/workflows/implement.js.map +1 -1
  277. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  278. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  279. package/dist/core/agent/workflows/reference/codes.js +8 -6
  280. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  281. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  282. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  283. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  284. package/dist/core/agent/workflows/reference/spine.js +50 -18
  285. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  286. package/dist/core/asyncapi/baseline/plan.js +14 -81
  287. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  288. package/dist/core/asyncapi/digest.js +30 -25
  289. package/dist/core/asyncapi/digest.js.map +1 -1
  290. package/dist/core/asyncapi/merge/components.js +5 -4
  291. package/dist/core/asyncapi/merge/components.js.map +1 -1
  292. package/dist/core/asyncapi/merge/error.js +7 -2
  293. package/dist/core/asyncapi/merge/error.js.map +1 -1
  294. package/dist/core/asyncapi/merge/markers.js +30 -50
  295. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  296. package/dist/core/asyncapi/merge/merge.js +31 -53
  297. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  298. package/dist/core/asyncapi/merge/pin.js +10 -36
  299. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  300. package/dist/core/asyncapi/model.js.map +1 -1
  301. package/dist/core/asyncapi/read.js +36 -12
  302. package/dist/core/asyncapi/read.js.map +1 -1
  303. package/dist/core/brief/brief.js +6 -18
  304. package/dist/core/brief/brief.js.map +1 -1
  305. package/dist/core/brief/landscape.js +8 -12
  306. package/dist/core/brief/landscape.js.map +1 -1
  307. package/dist/core/brief/map/extend.js +8 -5
  308. package/dist/core/brief/map/extend.js.map +1 -1
  309. package/dist/core/brief/map/owed.js +8 -26
  310. package/dist/core/brief/map/owed.js.map +1 -1
  311. package/dist/core/brief/targets.js +15 -5
  312. package/dist/core/brief/targets.js.map +1 -1
  313. package/dist/core/c4/likec4.js +147 -16
  314. package/dist/core/c4/likec4.js.map +1 -1
  315. package/dist/core/c4/locate/unlocated.js +150 -0
  316. package/dist/core/c4/locate/unlocated.js.map +1 -0
  317. package/dist/core/c4/parsed/values.js +54 -1
  318. package/dist/core/c4/parsed/values.js.map +1 -1
  319. package/dist/core/c4/prefetch/fleet.js +1 -9
  320. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  321. package/dist/core/c4/project/documents.js +31 -6
  322. package/dist/core/c4/project/documents.js.map +1 -1
  323. package/dist/core/c4/project/load.js +6 -17
  324. package/dist/core/c4/project/load.js.map +1 -1
  325. package/dist/core/c4/resolve/external.js +51 -0
  326. package/dist/core/c4/resolve/external.js.map +1 -0
  327. package/dist/core/c4/resolve/service.js +25 -1
  328. package/dist/core/c4/resolve/service.js.map +1 -1
  329. package/dist/core/c4/seed/template.js +15 -1
  330. package/dist/core/c4/seed/template.js.map +1 -1
  331. package/dist/core/c4/service-model/shape.js +2 -22
  332. package/dist/core/c4/service-model/shape.js.map +1 -1
  333. package/dist/core/c4/source-mask.js +288 -4
  334. package/dist/core/c4/source-mask.js.map +1 -1
  335. package/dist/core/c4/source-scan.js +203 -147
  336. package/dist/core/c4/source-scan.js.map +1 -1
  337. package/dist/core/c4/splice/authored-source.js +19 -0
  338. package/dist/core/c4/splice/authored-source.js.map +1 -1
  339. package/dist/core/c4/splice/delta-blocks.js +5 -18
  340. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  341. package/dist/core/c4/splice/landscape-merge.js +13 -8
  342. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  343. package/dist/core/c4/splice/model/merge.js +10 -6
  344. package/dist/core/c4/splice/model/merge.js.map +1 -1
  345. package/dist/core/c4/splice/placement.js +108 -18
  346. package/dist/core/c4/splice/placement.js.map +1 -1
  347. package/dist/core/c4/workspace.js +13 -22
  348. package/dist/core/c4/workspace.js.map +1 -1
  349. package/dist/core/capabilities/capabilities.js +6 -6
  350. package/dist/core/capabilities/capabilities.js.map +1 -1
  351. package/dist/core/capabilities/delta/doc.js +7 -10
  352. package/dist/core/capabilities/delta/doc.js.map +1 -1
  353. package/dist/core/capabilities/findings.js +15 -20
  354. package/dist/core/capabilities/findings.js.map +1 -1
  355. package/dist/core/capabilities/realizes/findings.js +12 -19
  356. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  357. package/dist/core/capabilities/realizes/join.js +0 -20
  358. package/dist/core/capabilities/realizes/join.js.map +1 -1
  359. package/dist/core/capabilities/rollup.js +6 -0
  360. package/dist/core/capabilities/rollup.js.map +1 -1
  361. package/dist/core/capabilities/tree.js +6 -28
  362. package/dist/core/capabilities/tree.js.map +1 -1
  363. package/dist/core/capabilities/usecase-join.js +3 -13
  364. package/dist/core/capabilities/usecase-join.js.map +1 -1
  365. package/dist/core/coherence/authoring/scaffold.js +11 -3
  366. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  367. package/dist/core/coherence/authoring/sentinels.js +2 -1
  368. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  369. package/dist/core/coherence/coherence.js +30 -16
  370. package/dist/core/coherence/coherence.js.map +1 -1
  371. package/dist/core/coherence/consumed/operations.js +70 -0
  372. package/dist/core/coherence/consumed/operations.js.map +1 -0
  373. package/dist/core/coherence/consumed/refusal.js +38 -0
  374. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  375. package/dist/core/coherence/declared.js +60 -34
  376. package/dist/core/coherence/declared.js.map +1 -1
  377. package/dist/core/coherence/events/declared.js +44 -59
  378. package/dist/core/coherence/events/declared.js.map +1 -1
  379. package/dist/core/coherence/events/events.js +119 -8
  380. package/dist/core/coherence/events/events.js.map +1 -1
  381. package/dist/core/coherence/events/grades.js +31 -8
  382. package/dist/core/coherence/events/grades.js.map +1 -1
  383. package/dist/core/coherence/events/lookups.js +14 -82
  384. package/dist/core/coherence/events/lookups.js.map +1 -1
  385. package/dist/core/coherence/lookups.js +18 -78
  386. package/dist/core/coherence/lookups.js.map +1 -1
  387. package/dist/core/coherence/retired/archived.js +285 -0
  388. package/dist/core/coherence/retired/archived.js.map +1 -0
  389. package/dist/core/coherence/retired/event-pair.js +62 -0
  390. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  391. package/dist/core/coherence/retired/merged.js +79 -0
  392. package/dist/core/coherence/retired/merged.js.map +1 -0
  393. package/dist/core/coherence/retired/retirement.js +49 -0
  394. package/dist/core/coherence/retired/retirement.js.map +1 -0
  395. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  396. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  397. package/dist/core/coherence/retired/revival/revived.js +297 -0
  398. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  399. package/dist/core/consumers/after.js +113 -0
  400. package/dist/core/consumers/after.js.map +1 -0
  401. package/dist/core/consumers/consumers.js +296 -0
  402. package/dist/core/consumers/consumers.js.map +1 -0
  403. package/dist/core/consumers/current.js +211 -0
  404. package/dist/core/consumers/current.js.map +1 -0
  405. package/dist/core/consumers/scan.js +46 -0
  406. package/dist/core/consumers/scan.js.map +1 -0
  407. package/dist/core/consumers/state.js +27 -0
  408. package/dist/core/consumers/state.js.map +1 -0
  409. package/dist/core/delta/delta.js +1 -1
  410. package/dist/core/delta/delta.js.map +1 -1
  411. package/dist/core/deployment/delta.js +3 -6
  412. package/dist/core/deployment/delta.js.map +1 -1
  413. package/dist/core/diff/base-git.js +94 -12
  414. package/dist/core/diff/base-git.js.map +1 -1
  415. package/dist/core/diff/base-state.js +28 -16
  416. package/dist/core/diff/base-state.js.map +1 -1
  417. package/dist/core/diff/joins.js +136 -0
  418. package/dist/core/diff/joins.js.map +1 -0
  419. package/dist/core/diff/scope/changed-paths.js +22 -6
  420. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  421. package/dist/core/diff/scope/edges.js +82 -0
  422. package/dist/core/diff/scope/edges.js.map +1 -0
  423. package/dist/core/diff/semantic.js +100 -31
  424. package/dist/core/diff/semantic.js.map +1 -1
  425. package/dist/core/doctor/agents.js +34 -18
  426. package/dist/core/doctor/agents.js.map +1 -1
  427. package/dist/core/doctor/report.js.map +1 -1
  428. package/dist/core/document/apply.js +10 -2
  429. package/dist/core/document/apply.js.map +1 -1
  430. package/dist/core/document/frontmatter.js +55 -0
  431. package/dist/core/document/frontmatter.js.map +1 -1
  432. package/dist/core/document/parse.js +29 -6
  433. package/dist/core/document/parse.js.map +1 -1
  434. package/dist/core/document/scenarios.js +17 -0
  435. package/dist/core/document/scenarios.js.map +1 -1
  436. package/dist/core/document/spec.js +4 -11
  437. package/dist/core/document/spec.js.map +1 -1
  438. package/dist/core/envelope/config.js +10 -0
  439. package/dist/core/envelope/config.js.map +1 -1
  440. package/dist/core/envelope/json.js +34 -0
  441. package/dist/core/envelope/json.js.map +1 -1
  442. package/dist/core/explain/families.js +17 -12
  443. package/dist/core/explain/families.js.map +1 -1
  444. package/dist/core/explain/refusals.js +3 -3
  445. package/dist/core/explain/refusals.js.map +1 -1
  446. package/dist/core/explain/terms.js +18 -9
  447. package/dist/core/explain/terms.js.map +1 -1
  448. package/dist/core/explore/explore.js +2 -7
  449. package/dist/core/explore/explore.js.map +1 -1
  450. package/dist/core/fleet-context.js +34 -95
  451. package/dist/core/fleet-context.js.map +1 -1
  452. package/dist/core/gate/checks.js +5 -2
  453. package/dist/core/gate/checks.js.map +1 -1
  454. package/dist/core/gate/partners.js +4 -2
  455. package/dist/core/gate/partners.js.map +1 -1
  456. package/dist/core/gate/report.js.map +1 -1
  457. package/dist/core/gate/verification.js +75 -12
  458. package/dist/core/gate/verification.js.map +1 -1
  459. package/dist/core/gherkin/stale.js +13 -37
  460. package/dist/core/gherkin/stale.js.map +1 -1
  461. package/dist/core/glossary/tree.js +6 -19
  462. package/dist/core/glossary/tree.js.map +1 -1
  463. package/dist/core/kernel/concurrency.js +19 -0
  464. package/dist/core/kernel/concurrency.js.map +1 -1
  465. package/dist/core/kernel/records.js +11 -0
  466. package/dist/core/kernel/records.js.map +1 -1
  467. package/dist/core/links/backlinks.js +1 -3
  468. package/dist/core/links/backlinks.js.map +1 -1
  469. package/dist/core/links/corpus.js +5 -36
  470. package/dist/core/links/corpus.js.map +1 -1
  471. package/dist/core/mcp/argv.js +101 -0
  472. package/dist/core/mcp/argv.js.map +1 -1
  473. package/dist/core/mcp/author-tools.js +2 -1
  474. package/dist/core/mcp/author-tools.js.map +1 -1
  475. package/dist/core/mcp/protocol.js +2 -2
  476. package/dist/core/mcp/protocol.js.map +1 -1
  477. package/dist/core/mcp/tools.js +43 -33
  478. package/dist/core/mcp/tools.js.map +1 -1
  479. package/dist/core/openapi/baseline/gate.js +53 -12
  480. package/dist/core/openapi/baseline/gate.js.map +1 -1
  481. package/dist/core/openapi/baseline/plan.js +22 -8
  482. package/dist/core/openapi/baseline/plan.js.map +1 -1
  483. package/dist/core/openapi/baseline/record.js +0 -1
  484. package/dist/core/openapi/baseline/record.js.map +1 -1
  485. package/dist/core/openapi/contract-text.js +33 -0
  486. package/dist/core/openapi/contract-text.js.map +1 -0
  487. package/dist/core/openapi/contract-tree.js +56 -0
  488. package/dist/core/openapi/contract-tree.js.map +1 -0
  489. package/dist/core/openapi/depth.js +38 -24
  490. package/dist/core/openapi/depth.js.map +1 -1
  491. package/dist/core/openapi/digest.js +52 -2
  492. package/dist/core/openapi/digest.js.map +1 -1
  493. package/dist/core/openapi/doc.js +12 -4
  494. package/dist/core/openapi/doc.js.map +1 -1
  495. package/dist/core/openapi/merge/components.js +25 -60
  496. package/dist/core/openapi/merge/components.js.map +1 -1
  497. package/dist/core/openapi/merge/error.js +3 -13
  498. package/dist/core/openapi/merge/error.js.map +1 -1
  499. package/dist/core/openapi/merge/markers.js +26 -30
  500. package/dist/core/openapi/merge/markers.js.map +1 -1
  501. package/dist/core/openapi/merge/merge.js +26 -52
  502. package/dist/core/openapi/merge/merge.js.map +1 -1
  503. package/dist/core/openapi/merge/pin.js +15 -35
  504. package/dist/core/openapi/merge/pin.js.map +1 -1
  505. package/dist/core/openspec/decide/verdict.js.map +1 -1
  506. package/dist/core/openspec/inventory.js +4 -5
  507. package/dist/core/openspec/inventory.js.map +1 -1
  508. package/dist/core/openspec/model/mapping.js +30 -28
  509. package/dist/core/openspec/model/mapping.js.map +1 -1
  510. package/dist/core/openspec/model/model.js +12 -7
  511. package/dist/core/openspec/model/model.js.map +1 -1
  512. package/dist/core/openspec/scan/changes.js +10 -4
  513. package/dist/core/openspec/scan/changes.js.map +1 -1
  514. package/dist/core/openspec/scan/config.js +7 -16
  515. package/dist/core/openspec/scan/config.js.map +1 -1
  516. package/dist/core/openspec/scan/shape.js +63 -19
  517. package/dist/core/openspec/scan/shape.js.map +1 -1
  518. package/dist/core/openspec/scan/walk.js +21 -38
  519. package/dist/core/openspec/scan/walk.js.map +1 -1
  520. package/dist/core/pack/living.js +2 -16
  521. package/dist/core/pack/living.js.map +1 -1
  522. package/dist/core/provenance/findings.js +48 -12
  523. package/dist/core/provenance/findings.js.map +1 -1
  524. package/dist/core/provenance/git.js +33 -57
  525. package/dist/core/provenance/git.js.map +1 -1
  526. package/dist/core/provenance/gitq/moves.js +71 -114
  527. package/dist/core/provenance/gitq/moves.js.map +1 -1
  528. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  529. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  530. package/dist/core/provenance/sources.js +23 -6
  531. package/dist/core/provenance/sources.js.map +1 -1
  532. package/dist/core/provenance/stamp.js +5 -17
  533. package/dist/core/provenance/stamp.js.map +1 -1
  534. package/dist/core/repo/entries.js.map +1 -1
  535. package/dist/core/repo/repo.js +24 -14
  536. package/dist/core/repo/repo.js.map +1 -1
  537. package/dist/core/repo/tree/find.js +12 -3
  538. package/dist/core/repo/tree/find.js.map +1 -1
  539. package/dist/core/repo/tree/fs.js +27 -1
  540. package/dist/core/repo/tree/fs.js.map +1 -1
  541. package/dist/core/repo/tree/walk.js +8 -30
  542. package/dist/core/repo/tree/walk.js.map +1 -1
  543. package/dist/core/results.js +47 -12
  544. package/dist/core/results.js.map +1 -1
  545. package/dist/core/staging/commit.js +10 -12
  546. package/dist/core/staging/commit.js.map +1 -1
  547. package/dist/core/staging/lock.js +74 -12
  548. package/dist/core/staging/lock.js.map +1 -1
  549. package/dist/core/staging/recovery/intent.js +2 -5
  550. package/dist/core/staging/recovery/intent.js.map +1 -1
  551. package/dist/core/staging/recovery/recover.js +60 -1
  552. package/dist/core/staging/recovery/recover.js.map +1 -1
  553. package/dist/core/staging/snapshot/manifest.js +152 -0
  554. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  555. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  556. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  557. package/dist/core/status/actions/execution.js +11 -0
  558. package/dist/core/status/actions/execution.js.map +1 -1
  559. package/dist/core/status/feature/artifacts.js +2 -2
  560. package/dist/core/status/feature/artifacts.js.map +1 -1
  561. package/dist/core/status/feature/feature.js +5 -11
  562. package/dist/core/status/feature/feature.js.map +1 -1
  563. package/dist/core/status/fleet/fleet.js +6 -2
  564. package/dist/core/status/fleet/fleet.js.map +1 -1
  565. package/dist/core/status/fleet/next.js +26 -3
  566. package/dist/core/status/fleet/next.js.map +1 -1
  567. package/dist/core/status/report.js.map +1 -1
  568. package/dist/core/status/verification.js +30 -15
  569. package/dist/core/status/verification.js.map +1 -1
  570. package/dist/core/tasks/packet.js +174 -0
  571. package/dist/core/tasks/packet.js.map +1 -0
  572. package/dist/core/tasks/record.js +43 -0
  573. package/dist/core/tasks/record.js.map +1 -0
  574. package/dist/core/tasks/sources.js +181 -0
  575. package/dist/core/tasks/sources.js.map +1 -0
  576. package/dist/core/tasks/stamps.js +76 -0
  577. package/dist/core/tasks/stamps.js.map +1 -0
  578. package/dist/core/tasks/unit.js +0 -0
  579. package/dist/core/tasks/unit.js.map +1 -0
  580. package/dist/core/usecases/capability.js +11 -14
  581. package/dist/core/usecases/capability.js.map +1 -1
  582. package/dist/core/usecases/delta/flows.js +5 -7
  583. package/dist/core/usecases/delta/flows.js.map +1 -1
  584. package/dist/core/usecases/fleet.js +178 -31
  585. package/dist/core/usecases/fleet.js.map +1 -1
  586. package/dist/core/usecases/operations.js +5 -4
  587. package/dist/core/usecases/operations.js.map +1 -1
  588. package/dist/core/usecases/service/flows.js +9 -6
  589. package/dist/core/usecases/service/flows.js.map +1 -1
  590. package/dist/core/usecases/suites/participants.js +83 -0
  591. package/dist/core/usecases/suites/participants.js.map +1 -0
  592. package/dist/core/usecases/suites/suites.js +45 -0
  593. package/dist/core/usecases/suites/suites.js.map +1 -0
  594. package/dist/core/verify/answers.js +12 -5
  595. package/dist/core/verify/answers.js.map +1 -1
  596. package/dist/core/verify/build.js +102 -82
  597. package/dist/core/verify/build.js.map +1 -1
  598. package/dist/core/verify/checklist.js +3 -0
  599. package/dist/core/verify/checklist.js.map +1 -1
  600. package/dist/core/verify/location.js +67 -0
  601. package/dist/core/verify/location.js.map +1 -0
  602. package/dist/core/verify/pins/lint.js +3 -2
  603. package/dist/core/verify/pins/lint.js.map +1 -1
  604. package/dist/core/verify/pins/pin.js +1 -1
  605. package/dist/core/verify/pins/pin.js.map +1 -1
  606. package/dist/core/verify/read/file.js +224 -0
  607. package/dist/core/verify/read/file.js.map +1 -0
  608. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  609. package/dist/core/verify/read/shape.js.map +1 -0
  610. package/dist/core/verify/read/union.js +105 -0
  611. package/dist/core/verify/read/union.js.map +1 -0
  612. package/dist/core/verify/record.js +35 -12
  613. package/dist/core/verify/record.js.map +1 -1
  614. package/dist/core/verify/store/commit.js +40 -30
  615. package/dist/core/verify/store/commit.js.map +1 -1
  616. package/dist/core/verify/store/render.js +8 -4
  617. package/dist/core/verify/store/render.js.map +1 -1
  618. package/dist/core/vocabulary/issue.js +1 -0
  619. package/dist/core/vocabulary/issue.js.map +1 -1
  620. package/dist/core/vocabulary/maturity.js +12 -2
  621. package/dist/core/vocabulary/maturity.js.map +1 -1
  622. package/examples/README.md +21 -6
  623. package/examples/docs/architecture/landscape.likec4 +8 -0
  624. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  625. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  626. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  627. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  628. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  629. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  630. package/package.json +1 -1
  631. package/dist/commands/validate/checks/vocabulary.js +0 -16
  632. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  633. package/dist/core/agent/agents-md/artifacts.js +0 -221
  634. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  635. package/dist/core/agent/agents-md/cycle.js +0 -180
  636. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  637. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  638. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  639. package/dist/core/agent/agents-md/spine.js +0 -40
  640. package/dist/core/agent/agents-md/spine.js.map +0 -1
  641. package/dist/core/coherence/events/merged.js +0 -40
  642. package/dist/core/coherence/events/merged.js.map +0 -1
  643. package/dist/core/diff/current-state.js +0 -96
  644. package/dist/core/diff/current-state.js.map +0 -1
  645. package/dist/core/diff/victims.js +0 -256
  646. package/dist/core/diff/victims.js.map +0 -1
  647. package/dist/core/staging/snapshot.js.map +0 -1
  648. package/dist/core/verify/file.js.map +0 -1
@@ -0,0 +1,140 @@
1
+ /**
2
+ * `loam list suites [<name>]` — which use cases run together, and WHICH
3
+ * SERVICES MUST BE UP for a suite's journeys to run.
4
+ *
5
+ * That answer is maintained by hand in most fleets — a compose file, a CI
6
+ * matrix, a paragraph in a runbook — and it is derivable, because a journey
7
+ * already names every participant it touches and the fleet map already says
8
+ * which `services/<id>/` each of those elements stands for. Deriving it means it
9
+ * cannot drift from the journeys: add a hop to a flow and the environment its
10
+ * suites need changes in the same commit. The grouping and the union are
11
+ * `core/usecases/suites/`; this module turns them into the section's payload
12
+ * and its text.
13
+ *
14
+ * A sub-package of list/ for `./campaign/`'s reason: the list package stands at
15
+ * its five-file limit. The direction is list → list/suites, never back.
16
+ *
17
+ * ONE SHAPE whether or not a suite was named. Naming one is a filter over the
18
+ * same answer, not a different question, so `suites[]` narrows and nothing else
19
+ * does — `ungrouped[]` in particular stays the fleet's, because which journeys
20
+ * reached no suite is not a fact about the suite being asked after.
21
+ *
22
+ * The use-case corpus's health travels as `useCases`, spelled as `list
23
+ * capabilities`, `status` and `delta` spell it (`unreadable`, `error`), plus a
24
+ * `state` this section needs and they do not: here the corpus IS the rows, so
25
+ * "the fleet declares no use case" (`absent`) and "loam read them" (`ok`) are
26
+ * two answers a reader has to be able to tell apart from one empty list. Over an
27
+ * unreadable `architecture/` the section DEGRADES rather than refusing, exactly
28
+ * as `list capabilities` does: `suites` and `ungrouped` are ABSENT keys — `[]`
29
+ * would be "loam looked and found none", said about a project nobody read — and
30
+ * a named suite is not refused as unknown, because that refusal would be a
31
+ * claim about suites loam never saw.
32
+ */
33
+ import { fail } from "../../../core/envelope/json.js";
34
+ import { listServices } from "../../../core/repo/repo.js";
35
+ import { closeNamesHint } from "../../../core/repo/tree/find.js";
36
+ import { readUseCases } from "../../../core/usecases/fleet.js";
37
+ import { viewFile } from "../../../core/usecases/place.js";
38
+ import { suiteListing } from "../../../core/usecases/suites/suites.js";
39
+ /**
40
+ * Read the fleet's use cases and group them, or refuse an unknown suite name.
41
+ *
42
+ * Returns `null` when it has already refused — the caller's cue to stop, the
43
+ * `resolveSubsystemSlice` convention one package over.
44
+ *
45
+ * The services are enumerated for the RESOLVER, not for the rows: the
46
+ * element→service join needs the fleet set for the reason every edge join does
47
+ * (`readUseCases`' `UseCaseRequest.known`), and without it a step into
48
+ * `orders.api` would resolve to a service called "api".
49
+ */
50
+ export async function suitesSection(req) {
51
+ const entries = await listServices(req.docsDir, req.fleet);
52
+ const scan = await readUseCases({ docsDir: req.docsDir, known: new Set(entries.map((e) => e.id)) });
53
+ if (scan.kind === "unreadable") {
54
+ const error = scan.errors[0];
55
+ return {
56
+ payload: { useCases: { state: "unreadable", unreadable: true, ...(error === undefined ? {} : { error }) } },
57
+ print: () => printUnreadable(error),
58
+ };
59
+ }
60
+ const listing = suiteListing(scan);
61
+ const state = scan.views.length === 0 ? "absent" : "ok";
62
+ const shown = req.name === undefined ? listing.suites : listing.suites.filter((s) => s.name === req.name);
63
+ if (req.name !== undefined && shown.length === 0) {
64
+ const names = listing.suites.map((s) => s.name);
65
+ // `unknown-target`, the code `--subsystem` and `show` use for a name that
66
+ // resolves to nothing, with the same close-name hint — an empty environment
67
+ // for a typo would read as a suite that needs nothing started.
68
+ fail(req.json, "unknown-target", `No suite '${req.name}'.` +
69
+ closeNamesHint(req.name, names, names.length === 0
70
+ ? " No use case under architecture/ carries a `#suite-` tag."
71
+ : ` Declared suites: ${names.join(", ")}.`));
72
+ return null;
73
+ }
74
+ return {
75
+ payload: {
76
+ suites: shown.map(suiteJson),
77
+ ungrouped: listing.ungrouped.map(viewJson),
78
+ useCases: { state, unreadable: false },
79
+ },
80
+ print: () => printSuites(shown, listing.ungrouped, state === "absent"),
81
+ };
82
+ }
83
+ /** A use case as the payload names it: enough to open the document it is written in. */
84
+ function viewJson(view) {
85
+ return { id: view.id, file: viewFile(view) };
86
+ }
87
+ function suiteJson(suite) {
88
+ return {
89
+ name: suite.name,
90
+ views: suite.views.map(viewJson),
91
+ services: suite.services,
92
+ unresolved: suite.unresolved,
93
+ };
94
+ }
95
+ /**
96
+ * One block per suite — its services, then the participants a stand cannot
97
+ * start — then the journeys in no suite.
98
+ *
99
+ * An unresolved participant gets a line of its own with a marker rather than
100
+ * being folded into the service list: it is not a service anybody can start,
101
+ * and reading it as one is how an environment comes up short.
102
+ */
103
+ function printSuites(suites, ungrouped, absent) {
104
+ if (absent) {
105
+ console.log("no use cases — no dynamic view under architecture/ carries #cap-, #req- or #suite-, so there are no suites");
106
+ return;
107
+ }
108
+ if (suites.length === 0)
109
+ console.log("no suites — no use case under architecture/ carries a #suite- tag");
110
+ for (const [i, suite] of suites.entries()) {
111
+ if (i > 0)
112
+ console.log("");
113
+ console.log(`suite ${suite.name} — ${suite.views.length} use case(s), ${suite.services.length} service(s) must be up`);
114
+ for (const service of suite.services)
115
+ console.log(` ${service}`);
116
+ for (const missing of suite.unresolved) {
117
+ console.log(` ! ${missing.element} — resolves to no service in services/` +
118
+ (missing.external ? " (#external: bring a double)" : ""));
119
+ }
120
+ console.log(` use cases: ${suite.views.map((v) => v.id).join(", ")}`);
121
+ }
122
+ console.log("");
123
+ console.log(`use cases in no suite (${ungrouped.length})`);
124
+ const width = Math.max(0, ...ungrouped.map((v) => v.id.length));
125
+ for (const view of ungrouped)
126
+ console.log(` ${view.id.padEnd(width)} ${viewFile(view)}`);
127
+ }
128
+ /**
129
+ * The `list capabilities` note, in this section's words: UNKNOWN rather than
130
+ * none, and the parser's own message on its own line, because a LikeC4 error is
131
+ * several lines of expected-token list and inlining it would push the sentence
132
+ * off the reader's screen.
133
+ */
134
+ function printUnreadable(error) {
135
+ console.log("note: architecture/ did not parse, so no use case could be read — " +
136
+ "which suites exist, and what each needs up, is UNKNOWN here, not none");
137
+ if (error !== undefined)
138
+ console.log(` ${error}`);
139
+ }
140
+ //# sourceMappingURL=suites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suites.js","sourceRoot":"","sources":["../../../../src/commands/list/suites/suites.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam list suites [<name>]` — which use cases run together, and WHICH\n * SERVICES MUST BE UP for a suite's journeys to run.\n *\n * That answer is maintained by hand in most fleets — a compose file, a CI\n * matrix, a paragraph in a runbook — and it is derivable, because a journey\n * already names every participant it touches and the fleet map already says\n * which `services/<id>/` each of those elements stands for. Deriving it means it\n * cannot drift from the journeys: add a hop to a flow and the environment its\n * suites need changes in the same commit. The grouping and the union are\n * `core/usecases/suites/`; this module turns them into the section's payload\n * and its text.\n *\n * A sub-package of list/ for `./campaign/`'s reason: the list package stands at\n * its five-file limit. The direction is list → list/suites, never back.\n *\n * ONE SHAPE whether or not a suite was named. Naming one is a filter over the\n * same answer, not a different question, so `suites[]` narrows and nothing else\n * does — `ungrouped[]` in particular stays the fleet's, because which journeys\n * reached no suite is not a fact about the suite being asked after.\n *\n * The use-case corpus's health travels as `useCases`, spelled as `list\n * capabilities`, `status` and `delta` spell it (`unreadable`, `error`), plus a\n * `state` this section needs and they do not: here the corpus IS the rows, so\n * \"the fleet declares no use case\" (`absent`) and \"loam read them\" (`ok`) are\n * two answers a reader has to be able to tell apart from one empty list. Over an\n * unreadable `architecture/` the section DEGRADES rather than refusing, exactly\n * as `list capabilities` does: `suites` and `ungrouped` are ABSENT keys — `[]`\n * would be \"loam looked and found none\", said about a project nobody read — and\n * a named suite is not refused as unknown, because that refusal would be a\n * claim about suites loam never saw.\n */\nimport { fail } from \"../../../core/envelope/json.js\";\nimport type { FleetContext } from \"../../../core/fleet-context.js\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { listServices } from \"../../../core/repo/repo.js\";\nimport { closeNamesHint } from \"../../../core/repo/tree/find.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { readUseCases } from \"../../../core/usecases/fleet.js\";\nimport { viewFile } from \"../../../core/usecases/place.js\";\nimport { suiteListing, type Suite } from \"../../../core/usecases/suites/suites.js\";\n\nexport interface SuitesRequest {\n docsDir: DocsDir;\n /** The `[name]` argument: narrows `suites[]` to one suite, refusing a name no use case carries. */\n name: string | undefined;\n json: boolean;\n fleet: FleetContext;\n}\n\n/** The section, ready for whichever view the run prints. */\nexport interface SuitesSection {\n /** The additive payload keys, spread into the `list` envelope. */\n payload: Record<string, unknown>;\n /** The text view of the same answer. */\n print: () => void;\n}\n\n/**\n * Read the fleet's use cases and group them, or refuse an unknown suite name.\n *\n * Returns `null` when it has already refused — the caller's cue to stop, the\n * `resolveSubsystemSlice` convention one package over.\n *\n * The services are enumerated for the RESOLVER, not for the rows: the\n * element→service join needs the fleet set for the reason every edge join does\n * (`readUseCases`' `UseCaseRequest.known`), and without it a step into\n * `orders.api` would resolve to a service called \"api\".\n */\nexport async function suitesSection(req: SuitesRequest): Promise<SuitesSection | null> {\n const entries = await listServices(req.docsDir, req.fleet);\n const scan = await readUseCases({ docsDir: req.docsDir, known: new Set(entries.map((e) => e.id)) });\n if (scan.kind === \"unreadable\") {\n const error = scan.errors[0];\n return {\n payload: { useCases: { state: \"unreadable\", unreadable: true, ...(error === undefined ? {} : { error }) } },\n print: () => printUnreadable(error),\n };\n }\n const listing = suiteListing(scan);\n const state = scan.views.length === 0 ? \"absent\" : \"ok\";\n const shown = req.name === undefined ? listing.suites : listing.suites.filter((s) => s.name === req.name);\n if (req.name !== undefined && shown.length === 0) {\n const names = listing.suites.map((s) => s.name);\n // `unknown-target`, the code `--subsystem` and `show` use for a name that\n // resolves to nothing, with the same close-name hint — an empty environment\n // for a typo would read as a suite that needs nothing started.\n fail(\n req.json,\n \"unknown-target\",\n `No suite '${req.name}'.` +\n closeNamesHint(\n req.name,\n names,\n names.length === 0\n ? \" No use case under architecture/ carries a `#suite-` tag.\"\n : ` Declared suites: ${names.join(\", \")}.`,\n ),\n );\n return null;\n }\n return {\n payload: {\n suites: shown.map(suiteJson),\n ungrouped: listing.ungrouped.map(viewJson),\n useCases: { state, unreadable: false },\n },\n print: () => printSuites(shown, listing.ungrouped, state === \"absent\"),\n };\n}\n\n/** A use case as the payload names it: enough to open the document it is written in. */\nfunction viewJson(view: ParsedView): { id: string; file: string } {\n return { id: view.id, file: viewFile(view) };\n}\n\nfunction suiteJson(suite: Suite): Record<string, unknown> {\n return {\n name: suite.name,\n views: suite.views.map(viewJson),\n services: suite.services,\n unresolved: suite.unresolved,\n };\n}\n\n/**\n * One block per suite — its services, then the participants a stand cannot\n * start — then the journeys in no suite.\n *\n * An unresolved participant gets a line of its own with a marker rather than\n * being folded into the service list: it is not a service anybody can start,\n * and reading it as one is how an environment comes up short.\n */\nfunction printSuites(suites: readonly Suite[], ungrouped: readonly ParsedView[], absent: boolean): void {\n if (absent) {\n console.log(\n \"no use cases — no dynamic view under architecture/ carries #cap-, #req- or #suite-, so there are no suites\",\n );\n return;\n }\n if (suites.length === 0) console.log(\"no suites — no use case under architecture/ carries a #suite- tag\");\n for (const [i, suite] of suites.entries()) {\n if (i > 0) console.log(\"\");\n console.log(\n `suite ${suite.name} — ${suite.views.length} use case(s), ${suite.services.length} service(s) must be up`,\n );\n for (const service of suite.services) console.log(` ${service}`);\n for (const missing of suite.unresolved) {\n console.log(\n ` ! ${missing.element} — resolves to no service in services/` +\n (missing.external ? \" (#external: bring a double)\" : \"\"),\n );\n }\n console.log(` use cases: ${suite.views.map((v) => v.id).join(\", \")}`);\n }\n console.log(\"\");\n console.log(`use cases in no suite (${ungrouped.length})`);\n const width = Math.max(0, ...ungrouped.map((v) => v.id.length));\n for (const view of ungrouped) console.log(` ${view.id.padEnd(width)} ${viewFile(view)}`);\n}\n\n/**\n * The `list capabilities` note, in this section's words: UNKNOWN rather than\n * none, and the parser's own message on its own line, because a LikeC4 error is\n * several lines of expected-token list and inlining it would push the sentence\n * off the reader's screen.\n */\nfunction printUnreadable(error: string | undefined): void {\n console.log(\n \"note: architecture/ did not parse, so no use case could be read — \" +\n \"which suites exist, and what each needs up, is UNKNOWN here, not none\",\n );\n if (error !== undefined) console.log(` ${error}`);\n}\n"]}
@@ -12,10 +12,9 @@ import { serviceResolver } from "../../core/c4/resolve/service.js";
12
12
  import { maturityGaps, serviceMaturity, } from "../../core/vocabulary/maturity.js";
13
13
  import {} from "../../core/repo/entries.js";
14
14
  import { landscapePath, servicePathsAt } from "../../core/repo/paths.js";
15
- import { featureChecklist } from "../../core/verify/checklist.js";
16
- import { readVerification } from "../../core/verify/file.js";
15
+ import { verificationState } from "../../core/status/verification.js";
17
16
  import {} from "../../core/verify/answers.js";
18
- import { confirmedProvenance, tallyRecord, verificationVerdict, } from "../../core/verify/record.js";
17
+ import {} from "../../core/verify/record.js";
19
18
  /**
20
19
  * Read the living landscape ONCE and decide, per service, whether an API is
21
20
  * expected of it. `serviceResolver` is handed the real service ids so an edge
@@ -70,40 +69,36 @@ export async function serviceViews(docsDir, services, boundService, preloaded) {
70
69
  });
71
70
  }
72
71
  /**
73
- * The verification column. `readVerification` is one file read; the checklist
74
- * is derived only when there is a record to judge — on a fleet-sized repo most
75
- * features have none, and deriving N checklists to print N dashes is the cost
76
- * that would get this column dropped.
72
+ * The verification column: `status`'s own record state (`verificationState`),
73
+ * projected onto the cell — the record read, the files added up against the
74
+ * checklist just derived, the archived record read as frozen history — so
75
+ * `list`, `status` and `gate` compute one cell, for the reasons that function
76
+ * records. Reading the record is cheap but no longer one file read — a
77
+ * directory listing and one small read per attesting service, plus the
78
+ * one-file `verification.yaml` — and the checklist is derived only when there
79
+ * is a record to judge: on a fleet-sized repo most features have none, and
80
+ * deriving N checklists to print N dashes is the cost that would get this
81
+ * column dropped. `fleet` is the invocation's read index, so each derivation's
82
+ * enumerations and delta parse are memo hits rather than a walk per feature.
77
83
  *
78
- * An archived feature's record is frozen history (see `core/verify/record.ts`): archive
79
- * merged the feature's operations into the living openapi, so a re-derived
80
- * checklist can only mismatch — the record is reported as recorded, never
81
- * judged stale.
84
+ * A record that cannot be read is one dash like an absent one: this column
85
+ * shows one glyph either way. That includes a record whose summary
86
+ * contradicts its own claims (`verify.record-miscounted`), so no count from it
87
+ * is printed as fact — reading it as absent is the safe half of the truth, and
88
+ * `status` and `verify`, which can say why, report it as unreadable instead.
82
89
  */
83
- export async function featureVerification(docsDir, f) {
84
- const v = await readVerification(f.dir);
85
- if (v === null)
90
+ export async function featureVerification(docsDir, f, fleet) {
91
+ const { state: s } = await verificationState(docsDir, f, { fleet });
92
+ if (s.state === "absent" || s.state === "unreadable" || s.recorded === null)
86
93
  return null;
87
- const stale = f.archived
88
- ? false
89
- : (await featureChecklist(docsDir, f.dir, f.id)).digest !== v.checklist;
90
- const tally = tallyRecord(v);
91
94
  return {
92
- state: stale ? "stale" : "recorded",
93
- recorded: v.recorded,
94
- verdict: verificationVerdict(tally, stale),
95
- confirmed: tally.confirmed,
96
- claims: tally.claims,
97
- attested: tally.attested,
98
- answered: confirmedProvenance(v.claims),
95
+ state: s.state,
96
+ recorded: s.recorded,
97
+ verdict: s.verdict,
98
+ confirmed: s.confirmed,
99
+ claims: s.claims,
100
+ attested: s.attested,
101
+ answered: s.answered,
99
102
  };
100
103
  }
101
- /* ------------------------------------------------------------------ */
102
- /* Text */
103
- /* ------------------------------------------------------------------ */
104
- /**
105
- * Fixed-width presence flags. `a` and `A` are deliberately the same letter in
106
- * two cases: the arch spec and the API are the two things a reader most often
107
- * mistakes for each other, and one is lowercase because it is optional.
108
- */
109
104
  //# sourceMappingURL=views.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"views.js","sourceRoot":"","sources":["../../../src/commands/list/views.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the list actually reads: one row per service, one verification cell per\n * feature.\n *\n * Both are gathered before anything is printed or serialized, because the same\n * numbers appear in `--json` and in the worklist footer — a footer computed\n * from a second read is a footer that can contradict the rows above it.\n */\nimport { existsSync } from \"node:fs\";\nimport { loadFile, type LoadedDoc } from \"../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../core/c4/resolve/service.js\";\nimport {\n maturityGaps,\n serviceMaturity,\n type Maturity,\n type MaturityInput,\n} from \"../../core/vocabulary/maturity.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"../../core/repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../../core/repo/paths.js\";\nimport { featureChecklist } from \"../../core/verify/checklist.js\";\nimport { readVerification } from \"../../core/verify/file.js\";\nimport { type AnsweredBy } from \"../../core/verify/answers.js\";\nimport {\n confirmedProvenance,\n tallyRecord,\n type VerificationVerdict,\n verificationVerdict,\n} from \"../../core/verify/record.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport interface ServiceView extends MaturityInput {\n /** True when this service's `sources` can only be resolved from its own repo. */\n unverifiableFromHere: boolean;\n maturity: Maturity;\n /** What stands between this service and the next rung, in fix order. */\n missing: string[];\n}\n\n/**\n * Read the living landscape ONCE and decide, per service, whether an API is\n * expected of it. `serviceResolver` is handed the real service ids so an edge\n * drawn into a modelled container (`paymentService.api`) counts for the service\n * that owns it — the same resolution `validate` uses, so the two commands\n * cannot disagree about who is called.\n */\nexport async function serviceViews(\n docsDir: DocsDir,\n services: ServiceEntry[],\n boundService: string | undefined,\n preloaded?: LoadedDoc | null,\n): Promise<ServiceView[]> {\n const known = new Set(services.map((s) => s.id));\n const lp = landscapePath(docsDir);\n // `preloaded` is the landscape a caller already holds — `undefined` means\n // \"load it if it exists\", null \"there is none\", the convention\n // validateService's own `preloaded` follows. The fleet scorecard hands in\n // the batch-parsed doc because a healthy `validate --all` must spin ZERO\n // per-document workspaces (test/validate-batch-fallback.test.ts counts\n // them), and a second parse of a document that run already holds was the\n // exact per-path cost the batch loader exists to retire.\n const land = preloaded !== undefined ? preloaded : existsSync(lp) ? await loadFile(lp) : null;\n const called = new Set<string>();\n // Positive evidence only: with no landscape, or one that does not parse, we\n // know nothing about who is called and every service is assumed to have an\n // API — the conservative direction, because claiming a service needs no\n // contract is how a real one goes undocumented. This is the SAME rule\n // `landscapeEvidence` (core/vocabulary/maturity.ts) spells per service for\n // explore and the context pack — derived fleet-wide here in one pass (one\n // `called` set for every service) because calling the per-service partition\n // from this loop would re-walk the whole relationship list once per\n // service. If the rule moves, both spellings move.\n const proven = land !== null && land.errors.length === 0;\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n // …including its self-edge rule: an edge whose two endpoints resolve to\n // one service is that service's own internal wiring, and counting it made\n // a service nobody calls owe an OpenAPI contract — `partial` here while\n // `loam context` (which reads the shared derivation) said `documented`.\n if (r.op !== undefined && svcOf(r.source) !== svcOf(r.target)) called.add(svcOf(r.target));\n }\n }\n\n return services.map((entry) => {\n const archSpec = existsSync(servicePathsAt(entry.dir).archSpec);\n const apiExpected = !proven || called.has(entry.id);\n const view = {\n entry,\n archSpec,\n apiExpected,\n unverifiableFromHere: entry.sources.declared && boundService !== entry.id,\n };\n const missing = maturityGaps(view);\n return { ...view, maturity: serviceMaturity(view), missing };\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* JSON */\n/* ------------------------------------------------------------------ */\n\nexport interface VerificationCell {\n state: \"recorded\" | \"stale\";\n /** The day the record was written. */\n recorded: string;\n /**\n * `verify --json`'s own three-valued verdict, through `verificationVerdict` —\n * not a fourth opinion computed here. `attested` is the one this column got\n * wrong: a scenario confirmed on an agent's word printed as `11/11` under a\n * heading that said `verified`, and `list --json` is how the fleet is graded.\n */\n verdict: VerificationVerdict;\n /** Both counts are `tallyRecord`'s — the record's `claims[]`, never its `summary:` block. */\n confirmed: number;\n claims: number;\n /** Of the confirmed, the scenario claims on an agent's word (`attestedClaims`). */\n attested: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`confirmedProvenance`), every key\n * present. A different question from `attested` above, which is why both are here: `attested`\n * asks only about `scenario.tested` because that is what the verdict turns on, while this asks\n * who answered EVERY kind — and reading the first as if it were the second is how the fleet\n * scorecard credited agent-answered permission and operation claims to a test runner.\n */\n answered: Record<AnsweredBy, number>;\n}\n\n/**\n * The verification column. `readVerification` is one file read; the checklist\n * is derived only when there is a record to judge — on a fleet-sized repo most\n * features have none, and deriving N checklists to print N dashes is the cost\n * that would get this column dropped.\n *\n * An archived feature's record is frozen history (see `core/verify/record.ts`): archive\n * merged the feature's operations into the living openapi, so a re-derived\n * checklist can only mismatch — the record is reported as recorded, never\n * judged stale.\n */\nexport async function featureVerification(\n docsDir: DocsDir,\n f: FeatureEntry,\n): Promise<VerificationCell | null> {\n const v = await readVerification(f.dir);\n if (v === null) return null;\n const stale = f.archived\n ? false\n : (await featureChecklist(docsDir, f.dir, f.id)).digest !== v.checklist;\n const tally = tallyRecord(v);\n return {\n state: stale ? \"stale\" : \"recorded\",\n recorded: v.recorded,\n verdict: verificationVerdict(tally, stale),\n confirmed: tally.confirmed,\n claims: tally.claims,\n attested: tally.attested,\n answered: confirmedProvenance(v.claims),\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Text */\n/* ------------------------------------------------------------------ */\n\n/**\n * Fixed-width presence flags. `a` and `A` are deliberately the same letter in\n * two cases: the arch spec and the API are the two things a reader most often\n * mistakes for each other, and one is lowercase because it is optional.\n */\n"]}
1
+ {"version":3,"file":"views.js","sourceRoot":"","sources":["../../../src/commands/list/views.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the list actually reads: one row per service, one verification cell per\n * feature.\n *\n * Both are gathered before anything is printed or serialized, because the same\n * numbers appear in `--json` and in the worklist footer — a footer computed\n * from a second read is a footer that can contradict the rows above it.\n */\nimport { existsSync } from \"node:fs\";\nimport { loadFile, type LoadedDoc } from \"../../core/c4/likec4.js\";\nimport { serviceResolver } from \"../../core/c4/resolve/service.js\";\nimport {\n maturityGaps,\n serviceMaturity,\n type Maturity,\n type MaturityInput,\n} from \"../../core/vocabulary/maturity.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"../../core/repo/entries.js\";\nimport { landscapePath, servicePathsAt } from \"../../core/repo/paths.js\";\nimport type { FleetContext } from \"../../core/fleet-context.js\";\nimport { verificationState } from \"../../core/status/verification.js\";\nimport { type AnsweredBy } from \"../../core/verify/answers.js\";\nimport { type VerificationVerdict } from \"../../core/verify/record.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport interface ServiceView extends MaturityInput {\n /** True when this service's `sources` can only be resolved from its own repo. */\n unverifiableFromHere: boolean;\n maturity: Maturity;\n /**\n * What stands between this service and the next rung, in fix order — and at\n * `vouched`, the re-vouch a document that moved under its stamp owes\n * (`maturityGaps`). Empty exactly when nothing is owed, which is the\n * worklist's membership test.\n */\n missing: string[];\n}\n\n/**\n * Read the living landscape ONCE and decide, per service, whether an API is\n * expected of it. `serviceResolver` is handed the real service ids so an edge\n * drawn into a modelled container (`paymentService.api`) counts for the service\n * that owns it — the same resolution `validate` uses, so the two commands\n * cannot disagree about who is called.\n */\nexport async function serviceViews(\n docsDir: DocsDir,\n services: ServiceEntry[],\n boundService: string | undefined,\n preloaded?: LoadedDoc | null,\n): Promise<ServiceView[]> {\n const known = new Set(services.map((s) => s.id));\n const lp = landscapePath(docsDir);\n // `preloaded` is the landscape a caller already holds — `undefined` means\n // \"load it if it exists\", null \"there is none\", the convention\n // validateService's own `preloaded` follows. The fleet scorecard hands in\n // the batch-parsed doc because a healthy `validate --all` must spin ZERO\n // per-document workspaces (test/validate-batch-fallback.test.ts counts\n // them), and a second parse of a document that run already holds was the\n // exact per-path cost the batch loader exists to retire.\n const land = preloaded !== undefined ? preloaded : existsSync(lp) ? await loadFile(lp) : null;\n const called = new Set<string>();\n // Positive evidence only: with no landscape, or one that does not parse, we\n // know nothing about who is called and every service is assumed to have an\n // API — the conservative direction, because claiming a service needs no\n // contract is how a real one goes undocumented. This is the SAME rule\n // `landscapeEvidence` (core/vocabulary/maturity.ts) spells per service for\n // explore and the context pack — derived fleet-wide here in one pass (one\n // `called` set for every service) because calling the per-service partition\n // from this loop would re-walk the whole relationship list once per\n // service. If the rule moves, both spellings move.\n const proven = land !== null && land.errors.length === 0;\n if (proven) {\n const svcOf = serviceResolver(land.elements, known);\n for (const r of land.relationships) {\n // …including its self-edge rule: an edge whose two endpoints resolve to\n // one service is that service's own internal wiring, and counting it made\n // a service nobody calls owe an OpenAPI contract — `partial` here while\n // `loam context` (which reads the shared derivation) said `documented`.\n if (r.op !== undefined && svcOf(r.source) !== svcOf(r.target)) called.add(svcOf(r.target));\n }\n }\n\n return services.map((entry) => {\n const archSpec = existsSync(servicePathsAt(entry.dir).archSpec);\n const apiExpected = !proven || called.has(entry.id);\n const view = {\n entry,\n archSpec,\n apiExpected,\n unverifiableFromHere: entry.sources.declared && boundService !== entry.id,\n };\n const missing = maturityGaps(view);\n return { ...view, maturity: serviceMaturity(view), missing };\n });\n}\n\n/** What the features table says about verification without N `loam verify` runs. */\nexport interface VerificationCell {\n state: \"recorded\" | \"stale\";\n /** The day the record was written. */\n recorded: string;\n /**\n * `verify --json`'s own three-valued verdict, through `verificationVerdict` —\n * not a fourth opinion computed here. `attested` is the one this column got\n * wrong: a scenario confirmed on an agent's word printed as `11/11` under a\n * heading that said `verified`, and `list --json` is how the fleet is graded.\n */\n verdict: VerificationVerdict;\n /** Both counts are `tallyRecord`'s — the record's `claims[]`, never its `summary:` block. */\n confirmed: number;\n claims: number;\n /** Of the confirmed, the scenario claims on an agent's word (`attestedClaims`). */\n attested: number;\n /**\n * Of the confirmed, one count per `answered_by` value (`confirmedProvenance`), every key\n * present. A different question from `attested` above, which is why both are here: `attested`\n * asks only about `scenario.tested` because that is what the verdict turns on, while this asks\n * who answered EVERY kind — and reading the first as if it were the second is how the fleet\n * scorecard credited agent-answered permission and operation claims to a test runner.\n */\n answered: Record<AnsweredBy, number>;\n}\n\n/**\n * The verification column: `status`'s own record state (`verificationState`),\n * projected onto the cell — the record read, the files added up against the\n * checklist just derived, the archived record read as frozen history — so\n * `list`, `status` and `gate` compute one cell, for the reasons that function\n * records. Reading the record is cheap but no longer one file read — a\n * directory listing and one small read per attesting service, plus the\n * one-file `verification.yaml` — and the checklist is derived only when there\n * is a record to judge: on a fleet-sized repo most features have none, and\n * deriving N checklists to print N dashes is the cost that would get this\n * column dropped. `fleet` is the invocation's read index, so each derivation's\n * enumerations and delta parse are memo hits rather than a walk per feature.\n *\n * A record that cannot be read is one dash like an absent one: this column\n * shows one glyph either way. That includes a record whose summary\n * contradicts its own claims (`verify.record-miscounted`), so no count from it\n * is printed as fact — reading it as absent is the safe half of the truth, and\n * `status` and `verify`, which can say why, report it as unreadable instead.\n */\nexport async function featureVerification(\n docsDir: DocsDir,\n f: FeatureEntry,\n fleet: FleetContext,\n): Promise<VerificationCell | null> {\n const { state: s } = await verificationState(docsDir, f, { fleet });\n if (s.state === \"absent\" || s.state === \"unreadable\" || s.recorded === null) return null;\n return {\n state: s.state,\n recorded: s.recorded,\n verdict: s.verdict,\n confirmed: s.confirmed,\n claims: s.claims,\n attested: s.attested,\n answered: s.answered,\n };\n}\n"]}
@@ -31,11 +31,13 @@ import { registerStatus } from "../status/status.js";
31
31
  import { registerList } from "../list/list.js";
32
32
  import { registerShow } from "../show/show.js";
33
33
  import { registerDelta } from "../delta/delta.js";
34
+ import { registerTasks } from "../tasks/tasks.js";
34
35
  import { registerDiff } from "../diff/diff.js";
35
36
  import { registerExplore } from "../explore.js";
36
37
  import { registerDependencies } from "../dependencies.js";
37
38
  import { registerDoctor } from "../doctor.js";
38
39
  import { registerContext } from "../context/context.js";
40
+ import { registerAdopt } from "../adopt/adopt.js";
39
41
  import { registerGate } from "../gate/gate.js";
40
42
  import { registerExplain } from "../explain/explain.js";
41
43
  import { registerInstructions } from "../instructions.js";
@@ -44,6 +46,7 @@ import { registerNew } from "../new/new.js";
44
46
  import { registerRebase } from "../rebase/rebase.js";
45
47
  import { registerGherkin } from "../gherkin/gherkin.js";
46
48
  import { registerArchive } from "../archive/archive.js";
49
+ import { MCP_TOOLS } from "../../core/mcp/tools.js";
47
50
  /**
48
51
  * Every console method Node's Console writes to STDOUT — the protocol stream,
49
52
  * where one stray line corrupts the transport. Hooking only `log` was the
@@ -93,13 +96,80 @@ export function captureConsole(sinks) {
93
96
  /** Commander output codes whose printed output is the point, not a usage failure. */
94
97
  const PASS_THROUGH = new Set(["commander.help", "commander.helpDisplayed", "commander.version"]);
95
98
  /**
96
- * The scoped program, output routed into `sinks`. configureOutput and
97
- * exitOverride are set BEFORE the register calls on purpose: commander copies
98
- * inherited settings onto a subcommand when it is created, so settings applied
99
- * after registration would leave every subcommand writing to the real stdout —
100
- * which for an MCP server is the protocol stream.
99
+ * Every command a tool table may name, keyed by the `command` its tool carries.
100
+ * A LOOKUP, not a roster: which of these a call's program registers is decided
101
+ * by the active tool table alone (`toolRunner`), so the authoring writers —
102
+ * new, rebase, gherkin, archive — exist in it only when `--author` put their
103
+ * tools in the table, and the table the read-only hints describe is the one
104
+ * place that gate is written.
105
+ *
106
+ * `instructions` and `steps` read no config at all, and are in MCP_TOOLS
107
+ * because an agent meets them BEFORE the repository is wired: every generated
108
+ * skill's first line points at `loam instructions <workflow>`. They are why
109
+ * registration follows the table. This list and MCP_TOOLS used to be two
110
+ * copies of one roster, and nothing counted them against each other until a
111
+ * tool advertised in `tools/list` and absent here answered a `tools/call` with
112
+ * `unknown command 'instructions'` — an envelope that reads as the caller's
113
+ * mistake. `test/mcp-serve.test.ts` calls every advertised tool, and checks
114
+ * this lookup against both tables directly.
115
+ */
116
+ const REGISTRARS = new Map([
117
+ ["validate", registerValidate],
118
+ ["status", registerStatus],
119
+ ["list", registerList],
120
+ ["show", registerShow],
121
+ ["delta", registerDelta],
122
+ ["tasks", registerTasks],
123
+ ["diff", registerDiff],
124
+ ["explore", registerExplore],
125
+ ["dependencies", registerDependencies],
126
+ ["doctor", registerDoctor],
127
+ ["context", registerContext],
128
+ ["adopt", registerAdopt],
129
+ ["gate", registerGate],
130
+ ["explain", registerExplain],
131
+ ["instructions", registerInstructions],
132
+ ["steps", registerSteps],
133
+ ["new", registerNew],
134
+ ["rebase", registerRebase],
135
+ ["gherkin", registerGherkin],
136
+ ["archive", registerArchive],
137
+ ]);
138
+ /**
139
+ * Resolve every command `tools` name to its registrar, or throw naming each
140
+ * one that has none. This is the server's STARTUP check, and it runs once per
141
+ * table: `toolRunner` calls it before the first frame is read, so a tool
142
+ * advertised with no registrar — the roster drift above, arriving again —
143
+ * stops `loam mcp` at launch, through cli.ts's catch like any other throw,
144
+ * before a client has been promised anything. It used to run inside every call's program build, where one
145
+ * missing entry turned EVERY `tools/call` into an `internal` envelope naming
146
+ * it, the tools that were wired correctly included: a call's outcome
147
+ * depended on a tool it never named.
148
+ */
149
+ export function resolveRegistrars(tools) {
150
+ const resolved = [];
151
+ const missing = [];
152
+ for (const command of new Set(tools.map((tool) => tool.command))) {
153
+ const register = REGISTRARS.get(command);
154
+ if (register === undefined)
155
+ missing.push(`'${command}'`);
156
+ else
157
+ resolved.push(register);
158
+ }
159
+ if (missing.length > 0) {
160
+ throw new Error(`loam mcp: no command registered for MCP tool command ${missing.join(", ")} — add it to REGISTRARS in src/commands/mcp/dispatch.ts`);
161
+ }
162
+ return resolved;
163
+ }
164
+ /**
165
+ * The scoped program, output routed into `sinks`, holding exactly the commands
166
+ * the resolved table names. configureOutput and exitOverride are set BEFORE the
167
+ * register calls on purpose: commander copies inherited settings onto a
168
+ * subcommand when it is created, so settings applied after registration would
169
+ * leave every subcommand writing to the real stdout — which for an MCP server
170
+ * is the protocol stream.
101
171
  */
102
- function readProgram(sinks, authoring = false) {
172
+ function readProgram(sinks, registrars) {
103
173
  const program = new Command();
104
174
  program.name("loam");
105
175
  program.exitOverride();
@@ -107,49 +177,34 @@ function readProgram(sinks, authoring = false) {
107
177
  writeOut: (text) => sinks.stdout.push(text),
108
178
  writeErr: (text) => sinks.stderr.push(text),
109
179
  });
110
- registerValidate(program);
111
- registerStatus(program);
112
- registerList(program);
113
- registerShow(program);
114
- registerDelta(program);
115
- registerDiff(program);
116
- registerExplore(program);
117
- registerDependencies(program);
118
- registerDoctor(program);
119
- registerContext(program);
120
- registerGate(program);
121
- registerExplain(program);
122
- // The two that read no config at all. They are here for the same reason they
123
- // are in MCP_TOOLS: an agent meets `instructions` and `steps` BEFORE the
124
- // repository is wired, and every generated skill's first line points at
125
- // `loam instructions <workflow>`. This list and MCP_TOOLS are two copies of
126
- // one roster, and nothing counted them against each other until a tool
127
- // advertised in `tools/list` and absent here answered a `tools/call` with
128
- // `unknown command 'instructions'` — an envelope that reads as the caller's
129
- // mistake. `test/mcp-serve.test.ts` now calls every advertised tool.
130
- registerInstructions(program);
131
- registerSteps(program);
132
- if (authoring) {
133
- registerNew(program);
134
- registerRebase(program);
135
- registerGherkin(program);
136
- registerArchive(program);
137
- }
180
+ for (const register of registrars)
181
+ register(program);
138
182
  return program;
139
183
  }
184
+ /**
185
+ * The runner `loam mcp` serves `tools` with. Throws HERE, at construction, when
186
+ * the table names a command with no registrar (`resolveRegistrars`); the
187
+ * runner it returns never throws, and builds each call's program from the
188
+ * registrars resolved once — so no call can fail for the sake of a tool it
189
+ * did not name.
190
+ */
191
+ export function toolRunner(tools = MCP_TOOLS) {
192
+ const registrars = resolveRegistrars(tools);
193
+ return (argv) => runToolArgv(argv, registrars);
194
+ }
140
195
  /**
141
196
  * Run one tool argv. Never throws: whatever goes wrong becomes an envelope in
142
197
  * `stdout` with `code` 1, so the serve loop's answer to THIS call is the only
143
198
  * thing affected — one bad call must never take the server down, and a
144
199
  * memoised rejection cannot exist because nothing here outlives the call.
145
200
  */
146
- export async function runToolArgv(argv, authoring = false) {
201
+ async function runToolArgv(argv, registrars) {
147
202
  const sinks = { stdout: [], stderr: [] };
148
203
  const previousExitCode = process.exitCode;
149
204
  const restoreConsole = captureConsole(sinks);
150
205
  process.exitCode = undefined;
151
206
  try {
152
- await readProgram(sinks, authoring).parseAsync(["node", "loam", ...argv]);
207
+ await readProgram(sinks, registrars).parseAsync(["node", "loam", ...argv]);
153
208
  }
154
209
  catch (err) {
155
210
  if (err instanceof CommanderError) {
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../../src/commands/mcp/dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * In-process dispatch for one MCP tool call: build a fresh commander program\n * holding only the commands the selected tool table exposes, run the argv through\n * it, and hand back what the CLI would have printed.\n *\n * This is the proven harness pattern (test/helpers/harness.ts runLoamNow)\n * moved to the commands layer, minus the chdir: the server's own cwd IS the\n * workdir, so every call resolves loam.json exactly as the CLI would in that\n * directory — the server serves ONE repository, the one it started in, and\n * never changes directory. In-process rather than a child per call because a\n * child re-pays LikeC4/Langium startup on every validate, and because in dev\n * there is no built binary to exec.\n *\n * The capture is process-global (console, exitCode), which is safe under\n * exactly one condition the serve loop guarantees: calls are strictly\n * sequential — each one is awaited to completion before the next frame is\n * read — and `parseAsync` resolves only after the action has fully run (the\n * entire test suite rests on that same guarantee). Nothing here may be\n * module-level state: two serve sessions in one process (the tests do this)\n * must not see each other.\n *\n * NOTE for test/agents.test.ts's scan: registration happens through the\n * imported register functions; this module must never contain the literal\n * dot-command-paren call, comments included, or the CLI surface count breaks.\n */\nimport { Command, CommanderError } from \"commander\";\nimport { format } from \"node:util\";\nimport { emitJsonError } from \"../../core/envelope/json.js\";\nimport { registerValidate } from \"../validate/validate.js\";\nimport { registerStatus } from \"../status/status.js\";\nimport { registerList } from \"../list/list.js\";\nimport { registerShow } from \"../show/show.js\";\nimport { registerDelta } from \"../delta/delta.js\";\nimport { registerDiff } from \"../diff/diff.js\";\nimport { registerExplore } from \"../explore.js\";\nimport { registerDependencies } from \"../dependencies.js\";\nimport { registerDoctor } from \"../doctor.js\";\nimport { registerContext } from \"../context/context.js\";\nimport { registerGate } from \"../gate/gate.js\";\nimport { registerExplain } from \"../explain/explain.js\";\nimport { registerInstructions } from \"../instructions.js\";\nimport { registerSteps } from \"../steps/steps.js\";\nimport { registerNew } from \"../new/new.js\";\nimport { registerRebase } from \"../rebase/rebase.js\";\nimport { registerGherkin } from \"../gherkin/gherkin.js\";\nimport { registerArchive } from \"../archive/archive.js\";\n\nexport interface DispatchResult {\n readonly stdout: string;\n readonly stderr: string;\n readonly code: number;\n}\n\n/** Console sinks for one dispatch — a value, not module state, so calls cannot share buffers. */\nexport interface Sinks {\n readonly stdout: string[];\n readonly stderr: string[];\n}\n\n/**\n * Every console method Node's Console writes to STDOUT — the protocol stream,\n * where one stray line corrupts the transport. Hooking only `log` was the\n * reviewed defect here: a `console.info` in any command, or in a dependency a\n * command loads, would have written straight into the client's frame stream\n * with every suite green. `warn`/`error` go to stderr, captured for the\n * server log rather than the wire.\n */\nconst STDOUT_METHODS = [\n \"log\", \"info\", \"debug\", \"dir\", \"dirxml\", \"table\",\n \"group\", \"groupCollapsed\", \"groupEnd\", \"count\", \"countReset\", \"timeLog\", \"timeEnd\",\n] as const;\nconst STDERR_METHODS = [\"warn\", \"error\"] as const;\n\n/**\n * Replace the console methods named above with sink writers; the returned\n * function restores every original. Captured output is `format(...)`ed, so\n * `table`/`group` lose their layout — acceptable on purpose: the invariant\n * bought here is that their BYTES stay off the protocol stream, not that\n * their rendering survives.\n */\nexport function captureConsole(sinks: Sinks): () => void {\n const target = console as unknown as Record<string, unknown>;\n const writer = (sink: string[]) => (...parts: unknown[]): void => {\n sink.push(format(...parts));\n };\n const toStdout = writer(sinks.stdout);\n const toStderr = writer(sinks.stderr);\n const original = new Map<string, unknown>();\n for (const [names, replacement] of [\n [STDOUT_METHODS, toStdout],\n [STDERR_METHODS, toStderr],\n ] as const) {\n for (const name of names) {\n // A method absent on this Node build has nothing to capture — and\n // nothing to restore, so it must not enter the map either.\n if (typeof target[name] !== \"function\") continue;\n original.set(name, target[name]);\n target[name] = replacement;\n }\n }\n return () => {\n for (const [name, method] of original) target[name] = method;\n };\n}\n\n/** Commander output codes whose printed output is the point, not a usage failure. */\nconst PASS_THROUGH = new Set([\"commander.help\", \"commander.helpDisplayed\", \"commander.version\"]);\n\n/**\n * The scoped program, output routed into `sinks`. configureOutput and\n * exitOverride are set BEFORE the register calls on purpose: commander copies\n * inherited settings onto a subcommand when it is created, so settings applied\n * after registration would leave every subcommand writing to the real stdout —\n * which for an MCP server is the protocol stream.\n */\nfunction readProgram(sinks: Sinks, authoring = false): Command {\n const program = new Command();\n program.name(\"loam\");\n program.exitOverride();\n program.configureOutput({\n writeOut: (text) => sinks.stdout.push(text),\n writeErr: (text) => sinks.stderr.push(text),\n });\n registerValidate(program);\n registerStatus(program);\n registerList(program);\n registerShow(program);\n registerDelta(program);\n registerDiff(program);\n registerExplore(program);\n registerDependencies(program);\n registerDoctor(program);\n registerContext(program);\n registerGate(program);\n registerExplain(program);\n // The two that read no config at all. They are here for the same reason they\n // are in MCP_TOOLS: an agent meets `instructions` and `steps` BEFORE the\n // repository is wired, and every generated skill's first line points at\n // `loam instructions <workflow>`. This list and MCP_TOOLS are two copies of\n // one roster, and nothing counted them against each other until a tool\n // advertised in `tools/list` and absent here answered a `tools/call` with\n // `unknown command 'instructions'` — an envelope that reads as the caller's\n // mistake. `test/mcp-serve.test.ts` now calls every advertised tool.\n registerInstructions(program);\n registerSteps(program);\n if (authoring) {\n registerNew(program);\n registerRebase(program);\n registerGherkin(program);\n registerArchive(program);\n }\n return program;\n}\n\n/**\n * Run one tool argv. Never throws: whatever goes wrong becomes an envelope in\n * `stdout` with `code` 1, so the serve loop's answer to THIS call is the only\n * thing affected — one bad call must never take the server down, and a\n * memoised rejection cannot exist because nothing here outlives the call.\n */\nexport async function runToolArgv(\n argv: readonly string[],\n authoring = false,\n): Promise<DispatchResult> {\n const sinks: Sinks = { stdout: [], stderr: [] };\n const previousExitCode = process.exitCode;\n const restoreConsole = captureConsole(sinks);\n process.exitCode = undefined;\n try {\n await readProgram(sinks, authoring).parseAsync([\"node\", \"loam\", ...argv]);\n } catch (err) {\n if (err instanceof CommanderError) {\n if (PASS_THROUGH.has(err.code)) {\n // Unreachable through the tool schemas (the argv builder refuses every\n // '-'-prefixed string, so --help/--version cannot arrive), kept for the\n // day it is not: the output already sits in the sinks; keep its code.\n if (process.exitCode === undefined) process.exitCode = err.exitCode;\n } else {\n // The same envelope cli.ts's catch emits for a usage error, so an MCP\n // caller still receives machine-readable JSON, not a bare exception.\n emitJsonError(\"invalid-option\", err.message.replace(/^error: /, \"\"));\n }\n } else {\n // An unexpected throw from a command action: the CLI's own catch-all\n // (`internal`) — the one code with no stable meaning, carried so the\n // caller gets an envelope rather than a dead call.\n emitJsonError(\"internal\", err instanceof Error ? err.message : String(err));\n }\n } finally {\n restoreConsole();\n }\n const code = typeof process.exitCode === \"number\" ? process.exitCode : 0;\n process.exitCode = previousExitCode;\n return { stdout: sinks.stdout.join(\"\\n\"), stderr: sinks.stderr.join(\"\\n\"), code };\n}\n"]}
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../../src/commands/mcp/dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * In-process dispatch for one MCP tool call: build a fresh commander program\n * holding only the commands the selected tool table exposes, run the argv through\n * it, and hand back what the CLI would have printed.\n *\n * This is the proven harness pattern (test/helpers/harness.ts runLoamNow)\n * moved to the commands layer, minus the chdir: the server's own cwd IS the\n * workdir, so every call resolves loam.json exactly as the CLI would in that\n * directory — the server serves ONE repository, the one it started in, and\n * never changes directory. In-process rather than a child per call because a\n * child re-pays LikeC4/Langium startup on every validate, and because in dev\n * there is no built binary to exec.\n *\n * The capture is process-global (console, exitCode), which is safe under\n * exactly one condition the serve loop guarantees: calls are strictly\n * sequential — each one is awaited to completion before the next frame is\n * read — and `parseAsync` resolves only after the action has fully run (the\n * entire test suite rests on that same guarantee). Nothing here may be\n * module-level state: two serve sessions in one process (the tests do this)\n * must not see each other.\n *\n * NOTE for test/agents.test.ts's scan: registration happens through the\n * imported register functions; this module must never contain the literal\n * dot-command-paren call, comments included, or the CLI surface count breaks.\n */\nimport { Command, CommanderError } from \"commander\";\nimport { format } from \"node:util\";\nimport { emitJsonError } from \"../../core/envelope/json.js\";\nimport { registerValidate } from \"../validate/validate.js\";\nimport { registerStatus } from \"../status/status.js\";\nimport { registerList } from \"../list/list.js\";\nimport { registerShow } from \"../show/show.js\";\nimport { registerDelta } from \"../delta/delta.js\";\nimport { registerTasks } from \"../tasks/tasks.js\";\nimport { registerDiff } from \"../diff/diff.js\";\nimport { registerExplore } from \"../explore.js\";\nimport { registerDependencies } from \"../dependencies.js\";\nimport { registerDoctor } from \"../doctor.js\";\nimport { registerContext } from \"../context/context.js\";\nimport { registerAdopt } from \"../adopt/adopt.js\";\nimport { registerGate } from \"../gate/gate.js\";\nimport { registerExplain } from \"../explain/explain.js\";\nimport { registerInstructions } from \"../instructions.js\";\nimport { registerSteps } from \"../steps/steps.js\";\nimport { registerNew } from \"../new/new.js\";\nimport { registerRebase } from \"../rebase/rebase.js\";\nimport { registerGherkin } from \"../gherkin/gherkin.js\";\nimport { registerArchive } from \"../archive/archive.js\";\nimport type { ToolRunOutcome } from \"../../core/mcp/protocol.js\";\nimport type { ToolRunner } from \"./serve.js\";\nimport { MCP_TOOLS, type McpTool } from \"../../core/mcp/tools.js\";\n\n/** Console sinks for one dispatch — a value, not module state, so calls cannot share buffers. */\nexport interface Sinks {\n readonly stdout: string[];\n readonly stderr: string[];\n}\n\n/**\n * Every console method Node's Console writes to STDOUT — the protocol stream,\n * where one stray line corrupts the transport. Hooking only `log` was the\n * reviewed defect here: a `console.info` in any command, or in a dependency a\n * command loads, would have written straight into the client's frame stream\n * with every suite green. `warn`/`error` go to stderr, captured for the\n * server log rather than the wire.\n */\nconst STDOUT_METHODS = [\n \"log\", \"info\", \"debug\", \"dir\", \"dirxml\", \"table\",\n \"group\", \"groupCollapsed\", \"groupEnd\", \"count\", \"countReset\", \"timeLog\", \"timeEnd\",\n] as const;\nconst STDERR_METHODS = [\"warn\", \"error\"] as const;\n\n/**\n * Replace the console methods named above with sink writers; the returned\n * function restores every original. Captured output is `format(...)`ed, so\n * `table`/`group` lose their layout — acceptable on purpose: the invariant\n * bought here is that their BYTES stay off the protocol stream, not that\n * their rendering survives.\n */\nexport function captureConsole(sinks: Sinks): () => void {\n const target = console as unknown as Record<string, unknown>;\n const writer = (sink: string[]) => (...parts: unknown[]): void => {\n sink.push(format(...parts));\n };\n const toStdout = writer(sinks.stdout);\n const toStderr = writer(sinks.stderr);\n const original = new Map<string, unknown>();\n for (const [names, replacement] of [\n [STDOUT_METHODS, toStdout],\n [STDERR_METHODS, toStderr],\n ] as const) {\n for (const name of names) {\n // A method absent on this Node build has nothing to capture — and\n // nothing to restore, so it must not enter the map either.\n if (typeof target[name] !== \"function\") continue;\n original.set(name, target[name]);\n target[name] = replacement;\n }\n }\n return () => {\n for (const [name, method] of original) target[name] = method;\n };\n}\n\n/** Commander output codes whose printed output is the point, not a usage failure. */\nconst PASS_THROUGH = new Set([\"commander.help\", \"commander.helpDisplayed\", \"commander.version\"]);\n\n/** One command's registration onto a program — the shape every `registerX` export has. */\ntype Registrar = (program: Command) => void;\n\n/**\n * Every command a tool table may name, keyed by the `command` its tool carries.\n * A LOOKUP, not a roster: which of these a call's program registers is decided\n * by the active tool table alone (`toolRunner`), so the authoring writers —\n * new, rebase, gherkin, archive — exist in it only when `--author` put their\n * tools in the table, and the table the read-only hints describe is the one\n * place that gate is written.\n *\n * `instructions` and `steps` read no config at all, and are in MCP_TOOLS\n * because an agent meets them BEFORE the repository is wired: every generated\n * skill's first line points at `loam instructions <workflow>`. They are why\n * registration follows the table. This list and MCP_TOOLS used to be two\n * copies of one roster, and nothing counted them against each other until a\n * tool advertised in `tools/list` and absent here answered a `tools/call` with\n * `unknown command 'instructions'` — an envelope that reads as the caller's\n * mistake. `test/mcp-serve.test.ts` calls every advertised tool, and checks\n * this lookup against both tables directly.\n */\nconst REGISTRARS = new Map<string, Registrar>([\n [\"validate\", registerValidate],\n [\"status\", registerStatus],\n [\"list\", registerList],\n [\"show\", registerShow],\n [\"delta\", registerDelta],\n [\"tasks\", registerTasks],\n [\"diff\", registerDiff],\n [\"explore\", registerExplore],\n [\"dependencies\", registerDependencies],\n [\"doctor\", registerDoctor],\n [\"context\", registerContext],\n [\"adopt\", registerAdopt],\n [\"gate\", registerGate],\n [\"explain\", registerExplain],\n [\"instructions\", registerInstructions],\n [\"steps\", registerSteps],\n [\"new\", registerNew],\n [\"rebase\", registerRebase],\n [\"gherkin\", registerGherkin],\n [\"archive\", registerArchive],\n]);\n\n/**\n * Resolve every command `tools` name to its registrar, or throw naming each\n * one that has none. This is the server's STARTUP check, and it runs once per\n * table: `toolRunner` calls it before the first frame is read, so a tool\n * advertised with no registrar — the roster drift above, arriving again —\n * stops `loam mcp` at launch, through cli.ts's catch like any other throw,\n * before a client has been promised anything. It used to run inside every call's program build, where one\n * missing entry turned EVERY `tools/call` into an `internal` envelope naming\n * it, the tools that were wired correctly included: a call's outcome\n * depended on a tool it never named.\n */\nexport function resolveRegistrars(tools: readonly McpTool[]): readonly Registrar[] {\n const resolved: Registrar[] = [];\n const missing: string[] = [];\n for (const command of new Set(tools.map((tool) => tool.command))) {\n const register = REGISTRARS.get(command);\n if (register === undefined) missing.push(`'${command}'`);\n else resolved.push(register);\n }\n if (missing.length > 0) {\n throw new Error(`loam mcp: no command registered for MCP tool command ${missing.join(\", \")} — add it to REGISTRARS in src/commands/mcp/dispatch.ts`);\n }\n return resolved;\n}\n\n/**\n * The scoped program, output routed into `sinks`, holding exactly the commands\n * the resolved table names. configureOutput and exitOverride are set BEFORE the\n * register calls on purpose: commander copies inherited settings onto a\n * subcommand when it is created, so settings applied after registration would\n * leave every subcommand writing to the real stdout — which for an MCP server\n * is the protocol stream.\n */\nfunction readProgram(sinks: Sinks, registrars: readonly Registrar[]): Command {\n const program = new Command();\n program.name(\"loam\");\n program.exitOverride();\n program.configureOutput({\n writeOut: (text) => sinks.stdout.push(text),\n writeErr: (text) => sinks.stderr.push(text),\n });\n for (const register of registrars) register(program);\n return program;\n}\n\n/**\n * The runner `loam mcp` serves `tools` with. Throws HERE, at construction, when\n * the table names a command with no registrar (`resolveRegistrars`); the\n * runner it returns never throws, and builds each call's program from the\n * registrars resolved once — so no call can fail for the sake of a tool it\n * did not name.\n */\nexport function toolRunner(tools: readonly McpTool[] = MCP_TOOLS): ToolRunner {\n const registrars = resolveRegistrars(tools);\n return (argv) => runToolArgv(argv, registrars);\n}\n\n/**\n * Run one tool argv. Never throws: whatever goes wrong becomes an envelope in\n * `stdout` with `code` 1, so the serve loop's answer to THIS call is the only\n * thing affected — one bad call must never take the server down, and a\n * memoised rejection cannot exist because nothing here outlives the call.\n */\nasync function runToolArgv(argv: readonly string[], registrars: readonly Registrar[]): Promise<ToolRunOutcome> {\n const sinks: Sinks = { stdout: [], stderr: [] };\n const previousExitCode = process.exitCode;\n const restoreConsole = captureConsole(sinks);\n process.exitCode = undefined;\n try {\n await readProgram(sinks, registrars).parseAsync([\"node\", \"loam\", ...argv]);\n } catch (err) {\n if (err instanceof CommanderError) {\n if (PASS_THROUGH.has(err.code)) {\n // Unreachable through the tool schemas (the argv builder refuses every\n // '-'-prefixed string, so --help/--version cannot arrive), kept for the\n // day it is not: the output already sits in the sinks; keep its code.\n if (process.exitCode === undefined) process.exitCode = err.exitCode;\n } else {\n // The same envelope cli.ts's catch emits for a usage error, so an MCP\n // caller still receives machine-readable JSON, not a bare exception.\n emitJsonError(\"invalid-option\", err.message.replace(/^error: /, \"\"));\n }\n } else {\n // An unexpected throw from a command action: the CLI's own catch-all\n // (`internal`) — the one code with no stable meaning, carried so the\n // caller gets an envelope rather than a dead call.\n emitJsonError(\"internal\", err instanceof Error ? err.message : String(err));\n }\n } finally {\n restoreConsole();\n }\n const code = typeof process.exitCode === \"number\" ? process.exitCode : 0;\n process.exitCode = previousExitCode;\n return { stdout: sinks.stdout.join(\"\\n\"), stderr: sinks.stderr.join(\"\\n\"), code };\n}\n"]}