@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
@@ -19,6 +19,12 @@
19
19
  * run that has not happened yet), so an MCP-only host that could not reach
20
20
  * them could not fetch the protocol its own generated skill points at.
21
21
  *
22
+ * `adopt` is here for the same reason one step later: it writes nothing — it
23
+ * emits the brief an agent then writes a baseline from — and the loam-adopt
24
+ * protocol an MCP-only host fetches through `loam_instructions` opens by
25
+ * running it. Without the tool that host could read the protocol and never
26
+ * receive the brief the protocol is about.
27
+ *
22
28
  * Every table entry mirrors a real registration in `src/commands/` — same
23
29
  * positionals, same flags, same spellings — so the contract an MCP client
24
30
  * sees is the CLI contract, not a second one. `--json` is appended by
@@ -102,9 +108,10 @@ export const MCP_TOOLS = [
102
108
  name: "loam_list",
103
109
  command: "list",
104
110
  description: "List the services and features in the docs repo. " +
105
- "CLI equivalent: loam list [section] [--archived] [--needs-work] [--review-order] [--subsystem <name>] [--owners <path>] --json",
111
+ "CLI equivalent: loam list [section] [name] [--archived] [--needs-work] [--review-order] [--subsystem <name>] [--owners <path>] --json",
106
112
  positionals: [
107
- { property: "section", required: false, variadic: false, description: "services | features | capabilities (default: services + features)" },
113
+ { property: "section", required: false, variadic: false, description: "services | features | capabilities | glossary | suites (default: services + features)" },
114
+ { property: "name", required: false, variadic: false, description: "suites only: the one suite to show" },
108
115
  ],
109
116
  flags: [
110
117
  { property: "archived", flag: "--archived", kind: "boolean", description: "include archived features" },
@@ -136,6 +143,19 @@ export const MCP_TOOLS = [
136
143
  ],
137
144
  flags: [{ ...SERVICE_FLAG, description: "service to project onto (defaults to the configured service)" }],
138
145
  },
146
+ {
147
+ name: "loam_tasks",
148
+ command: "tasks",
149
+ description: "What is still open on a feature, at which step, and what closes each unit — a read-only projection that writes nothing. " +
150
+ "CLI equivalent: loam tasks <featureId> [--service <id>] [--next] --json",
151
+ positionals: [
152
+ { property: "featureId", required: true, variadic: false, description: "feature id, e.g. FEAT-101" },
153
+ ],
154
+ flags: [
155
+ { ...SERVICE_FLAG, description: "narrow the packet to the units one service owns (a lens: no binding needed)" },
156
+ { property: "next", flag: "--next", kind: "boolean", description: "the frontier head alone — the single leaf to hand to a worker" },
157
+ ],
158
+ },
139
159
  {
140
160
  name: "loam_explore",
141
161
  command: "explore",
@@ -196,14 +216,27 @@ export const MCP_TOOLS = [
196
216
  { property: "feature", flag: "--feature", kind: "string", description: "narrow the in-flight section to this one feature" },
197
217
  ],
198
218
  },
219
+ {
220
+ name: "loam_adopt",
221
+ command: "adopt",
222
+ description: "Brief an agent to write one service's baseline docs from its code, and say what will be checked — writes nothing. " +
223
+ "CLI equivalent: loam adopt [--service <id>] [--subsystem <name>] [--targets] --json",
224
+ positionals: [],
225
+ flags: [
226
+ { ...SERVICE_FLAG, description: "service to adopt (defaults to the configured service)" },
227
+ { property: "subsystem", flag: "--subsystem", kind: "string", description: "brief a NEW service's baseline into this subsystem instead of the services/ root" },
228
+ { property: "targets", flag: "--targets", kind: "boolean", description: "only what varies by service; a `full` field names the run that carries the walk, the checks and the unchecked list" },
229
+ ],
230
+ },
199
231
  {
200
232
  name: "loam_gate",
201
233
  command: "gate",
202
234
  description: "Can this service deploy? A read-only query over recorded evidence — advisory by default. " +
203
- "CLI equivalent: loam gate [--service <id>] [--strict] --json",
235
+ "CLI equivalent: loam gate [--service <id>] [--since <ref>] [--strict] --json",
204
236
  positionals: [],
205
237
  flags: [
206
238
  { ...SERVICE_FLAG, description: "the service being deployed (defaults to the configured service)" },
239
+ { property: "since", flag: "--since", kind: "string", description: "also grade features archived after this docs-repo git ref; without it only in-flight features are read" },
207
240
  { property: "strict", flag: "--strict", kind: "boolean", description: "exit 1 on any warning too; the payload does not change" },
208
241
  ],
209
242
  },
@@ -243,9 +276,9 @@ export const MCP_TOOLS = [
243
276
  command: "instructions",
244
277
  description: "Print a workflow, support protocol or reference page, version-matched to this binary; reads no config and no docs repo, so it answers " +
245
278
  "before the repository is wired. Ask for loam-check DELIBERATELY: it is by far the largest protocol " +
246
- "(its per-code fix tables are ~83 KB of the ~84 KB it prints), and noFixTables true returns the narrowed page " +
279
+ "(almost all of what it prints is its per-code fix tables), and noFixTables true returns the narrowed page " +
247
280
  "— every paragraph that introduces a table, none of the rows — after which loam_explain answers any one code " +
248
- "a run reports in under 500 bytes. " +
281
+ "a run reports, on its own. " +
249
282
  "CLI equivalent: loam instructions [workflow] [args...] [--no-fix-tables] --json",
250
283
  positionals: [
251
284
  {
@@ -280,35 +313,12 @@ export const MCP_TOOLS = [
280
313
  export function toolByName(name, tools = MCP_TOOLS) {
281
314
  return tools.find((tool) => tool.name === name);
282
315
  }
283
- /** The MCP inputSchema for one tool: a closed object, so a typo'd argument refuses instead of vanishing. */
284
- export function toInputSchema(tool) {
285
- const properties = {};
286
- for (const positional of tool.positionals) {
287
- properties[positional.property] = positional.variadic
288
- ? { type: "array", items: { type: "string" }, description: positional.description }
289
- : { type: "string", description: positional.description };
290
- }
291
- for (const flag of tool.flags) {
292
- properties[flag.property] =
293
- flag.kind === "boolean"
294
- ? { type: "boolean", description: flag.description }
295
- : flag.kind === "strings"
296
- ? { type: "array", items: { type: "string" }, description: flag.description }
297
- : { type: "string", description: flag.description };
298
- }
299
- const required = [
300
- ...tool.positionals.filter((p) => p.required).map((p) => p.property),
301
- ...tool.flags.filter((f) => f.required === true).map((f) => f.property),
302
- ];
303
- return {
304
- type: "object",
305
- properties,
306
- ...(required.length > 0 ? { required } : {}),
307
- additionalProperties: false,
308
- };
309
- }
310
316
  // How a tool call's JSON arguments become an argv — including the refusal of
311
317
  // any string a shell-less argv would still read as a flag — lives in
312
318
  // `./argv.ts`: the table above says WHAT the tools are, that module says how
313
- // a call crosses into the CLI's own parsing.
319
+ // a call crosses into the CLI's own parsing. The inputSchema each tool is
320
+ // advertised with (`toInputSchema`) lives there too: it DECLARES the argument
321
+ // contract `toArgv` ENFORCES — the closed object, the required set, the order
322
+ // the positionals are read in — and the two halves change together or a
323
+ // client validates against one rule and is refused by another.
314
324
  //# sourceMappingURL=tools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/core/mcp/tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The curated MCP tool table: which loam commands an MCP client can reach,\n * and how a JSON argument object becomes an argv the CLI already trusts.\n *\n * This base table is read-only. Opt-in writers live in `./author-tools.ts`;\n * vouch and a committing archive remain absent: an MCP caller is definitionally\n * an agent, `loam vouch` is a HUMAN act (the CLI refuses unattended runs with\n * vouch-unattended), and exposing it here would rebuild the hole the generated\n * skill files' allowlist was narrowed to close. verify is excluded with them: its read form and its\n * attesting `--record`/`--results`/`--contract-results` form are one command,\n * and a facade that exposes \"only the read half\" of a writer is one\n * flag-mapping bug away from exposing the writer.\n *\n * The inclusion of the two commands that read no CONFIG — `explain` and\n * `instructions` — is equally deliberate, and is recorded here because the\n * gap is otherwise re-derivable as an oversight: an agent meets both BEFORE\n * the repository is wired (every generated skill's first line is `loam\n * instructions <workflow>`, and `loam explain <code>` answers a code from a\n * run that has not happened yet), so an MCP-only host that could not reach\n * them could not fetch the protocol its own generated skill points at.\n *\n * Every table entry mirrors a real registration in `src/commands/` — same\n * positionals, same flags, same spellings — so the contract an MCP client\n * sees is the CLI contract, not a second one. `--json` is appended by\n * `toArgv` unconditionally: the tool result IS the machine envelope.\n */\n\nimport { TERMS } from \"../explain/terms.js\";\n\n/**\n * The concept-term names, from the registry that owns them — `loam_explain`'s\n * schema description must not be a hand copy that silently trails TERMS.\n */\nconst TERM_LIST = TERMS.map(({ term }) => term).join(\", \");\n\n/** A positional argument, in declaration order. A variadic one is always last. */\nexport interface ToolPositional {\n readonly property: string;\n readonly required: boolean;\n /** True when the JSON argument is an array of strings spread as trailing positionals. */\n readonly variadic: boolean;\n readonly description: string;\n}\n\n/** One CLI flag: `kind` decides both the JSON Schema type and the argv shape. */\nexport interface ToolFlag {\n readonly property: string;\n readonly flag: string;\n readonly kind: \"string\" | \"boolean\" | \"strings\";\n /**\n * Set when the CLI declares the option with `requiredOption` — the schema\n * then requires the property and the argv builder refuses its absence, so\n * a client learns the requirement from the schema instead of from an\n * invalid-option envelope after the call. The shape test asserts this\n * mirrors commander's own `mandatory`, both ways.\n */\n readonly required?: true;\n readonly description: string;\n}\n\nexport interface McpTool {\n readonly name: string;\n readonly command: string;\n readonly description: string;\n readonly positionals: readonly ToolPositional[];\n readonly flags: readonly ToolFlag[];\n /** Arguments enforced by the server rather than exposed to the caller. */\n readonly fixed?: readonly string[];\n readonly annotations?: Readonly<Record<string, boolean>>;\n}\n\nconst SERVICE_FLAG = {\n property: \"service\",\n flag: \"--service\",\n kind: \"string\",\n description: \"service id (defaults to the one loam.json configures)\",\n} as const;\n\n/**\n * The tool annotations EVERY entry in this table carries — one shared literal,\n * because the table is read-only by construction and a per-tool copy is a\n * place for one of them to drift into claiming something else.\n *\n * `readOnlyHint` is the whole point: without it a host has no machine signal\n * that `loam_validate` or `loam_context` is safe, so a call that only reads\n * files falls into the same approval prompt as a mutating tool. Read-only\n * hints are what let a host auto-approve, and the reason the writers are\n * excluded from the table above is precisely so this hint can be true of\n * everything left. `openWorldHint: false` states the other half a host acts\n * on: loam reaches no network and no service — it reads the files in the\n * directory the server started in, and nothing else.\n *\n * `idempotentHint` and `destructiveHint` are deliberately ABSENT rather than\n * spelled `true`/`false`: MCP defines both as meaningful only when\n * `readOnlyHint` is false, so shipping them would be noise a reviewer has to\n * justify before deciding they mean nothing here.\n *\n * `outputSchema` lives in `./protocol.ts`, beside `toolReply`: the declaration\n * and the fallback structured envelope have to change together or a client is\n * promised structured content one branch can omit.\n */\nexport const READ_ONLY_ANNOTATIONS = { readOnlyHint: true, openWorldHint: false } as const;\n\n/**\n * The `loam_` prefix on every name: multi-server MCP hosts flatten tool names\n * into one namespace, and a bare `validate` collides with anybody else's.\n * Each description names the CLI command it mirrors, so the equivalence is\n * stated where the client reads it.\n */\nexport const MCP_TOOLS: readonly McpTool[] = [\n {\n name: \"loam_validate\",\n command: \"validate\",\n description:\n \"Validate a service (C4 + requirement coverage), a feature (delta + coverage), or the whole fleet. \" +\n \"CLI equivalent: loam validate [target] [--service <id>] [--feature <id>] [--all] [--strict] [--errors-only] --json\",\n positionals: [\n {\n property: \"target\",\n required: false,\n variadic: false,\n description: \"service or feature id (a feature wins when both match; service/feature force the reading)\",\n },\n ],\n flags: [\n { ...SERVICE_FLAG, description: \"service to validate (defaults to the configured service)\" },\n { property: \"feature\", flag: \"--feature\", kind: \"string\", description: \"validate a feature delta instead of a service\" },\n { property: \"all\", flag: \"--all\", kind: \"boolean\", description: \"validate every service and every active feature\" },\n { property: \"strict\", flag: \"--strict\", kind: \"boolean\", description: \"exit 1 on any warning too; the payload does not change\" },\n { property: \"errorsOnly\", flag: \"--errors-only\", kind: \"boolean\", description: \"print only errors and warnings; the payload does not change\" },\n ],\n },\n {\n name: \"loam_status\",\n command: \"status\",\n description:\n \"Where the work stands and what to do next, derived from the files. \" +\n \"CLI equivalent: loam status [feature] [--service <id>] --json\",\n positionals: [\n { property: \"feature\", required: false, variadic: false, description: \"feature id or directory name; omit for the whole repository\" },\n ],\n flags: [{ ...SERVICE_FLAG, description: \"narrow the per-service view to one service\" }],\n },\n {\n name: \"loam_list\",\n command: \"list\",\n description:\n \"List the services and features in the docs repo. \" +\n \"CLI equivalent: loam list [section] [--archived] [--needs-work] [--review-order] [--subsystem <name>] [--owners <path>] --json\",\n positionals: [\n { property: \"section\", required: false, variadic: false, description: \"services | features | capabilities (default: services + features)\" },\n ],\n flags: [\n { property: \"archived\", flag: \"--archived\", kind: \"boolean\", description: \"include archived features\" },\n { property: \"needsWork\", flag: \"--needs-work\", kind: \"boolean\", description: \"services only: every service below vouched, with what it is missing\" },\n { property: \"reviewOrder\", flag: \"--review-order\", kind: \"boolean\", description: \"with needsWork: order the worklist by fan-in, highest blast radius first\" },\n { property: \"subsystem\", flag: \"--subsystem\", kind: \"string\", description: \"services only: limit the listing to services filed under this subsystem, at any depth ('unfiled' selects the ones filed under none)\" },\n { property: \"owners\", flag: \"--owners\", kind: \"string\", description: \"services only: group the listing by owning team from this CODEOWNERS file (directory-pattern rules only; unsupported rules are listed as skipped, never guessed)\" },\n ],\n },\n {\n name: \"loam_show\",\n command: \"show\",\n description:\n \"Everything loam knows about a service or a feature. \" +\n \"CLI equivalent: loam show <target> [--type service|feature] --json\",\n positionals: [\n { property: \"target\", required: true, variadic: false, description: \"service id or feature id\" },\n ],\n flags: [\n { property: \"type\", flag: \"--type\", kind: \"string\", description: \"force the reading: service | feature\" },\n ],\n },\n {\n name: \"loam_delta\",\n command: \"delta\",\n description:\n \"Project a feature onto a service: why + requirement delta + C4 changes. \" +\n \"CLI equivalent: loam delta <featureId> [--service <id>] --json\",\n positionals: [\n { property: \"featureId\", required: true, variadic: false, description: \"feature id, e.g. FEAT-101\" },\n ],\n flags: [{ ...SERVICE_FLAG, description: \"service to project onto (defaults to the configured service)\" }],\n },\n {\n name: \"loam_explore\",\n command: \"explore\",\n description:\n \"Read the fleet around a change nobody has written down yet — writes nothing. \" +\n \"CLI equivalent: loam explore [service...] [--op <id>] [--capability <id>] [--as <FEAT>] --json\",\n positionals: [\n { property: \"services\", required: false, variadic: true, description: \"service ids the change starts from\" },\n ],\n flags: [\n { property: \"op\", flag: \"--op\", kind: \"strings\", description: \"seed from an operation instead of a service; repeatable\" },\n { property: \"capability\", flag: \"--capability\", kind: \"strings\", description: \"seed from a declared capability's realizing services; repeatable\" },\n { property: \"as\", flag: \"--as\", kind: \"string\", description: \"feature id for the suggested command line\" },\n ],\n },\n {\n name: \"loam_dependencies\",\n command: \"dependencies\",\n description:\n \"Dependencies and conflicts derived from active feature artifacts. \" +\n \"CLI equivalent: loam dependencies [featureId] --json\",\n positionals: [\n { property: \"featureId\", required: false, variadic: false, description: \"active feature id; omit for the whole in-flight graph\" },\n ],\n flags: [],\n },\n {\n name: \"loam_diff\",\n command: \"diff\",\n description:\n \"Semantic diff of the living docs against a base git ref of the docs repo — removals graded against their current consumers; read-only. \" +\n \"CLI equivalent: loam diff --base <ref> --json\",\n positionals: [],\n flags: [\n {\n property: \"base\",\n flag: \"--base\",\n kind: \"string\",\n required: true,\n description: \"base git ref of the docs repo (e.g. main, origin/main, a commit sha)\",\n },\n ],\n },\n {\n name: \"loam_doctor\",\n command: \"doctor\",\n description:\n \"Diagnose local loam configuration and docs-repo accessibility without writing. \" +\n \"CLI equivalent: loam doctor --json\",\n positionals: [],\n flags: [],\n },\n {\n name: \"loam_context\",\n command: \"context\",\n description:\n \"Assemble one service's whole docs slice as one deterministic briefing. \" +\n \"CLI equivalent: loam context <service> [--feature <FEAT>] --json\",\n positionals: [\n { property: \"service\", required: true, variadic: false, description: \"service id, e.g. payment-service\" },\n ],\n flags: [\n { property: \"feature\", flag: \"--feature\", kind: \"string\", description: \"narrow the in-flight section to this one feature\" },\n ],\n },\n {\n name: \"loam_gate\",\n command: \"gate\",\n description:\n \"Can this service deploy? A read-only query over recorded evidence — advisory by default. \" +\n \"CLI equivalent: loam gate [--service <id>] [--strict] --json\",\n positionals: [],\n flags: [\n { ...SERVICE_FLAG, description: \"the service being deployed (defaults to the configured service)\" },\n { property: \"strict\", flag: \"--strict\", kind: \"boolean\", description: \"exit 1 on any warning too; the payload does not change\" },\n ],\n },\n {\n name: \"loam_steps\",\n command: \"steps\",\n description:\n \"Inventory the step phrases of a service's scenarios — how many step definitions its suite needs. \" +\n \"CLI equivalent: loam steps [--service <id>] [--duplicates] --json\",\n positionals: [],\n flags: [\n { ...SERVICE_FLAG, description: \"service to inventory (defaults to the configured service)\" },\n {\n property: \"duplicates\",\n flag: \"--duplicates\",\n kind: \"boolean\",\n description: \"list only the near-duplicate groups — phrases that differ by an article or a trailing clause\",\n },\n ],\n },\n {\n name: \"loam_explain\",\n command: \"explain\",\n description:\n \"Explain a finding code, a refusal code, or a loam concept term, version-matched to this binary; reads no config and no docs repo. \" +\n \"CLI equivalent: loam explain [subject] --json\",\n positionals: [\n {\n property: \"subject\",\n required: false,\n variadic: false,\n description: `finding code (spine.op-undefined), refusal code (docs-busy), or concept term (${TERM_LIST}); omit to list the terms`,\n },\n ],\n flags: [],\n },\n {\n name: \"loam_instructions\",\n command: \"instructions\",\n description:\n \"Print a workflow, support protocol or reference page, version-matched to this binary; reads no config and no docs repo, so it answers \" +\n \"before the repository is wired. Ask for loam-check DELIBERATELY: it is by far the largest protocol \" +\n \"(its per-code fix tables are ~83 KB of the ~84 KB it prints), and noFixTables true returns the narrowed page \" +\n \"— every paragraph that introduces a table, none of the rows — after which loam_explain answers any one code \" +\n \"a run reports in under 500 bytes. \" +\n \"CLI equivalent: loam instructions [workflow] [args...] [--no-fix-tables] --json\",\n positionals: [\n {\n property: \"workflow\",\n required: false,\n variadic: false,\n description: \"workflow, support or reference name (loam-adopt, loam-report, loam-codes, …); omit to list them\",\n },\n {\n property: \"args\",\n required: false,\n variadic: true,\n description: \"values substituted for the protocol's $1, $2, … placeholders\",\n },\n ],\n flags: [\n {\n // `noFixTables`, not `fixTables`, and the mismatch with commander's own\n // attribute name (`fixTables`, because `--no-` negates) is the point:\n // `toArgv` spells a true boolean as its flag string, so a property named\n // for the option's ATTRIBUTE would make `{fixTables: true}` emit\n // `--no-fix-tables` and turn the tables off. The JSON property is named\n // for what setting it true DOES.\n property: \"noFixTables\",\n flag: \"--no-fix-tables\",\n kind: \"boolean\",\n description: \"drop the per-code fix tables; loam_explain answers any code a run reports\",\n },\n ],\n },\n];\n\nexport function toolByName(\n name: string,\n tools: readonly McpTool[] = MCP_TOOLS,\n): McpTool | undefined {\n return tools.find((tool) => tool.name === name);\n}\n\n/** The MCP inputSchema for one tool: a closed object, so a typo'd argument refuses instead of vanishing. */\nexport function toInputSchema(tool: McpTool): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n for (const positional of tool.positionals) {\n properties[positional.property] = positional.variadic\n ? { type: \"array\", items: { type: \"string\" }, description: positional.description }\n : { type: \"string\", description: positional.description };\n }\n for (const flag of tool.flags) {\n properties[flag.property] =\n flag.kind === \"boolean\"\n ? { type: \"boolean\", description: flag.description }\n : flag.kind === \"strings\"\n ? { type: \"array\", items: { type: \"string\" }, description: flag.description }\n : { type: \"string\", description: flag.description };\n }\n const required = [\n ...tool.positionals.filter((p) => p.required).map((p) => p.property),\n ...tool.flags.filter((f) => f.required === true).map((f) => f.property),\n ];\n return {\n type: \"object\",\n properties,\n ...(required.length > 0 ? { required } : {}),\n additionalProperties: false,\n };\n}\n\n// How a tool call's JSON arguments become an argv — including the refusal of\n// any string a shell-less argv would still read as a flag — lives in\n// `./argv.ts`: the table above says WHAT the tools are, that module says how\n// a call crosses into the CLI's own parsing.\n"]}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/core/mcp/tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The curated MCP tool table: which loam commands an MCP client can reach,\n * and how a JSON argument object becomes an argv the CLI already trusts.\n *\n * This base table is read-only. Opt-in writers live in `./author-tools.ts`;\n * vouch and a committing archive remain absent: an MCP caller is definitionally\n * an agent, `loam vouch` is a HUMAN act (the CLI refuses unattended runs with\n * vouch-unattended), and exposing it here would rebuild the hole the generated\n * skill files' allowlist was narrowed to close. verify is excluded with them: its read form and its\n * attesting `--record`/`--results`/`--contract-results` form are one command,\n * and a facade that exposes \"only the read half\" of a writer is one\n * flag-mapping bug away from exposing the writer.\n *\n * The inclusion of the two commands that read no CONFIG — `explain` and\n * `instructions` — is equally deliberate, and is recorded here because the\n * gap is otherwise re-derivable as an oversight: an agent meets both BEFORE\n * the repository is wired (every generated skill's first line is `loam\n * instructions <workflow>`, and `loam explain <code>` answers a code from a\n * run that has not happened yet), so an MCP-only host that could not reach\n * them could not fetch the protocol its own generated skill points at.\n *\n * `adopt` is here for the same reason one step later: it writes nothing — it\n * emits the brief an agent then writes a baseline from — and the loam-adopt\n * protocol an MCP-only host fetches through `loam_instructions` opens by\n * running it. Without the tool that host could read the protocol and never\n * receive the brief the protocol is about.\n *\n * Every table entry mirrors a real registration in `src/commands/` — same\n * positionals, same flags, same spellings — so the contract an MCP client\n * sees is the CLI contract, not a second one. `--json` is appended by\n * `toArgv` unconditionally: the tool result IS the machine envelope.\n */\n\nimport { TERMS } from \"../explain/terms.js\";\n\n/**\n * The concept-term names, from the registry that owns them — `loam_explain`'s\n * schema description must not be a hand copy that silently trails TERMS.\n */\nconst TERM_LIST = TERMS.map(({ term }) => term).join(\", \");\n\n/** A positional argument, in declaration order. A variadic one is always last. */\nexport interface ToolPositional {\n readonly property: string;\n readonly required: boolean;\n /** True when the JSON argument is an array of strings spread as trailing positionals. */\n readonly variadic: boolean;\n readonly description: string;\n}\n\n/** One CLI flag: `kind` decides both the JSON Schema type and the argv shape. */\nexport interface ToolFlag {\n readonly property: string;\n readonly flag: string;\n readonly kind: \"string\" | \"boolean\" | \"strings\";\n /**\n * Set when the CLI declares the option with `requiredOption` — the schema\n * then requires the property and the argv builder refuses its absence, so\n * a client learns the requirement from the schema instead of from an\n * invalid-option envelope after the call. The shape test asserts this\n * mirrors commander's own `mandatory`, both ways.\n */\n readonly required?: true;\n readonly description: string;\n}\n\nexport interface McpTool {\n readonly name: string;\n readonly command: string;\n readonly description: string;\n readonly positionals: readonly ToolPositional[];\n readonly flags: readonly ToolFlag[];\n /** Arguments enforced by the server rather than exposed to the caller. */\n readonly fixed?: readonly string[];\n readonly annotations?: Readonly<Record<string, boolean>>;\n}\n\nconst SERVICE_FLAG = {\n property: \"service\",\n flag: \"--service\",\n kind: \"string\",\n description: \"service id (defaults to the one loam.json configures)\",\n} as const;\n\n/**\n * The tool annotations EVERY entry in this table carries — one shared literal,\n * because the table is read-only by construction and a per-tool copy is a\n * place for one of them to drift into claiming something else.\n *\n * `readOnlyHint` is the whole point: without it a host has no machine signal\n * that `loam_validate` or `loam_context` is safe, so a call that only reads\n * files falls into the same approval prompt as a mutating tool. Read-only\n * hints are what let a host auto-approve, and the reason the writers are\n * excluded from the table above is precisely so this hint can be true of\n * everything left. `openWorldHint: false` states the other half a host acts\n * on: loam reaches no network and no service — it reads the files in the\n * directory the server started in, and nothing else.\n *\n * `idempotentHint` and `destructiveHint` are deliberately ABSENT rather than\n * spelled `true`/`false`: MCP defines both as meaningful only when\n * `readOnlyHint` is false, so shipping them would be noise a reviewer has to\n * justify before deciding they mean nothing here.\n *\n * `outputSchema` lives in `./protocol.ts`, beside `toolReply`: the declaration\n * and the fallback structured envelope have to change together or a client is\n * promised structured content one branch can omit.\n */\nexport const READ_ONLY_ANNOTATIONS = { readOnlyHint: true, openWorldHint: false } as const;\n\n/**\n * The `loam_` prefix on every name: multi-server MCP hosts flatten tool names\n * into one namespace, and a bare `validate` collides with anybody else's.\n * Each description names the CLI command it mirrors, so the equivalence is\n * stated where the client reads it.\n */\nexport const MCP_TOOLS: readonly McpTool[] = [\n {\n name: \"loam_validate\",\n command: \"validate\",\n description:\n \"Validate a service (C4 + requirement coverage), a feature (delta + coverage), or the whole fleet. \" +\n \"CLI equivalent: loam validate [target] [--service <id>] [--feature <id>] [--all] [--strict] [--errors-only] --json\",\n positionals: [\n {\n property: \"target\",\n required: false,\n variadic: false,\n description: \"service or feature id (a feature wins when both match; service/feature force the reading)\",\n },\n ],\n flags: [\n { ...SERVICE_FLAG, description: \"service to validate (defaults to the configured service)\" },\n { property: \"feature\", flag: \"--feature\", kind: \"string\", description: \"validate a feature delta instead of a service\" },\n { property: \"all\", flag: \"--all\", kind: \"boolean\", description: \"validate every service and every active feature\" },\n { property: \"strict\", flag: \"--strict\", kind: \"boolean\", description: \"exit 1 on any warning too; the payload does not change\" },\n { property: \"errorsOnly\", flag: \"--errors-only\", kind: \"boolean\", description: \"print only errors and warnings; the payload does not change\" },\n ],\n },\n {\n name: \"loam_status\",\n command: \"status\",\n description:\n \"Where the work stands and what to do next, derived from the files. \" +\n \"CLI equivalent: loam status [feature] [--service <id>] --json\",\n positionals: [\n { property: \"feature\", required: false, variadic: false, description: \"feature id or directory name; omit for the whole repository\" },\n ],\n flags: [{ ...SERVICE_FLAG, description: \"narrow the per-service view to one service\" }],\n },\n {\n name: \"loam_list\",\n command: \"list\",\n description:\n \"List the services and features in the docs repo. \" +\n \"CLI equivalent: loam list [section] [name] [--archived] [--needs-work] [--review-order] [--subsystem <name>] [--owners <path>] --json\",\n positionals: [\n { property: \"section\", required: false, variadic: false, description: \"services | features | capabilities | glossary | suites (default: services + features)\" },\n { property: \"name\", required: false, variadic: false, description: \"suites only: the one suite to show\" },\n ],\n flags: [\n { property: \"archived\", flag: \"--archived\", kind: \"boolean\", description: \"include archived features\" },\n { property: \"needsWork\", flag: \"--needs-work\", kind: \"boolean\", description: \"services only: every service below vouched, with what it is missing\" },\n { property: \"reviewOrder\", flag: \"--review-order\", kind: \"boolean\", description: \"with needsWork: order the worklist by fan-in, highest blast radius first\" },\n { property: \"subsystem\", flag: \"--subsystem\", kind: \"string\", description: \"services only: limit the listing to services filed under this subsystem, at any depth ('unfiled' selects the ones filed under none)\" },\n { property: \"owners\", flag: \"--owners\", kind: \"string\", description: \"services only: group the listing by owning team from this CODEOWNERS file (directory-pattern rules only; unsupported rules are listed as skipped, never guessed)\" },\n ],\n },\n {\n name: \"loam_show\",\n command: \"show\",\n description:\n \"Everything loam knows about a service or a feature. \" +\n \"CLI equivalent: loam show <target> [--type service|feature] --json\",\n positionals: [\n { property: \"target\", required: true, variadic: false, description: \"service id or feature id\" },\n ],\n flags: [\n { property: \"type\", flag: \"--type\", kind: \"string\", description: \"force the reading: service | feature\" },\n ],\n },\n {\n name: \"loam_delta\",\n command: \"delta\",\n description:\n \"Project a feature onto a service: why + requirement delta + C4 changes. \" +\n \"CLI equivalent: loam delta <featureId> [--service <id>] --json\",\n positionals: [\n { property: \"featureId\", required: true, variadic: false, description: \"feature id, e.g. FEAT-101\" },\n ],\n flags: [{ ...SERVICE_FLAG, description: \"service to project onto (defaults to the configured service)\" }],\n },\n {\n name: \"loam_tasks\",\n command: \"tasks\",\n description:\n \"What is still open on a feature, at which step, and what closes each unit — a read-only projection that writes nothing. \" +\n \"CLI equivalent: loam tasks <featureId> [--service <id>] [--next] --json\",\n positionals: [\n { property: \"featureId\", required: true, variadic: false, description: \"feature id, e.g. FEAT-101\" },\n ],\n flags: [\n { ...SERVICE_FLAG, description: \"narrow the packet to the units one service owns (a lens: no binding needed)\" },\n { property: \"next\", flag: \"--next\", kind: \"boolean\", description: \"the frontier head alone — the single leaf to hand to a worker\" },\n ],\n },\n {\n name: \"loam_explore\",\n command: \"explore\",\n description:\n \"Read the fleet around a change nobody has written down yet — writes nothing. \" +\n \"CLI equivalent: loam explore [service...] [--op <id>] [--capability <id>] [--as <FEAT>] --json\",\n positionals: [\n { property: \"services\", required: false, variadic: true, description: \"service ids the change starts from\" },\n ],\n flags: [\n { property: \"op\", flag: \"--op\", kind: \"strings\", description: \"seed from an operation instead of a service; repeatable\" },\n { property: \"capability\", flag: \"--capability\", kind: \"strings\", description: \"seed from a declared capability's realizing services; repeatable\" },\n { property: \"as\", flag: \"--as\", kind: \"string\", description: \"feature id for the suggested command line\" },\n ],\n },\n {\n name: \"loam_dependencies\",\n command: \"dependencies\",\n description:\n \"Dependencies and conflicts derived from active feature artifacts. \" +\n \"CLI equivalent: loam dependencies [featureId] --json\",\n positionals: [\n { property: \"featureId\", required: false, variadic: false, description: \"active feature id; omit for the whole in-flight graph\" },\n ],\n flags: [],\n },\n {\n name: \"loam_diff\",\n command: \"diff\",\n description:\n \"Semantic diff of the living docs against a base git ref of the docs repo — removals graded against their current consumers; read-only. \" +\n \"CLI equivalent: loam diff --base <ref> --json\",\n positionals: [],\n flags: [\n {\n property: \"base\",\n flag: \"--base\",\n kind: \"string\",\n required: true,\n description: \"base git ref of the docs repo (e.g. main, origin/main, a commit sha)\",\n },\n ],\n },\n {\n name: \"loam_doctor\",\n command: \"doctor\",\n description:\n \"Diagnose local loam configuration and docs-repo accessibility without writing. \" +\n \"CLI equivalent: loam doctor --json\",\n positionals: [],\n flags: [],\n },\n {\n name: \"loam_context\",\n command: \"context\",\n description:\n \"Assemble one service's whole docs slice as one deterministic briefing. \" +\n \"CLI equivalent: loam context <service> [--feature <FEAT>] --json\",\n positionals: [\n { property: \"service\", required: true, variadic: false, description: \"service id, e.g. payment-service\" },\n ],\n flags: [\n { property: \"feature\", flag: \"--feature\", kind: \"string\", description: \"narrow the in-flight section to this one feature\" },\n ],\n },\n {\n name: \"loam_adopt\",\n command: \"adopt\",\n description:\n \"Brief an agent to write one service's baseline docs from its code, and say what will be checked — writes nothing. \" +\n \"CLI equivalent: loam adopt [--service <id>] [--subsystem <name>] [--targets] --json\",\n positionals: [],\n flags: [\n { ...SERVICE_FLAG, description: \"service to adopt (defaults to the configured service)\" },\n { property: \"subsystem\", flag: \"--subsystem\", kind: \"string\", description: \"brief a NEW service's baseline into this subsystem instead of the services/ root\" },\n { property: \"targets\", flag: \"--targets\", kind: \"boolean\", description: \"only what varies by service; a `full` field names the run that carries the walk, the checks and the unchecked list\" },\n ],\n },\n {\n name: \"loam_gate\",\n command: \"gate\",\n description:\n \"Can this service deploy? A read-only query over recorded evidence — advisory by default. \" +\n \"CLI equivalent: loam gate [--service <id>] [--since <ref>] [--strict] --json\",\n positionals: [],\n flags: [\n { ...SERVICE_FLAG, description: \"the service being deployed (defaults to the configured service)\" },\n { property: \"since\", flag: \"--since\", kind: \"string\", description: \"also grade features archived after this docs-repo git ref; without it only in-flight features are read\" },\n { property: \"strict\", flag: \"--strict\", kind: \"boolean\", description: \"exit 1 on any warning too; the payload does not change\" },\n ],\n },\n {\n name: \"loam_steps\",\n command: \"steps\",\n description:\n \"Inventory the step phrases of a service's scenarios — how many step definitions its suite needs. \" +\n \"CLI equivalent: loam steps [--service <id>] [--duplicates] --json\",\n positionals: [],\n flags: [\n { ...SERVICE_FLAG, description: \"service to inventory (defaults to the configured service)\" },\n {\n property: \"duplicates\",\n flag: \"--duplicates\",\n kind: \"boolean\",\n description: \"list only the near-duplicate groups — phrases that differ by an article or a trailing clause\",\n },\n ],\n },\n {\n name: \"loam_explain\",\n command: \"explain\",\n description:\n \"Explain a finding code, a refusal code, or a loam concept term, version-matched to this binary; reads no config and no docs repo. \" +\n \"CLI equivalent: loam explain [subject] --json\",\n positionals: [\n {\n property: \"subject\",\n required: false,\n variadic: false,\n description: `finding code (spine.op-undefined), refusal code (docs-busy), or concept term (${TERM_LIST}); omit to list the terms`,\n },\n ],\n flags: [],\n },\n {\n name: \"loam_instructions\",\n command: \"instructions\",\n description:\n \"Print a workflow, support protocol or reference page, version-matched to this binary; reads no config and no docs repo, so it answers \" +\n \"before the repository is wired. Ask for loam-check DELIBERATELY: it is by far the largest protocol \" +\n \"(almost all of what it prints is its per-code fix tables), and noFixTables true returns the narrowed page \" +\n \"— every paragraph that introduces a table, none of the rows — after which loam_explain answers any one code \" +\n \"a run reports, on its own. \" +\n \"CLI equivalent: loam instructions [workflow] [args...] [--no-fix-tables] --json\",\n positionals: [\n {\n property: \"workflow\",\n required: false,\n variadic: false,\n description: \"workflow, support or reference name (loam-adopt, loam-report, loam-codes, …); omit to list them\",\n },\n {\n property: \"args\",\n required: false,\n variadic: true,\n description: \"values substituted for the protocol's $1, $2, … placeholders\",\n },\n ],\n flags: [\n {\n // `noFixTables`, not `fixTables`, and the mismatch with commander's own\n // attribute name (`fixTables`, because `--no-` negates) is the point:\n // `toArgv` spells a true boolean as its flag string, so a property named\n // for the option's ATTRIBUTE would make `{fixTables: true}` emit\n // `--no-fix-tables` and turn the tables off. The JSON property is named\n // for what setting it true DOES.\n property: \"noFixTables\",\n flag: \"--no-fix-tables\",\n kind: \"boolean\",\n description: \"drop the per-code fix tables; loam_explain answers any code a run reports\",\n },\n ],\n },\n];\n\nexport function toolByName(\n name: string,\n tools: readonly McpTool[] = MCP_TOOLS,\n): McpTool | undefined {\n return tools.find((tool) => tool.name === name);\n}\n\n// How a tool call's JSON arguments become an argv — including the refusal of\n// any string a shell-less argv would still read as a flag — lives in\n// `./argv.ts`: the table above says WHAT the tools are, that module says how\n// a call crosses into the CLI's own parsing. The inputSchema each tool is\n// advertised with (`toInputSchema`) lives there too: it DECLARES the argument\n// contract `toArgv` ENFORCES — the closed object, the required set, the order\n// the positionals are read in — and the two halves change together or a\n// client validates against one rule and is refused by another.\n"]}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The baseline axis of the feature gate: which of the surfaces this delta
3
- * SPELLS does it actually edit? A feature's openapi.yaml is a complete
4
- * document, so most of it is quotation — and until the pins existed the merge
3
+ * SPELLS does it actually edit? A feature's openapi.yaml needs only what it
4
+ * changes, but it may quote more — and until the pins existed the merge
5
5
  * upserted quotations too, which reverted whatever had landed on them since.
6
6
  *
7
7
  * Extracted from `coherence/declared.ts` when the operation pin grew its two
@@ -25,14 +25,25 @@ import { entryFor, readBaselineRecord, restatedSurfaces, surfaceIn } from "./rec
25
25
  export function openapiBaselineIssues(input) {
26
26
  const { featDoc, livingDoc, service, featureId } = input;
27
27
  const issues = [];
28
- const livingBySlot = new Map(livingDoc.ops.map((op) => [`${op.method} ${op.path}`, op]));
29
- let unpinned = 0;
30
- for (const op of featDoc.ops) {
28
+ // Slot-keyed on BOTH sides, an operation with no operationId included: the
29
+ // merge replaces whatever stands at a method and path, and `loam rebase`
30
+ // pins against whatever stands there, named or not. Reading named
31
+ // operations alone, this gate refused as pinned over "no operation at that
32
+ // slot" the very pin rebase had just written over an unnamed one — the two
33
+ // could never agree — and an unpinned quote over it drew no
34
+ // `openapi.baseline-missing` at all.
35
+ const livingBySlot = new Map([...livingDoc.ops, ...livingDoc.anonymousOps].map((op) => [`${op.method} ${op.path}`, op]));
36
+ const restated = [
31
37
  // A removal marker is not a restatement of anything; its own exactness
32
38
  // check (`openapi.remove-target-mismatch`) already guards the slot.
33
- if (op.remove)
34
- continue;
39
+ ...featDoc.ops.filter((op) => !op.remove),
40
+ ...featDoc.anonymousOps,
41
+ ];
42
+ let unpinned = 0;
43
+ for (const op of restated) {
35
44
  const where = `${op.method.toUpperCase()} ${op.path}`;
45
+ // How the messages name it: the slot, and the operationId where it has one.
46
+ const named = op.id === undefined ? where : `${where} ('${op.id}')`;
36
47
  const livingOp = livingBySlot.get(`${op.method} ${op.path}`);
37
48
  if (op.basedOn !== undefined && !OPERATION_DIGEST_RE.test(op.basedOn)) {
38
49
  issues.push({
@@ -45,11 +56,36 @@ export function openapiBaselineIssues(input) {
45
56
  }
46
57
  const verdict = classifyBaselineDigests(op.basedOn, op.digest, livingOp?.digest);
47
58
  if (verdict === "unfounded") {
59
+ // Three ways here, and the message must not pick the wrong one for the
60
+ // author. A pin is only ever written over a living operation, so a pin
61
+ // with nothing living behind it usually means the operation was there
62
+ // and LEFT — a parallel feature retired it, or MOVED it to another slot
63
+ // (moving an endpoint is the most common contract edit there is). This
64
+ // used to say "drop the marker", and dropping it is exactly how the
65
+ // quote then archived as an addition and put the operation back — and
66
+ // for a move the retirement advice is wrong too: the id still lives, so
67
+ // archiving past `openapi.op-revived` would answer it at both slots.
68
+ // The living contract says which: a move leaves the id at another slot
69
+ // (and so does a retirement here followed by a re-add there, which
70
+ // reads the same from this side). The third way — a pin hand-written
71
+ // onto a genuinely new operation — is the rare one. The two addresses
72
+ // are a condition ("while it still lives there"), not a forecast: a
73
+ // delta swapping the id back defines another operation at the address
74
+ // it lives at, with or without an operationId, and the merge replaces
75
+ // it there. The revival check (coherence/retired/revival/) reads the
76
+ // contract that merge would write and stands down whenever it answers
77
+ // an id the living contract still holds — as here — at one address, so
78
+ // this must not tell the author otherwise —
79
+ // and for that delta a removal marker at the address cannot be written
80
+ // beside the other operation, hence the second remedy.
81
+ const movedTo = op.id === undefined ? [] : livingDoc.ops.filter((l) => !l.remove && l.id === op.id).map((l) => `${l.method.toUpperCase()} ${l.path}`);
48
82
  issues.push({
49
83
  severity: "error",
50
84
  code: "openapi.baseline-invalid",
51
85
  subject: service,
52
- message: `${service}: ${where} ('${op.id}') carries ${OPENAPI_BASELINE_KEY}, but the living contract has no operation at that slot — a new operation has no living version to be based on; drop the marker`,
86
+ message: movedTo.length > 0
87
+ ? `${service}: ${where} ('${op.id}') carries ${OPENAPI_BASELINE_KEY}, but the living contract has no operation at that slot — '${op.id}' lives at ${movedTo.join(" and ")} now: a parallel feature moved it there (or retired it here, and another re-added it there). Re-read it there and delete this operation from the delta, making any change at its new address; archived while it still lives there, it would put '${op.id}' back at ${where} as well, one operationId at two addresses. To move it back on purpose, drop the pin and add a removal marker for '${op.id}' at ${movedTo.join(" and ")} to this delta, or define another operation there.`
88
+ : `${service}: ${named} carries ${OPENAPI_BASELINE_KEY}, but the living contract has no operation at that slot — the version it was pinned to is gone. If a parallel feature retired it, re-read the living contract and delete this operation from the delta, unless this feature deliberately brings it back (then drop the pin and archive with --approve past \`openapi.op-revived\`). A genuinely new operation has no living version to be based on and carries no pin.`,
53
89
  });
54
90
  }
55
91
  else if (verdict === "stale") {
@@ -57,7 +93,7 @@ export function openapiBaselineIssues(input) {
57
93
  severity: "error",
58
94
  code: "openapi.baseline-stale",
59
95
  subject: service,
60
- message: `${service}: ${where} ('${op.id}') was written against living version ${op.basedOn}, but the living contract now holds ${livingOp.digest} — somebody landed a change to it in between, and merging this would replace theirs outright. Re-read the living operation, fold in what you still mean, then run \`loam rebase ${featureId}\`.`,
96
+ message: `${service}: ${named} was written against living version ${op.basedOn}, but the living contract now holds ${livingOp.digest} — somebody landed a change to it in between, and merging this would replace theirs outright. Re-read the living operation, fold in what you still mean, then run \`loam rebase ${featureId}\`.`,
61
97
  });
62
98
  }
63
99
  else if (verdict === "unpinned" && livingOp !== undefined) {
@@ -150,9 +186,14 @@ export function openapiBaselineIssues(input) {
150
186
  // (delta/select.ts): the document is legal, but an unpinned quote is
151
187
  // exactly the shape that reverts another feature's landed change at exit
152
188
  // 0, and every count covers only values the living contract already has —
153
- // a genuinely new operation, key or component never trips it. `loam
154
- // rebase` pins a whole service in one command; `--approve` says the
155
- // unpinned merge is meant.
189
+ // a genuinely new operation, key or component never trips it. That is
190
+ // also why it cannot see a quote of an operation a parallel feature
191
+ // RETIRED: absent from living, it reads exactly like a new one — nor of
192
+ // one a parallel feature MOVED, quoted at the slot it left, since nothing
193
+ // living stands at that slot. Telling either apart from an addition takes
194
+ // the archived features' removal markers, which this gate never reads —
195
+ // `openapi.op-revived` (core/coherence/retired/) asks that question. `loam rebase` pins a whole service in one command;
196
+ // `--approve` says the unpinned merge is meant.
156
197
  issues.push({
157
198
  severity: "warn",
158
199
  gates: true,
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The baseline axis of the feature gate: which of the surfaces this delta\n * SPELLS does it actually edit? A feature's openapi.yaml is a complete\n * document, so most of it is quotation — and until the pins existed the merge\n * upserted quotations too, which reverted whatever had landed on them since.\n *\n * Extracted from `coherence/declared.ts` when the operation pin grew its two\n * sibling surfaces (path-item non-method keys, components): the grading is one\n * subject with three families, and coherence/ is at its file limit. Everything\n * here is graded by the ONE classifier in `../digest.ts` over the ONE surface\n * enumeration in `./record.ts`, so the gate, the rebase plan and the merge\n * cannot drift into different ideas of what a delta restates.\n */\nimport { parse } from \"yaml\";\nimport type { Issue } from \"../../vocabulary/issue.js\";\nimport type { OpenapiDoc } from \"../doc.js\";\nimport {\n classifyBaselineDigests,\n OPENAPI_BASELINE_KEY,\n OPENAPI_BASELINES_KEY,\n OPERATION_DIGEST_LENGTH,\n OPERATION_DIGEST_RE,\n valueDigest,\n} from \"../digest.js\";\nimport { entryFor, readBaselineRecord, restatedSurfaces, surfaceIn } from \"./record.js\";\n\n/** Everything the gate needs about one service's pair of contracts. */\nexport interface BaselineGateInput {\n /** The FEATURE contract as `readOpenapi` parsed it — ops carry digests and pins. */\n featDoc: OpenapiDoc;\n /** The LIVING contract's parse; empty when the file is absent. */\n livingDoc: OpenapiDoc;\n /** The feature contract's text — the surface record and values are read from it. */\n featureText: string;\n /** The living contract's text, or undefined when no living contract exists. */\n livingText: string | undefined;\n service: string;\n featureId: string;\n}\n\n/**\n * Grade every baseline this feature contract carries — operation pins,\n * path-item keys, components — against the living contract. Slot-keyed for\n * operations, (path, key)-keyed and (kind/name)-keyed for the record, exactly\n * as the merge writes. With no living side there are no surface checks at all:\n * everything the delta spells is new, and a baseline only means something for\n * a value that already exists.\n */\nexport function openapiBaselineIssues(input: BaselineGateInput): Issue[] {\n const { featDoc, livingDoc, service, featureId } = input;\n const issues: Issue[] = [];\n const livingBySlot = new Map(livingDoc.ops.map((op) => [`${op.method} ${op.path}`, op]));\n let unpinned = 0;\n for (const op of featDoc.ops) {\n // A removal marker is not a restatement of anything; its own exactness\n // check (`openapi.remove-target-mismatch`) already guards the slot.\n if (op.remove) continue;\n const where = `${op.method.toUpperCase()} ${op.path}`;\n const livingOp = livingBySlot.get(`${op.method} ${op.path}`);\n if (op.basedOn !== undefined && !OPERATION_DIGEST_RE.test(op.basedOn)) {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${where} has invalid ${OPENAPI_BASELINE_KEY} '${op.basedOn}' — expected ${OPERATION_DIGEST_LENGTH} lowercase hex characters, as \\`loam rebase\\` writes them`,\n });\n continue;\n }\n const verdict = classifyBaselineDigests(op.basedOn, op.digest, livingOp?.digest);\n if (verdict === \"unfounded\") {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${where} ('${op.id}') carries ${OPENAPI_BASELINE_KEY}, but the living contract has no operation at that slot — a new operation has no living version to be based on; drop the marker`,\n });\n } else if (verdict === \"stale\") {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-stale\",\n subject: service,\n message: `${service}: ${where} ('${op.id}') was written against living version ${op.basedOn}, but the living contract now holds ${livingOp!.digest} — somebody landed a change to it in between, and merging this would replace theirs outright. Re-read the living operation, fold in what you still mean, then run \\`loam rebase ${featureId}\\`.`,\n });\n } else if (verdict === \"unpinned\" && livingOp !== undefined) {\n // Counted, not listed. A delta over a thirty-operation service quotes\n // twenty-nine of them, and twenty-nine identical warnings naming a fix\n // that is ONE command teaches people to filter the code out.\n unpinned += 1;\n }\n }\n\n // The two surfaces the operation pin cannot reach, graded from the\n // `x-loam-baselines` record by the same classifier. Unreadable documents\n // never get here on the feature side (`openapi.invalid` suspends the axis);\n // an unreadable LIVING side is that contract's own `openapi.invalid`, and\n // grading against its empty parse would call every restatement new.\n const missing = { keys: 0, components: 0 };\n if (input.livingText !== undefined && !featDoc.unreadable && !livingDoc.unreadable) {\n const featPlain: unknown = parse(input.featureText);\n const livingPlain: unknown = parse(input.livingText);\n const { record, problems } = readBaselineRecord(featPlain);\n for (const problem of problems) {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${problem} — the record is \\`loam rebase\\`'s bookkeeping and cannot be graded as it stands; re-run \\`loam rebase ${featureId}\\` to rebuild it`,\n });\n }\n const surfaces = restatedSurfaces(featPlain);\n // An entry pinning a surface the delta does not restate is a claim about\n // nothing — usually a surface the author deleted after rebasing. Distinct\n // from `unfounded` below, where the surface IS restated and the LIVING\n // side is what vanished.\n const restated = new Set(surfaces.map((s) => (s.kind === \"path-item\" ? `${s.path}\\0${s.key}` : s.id)));\n for (const [path, keys] of Object.entries(record.pathItems)) {\n for (const key of Object.keys(keys)) {\n if (restated.has(`${path}\\0${key}`)) continue;\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${OPENAPI_BASELINES_KEY} pins '${key}' (${path}), but this feature's openapi.yaml does not restate that key — re-run \\`loam rebase ${featureId}\\` to rebuild the record`,\n });\n }\n }\n for (const id of Object.keys(record.components)) {\n if (restated.has(id)) continue;\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${OPENAPI_BASELINES_KEY} pins component '${id}', but this feature's openapi.yaml does not declare it — re-run \\`loam rebase ${featureId}\\` to rebuild the record`,\n });\n }\n for (const s of surfaces) {\n const label = s.kind === \"path-item\" ? `path-level key '${s.key}' (${s.path})` : `component '${s.id}'`;\n const entry = entryFor(record, s);\n const living = surfaceIn(livingPlain, s);\n const livingDigest = living.found ? valueDigest(living.value) : undefined;\n const verdict = classifyBaselineDigests(entry, valueDigest(s.value), livingDigest);\n if (verdict === \"unpinned\") {\n if (living.found) {\n if (s.kind === \"path-item\") missing.keys += 1;\n else missing.components += 1;\n }\n } else if (verdict === \"stale\") {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-stale\",\n subject: service,\n message: `${service}: ${label} was pinned to living version ${entry}, but the living contract now holds ${livingDigest} — somebody landed a change to it in between, and merging this would replace theirs outright. Re-read the living value, fold in what you still mean, then run \\`loam rebase ${featureId}\\`.`,\n });\n } else if (verdict === \"unfounded\") {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${label} is pinned to ${entry}, but the living contract no longer has that surface — the pin is structurally unresolvable; re-read the living contract, then run \\`loam rebase ${featureId}\\``,\n });\n }\n }\n }\n\n if (unpinned > 0 || missing.keys > 0 || missing.components > 0) {\n // A warning that GATES archive, same doctrine as `delta.baseline-missing`\n // (delta/select.ts): the document is legal, but an unpinned quote is\n // exactly the shape that reverts another feature's landed change at exit\n // 0, and every count covers only values the living contract already has —\n // a genuinely new operation, key or component never trips it. `loam\n // rebase` pins a whole service in one command; `--approve` says the\n // unpinned merge is meant.\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"openapi.baseline-missing\",\n subject: service,\n message: `${service}: ${unpinned} operation(s), ${missing.keys} path-level key(s) and ${missing.components} component(s) in this feature's openapi.yaml carry no baseline pin (${OPENAPI_BASELINE_KEY} / ${OPENAPI_BASELINES_KEY}), so the merge cannot tell which ones this delta EDITS from the ones it merely restates — it will upsert all of them, reverting anything that landed on the restated ones. Run \\`loam rebase ${featureId} --service ${service}\\`, or archive with --approve to merge unpinned deliberately.`,\n });\n }\n return issues;\n}\n"]}
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/gate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The baseline axis of the feature gate: which of the surfaces this delta\n * SPELLS does it actually edit? A feature's openapi.yaml needs only what it\n * changes, but it may quote more — and until the pins existed the merge\n * upserted quotations too, which reverted whatever had landed on them since.\n *\n * Extracted from `coherence/declared.ts` when the operation pin grew its two\n * sibling surfaces (path-item non-method keys, components): the grading is one\n * subject with three families, and coherence/ is at its file limit. Everything\n * here is graded by the ONE classifier in `../digest.ts` over the ONE surface\n * enumeration in `./record.ts`, so the gate, the rebase plan and the merge\n * cannot drift into different ideas of what a delta restates.\n */\nimport { parse } from \"yaml\";\nimport type { Issue } from \"../../vocabulary/issue.js\";\nimport type { OpenapiDoc } from \"../doc.js\";\nimport {\n classifyBaselineDigests,\n OPENAPI_BASELINE_KEY,\n OPENAPI_BASELINES_KEY,\n OPERATION_DIGEST_LENGTH,\n OPERATION_DIGEST_RE,\n valueDigest,\n} from \"../digest.js\";\nimport { entryFor, readBaselineRecord, restatedSurfaces, surfaceIn } from \"./record.js\";\n\n/** Everything the gate needs about one service's pair of contracts. */\nexport interface BaselineGateInput {\n /** The FEATURE contract as `readOpenapi` parsed it — ops carry digests and pins. */\n featDoc: OpenapiDoc;\n /** The LIVING contract's parse; empty when the file is absent. */\n livingDoc: OpenapiDoc;\n /** The feature contract's text — the surface record and values are read from it. */\n featureText: string;\n /** The living contract's text, or undefined when no living contract exists. */\n livingText: string | undefined;\n service: string;\n featureId: string;\n}\n\n/**\n * Grade every baseline this feature contract carries — operation pins,\n * path-item keys, components — against the living contract. Slot-keyed for\n * operations, (path, key)-keyed and (kind/name)-keyed for the record, exactly\n * as the merge writes. With no living side there are no surface checks at all:\n * everything the delta spells is new, and a baseline only means something for\n * a value that already exists.\n */\nexport function openapiBaselineIssues(input: BaselineGateInput): Issue[] {\n const { featDoc, livingDoc, service, featureId } = input;\n const issues: Issue[] = [];\n // Slot-keyed on BOTH sides, an operation with no operationId included: the\n // merge replaces whatever stands at a method and path, and `loam rebase`\n // pins against whatever stands there, named or not. Reading named\n // operations alone, this gate refused as pinned over \"no operation at that\n // slot\" the very pin rebase had just written over an unnamed one — the two\n // could never agree — and an unpinned quote over it drew no\n // `openapi.baseline-missing` at all.\n const livingBySlot = new Map<string, { digest: string }>(\n [...livingDoc.ops, ...livingDoc.anonymousOps].map((op) => [`${op.method} ${op.path}`, op]),\n );\n const restated: Array<{ id?: string; path: string; method: string; digest: string; basedOn?: string }> = [\n // A removal marker is not a restatement of anything; its own exactness\n // check (`openapi.remove-target-mismatch`) already guards the slot.\n ...featDoc.ops.filter((op) => !op.remove),\n ...featDoc.anonymousOps,\n ];\n let unpinned = 0;\n for (const op of restated) {\n const where = `${op.method.toUpperCase()} ${op.path}`;\n // How the messages name it: the slot, and the operationId where it has one.\n const named = op.id === undefined ? where : `${where} ('${op.id}')`;\n const livingOp = livingBySlot.get(`${op.method} ${op.path}`);\n if (op.basedOn !== undefined && !OPERATION_DIGEST_RE.test(op.basedOn)) {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${where} has invalid ${OPENAPI_BASELINE_KEY} '${op.basedOn}' — expected ${OPERATION_DIGEST_LENGTH} lowercase hex characters, as \\`loam rebase\\` writes them`,\n });\n continue;\n }\n const verdict = classifyBaselineDigests(op.basedOn, op.digest, livingOp?.digest);\n if (verdict === \"unfounded\") {\n // Three ways here, and the message must not pick the wrong one for the\n // author. A pin is only ever written over a living operation, so a pin\n // with nothing living behind it usually means the operation was there\n // and LEFT — a parallel feature retired it, or MOVED it to another slot\n // (moving an endpoint is the most common contract edit there is). This\n // used to say \"drop the marker\", and dropping it is exactly how the\n // quote then archived as an addition and put the operation back — and\n // for a move the retirement advice is wrong too: the id still lives, so\n // archiving past `openapi.op-revived` would answer it at both slots.\n // The living contract says which: a move leaves the id at another slot\n // (and so does a retirement here followed by a re-add there, which\n // reads the same from this side). The third way — a pin hand-written\n // onto a genuinely new operation — is the rare one. The two addresses\n // are a condition (\"while it still lives there\"), not a forecast: a\n // delta swapping the id back defines another operation at the address\n // it lives at, with or without an operationId, and the merge replaces\n // it there. The revival check (coherence/retired/revival/) reads the\n // contract that merge would write and stands down whenever it answers\n // an id the living contract still holds — as here — at one address, so\n // this must not tell the author otherwise —\n // and for that delta a removal marker at the address cannot be written\n // beside the other operation, hence the second remedy.\n const movedTo = op.id === undefined ? [] : livingDoc.ops.filter((l) => !l.remove && l.id === op.id).map((l) => `${l.method.toUpperCase()} ${l.path}`);\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: movedTo.length > 0\n ? `${service}: ${where} ('${op.id}') carries ${OPENAPI_BASELINE_KEY}, but the living contract has no operation at that slot — '${op.id}' lives at ${movedTo.join(\" and \")} now: a parallel feature moved it there (or retired it here, and another re-added it there). Re-read it there and delete this operation from the delta, making any change at its new address; archived while it still lives there, it would put '${op.id}' back at ${where} as well, one operationId at two addresses. To move it back on purpose, drop the pin and add a removal marker for '${op.id}' at ${movedTo.join(\" and \")} to this delta, or define another operation there.`\n : `${service}: ${named} carries ${OPENAPI_BASELINE_KEY}, but the living contract has no operation at that slot — the version it was pinned to is gone. If a parallel feature retired it, re-read the living contract and delete this operation from the delta, unless this feature deliberately brings it back (then drop the pin and archive with --approve past \\`openapi.op-revived\\`). A genuinely new operation has no living version to be based on and carries no pin.`,\n });\n } else if (verdict === \"stale\") {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-stale\",\n subject: service,\n message: `${service}: ${named} was written against living version ${op.basedOn}, but the living contract now holds ${livingOp!.digest} — somebody landed a change to it in between, and merging this would replace theirs outright. Re-read the living operation, fold in what you still mean, then run \\`loam rebase ${featureId}\\`.`,\n });\n } else if (verdict === \"unpinned\" && livingOp !== undefined) {\n // Counted, not listed. A delta over a thirty-operation service quotes\n // twenty-nine of them, and twenty-nine identical warnings naming a fix\n // that is ONE command teaches people to filter the code out.\n unpinned += 1;\n }\n }\n\n // The two surfaces the operation pin cannot reach, graded from the\n // `x-loam-baselines` record by the same classifier. Unreadable documents\n // never get here on the feature side (`openapi.invalid` suspends the axis);\n // an unreadable LIVING side is that contract's own `openapi.invalid`, and\n // grading against its empty parse would call every restatement new.\n const missing = { keys: 0, components: 0 };\n if (input.livingText !== undefined && !featDoc.unreadable && !livingDoc.unreadable) {\n const featPlain: unknown = parse(input.featureText);\n const livingPlain: unknown = parse(input.livingText);\n const { record, problems } = readBaselineRecord(featPlain);\n for (const problem of problems) {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${problem} — the record is \\`loam rebase\\`'s bookkeeping and cannot be graded as it stands; re-run \\`loam rebase ${featureId}\\` to rebuild it`,\n });\n }\n const surfaces = restatedSurfaces(featPlain);\n // An entry pinning a surface the delta does not restate is a claim about\n // nothing — usually a surface the author deleted after rebasing. Distinct\n // from `unfounded` below, where the surface IS restated and the LIVING\n // side is what vanished.\n const restated = new Set(surfaces.map((s) => (s.kind === \"path-item\" ? `${s.path}\\0${s.key}` : s.id)));\n for (const [path, keys] of Object.entries(record.pathItems)) {\n for (const key of Object.keys(keys)) {\n if (restated.has(`${path}\\0${key}`)) continue;\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${OPENAPI_BASELINES_KEY} pins '${key}' (${path}), but this feature's openapi.yaml does not restate that key — re-run \\`loam rebase ${featureId}\\` to rebuild the record`,\n });\n }\n }\n for (const id of Object.keys(record.components)) {\n if (restated.has(id)) continue;\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${OPENAPI_BASELINES_KEY} pins component '${id}', but this feature's openapi.yaml does not declare it — re-run \\`loam rebase ${featureId}\\` to rebuild the record`,\n });\n }\n for (const s of surfaces) {\n const label = s.kind === \"path-item\" ? `path-level key '${s.key}' (${s.path})` : `component '${s.id}'`;\n const entry = entryFor(record, s);\n const living = surfaceIn(livingPlain, s);\n const livingDigest = living.found ? valueDigest(living.value) : undefined;\n const verdict = classifyBaselineDigests(entry, valueDigest(s.value), livingDigest);\n if (verdict === \"unpinned\") {\n if (living.found) {\n if (s.kind === \"path-item\") missing.keys += 1;\n else missing.components += 1;\n }\n } else if (verdict === \"stale\") {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-stale\",\n subject: service,\n message: `${service}: ${label} was pinned to living version ${entry}, but the living contract now holds ${livingDigest} — somebody landed a change to it in between, and merging this would replace theirs outright. Re-read the living value, fold in what you still mean, then run \\`loam rebase ${featureId}\\`.`,\n });\n } else if (verdict === \"unfounded\") {\n issues.push({\n severity: \"error\",\n code: \"openapi.baseline-invalid\",\n subject: service,\n message: `${service}: ${label} is pinned to ${entry}, but the living contract no longer has that surface — the pin is structurally unresolvable; re-read the living contract, then run \\`loam rebase ${featureId}\\``,\n });\n }\n }\n }\n\n if (unpinned > 0 || missing.keys > 0 || missing.components > 0) {\n // A warning that GATES archive, same doctrine as `delta.baseline-missing`\n // (delta/select.ts): the document is legal, but an unpinned quote is\n // exactly the shape that reverts another feature's landed change at exit\n // 0, and every count covers only values the living contract already has —\n // a genuinely new operation, key or component never trips it. That is\n // also why it cannot see a quote of an operation a parallel feature\n // RETIRED: absent from living, it reads exactly like a new one — nor of\n // one a parallel feature MOVED, quoted at the slot it left, since nothing\n // living stands at that slot. Telling either apart from an addition takes\n // the archived features' removal markers, which this gate never reads —\n // `openapi.op-revived` (core/coherence/retired/) asks that question. `loam rebase` pins a whole service in one command;\n // `--approve` says the unpinned merge is meant.\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"openapi.baseline-missing\",\n subject: service,\n message: `${service}: ${unpinned} operation(s), ${missing.keys} path-level key(s) and ${missing.components} component(s) in this feature's openapi.yaml carry no baseline pin (${OPENAPI_BASELINE_KEY} / ${OPENAPI_BASELINES_KEY}), so the merge cannot tell which ones this delta EDITS from the ones it merely restates — it will upsert all of them, reverting anything that landed on the restated ones. Run \\`loam rebase ${featureId} --service ${service}\\`, or archive with --approve to merge unpinned deliberately.`,\n });\n }\n return issues;\n}\n"]}
@@ -13,7 +13,8 @@
13
13
  * `./record.ts`, so a value edge back into `merge/` would be a package cycle.
14
14
  * That is also why an unreadable document is answered with "nothing to pin"
15
15
  * instead of `OpenapiMergeError`: every caller chains this AFTER
16
- * `pinOpenapiOperations`, which has already thrown that error for any document
16
+ * `pinOpenapiOperations` (on the event axis, after `pinAsyncapiSlots` and its
17
+ * `AsyncapiMergeError`), which has already thrown that error for any document
17
18
  * either side cannot parse, so the silent answer here is unreachable in
18
19
  * practice and merely keeps the failure ownership where it already lives.
19
20
  */
@@ -27,11 +28,22 @@ import { buildRecord, entryFor, readBaselineRecord, restatedSurfaces, surfaceIn
27
28
  * the living one. The entry is always `valueDigest` of the LIVING value, never
28
29
  * of the delta's own — the one rule that yields both merge verdicts: a surface
29
30
  * the author only QUOTED equals its own entry, one they EDITED differs from it.
31
+ *
30
32
  * A surface with no living counterpart is `unresolved` and deliberately absent
31
- * from the record: there is no baseline to claim, and inventing one would make
32
- * `classifyBaselineDigests` call a genuinely new value `unfounded`.
33
+ * from the record. Stamping a pin against nothing is not a harmless extra key:
34
+ * `classifyBaselineDigests` calls a pin with no living side `unfounded`, and
35
+ * the gate then refuses the delta as `openapi.baseline-invalid` (or
36
+ * `asyncapi.baseline-invalid`) — rebase would be writing the file the gate
37
+ * sends its author back to rebase.
38
+ *
39
+ * `surfacesOf` is the one thing the two contract axes do differently: the
40
+ * OpenAPI default walks `paths` and `components`, and the event axis passes
41
+ * `asyncapiSurfaces` (core/asyncapi/baseline/surfaces.ts says why it is a
42
+ * separate enumeration). Everything after the walk — the record, its sorted
43
+ * writer, the idempotence check — is axis-neutral, which is why the event
44
+ * planner is this function rather than a copy of it.
33
45
  */
34
- export function planOpenapiBaselines(featureText, livingText, _service) {
46
+ export function planOpenapiBaselines(featureText, livingText, surfacesOf = restatedSurfaces) {
35
47
  const doc = parseDocument(featureText);
36
48
  if (doc.errors.length > 0)
37
49
  return { text: null, pins: [] };
@@ -51,11 +63,12 @@ export function planOpenapiBaselines(featureText, livingText, _service) {
51
63
  const pathItems = new Map();
52
64
  const components = new Map();
53
65
  const pins = [];
54
- for (const surface of restatedSurfaces(featPlain)) {
66
+ for (const surface of surfacesOf(featPlain)) {
55
67
  // A malformed current entry reads back as absent (readBaselineRecord drops
56
68
  // it into `problems`), so this run reports `pinned` and overwrites it —
57
- // rebase is the command the gate's `openapi.baseline-invalid` sends people
58
- // to, and it must repair what it names.
69
+ // rebase is the command the gate's `openapi.baseline-invalid` (and
70
+ // `asyncapi.baseline-invalid`) sends people to, and it must repair what it
71
+ // names.
59
72
  const from = entryFor(current, surface);
60
73
  const base = {
61
74
  kind: surface.kind,
@@ -85,7 +98,8 @@ export function planOpenapiBaselines(featureText, livingText, _service) {
85
98
  }
86
99
  // The written form drops empty sections, and the whole key when both are —
87
100
  // a bare `x-loam-baselines: {}` would be bookkeeping about nothing, in every
88
- // delta whose surfaces are all new.
101
+ // delta whose surfaces are all new. An AsyncAPI document has no path items,
102
+ // so on that axis `pathItems` is always the empty section dropped here.
89
103
  const rebuilt = buildRecord({ pathItems, components });
90
104
  const written = {};
91
105
  if (Object.keys(rebuilt.pathItems).length > 0)
@@ -1 +1 @@
1
- {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `loam rebase` writes on the surface axis: the `x-loam-baselines` record,\n * rebuilt wholesale from what is on disk right now.\n *\n * The record is rebuilt rather than patched for the same reason the operation\n * pin is always the LIVING digest: a pin is a claim about what its author read,\n * and an entry left behind for a surface the delta no longer restates — or one\n * whose living counterpart is gone — is a claim nobody is making any more.\n * Wholesale rebuild prunes both without a second code path, and the sorted\n * serialization (`buildRecord`) is what makes a second run byte-identical.\n *\n * This module must not import from `../merge/` — the merge reads\n * `./record.ts`, so a value edge back into `merge/` would be a package cycle.\n * That is also why an unreadable document is answered with \"nothing to pin\"\n * instead of `OpenapiMergeError`: every caller chains this AFTER\n * `pinOpenapiOperations`, which has already thrown that error for any document\n * either side cannot parse, so the silent answer here is unreachable in\n * practice and merely keeps the failure ownership where it already lives.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { parseDocument } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\nimport { OPENAPI_BASELINES_KEY, valueDigest } from \"../digest.js\";\nimport { buildRecord, entryFor, readBaselineRecord, restatedSurfaces, surfaceIn } from \"./record.js\";\n\n/** What happened to one restated surface's baseline entry. */\nexport interface SurfacePin {\n /** Which surface family the entry pins. */\n kind: \"path-item\" | \"component\";\n /** `/orders '<key>'` for a path-item key; `<kind>/<name>` for a component. */\n target: string;\n status:\n /** It had no entry and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no such surface: this feature is adding it, and it gets NO entry. */\n | \"unresolved\";\n from: string | null;\n to: string | null;\n}\n\nexport interface OpenapiBaselinePlan {\n /** The rewritten contract, or null when the record already says what this run would write. */\n text: string | null;\n pins: SurfacePin[];\n}\n\n/**\n * Pin every restated path-item key and component of a feature contract against\n * the living one. The entry is always `valueDigest` of the LIVING value, never\n * of the delta's own — the one rule that yields both merge verdicts: a surface\n * the author only QUOTED equals its own entry, one they EDITED differs from it.\n * A surface with no living counterpart is `unresolved` and deliberately absent\n * from the record: there is no baseline to claim, and inventing one would make\n * `classifyBaselineDigests` call a genuinely new value `unfounded`.\n */\nexport function planOpenapiBaselines(\n featureText: string,\n livingText: string,\n _service: string,\n): OpenapiBaselinePlan {\n const doc = parseDocument(featureText);\n if (doc.errors.length > 0) return { text: null, pins: [] };\n let featPlain: unknown;\n let livingPlain: unknown;\n try {\n featPlain = doc.toJS() ?? {};\n const living = parseDocument(livingText);\n if (living.errors.length > 0) return { text: null, pins: [] };\n livingPlain = living.toJS() ?? {};\n } catch {\n return { text: null, pins: [] };\n }\n\n const current = readBaselineRecord(featPlain).record;\n const pathItems = new Map<string, Map<string, string>>();\n const components = new Map<string, string>();\n const pins: SurfacePin[] = [];\n for (const surface of restatedSurfaces(featPlain)) {\n // A malformed current entry reads back as absent (readBaselineRecord drops\n // it into `problems`), so this run reports `pinned` and overwrites it —\n // rebase is the command the gate's `openapi.baseline-invalid` sends people\n // to, and it must repair what it names.\n const from = entryFor(current, surface);\n const base = {\n kind: surface.kind,\n target: surface.kind === \"path-item\" ? `${surface.path} '${surface.key}'` : surface.id,\n } as const;\n const livingValue = surfaceIn(livingPlain, surface);\n if (!livingValue.found) {\n pins.push({ ...base, status: \"unresolved\", from: from ?? null, to: null });\n continue;\n }\n const digest = valueDigest(livingValue.value);\n if (surface.kind === \"path-item\") {\n let keys = pathItems.get(surface.path);\n if (keys === undefined) pathItems.set(surface.path, (keys = new Map()));\n keys.set(surface.key, digest);\n } else {\n components.set(surface.id, digest);\n }\n pins.push({\n ...base,\n status: from === digest ? \"unchanged\" : from === undefined ? \"pinned\" : \"repinned\",\n from: from ?? null,\n to: digest,\n });\n }\n\n // The written form drops empty sections, and the whole key when both are —\n // a bare `x-loam-baselines: {}` would be bookkeeping about nothing, in every\n // delta whose surfaces are all new.\n const rebuilt = buildRecord({ pathItems, components });\n const written: Record<string, unknown> = {};\n if (Object.keys(rebuilt.pathItems).length > 0) written[\"pathItems\"] = rebuilt.pathItems;\n if (Object.keys(rebuilt.components).length > 0) written[\"components\"] = rebuilt.components;\n const raw = isRecord(featPlain) ? featPlain[OPENAPI_BASELINES_KEY] : undefined;\n if (Object.keys(written).length === 0) {\n if (raw === undefined) return { text: null, pins };\n doc.deleteIn([OPENAPI_BASELINES_KEY]);\n return { text: doc.toString(), pins };\n }\n // Deep-equal, not byte-equal: the comparison must ignore key order exactly as\n // the digests do, so a record an author reordered by hand is left in their\n // order — and a second run over this function's own sorted output finds the\n // parse equal and writes nothing, which is the byte-for-byte idempotence\n // `loam rebase` promises.\n if (isDeepStrictEqual(raw, written)) return { text: null, pins };\n doc.setIn([OPENAPI_BASELINES_KEY], written);\n return { text: doc.toString(), pins };\n}\n"]}
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `loam rebase` writes on the surface axis: the `x-loam-baselines` record,\n * rebuilt wholesale from what is on disk right now.\n *\n * The record is rebuilt rather than patched for the same reason the operation\n * pin is always the LIVING digest: a pin is a claim about what its author read,\n * and an entry left behind for a surface the delta no longer restates — or one\n * whose living counterpart is gone — is a claim nobody is making any more.\n * Wholesale rebuild prunes both without a second code path, and the sorted\n * serialization (`buildRecord`) is what makes a second run byte-identical.\n *\n * This module must not import from `../merge/` — the merge reads\n * `./record.ts`, so a value edge back into `merge/` would be a package cycle.\n * That is also why an unreadable document is answered with \"nothing to pin\"\n * instead of `OpenapiMergeError`: every caller chains this AFTER\n * `pinOpenapiOperations` (on the event axis, after `pinAsyncapiSlots` and its\n * `AsyncapiMergeError`), which has already thrown that error for any document\n * either side cannot parse, so the silent answer here is unreachable in\n * practice and merely keeps the failure ownership where it already lives.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { parseDocument } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\nimport { OPENAPI_BASELINES_KEY, valueDigest } from \"../digest.js\";\nimport { buildRecord, entryFor, readBaselineRecord, restatedSurfaces, surfaceIn, type RestatedSurface } from \"./record.js\";\n\n/** What happened to one restated surface's baseline entry. */\nexport interface SurfacePin {\n /** Which surface family the entry pins. */\n kind: \"path-item\" | \"component\";\n /** `/orders '<key>'` for a path-item key; `<kind>/<name>` for a component. */\n target: string;\n status:\n /** It had no entry and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no such surface: this feature is adding it, and it gets NO entry. */\n | \"unresolved\";\n from: string | null;\n to: string | null;\n}\n\nexport interface OpenapiBaselinePlan {\n /** The rewritten contract, or null when the record already says what this run would write. */\n text: string | null;\n pins: SurfacePin[];\n}\n\n/**\n * Pin every restated path-item key and component of a feature contract against\n * the living one. The entry is always `valueDigest` of the LIVING value, never\n * of the delta's own — the one rule that yields both merge verdicts: a surface\n * the author only QUOTED equals its own entry, one they EDITED differs from it.\n *\n * A surface with no living counterpart is `unresolved` and deliberately absent\n * from the record. Stamping a pin against nothing is not a harmless extra key:\n * `classifyBaselineDigests` calls a pin with no living side `unfounded`, and\n * the gate then refuses the delta as `openapi.baseline-invalid` (or\n * `asyncapi.baseline-invalid`) — rebase would be writing the file the gate\n * sends its author back to rebase.\n *\n * `surfacesOf` is the one thing the two contract axes do differently: the\n * OpenAPI default walks `paths` and `components`, and the event axis passes\n * `asyncapiSurfaces` (core/asyncapi/baseline/surfaces.ts says why it is a\n * separate enumeration). Everything after the walk — the record, its sorted\n * writer, the idempotence check — is axis-neutral, which is why the event\n * planner is this function rather than a copy of it.\n */\nexport function planOpenapiBaselines(\n featureText: string,\n livingText: string,\n surfacesOf: (plain: unknown) => readonly RestatedSurface[] = restatedSurfaces,\n): OpenapiBaselinePlan {\n const doc = parseDocument(featureText);\n if (doc.errors.length > 0) return { text: null, pins: [] };\n let featPlain: unknown;\n let livingPlain: unknown;\n try {\n featPlain = doc.toJS() ?? {};\n const living = parseDocument(livingText);\n if (living.errors.length > 0) return { text: null, pins: [] };\n livingPlain = living.toJS() ?? {};\n } catch {\n return { text: null, pins: [] };\n }\n\n const current = readBaselineRecord(featPlain).record;\n const pathItems = new Map<string, Map<string, string>>();\n const components = new Map<string, string>();\n const pins: SurfacePin[] = [];\n for (const surface of surfacesOf(featPlain)) {\n // A malformed current entry reads back as absent (readBaselineRecord drops\n // it into `problems`), so this run reports `pinned` and overwrites it —\n // rebase is the command the gate's `openapi.baseline-invalid` (and\n // `asyncapi.baseline-invalid`) sends people to, and it must repair what it\n // names.\n const from = entryFor(current, surface);\n const base = {\n kind: surface.kind,\n target: surface.kind === \"path-item\" ? `${surface.path} '${surface.key}'` : surface.id,\n } as const;\n const livingValue = surfaceIn(livingPlain, surface);\n if (!livingValue.found) {\n pins.push({ ...base, status: \"unresolved\", from: from ?? null, to: null });\n continue;\n }\n const digest = valueDigest(livingValue.value);\n if (surface.kind === \"path-item\") {\n let keys = pathItems.get(surface.path);\n if (keys === undefined) pathItems.set(surface.path, (keys = new Map()));\n keys.set(surface.key, digest);\n } else {\n components.set(surface.id, digest);\n }\n pins.push({\n ...base,\n status: from === digest ? \"unchanged\" : from === undefined ? \"pinned\" : \"repinned\",\n from: from ?? null,\n to: digest,\n });\n }\n\n // The written form drops empty sections, and the whole key when both are —\n // a bare `x-loam-baselines: {}` would be bookkeeping about nothing, in every\n // delta whose surfaces are all new. An AsyncAPI document has no path items,\n // so on that axis `pathItems` is always the empty section dropped here.\n const rebuilt = buildRecord({ pathItems, components });\n const written: Record<string, unknown> = {};\n if (Object.keys(rebuilt.pathItems).length > 0) written[\"pathItems\"] = rebuilt.pathItems;\n if (Object.keys(rebuilt.components).length > 0) written[\"components\"] = rebuilt.components;\n const raw = isRecord(featPlain) ? featPlain[OPENAPI_BASELINES_KEY] : undefined;\n if (Object.keys(written).length === 0) {\n if (raw === undefined) return { text: null, pins };\n doc.deleteIn([OPENAPI_BASELINES_KEY]);\n return { text: doc.toString(), pins };\n }\n // Deep-equal, not byte-equal: the comparison must ignore key order exactly as\n // the digests do, so a record an author reordered by hand is left in their\n // order — and a second run over this function's own sorted output finds the\n // parse equal and writes nothing, which is the byte-for-byte idempotence\n // `loam rebase` promises.\n if (isDeepStrictEqual(raw, written)) return { text: null, pins };\n doc.setIn([OPENAPI_BASELINES_KEY], written);\n return { text: doc.toString(), pins };\n}\n"]}
@@ -12,7 +12,6 @@
12
12
  import { isRecord } from "../../kernel/records.js";
13
13
  import { HTTP_METHODS } from "../doc.js";
14
14
  import { FEATURE_ONLY_KEYS, OPENAPI_BASELINES_KEY, OPERATION_DIGEST_RE } from "../digest.js";
15
- export const EMPTY_RECORD = { pathItems: {}, components: {} };
16
15
  /** The record a plain-parsed feature document carries, plus everything wrong with it. */
17
16
  export function readBaselineRecord(plain) {
18
17
  const problems = [];