@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13

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 (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  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/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../../src/core/diff/semantic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The diff computation: join the base fleet (`./base-state.ts`) against the\n * working tree (`./current-state.ts`) and say, per service, what changed in\n * FLEET-MEANINGFUL terms — requirements by identity and digest, operations\n * and messages by their join tokens, cross-service joins by tuple — with the\n * current consumers of every removal named (`./victims.ts`).\n *\n * Two disciplines rule every branch here. Removal is graded against who still\n * depends on it: a removal nobody names is a warning, a removal the fleet\n * still joins to is an error and sets `breaking`. And \"nobody could look\" is\n * never \"nothing changed\": an unreadable artifact on EITHER side suspends\n * that subject's axis — recorded in `unreadable[]`, counted in the summary,\n * carried into the exit code by the command — instead of letting an\n * unreadable base read as everything-added or an unreadable working file\n * read as everything-removed.\n */\nimport { requirementDigest, type Requirement } from \"../document/spec.js\";\nimport type { Severity, Finding } from \"../vocabulary/report.js\";\nimport type { AxisState, BaseFleet, ServiceState } from \"./base-state.js\";\nimport type { CurrentFleet } from \"./current-state.js\";\nimport { consumerJoinChanges, type VictimIndex } from \"./victims.js\";\n\n/**\n * The stable finding codes `loam diff` emits. Codes are the machine contract;\n * prose may be reworded, these may not. Documented (backticked) in the\n * generated AGENTS.md's diff section — codes-drift enforces that.\n */\nexport type DiffCode =\n /** A `services/<id>/` directory exists now that did not at the base ref. */\n | \"diff.service-added\"\n /** A service present at the base ref is gone from the working tree. */\n | \"diff.service-removed\"\n /** A living requirement (by `Requirement-ID:`, else heading) not present at base. */\n | \"diff.requirement-added\"\n /** A base requirement no longer in the living spec. */\n | \"diff.requirement-removed\"\n /** Same requirement identity on both sides, `requirementDigest` moved — rebase pins never move it. */\n | \"diff.requirement-modified\"\n /** An operationId in the living contract absent at base. */\n | \"diff.op-added\"\n /** A base operationId gone from the living contract, and no current consumer names it. */\n | \"diff.op-removed\"\n /** Removed since base AND the current fleet still names it — landscape edge or foreign living requirement; details list the victims. Sets `breaking`. */\n | \"diff.op-removed-consumed\"\n /** `deprecated: true` introduced since base; details name the current consumers. */\n | \"diff.op-deprecated\"\n /** An AsyncAPI message name absent at base. */\n | \"diff.message-added\"\n /** A base message gone, nothing currently consumes it (or another service still declares it — the details say). */\n | \"diff.message-removed\"\n /** Removed since base AND still consumed — a `Consumes:` line or a consumes-edge; details list the victims. Sets `breaking`. */\n | \"diff.message-removed-consumed\"\n /** A cross-service join appeared since base: another service's living requirement now names this service's operation or message. */\n | \"diff.consumer-added\"\n /** A cross-service join present at base is gone. */\n | \"diff.consumer-removed\";\n\n/** Severity is a fact about the code, not a call-site choice — one table, no drift. */\nconst DIFF_SEVERITY: Record<DiffCode, Severity> = {\n \"diff.service-added\": \"ok\",\n \"diff.service-removed\": \"warn\",\n \"diff.requirement-added\": \"ok\",\n \"diff.requirement-removed\": \"warn\",\n \"diff.requirement-modified\": \"ok\",\n \"diff.op-added\": \"ok\",\n \"diff.op-removed\": \"warn\",\n \"diff.op-removed-consumed\": \"error\",\n \"diff.op-deprecated\": \"warn\",\n \"diff.message-added\": \"ok\",\n \"diff.message-removed\": \"warn\",\n \"diff.message-removed-consumed\": \"error\",\n \"diff.consumer-added\": \"ok\",\n \"diff.consumer-removed\": \"ok\",\n};\n\nfunction found(code: DiffCode, message: string, details?: string[]): Finding {\n const carry = details !== undefined && details.length > 0;\n return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };\n}\n\n/** One suspended axis: which side could not be read, and why. */\nexport interface UnreadableEntry {\n side: \"base\" | \"current\";\n axis: \"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\";\n path: string;\n error: string;\n}\n\nexport interface ServiceDiff {\n id: string;\n change: \"added\" | \"removed\" | \"changed\" | \"unchanged\";\n findings: Finding[];\n unreadable: UnreadableEntry[];\n /** Directories claiming this id when more than one does, on either side — every finding suspended. */\n ambiguous?: string[];\n}\n\nexport interface FleetDiff {\n services: ServiceDiff[];\n /** True iff any error-severity finding — a removal the fleet still consumes. */\n breaking: boolean;\n /** `unreadable` counts suspended axes plus ambiguous base identities: places nobody could look. */\n summary: { added: number; removed: number; modified: number; deprecated: number; unreadable: number };\n}\n\n/** A side the service does not exist on: absence everywhere, honestly. */\nconst ABSENT = { kind: \"absent\" } as const;\nconst emptyState = (id: string): ServiceState => ({ id, dir: \"\", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });\n\n/**\n * Guard one axis pair: record unreadable sides on the sink and say whether\n * the axis may be compared at all. Reading one good side against a suspended\n * one would grade absence it never proved.\n */\nfunction comparable(sink: ServiceDiff, axis: UnreadableEntry[\"axis\"], pair: { base: AxisState<unknown>; current: AxisState<unknown> }): boolean {\n for (const side of [\"base\", \"current\"] as const) {\n const state = pair[side];\n if (state.kind === \"unreadable\") sink.unreadable.push({ side, axis, path: state.path, error: state.error });\n }\n return pair.base.kind !== \"unreadable\" && pair.current.kind !== \"unreadable\";\n}\n\nconst valueOr = <T>(axis: AxisState<T[]>): T[] => (axis.kind === \"read\" ? axis.value : []);\n\n/** Identity: `Requirement-ID:` when authored, the heading otherwise — the same join `loam rebase` pins against. */\nconst reqKey = (r: Requirement): string => (r.id !== undefined ? `id:${r.id}` : `name:${r.name}`);\nconst reqLabel = (r: Requirement): string => (r.id !== undefined ? `${r.name} [${r.id}]` : r.name);\n\n/** First declaration wins a duplicated key — the same arbitrary-but-stated pick every join on these axes makes. */\nfunction firstWins<T>(items: T[], key: (item: T) => string): Map<string, T> {\n const map = new Map<string, T>();\n for (const item of items) if (!map.has(key(item))) map.set(key(item), item);\n return map;\n}\n\n/** Entry comparator by map key — explicit, because the default sort stringifies whole entries. */\nconst byKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : 1);\n\nfunction requirementsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }): void {\n // v1 diffs spec.md's requirements only; arch.spec.md is read for the\n // Consumes: join but its requirement inventory is deferred (the two axes\n // are separate namespaces, and diffing both doubles the surface). Both\n // pairs are guarded BEFORE the early return, so a service whose two spec\n // files are both unreadable reports both suspensions, not just the first.\n const specComparable = comparable(sink, \"spec\", { base: pair.base.spec, current: pair.current.spec });\n comparable(sink, \"archSpec\", { base: pair.base.archSpec, current: pair.current.archSpec });\n if (!specComparable) return;\n const base = firstWins(valueOr(pair.base.spec), reqKey);\n const current = firstWins(valueOr(pair.current.spec), reqKey);\n for (const [key, r] of [...current].sort(byKey)) {\n const before = base.get(key);\n if (before === undefined) sink.findings.push(found(\"diff.requirement-added\", `living requirement added since base: '${reqLabel(r)}'`));\n else if (requirementDigest(before) !== requirementDigest(r)) {\n sink.findings.push(found(\"diff.requirement-modified\", `living requirement modified since base: '${reqLabel(r)}'`));\n }\n }\n for (const [key, r] of [...base].sort(byKey)) {\n if (!current.has(key)) sink.findings.push(found(\"diff.requirement-removed\", `living requirement removed since base: '${reqLabel(r)}'`));\n }\n}\n\nasync function opsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: VictimIndex): Promise<void> {\n if (!comparable(sink, \"openapi\", { base: pair.base.openapi, current: pair.current.openapi })) return;\n const base = firstWins(valueOr(pair.base.openapi), (op) => op.id);\n const current = firstWins(valueOr(pair.current.openapi), (op) => op.id);\n for (const [id, op] of [...current].sort(byKey)) {\n const before = base.get(id);\n if (before === undefined) {\n sink.findings.push(found(\"diff.op-added\", `operation '${id}' added since base (${op.method.toUpperCase()} ${op.path})`));\n } else if (!before.deprecated && op.deprecated) {\n const scan = await victims.opConsumers(sink.id, id);\n // Three honest phrasings — never a confident negative over a scan that skipped an unreadable consumer document.\n const stance = scan.victims.length > 0 ? `${scan.victims.length} current consumer(s) still name it`\n : scan.suspended.length > 0 ? \"whether current consumers name it could not be fully answered\"\n : \"no current consumer names it\";\n sink.findings.push(found(\"diff.op-deprecated\", `operation '${id}' is deprecated since base — ${stance}`, [...scan.victims, ...scan.suspended]));\n }\n }\n for (const [id] of [...base].sort(byKey)) {\n if (current.has(id)) continue;\n const scan = await victims.opConsumers(sink.id, id);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.op-removed-consumed\", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, [...scan.victims, ...scan.suspended]));\n } else if (scan.suspended.length > 0) {\n // \"Nobody could be scanned\" is not \"nobody names it\" — the consumer's own\n // unreadable[] entry already carries the run to exit 1; this message must\n // not assert a negative the scan never proved.\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));\n } else {\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; no current landscape edge or living requirement names it`));\n }\n }\n}\n\nasync function messagesDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: VictimIndex): Promise<void> {\n if (!comparable(sink, \"asyncapi\", { base: pair.base.asyncapi, current: pair.current.asyncapi })) return;\n const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);\n const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);\n for (const [name] of [...current].sort(byKey)) {\n if (!base.has(name)) sink.findings.push(found(\"diff.message-added\", `message '${name}' added since base`));\n }\n for (const [name] of [...base].sort(byKey)) {\n if (current.has(name)) continue;\n // Fleet-global join: a name another current service still declares is not\n // orphaned (positive evidence only — an unreadable contract never vouches).\n const elsewhere = victims.declarers(name).filter((d) => d !== sink.id);\n if (elsewhere.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; still declared by ${elsewhere.join(\", \")}`));\n continue;\n }\n const scan = await victims.messageConsumers(sink.id, name);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.message-removed-consumed\", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, [...scan.victims, ...scan.suspended]));\n } else if (scan.suspended.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));\n } else {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; nothing currently consumes it`));\n }\n }\n}\n\n/** The graded join changes (`victims.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */\nfunction consumerFindings(input: { base: BaseFleet; current: CurrentFleet }): Map<string, Finding[]> {\n const changes = consumerJoinChanges({\n base: input.base.services,\n baseAmbiguous: new Set(input.base.ambiguous.keys()),\n current: input.current.services,\n currentAmbiguous: new Set(input.current.ambiguous.keys()),\n });\n const byProvider = new Map<string, Finding[]>();\n for (const { t, change } of changes) {\n const what = t.kind === \"op\" ? `operation '${t.token}'` : `message '${t.token}'`;\n const message = change === \"added\"\n ? `${t.consumer}'s living requirements now name this service's ${what} — a cross-service join appeared since base`\n : `${t.consumer}'s living requirements no longer name this service's ${what} — a cross-service join went away`;\n // The call sits directly in the push — assigned to a `const x = found(…)`\n // it reads as an arrow-function DECLARATION to the stable-code collector,\n // and both consumer codes silently leave the guarded set.\n byProvider.set(t.provider, [\n ...(byProvider.get(t.provider) ?? []),\n found(change === \"added\" ? \"diff.consumer-added\" : \"diff.consumer-removed\", message),\n ]);\n }\n return byProvider;\n}\n\nasync function diffService(unit: { id: string; base?: ServiceState; current?: ServiceState; victims: VictimIndex }): Promise<ServiceDiff> {\n const sink: ServiceDiff = { id: unit.id, change: \"unchanged\", findings: [], unreadable: [] };\n if (unit.base === undefined) sink.findings.push(found(\"diff.service-added\", `service '${unit.id}' added since base`));\n if (unit.current === undefined && unit.base !== undefined) {\n sink.findings.push(found(\"diff.service-removed\", `service '${unit.id}' (${unit.base.dir}) is gone from the working tree`));\n }\n const pair = { base: unit.base ?? emptyState(unit.id), current: unit.current ?? emptyState(unit.id) };\n requirementsDiff(sink, pair);\n await opsDiff(sink, pair, unit.victims);\n await messagesDiff(sink, pair, unit.victims);\n return sink;\n}\n\nexport async function computeDiff(input: { base: BaseFleet; current: CurrentFleet; victims: VictimIndex }): Promise<FleetDiff> {\n const sides = [input.base.services, input.base.ambiguous, input.current.services, input.current.ambiguous];\n const ids = [...new Set(sides.flatMap((side) => [...side.keys()]))].sort();\n const joins = consumerFindings({ base: input.base, current: input.current });\n const services: ServiceDiff[] = [];\n // Sequential on purpose: the report's order IS the contract (identical\n // states must emit identical bytes), the work per service is in-memory\n // joins, and the one expensive read — the victim index's landscape — is\n // memoised behind its first ask, so a fan-out would buy nothing.\n for (const id of ids) {\n const claimants = [...(input.base.ambiguous.get(id) ?? []), ...(input.current.ambiguous.get(id) ?? [])];\n if (claimants.length > 0) {\n // An id two directories claim — either side — has no single state to\n // diff; the whole subject suspends (containment), and the live walk's\n // `subsystem.name-collision` names the same defect for validate.\n services.push({ id, change: \"changed\", findings: [], unreadable: [], ambiguous: claimants });\n continue;\n }\n const sink = await diffService({ id, base: input.base.services.get(id), current: input.current.services.get(id), victims: input.victims });\n sink.findings.push(...(joins.get(id) ?? []));\n if (input.base.services.has(id) && !input.current.services.has(id)) sink.change = \"removed\";\n else if (!input.base.services.has(id)) sink.change = \"added\";\n else sink.change = sink.findings.length > 0 || sink.unreadable.length > 0 ? \"changed\" : \"unchanged\";\n services.push(sink);\n }\n const all = services.flatMap((s) => s.findings);\n const count = (codes: (f: Finding) => boolean): number => all.filter(codes).length;\n return {\n services,\n breaking: all.some((f) => f.severity === \"error\"),\n summary: {\n added: count((f) => f.code.endsWith(\"-added\")),\n removed: count((f) => f.code.endsWith(\"-removed\") || f.code.endsWith(\"-removed-consumed\")),\n modified: count((f) => f.code === \"diff.requirement-modified\"),\n deprecated: count((f) => f.code === \"diff.op-deprecated\"),\n unreadable:\n services.reduce((n, s) => n + s.unreadable.length, 0) +\n services.filter((s) => s.ambiguous !== undefined).length,\n },\n };\n}\n"]}
1
+ {"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../../src/core/diff/semantic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The diff computation: join the base fleet (`./base-state.ts`) against the\n * working tree (`core/consumers/current.ts`) and say, per service, what changed in\n * FLEET-MEANINGFUL terms — requirements by identity and digest, operations\n * and messages by their join tokens and their `$ref`-closure digests,\n * cross-service joins by tuple — with the authored consumers of every\n * removal, deprecation and modification named (`core/consumers/consumers.ts`),\n * and the cross-service joins that appeared or went away (`./joins.ts`).\n *\n * Two disciplines rule every branch here. Removal is graded against who still\n * depends on it: a removal nobody names is a warning, a removal the fleet\n * still joins to is an error and sets `breaking`. A MODIFICATION is never an\n * error, whoever is joined to it: whether a changed schema breaks its readers\n * is a field-level question loam hands to oasdiff on purpose, so the finding\n * names the consumers and the tool, and stops there. And \"nobody could look\" is\n * never \"nothing changed\": an unreadable artifact on EITHER side suspends\n * that subject's axis — recorded in `unreadable[]`, counted in the summary,\n * carried into the exit code by the command — instead of letting an\n * unreadable base read as everything-added or an unreadable working file\n * read as everything-removed. The one thing that is not a suspension is a\n * contract the base held byte-for-byte: somebody COULD look, at the bytes,\n * and they did not change (`comparable`, `UNTOUCHED_SKIPS`).\n */\nimport { requirementDigest, type Requirement } from \"../document/spec.js\";\nimport type { Severity, Finding } from \"../vocabulary/report.js\";\nimport type { BaseFleet } from \"./base-state.js\";\nimport { rawServiceId } from \"../kernel/ids/service.js\";\nimport type { AxisState, ServiceState } from \"../consumers/state.js\";\nimport type { CurrentFleet } from \"../consumers/current.js\";\nimport { consumerStance, scanDetails } from \"../consumers/scan.js\";\nimport type { ConsumerIndex } from \"../consumers/consumers.js\";\nimport { consumerJoinChanges } from \"./joins.js\";\n\n/**\n * The stable finding codes `loam diff` emits. Codes are the machine contract;\n * prose may be reworded, these may not. Documented (backticked) in the\n * generated AGENTS.md's diff section — codes-drift enforces that.\n */\nexport type DiffCode =\n /** A `services/<id>/` directory exists now that did not at the base ref. */\n | \"diff.service-added\"\n /** A service present at the base ref is gone from the working tree. */\n | \"diff.service-removed\"\n /** A living requirement (by `Requirement-ID:`, else heading) not present at base. */\n | \"diff.requirement-added\"\n /** A base requirement no longer in the living spec. */\n | \"diff.requirement-removed\"\n /** Same requirement identity on both sides, `requirementDigest` moved — rebase pins never move it. */\n | \"diff.requirement-modified\"\n /** An operationId in the living contract absent at base. */\n | \"diff.op-added\"\n /** A base operationId gone from the living contract, and no current consumer names it. */\n | \"diff.op-removed\"\n /** Removed since base AND the current fleet still names it — landscape edge or foreign living requirement; details list the victims. Sets `breaking`. */\n | \"diff.op-removed-consumed\"\n /** `deprecated: true` introduced since base; details name the current consumers. */\n | \"diff.op-deprecated\"\n /** Same operationId on both sides, and its node or anything its internal `$ref`s reach moved; details name the authored consumers. Whether it BREAKS them is oasdiff's question, not loam's. */\n | \"diff.op-modified\"\n /** An AsyncAPI message name absent at base. */\n | \"diff.message-added\"\n /** A base message gone, nothing currently consumes it (or another service still sends it — the message says). */\n | \"diff.message-removed\"\n /** Removed since base AND still consumed — a `Consumes:` line or a consumes-edge; details list the victims. Sets `breaking`. */\n | \"diff.message-removed-consumed\"\n /** Same message name on both sides, and its declaration or anything its internal `$ref`s reach moved; details name the authored consumers. Whether it BREAKS them is not graded. */\n | \"diff.message-modified\"\n /** A cross-service join appeared since base: another service's living requirement now names this service's operation or message. */\n | \"diff.consumer-added\"\n /** A cross-service join present at base is gone. */\n | \"diff.consumer-removed\";\n\n/** Severity is a fact about the code, not a call-site choice — one table, no drift. */\nconst DIFF_SEVERITY: Record<DiffCode, Severity> = {\n \"diff.service-added\": \"ok\",\n \"diff.service-removed\": \"warn\",\n \"diff.requirement-added\": \"ok\",\n \"diff.requirement-removed\": \"warn\",\n \"diff.requirement-modified\": \"ok\",\n \"diff.op-added\": \"ok\",\n \"diff.op-removed\": \"warn\",\n \"diff.op-removed-consumed\": \"error\",\n \"diff.op-deprecated\": \"warn\",\n \"diff.op-modified\": \"warn\",\n \"diff.message-added\": \"ok\",\n \"diff.message-removed\": \"warn\",\n \"diff.message-removed-consumed\": \"error\",\n \"diff.message-modified\": \"warn\",\n \"diff.consumer-added\": \"ok\",\n \"diff.consumer-removed\": \"ok\",\n};\n\nfunction found(code: DiffCode, message: string, details?: string[]): Finding {\n const carry = details !== undefined && details.length > 0;\n return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };\n}\n\n/** One suspended axis: which side could not be read, and why. */\nexport interface UnreadableEntry {\n side: \"base\" | \"current\";\n axis: \"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\";\n path: string;\n error: string;\n}\n\nexport interface ServiceDiff {\n id: string;\n change: \"added\" | \"removed\" | \"changed\" | \"unchanged\";\n findings: Finding[];\n unreadable: UnreadableEntry[];\n /** Directories claiming this id when more than one does, on either side — every finding suspended. */\n ambiguous?: string[];\n}\n\nexport interface FleetDiff {\n services: ServiceDiff[];\n /** True iff any error-severity finding — a removal the fleet still consumes. */\n breaking: boolean;\n /** `unreadable` counts suspended axes plus ambiguous base identities: places nobody could look. */\n summary: { added: number; removed: number; modified: number; deprecated: number; unreadable: number };\n}\n\n/**\n * A side the service does not exist on: absence everywhere, honestly. The id is\n * a key of one of the two sides' maps, each keyed by a directory's leaf name.\n */\nconst ABSENT = { kind: \"absent\" } as const;\nconst emptyState = (id: string): ServiceState => ({ id: rawServiceId(id), dir: \"\", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });\n\n/**\n * The axes on which an unreadable document the branch left byte-identical is\n * SKIPPED rather than suspended: the two contracts. Identical bytes are a\n * proof, not a guess — the same parser over the same bytes cannot read\n * differently on the two sides, so there is nothing of this document's own\n * to diff, and suspending it anyway made every `loam diff` exit 1 over a\n * fleet that holds one AsyncAPI 2.x file (unreadable by policy since\n * core/asyncapi/read.ts refused pre-3.0), whatever the branch changed.\n *\n * Not the spec axes, and that is the victims scan's doing, not an oversight:\n * an unreadable spec.md or arch.spec.md is also the document ANOTHER\n * service's removal could not be checked against (`core/consumers/consumers.ts`'s\n * `scanRequirements`), and its `unreadable[]` entry here is what carries that\n * suspension to exit 1 (`opsDiff`'s removal branch says so). No scan reads a\n * contract it could not parse as evidence of anything — `producers` takes\n * positive evidence only — so skipping one hides no other answer.\n */\nconst UNTOUCHED_SKIPS: ReadonlySet<UnreadableEntry[\"axis\"]> = new Set([\"openapi\", \"asyncapi\"]);\n\n/**\n * Guard one axis pair: record unreadable sides on the sink and say whether\n * the axis may be compared at all. Reading one good side against a suspended\n * one would grade absence it never proved. A contract unreadable on BOTH\n * sides with one byte hash is the branch's untouched file: not compared and\n * not recorded either (`UNTOUCHED_SKIPS`). A changed one, or one only one\n * side cannot read, still suspends.\n */\nfunction comparable(sink: ServiceDiff, axis: UnreadableEntry[\"axis\"], pair: { base: AxisState<unknown>; current: AxisState<unknown> }): boolean {\n const { base, current } = pair;\n if (UNTOUCHED_SKIPS.has(axis) && base.kind === \"unreadable\" && current.kind === \"unreadable\" &&\n base.bytes !== undefined && base.bytes === current.bytes) return false;\n for (const side of [\"base\", \"current\"] as const) {\n const state = pair[side];\n if (state.kind === \"unreadable\") sink.unreadable.push({ side, axis, path: state.path, error: state.error });\n }\n return pair.base.kind !== \"unreadable\" && pair.current.kind !== \"unreadable\";\n}\n\nconst valueOr = <T>(axis: AxisState<T[]>): T[] => (axis.kind === \"read\" ? axis.value : []);\n\n/** Identity: `Requirement-ID:` when authored, the heading otherwise — the same join `loam rebase` pins against. */\nconst reqKey = (r: Requirement): string => (r.id !== undefined ? `id:${r.id}` : `name:${r.name}`);\nconst reqLabel = (r: Requirement): string => (r.id !== undefined ? `${r.name} [${r.id}]` : r.name);\n\n/** First declaration wins a duplicated key — the same arbitrary-but-stated pick every join on these axes makes. */\nfunction firstWins<T>(items: T[], key: (item: T) => string): Map<string, T> {\n const map = new Map<string, T>();\n for (const item of items) if (!map.has(key(item))) map.set(key(item), item);\n return map;\n}\n\n/** Entry comparator by map key — explicit, because the default sort stringifies whole entries. */\nconst byKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : 1);\n\nfunction requirementsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }): void {\n // v1 diffs spec.md's requirements only; arch.spec.md is read for the\n // Consumes: join but its requirement inventory is deferred (the two axes\n // are separate namespaces, and diffing both doubles the surface). Both\n // pairs are guarded BEFORE the early return, so a service whose two spec\n // files are both unreadable reports both suspensions, not just the first.\n const specComparable = comparable(sink, \"spec\", { base: pair.base.spec, current: pair.current.spec });\n comparable(sink, \"archSpec\", { base: pair.base.archSpec, current: pair.current.archSpec });\n if (!specComparable) return;\n const base = firstWins(valueOr(pair.base.spec), reqKey);\n const current = firstWins(valueOr(pair.current.spec), reqKey);\n for (const [key, r] of [...current].sort(byKey)) {\n const before = base.get(key);\n if (before === undefined) sink.findings.push(found(\"diff.requirement-added\", `living requirement added since base: '${reqLabel(r)}'`));\n else if (requirementDigest(before) !== requirementDigest(r)) {\n sink.findings.push(found(\"diff.requirement-modified\", `living requirement modified since base: '${reqLabel(r)}'`));\n }\n }\n for (const [key, r] of [...base].sort(byKey)) {\n if (!current.has(key)) sink.findings.push(found(\"diff.requirement-removed\", `living requirement removed since base: '${reqLabel(r)}'`));\n }\n}\n\n/**\n * The second half of every modification message. loam joins consumers to the\n * thing that moved; it does not read what a schema SAYS (core/brief/unchecked.ts\n * lists that as unchecked), so a breaking rename and a new optional field are\n * the same finding here — the specialist diff tells them apart.\n */\nconst notGraded = (tool: string): string =>\n `loam does not grade whether the change is breaking — run ${tool} on the two states of this contract`;\n\nasync function opsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: ConsumerIndex): Promise<void> {\n if (!comparable(sink, \"openapi\", { base: pair.base.openapi, current: pair.current.openapi })) return;\n const base = firstWins(valueOr(pair.base.openapi), (op) => op.id);\n const current = firstWins(valueOr(pair.current.openapi), (op) => op.id);\n for (const [id, op] of [...current].sort(byKey)) {\n const before = base.get(id);\n if (before === undefined) {\n sink.findings.push(found(\"diff.op-added\", `operation '${id}' added since base (${op.method.toUpperCase()} ${op.path})`));\n } else if (!before.deprecated && op.deprecated) {\n const scan = await victims.opConsumers(sink.id, id);\n sink.findings.push(found(\"diff.op-deprecated\", `operation '${id}' is deprecated since base — ${consumerStance(scan)}`, scanDetails(scan)));\n } else if (before.closureDigest !== op.closureDigest || before.path !== op.path || before.method !== op.method) {\n // `else`, because setting the flag moves the digest by itself (it is a\n // key of the node): a deprecation already names the same consumers, and\n // a second finding over them would say nothing new. The digest covers\n // the `$ref` closure — a field renamed in a shared component is exactly\n // the change this code exists for, and the operation's own bytes never\n // move for it. A relocated slot is a change too, though the node is not.\n const scan = await victims.opConsumers(sink.id, id);\n sink.findings.push(found(\n \"diff.op-modified\",\n `operation '${id}' (${op.method.toUpperCase()} ${op.path}) changed since base, in its own definition or a schema it references — ${consumerStance(scan)}; ${notGraded(\"`oasdiff breaking`\")}`,\n scanDetails(scan),\n ));\n }\n }\n for (const [id] of [...base].sort(byKey)) {\n if (current.has(id)) continue;\n const scan = await victims.opConsumers(sink.id, id);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.op-removed-consumed\", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, scanDetails(scan)));\n } else if (scan.suspended.length > 0) {\n // \"Nobody could be checked\" is not \"nobody names it\", so this message\n // must not assert a negative the scan never proved. An unreadable\n // consumer document carries the run to exit 1 through its own\n // unreadable[] entry; a caller drawn with no op does not — it stays this\n // warning, so a seeded, op-less map does not fail every removal.\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} possible consumer(s) could not be checked`, scanDetails(scan)));\n } else {\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; no current edge, requirement or use-case hop names it`));\n }\n }\n}\n\nasync function messagesDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: ConsumerIndex): Promise<void> {\n if (!comparable(sink, \"asyncapi\", { base: pair.base.asyncapi, current: pair.current.asyncapi })) return;\n const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);\n const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);\n for (const [name, message] of [...current].sort(byKey)) {\n const before = base.get(name);\n if (before === undefined) {\n sink.findings.push(found(\"diff.message-added\", `message '${name}' added since base`));\n } else if (before.closureDigest !== message.closureDigest) {\n const scan = await victims.messageConsumers(sink.id, name);\n sink.findings.push(found(\n \"diff.message-modified\",\n `message '${name}' changed since base, in its declaration or a schema its payload references — ${consumerStance(scan)}; ${notGraded(\"`asyncapi diff`\")}`,\n scanDetails(scan),\n ));\n }\n }\n for (const [name] of [...base].sort(byKey)) {\n if (current.has(name)) continue;\n // Fleet-global join: a name another current service still SENDS is not\n // orphaned (positive evidence only — an unreadable contract never vouches,\n // and a service that only receives it is one of its consumers, never its\n // producer: `producers` says why that distinction is the gate's too). Nor\n // is one an #external element still publishes, by validate's own rule\n // (`externalProducer` says why): counting the fleet's senders alone made\n // this removal an error that the next `validate --all` called clean.\n const elsewhere = (await victims.producers(name)).filter((d) => d !== sink.id);\n const outside = await victims.externalProducer(name);\n const senders = outside === null ? elsewhere : [...elsewhere, `${outside} (#external)`];\n if (senders.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; still sent by ${senders.join(\", \")}`));\n continue;\n }\n const scan = await victims.messageConsumers(sink.id, name);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.message-removed-consumed\", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, scanDetails(scan)));\n } else if (scan.suspended.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scanDetails(scan)));\n } else {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; nothing currently consumes it`));\n }\n }\n}\n\n/** The graded join changes (`./joins.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */\nfunction consumerFindings(input: { base: BaseFleet; current: CurrentFleet }): Map<string, Finding[]> {\n const changes = consumerJoinChanges({\n base: input.base.services,\n baseAmbiguous: new Set(input.base.ambiguous.keys()),\n current: input.current.services,\n currentAmbiguous: new Set(input.current.ambiguous.keys()),\n });\n const byProvider = new Map<string, Finding[]>();\n for (const { t, change } of changes) {\n const what = t.kind === \"op\" ? `operation '${t.token}'` : `message '${t.token}'`;\n const message = change === \"added\"\n ? `${t.consumer}'s living requirements now name this service's ${what} — a cross-service join appeared since base`\n : `${t.consumer}'s living requirements no longer name this service's ${what} — a cross-service join went away`;\n // The call sits directly in the push — assigned to a `const x = found(…)`\n // it reads as an arrow-function DECLARATION to the stable-code collector,\n // and both consumer codes silently leave the guarded set.\n byProvider.set(t.provider, [\n ...(byProvider.get(t.provider) ?? []),\n found(change === \"added\" ? \"diff.consumer-added\" : \"diff.consumer-removed\", message),\n ]);\n }\n return byProvider;\n}\n\nasync function diffService(unit: { id: string; base?: ServiceState; current?: ServiceState; victims: ConsumerIndex }): Promise<ServiceDiff> {\n const sink: ServiceDiff = { id: unit.id, change: \"unchanged\", findings: [], unreadable: [] };\n if (unit.base === undefined) sink.findings.push(found(\"diff.service-added\", `service '${unit.id}' added since base`));\n if (unit.current === undefined && unit.base !== undefined) {\n sink.findings.push(found(\"diff.service-removed\", `service '${unit.id}' (${unit.base.dir}) is gone from the working tree`));\n }\n const pair = { base: unit.base ?? emptyState(unit.id), current: unit.current ?? emptyState(unit.id) };\n requirementsDiff(sink, pair);\n await opsDiff(sink, pair, unit.victims);\n await messagesDiff(sink, pair, unit.victims);\n return sink;\n}\n\nexport async function computeDiff(input: { base: BaseFleet; current: CurrentFleet; victims: ConsumerIndex }): Promise<FleetDiff> {\n const sides = [input.base.services, input.base.ambiguous, input.current.services, input.current.ambiguous];\n const ids = [...new Set(sides.flatMap((side) => [...side.keys()]))].sort();\n const joins = consumerFindings({ base: input.base, current: input.current });\n const services: ServiceDiff[] = [];\n // Sequential on purpose: the report's order IS the contract (identical\n // states must emit identical bytes), the work per service is in-memory\n // joins, and the one expensive read — the victim index's landscape — is\n // memoised behind its first ask, so a fan-out would buy nothing.\n for (const id of ids) {\n const claimants = [...(input.base.ambiguous.get(id) ?? []), ...(input.current.ambiguous.get(id) ?? [])];\n if (claimants.length > 0) {\n // An id two directories claim — either side — has no single state to\n // diff; the whole subject suspends (containment), and the live walk's\n // `subsystem.name-collision` names the same defect for validate.\n services.push({ id, change: \"changed\", findings: [], unreadable: [], ambiguous: claimants });\n continue;\n }\n const sink = await diffService({ id, base: input.base.services.get(id), current: input.current.services.get(id), victims: input.victims });\n sink.findings.push(...(joins.get(id) ?? []));\n if (input.base.services.has(id) && !input.current.services.has(id)) sink.change = \"removed\";\n else if (!input.base.services.has(id)) sink.change = \"added\";\n else sink.change = sink.findings.length > 0 || sink.unreadable.length > 0 ? \"changed\" : \"unchanged\";\n services.push(sink);\n }\n const all = services.flatMap((s) => s.findings);\n const count = (codes: (f: Finding) => boolean): number => all.filter(codes).length;\n return {\n services,\n breaking: all.some((f) => f.severity === \"error\"),\n summary: {\n added: count((f) => f.code.endsWith(\"-added\")),\n removed: count((f) => f.code.endsWith(\"-removed\") || f.code.endsWith(\"-removed-consumed\")),\n // Requirements, operations and messages alike: a reader scanning the one\n // summary line must not see \"0 modified\" over a PR that renamed a field.\n modified: count((f) => f.code.endsWith(\"-modified\")),\n deprecated: count((f) => f.code === \"diff.op-deprecated\"),\n unreadable:\n services.reduce((n, s) => n + s.unreadable.length, 0) +\n services.filter((s) => s.ambiguous !== undefined).length,\n },\n };\n}\n"]}
package/dist/core/docs.js CHANGED
@@ -14,7 +14,7 @@ import { join, resolve } from "node:path";
14
14
  import { AGENTS_MD } from "./agent/agents-md.js";
