@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1

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 (1026) hide show
  1. package/CHANGELOG.md +2570 -61
  2. package/COMPARISON.md +80 -63
  3. package/CONTRIBUTING.md +84 -0
  4. package/MIGRATING-from-OpenSpec.md +213 -54
  5. package/QUICKSTART.md +125 -0
  6. package/README.md +450 -150
  7. package/ROADMAP.md +494 -0
  8. package/SCHEMA.md +2362 -245
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +701 -0
  11. package/dist/cli.js +113 -19
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +209 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +268 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +77 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/openapi.js +159 -0
  22. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  23. package/dist/commands/archive/plan/gate.js +317 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +161 -0
  26. package/dist/commands/archive/plan/landscape.js.map +1 -0
  27. package/dist/commands/archive/plan/refusal.js +111 -0
  28. package/dist/commands/archive/plan/refusal.js.map +1 -0
  29. package/dist/commands/archive/plan/specs.js +237 -0
  30. package/dist/commands/archive/plan/specs.js.map +1 -0
  31. package/dist/commands/archive/plan/state.js +27 -0
  32. package/dist/commands/archive/plan/state.js.map +1 -0
  33. package/dist/commands/archive/run.js +251 -0
  34. package/dist/commands/archive/run.js.map +1 -0
  35. package/dist/commands/context/context.js +99 -0
  36. package/dist/commands/context/context.js.map +1 -0
  37. package/dist/commands/context/print.js +283 -0
  38. package/dist/commands/context/print.js.map +1 -0
  39. package/dist/commands/delta/delta.js +279 -0
  40. package/dist/commands/delta/delta.js.map +1 -0
  41. package/dist/commands/delta/print.js +205 -0
  42. package/dist/commands/delta/print.js.map +1 -0
  43. package/dist/commands/dependencies.js +9 -7
  44. package/dist/commands/dependencies.js.map +1 -1
  45. package/dist/commands/diff/diff.js +93 -0
  46. package/dist/commands/diff/diff.js.map +1 -0
  47. package/dist/commands/diff/print.js +38 -0
  48. package/dist/commands/diff/print.js.map +1 -0
  49. package/dist/commands/doctor.js +5 -4
  50. package/dist/commands/doctor.js.map +1 -1
  51. package/dist/commands/explain/explain.js +151 -0
  52. package/dist/commands/explain/explain.js.map +1 -0
  53. package/dist/commands/explore.js +29 -24
  54. package/dist/commands/explore.js.map +1 -1
  55. package/dist/commands/gate/gate.js +115 -0
  56. package/dist/commands/gate/gate.js.map +1 -0
  57. package/dist/commands/gate/print.js +54 -0
  58. package/dist/commands/gate/print.js.map +1 -0
  59. package/dist/commands/gherkin/commit.js +146 -0
  60. package/dist/commands/gherkin/commit.js.map +1 -0
  61. package/dist/commands/gherkin/gherkin.js +246 -0
  62. package/dist/commands/gherkin/gherkin.js.map +1 -0
  63. package/dist/commands/gherkin/reconcile.js +149 -0
  64. package/dist/commands/gherkin/reconcile.js.map +1 -0
  65. package/dist/commands/gherkin/render.js +77 -0
  66. package/dist/commands/gherkin/render.js.map +1 -0
  67. package/dist/commands/init/example.js +204 -0
  68. package/dist/commands/init/example.js.map +1 -0
  69. package/dist/commands/init/first-hour.js +28 -0
  70. package/dist/commands/init/first-hour.js.map +1 -0
  71. package/dist/commands/{init.js → init/init.js} +146 -71
  72. package/dist/commands/init/init.js.map +1 -0
  73. package/dist/commands/init/mcp.js +131 -0
  74. package/dist/commands/init/mcp.js.map +1 -0
  75. package/dist/commands/init/options.js +78 -0
  76. package/dist/commands/init/options.js.map +1 -0
  77. package/dist/commands/instructions.js +82 -14
  78. package/dist/commands/instructions.js.map +1 -1
  79. package/dist/commands/list/campaign/campaign.js +65 -0
  80. package/dist/commands/list/campaign/campaign.js.map +1 -0
  81. package/dist/commands/list/campaign/owners.js +65 -0
  82. package/dist/commands/list/campaign/owners.js.map +1 -0
  83. package/dist/commands/list/json.js +157 -0
  84. package/dist/commands/list/json.js.map +1 -0
  85. package/dist/commands/list/list.js +343 -0
  86. package/dist/commands/list/list.js.map +1 -0
  87. package/dist/commands/list/print.js +301 -0
  88. package/dist/commands/list/print.js.map +1 -0
  89. package/dist/commands/list/review.js +120 -0
  90. package/dist/commands/list/review.js.map +1 -0
  91. package/dist/commands/list/views.js +105 -0
  92. package/dist/commands/list/views.js.map +1 -0
  93. package/dist/commands/mcp/dispatch.js +183 -0
  94. package/dist/commands/mcp/dispatch.js.map +1 -0
  95. package/dist/commands/mcp/mcp.js +34 -0
  96. package/dist/commands/mcp/mcp.js.map +1 -0
  97. package/dist/commands/mcp/serve.js +55 -0
  98. package/dist/commands/mcp/serve.js.map +1 -0
  99. package/dist/commands/migrate-openspec/materialize/active.js +170 -0
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  101. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  102. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  103. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  104. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  105. package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
  106. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  107. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  108. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  109. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  110. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  111. package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
  112. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  113. package/dist/commands/migrate-openspec/openspec/error.js +19 -0
  114. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  115. package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
  116. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  117. package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
  118. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  119. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  120. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  121. package/dist/commands/migrate-openspec/print.js +89 -0
  122. package/dist/commands/migrate-openspec/print.js.map +1 -0
  123. package/dist/commands/new/commit.js +76 -0
  124. package/dist/commands/new/commit.js.map +1 -0
  125. package/dist/commands/new/new.js +283 -0
  126. package/dist/commands/new/new.js.map +1 -0
  127. package/dist/commands/new/templates.js +355 -0
  128. package/dist/commands/new/templates.js.map +1 -0
  129. package/dist/commands/open.js +203 -0
  130. package/dist/commands/open.js.map +1 -0
  131. package/dist/commands/policy/format.js +152 -0
  132. package/dist/commands/policy/format.js.map +1 -0
  133. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
  134. package/dist/commands/policy/gate.js.map +1 -0
  135. package/dist/commands/rebase/edit.js +106 -0
  136. package/dist/commands/rebase/edit.js.map +1 -0
  137. package/dist/commands/rebase/living.js +185 -0
  138. package/dist/commands/rebase/living.js.map +1 -0
  139. package/dist/commands/rebase/plan.js +245 -0
  140. package/dist/commands/rebase/plan.js.map +1 -0
  141. package/dist/commands/rebase/rebase.js +275 -0
  142. package/dist/commands/rebase/rebase.js.map +1 -0
  143. package/dist/commands/seed/commit.js +192 -0
  144. package/dist/commands/seed/commit.js.map +1 -0
  145. package/dist/commands/seed/plan.js +133 -0
  146. package/dist/commands/seed/plan.js.map +1 -0
  147. package/dist/commands/seed/seed.js +172 -0
  148. package/dist/commands/seed/seed.js.map +1 -0
  149. package/dist/commands/show/feature.js +178 -0
  150. package/dist/commands/show/feature.js.map +1 -0
  151. package/dist/commands/show/marks.js +24 -0
  152. package/dist/commands/show/marks.js.map +1 -0
  153. package/dist/commands/{show.js → show/service.js} +63 -152
  154. package/dist/commands/show/service.js.map +1 -0
  155. package/dist/commands/show/show.js +77 -0
  156. package/dist/commands/show/show.js.map +1 -0
  157. package/dist/commands/status/json.js +45 -0
  158. package/dist/commands/status/json.js.map +1 -0
  159. package/dist/commands/status/print.js +157 -0
  160. package/dist/commands/status/print.js.map +1 -0
  161. package/dist/commands/status/status.js +119 -0
  162. package/dist/commands/status/status.js.map +1 -0
  163. package/dist/commands/steps/steps.js +189 -0
  164. package/dist/commands/steps/steps.js.map +1 -0
  165. package/dist/commands/subsystem/edit.js +192 -0
  166. package/dist/commands/subsystem/edit.js.map +1 -0
  167. package/dist/commands/subsystem/move.js +265 -0
  168. package/dist/commands/subsystem/move.js.map +1 -0
  169. package/dist/commands/subsystem/query.js +115 -0
  170. package/dist/commands/subsystem/query.js.map +1 -0
  171. package/dist/commands/subsystem/subsystem.js +101 -0
  172. package/dist/commands/subsystem/subsystem.js.map +1 -0
  173. package/dist/commands/subsystem/sync.js +103 -0
  174. package/dist/commands/subsystem/sync.js.map +1 -0
  175. package/dist/commands/subsystem/txn/txn.js +94 -0
  176. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  177. package/dist/commands/subsystem/txn/views.js +70 -0
  178. package/dist/commands/subsystem/txn/views.js.map +1 -0
  179. package/dist/commands/unarchive/manifest.js +160 -0
  180. package/dist/commands/unarchive/manifest.js.map +1 -0
  181. package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
  182. package/dist/commands/unarchive/restore.js.map +1 -0
  183. package/dist/commands/unarchive/unarchive.js +67 -0
  184. package/dist/commands/unarchive/unarchive.js.map +1 -0
  185. package/dist/commands/validate/arch-coverage.js +194 -0
  186. package/dist/commands/validate/arch-coverage.js.map +1 -0
  187. package/dist/commands/validate/checks/fleet-shape.js +296 -0
  188. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  189. package/dist/commands/validate/checks/health-deps.js +55 -0
  190. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  191. package/dist/commands/validate/checks/requirements.js +254 -0
  192. package/dist/commands/validate/checks/requirements.js.map +1 -0
  193. package/dist/commands/validate/checks/sources.js +76 -0
  194. package/dist/commands/validate/checks/sources.js.map +1 -0
  195. package/dist/commands/validate/checks/vocabulary.js +31 -0
  196. package/dist/commands/validate/checks/vocabulary.js.map +1 -0
  197. package/dist/commands/validate/feature.js +216 -0
  198. package/dist/commands/validate/feature.js.map +1 -0
  199. package/dist/commands/validate/fleet/census.js +69 -0
  200. package/dist/commands/validate/fleet/census.js.map +1 -0
  201. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  202. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  203. package/dist/commands/validate/fleet/landscape.js +353 -0
  204. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  205. package/dist/commands/validate/fleet/load.js +52 -0
  206. package/dist/commands/validate/fleet/load.js.map +1 -0
  207. package/dist/commands/validate/fleet/obligations.js +236 -0
  208. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  209. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  210. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  211. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  212. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  213. package/dist/commands/validate/fleet/scorecard/print.js +63 -0
  214. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  215. package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
  216. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  217. package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
  218. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
  219. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  220. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  221. package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
  222. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
  223. package/dist/commands/validate/fleet/usecases/steps.js +197 -0
  224. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
  225. package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
  226. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
  227. package/dist/commands/validate/fleet/views/ids.js +40 -0
  228. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  229. package/dist/commands/validate/fleet/views/stale.js +59 -0
  230. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  231. package/dist/commands/validate/links/corpus.js +92 -0
  232. package/dist/commands/validate/links/corpus.js.map +1 -0
  233. package/dist/commands/validate/links/glossary.js +75 -0
  234. package/dist/commands/validate/links/glossary.js.map +1 -0
  235. package/dist/commands/validate/report.js +269 -0
  236. package/dist/commands/validate/report.js.map +1 -0
  237. package/dist/commands/validate/service/api.js +338 -0
  238. package/dist/commands/validate/service/api.js.map +1 -0
  239. package/dist/commands/validate/service/events/events.js +219 -0
  240. package/dist/commands/validate/service/events/events.js.map +1 -0
  241. package/dist/commands/validate/service/events/message-producers.js +77 -0
  242. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  243. package/dist/commands/validate/service/evidence-pins.js +131 -0
  244. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  245. package/dist/commands/validate/service/service.js +250 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +219 -0
  248. package/dist/commands/validate/service/specs.js.map +1 -0
  249. package/dist/commands/validate/service/spine.js +113 -0
  250. package/dist/commands/validate/service/spine.js.map +1 -0
  251. package/dist/commands/validate/validate.js +338 -0
  252. package/dist/commands/validate/validate.js.map +1 -0
  253. package/dist/commands/verify/cross/diff.js +182 -0
  254. package/dist/commands/verify/cross/diff.js.map +1 -0
  255. package/dist/commands/verify/evidence/contract.js +32 -0
  256. package/dist/commands/verify/evidence/contract.js.map +1 -0
  257. package/dist/commands/verify/evidence/pins.js +52 -0
  258. package/dist/commands/verify/evidence/pins.js.map +1 -0
  259. package/dist/commands/verify/evidence/read.js +72 -0
  260. package/dist/commands/verify/evidence/read.js.map +1 -0
  261. package/dist/commands/verify/frozen.js +241 -0
  262. package/dist/commands/verify/frozen.js.map +1 -0
  263. package/dist/commands/verify/record.js +226 -0
  264. package/dist/commands/verify/record.js.map +1 -0
  265. package/dist/commands/verify/report.js +223 -0
  266. package/dist/commands/verify/report.js.map +1 -0
  267. package/dist/commands/verify/results.js +246 -0
  268. package/dist/commands/verify/results.js.map +1 -0
  269. package/dist/commands/verify/verify.js +217 -0
  270. package/dist/commands/verify/verify.js.map +1 -0
  271. package/dist/commands/vouch/contract.js +11 -0
  272. package/dist/commands/vouch/contract.js.map +1 -0
  273. package/dist/commands/vouch/pack/contract.js +2 -0
  274. package/dist/commands/vouch/pack/contract.js.map +1 -0
  275. package/dist/commands/vouch/pack/pack.js +266 -0
  276. package/dist/commands/vouch/pack/pack.js.map +1 -0
  277. package/dist/commands/vouch/pack/print.js +271 -0
  278. package/dist/commands/vouch/pack/print.js.map +1 -0
  279. package/dist/commands/vouch/pack/sections.js +142 -0
  280. package/dist/commands/vouch/pack/sections.js.map +1 -0
  281. package/dist/commands/vouch/prompt/confirm.js +46 -0
  282. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  283. package/dist/commands/vouch/report.js +101 -0
  284. package/dist/commands/vouch/report.js.map +1 -0
  285. package/dist/commands/vouch/run.js +285 -0
  286. package/dist/commands/vouch/run.js.map +1 -0
  287. package/dist/commands/vouch/sample/plan.js +125 -0
  288. package/dist/commands/vouch/sample/plan.js.map +1 -0
  289. package/dist/commands/vouch/sample/print.js +109 -0
  290. package/dist/commands/vouch/sample/print.js.map +1 -0
  291. package/dist/commands/vouch/vet/verify.js +123 -0
  292. package/dist/commands/vouch/vet/verify.js.map +1 -0
  293. package/dist/commands/vouch/vouch.js +176 -0
  294. package/dist/commands/vouch/vouch.js.map +1 -0
  295. package/dist/core/agent/agents-md/artifacts.js +220 -0
  296. package/dist/core/agent/agents-md/artifacts.js.map +1 -0
  297. package/dist/core/agent/agents-md/command-map.js +324 -0
  298. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  299. package/dist/core/agent/agents-md/cycle.js +165 -0
  300. package/dist/core/agent/agents-md/cycle.js.map +1 -0
  301. package/dist/core/agent/agents-md/map/explain.js +34 -0
  302. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  303. package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
  304. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  305. package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
  306. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  307. package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
  308. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  309. package/dist/core/agent/agents-md/map/mcp.js +41 -0
  310. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  311. package/dist/core/agent/agents-md/map/status.js +62 -0
  312. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  313. package/dist/core/agent/agents-md/map/subsystem.js +32 -0
  314. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  315. package/dist/core/agent/agents-md/map/usecases.js +60 -0
  316. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  317. package/dist/core/agent/agents-md/refusals.js +246 -0
  318. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  319. package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
  320. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
  321. package/dist/core/agent/agents-md/spine.js +40 -0
  322. package/dist/core/agent/agents-md/spine.js.map +1 -0
  323. package/dist/core/agent/agents-md.js +170 -0
  324. package/dist/core/agent/agents-md.js.map +1 -0
  325. package/dist/core/agent/agents-stamp.js +155 -0
  326. package/dist/core/agent/agents-stamp.js.map +1 -0
  327. package/dist/core/agent/contract.js +2 -0
  328. package/dist/core/agent/contract.js.map +1 -0
  329. package/dist/core/agent/protocol.js +193 -0
  330. package/dist/core/agent/protocol.js.map +1 -0
  331. package/dist/core/agent/scaffold.js +259 -0
  332. package/dist/core/agent/scaffold.js.map +1 -0
  333. package/dist/core/agent/support/report.js +103 -0
  334. package/dist/core/agent/support/report.js.map +1 -0
  335. package/dist/core/agent/tools/dialects.js +150 -0
  336. package/dist/core/agent/tools/dialects.js.map +1 -0
  337. package/dist/core/agent/tools/registry.js +228 -0
  338. package/dist/core/agent/tools/registry.js.map +1 -0
  339. package/dist/core/agent/workflows/adopt.js +133 -0
  340. package/dist/core/agent/workflows/adopt.js.map +1 -0
  341. package/dist/core/agent/workflows/check.js +334 -0
  342. package/dist/core/agent/workflows/check.js.map +1 -0
  343. package/dist/core/agent/workflows/closing.js +262 -0
  344. package/dist/core/agent/workflows/closing.js.map +1 -0
  345. package/dist/core/agent/workflows/feature.js +105 -0
  346. package/dist/core/agent/workflows/feature.js.map +1 -0
  347. package/dist/core/agent/workflows/implement.js +105 -0
  348. package/dist/core/agent/workflows/implement.js.map +1 -0
  349. package/dist/core/agent/workflows/reference/authoring.js +209 -0
  350. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  351. package/dist/core/agent/workflows/reference/codes.js +65 -0
  352. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  353. package/dist/core/agent/workflows/reference/done-check.js +188 -0
  354. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  355. package/dist/core/agent/workflows/reference/reference.js +16 -0
  356. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  357. package/dist/core/agent/workflows/reference/spine.js +297 -0
  358. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  359. package/dist/core/asyncapi/baseline/plan.js +110 -0
  360. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  361. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  362. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  363. package/dist/core/asyncapi/depth.js +164 -0
  364. package/dist/core/asyncapi/depth.js.map +1 -0
  365. package/dist/core/asyncapi/digest.js +141 -0
  366. package/dist/core/asyncapi/digest.js.map +1 -0
  367. package/dist/core/asyncapi/merge/components.js +91 -0
  368. package/dist/core/asyncapi/merge/components.js.map +1 -0
  369. package/dist/core/asyncapi/merge/error.js +33 -0
  370. package/dist/core/asyncapi/merge/error.js.map +1 -0
  371. package/dist/core/asyncapi/merge/markers.js +308 -0
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  373. package/dist/core/asyncapi/merge/merge.js +215 -0
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  375. package/dist/core/asyncapi/merge/pin.js +107 -0
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  377. package/dist/core/asyncapi/model.js +11 -0
  378. package/dist/core/asyncapi/model.js.map +1 -0
  379. package/dist/core/asyncapi/producers.js +50 -0
  380. package/dist/core/asyncapi/producers.js.map +1 -0
  381. package/dist/core/asyncapi/read.js +239 -0
  382. package/dist/core/asyncapi/read.js.map +1 -0
  383. package/dist/core/brief/brief.js +225 -0
  384. package/dist/core/brief/brief.js.map +1 -0
  385. package/dist/core/brief/checks.js +250 -0
  386. package/dist/core/brief/checks.js.map +1 -0
  387. package/dist/core/brief/landscape.js +194 -0
  388. package/dist/core/brief/landscape.js.map +1 -0
  389. package/dist/core/brief/targets.js +226 -0
  390. package/dist/core/brief/targets.js.map +1 -0
  391. package/dist/core/brief/unchecked.js +47 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +282 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +88 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  398. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  399. package/dist/core/c4/parsed/specification.js +78 -0
  400. package/dist/core/c4/parsed/specification.js.map +1 -0
  401. package/dist/core/c4/parsed/values.js +57 -0
  402. package/dist/core/c4/parsed/values.js.map +1 -0
  403. package/dist/core/c4/parsed/view-ids.js +75 -0
  404. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  405. package/dist/core/c4/project/architecture.js +45 -0
  406. package/dist/core/c4/project/architecture.js.map +1 -0
  407. package/dist/core/c4/project/documents.js +58 -0
  408. package/dist/core/c4/project/documents.js.map +1 -0
  409. package/dist/core/c4/project/load.js +183 -0
  410. package/dist/core/c4/project/load.js.map +1 -0
  411. package/dist/core/c4/resolve/service.js +113 -0
  412. package/dist/core/c4/resolve/service.js.map +1 -0
  413. package/dist/core/c4/seed/fleet-file.js +201 -0
  414. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  415. package/dist/core/c4/seed/items.js +91 -0
  416. package/dist/core/c4/seed/items.js.map +1 -0
  417. package/dist/core/c4/seed/stamp.js +48 -0
  418. package/dist/core/c4/seed/stamp.js.map +1 -0
  419. package/dist/core/c4/seed/template.js +109 -0
  420. package/dist/core/c4/seed/template.js.map +1 -0
  421. package/dist/core/c4/source-mask.js +82 -0
  422. package/dist/core/c4/source-mask.js.map +1 -0
  423. package/dist/core/c4/source-scan.js +227 -0
  424. package/dist/core/c4/source-scan.js.map +1 -0
  425. package/dist/core/c4/splice/authored-source.js +101 -0
  426. package/dist/core/c4/splice/authored-source.js.map +1 -0
  427. package/dist/core/c4/splice/contract.js +10 -0
  428. package/dist/core/c4/splice/contract.js.map +1 -0
  429. package/dist/core/c4/splice/delta-blocks.js +126 -0
  430. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  431. package/dist/core/c4/splice/identity/edges.js +36 -0
  432. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  433. package/dist/core/c4/splice/landscape-merge.js +269 -0
  434. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  435. package/dist/core/c4/splice/placement.js +194 -0
  436. package/dist/core/c4/splice/placement.js.map +1 -0
  437. package/dist/core/c4/workspace.js +172 -0
  438. package/dist/core/c4/workspace.js.map +1 -0
  439. package/dist/core/capabilities/authoring/notes.js +141 -0
  440. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  441. package/dist/core/capabilities/capabilities.js +124 -0
  442. package/dist/core/capabilities/capabilities.js.map +1 -0
  443. package/dist/core/capabilities/delta/doc.js +50 -0
  444. package/dist/core/capabilities/delta/doc.js.map +1 -0
  445. package/dist/core/capabilities/delta/overlay.js +129 -0
  446. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  447. package/dist/core/capabilities/delta/summary.js +57 -0
  448. package/dist/core/capabilities/delta/summary.js.map +1 -0
  449. package/dist/core/capabilities/delta/tree.js +43 -0
  450. package/dist/core/capabilities/delta/tree.js.map +1 -0
  451. package/dist/core/capabilities/delta/uncovered.js +222 -0
  452. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  453. package/dist/core/capabilities/findings.js +342 -0
  454. package/dist/core/capabilities/findings.js.map +1 -0
  455. package/dist/core/capabilities/realizes/findings.js +181 -0
  456. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  457. package/dist/core/capabilities/realizes/join.js +143 -0
  458. package/dist/core/capabilities/realizes/join.js.map +1 -0
  459. package/dist/core/capabilities/rollup.js +199 -0
  460. package/dist/core/capabilities/rollup.js.map +1 -0
  461. package/dist/core/capabilities/tree.js +126 -0
  462. package/dist/core/capabilities/tree.js.map +1 -0
  463. package/dist/core/capabilities/usecase-join.js +277 -0
  464. package/dist/core/capabilities/usecase-join.js.map +1 -0
  465. package/dist/core/coherence/authoring/scaffold.js +177 -0
  466. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  467. package/dist/core/coherence/authoring/sentinels.js +74 -0
  468. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  469. package/dist/core/coherence/coherence.js +251 -0
  470. package/dist/core/coherence/coherence.js.map +1 -0
  471. package/dist/core/coherence/declared.js +269 -0
  472. package/dist/core/coherence/declared.js.map +1 -0
  473. package/dist/core/coherence/events/declared.js +206 -0
  474. package/dist/core/coherence/events/declared.js.map +1 -0
  475. package/dist/core/coherence/events/events.js +204 -0
  476. package/dist/core/coherence/events/events.js.map +1 -0
  477. package/dist/core/coherence/events/grades.js +166 -0
  478. package/dist/core/coherence/events/grades.js.map +1 -0
  479. package/dist/core/coherence/events/lookups.js +122 -0
  480. package/dist/core/coherence/events/lookups.js.map +1 -0
  481. package/dist/core/coherence/events/merged.js +40 -0
  482. package/dist/core/coherence/events/merged.js.map +1 -0
  483. package/dist/core/coherence/living.js +198 -0
  484. package/dist/core/coherence/living.js.map +1 -0
  485. package/dist/core/coherence/lookups.js +155 -0
  486. package/dist/core/coherence/lookups.js.map +1 -0
  487. package/dist/core/coherence/pending.js +39 -0
  488. package/dist/core/coherence/pending.js.map +1 -0
  489. package/dist/core/conflict-markers.js +60 -0
  490. package/dist/core/conflict-markers.js.map +1 -0
  491. package/dist/core/delta/claims.js +73 -0
  492. package/dist/core/delta/claims.js.map +1 -0
  493. package/dist/core/delta/delta.js +296 -0
  494. package/dist/core/delta/delta.js.map +1 -0
  495. package/dist/core/delta/document.js +177 -0
  496. package/dist/core/delta/document.js.map +1 -0
  497. package/dist/core/delta/scope.js +48 -0
  498. package/dist/core/delta/scope.js.map +1 -0
  499. package/dist/core/delta/select.js +237 -0
  500. package/dist/core/delta/select.js.map +1 -0
  501. package/dist/core/dependencies/dependencies.js +141 -0
  502. package/dist/core/dependencies/dependencies.js.map +1 -0
  503. package/dist/core/dependencies/facts.js +165 -0
  504. package/dist/core/dependencies/facts.js.map +1 -0
  505. package/dist/core/dependencies/fanin.js +88 -0
  506. package/dist/core/dependencies/fanin.js.map +1 -0
  507. package/dist/core/dependencies/graph.js +106 -0
  508. package/dist/core/dependencies/graph.js.map +1 -0
  509. package/dist/core/diff/base-git.js +148 -0
  510. package/dist/core/diff/base-git.js.map +1 -0
  511. package/dist/core/diff/base-state.js +209 -0
  512. package/dist/core/diff/base-state.js.map +1 -0
  513. package/dist/core/diff/current-state.js +96 -0
  514. package/dist/core/diff/current-state.js.map +1 -0
  515. package/dist/core/diff/scope/changed-paths.js +188 -0
  516. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  517. package/dist/core/diff/semantic.js +243 -0
  518. package/dist/core/diff/semantic.js.map +1 -0
  519. package/dist/core/diff/victims.js +247 -0
  520. package/dist/core/diff/victims.js.map +1 -0
  521. package/dist/core/docs.js +59 -55
  522. package/dist/core/docs.js.map +1 -1
  523. package/dist/core/doctor/agents.js +222 -0
  524. package/dist/core/doctor/agents.js.map +1 -0
  525. package/dist/core/doctor/config.js +135 -0
  526. package/dist/core/doctor/config.js.map +1 -0
  527. package/dist/core/doctor/doctor.js +275 -0
  528. package/dist/core/doctor/doctor.js.map +1 -0
  529. package/dist/core/doctor/report.js +13 -0
  530. package/dist/core/doctor/report.js.map +1 -0
  531. package/dist/core/doctor/residue.js +110 -0
  532. package/dist/core/doctor/residue.js.map +1 -0
  533. package/dist/core/document/apply.js +77 -0
  534. package/dist/core/document/apply.js.map +1 -0
  535. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  536. package/dist/core/document/frontmatter.js.map +1 -0
  537. package/dist/core/document/grammar/directives.js +143 -0
  538. package/dist/core/document/grammar/directives.js.map +1 -0
  539. package/dist/core/document/parse.js +263 -0
  540. package/dist/core/document/parse.js.map +1 -0
  541. package/dist/core/document/scenarios.js +221 -0
  542. package/dist/core/document/scenarios.js.map +1 -0
  543. package/dist/core/document/spec.js +241 -0
  544. package/dist/core/document/spec.js.map +1 -0
  545. package/dist/core/{config.js → envelope/config.js} +79 -18
  546. package/dist/core/envelope/config.js.map +1 -0
  547. package/dist/core/envelope/json.js +175 -0
  548. package/dist/core/envelope/json.js.map +1 -0
  549. package/dist/core/{version.js → envelope/version.js} +3 -3
  550. package/dist/core/envelope/version.js.map +1 -0
  551. package/dist/core/explain/families.js +175 -0
  552. package/dist/core/explain/families.js.map +1 -0
  553. package/dist/core/explain/fix-tables.js +178 -0
  554. package/dist/core/explain/fix-tables.js.map +1 -0
  555. package/dist/core/explain/lookup.js +181 -0
  556. package/dist/core/explain/lookup.js.map +1 -0
  557. package/dist/core/explain/refusals.js +49 -0
  558. package/dist/core/explain/refusals.js.map +1 -0
  559. package/dist/core/explain/terms.js +144 -0
  560. package/dist/core/explain/terms.js.map +1 -0
  561. package/dist/core/explore/describe.js +105 -0
  562. package/dist/core/explore/describe.js.map +1 -0
  563. package/dist/core/{explore.js → explore/explore.js} +50 -101
  564. package/dist/core/explore/explore.js.map +1 -0
  565. package/dist/core/fleet-context.js +132 -77
  566. package/dist/core/fleet-context.js.map +1 -1
  567. package/dist/core/gate/checks.js +201 -0
  568. package/dist/core/gate/checks.js.map +1 -0
  569. package/dist/core/gate/partners.js +205 -0
  570. package/dist/core/gate/partners.js.map +1 -0
  571. package/dist/core/gate/report.js +47 -0
  572. package/dist/core/gate/report.js.map +1 -0
  573. package/dist/core/gate/verification.js +113 -0
  574. package/dist/core/gate/verification.js.map +1 -0
  575. package/dist/core/gherkin/digest.js +60 -0
  576. package/dist/core/gherkin/digest.js.map +1 -0
  577. package/dist/core/gherkin/emit.js +177 -0
  578. package/dist/core/gherkin/emit.js.map +1 -0
  579. package/dist/core/gherkin/read.js +76 -0
  580. package/dist/core/gherkin/read.js.map +1 -0
  581. package/dist/core/gherkin/stale.js +234 -0
  582. package/dist/core/gherkin/stale.js.map +1 -0
  583. package/dist/core/gherkin/stamp.js +77 -0
  584. package/dist/core/gherkin/stamp.js.map +1 -0
  585. package/dist/core/gherkin/steps/catalogue.js +154 -0
  586. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  587. package/dist/core/gherkin/steps/inventory.js +98 -0
  588. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  589. package/dist/core/gherkin/steps/phrase.js +72 -0
  590. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  591. package/dist/core/glossary/delta.js +106 -0
  592. package/dist/core/glossary/delta.js.map +1 -0
  593. package/dist/core/glossary/tree.js +86 -0
  594. package/dist/core/glossary/tree.js.map +1 -0
  595. package/dist/core/kernel/concurrency.js.map +1 -0
  596. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  597. package/dist/core/kernel/document-bytes.js.map +1 -0
  598. package/dist/core/kernel/fences.js +42 -0
  599. package/dist/core/kernel/fences.js.map +1 -0
  600. package/dist/core/kernel/ids/capability.js +72 -0
  601. package/dist/core/kernel/ids/capability.js.map +1 -0
  602. package/dist/core/kernel/ids/dirs.js +33 -0
  603. package/dist/core/kernel/ids/dirs.js.map +1 -0
  604. package/dist/core/kernel/ids/feature.js +75 -0
  605. package/dist/core/kernel/ids/feature.js.map +1 -0
  606. package/dist/core/kernel/ids/service.js +156 -0
  607. package/dist/core/kernel/ids/service.js.map +1 -0
  608. package/dist/core/kernel/ids/subsystem.js +63 -0
  609. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  610. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  611. package/dist/core/kernel/path-safety.js.map +1 -0
  612. package/dist/core/kernel/records.js.map +1 -0
  613. package/dist/core/links/backlinks.js +68 -0
  614. package/dist/core/links/backlinks.js.map +1 -0
  615. package/dist/core/links/case.js +74 -0
  616. package/dist/core/links/case.js.map +1 -0
  617. package/dist/core/links/corpus.js +104 -0
  618. package/dist/core/links/corpus.js.map +1 -0
  619. package/dist/core/links/findings.js +110 -0
  620. package/dist/core/links/findings.js.map +1 -0
  621. package/dist/core/links/parse.js +236 -0
  622. package/dist/core/links/parse.js.map +1 -0
  623. package/dist/core/mcp/argv.js +111 -0
  624. package/dist/core/mcp/argv.js.map +1 -0
  625. package/dist/core/mcp/author-tools.js +72 -0
  626. package/dist/core/mcp/author-tools.js.map +1 -0
  627. package/dist/core/mcp/framing.js +46 -0
  628. package/dist/core/mcp/framing.js.map +1 -0
  629. package/dist/core/mcp/protocol.js +280 -0
  630. package/dist/core/mcp/protocol.js.map +1 -0
  631. package/dist/core/mcp/tools.js +314 -0
  632. package/dist/core/mcp/tools.js.map +1 -0
  633. package/dist/core/obligations/obligations.js +106 -0
  634. package/dist/core/obligations/obligations.js.map +1 -0
  635. package/dist/core/openapi/baseline/gate.js +166 -0
  636. package/dist/core/openapi/baseline/gate.js.map +1 -0
  637. package/dist/core/openapi/baseline/plan.js +112 -0
  638. package/dist/core/openapi/baseline/plan.js.map +1 -0
  639. package/dist/core/openapi/baseline/record.js +175 -0
  640. package/dist/core/openapi/baseline/record.js.map +1 -0
  641. package/dist/core/openapi/depth.js +122 -0
  642. package/dist/core/openapi/depth.js.map +1 -0
  643. package/dist/core/openapi/digest.js +189 -0
  644. package/dist/core/openapi/digest.js.map +1 -0
  645. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  646. package/dist/core/openapi/doc.js.map +1 -0
  647. package/dist/core/openapi/merge/components.js +234 -0
  648. package/dist/core/openapi/merge/components.js.map +1 -0
  649. package/dist/core/openapi/merge/error.js +26 -0
  650. package/dist/core/openapi/merge/error.js.map +1 -0
  651. package/dist/core/openapi/merge/markers.js +208 -0
  652. package/dist/core/openapi/merge/markers.js.map +1 -0
  653. package/dist/core/openapi/merge/merge.js +256 -0
  654. package/dist/core/openapi/merge/merge.js.map +1 -0
  655. package/dist/core/openapi/merge/pin.js +128 -0
  656. package/dist/core/openapi/merge/pin.js.map +1 -0
  657. package/dist/core/openspec/decide/capabilities.js +244 -0
  658. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  659. package/dist/core/openspec/decide/dispositions.js +63 -0
  660. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  661. package/dist/core/openspec/decide/renames.js +179 -0
  662. package/dist/core/openspec/decide/renames.js.map +1 -0
  663. package/dist/core/openspec/decide/verdict.js +84 -0
  664. package/dist/core/openspec/decide/verdict.js.map +1 -0
  665. package/dist/core/openspec/inventory.js +221 -0
  666. package/dist/core/openspec/inventory.js.map +1 -0
  667. package/dist/core/openspec/model/mapping.js +76 -0
  668. package/dist/core/openspec/model/mapping.js.map +1 -0
  669. package/dist/core/openspec/model/model.js +65 -0
  670. package/dist/core/openspec/model/model.js.map +1 -0
  671. package/dist/core/openspec/scan/changes.js +235 -0
  672. package/dist/core/openspec/scan/changes.js.map +1 -0
  673. package/dist/core/openspec/scan/config.js +200 -0
  674. package/dist/core/openspec/scan/config.js.map +1 -0
  675. package/dist/core/openspec/scan/shape.js +260 -0
  676. package/dist/core/openspec/scan/shape.js.map +1 -0
  677. package/dist/core/openspec/scan/walk.js +133 -0
  678. package/dist/core/openspec/scan/walk.js.map +1 -0
  679. package/dist/core/openspec/scan/workspace.js +71 -0
  680. package/dist/core/openspec/scan/workspace.js.map +1 -0
  681. package/dist/core/owners/codeowners.js +141 -0
  682. package/dist/core/owners/codeowners.js.map +1 -0
  683. package/dist/core/pack/features.js +120 -0
  684. package/dist/core/pack/features.js.map +1 -0
  685. package/dist/core/pack/joins.js +112 -0
  686. package/dist/core/pack/joins.js.map +1 -0
  687. package/dist/core/pack/living.js +118 -0
  688. package/dist/core/pack/living.js.map +1 -0
  689. package/dist/core/pack/pack.js +76 -0
  690. package/dist/core/pack/pack.js.map +1 -0
  691. package/dist/core/permissions/permissions.js +87 -0
  692. package/dist/core/permissions/permissions.js.map +1 -0
  693. package/dist/core/projection/api.js +84 -0
  694. package/dist/core/projection/api.js.map +1 -0
  695. package/dist/core/projection/arch-slice.js +63 -0
  696. package/dist/core/projection/arch-slice.js.map +1 -0
  697. package/dist/core/projection/events.js +34 -0
  698. package/dist/core/projection/events.js.map +1 -0
  699. package/dist/core/provenance/findings.js +208 -0
  700. package/dist/core/provenance/findings.js.map +1 -0
  701. package/dist/core/provenance/git.js +225 -0
  702. package/dist/core/provenance/git.js.map +1 -0
  703. package/dist/core/provenance/gitq/moves.js +182 -0
  704. package/dist/core/provenance/gitq/moves.js.map +1 -0
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  707. package/dist/core/provenance/sample/scope.js +238 -0
  708. package/dist/core/provenance/sample/scope.js.map +1 -0
  709. package/dist/core/provenance/sample/sections.js +61 -0
  710. package/dist/core/provenance/sample/sections.js.map +1 -0
  711. package/dist/core/provenance/sources.js +263 -0
  712. package/dist/core/provenance/sources.js.map +1 -0
  713. package/dist/core/provenance/stamp.js +232 -0
  714. package/dist/core/provenance/stamp.js.map +1 -0
  715. package/dist/core/provenance/walk.js +157 -0
  716. package/dist/core/provenance/walk.js.map +1 -0
  717. package/dist/core/repo/authored/paths.js +192 -0
  718. package/dist/core/repo/authored/paths.js.map +1 -0
  719. package/dist/core/repo/entries.js +126 -0
  720. package/dist/core/repo/entries.js.map +1 -0
  721. package/dist/core/repo/paths.js +236 -0
  722. package/dist/core/repo/paths.js.map +1 -0
  723. package/dist/core/repo/repo.js +248 -0
  724. package/dist/core/repo/repo.js.map +1 -0
  725. package/dist/core/repo/service-target.js +153 -0
  726. package/dist/core/repo/service-target.js.map +1 -0
  727. package/dist/core/repo/state.js +58 -0
  728. package/dist/core/repo/state.js.map +1 -0
  729. package/dist/core/repo/tree/find.js +52 -0
  730. package/dist/core/repo/tree/find.js.map +1 -0
  731. package/dist/core/repo/tree/fs.js +64 -0
  732. package/dist/core/repo/tree/fs.js.map +1 -0
  733. package/dist/core/repo/tree/marker.js +61 -0
  734. package/dist/core/repo/tree/marker.js.map +1 -0
  735. package/dist/core/repo/tree/views.js +172 -0
  736. package/dist/core/repo/tree/views.js.map +1 -0
  737. package/dist/core/repo/tree/walk.js +234 -0
  738. package/dist/core/repo/tree/walk.js.map +1 -0
  739. package/dist/core/results.js +99 -2
  740. package/dist/core/results.js.map +1 -1
  741. package/dist/core/scaffold/landscape.js +131 -0
  742. package/dist/core/scaffold/landscape.js.map +1 -0
  743. package/dist/core/scaffold/readme.js +84 -0
  744. package/dist/core/scaffold/readme.js.map +1 -0
  745. package/dist/core/staging/commit.js +241 -0
  746. package/dist/core/staging/commit.js.map +1 -0
  747. package/dist/core/staging/interrupted.js +96 -0
  748. package/dist/core/staging/interrupted.js.map +1 -0
  749. package/dist/core/staging/lock.js +206 -0
  750. package/dist/core/staging/lock.js.map +1 -0
  751. package/dist/core/staging/recovery/finding.js +35 -0
  752. package/dist/core/staging/recovery/finding.js.map +1 -0
  753. package/dist/core/staging/recovery/intent.js +98 -0
  754. package/dist/core/staging/recovery/intent.js.map +1 -0
  755. package/dist/core/staging/recovery/recover.js +126 -0
  756. package/dist/core/staging/recovery/recover.js.map +1 -0
  757. package/dist/core/staging/recovery/residue.js +62 -0
  758. package/dist/core/staging/recovery/residue.js.map +1 -0
  759. package/dist/core/staging/snapshot.js +202 -0
  760. package/dist/core/staging/snapshot.js.map +1 -0
  761. package/dist/core/staging/txn/forward.js +171 -0
  762. package/dist/core/staging/txn/forward.js.map +1 -0
  763. package/dist/core/staging/txn/journal.js +139 -0
  764. package/dist/core/staging/txn/journal.js.map +1 -0
  765. package/dist/core/staging/txn/transaction.js +75 -0
  766. package/dist/core/staging/txn/transaction.js.map +1 -0
  767. package/dist/core/staging/writes.js +113 -0
  768. package/dist/core/staging/writes.js.map +1 -0
  769. package/dist/core/status/actions/execution.js +49 -0
  770. package/dist/core/status/actions/execution.js.map +1 -0
  771. package/dist/core/status/contracts.js +102 -0
  772. package/dist/core/status/contracts.js.map +1 -0
  773. package/dist/core/status/feature/artifacts.js +171 -0
  774. package/dist/core/status/feature/artifacts.js.map +1 -0
  775. package/dist/core/status/feature/feature.js +207 -0
  776. package/dist/core/status/feature/feature.js.map +1 -0
  777. package/dist/core/status/feature/next.js +150 -0
  778. package/dist/core/status/feature/next.js.map +1 -0
  779. package/dist/core/status/feature/state.js +21 -0
  780. package/dist/core/status/feature/state.js.map +1 -0
  781. package/dist/core/status/fleet/fleet.js +187 -0
  782. package/dist/core/status/fleet/fleet.js.map +1 -0
  783. package/dist/core/status/fleet/next.js +207 -0
  784. package/dist/core/status/fleet/next.js.map +1 -0
  785. package/dist/core/status/interrupted.js +97 -0
  786. package/dist/core/status/interrupted.js.map +1 -0
  787. package/dist/core/status/report.js +31 -0
  788. package/dist/core/status/report.js.map +1 -0
  789. package/dist/core/status/scan.js +41 -0
  790. package/dist/core/status/scan.js.map +1 -0
  791. package/dist/core/status/verification.js +201 -0
  792. package/dist/core/status/verification.js.map +1 -0
  793. package/dist/core/usecases/capability.js +169 -0
  794. package/dist/core/usecases/capability.js.map +1 -0
  795. package/dist/core/usecases/delta/claims.js +116 -0
  796. package/dist/core/usecases/delta/claims.js.map +1 -0
  797. package/dist/core/usecases/delta/flows.js +103 -0
  798. package/dist/core/usecases/delta/flows.js.map +1 -0
  799. package/dist/core/usecases/delta/overlay.js +235 -0
  800. package/dist/core/usecases/delta/overlay.js.map +1 -0
  801. package/dist/core/usecases/fleet.js +137 -0
  802. package/dist/core/usecases/fleet.js.map +1 -0
  803. package/dist/core/usecases/operations.js +111 -0
  804. package/dist/core/usecases/operations.js.map +1 -0
  805. package/dist/core/usecases/place.js +44 -0
  806. package/dist/core/usecases/place.js.map +1 -0
  807. package/dist/core/usecases/touch.js +101 -0
  808. package/dist/core/usecases/touch.js.map +1 -0
  809. package/dist/core/verify/answers.js +173 -0
  810. package/dist/core/verify/answers.js.map +1 -0
  811. package/dist/core/verify/build.js +150 -0
  812. package/dist/core/verify/build.js.map +1 -0
  813. package/dist/core/verify/checklist.js +201 -0
  814. package/dist/core/verify/checklist.js.map +1 -0
  815. package/dist/core/verify/claims/identity.js +61 -0
  816. package/dist/core/verify/claims/identity.js.map +1 -0
  817. package/dist/core/verify/cross/diff.js +97 -0
  818. package/dist/core/verify/cross/diff.js.map +1 -0
  819. package/dist/core/verify/evidence/contract.js +169 -0
  820. package/dist/core/verify/evidence/contract.js.map +1 -0
  821. package/dist/core/verify/file.js +257 -0
  822. package/dist/core/verify/file.js.map +1 -0
  823. package/dist/core/verify/pins/lint.js +157 -0
  824. package/dist/core/verify/pins/lint.js.map +1 -0
  825. package/dist/core/verify/pins/pin.js +95 -0
  826. package/dist/core/verify/pins/pin.js.map +1 -0
  827. package/dist/core/verify/record.js +142 -0
  828. package/dist/core/verify/record.js.map +1 -0
  829. package/dist/core/verify/store/commit.js +89 -0
  830. package/dist/core/verify/store/commit.js.map +1 -0
  831. package/dist/core/verify/store/render.js +57 -0
  832. package/dist/core/verify/store/render.js.map +1 -0
  833. package/dist/core/vocabulary/health.js +91 -0
  834. package/dist/core/vocabulary/health.js.map +1 -0
  835. package/dist/core/vocabulary/issue.js +55 -0
  836. package/dist/core/vocabulary/issue.js.map +1 -0
  837. package/dist/core/vocabulary/maturity.js +145 -0
  838. package/dist/core/vocabulary/maturity.js.map +1 -0
  839. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  840. package/dist/core/vocabulary/report.js.map +1 -0
  841. package/dist/core/vocabulary/steps.js +266 -0
  842. package/dist/core/vocabulary/steps.js.map +1 -0
  843. package/dist/core/workspace/discover.js +239 -0
  844. package/dist/core/workspace/discover.js.map +1 -0
  845. package/dist/core/workspace/render.js +52 -0
  846. package/dist/core/workspace/render.js.map +1 -0
  847. package/examples/README.md +189 -0
  848. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  849. package/examples/docs/architecture/capabilities.yaml +31 -0
  850. package/examples/docs/architecture/landscape.likec4 +280 -0
  851. package/examples/docs/architecture/obligations.yaml +23 -0
  852. package/examples/docs/architecture/permissions.yaml +50 -0
  853. package/examples/docs/architecture/subsystems.likec4 +11 -0
  854. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  855. package/examples/docs/capabilities/checkout/spec.md +55 -0
  856. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  857. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  858. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  859. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  860. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  863. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  864. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  865. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  866. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  867. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  868. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  869. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  870. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  871. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  872. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  873. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  874. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  875. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  876. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  877. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  878. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  879. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  882. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  883. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  884. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  885. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  886. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  887. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  888. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  889. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  890. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  891. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  896. package/examples/docs/glossary/order.md +15 -0
  897. package/examples/docs/glossary/payments/authorization.md +14 -0
  898. package/examples/docs/likec4.config.json +9 -0
  899. package/examples/docs/loam.json +3 -0
  900. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  901. package/examples/docs/services/checkout-web/spec.md +91 -0
  902. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  903. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  904. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  905. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  906. package/examples/docs/services/order-service/arch.spec.md +127 -0
  907. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  908. package/examples/docs/services/order-service/health.yaml +57 -0
  909. package/examples/docs/services/order-service/model.likec4 +138 -0
  910. package/examples/docs/services/order-service/openapi.yaml +257 -0
  911. package/examples/docs/services/order-service/runbook.md +62 -0
  912. package/examples/docs/services/order-service/spec.md +134 -0
  913. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  914. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  915. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  916. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  917. package/examples/docs/services/payment-service/health.yaml +39 -0
  918. package/examples/docs/services/payment-service/model.likec4 +67 -0
  919. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  920. package/examples/docs/services/payment-service/runbook.md +37 -0
  921. package/examples/docs/services/payment-service/spec.md +88 -0
  922. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  923. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  924. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  925. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  926. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  927. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  928. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  929. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  930. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  931. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  932. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  933. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  934. package/examples/docs/services/platform/subsystem.yaml +2 -0
  935. package/examples/fleet.yaml +57 -0
  936. package/package.json +19 -6
  937. package/dist/commands/adopt.js +0 -240
  938. package/dist/commands/adopt.js.map +0 -1
  939. package/dist/commands/archive.js +0 -1313
  940. package/dist/commands/archive.js.map +0 -1
  941. package/dist/commands/delta.js +0 -388
  942. package/dist/commands/delta.js.map +0 -1
  943. package/dist/commands/docs-repo-gate.js.map +0 -1
  944. package/dist/commands/format.js +0 -17
  945. package/dist/commands/format.js.map +0 -1
  946. package/dist/commands/gherkin.js +0 -309
  947. package/dist/commands/gherkin.js.map +0 -1
  948. package/dist/commands/init.js.map +0 -1
  949. package/dist/commands/list.js +0 -291
  950. package/dist/commands/list.js.map +0 -1
  951. package/dist/commands/migrate-openspec.js +0 -950
  952. package/dist/commands/migrate-openspec.js.map +0 -1
  953. package/dist/commands/new.js +0 -413
  954. package/dist/commands/new.js.map +0 -1
  955. package/dist/commands/rebase.js +0 -356
  956. package/dist/commands/rebase.js.map +0 -1
  957. package/dist/commands/show.js.map +0 -1
  958. package/dist/commands/status.js +0 -254
  959. package/dist/commands/status.js.map +0 -1
  960. package/dist/commands/unarchive.js.map +0 -1
  961. package/dist/commands/validate.js +0 -1591
  962. package/dist/commands/validate.js.map +0 -1
  963. package/dist/commands/verify.js +0 -775
  964. package/dist/commands/verify.js.map +0 -1
  965. package/dist/commands/vouch.js +0 -343
  966. package/dist/commands/vouch.js.map +0 -1
  967. package/dist/core/agent.js +0 -2065
  968. package/dist/core/agent.js.map +0 -1
  969. package/dist/core/agents-stamp.js +0 -68
  970. package/dist/core/agents-stamp.js.map +0 -1
  971. package/dist/core/arch.js +0 -130
  972. package/dist/core/arch.js.map +0 -1
  973. package/dist/core/brief.js +0 -733
  974. package/dist/core/brief.js.map +0 -1
  975. package/dist/core/coherence.js +0 -577
  976. package/dist/core/coherence.js.map +0 -1
  977. package/dist/core/concurrency.js.map +0 -1
  978. package/dist/core/config.js.map +0 -1
  979. package/dist/core/delta.js +0 -476
  980. package/dist/core/delta.js.map +0 -1
  981. package/dist/core/dependencies.js +0 -356
  982. package/dist/core/dependencies.js.map +0 -1
  983. package/dist/core/doctor.js +0 -559
  984. package/dist/core/doctor.js.map +0 -1
  985. package/dist/core/document-bytes.js.map +0 -1
  986. package/dist/core/explore.js.map +0 -1
  987. package/dist/core/frontmatter.js.map +0 -1
  988. package/dist/core/gherkin.js +0 -463
  989. package/dist/core/gherkin.js.map +0 -1
  990. package/dist/core/health.js +0 -67
  991. package/dist/core/health.js.map +0 -1
  992. package/dist/core/ids.js +0 -122
  993. package/dist/core/ids.js.map +0 -1
  994. package/dist/core/issue.js +0 -9
  995. package/dist/core/issue.js.map +0 -1
  996. package/dist/core/json.js +0 -74
  997. package/dist/core/json.js.map +0 -1
  998. package/dist/core/likec4.js +0 -479
  999. package/dist/core/likec4.js.map +0 -1
  1000. package/dist/core/maturity.js +0 -66
  1001. package/dist/core/maturity.js.map +0 -1
  1002. package/dist/core/openapi-merge.js +0 -552
  1003. package/dist/core/openapi-merge.js.map +0 -1
  1004. package/dist/core/openapi.js.map +0 -1
  1005. package/dist/core/openspec-inventory.js +0 -1440
  1006. package/dist/core/openspec-inventory.js.map +0 -1
  1007. package/dist/core/path-safety.js.map +0 -1
  1008. package/dist/core/provenance.js +0 -720
  1009. package/dist/core/provenance.js.map +0 -1
  1010. package/dist/core/records.js.map +0 -1
  1011. package/dist/core/repo.js +0 -436
  1012. package/dist/core/repo.js.map +0 -1
  1013. package/dist/core/report.js.map +0 -1
  1014. package/dist/core/spec.js +0 -481
  1015. package/dist/core/spec.js.map +0 -1
  1016. package/dist/core/staging.js +0 -890
  1017. package/dist/core/staging.js.map +0 -1
  1018. package/dist/core/status.js +0 -1104
  1019. package/dist/core/status.js.map +0 -1
  1020. package/dist/core/steps.js +0 -72
  1021. package/dist/core/steps.js.map +0 -1
  1022. package/dist/core/verify.js +0 -807
  1023. package/dist/core/verify.js.map +0 -1
  1024. package/dist/core/version.js.map +0 -1
  1025. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1026. /package/dist/core/{records.js → kernel/records.js} +0 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Member discovery for `loam open` — pure computation, no printing.