15
15
  import { LANDSCAPE_STUB } from "./scaffold/landscape.js";
16
16
  import { README_FILENAME, README_MD } from "./scaffold/readme.js";
17
- import { AGENTS_FILENAME } from "./repo/paths.js";
17
+ import { AGENTS_FILENAME, LIKEC4_PROJECT_FILENAME, LIKEC4_ROOT_PROJECT } from "./repo/paths.js";
18
18
  /**
19
19
  * Top-level layout of the shared docs repo, and part of its identity rather
20
20
  * than a convenience: `services/` is what makes a directory a docs repo —
@@ -37,33 +37,42 @@ const EMPTY_SUBDIRS = [
37
37
  * tool other than itself.
38
38
  *
39
39
  * LikeC4's workspace loader merges EVERY `.likec4` file under a directory tree
40
- * into one model. loam's layout is the opposite by design: the landscape,
41
- * each `services/<id>/model.likec4` and each `features/<FEAT>/delta.likec4` is
42
- * parsed in isolation (`loadSource`, one file, one throwaway workspace), so
43
- * every one of them legitimately declares its own `specification { … }` block
44
- * and re-declares the elements it needs to talk about. Point the real renderer
45
- * at the repository root and those declarations collide: on loam's own
46
- * `examples/docs` — four files — `npx likec4 start` reported 16 errors, sixteen
47
- * duplicate kinds and names, while `loam validate --all` reported none. Both
48
- * were right; only one of them was reading the tree as a workspace.
40
+ * into one model, and whether that is what loam wants depends on the document.
41
+ * A feature's `features/<FEAT>/delta.likec4` is parsed in isolation and declares
42
+ * its own `specification { … }` block, so merging it into the root would report
43
+ * every declaration as a duplicate — measured on loam's own `examples/docs`:
44
+ * `npx likec4 start` reported 16 errors, sixteen duplicate kinds and names,
45
+ * while `loam validate --all` reported none. Both were right; only one of them
46
+ * was reading the tree as a workspace. That directory is therefore excluded
47
+ * here, permanently.
49
48
  *
50
- * So the root is declared as one LikeC4 project holding the landscape, and the
51
- * two directories whose files are meant to be read alone are excluded from it.
52
- * That makes `npx likec4 start` in the docs repo — the command loam's own docs
53
- * have always recommended — render the fleet map instead of failing. A service
54
- * model or a feature delta is still rendered by pointing the renderer at its own
55
- * directory, which is what parsing them in isolation meant all along.
49
+ * `services/**` is NOT, and that is this file's one substantive change since the
50
+ * axis that let a model EXTEND the fleet map. A model written that way declares
51
+ * no kinds of its own and says what is inside an element the map already draws,
52
+ * so the root project is the only place it parses — and excluding it renders the
53
+ * service as a box with nothing inside. A model that STANDS ALONE still has to
54
+ * be excluded, and `loam subsystem sync` is what adds `services/<tree>/**` for
55
+ * each one and removes the entry again when a model migrates. A fresh repo's
56
+ * models are extending, so the scaffold starts with no `services/` entry at all.
56
57
  *
57
58
  * `**\/node_modules/**` is repeated because naming `exclude` replaces LikeC4's
58
- * default rather than adding to it. loam reads none of this file: it is written
59
- * once, never re-read, and a team that wants a different project layout owns it
60
- * like every other scaffolded file.
59
+ * default rather than adding to it.
60
+ *
61
+ * OWNERSHIP, corrected. This file is still the team's — written once by
62
+ * `loam init --create`, never regenerated, and a team that wants a different
63
+ * project layout keeps it. But loam is no longer blind to it: it reads ONE
64
+ * literal fact out of it (`core/c4/root-project/exclude.ts`, `readRootExclude`
65
+ * — the `exclude` list and nothing else), because that list decides whether an
66
+ * extending model is renderable at all, and it MAINTAINS the `services/` entries
67
+ * in it through `loam subsystem sync`. Every other key, and every entry that is
68
+ * not about `services/`, is left exactly as written. The filename and the root
69
+ * project's name are spelled in `repo/paths.ts`, beside every other file loam
70
+ * writes.
61
71
  */
62
- export const LIKEC4_PROJECT_FILENAME = "likec4.config.json";
63
72
  export const LIKEC4_PROJECT_CONFIG = `${JSON.stringify({
64
- name: "fleet",
73
+ name: LIKEC4_ROOT_PROJECT,
65
74
  title: "Fleet landscape",
66
- exclude: ["**/node_modules/**", "services/**", "features/**"],
75
+ exclude: ["**/node_modules/**", "features/**"],
67
76
  }, null, 2)}\n`;
68
77
  /**
69
78
  * The placeholder that makes an empty scaffolded directory survive a clone.
@@ -1 +1 @@
1
- {"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/core/docs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scaffolding for the shared docs repo.\n *\n * There is deliberately no manifest. `init` used to write a `loam.docs.json`\n * listing the repo's services; nothing ever read it — `repo.ts` enumerates from\n * the filesystem, because files are the source of truth — and nothing ever\n * updated it, so it named an empty fleet forever. A second list of services is\n * exactly the drift `loam validate` now cross-checks the landscape for; the\n * cheapest way to keep it honest is not to have it.\n */\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { AGENTS_MD } from \"./agent/agents-md.js\";\nimport { LANDSCAPE_STUB } from \"./scaffold/landscape.js\";\nimport { README_FILENAME, README_MD } from \"./scaffold/readme.js\";\nimport { AGENTS_FILENAME } from \"./repo/paths.js\";\n\n/**\n * Top-level layout of the shared docs repo, and part of its identity rather\n * than a convenience: `services/` is what makes a directory a docs repo —\n * `init` will not join one without it, and every enumerating command refuses a\n * `docsDir` that has none. So it is laid down even when nothing is in it yet.\n */\nexport const DOCS_SUBDIRS = [\"architecture\", \"services\", \"features\"] as const;\n\n/**\n * The scaffolded directories that start out empty, and the file that keeps each\n * of them in version control. See {@link GITKEEP}. Spelled as pairs so the\n * marker's name is written once — `listServices` and `listFeatures` enumerate\n * subdirectories only, so a file here is invisible to both.\n */\nconst EMPTY_SUBDIRS = [\n [\"services\", \".gitkeep\"],\n [\"features\", \".gitkeep\"],\n] as const;\n\n/**\n * The LikeC4 project file, and the one thing in the docs repo loam writes for a\n * tool other than itself.\n *\n * LikeC4's workspace loader merges EVERY `.likec4` file under a directory tree\n * into one model. loam's layout is the opposite by design: the landscape,\n * each `services/<id>/model.likec4` and each `features/<FEAT>/delta.likec4` is\n * parsed in isolation (`loadSource`, one file, one throwaway workspace), so\n * every one of them legitimately declares its own `specification { … }` block\n * and re-declares the elements it needs to talk about. Point the real renderer\n * at the repository root and those declarations collide: on loam's own\n * `examples/docs` — four files — `npx likec4 start` reported 16 errors, sixteen\n * duplicate kinds and names, while `loam validate --all` reported none. Both\n * were right; only one of them was reading the tree as a workspace.\n *\n * So the root is declared as one LikeC4 project holding the landscape, and the\n * two directories whose files are meant to be read alone are excluded from it.\n * That makes `npx likec4 start` in the docs repo — the command loam's own docs\n * have always recommended — render the fleet map instead of failing. A service\n * model or a feature delta is still rendered by pointing the renderer at its own\n * directory, which is what parsing them in isolation meant all along.\n *\n * `**\\/node_modules/**` is repeated because naming `exclude` replaces LikeC4's\n * default rather than adding to it. loam reads none of this file: it is written\n * once, never re-read, and a team that wants a different project layout owns it\n * like every other scaffolded file.\n */\nexport const LIKEC4_PROJECT_FILENAME = \"likec4.config.json\";\n\nexport const LIKEC4_PROJECT_CONFIG = `${JSON.stringify(\n {\n name: \"fleet\",\n title: \"Fleet landscape\",\n exclude: [\"**/node_modules/**\", \"services/**\", \"features/**\"],\n },\n null,\n 2,\n)}\\n`;\n\n/**\n * The placeholder that makes an empty scaffolded directory survive a clone.\n *\n * git tracks files, not directories, so `services/` and `features/` — created\n * empty by `loam init --create` — were absent for everyone after the first push:\n * the person who ran init had a green repo, and the second person to clone it\n * got `doctor.services-missing`, a BLOCKER, on a repository nobody had touched.\n * A repo that cannot survive being cloned is not a shared docs repo.\n */\nconst GITKEEP = \"\";\n\n/**\n * The docs repo's own loam.json. It makes the docs repo self-describing: a\n * command run from inside it (or from any directory under it) finds this file\n * first and resolves the fleet to the repo it is standing in, instead of\n * walking out to whatever service repo happens to be above.\n *\n * `\".\"` and not an absolute path for the same reason every other docsDir is\n * stored as written: this file is committed and cloned to machines whose\n * directory layout nobody here can predict.\n */\nconst DOCS_SELF_CONFIG = `${JSON.stringify({ docsDir: \".\" }, null, 2)}\\n`;\n\nexport interface ScaffoldResult {\n root: string;\n created: string[];\n}\n\n/** Idempotently create the docs-repo skeleton. Existing files/dirs are left untouched. */\nexport async function scaffoldDocs(docsDir: string): Promise<ScaffoldResult> {\n const root = resolve(docsDir);\n const created: string[] = [];\n\n await mkdir(root, { recursive: true });\n\n for (const dir of DOCS_SUBDIRS) {\n const p = join(root, dir);\n if (!existsSync(p)) {\n await mkdir(p, { recursive: true });\n created.push(p);\n }\n }\n\n // Never overwritten, all three of them — a team's own house rules, their own\n // map and their own config outrank the template. The order here IS the order\n // `init` probes for what it will skip; keep the two lists in step.\n for (const [rel, content] of docsRepoFiles()) {\n const path = join(root, rel);\n if (!existsSync(path)) {\n await writeFile(path, content, \"utf8\");\n created.push(path);\n }\n }\n\n return { root, created };\n}\n\nexport interface DocsRepoFileOptions {\n /**\n * Comment lines prepended to the landscape stub, for a scaffold with a reason\n * of its own to hand over an empty map — `migrate-openspec` stages a docs repo\n * out of a corpus that HAS no topology, and says so where the migrator reads.\n *\n * A parameter and not a second template on purpose. The rest of the stub —\n * how to declare a service, how to bind an edge to an operationId, why there\n * is no `views` block — is the same advice however the repo came to exist, and\n * the migration used to carry its own shorter copy of it: two files that were\n * both correct on the day they were written and could only diverge after.\n */\n landscapePreamble?: string;\n}\n\n/**\n * The files every docs repo starts with, relative to its root, in creation\n * order. `init` scaffolds them directly; `migrate-openspec` stages the same\n * bytes into its target, because the whole point of migrating into a docs repo\n * is that it be the docs repo `loam init` makes.\n *\n * Exported as paths only (via `plannedDocsFiles`) where `init` reports what it\n * will skip — a duplicate list is exactly the drift that made `created +\n * skipped` disagree with reality.\n */\nexport function docsRepoFiles(opts: DocsRepoFileOptions = {}): Array<[string, string]> {\n const preamble = opts.landscapePreamble === undefined ? \"\" : `${opts.landscapePreamble.trimEnd()}\\n//\\n`;\n return [\n // The one file addressed to a person, first because a forge renders it as\n // the landing page and a reader meets it before anything else here.\n [README_FILENAME, README_MD],\n // The process contract lives with the docs it describes, so an agent handed\n // only the docs repo still knows the cycle.\n [AGENTS_FILENAME, AGENTS_MD],\n [join(\"architecture\", \"landscape.likec4\"), `${preamble}${LANDSCAPE_STUB}`],\n [\"loam.json\", DOCS_SELF_CONFIG],\n [LIKEC4_PROJECT_FILENAME, LIKEC4_PROJECT_CONFIG],\n // The two directories git would otherwise drop on the way to the next\n // clone. `architecture/` needs none — the landscape above is in it.\n ...EMPTY_SUBDIRS.map(([dir, keep]) => [join(dir, keep), GITKEEP] as [string, string]),\n ];\n}\n\n/** Everything `scaffoldDocs(docsDir)` would create, in the order it creates it. */\nexport function plannedDocsFiles(docsDir: string): string[] {\n const root = resolve(docsDir);\n return [\n ...DOCS_SUBDIRS.map((d) => join(root, d)),\n ...docsRepoFiles().map(([rel]) => join(root, rel)),\n ];\n}\n"]}
1
+ {"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/core/docs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scaffolding for the shared docs repo.\n *\n * There is deliberately no manifest. `init` used to write a `loam.docs.json`\n * listing the repo's services; nothing ever read it — `repo.ts` enumerates from\n * the filesystem, because files are the source of truth — and nothing ever\n * updated it, so it named an empty fleet forever. A second list of services is\n * exactly the drift `loam validate` now cross-checks the landscape for; the\n * cheapest way to keep it honest is not to have it.\n */\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { AGENTS_MD } from \"./agent/agents-md.js\";\nimport { LANDSCAPE_STUB } from \"./scaffold/landscape.js\";\nimport { README_FILENAME, README_MD } from \"./scaffold/readme.js\";\nimport { AGENTS_FILENAME, LIKEC4_PROJECT_FILENAME, LIKEC4_ROOT_PROJECT } from \"./repo/paths.js\";\n\n/**\n * Top-level layout of the shared docs repo, and part of its identity rather\n * than a convenience: `services/` is what makes a directory a docs repo —\n * `init` will not join one without it, and every enumerating command refuses a\n * `docsDir` that has none. So it is laid down even when nothing is in it yet.\n */\nexport const DOCS_SUBDIRS = [\"architecture\", \"services\", \"features\"] as const;\n\n/**\n * The scaffolded directories that start out empty, and the file that keeps each\n * of them in version control. See {@link GITKEEP}. Spelled as pairs so the\n * marker's name is written once — `listServices` and `listFeatures` enumerate\n * subdirectories only, so a file here is invisible to both.\n */\nconst EMPTY_SUBDIRS = [\n [\"services\", \".gitkeep\"],\n [\"features\", \".gitkeep\"],\n] as const;\n\n/**\n * The LikeC4 project file, and the one thing in the docs repo loam writes for a\n * tool other than itself.\n *\n * LikeC4's workspace loader merges EVERY `.likec4` file under a directory tree\n * into one model, and whether that is what loam wants depends on the document.\n * A feature's `features/<FEAT>/delta.likec4` is parsed in isolation and declares\n * its own `specification { … }` block, so merging it into the root would report\n * every declaration as a duplicate — measured on loam's own `examples/docs`:\n * `npx likec4 start` reported 16 errors, sixteen duplicate kinds and names,\n * while `loam validate --all` reported none. Both were right; only one of them\n * was reading the tree as a workspace. That directory is therefore excluded\n * here, permanently.\n *\n * `services/**` is NOT, and that is this file's one substantive change since the\n * axis that let a model EXTEND the fleet map. A model written that way declares\n * no kinds of its own and says what is inside an element the map already draws,\n * so the root project is the only place it parses — and excluding it renders the\n * service as a box with nothing inside. A model that STANDS ALONE still has to\n * be excluded, and `loam subsystem sync` is what adds `services/<tree>/**` for\n * each one and removes the entry again when a model migrates. A fresh repo's\n * models are extending, so the scaffold starts with no `services/` entry at all.\n *\n * `**\\/node_modules/**` is repeated because naming `exclude` replaces LikeC4's\n * default rather than adding to it.\n *\n * OWNERSHIP, corrected. This file is still the team's — written once by\n * `loam init --create`, never regenerated, and a team that wants a different\n * project layout keeps it. But loam is no longer blind to it: it reads ONE\n * literal fact out of it (`core/c4/root-project/exclude.ts`, `readRootExclude`\n * — the `exclude` list and nothing else), because that list decides whether an\n * extending model is renderable at all, and it MAINTAINS the `services/` entries\n * in it through `loam subsystem sync`. Every other key, and every entry that is\n * not about `services/`, is left exactly as written. The filename and the root\n * project's name are spelled in `repo/paths.ts`, beside every other file loam\n * writes.\n */\nexport const LIKEC4_PROJECT_CONFIG = `${JSON.stringify(\n {\n name: LIKEC4_ROOT_PROJECT,\n title: \"Fleet landscape\",\n exclude: [\"**/node_modules/**\", \"features/**\"],\n },\n null,\n 2,\n)}\\n`;\n\n/**\n * The placeholder that makes an empty scaffolded directory survive a clone.\n *\n * git tracks files, not directories, so `services/` and `features/` — created\n * empty by `loam init --create` — were absent for everyone after the first push:\n * the person who ran init had a green repo, and the second person to clone it\n * got `doctor.services-missing`, a BLOCKER, on a repository nobody had touched.\n * A repo that cannot survive being cloned is not a shared docs repo.\n */\nconst GITKEEP = \"\";\n\n/**\n * The docs repo's own loam.json. It makes the docs repo self-describing: a\n * command run from inside it (or from any directory under it) finds this file\n * first and resolves the fleet to the repo it is standing in, instead of\n * walking out to whatever service repo happens to be above.\n *\n * `\".\"` and not an absolute path for the same reason every other docsDir is\n * stored as written: this file is committed and cloned to machines whose\n * directory layout nobody here can predict.\n */\nconst DOCS_SELF_CONFIG = `${JSON.stringify({ docsDir: \".\" }, null, 2)}\\n`;\n\nexport interface ScaffoldResult {\n root: string;\n created: string[];\n}\n\n/** Idempotently create the docs-repo skeleton. Existing files/dirs are left untouched. */\nexport async function scaffoldDocs(docsDir: string): Promise<ScaffoldResult> {\n const root = resolve(docsDir);\n const created: string[] = [];\n\n await mkdir(root, { recursive: true });\n\n for (const dir of DOCS_SUBDIRS) {\n const p = join(root, dir);\n if (!existsSync(p)) {\n await mkdir(p, { recursive: true });\n created.push(p);\n }\n }\n\n // Never overwritten, all three of them — a team's own house rules, their own\n // map and their own config outrank the template. The order here IS the order\n // `init` probes for what it will skip; keep the two lists in step.\n for (const [rel, content] of docsRepoFiles()) {\n const path = join(root, rel);\n if (!existsSync(path)) {\n await writeFile(path, content, \"utf8\");\n created.push(path);\n }\n }\n\n return { root, created };\n}\n\nexport interface DocsRepoFileOptions {\n /**\n * Comment lines prepended to the landscape stub, for a scaffold with a reason\n * of its own to hand over an empty map — `migrate-openspec` stages a docs repo\n * out of a corpus that HAS no topology, and says so where the migrator reads.\n *\n * A parameter and not a second template on purpose. The rest of the stub —\n * how to declare a service, how to bind an edge to an operationId, why there\n * is no `views` block — is the same advice however the repo came to exist, and\n * the migration used to carry its own shorter copy of it: two files that were\n * both correct on the day they were written and could only diverge after.\n */\n landscapePreamble?: string;\n}\n\n/**\n * The files every docs repo starts with, relative to its root, in creation\n * order. `init` scaffolds them directly; `migrate-openspec` stages the same\n * bytes into its target, because the whole point of migrating into a docs repo\n * is that it be the docs repo `loam init` makes.\n *\n * Exported as paths only (via `plannedDocsFiles`) where `init` reports what it\n * will skip — a duplicate list is exactly the drift that made `created +\n * skipped` disagree with reality.\n */\nexport function docsRepoFiles(opts: DocsRepoFileOptions = {}): Array<[string, string]> {\n const preamble = opts.landscapePreamble === undefined ? \"\" : `${opts.landscapePreamble.trimEnd()}\\n//\\n`;\n return [\n // The one file addressed to a person, first because a forge renders it as\n // the landing page and a reader meets it before anything else here.\n [README_FILENAME, README_MD],\n // The process contract lives with the docs it describes, so an agent handed\n // only the docs repo still knows the cycle.\n [AGENTS_FILENAME, AGENTS_MD],\n [join(\"architecture\", \"landscape.likec4\"), `${preamble}${LANDSCAPE_STUB}`],\n [\"loam.json\", DOCS_SELF_CONFIG],\n [LIKEC4_PROJECT_FILENAME, LIKEC4_PROJECT_CONFIG],\n // The two directories git would otherwise drop on the way to the next\n // clone. `architecture/` needs none — the landscape above is in it.\n ...EMPTY_SUBDIRS.map(([dir, keep]) => [join(dir, keep), GITKEEP] as [string, string]),\n ];\n}\n\n/** Everything `scaffoldDocs(docsDir)` would create, in the order it creates it. */\nexport function plannedDocsFiles(docsDir: string): string[] {\n const root = resolve(docsDir);\n return [\n ...DOCS_SUBDIRS.map((d) => join(root, d)),\n ...docsRepoFiles().map(([rel]) => join(root, rel)),\n ];\n}\n"]}
@@ -17,7 +17,7 @@ import { LOAM_VERSION } from "../envelope/version.js";
17
17
  import { agentsPath } from "../repo/paths.js";