3
+ *
4
+ * The hard problem this module solves is binding DIRECTION: each service
5
+ * repo's committed loam.json points AT the docs repo, and the docs repo knows
6
+ * nothing about who points at it. So membership is a join computed at run
7
+ * time — {candidate directory has a loam.json} ∧ {that config resolves its
8
+ * docsDir to this docs repo} — over a deliberately bounded search: a depth-1
9
+ * readdir of a handful of roots, never a disk walk. Same committed files plus
10
+ * same filesystem always produce the same member set, in the same order.
11
+ */
12
+ import { realpathSync, statSync } from "node:fs";
13
+ import { readFile, readdir } from "node:fs/promises";
14
+ import { basename, dirname, join } from "node:path";
15
+ import { CONFIG_FILENAME, parseConfig } from "../envelope/config.js";
16
+ import { inOrder } from "../kernel/concurrency.js";
17
+ import { DocsRepoUnavailableError } from "../repo/state.js";
18
+ /**
19
+ * One directory's identity for "is this the same checkout?" comparisons.
20
+ *
21
+ * Realpath first, because two spellings of one directory (a symlinked sibling,
22
+ * an 8.3 short name, a `..` route) must not become two workspace folders.
23
+ * Case-folded only on win32, because NTFS resolves names case-insensitively
24
+ * and `realpathSync` does not promise canonical case — without the fold,
25
+ * `C:\Repos` and `c:\repos` read as two checkouts of one service and convict
26
+ * an innocent tree of `binding-duplicate`. The fold is NOT applied elsewhere:
27
+ * on a case-sensitive filesystem two genuinely distinct directories may differ
28
+ * only by case, and folding there would silently merge them.
29
+ *
30
+ * `null` means the path names nothing on this machine — for a candidate's
31
+ * docsDir that is the answer "bound to a missing or foreign fleet", never an
32
+ * error.
33
+ */
34
+ function identityOf(dir) {
35
+ try {
36
+ const real = realpathSync(dir);
37
+ return process.platform === "win32" ? real.toLowerCase() : real;
38
+ }
39
+ catch {
40
+ // Unresolvable is a fact about the filesystem, not a failure of the scan.
41
+ return null;
42
+ }
43
+ }
44
+ /** Read and validate one candidate directory's committed binding. */
45
+ async function readBinding(dir) {
46
+ let raw;
47
+ try {
48
+ // The read stays inside the try that classifies it: a loam.json that is a
49
+ // directory, or one whose permissions refuse us, is "exists but cannot be
50
+ // believed", exactly as loadConfig treats the same failures.
51
+ raw = await readFile(join(dir, CONFIG_FILENAME), "utf8");
52
+ }
53
+ catch (err) {
54
+ const e = err;
55
+ // Not present reads as "not bound to anything" — most siblings of a docs
56
+ // repo are ordinary directories, and silence is the honest answer for them.
57
+ if (e.code === "ENOENT")
58
+ return { kind: "absent" };
59
+ return { kind: "unreadable", problem: err instanceof Error ? err.message : String(err) };
60
+ }
61
+ try {
62
+ // THE config validator — a second opinion here would let `loam open` count
63
+ // a checkout every other command refuses to load, or vice versa.
64
+ return { kind: "config", config: parseConfig(raw, dir) };
65
+ }
66
+ catch (err) {
67
+ return { kind: "unreadable", problem: err instanceof Error ? err.message : String(err) };
68
+ }
69
+ }
70
+ /**
71
+ * Depth-1 candidate directories under one scan root, sorted by name so the
72
+ * member set never depends on readdir order — or the reason the root could
73
+ * not be listed at all. The failure is DATA here, not a degrade: whether an
74
+ * unlistable root reads as empty (a default root a CI sandbox denies) or as a
75
+ * refusal (an explicit `--root` that owes an honest answer) is the caller's
76
+ * decision, and this function deciding it would hide the second case forever.
77
+ */
78
+ async function candidateDirs(root) {
79
+ let entries;
80
+ try {
81
+ entries = await readdir(root, { withFileTypes: true });
82
+ }
83
+ catch (err) {
84
+ return { kind: "unlistable", problem: err instanceof Error ? err.message : String(err) };
85
+ }
86
+ const dirs = [];
87
+ for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
88
+ const candidate = join(root, entry.name);
89
+ if (entry.isDirectory()) {
90
+ dirs.push(candidate);
91
+ continue;
92
+ }
93
+ if (!entry.isSymbolicLink())
94
+ continue;
95
+ try {
96
+ // A symlinked sibling is a real checkout on its other end — developers
97
+ // link worktrees beside a docs repo — so it is classified by its target.
98
+ if (statSync(candidate).isDirectory())
99
+ dirs.push(candidate);
100
+ }
101
+ catch {
102
+ // A dangling link names nothing; there is no checkout to consider.
103
+ }
104
+ }
105
+ return { kind: "listed", dirs };
106
+ }
107
+ /**
108
+ * Derive the workspace member set from the committed bindings.
109
+ *
110
+ * The docs repo and the current repo are always members — their bindings are
111
+ * the committed facts `loadConfig` already validated. A scanned sibling is a
112
+ * member iff its loam.json parses and resolves its docsDir to this docs repo.
113
+ * Ordering is deterministic: docs repo, current repo, then scan members
114
+ * sorted by folder name and path.
115
+ *
116
+ * Throws `DocsRepoUnavailableError` when the docs repo's own identity cannot
117
+ * be established — it passed the readiness gate moments ago, so an
118
+ * unresolvable realpath here means it vanished mid-run. Failing closed is the
119
+ * doctrine: comparing candidates against a guessed identity would silently
120
+ * drop every member and answer `no-members` about a fleet nobody scanned.
121
+ */
122
+ export async function discoverMembers(req) {
123
+ const docsIdentity = identityOf(req.docsDir);
124
+ if (docsIdentity === null) {
125
+ throw new DocsRepoUnavailableError({ kind: "missing", path: req.docsDir }, `The configured docs repo does not exist: ${req.docsDir}. ` +
126
+ "Fix `docsDir` in loam.json, clone the docs repo there, " +
127
+ "or run `loam init --docs <dir> --create` to make a new one.");
128
+ }
129
+ const seen = new Set([docsIdentity]);
130
+ const docsMember = {
131
+ path: req.docsDir,
132
+ name: basename(req.docsDir),
133
+ service: null,
134
+ via: "docs-repo",
135
+ };
136
+ // Run from the docs repo itself, the current repo IS the docs repo: one
137
+ // member, and it answers as the docs repo — "docs-repo" is the stronger
138
+ // fact. The `?? spelling` fallback only feeds the dedupe key: an
139
+ // unresolvable configRoot still becomes a member, it just dedupes by its
140
+ // own spelling instead of a canonical one.
141
+ const currentIdentity = identityOf(req.configRoot) ?? req.configRoot;
142
+ const currentMember = seen.has(currentIdentity)
143
+ ? null
144
+ : {
145
+ path: req.configRoot,
146
+ name: req.service ?? basename(req.configRoot),
147
+ service: req.service ?? null,
148
+ via: "current-repo",
149
+ };
150
+ if (currentMember !== null)
151
+ seen.add(currentIdentity);
152
+ // Default roots: beside the docs repo and beside the current repo — the
153
+ // side-by-side layout SCHEMA.md's "../docs-repo" example already assumes.
154
+ // Usually one directory; identity-deduped so it is scanned once.
155
+ const scannedRoots = [];
156
+ const rootIds = new Set();
157
+ for (const root of req.roots ?? [dirname(req.docsDir), dirname(req.configRoot)]) {
158
+ const id = identityOf(root) ?? root;
159
+ if (rootIds.has(id))
160
+ continue;
161
+ rootIds.add(id);
162
+ scannedRoots.push(root);
163
+ }
164
+ const unreadableRoots = [];
165
+ const skipped = [];
166
+ const scanMembers = [];
167
+ // The ROOT loop is sequential on purpose: `seen` is a shared accumulator
168
+ // and "the earlier root wins" is the documented tie-break for a checkout
169
+ // reachable under two roots — scanning roots in parallel would make
170
+ // membership depend on I/O timing. The candidates WITHIN a root carry no
171
+ // such ordering, so their bindings are read through the shared pool;
172
+ // `inOrder` returns them in listing order, which keeps the verdicts below
173
+ // deterministic anyway.
174
+ for (const root of scannedRoots) {
175
+ const listing = await candidateDirs(root);
176
+ if (listing.kind === "unlistable") {
177
+ unreadableRoots.push({ root, problem: listing.problem });
178
+ continue;
179
+ }
180
+ const bindings = await inOrder(listing.dirs, async (dir) => ({
181
+ dir,
182
+ binding: await readBinding(dir),
183
+ }));
184
+ for (const { dir, binding } of bindings) {
185
+ // Identity first, before any verdict: a candidate that already IS a
186
+ // member (the docs repo itself, the current repo, a symlink twin from
187
+ // an earlier root) must not be re-reported — least of all as skipped,
188
+ // which would call one checkout both a member and unreadable.
189
+ const id = identityOf(dir) ?? dir;
190
+ if (seen.has(id))
191
+ continue;
192
+ if (binding.kind === "absent")
193
+ continue;
194
+ if (binding.kind === "unreadable") {
195
+ // Reported, never fatal — "a file loam cannot read is reported, never
196
+ // skipped" — but never a member either: an unreadable binding proves
197
+ // nothing about which fleet the checkout belongs to.
198
+ skipped.push({ path: join(dir, CONFIG_FILENAME), problem: binding.problem });
199
+ continue;
200
+ }
201
+ // The join itself: bound to THIS docs repo, or not a member. `null`
202
+ // (docsDir names nothing here) is a checkout of a missing or foreign
203
+ // fleet — silently out, exactly like one bound elsewhere.
204
+ if (identityOf(binding.config.docsDir) !== docsIdentity)
205
+ continue;
206
+ seen.add(id);
207
+ scanMembers.push({
208
+ path: dir,
209
+ name: binding.config.service ?? basename(dir),
210
+ service: binding.config.service ?? null,
211
+ via: "scan",
212
+ root,
213
+ });
214
+ }
215
+ }
216
+ scanMembers.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
217
+ // Two realpath-DISTINCT checkouts declaring one service id: loam cannot know
218
+ // which one speaks for the service, and a workspace is derived, not guessed.
219
+ // The current repo participates — a scanned twin of the repo you are
220
+ // standing in is the commonest way this happens (a second worktree).
221
+ const byService = new Map();
222
+ for (const member of [...(currentMember === null ? [] : [currentMember]), ...scanMembers]) {
223
+ if (member.service === null)
224
+ continue;
225
+ byService.set(member.service, [...(byService.get(member.service) ?? []), member.path]);
226
+ }
227
+ const duplicates = [...byService.entries()]
228
+ .filter(([, paths]) => paths.length > 1)
229
+ .map(([service, paths]) => ({ service, paths }))
230
+ .sort((a, b) => (a.service < b.service ? -1 : a.service > b.service ? 1 : 0));
231
+ return {
232
+ members: [docsMember, ...(currentMember === null ? [] : [currentMember]), ...scanMembers],
233
+ scannedRoots,
234
+ unreadableRoots,
235
+ skipped,
236
+ duplicates,
237
+ };
238
+ }
239
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../../src/core/workspace/discover.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Member discovery for `loam open` — pure computation, no printing.\n *\n * The hard problem this module solves is binding DIRECTION: each service\n * repo's committed loam.json points AT the docs repo, and the docs repo knows\n * nothing about who points at it. So membership is a join computed at run\n * time — {candidate directory has a loam.json} ∧ {that config resolves its\n * docsDir to this docs repo} — over a deliberately bounded search: a depth-1\n * readdir of a handful of roots, never a disk walk. Same committed files plus\n * same filesystem always produce the same member set, in the same order.\n */\nimport { realpathSync, statSync } from \"node:fs\";\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { CONFIG_FILENAME, parseConfig, type LoamConfig } from \"../envelope/config.js\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { ServiceId } from \"../kernel/ids/service.js\";\nimport { DocsRepoUnavailableError } from \"../repo/state.js\";\n\ninterface MemberFacts {\n /** Absolute path of the member's repository root. */\n path: string;\n /** Folder display name: the member's service id when bound, else its basename. */\n name: string;\n service: ServiceId | null;\n}\n\n/**\n * One folder of the workspace being derived, with where it came from. A\n * variant, not an optional field: `root` exists exactly when the member was\n * found by the scan, and the union makes an always-member carrying a scan\n * root — or a scan member missing one — unrepresentable. One arm per `via`\n * value (not \"docs-repo\" | \"current-repo\" folded into one), because a\n * single-literal discriminant is what the checker's narrowing filters on.\n */\nexport type WorkspaceMember =\n | (MemberFacts & { via: \"docs-repo\" })\n | (MemberFacts & { via: \"current-repo\" })\n | (MemberFacts & { via: \"scan\"; root: string });\n\n/** Everything the scan learned, as data — the command decides what refuses. */\nexport interface Discovery {\n members: WorkspaceMember[];\n /** The roots actually scanned, deduped, in scan order. */\n scannedRoots: string[];\n /**\n * Roots whose depth-1 listing itself failed. Recorded, never swallowed:\n * \"nothing is wrong\" and \"I could not look\" are opposite facts, and only\n * the caller knows whether the root was a degradable default or an explicit\n * `--root` that owes a refusal.\n */\n unreadableRoots: { root: string; problem: string }[];\n /** Candidates whose loam.json exists but cannot be believed — reported, never members. */\n skipped: { path: string; problem: string }[];\n /** Service ids declared by two or more realpath-distinct member checkouts. */\n duplicates: { service: string; paths: string[] }[];\n}\n\nexport interface DiscoverRequest {\n /** Resolved docs repo root — the identity every candidate binding is compared against. */\n docsDir: DocsDir;\n /** Root of the repo whose loam.json governs this invocation (`config.root`). */\n configRoot: string;\n /** The service that repo declares, when it declares one. */\n service?: ServiceId;\n /** Explicit scan roots. When present they REPLACE the defaults — an explicit scan is exactly what was asked. */\n roots?: string[];\n}\n\n/**\n * One directory's identity for \"is this the same checkout?\" comparisons.\n *\n * Realpath first, because two spellings of one directory (a symlinked sibling,\n * an 8.3 short name, a `..` route) must not become two workspace folders.\n * Case-folded only on win32, because NTFS resolves names case-insensitively\n * and `realpathSync` does not promise canonical case — without the fold,\n * `C:\\Repos` and `c:\\repos` read as two checkouts of one service and convict\n * an innocent tree of `binding-duplicate`. The fold is NOT applied elsewhere:\n * on a case-sensitive filesystem two genuinely distinct directories may differ\n * only by case, and folding there would silently merge them.\n *\n * `null` means the path names nothing on this machine — for a candidate's\n * docsDir that is the answer \"bound to a missing or foreign fleet\", never an\n * error.\n */\nfunction identityOf(dir: string): string | null {\n try {\n const real = realpathSync(dir);\n return process.platform === \"win32\" ? real.toLowerCase() : real;\n } catch {\n // Unresolvable is a fact about the filesystem, not a failure of the scan.\n return null;\n }\n}\n\n/** A parsed candidate binding, or the reason it cannot be believed. */\ntype Candidate =\n | { kind: \"config\"; config: LoamConfig }\n | { kind: \"unreadable\"; problem: string }\n | { kind: \"absent\" };\n\n/** Read and validate one candidate directory's committed binding. */\nasync function readBinding(dir: string): Promise<Candidate> {\n let raw: string;\n try {\n // The read stays inside the try that classifies it: a loam.json that is a\n // directory, or one whose permissions refuse us, is \"exists but cannot be\n // believed\", exactly as loadConfig treats the same failures.\n raw = await readFile(join(dir, CONFIG_FILENAME), \"utf8\");\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n // Not present reads as \"not bound to anything\" — most siblings of a docs\n // repo are ordinary directories, and silence is the honest answer for them.\n if (e.code === \"ENOENT\") return { kind: \"absent\" };\n return { kind: \"unreadable\", problem: err instanceof Error ? err.message : String(err) };\n }\n try {\n // THE config validator — a second opinion here would let `loam open` count\n // a checkout every other command refuses to load, or vice versa.\n return { kind: \"config\", config: parseConfig(raw, dir) };\n } catch (err) {\n return { kind: \"unreadable\", problem: err instanceof Error ? err.message : String(err) };\n }\n}\n\n/**\n * Depth-1 candidate directories under one scan root, sorted by name so the\n * member set never depends on readdir order — or the reason the root could\n * not be listed at all. The failure is DATA here, not a degrade: whether an\n * unlistable root reads as empty (a default root a CI sandbox denies) or as a\n * refusal (an explicit `--root` that owes an honest answer) is the caller's\n * decision, and this function deciding it would hide the second case forever.\n */\nasync function candidateDirs(\n root: string,\n): Promise<{ kind: \"listed\"; dirs: string[] } | { kind: \"unlistable\"; problem: string }> {\n let entries;\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch (err) {\n return { kind: \"unlistable\", problem: err instanceof Error ? err.message : String(err) };\n }\n const dirs: string[] = [];\n for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {\n const candidate = join(root, entry.name);\n if (entry.isDirectory()) {\n dirs.push(candidate);\n continue;\n }\n if (!entry.isSymbolicLink()) continue;\n try {\n // A symlinked sibling is a real checkout on its other end — developers\n // link worktrees beside a docs repo — so it is classified by its target.\n if (statSync(candidate).isDirectory()) dirs.push(candidate);\n } catch {\n // A dangling link names nothing; there is no checkout to consider.\n }\n }\n return { kind: \"listed\", dirs };\n}\n\n/**\n * Derive the workspace member set from the committed bindings.\n *\n * The docs repo and the current repo are always members — their bindings are\n * the committed facts `loadConfig` already validated. A scanned sibling is a\n * member iff its loam.json parses and resolves its docsDir to this docs repo.\n * Ordering is deterministic: docs repo, current repo, then scan members\n * sorted by folder name and path.\n *\n * Throws `DocsRepoUnavailableError` when the docs repo's own identity cannot\n * be established — it passed the readiness gate moments ago, so an\n * unresolvable realpath here means it vanished mid-run. Failing closed is the\n * doctrine: comparing candidates against a guessed identity would silently\n * drop every member and answer `no-members` about a fleet nobody scanned.\n */\nexport async function discoverMembers(req: DiscoverRequest): Promise<Discovery> {\n const docsIdentity = identityOf(req.docsDir);\n if (docsIdentity === null) {\n throw new DocsRepoUnavailableError(\n { kind: \"missing\", path: req.docsDir },\n `The configured docs repo does not exist: ${req.docsDir}. ` +\n \"Fix `docsDir` in loam.json, clone the docs repo there, \" +\n \"or run `loam init --docs <dir> --create` to make a new one.\",\n );\n }\n const seen = new Set<string>([docsIdentity]);\n const docsMember: WorkspaceMember = {\n path: req.docsDir,\n name: basename(req.docsDir),\n service: null,\n via: \"docs-repo\",\n };\n\n // Run from the docs repo itself, the current repo IS the docs repo: one\n // member, and it answers as the docs repo — \"docs-repo\" is the stronger\n // fact. The `?? spelling` fallback only feeds the dedupe key: an\n // unresolvable configRoot still becomes a member, it just dedupes by its\n // own spelling instead of a canonical one.\n const currentIdentity = identityOf(req.configRoot) ?? req.configRoot;\n const currentMember: WorkspaceMember | null = seen.has(currentIdentity)\n ? null\n : {\n path: req.configRoot,\n name: req.service ?? basename(req.configRoot),\n service: req.service ?? null,\n via: \"current-repo\",\n };\n if (currentMember !== null) seen.add(currentIdentity);\n\n // Default roots: beside the docs repo and beside the current repo — the\n // side-by-side layout SCHEMA.md's \"../docs-repo\" example already assumes.\n // Usually one directory; identity-deduped so it is scanned once.\n const scannedRoots: string[] = [];\n const rootIds = new Set<string>();\n for (const root of req.roots ?? [dirname(req.docsDir), dirname(req.configRoot)]) {\n const id = identityOf(root) ?? root;\n if (rootIds.has(id)) continue;\n rootIds.add(id);\n scannedRoots.push(root);\n }\n\n const unreadableRoots: Discovery[\"unreadableRoots\"] = [];\n const skipped: Discovery[\"skipped\"] = [];\n const scanMembers: WorkspaceMember[] = [];\n // The ROOT loop is sequential on purpose: `seen` is a shared accumulator\n // and \"the earlier root wins\" is the documented tie-break for a checkout\n // reachable under two roots — scanning roots in parallel would make\n // membership depend on I/O timing. The candidates WITHIN a root carry no\n // such ordering, so their bindings are read through the shared pool;\n // `inOrder` returns them in listing order, which keeps the verdicts below\n // deterministic anyway.\n for (const root of scannedRoots) {\n const listing = await candidateDirs(root);\n if (listing.kind === \"unlistable\") {\n unreadableRoots.push({ root, problem: listing.problem });\n continue;\n }\n const bindings = await inOrder(listing.dirs, async (dir) => ({\n dir,\n binding: await readBinding(dir),\n }));\n for (const { dir, binding } of bindings) {\n // Identity first, before any verdict: a candidate that already IS a\n // member (the docs repo itself, the current repo, a symlink twin from\n // an earlier root) must not be re-reported — least of all as skipped,\n // which would call one checkout both a member and unreadable.\n const id = identityOf(dir) ?? dir;\n if (seen.has(id)) continue;\n if (binding.kind === \"absent\") continue;\n if (binding.kind === \"unreadable\") {\n // Reported, never fatal — \"a file loam cannot read is reported, never\n // skipped\" — but never a member either: an unreadable binding proves\n // nothing about which fleet the checkout belongs to.\n skipped.push({ path: join(dir, CONFIG_FILENAME), problem: binding.problem });\n continue;\n }\n // The join itself: bound to THIS docs repo, or not a member. `null`\n // (docsDir names nothing here) is a checkout of a missing or foreign\n // fleet — silently out, exactly like one bound elsewhere.\n if (identityOf(binding.config.docsDir) !== docsIdentity) continue;\n seen.add(id);\n scanMembers.push({\n path: dir,\n name: binding.config.service ?? basename(dir),\n service: binding.config.service ?? null,\n via: \"scan\",\n root,\n });\n }\n }\n scanMembers.sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : a.path < b.path ? -1 : a.path > b.path ? 1 : 0,\n );\n\n // Two realpath-DISTINCT checkouts declaring one service id: loam cannot know\n // which one speaks for the service, and a workspace is derived, not guessed.\n // The current repo participates — a scanned twin of the repo you are\n // standing in is the commonest way this happens (a second worktree).\n const byService = new Map<string, string[]>();\n for (const member of [...(currentMember === null ? [] : [currentMember]), ...scanMembers]) {\n if (member.service === null) continue;\n byService.set(member.service, [...(byService.get(member.service) ?? []), member.path]);\n }\n const duplicates = [...byService.entries()]\n .filter(([, paths]) => paths.length > 1)\n .map(([service, paths]) => ({ service, paths }))\n .sort((a, b) => (a.service < b.service ? -1 : a.service > b.service ? 1 : 0));\n\n return {\n members: [docsMember, ...(currentMember === null ? [] : [currentMember]), ...scanMembers],\n scannedRoots,\n unreadableRoots,\n skipped,\n duplicates,\n };\n}\n"]}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Deterministic `.code-workspace` rendering — pure computation, no printing.
3
+ *
4
+ * The output is a derived file: same members plus same target path must
5
+ * produce the same bytes on every machine, which is why every path is spelled
6
+ * with forward slashes (VS Code and Cursor accept them on Windows too, and
7
+ * they are the one spelling that diffs identically across checkouts) and why
8
+ * the JSON shape follows saveConfig's convention — 2-space indent, trailing
9
+ * newline.
10
+ */
11
+ import { dirname, isAbsolute, relative } from "node:path";
12
+ /**
13
+ * The `path` a folder entry gets, from the already-computed relative and the
14
+ * member's absolute path.
15
+ *
16
+ * Split out of `renderWorkspace` because the fallback decision is win32-only
17
+ * on a win32 host — `relative()` answers a cross-drive pair with the ABSOLUTE
18
+ * target, since no `..` chain crosses drives — and a POSIX test host can
19
+ * therefore never steer the platform `relative()` into producing one. Taking
20
+ * `rel` as data lets the suite exercise the branch with win32-shaped input.
21
+ *
22
+ * The drive-letter test backs up `isAbsolute` because that check is also
23
+ * platform-bound: on POSIX, `D:\other\svc` reads as a relative name. A POSIX
24
+ * directory literally named `D:` would match too and get the absolute
25
+ * spelling instead of the relative one — a stranger workspace file, but a
26
+ * correct one, and the pathological name is not worth a platform fork here.
27
+ */
28
+ export function workspacePathSpelling(rel, absolute) {
29
+ const spelling = rel === "" ? "." : isAbsolute(rel) || /^[A-Za-z]:[\\/]/.test(rel) ? absolute : rel;
30
+ return spelling.split(/[\\/]/).join("/");
31
+ }
32
+ /**
33
+ * Render the workspace document for `members`, targeted at `outPath`.
34
+ *
35
+ * Folder paths are relative to the file's own directory, so the committed
36
+ * side-by-side layout opens on every machine that checks it out; a member on
37
+ * another drive keeps its absolute path, because no relative spelling exists.
38
+ * The folders are returned still attached to their members so the command can
39
+ * echo the exact written spelling into its `--json` payload without joining
40
+ * two arrays by position.
41
+ */
42
+ export function renderWorkspace(members, outPath) {
43
+ const outDir = dirname(outPath);
44
+ const folders = members.map((member) => ({
45
+ member,
46
+ name: member.name,
47
+ path: workspacePathSpelling(relative(outDir, member.path), member.path),
48
+ }));
49
+ const text = JSON.stringify({ folders: folders.map(({ name, path }) => ({ name, path })) }, null, 2) + "\n";
50
+ return { text, folders };
51
+ }
52
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/core/workspace/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAY1D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW,EAAE,QAAgB;IACjE,MAAM,QAAQ,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC;IACpG,OAAO,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAmC,EACnC,OAAe;IAEf,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACvC,MAAM;QACN,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,qBAAqB,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC;KACxE,CAAC,CAAC,CAAC;IACJ,MAAM,IAAI,GACR,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IACjG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * Deterministic `.code-workspace` rendering — pure computation, no printing.\n *\n * The output is a derived file: same members plus same target path must\n * produce the same bytes on every machine, which is why every path is spelled\n * with forward slashes (VS Code and Cursor accept them on Windows too, and\n * they are the one spelling that diffs identically across checkouts) and why\n * the JSON shape follows saveConfig's convention — 2-space indent, trailing\n * newline.\n */\nimport { dirname, isAbsolute, relative } from \"node:path\";\nimport type { WorkspaceMember } from \"./discover.js\";\n\n/** One folder row, still carrying the member it was derived from. */\nexport interface RenderedFolder {\n member: WorkspaceMember;\n /** Exactly what lands in the file's `name`. */\n name: string;\n /** Exactly what lands in the file's `path` — the forward-slash spelling. */\n path: string;\n}\n\n/**\n * The `path` a folder entry gets, from the already-computed relative and the\n * member's absolute path.\n *\n * Split out of `renderWorkspace` because the fallback decision is win32-only\n * on a win32 host — `relative()` answers a cross-drive pair with the ABSOLUTE\n * target, since no `..` chain crosses drives — and a POSIX test host can\n * therefore never steer the platform `relative()` into producing one. Taking\n * `rel` as data lets the suite exercise the branch with win32-shaped input.\n *\n * The drive-letter test backs up `isAbsolute` because that check is also\n * platform-bound: on POSIX, `D:\\other\\svc` reads as a relative name. A POSIX\n * directory literally named `D:` would match too and get the absolute\n * spelling instead of the relative one — a stranger workspace file, but a\n * correct one, and the pathological name is not worth a platform fork here.\n */\nexport function workspacePathSpelling(rel: string, absolute: string): string {\n const spelling = rel === \"\" ? \".\" : isAbsolute(rel) || /^[A-Za-z]:[\\\\/]/.test(rel) ? absolute : rel;\n return spelling.split(/[\\\\/]/).join(\"/\");\n}\n\n/**\n * Render the workspace document for `members`, targeted at `outPath`.\n *\n * Folder paths are relative to the file's own directory, so the committed\n * side-by-side layout opens on every machine that checks it out; a member on\n * another drive keeps its absolute path, because no relative spelling exists.\n * The folders are returned still attached to their members so the command can\n * echo the exact written spelling into its `--json` payload without joining\n * two arrays by position.\n */\nexport function renderWorkspace(\n members: readonly WorkspaceMember[],\n outPath: string,\n): { text: string; folders: RenderedFolder[] } {\n const outDir = dirname(outPath);\n const folders = members.map((member) => ({\n member,\n name: member.name,\n path: workspacePathSpelling(relative(outDir, member.path), member.path),\n }));\n const text =\n JSON.stringify({ folders: folders.map(({ name, path }) => ({ name, path })) }, null, 2) + \"\\n\";\n return { text, folders };\n}\n"]}
@@ -0,0 +1,189 @@
1
+ # The example fleet
2
+
3
+ `docs/` is a complete, runnable loam docs repo. It is small enough to read in one sitting and large
4
+ enough that every row of [SCHEMA.md](../SCHEMA.md)'s canonical-joins table is exercised by something
5
+ — including the joins that only appear once a fleet has more than two services in it.
6
+
7
+ Run it from a clone. `docs/loam.json` is committed and says `"docsDir": "."`, so the tree governs
8
+ itself: every command below resolves to this fleet because it is run from inside it. There is
9
+ nothing to write first and nothing to delete afterwards.
10
+
11
+ ```bash
12
+ cd examples/docs
13
+ loam list # the fleet and its maturity ladder
14
+ loam status # what to do next, derived from the files
15
+ loam validate --all # the gate CI runs
16
+ loam archive FEAT-101 --dry-run # the three-axis merge plan, writing nothing
17
+ loam archive FEAT-112 --dry-run # an operation being retired, writing nothing
18
+ loam verify FEAT-088 # a shipped feature's done-check: attested
19
+ loam verify FEAT-120 # …and its pair, the same check: verified
20
+ loam dependencies # the active-feature graph
21
+ ```
22
+
23
+ Working on loam itself and running from source, use `npx tsx ../../src/cli.ts <args>` in place of
24
+ `loam`. Not `npm run dev --`: npm runs a script from the package root, so it would leave
25
+ `examples/docs` and resolve some other `loam.json` — the fleet it reported would not be this one.
26
+
27
+ `test/examples.test.ts` pins the validate summary, every finding code and the archive plans
28
+ file-for-file, so nothing below can drift away from the code without a test going red.
29
+
30
+ ## What is in it
31
+
32
+ Five services, drawn inside one grouping element so the map looks like ordinary grouped C4:
33
+
34
+ | service | what it is here to show |
35
+ |---|---|
36
+ | `checkout-web` | a UI service: page-specs under `ui/pages/`, no `openapi.yaml` of its own, and the one spec in the fleet that names no `sources` |
37
+ | `order-service` | the busiest spine: four operations, a produced event, a consumed event, and a deprecated operation with a feature already open to retire it |
38
+ | `payment-service` | the outbox, the acquirer, and an `arch.spec.md` whose `Covers:` lines reach the C4 model *and* the health signals |
39
+ | `identity-service` | the permission vocabulary's owner, and a deprecated operation whose consumer has not migrated — filed into `services/platform/` |
40
+ | `notification-service` | a service with no HTTP API at all — three consumed messages, one of them from a producer outside the fleet — filed into `services/platform/` |
41
+
42
+ Around them: `kafka` as an `#external` `#platform` system with a topic per channel (edges point at
43
+ the topic, never at the broker), `stripe` and `salesforce` as external systems, and
44
+ `architecture/permissions.yaml` as the fleet's authorization vocabulary.
45
+
46
+ **Three authored capability documents**, which are the business tree rather than the architecture:
47
+ `capabilities/checkout/spec.md`, `capabilities/identity/tokens/spec.md` and
48
+ `capabilities/order-notifications/spec.md`. The third carries the axis's second realizer: its one
49
+ promise — "a placed order produces exactly one confirmation" — is cross-service by construction, so
50
+ no service's `spec.md` can keep it, and `architecture/usecases/order-notification.likec4` claims it
51
+ with `#req-NOTIFY-ONCE` beside its `#cap-` tag. The other two are kept by service requirements
52
+ through `Realizes:`. Both ids are also declared in `architecture/capabilities.yaml` — the vocabulary
53
+ is the union of the two sides, and a name a document elaborates does not have to leave the YAML,
54
+ which is what keeps the metadata (`description`, `owner`) that a document has no field for. The
55
+ nested one carries the lesson: the id `identity/tokens` keeps its slash everywhere it is written, so
56
+ the tree spells it as directories, and `capabilities/identity/` holding no `spec.md` of its own is a
57
+ GROUP and earns no finding. Each requirement in both files is a promise a customer could check,
58
+ names no service, and carries a `Requirement-ID:` — required here, unlike in a service spec, because
59
+ these documents outlive every service that realizes them.
60
+
61
+ **Two glossary terms**, `glossary/order.md` and the nested `glossary/payments/authorization.md`,
62
+ each cited from the capability documents that use the word. This is the axis's whole mechanism in
63
+ two files: the citation is an ordinary markdown link, so `loam validate` resolves it like any other
64
+ and `loam list glossary` can answer "which documents use this term" as a join rather than a grep.
65
+ The two terms also cite EACH OTHER, which is deliberate — a glossary is a network of definitions —
66
+ and that reciprocal pair is exactly what does NOT count as adoption: both terms stay out of
67
+ `glossary.unlinked` because a capability document cites them, not because they cite each other.
68
+ Delete either of those two capability citations and the example gains a tenth warning.
69
+
70
+ **One architectural obligation, declared and applied and met** — which is three files agreeing, and
71
+ the axis's whole mechanism. `architecture/adrs/0001-transactional-outbox.md` says WHAT was decided;
72
+ `#obl-outbox` on the `payment-service → kafka.paymentEvents` edge in the fleet map says WHERE it
73
+ holds (on the publish edge, not on the service — payment-service also serves an API that owes
74
+ nothing of the kind); `architecture/obligations.yaml` declares the name so a mistyped tag is an
75
+ error rather than a word nobody notices; and `ARCH-PAY-OUTBOX` in payment-service's `arch.spec.md`
76
+ covers that same edge, which is the team saying it is met. Because all four line up, the example is
77
+ SILENT about the outbox — the working join produces no finding, which is what a working join should
78
+ do. The second declaration, `idempotency-key`, is the one that talks: it is declared and placed
79
+ nowhere, and `obligation.unapplied` is the warning in the table below.
80
+
81
+ **On the event spine the arrow follows the message** — producer → topic for `publishes`, topic →
82
+ consumer for `consumes` — and that is load-bearing rather than aesthetic. `publishes` binds the
83
+ edge's source and `consumes` binds its target, so a consume edge drawn the other way round binds to
84
+ nothing: the metadata parses, the document validates, and the check silently grades zero edges.
85
+ Nothing in loam reports that today, which is why the landscape says so in a comment.
86
+
87
+ ## Four features, at four points in their life
88
+
89
+ The two archived ones are a **matched pair**, and the pairing is the point: same command, same
90
+ shape of record, two different verdicts. A showcase that demonstrated only one would teach that the
91
+ distinction is decorative — and keeping those two answers apart is most of what loam claims to be
92
+ for.
93
+
94
+ - **`features/archive/FEAT-088-refunds/`** — shipped, and **`attested`**. It was merged by the real
95
+ `loam archive`, so its `.loam-before/` snapshot holds the exact bytes the merge overwrote and
96
+ `loam unarchive FEAT-088` would put them back. Its `verification.yaml` is the done-check written
97
+ down: every claim is confirmed, but the scenario claims rest on an agent's word instead of a
98
+ digest-matched test run. loam does not pretend those are the same thing.
99
+ - **`features/archive/FEAT-120-refund-notification/`** — shipped, and **`verified`**. The sequel
100
+ FEAT-088 left open: the money went back and nobody told the customer. It publishes
101
+ `payment.PaymentRefunded` from payment-service and has notification-service turn it into the
102
+ message the customer is waiting for — so it exercises the event spine end to end, across two
103
+ services, with no HTTP anywhere in it.
104
+
105
+ Its five scenario claims were answered by `--results` from the `scenario-report.json` beside it:
106
+ loam's own runner-neutral `{"loamScenarioReport": 1, …}` shape, which any runner can be adapted
107
+ into. Its two `event.declares` claims still rest on an agent's word. That mix is the honest
108
+ common case — a suite that answers the scenarios, and wiring claims a human vouches for — and it
109
+ still reads `verified`, because the verdict turns on the scenario claims alone.
110
+ - **`features/FEAT-101-payment-splitting/`** — in flight, and the big one: a new service arriving
111
+ with its own requirements, architecture requirements and contract, a C4 delta that splices a
112
+ nested element into the living landscape, a `MODIFIED` requirement that **renames its heading**
113
+ while keeping its `Requirement-ID` — loam's rename mechanism — and an **event-contract delta**
114
+ (`specs/payment-service/asyncapi.yaml`): a complete AsyncAPI 3.0 document restating
115
+ payment-service's living contract under `loam rebase`-written `x-loam-based-on` pins and adding
116
+ one new producer side, so the archive merges the new slots and leaves every pinned quote to the
117
+ living contract's own copy.
118
+ - **`features/FEAT-112-retire-order-v1/`** — in flight, and the smallest legal shape: no
119
+ `delta.likec4` at all, because retiring an operation moves no boxes. It carries the two halves
120
+ loam requires together for a removal — a `REMOVED` requirement and an `x-loam-remove: true` marker
121
+ inside the operation object.
122
+
123
+ ## The warnings are the lesson
124
+
125
+ `loam validate --all` reports **0 errors and 10 warnings** here, and every one of them is
126
+ deliberate. An example that reported nothing would teach nothing about what these checks catch:
127
+
128
+ | finding | what it is demonstrating |
129
+ |---|---|
130
+ | `sources.absent` | `checkout-web`'s spec names no `sources`, so nothing ties it to code and `loam vouch` would refuse to stamp it |
131
+ | `spine.op-link-missing` | one landscape edge says it calls payment-service and names no operation, so no check can tell whether the call still exists |
132
+ | `spine.op-deprecated` | payment-service still calls identity-service's `validateToken`, which that contract marks `deprecated: true` — the consumer, not the provider, is the one being told |
133
+ | `api.requirement-deprecated` (×2) | `IDN-VALIDATE-LEGACY` and `ORD-PLACE-V1` each govern only a deprecated operation: promised behaviour on its way out |
134
+ | `permissions.unenforced` | `user/profile:read` is declared in the vocabulary and named by no requirement — the shape a vocabulary drifts into |
135
+ | `capability.unrealized` | `payments/settlement` is declared in `architecture/capabilities.yaml` and no living requirement's `Capability:` line names it — a promise nobody implemented, or a word nobody adopted; `loam list capabilities` shows it as `0 — unrealized` beside the realized ones |
136
+ | `capability.requirement-unrealized` | `checkout#CHECKOUT-PRICE-HONOURED` — one promise inside a capability whose OTHER promise three services realize, so the row above says nothing about it. That contrast IS the demonstration: `capability.unrealized` finds a capability nobody claimed, and only this code finds the gap inside a capability that looks healthy. Three requirements do carry `Realizes:` lines — `WEB-CHECKOUT`, `ORD-PLACE` and `PAY-AUTHORIZE` all name `checkout#CHECKOUT-CHARGE-ONCE` — and the fourth promise is simply not implemented, which is a normal state for a business document written ahead of the fleet |
137
+ | `c4.uncovered` | FEAT-101 adds a `checkout-web → payment-split-service` edge that no arch requirement covers, so its architectural obligations would ship untested |
138
+ | `obligation.unapplied` | `idempotency-key` is declared in `architecture/obligations.yaml` and no `#obl-` tag applies it anywhere on the fleet map — a decision that was reversed and left its word behind, or one nobody has placed yet. Its sibling `outbox` IS placed and IS covered, and says nothing: that contrast is the demonstration |
139
+
140
+ **Two of the five are filed into a subsystem.** `services/platform/` (its `subsystem.yaml` is the
141
+ marker — title and description, never members) groups `identity-service` and `notification-service`,
142
+ while the other three sit unfiled — the permanent, normal state a partially organized fleet lives
143
+ in, counted by `loam list` and never a finding. Placement is not identity: both services' specs,
144
+ contracts and digests are byte-identical to their unfiled days, every command addresses them by bare
145
+ id, and `architecture/subsystems.likec4` — GENERATED by `loam subsystem sync`, one view per
146
+ subsystem, one `include` per line — is the only file the grouping added. Edit the tree with
147
+ `loam subsystem move|rename|rm` and the views file travels in the same transaction; edit it by hand
148
+ and `validate --all` answers `subsystem.views-stale`. (`order-service` and `payment-service` stay
149
+ unfiled deliberately: FEAT-088's committed version-2 snapshot restores them by literal path, and the
150
+ `loam unarchive FEAT-088` walkthrough must keep working byte-for-byte.)
151
+
152
+ Plus one count rather than a finding: `sourcesUnverifiableFromHere: 4`. Four specs name paths in
153
+ their own service repositories, and this is none of those repositories — the fleet gate reports the
154
+ blind spot instead of resolving it. Running `loam validate --service <id>` inside each service repo
155
+ is what closes it.
156
+
157
+ ## What the example deliberately does not carry
158
+
159
+ - **`AGENTS.md`.** A real docs repo has one, written by `loam init --docs . --create`. It is left
160
+ out here because a generated `AGENTS.md` carries the version stamp of the binary that wrote it,
161
+ and this tree is committed while the binary keeps moving — a stamped copy in the repository would
162
+ be stale by the next release and would say so to every reader of the example.
163
+
164
+ **`loam.json` used to be left out with it**, on the same "keep the tree a pure set of documents"
165
+ reasoning, and that trade was wrong: without it the example could not be RUN. `loam status` here
166
+ answered "No loam.json found" and exited 1, and the workaround this page printed instead — a
167
+ throwaway `loam.json` at the repository root — then governed every other directory in the clone,
168
+ so `loam init` from anywhere below it refused, and forgetting to delete it silently pointed later
169
+ commands at the example fleet. It is committed now, byte-for-byte the two lines `loam init` writes
170
+ for a docs repo that governs itself, which makes `cd examples/docs` the whole of the setup.
171
+ - **`payment-split-service`'s own `asyncapi.yaml`.** The async axis has the full feature lifecycle
172
+ now — FEAT-101's `specs/payment-service/asyncapi.yaml` demonstrates the merge half on a living
173
+ contract — but the NEW service's contract for the drawn `PaymentSplit` edge is deliberately
174
+ deferred: the edge keeps the message in its title and no `metadata { publishes }`, because that
175
+ metadata is a join and the join demands a contract declaring the send
176
+ (`c4-event.message-undefined` refuses one without it). A
177
+ `specs/payment-split-service/asyncapi.yaml` would ride the archive's creation branch once the
178
+ consumer of `PaymentSplit` exists. The comment in `delta.likec4` says so rather than leaving it to
179
+ be discovered.
180
+ - **A generated Gherkin suite.** `loam gherkin` writes into a *service's* repository, and there are
181
+ none here — this fleet is six repositories, and `docs/` is one of them. The same reason leaves
182
+ `sources` unresolvable and every service `draft`: `loam vouch` only runs where the code is, so no
183
+ SERVICE in this tree can be promoted past `draft`.
184
+
185
+ That is a different word from FEAT-120's `verified` verdict and the two must not be read as one.
186
+ A service's ladder rung (`empty → partial → documented → sourced → vouched`) says a human stamped
187
+ its living spec against the code; a feature's verdict says how its claims were answered. FEAT-120
188
+ is `verified` in a fleet whose services are all `draft`, which is exactly the state a team is in
189
+ the day its first suite goes green.
@@ -0,0 +1,33 @@
1
+ # 0001 — Event publishers write through a transactional outbox
2
+
3
+ Status: accepted
4
+ Date: 2026-08-28
5
+
6
+ ## Context
7
+
8
+ A service that writes its own state and then publishes an event about it has two
9
+ places to fail between. The failure is silent and asymmetric: the state change
10
+ lands, the event does not, and every consumer's view of the world is quietly
11
+ wrong until somebody reconciles by hand.
12
+
13
+ ## Decision
14
+
15
+ A service that publishes a domain event SHALL write the event and the state
16
+ change it reports in one database transaction, and relay it to the broker
17
+ afterwards. Never a dual write.
18
+
19
+ ## Scope
20
+
21
+ This is not a fleet-wide policy every service inherits — that is the shape this
22
+ repository deliberately does not use. It applies where it is tagged: see
23
+ `#obl-outbox` in [the fleet map](../landscape.likec4), which today marks the
24
+ `payment-service → kafka.paymentEvents` edge and nothing else.
25
+ `payment-service`'s [ARCH-PAY-OUTBOX](../../services/payment-service/arch.spec.md)
26
+ is the requirement that keeps it.
27
+
28
+ ## Consequences
29
+
30
+ A publisher needs an outbox table and a relay. A consumer must tolerate a
31
+ duplicate: the relay guarantees at-least-once, which is why
32
+ `checkout#CHECKOUT-CHARGE-ONCE` is a promise about the customer's card and not
33
+ about the number of messages.
@@ -0,0 +1,31 @@
1
+ # The fleet's capability vocabulary, joined to requirements by `Capability:`.
2
+ #
3
+ # Opt-in the OTHER way round from permissions.yaml: the FILE is the opt-in, not the line.
4
+ # A fleet without this file gets no capability findings at all, however many `Capability:`
5
+ # lines its requirements carry; writing it is the moment the axis turns on. `loam validate`
6
+ # then grades both directions — an undeclared name is `capability.unknown` (error, with
7
+ # close-name suggestions), an unreadable file is `capability.invalid` exactly once per run,
8
+ # and a declared capability nothing realizes is `capability.unrealized` (warning, one per
9
+ # capability). `loam list capabilities` reports the whole rollup: which requirements realize
10
+ # each capability, in which services, and how much of that rests on drafts.
11
+ #
12
+ # Ids are flat keys with `/` allowed for nesting — `identity/tokens` is ONE id, exactly the
13
+ # token a requirement writes, never collapsed to `identity`. Descriptions and owners are
14
+ # explanatory; nothing resolves them.
15
+ capabilities:
16
+ checkout:
17
+ description: carry a customer from a filled cart to a confirmed, paid order
18
+ owner: checkout-team
19
+ identity/tokens:
20
+ description: issue and answer for the access tokens every fleet call presents
21
+ owner: platform-identity
22
+ order-notifications:
23
+ description: tell the customer what happened to their order, exactly once per event
24
+ owner: growth-team
25
+ # Declared, and realized by no living requirement anywhere in the fleet — the example's
26
+ # `capability.unrealized` (warning) demonstration. It is the shape this axis exists to
27
+ # make visible: a capability page was agreed in a meeting, the word entered the fleet's
28
+ # vocabulary, and no requirement anywhere claims to implement any part of it.
29
+ payments/settlement:
30
+ description: pay merchants out for captured orders
31
+ owner: payments-team