18
18
  import { DELIVERIES, plannedCommandFiles, } from "../agent/scaffold.js";
19
19
  import { AGENT_TOOLS } from "../agent/tools/registry.js";
20
- import { agentsStaleFinding, binaryBehindFinding, agentsStampLine, agentsStampVersion, versionTrails, } from "../agent/agents-stamp.js";
20
+ import { agentsStaleFinding, binaryBehindFinding, agentsStampLine, agentsStampVersion, restamped, versionTrails, } from "../agent/agents-stamp.js";
21
21
  import {} from "./report.js";
22
22
  export function recordedTools(config) {
23
23
  const ids = (config?.agentTools ?? []).filter((t) => t in AGENT_TOOLS);
@@ -60,20 +60,34 @@ export function heldDeliveries(repoRoot, tool, profile = "full") {
60
60
  }
61
61
  /**
62
62
  * Of the planned files that ARE here, the ones whose stamp is missing or older
63
- * than this binary.
63
+ * than this binary AND whose body is not the one this binary writes.
64
64
  *
65
- * The STALENESS check reads only the stamp, never the body. Safe refresh is a
66
- * separate decision made by `init`: the current bytes must match the digest
67
- * recorded when loam wrote them. That proof lets an unchanged pointer refresh
68
- * without turning a differing body into an offer to overwrite it. A stamp is a
69
- * different claim — it says which loam wrote these instructions — and it can
70
- * be false while every edit around it is legitimate. Same doctrine as
71
- * AGENTS.md (agents-stamp.ts); a customized file is repaired only when a human
72
- * reviews it and bumps the stamp or deletes it for `loam init` to recreate.
65
+ * Both halves, and the second is what makes the finding mean something. The
66
+ * stamp sits inside the bytes, so every release moves it in every generated
67
+ * file — and while the stamp was the whole test, every pointer in every repo
68
+ * of a fleet went stale on every upgrade, even when the release had not
69
+ * changed a word of the pointer. A warning that is always on is one people
70
+ * learn to read past, and it then hid the one release where a pointer's body
71
+ * really had moved. So a file whose bytes, its stamp line aside, are exactly
72
+ * what this binary would write is current: it instructs an agent with this
73
+ * binary's text, whichever release happened to write it. Line endings are
74
+ * compared the same way, because a Windows checkout with `core.autocrlf`
75
+ * hands back CRLF for a body nobody touched.
76
+ *
77
+ * A file that differs in its body falls back to the stamp, which is the claim
78
+ * it always was — which loam a person last checked these instructions
79
+ * against. That is why a CURRENT stamp over a customized body stays quiet:
80
+ * somebody reviewed it and said so. Safe refresh is a separate decision made by
81
+ * `init`: the current bytes must match the digest recorded when loam wrote
82
+ * them, which lets an unchanged pointer refresh without turning a differing
83
+ * body into an offer to overwrite it. Same doctrine as AGENTS.md
84
+ * (agents-stamp.ts); a customized file is repaired only when a human reviews it
85
+ * and bumps the stamp or deletes it for `loam init` to recreate.
73
86
  *
74
87
  * Absence of a stamp counts, because that is what every file written before
75
88
  * stamping existed looks like — precisely the "initialized by an older loam"
76
- * population this check is for.
89
+ * population this check is for — and such a file cannot match the current
90
+ * body, which opens with the stamp.
77
91
  */
78
92
  export async function staleAgentFiles(repoRoot, planned) {
79
93
  // Read them together rather than one after another. `planned` is bounded by
@@ -92,10 +106,12 @@ export async function staleAgentFiles(repoRoot, planned) {
92
106
  return null;
93
107
  }
94
108
  const stamp = agentsStampVersion(text);
95
- if (stamp === null || versionTrails(stamp, LOAM_VERSION)) {
96
- return relative(repoRoot, file.path).split(/[\\/]/).join("/");
97
- }
98
- return null;
109
+ if (stamp !== null && !versionTrails(stamp, LOAM_VERSION))
110
+ return null;
111
+ const lf = (s) => s.replace(/\r\n/g, "\n");
112
+ if (lf(restamped(text, LOAM_VERSION)) === lf(file.content))
113
+ return null;
114
+ return relative(repoRoot, file.path).split(/[\\/]/).join("/");
99
115
  }));
100
116
  return graded.filter((p) => p !== null);
101
117
  }
@@ -145,9 +161,9 @@ export async function inspectAgentSurface(repoRoot, config, docsDir, findings) {
145
161
  findings.push({
146
162
  severity: "warning",
147
163
  code: "doctor.agent-files-stale",
148
- message: `${staleFiles.length} of the ${planned.length} command and skill files here carry no `
149
- + `\`${agentsStampLine(LOAM_VERSION)}\` stamp, or one older than this loam — the protocol and `
150
- + `code tables they instruct an agent with may describe a loam that no longer exists: `
164
+ message: `${staleFiles.length} of the ${planned.length} command and skill files here differ from the body `
165
+ + `loam v${LOAM_VERSION} writes and carry no \`${agentsStampLine(LOAM_VERSION)}\` stamp, or one older `
166
+ + `than this loam — what they instruct an agent with may describe a loam that no longer exists: `
151
167
  + `${staleFiles.join(", ")}`,
152
168
  fix: "Read each against the body this loam writes, then set its stamp line to "
153
169
  + `\`${agentsStampLine(LOAM_VERSION)}\`; or re-run \`loam init\`. A file whose recorded digest `
@@ -1 +1 @@
1
- {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../../src/core/doctor/agents.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The agent surface: which tool files this repo should hold, which it does, and\n * whether they still describe this binary.\n *\n * The registry filter lives here rather than in the config loader because a\n * config written by a newer binary may name a tool this one has never heard of,\n * and refusing to load it would break every command rather than just this\n * check. A tool loam cannot plan files for is a tool it has nothing to say\n * about — so the preflight stays read-only instead of crashing on a config from\n * the future.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport { type LoamConfig } from \"../envelope/config.js\";\nimport { LOAM_VERSION } from \"../envelope/version.js\";\nimport { agentsPath } from \"../repo/paths.js\";\nimport {\n DELIVERIES,\n plannedCommandFiles,\n type AgentProfile,\n type Delivery,\n} from \"../agent/scaffold.js\";\nimport { AGENT_TOOLS } from \"../agent/tools/registry.js\";\nimport {\n agentsStaleFinding,\n binaryBehindFinding,\n agentsStampLine,\n agentsStampVersion,\n versionTrails,\n} from \"../agent/agents-stamp.js\";\nimport { type AgentSurface, type DoctorFinding } from \"./report.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\nexport function recordedTools(config: LoamConfig | null): string[] | null {\n const ids = (config?.agentTools ?? []).filter((t) => t in AGENT_TOOLS);\n return ids.length === 0 ? null : ids;\n}\n\n/**\n * Which tools this repo has agent files for, asked of the layout itself: a tool\n * counts when any file it would be given is already there.\n *\n * The fallback for a loam.json written before `agentTools` existed, and the\n * only answer a hand-written one can have — so its absence must never be the\n * thing that reports a problem. This asks a narrower question than `init`'s\n * own scan, and on purpose: init scans for a tool's dot-directory to decide\n * what a repo would LIKE, while doctor grades what loam has already put there.\n * A repo that runs Claude Code and took `--no-commands --no-skills` has chosen\n * to have no loam files, and is not behind the binary for it.\n */\nexport function detectedTools(repoRoot: string): string[] {\n return Object.keys(AGENT_TOOLS).filter((id) =>\n plannedCommandFiles(repoRoot, [id]).some((f) => existsSync(f.path)));\n}\n\n/**\n * Which deliveries this repo actually HOLDS for one tool — a delivery counts\n * when at least one of its files is on disk.\n *\n * `loam init --no-skills` (or `--no-commands`) records the tool in `agentTools`\n * and writes one delivery, so planning both and subtracting what exists reported\n * every skill file as missing on a brand-new repo — under a message saying it\n * \"was initialized by an older loam\" and a `fix` that re-runs init without the\n * flag that was the whole point. Which deliveries a repo asked for is written\n * down nowhere, and must not be: the files ARE the record (there is no state\n * file loam can disagree with). So the question is asked of them.\n *\n * A tool with NO files at all is the other story — recorded, then deleted — and\n * the caller falls back to the full plan there, which is what the finding's\n * \"or they were deleted\" already says.\n */\nexport function heldDeliveries(\n repoRoot: string,\n tool: string,\n profile: AgentProfile = \"full\",\n): readonly Delivery[] {\n const held = DELIVERIES.filter((d) =>\n plannedCommandFiles(repoRoot, [tool], [d], profile).some((f) => existsSync(f.path)));\n return held.length > 0 ? held : DELIVERIES;\n}\n\n/**\n * Of the planned files that ARE here, the ones whose stamp is missing or older\n * than this binary.\n *\n * The STALENESS check reads only the stamp, never the body. Safe refresh is a\n * separate decision made by `init`: the current bytes must match the digest\n * recorded when loam wrote them. That proof lets an unchanged pointer refresh\n * without turning a differing body into an offer to overwrite it. A stamp is a\n * different claim — it says which loam wrote these instructions — and it can\n * be false while every edit around it is legitimate. Same doctrine as\n * AGENTS.md (agents-stamp.ts); a customized file is repaired only when a human\n * reviews it and bumps the stamp or deletes it for `loam init` to recreate.\n *\n * Absence of a stamp counts, because that is what every file written before\n * stamping existed looks like — precisely the \"initialized by an older loam\"\n * population this check is for.\n */\nexport async function staleAgentFiles(\n repoRoot: string,\n planned: Array<{ path: string }>,\n): Promise<string[]> {\n // Read them together rather than one after another. `planned` is bounded by\n // the tool registry — every delivery of every tool loam knows, a couple of\n // hundred small files — so there is nothing here worth a concurrency cap, and\n // `Promise.all` preserves input order, which is the only property the caller\n // asks of the list it gets back.\n const graded = await Promise.all(planned.map(async (file): Promise<string | null> => {\n let text: string;\n try {\n text = await readFile(file.path, \"utf8\");\n } catch {\n // Unreadable is not stale: it is either the missing-file finding above or\n // a permissions problem, and neither is answered by editing a stamp.\n return null;\n }\n const stamp = agentsStampVersion(text);\n if (stamp === null || versionTrails(stamp, LOAM_VERSION)) {\n return relative(repoRoot, file.path).split(/[\\\\/]/).join(\"/\");\n }\n return null;\n }));\n return graded.filter((p) => p !== null);\n}\n\n/**\n * What the running binary would write for this repo's tools that is not here,\n * and what is here under an older loam's name.\n *\n * Presence is `existsSync`, the same probe `init` uses to compute its `skipped`\n * list, so doctor cannot disagree with init about which files init would write.\n */\nexport async function inspectAgentSurface(\n repoRoot: string,\n config: LoamConfig | null,\n docsDir: DocsDir | null,\n findings: DoctorFinding[],\n): Promise<AgentSurface> {\n const recorded = recordedTools(config);\n const tools = recorded ?? detectedTools(repoRoot);\n const profile = config?.agentProfile ?? \"full\";\n const planned = tools.flatMap((id) =>\n plannedCommandFiles(repoRoot, [id], heldDeliveries(repoRoot, id, profile), profile));\n const present = planned.filter((f) => existsSync(f.path));\n const missingFiles = planned\n .filter((f) => !existsSync(f.path))\n .map((f) => relative(repoRoot, f.path).split(/[\\\\/]/).join(\"/\"));\n const staleFiles = await staleAgentFiles(repoRoot, present);\n\n if (missingFiles.length > 0) {\n // A warning, never a blocker: an out-of-date command set is a repo whose\n // agents have fewer entry points than they could, not one where anything\n // refuses to run. doctor's blockers are for the second kind.\n //\n // The fix spells `--docs` and `--tools` and deliberately NOT `--service`:\n // `init` spreads the committed config forward, and a `--docs`-less re-run\n // keeps the pointer the repo already commits, so the binding survives\n // untouched. Every interpolation here stands for exactly one argument —\n // test/agent-commands-runnable.test.ts parses this string, and a `${flags}`\n // holding three of them is a command it cannot check.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-missing\",\n message:\n `${missingFiles.length} of the ${planned.length} command and skill files loam v${LOAM_VERSION} `\n + `lays down for ${tools.join(\", \")} are not in this repo — it was initialized by an older loam, `\n + `or they were deleted: ${missingFiles.join(\", \")}`,\n fix:\n `Re-run \\`loam init --docs ${config?.docsDirAsWritten ?? \"<dir>\"} --tools ${tools.join(\",\")}\\` `\n + \"here — it creates files that are absent and keeps this repo's service binding. Files unchanged \"\n + \"since loam wrote them refresh safely; customized files stay untouched.\",\n });\n }\n\n if (staleFiles.length > 0) {\n // Also a warning, and for a stronger reason than the one above: these files\n // are the team's now. Only a person can say whether their edits still mean\n // what the current binary's tables say, so loam reports and stops.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-stale\",\n message:\n `${staleFiles.length} of the ${planned.length} command and skill files here carry no `\n + `\\`${agentsStampLine(LOAM_VERSION)}\\` stamp, or one older than this loam — the protocol and `\n + `code tables they instruct an agent with may describe a loam that no longer exists: `\n + `${staleFiles.join(\", \")}`,\n fix:\n \"Read each against the body this loam writes, then set its stamp line to \"\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`; or re-run \\`loam init\\`. A file whose recorded digest `\n + \"still matches is refreshed; a customized file is preserved and remains yours to review.\",\n });\n }\n\n // AGENTS.md lives in the DOCS repo, not here: it travels with the thing it\n // describes. The comparison is `agentsStaleFinding`'s, not a second copy of\n // it — doctor only adds the `fix` column its own findings owe the reader.\n const agentsFile = docsDir === null ? null : agentsPath(docsDir);\n let agentsText: string | null = null;\n if (agentsFile !== null) {\n try {\n agentsText = await readFile(agentsFile, \"utf8\");\n } catch {\n // Unreadable reads as absent, exactly as `validate --all` treats it: no\n // file, no contract to have drifted. An unreadable docs repo is already\n // its own finding.\n }\n }\n const stale = agentsStaleFinding(agentsText, LOAM_VERSION);\n if (stale !== null) {\n findings.push({\n severity: \"warning\",\n code: stale.code,\n message: stale.message,\n fix:\n `Review ${agentsFile} against the current \\`loam --help\\`, then set its stamp line to `\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`. loam never rewrites that file — bumping the stamp IS `\n + \"the record that somebody looked.\",\n });\n }\n // The mirror, and a different fix: nothing is wrong with the documents, the\n // reader is behind them — so this one never points at AGENTS.md.\n const behind = binaryBehindFinding(agentsText, LOAM_VERSION);\n if (behind !== null) {\n findings.push({\n severity: \"warning\",\n code: behind.code,\n message: behind.message,\n fix: \"Upgrade loam (`npm i -g @ybotok/loam`), then re-run. Do not edit AGENTS.md: its stamp is correct and this binary is the older half.\",\n });\n }\n\n return {\n tools,\n profile,\n toolsSource: recorded === null ? \"disk\" : \"config\",\n plannedFiles: planned.length,\n missingFiles,\n staleFiles,\n stamp: {\n path: agentsFile,\n present: agentsText !== null,\n version: agentsText === null ? null : agentsStampVersion(agentsText),\n stale: stale !== null,\n },\n };\n}\n\n/**\n * Grade what a write that did not finish left in the docs repo.\n *\n * Three of the four are blockers, which is unusual for doctor and deliberate:\n * they are not \"you have fewer entry points than you could\", they are \"the\n * living docs may be half-written and the next reader cannot tell\". Before this\n * existed, a SIGKILL between two of archive's renames left `doctor: healthy:\n * true` over merged spec.md + openapi.yaml and an unmerged landscape, and the\n * next `loam archive` reported loam's own half-merge as the author's bug.\n *\n * The temp files are the exception: a `.loam-*.tmp` was never linked into place,\n * so nothing reads it and nothing depends on it. It is litter, not damage.\n */\n"]}
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../../src/core/doctor/agents.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The agent surface: which tool files this repo should hold, which it does, and\n * whether they still describe this binary.\n *\n * The registry filter lives here rather than in the config loader because a\n * config written by a newer binary may name a tool this one has never heard of,\n * and refusing to load it would break every command rather than just this\n * check. A tool loam cannot plan files for is a tool it has nothing to say\n * about — so the preflight stays read-only instead of crashing on a config from\n * the future.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport { type LoamConfig } from \"../envelope/config.js\";\nimport { LOAM_VERSION } from \"../envelope/version.js\";\nimport { agentsPath } from \"../repo/paths.js\";\nimport {\n DELIVERIES,\n plannedCommandFiles,\n type AgentProfile,\n type Delivery,\n} from \"../agent/scaffold.js\";\nimport { AGENT_TOOLS } from \"../agent/tools/registry.js\";\nimport {\n agentsStaleFinding,\n binaryBehindFinding,\n agentsStampLine,\n agentsStampVersion,\n restamped,\n versionTrails,\n} from \"../agent/agents-stamp.js\";\nimport { type AgentSurface, type DoctorFinding } from \"./report.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\nexport function recordedTools(config: LoamConfig | null): string[] | null {\n const ids = (config?.agentTools ?? []).filter((t) => t in AGENT_TOOLS);\n return ids.length === 0 ? null : ids;\n}\n\n/**\n * Which tools this repo has agent files for, asked of the layout itself: a tool\n * counts when any file it would be given is already there.\n *\n * The fallback for a loam.json written before `agentTools` existed, and the\n * only answer a hand-written one can have — so its absence must never be the\n * thing that reports a problem. This asks a narrower question than `init`'s\n * own scan, and on purpose: init scans for a tool's dot-directory to decide\n * what a repo would LIKE, while doctor grades what loam has already put there.\n * A repo that runs Claude Code and took `--no-commands --no-skills` has chosen\n * to have no loam files, and is not behind the binary for it.\n */\nexport function detectedTools(repoRoot: string): string[] {\n return Object.keys(AGENT_TOOLS).filter((id) =>\n plannedCommandFiles(repoRoot, [id]).some((f) => existsSync(f.path)));\n}\n\n/**\n * Which deliveries this repo actually HOLDS for one tool — a delivery counts\n * when at least one of its files is on disk.\n *\n * `loam init --no-skills` (or `--no-commands`) records the tool in `agentTools`\n * and writes one delivery, so planning both and subtracting what exists reported\n * every skill file as missing on a brand-new repo — under a message saying it\n * \"was initialized by an older loam\" and a `fix` that re-runs init without the\n * flag that was the whole point. Which deliveries a repo asked for is written\n * down nowhere, and must not be: the files ARE the record (there is no state\n * file loam can disagree with). So the question is asked of them.\n *\n * A tool with NO files at all is the other story — recorded, then deleted — and\n * the caller falls back to the full plan there, which is what the finding's\n * \"or they were deleted\" already says.\n */\nexport function heldDeliveries(\n repoRoot: string,\n tool: string,\n profile: AgentProfile = \"full\",\n): readonly Delivery[] {\n const held = DELIVERIES.filter((d) =>\n plannedCommandFiles(repoRoot, [tool], [d], profile).some((f) => existsSync(f.path)));\n return held.length > 0 ? held : DELIVERIES;\n}\n\n/**\n * Of the planned files that ARE here, the ones whose stamp is missing or older\n * than this binary AND whose body is not the one this binary writes.\n *\n * Both halves, and the second is what makes the finding mean something. The\n * stamp sits inside the bytes, so every release moves it in every generated\n * file — and while the stamp was the whole test, every pointer in every repo\n * of a fleet went stale on every upgrade, even when the release had not\n * changed a word of the pointer. A warning that is always on is one people\n * learn to read past, and it then hid the one release where a pointer's body\n * really had moved. So a file whose bytes, its stamp line aside, are exactly\n * what this binary would write is current: it instructs an agent with this\n * binary's text, whichever release happened to write it. Line endings are\n * compared the same way, because a Windows checkout with `core.autocrlf`\n * hands back CRLF for a body nobody touched.\n *\n * A file that differs in its body falls back to the stamp, which is the claim\n * it always was — which loam a person last checked these instructions\n * against. That is why a CURRENT stamp over a customized body stays quiet:\n * somebody reviewed it and said so. Safe refresh is a separate decision made by\n * `init`: the current bytes must match the digest recorded when loam wrote\n * them, which lets an unchanged pointer refresh without turning a differing\n * body into an offer to overwrite it. Same doctrine as AGENTS.md\n * (agents-stamp.ts); a customized file is repaired only when a human reviews it\n * and bumps the stamp or deletes it for `loam init` to recreate.\n *\n * Absence of a stamp counts, because that is what every file written before\n * stamping existed looks like — precisely the \"initialized by an older loam\"\n * population this check is for — and such a file cannot match the current\n * body, which opens with the stamp.\n */\nexport async function staleAgentFiles(\n repoRoot: string,\n planned: Array<{ path: string; content: string }>,\n): Promise<string[]> {\n // Read them together rather than one after another. `planned` is bounded by\n // the tool registry — every delivery of every tool loam knows, a couple of\n // hundred small files — so there is nothing here worth a concurrency cap, and\n // `Promise.all` preserves input order, which is the only property the caller\n // asks of the list it gets back.\n const graded = await Promise.all(planned.map(async (file): Promise<string | null> => {\n let text: string;\n try {\n text = await readFile(file.path, \"utf8\");\n } catch {\n // Unreadable is not stale: it is either the missing-file finding above or\n // a permissions problem, and neither is answered by editing a stamp.\n return null;\n }\n const stamp = agentsStampVersion(text);\n if (stamp !== null && !versionTrails(stamp, LOAM_VERSION)) return null;\n const lf = (s: string): string => s.replace(/\\r\\n/g, \"\\n\");\n if (lf(restamped(text, LOAM_VERSION)) === lf(file.content)) return null;\n return relative(repoRoot, file.path).split(/[\\\\/]/).join(\"/\");\n }));\n return graded.filter((p) => p !== null);\n}\n\n/**\n * What the running binary would write for this repo's tools that is not here,\n * and what is here under an older loam's name.\n *\n * Presence is `existsSync`, the same probe `init` uses to compute its `skipped`\n * list, so doctor cannot disagree with init about which files init would write.\n */\nexport async function inspectAgentSurface(\n repoRoot: string,\n config: LoamConfig | null,\n docsDir: DocsDir | null,\n findings: DoctorFinding[],\n): Promise<AgentSurface> {\n const recorded = recordedTools(config);\n const tools = recorded ?? detectedTools(repoRoot);\n const profile = config?.agentProfile ?? \"full\";\n const planned = tools.flatMap((id) =>\n plannedCommandFiles(repoRoot, [id], heldDeliveries(repoRoot, id, profile), profile));\n const present = planned.filter((f) => existsSync(f.path));\n const missingFiles = planned\n .filter((f) => !existsSync(f.path))\n .map((f) => relative(repoRoot, f.path).split(/[\\\\/]/).join(\"/\"));\n const staleFiles = await staleAgentFiles(repoRoot, present);\n\n if (missingFiles.length > 0) {\n // A warning, never a blocker: an out-of-date command set is a repo whose\n // agents have fewer entry points than they could, not one where anything\n // refuses to run. doctor's blockers are for the second kind.\n //\n // The fix spells `--docs` and `--tools` and deliberately NOT `--service`:\n // `init` spreads the committed config forward, and a `--docs`-less re-run\n // keeps the pointer the repo already commits, so the binding survives\n // untouched. Every interpolation here stands for exactly one argument —\n // test/agent-commands-runnable.test.ts parses this string, and a `${flags}`\n // holding three of them is a command it cannot check.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-missing\",\n message:\n `${missingFiles.length} of the ${planned.length} command and skill files loam v${LOAM_VERSION} `\n + `lays down for ${tools.join(\", \")} are not in this repo — it was initialized by an older loam, `\n + `or they were deleted: ${missingFiles.join(\", \")}`,\n fix:\n `Re-run \\`loam init --docs ${config?.docsDirAsWritten ?? \"<dir>\"} --tools ${tools.join(\",\")}\\` `\n + \"here — it creates files that are absent and keeps this repo's service binding. Files unchanged \"\n + \"since loam wrote them refresh safely; customized files stay untouched.\",\n });\n }\n\n if (staleFiles.length > 0) {\n // Also a warning, and for a stronger reason than the one above: these files\n // are the team's now. Only a person can say whether their edits still mean\n // what the current binary's tables say, so loam reports and stops.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-stale\",\n message:\n `${staleFiles.length} of the ${planned.length} command and skill files here differ from the body `\n + `loam v${LOAM_VERSION} writes and carry no \\`${agentsStampLine(LOAM_VERSION)}\\` stamp, or one older `\n + `than this loam — what they instruct an agent with may describe a loam that no longer exists: `\n + `${staleFiles.join(\", \")}`,\n fix:\n \"Read each against the body this loam writes, then set its stamp line to \"\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`; or re-run \\`loam init\\`. A file whose recorded digest `\n + \"still matches is refreshed; a customized file is preserved and remains yours to review.\",\n });\n }\n\n // AGENTS.md lives in the DOCS repo, not here: it travels with the thing it\n // describes. The comparison is `agentsStaleFinding`'s, not a second copy of\n // it — doctor only adds the `fix` column its own findings owe the reader.\n const agentsFile = docsDir === null ? null : agentsPath(docsDir);\n let agentsText: string | null = null;\n if (agentsFile !== null) {\n try {\n agentsText = await readFile(agentsFile, \"utf8\");\n } catch {\n // Unreadable reads as absent, exactly as `validate --all` treats it: no\n // file, no contract to have drifted. An unreadable docs repo is already\n // its own finding.\n }\n }\n const stale = agentsStaleFinding(agentsText, LOAM_VERSION);\n if (stale !== null) {\n findings.push({\n severity: \"warning\",\n code: stale.code,\n message: stale.message,\n fix:\n `Review ${agentsFile} against the current \\`loam --help\\`, then set its stamp line to `\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`. loam never rewrites that file — bumping the stamp IS `\n + \"the record that somebody looked.\",\n });\n }\n // The mirror, and a different fix: nothing is wrong with the documents, the\n // reader is behind them — so this one never points at AGENTS.md.\n const behind = binaryBehindFinding(agentsText, LOAM_VERSION);\n if (behind !== null) {\n findings.push({\n severity: \"warning\",\n code: behind.code,\n message: behind.message,\n fix: \"Upgrade loam (`npm i -g @ybotok/loam`), then re-run. Do not edit AGENTS.md: its stamp is correct and this binary is the older half.\",\n });\n }\n\n return {\n tools,\n profile,\n toolsSource: recorded === null ? \"disk\" : \"config\",\n plannedFiles: planned.length,\n missingFiles,\n staleFiles,\n stamp: {\n path: agentsFile,\n present: agentsText !== null,\n version: agentsText === null ? null : agentsStampVersion(agentsText),\n stale: stale !== null,\n },\n };\n}\n\n/**\n * Grade what a write that did not finish left in the docs repo.\n *\n * Three of the four are blockers, which is unusual for doctor and deliberate:\n * they are not \"you have fewer entry points than you could\", they are \"the\n * living docs may be half-written and the next reader cannot tell\". Before this\n * existed, a SIGKILL between two of archive's renames left `doctor: healthy:\n * true` over merged spec.md + openapi.yaml and an unmerged landscape, and the\n * next `loam archive` reported loam's own half-merge as the author's bug.\n *\n * The temp files are the exception: a `.loam-*.tmp` was never linked into place,\n * so nothing reads it and nothing depends on it. It is litter, not damage.\n */\n"]}
@@ -15,7 +15,7 @@
15
15
  import { access, readFile } from "node:fs/promises";
16
16
  import { dirname } from "node:path";
17
17
  import { configPath, parseConfig } from "../envelope/config.js";
18
- import { loadFile } from "../c4/likec4.js";
18
+ import { errorText, loadFile } from "../c4/likec4.js";
19
19
  import { conflictMarkerLines } from "../conflict-markers.js";
20
20
  import {} from "./report.js";
21
21
  /**
@@ -113,8 +113,12 @@ export async function inspectLandscape(path, findings) {
113
113
  findings.push({
114
114
  severity: "blocker",
115
115
  code: "doctor.landscape-invalid",
116
- message: `architecture/landscape.likec4 does not parse: ${first.message}` +
117
- (first.line === undefined ? "" : ` (line ${first.line})`) +
116
+ // Through `errorText`, the one spelling of a diagnostic loam prints. This
117
+ // site hand-rolled its own and passed the LSP range's line through raw, so
118
+ // `doctor` named the line ABOVE the fault and disagreed with `validate
119
+ // --all` about the same error on the same tree — the last formatter the
120
+ // 1-based consolidation missed (verification 2026-09-04).
121
+ message: `architecture/landscape.likec4 does not parse: ${errorText(first)}` +
118
122
  (doc.errors.length > 1 ? ` — and ${doc.errors.length - 1} more` : ""),
119
123
  fix: `Fix ${path}; every fleet-wide check is blind until it parses.`,
120
124
  });
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/core/doctor/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The config and the fleet map, read the way every other command reads them.\n *\n * `doctor` used to carry its own config validator. It agreed with `loadConfig`\n * on the fields both happened to check and disagreed everywhere else — it\n * accepted a `gherkinDir` of `\"../shared\"` that `loadConfig` refused outright,\n * so `doctor` reported a healthy repo in which no command could run. Two\n * validators are two opinions about one file, and the one the user reads is\n * never the one the commands obey.\n *\n * The landscape is READ, not stat'ed, for the same reason: `doctor` once\n * reported `landscape: yes` for a file full of conflict markers, which answers\n * \"is there a file\" — not the question anyone runs doctor to ask.\n */\nimport { access, readFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { configPath, parseConfig, type LoamConfig } from \"../envelope/config.js\";\nimport { loadFile } from \"../c4/likec4.js\";\nimport { conflictMarkerLines } from \"../conflict-markers.js\";\nimport { type DoctorFinding, type DoctorReport } from \"./report.js\";\n\nexport interface ConfigInspection {\n status: DoctorReport[\"config\"][\"status\"];\n config: LoamConfig | null;\n error: string | null;\n}\n\n/**\n * Read and validate the config the way every other command does — through\n * `parseConfig`, never through a second implementation.\n *\n * doctor used to carry its own validator. It agreed with `loadConfig` on the\n * fields both happened to check and disagreed everywhere else: doctor accepted\n * a `gherkinDir` of `\"../shared\"` that `loadConfig` refused outright, so\n * `doctor` reported a healthy repo in which no command could run. Two\n * validators are two opinions about the same file, and the one the user reads\n * is never the one the commands obey.\n */\nexport async function inspectConfig(cwd: string): Promise<ConfigInspection> {\n const path = configPath(cwd);\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return { status: \"missing\", config: null, error: null };\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n try {\n return { status: \"valid\", error: null, config: parseConfig(raw, dirname(path)) };\n } catch (error) {\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport async function canAccess(path: string, mode: number): Promise<boolean> {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the landscape, don't just stat it. `doctor` reported `landscape: yes`\n * for a file full of conflict markers — the check answered \"is there a file\",\n * which is not the question anyone runs doctor to ask.\n */\nexport async function inspectLandscape(path: string, findings: DoctorFinding[]): Promise<void> {\n let source: string;\n try {\n source = await readFile(path, \"utf8\");\n } catch (error) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-unreadable\",\n message: `${path} exists but could not be read: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check permissions on ${path}.`,\n });\n return;\n }\n\n // The three-way merge left its markers in the file. Checked BEFORE the\n // parser, because a conflicted landscape does parse sometimes — both sides of\n // a conflict can be syntactically valid LikeC4 — and \"your map contains two\n // halves of two different maps\" is a more useful sentence than any parser\n // error. This is the failure mode of onboarding a fleet: ten people adopt ten\n // services into one landscape.likec4 in the same week.\n //\n // The scan itself is conflict-markers's, which is where the same rule already\n // grades every other document loam reads. doctor kept a second copy of it,\n // and a second copy of a rule is a second chance to spell it differently;\n // what doctor owns is what a conflicted landscape COSTS, which is the finding\n // below and not the search.\n const conflicted = conflictMarkerLines(source);\n if (conflicted.length > 0) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-merge-conflict\",\n message:\n `architecture/landscape.likec4 still contains merge conflict markers ` +\n `(line${conflicted.length === 1 ? \"\" : \"s\"} ${conflicted.join(\", \")}).`,\n fix: `Resolve the conflict in ${path} — keep BOTH services' elements and edges, then re-run \\`loam doctor\\`.`,\n });\n return;\n }\n\n const doc = await loadFile(path);\n if (doc.errors.length > 0) {\n const first = doc.errors[0]!;\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-invalid\",\n message:\n `architecture/landscape.likec4 does not parse: ${first.message}` +\n (first.line === undefined ? \"\" : ` (line ${first.line})`) +\n (doc.errors.length > 1 ? ` — and ${doc.errors.length - 1} more` : \"\"),\n fix: `Fix ${path}; every fleet-wide check is blind until it parses.`,\n });\n }\n}\n\n/**\n * The tool ids loam.json records `init` as having written files for, or null\n * when it records none we can plan for.\n *\n * `parseConfig` checks `agentTools`' SHAPE and deliberately not its contents —\n * a config written by a newer binary may name a tool this one has never heard\n * of, and refusing to load it would break every command, not just this check.\n * So the registry filter belongs here: `plannedCommandFiles` throws on an id it\n * does not know, and a config from the future must not turn the read-only\n * preflight into a crash. A tool we cannot plan files for is a tool we have\n * nothing to say about.\n */\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/core/doctor/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The config and the fleet map, read the way every other command reads them.\n *\n * `doctor` used to carry its own config validator. It agreed with `loadConfig`\n * on the fields both happened to check and disagreed everywhere else — it\n * accepted a `gherkinDir` of `\"../shared\"` that `loadConfig` refused outright,\n * so `doctor` reported a healthy repo in which no command could run. Two\n * validators are two opinions about one file, and the one the user reads is\n * never the one the commands obey.\n *\n * The landscape is READ, not stat'ed, for the same reason: `doctor` once\n * reported `landscape: yes` for a file full of conflict markers, which answers\n * \"is there a file\" — not the question anyone runs doctor to ask.\n */\nimport { access, readFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { configPath, parseConfig, type LoamConfig } from \"../envelope/config.js\";\nimport { errorText, loadFile } from \"../c4/likec4.js\";\nimport { conflictMarkerLines } from \"../conflict-markers.js\";\nimport { type DoctorFinding, type DoctorReport } from \"./report.js\";\n\nexport interface ConfigInspection {\n status: DoctorReport[\"config\"][\"status\"];\n config: LoamConfig | null;\n error: string | null;\n}\n\n/**\n * Read and validate the config the way every other command does — through\n * `parseConfig`, never through a second implementation.\n *\n * doctor used to carry its own validator. It agreed with `loadConfig` on the\n * fields both happened to check and disagreed everywhere else: doctor accepted\n * a `gherkinDir` of `\"../shared\"` that `loadConfig` refused outright, so\n * `doctor` reported a healthy repo in which no command could run. Two\n * validators are two opinions about the same file, and the one the user reads\n * is never the one the commands obey.\n */\nexport async function inspectConfig(cwd: string): Promise<ConfigInspection> {\n const path = configPath(cwd);\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return { status: \"missing\", config: null, error: null };\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n try {\n return { status: \"valid\", error: null, config: parseConfig(raw, dirname(path)) };\n } catch (error) {\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nexport async function canAccess(path: string, mode: number): Promise<boolean> {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the landscape, don't just stat it. `doctor` reported `landscape: yes`\n * for a file full of conflict markers — the check answered \"is there a file\",\n * which is not the question anyone runs doctor to ask.\n */\nexport async function inspectLandscape(path: string, findings: DoctorFinding[]): Promise<void> {\n let source: string;\n try {\n source = await readFile(path, \"utf8\");\n } catch (error) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-unreadable\",\n message: `${path} exists but could not be read: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check permissions on ${path}.`,\n });\n return;\n }\n\n // The three-way merge left its markers in the file. Checked BEFORE the\n // parser, because a conflicted landscape does parse sometimes — both sides of\n // a conflict can be syntactically valid LikeC4 — and \"your map contains two\n // halves of two different maps\" is a more useful sentence than any parser\n // error. This is the failure mode of onboarding a fleet: ten people adopt ten\n // services into one landscape.likec4 in the same week.\n //\n // The scan itself is conflict-markers's, which is where the same rule already\n // grades every other document loam reads. doctor kept a second copy of it,\n // and a second copy of a rule is a second chance to spell it differently;\n // what doctor owns is what a conflicted landscape COSTS, which is the finding\n // below and not the search.\n const conflicted = conflictMarkerLines(source);\n if (conflicted.length > 0) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-merge-conflict\",\n message:\n `architecture/landscape.likec4 still contains merge conflict markers ` +\n `(line${conflicted.length === 1 ? \"\" : \"s\"} ${conflicted.join(\", \")}).`,\n fix: `Resolve the conflict in ${path} — keep BOTH services' elements and edges, then re-run \\`loam doctor\\`.`,\n });\n return;\n }\n\n const doc = await loadFile(path);\n if (doc.errors.length > 0) {\n const first = doc.errors[0]!;\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-invalid\",\n // Through `errorText`, the one spelling of a diagnostic loam prints. This\n // site hand-rolled its own and passed the LSP range's line through raw, so\n // `doctor` named the line ABOVE the fault and disagreed with `validate\n // --all` about the same error on the same tree — the last formatter the\n // 1-based consolidation missed (verification 2026-09-04).\n message:\n `architecture/landscape.likec4 does not parse: ${errorText(first)}` +\n (doc.errors.length > 1 ? ` — and ${doc.errors.length - 1} more` : \"\"),\n fix: `Fix ${path}; every fleet-wide check is blind until it parses.`,\n });\n }\n}\n\n/**\n * The tool ids loam.json records `init` as having written files for, or null\n * when it records none we can plan for.\n *\n * `parseConfig` checks `agentTools`' SHAPE and deliberately not its contents —\n * a config written by a newer binary may name a tool this one has never heard\n * of, and refusing to load it would break every command, not just this check.\n * So the registry filter belongs here: `plannedCommandFiles` throws on an id it\n * does not know, and a config from the future must not turn the read-only\n * preflight into a crash. A tool we cannot plan files for is a tool we have\n * nothing to say about.\n */\n"]}