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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -1,10 +1,9 @@
1
1
  import { resolve } from "node:path";
2
- import { loadSource } from "../../core/c4/likec4.js";
2
+ import { errorText, loadSource } from "../../core/c4/likec4.js";
3
3
  import { readFleetFile } from "../../core/c4/seed/fleet-file.js";
4
4
  import { sealLandscape } from "../../core/c4/seed/stamp.js";
5
5
  import { renderLandscape } from "../../core/c4/seed/template.js";
6
- import { loadConfig } from "../../core/envelope/config.js";
7
- import { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from "../../core/envelope/json.js";
6
+ import { configOrRefuse, emitJson, emitJsonError, fail, repoPath } from "../../core/envelope/json.js";
8
7
  import { NotUtf8Error, readUtf8 } from "../../core/staging/writes.js";
9
8
  import { sayRecovered } from "../policy/format.js";
10
9
  import { docsRepoReady, reportDocsRepoError } from "../policy/gate.js";
@@ -19,12 +18,10 @@ export function registerSeed(program) {
19
18
  .action(async (opts) => {
20
19
  const json = opts.json === true;
21
20
  const fromArg = opts.from ?? "fleet.yaml";
22
- const loaded = await loadConfig();
23
- if (loaded.kind !== "loaded") {
24
- reportNoConfig(json, loaded);
21
+ const config = await configOrRefuse(json);
22
+ if (config === null)
25
23
  return;
26
- }
27
- const { docsDir } = loaded.config;
24
+ const { docsDir } = config;
28
25
  // `services`, not `docs`: every id in fleet.yaml becomes a
29
26
  // services/<id>/ directory, and the under-lock preflight enumerates the
30
27
  // tree — a run without the directory that IS the fleet is guessing.
@@ -70,7 +67,7 @@ export function registerSeed(program) {
70
67
  const firstError = doc.errors[0];
71
68
  if (firstError !== undefined) {
72
69
  return fail(json, "internal", `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +
73
- `in ${fromArg}; nothing was written. First error: ${firstError.message}`);
70
+ `in ${fromArg}; nothing was written. First error: ${errorText(firstError)}`);
74
71
  }
75
72
  const committed = await commitSeed({
76
73
  docsDir,
@@ -1 +1 @@
1
- {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/commands/seed/seed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam seed` — the fleet map's first hour, mechanically: a tiny\n * human-authored fleet.yaml (service ids, optional subsystems, externals and\n * `a -> b` calls) templated into architecture/landscape.likec4 plus one\n * services/<id>/ directory per service, in one journaled transaction.\n *\n * NOT an extractor, deliberately: a human stated every fact in the file —\n * which services exist and who calls whom is the one thing no generator can\n * read off a repository (core/docs.ts's landscape doctrine) — and loam does\n * mechanical templating over those statements, the same category as\n * `loam new`. Seed never overwrites human work: it writes the landscape only\n * when the file is absent, is the scaffold's untouched stub, or carries its\n * own verified line-1 stamp (`core/c4/seed/stamp.ts`); everything else\n * refuses `seed-landscape-edited`.\n */\nimport type { Command } from \"commander\";\nimport { resolve } from \"node:path\";\nimport { loadSource } from \"../../core/c4/likec4.js\";\nimport { readFleetFile, type SeedFileProblem } from \"../../core/c4/seed/fleet-file.js\";\nimport { sealLandscape } from \"../../core/c4/seed/stamp.js\";\nimport { renderLandscape } from \"../../core/c4/seed/template.js\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { NotUtf8Error, readUtf8 } from \"../../core/staging/writes.js\";\nimport { sayRecovered } from \"../policy/format.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"../policy/gate.js\";\nimport { serviceTreePathOf } from \"../../core/repo/service-target.js\";\nimport { commitSeed, type LandscapeDisposition, type SeedCommit } from \"./commit.js\";\n\ninterface SeedOptions {\n from?: string;\n json?: boolean;\n}\n\nexport function registerSeed(program: Command): void {\n program\n .command(\"seed\")\n .description(\n \"Template the fleet map and service directories from a tiny human-authored fleet.yaml\",\n )\n .option(\"--from <file>\", \"the fleet file to read (default: fleet.yaml in the current directory)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: SeedOptions) => {\n const json = opts.json === true;\n const fromArg = opts.from ?? \"fleet.yaml\";\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const { docsDir } = loaded.config;\n // `services`, not `docs`: every id in fleet.yaml becomes a\n // services/<id>/ directory, and the under-lock preflight enumerates the\n // tree — a run without the directory that IS the fleet is guessing.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n // The fleet file is INPUT only — read once, from wherever the caller\n // keeps it (it may legitimately live outside both repos), never stored\n // and never written back. Refusals name the spelling the caller typed.\n let text: string;\n try {\n text = await readUtf8(resolve(fromArg));\n } catch (err) {\n if (err instanceof NotUtf8Error) return fail(json, \"seed-file-invalid\", err.message);\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} does not exist — write the fleet file (a services: list of ids, plus ` +\n `optional subsystems:, externals: and calls: like 'checkout -> payments'), or ` +\n `point --from at where it lives. Nothing was written.`,\n );\n }\n // An errno is a filesystem answer (EISDIR, EACCES, …) about the named\n // file; anything without one is a real bug and stays `internal`.\n if (e.path !== undefined || typeof e.errno === \"number\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} could not be read as the fleet file — ${e.message}. Nothing was written.`,\n );\n }\n throw err;\n }\n\n const read = readFleetFile(text, fromArg);\n if (!read.ok) return refuseFleetFile(json, read.problem);\n const seed = read.seed;\n\n // The self-check: the emitted DSL is parsed in memory BEFORE anything\n // is written, so a templater bug can never land as a broken landscape.\n // Its parsed document travels to the plan as the landscape's OWN facts —\n // the render's input when no other `architecture/` document exists, and\n // the fallback otherwise. It is not the render's whole input: the global\n // style ids the render asks for are the PROJECT's (the template declares\n // no `global`, but a sibling document may), which `plan.ts`'s\n // `projectFacts` reads the way `validate --all` will.\n const sealed = sealLandscape(renderLandscape(seed));\n const doc = await loadSource(sealed);\n const firstError = doc.errors[0];\n if (firstError !== undefined) {\n return fail(\n json,\n \"internal\",\n `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +\n `in ${fromArg}; nothing was written. First error: ${firstError.message}`,\n );\n }\n\n const committed = await commitSeed({\n docsDir,\n seed,\n landscape: sealed,\n map: doc,\n rerun: `loam seed --from ${fromArg}`,\n });\n if (!committed.ok) return refuseCommit(json, committed);\n\n const written = committed.written.map((w) => repoPath(docsDir, w));\n const removed = committed.removed.map((w) => repoPath(docsDir, w));\n const firstCreated = committed.services.created[0];\n const next = [\n \"loam validate --all\",\n \"loam list --needs-work\",\n // Interpolation is agent-safe: the id already passed the grammar.\n ...(firstCreated === undefined ? [] : [`loam adopt --service ${firstCreated}`]),\n ];\n\n if (json) {\n emitJson({\n command: \"seed\",\n fleetFile: fromArg,\n landscape: committed.landscape,\n created: written,\n removed,\n services: committed.services,\n subsystems: [...seed.subsystems].sort(),\n externals: [...seed.externals].sort(),\n calls: seed.calls.length,\n next,\n ...(committed.recovered === null ? {} : { recovered: committed.recovered }),\n });\n return;\n }\n if (committed.recovered !== null && committed.recovered.outcome !== \"consistent\") {\n console.log(`note: ${sayRecovered(committed.recovered)}`);\n }\n console.log(\n `architecture/landscape.likec4 ${LANDSCAPE_SENTENCE[committed.landscape]} — ` +\n `${seed.services.length} service(s), ${seed.calls.length} call(s), from ${fromArg}`,\n );\n for (const w of written) console.log(` + ${w}`);\n // Only ever the generated views file, and only when the resulting tree\n // has no subsystems left to view — but a removal printed as `+` would be\n // a lie, and an unmentioned one a surprise in `git status`.\n for (const w of removed) console.log(` - ${w} (generated, and this tree has no subsystems)`);\n for (const id of committed.services.existing) {\n // Spelled from the enumeration, and this line is the one place it matters\n // most: the sentence is ABOUT placement, so naming the root form would\n // say \"left exactly where it is\" while pointing at somewhere it is not.\n const at = await serviceTreePathOf(docsDir, id);\n console.log(\n ` = ${at}/ already exists — left exactly where it is (fleet.yaml's ` +\n `placement is ignored for an existing service; \\`loam subsystem move ${id} ` +\n `--into <name>\\` is how one moves)`,\n );\n }\n console.log(\"\\nNext:\");\n for (const n of next) console.log(` ${n}`);\n });\n}\n\nconst LANDSCAPE_SENTENCE: Record<LandscapeDisposition, string> = {\n created: \"created\",\n \"replaced-stub\": \"written (replaced the scaffold's untouched stub)\",\n regenerated: \"regenerated (the line-1 stamp proved no hand edits)\",\n};\n\n/** One stable code per problem kind — the mapping the fleet-file union exists for. */\nfunction refuseFleetFile(json: boolean, problem: SeedFileProblem): void {\n switch (problem.kind) {\n case \"invalid\":\n return fail(json, \"seed-file-invalid\", problem.message);\n case \"duplicate\":\n return fail(json, \"seed-duplicate-service\", problem.message);\n case \"unknown-subsystem\":\n return fail(json, \"seed-unknown-subsystem\", problem.message);\n case \"unknown-endpoint\":\n // The existing code, not a fifth new one: the caller's action is the\n // same as for every other unknown service — fix the name, re-run.\n return fail(json, \"unknown-service\", problem.message);\n }\n // Exhaustiveness checked, not assumed, and the branch is genuinely dead:\n // without it a fifth `kind` falls out of the switch printing nothing and\n // leaving `process.exitCode` unset — a refused fleet file exiting 0, which\n // is the worst answer available.\n const unreachable: never = problem;\n throw new Error(`refuseFleetFile: no code for fleet-file problem '${JSON.stringify(unreachable)}'`);\n}\n\nfunction refuseCommit(json: boolean, committed: Exclude<SeedCommit, { ok: true }>): void {\n if (\"repoGone\" in committed) return reportDocsRepoError(json, committed.repoGone);\n if (committed.code === \"seed-file-invalid\") {\n // The exact ids to paste ride as data beside the prose, so an agent can\n // repair fleet.yaml without parsing a sentence.\n if (json) {\n emitJsonError(\"seed-file-invalid\", committed.message, {\n missingServices: committed.missingServices,\n });\n return;\n }\n return fail(json, \"seed-file-invalid\", committed.message);\n }\n return fail(json, committed.code, committed.message);\n}\n"]}
1
+ {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/commands/seed/seed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam seed` — the fleet map's first hour, mechanically: a tiny\n * human-authored fleet.yaml (service ids, optional subsystems, externals and\n * `a -> b` calls) templated into architecture/landscape.likec4 plus one\n * services/<id>/ directory per service, in one journaled transaction.\n *\n * NOT an extractor, deliberately: a human stated every fact in the file —\n * which services exist and who calls whom is the one thing no generator can\n * read off a repository (core/docs.ts's landscape doctrine) — and loam does\n * mechanical templating over those statements, the same category as\n * `loam new`. Seed never overwrites human work: it writes the landscape only\n * when the file is absent, is the scaffold's untouched stub, or carries its\n * own verified line-1 stamp (`core/c4/seed/stamp.ts`); everything else\n * refuses `seed-landscape-edited`.\n */\nimport type { Command } from \"commander\";\nimport { resolve } from \"node:path\";\nimport { errorText, loadSource } from \"../../core/c4/likec4.js\";\nimport { readFleetFile, type SeedFileProblem } from \"../../core/c4/seed/fleet-file.js\";\nimport { sealLandscape } from \"../../core/c4/seed/stamp.js\";\nimport { renderLandscape } from \"../../core/c4/seed/template.js\";\nimport { configOrRefuse, emitJson, emitJsonError, fail, repoPath } from \"../../core/envelope/json.js\";\nimport { NotUtf8Error, readUtf8 } from \"../../core/staging/writes.js\";\nimport { sayRecovered } from \"../policy/format.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"../policy/gate.js\";\nimport { serviceTreePathOf } from \"../../core/repo/service-target.js\";\nimport { commitSeed, type LandscapeDisposition, type SeedCommit } from \"./commit.js\";\n\ninterface SeedOptions {\n from?: string;\n json?: boolean;\n}\n\nexport function registerSeed(program: Command): void {\n program\n .command(\"seed\")\n .description(\n \"Template the fleet map and service directories from a tiny human-authored fleet.yaml\",\n )\n .option(\"--from <file>\", \"the fleet file to read (default: fleet.yaml in the current directory)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: SeedOptions) => {\n const json = opts.json === true;\n const fromArg = opts.from ?? \"fleet.yaml\";\n\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // `services`, not `docs`: every id in fleet.yaml becomes a\n // services/<id>/ directory, and the under-lock preflight enumerates the\n // tree — a run without the directory that IS the fleet is guessing.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n // The fleet file is INPUT only — read once, from wherever the caller\n // keeps it (it may legitimately live outside both repos), never stored\n // and never written back. Refusals name the spelling the caller typed.\n let text: string;\n try {\n text = await readUtf8(resolve(fromArg));\n } catch (err) {\n if (err instanceof NotUtf8Error) return fail(json, \"seed-file-invalid\", err.message);\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"ENOENT\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} does not exist — write the fleet file (a services: list of ids, plus ` +\n `optional subsystems:, externals: and calls: like 'checkout -> payments'), or ` +\n `point --from at where it lives. Nothing was written.`,\n );\n }\n // An errno is a filesystem answer (EISDIR, EACCES, …) about the named\n // file; anything without one is a real bug and stays `internal`.\n if (e.path !== undefined || typeof e.errno === \"number\") {\n return fail(\n json,\n \"seed-file-invalid\",\n `${fromArg} could not be read as the fleet file — ${e.message}. Nothing was written.`,\n );\n }\n throw err;\n }\n\n const read = readFleetFile(text, fromArg);\n if (!read.ok) return refuseFleetFile(json, read.problem);\n const seed = read.seed;\n\n // The self-check: the emitted DSL is parsed in memory BEFORE anything\n // is written, so a templater bug can never land as a broken landscape.\n // Its parsed document travels to the plan as the landscape's OWN facts —\n // the render's input when no other `architecture/` document exists, and\n // the fallback otherwise. It is not the render's whole input: the global\n // style ids the render asks for are the PROJECT's (the template declares\n // no `global`, but a sibling document may), which `plan.ts`'s\n // `projectFacts` reads the way `validate --all` will.\n const sealed = sealLandscape(renderLandscape(seed));\n const doc = await loadSource(sealed);\n const firstError = doc.errors[0];\n if (firstError !== undefined) {\n return fail(\n json,\n \"internal\",\n `loam's own templater produced a landscape that does not parse — a bug in loam, not ` +\n `in ${fromArg}; nothing was written. First error: ${errorText(firstError)}`,\n );\n }\n\n const committed = await commitSeed({\n docsDir,\n seed,\n landscape: sealed,\n map: doc,\n rerun: `loam seed --from ${fromArg}`,\n });\n if (!committed.ok) return refuseCommit(json, committed);\n\n const written = committed.written.map((w) => repoPath(docsDir, w));\n const removed = committed.removed.map((w) => repoPath(docsDir, w));\n const firstCreated = committed.services.created[0];\n const next = [\n \"loam validate --all\",\n \"loam list --needs-work\",\n // Interpolation is agent-safe: the id already passed the grammar.\n ...(firstCreated === undefined ? [] : [`loam adopt --service ${firstCreated}`]),\n ];\n\n if (json) {\n emitJson({\n command: \"seed\",\n fleetFile: fromArg,\n landscape: committed.landscape,\n created: written,\n removed,\n services: committed.services,\n subsystems: [...seed.subsystems].sort(),\n externals: [...seed.externals].sort(),\n calls: seed.calls.length,\n next,\n ...(committed.recovered === null ? {} : { recovered: committed.recovered }),\n });\n return;\n }\n if (committed.recovered !== null && committed.recovered.outcome !== \"consistent\") {\n console.log(`note: ${sayRecovered(committed.recovered)}`);\n }\n console.log(\n `architecture/landscape.likec4 ${LANDSCAPE_SENTENCE[committed.landscape]} — ` +\n `${seed.services.length} service(s), ${seed.calls.length} call(s), from ${fromArg}`,\n );\n for (const w of written) console.log(` + ${w}`);\n // Only ever the generated views file, and only when the resulting tree\n // has no subsystems left to view — but a removal printed as `+` would be\n // a lie, and an unmentioned one a surprise in `git status`.\n for (const w of removed) console.log(` - ${w} (generated, and this tree has no subsystems)`);\n for (const id of committed.services.existing) {\n // Spelled from the enumeration, and this line is the one place it matters\n // most: the sentence is ABOUT placement, so naming the root form would\n // say \"left exactly where it is\" while pointing at somewhere it is not.\n const at = await serviceTreePathOf(docsDir, id);\n console.log(\n ` = ${at}/ already exists — left exactly where it is (fleet.yaml's ` +\n `placement is ignored for an existing service; \\`loam subsystem move ${id} ` +\n `--into <name>\\` is how one moves)`,\n );\n }\n console.log(\"\\nNext:\");\n for (const n of next) console.log(` ${n}`);\n });\n}\n\nconst LANDSCAPE_SENTENCE: Record<LandscapeDisposition, string> = {\n created: \"created\",\n \"replaced-stub\": \"written (replaced the scaffold's untouched stub)\",\n regenerated: \"regenerated (the line-1 stamp proved no hand edits)\",\n};\n\n/** One stable code per problem kind — the mapping the fleet-file union exists for. */\nfunction refuseFleetFile(json: boolean, problem: SeedFileProblem): void {\n switch (problem.kind) {\n case \"invalid\":\n return fail(json, \"seed-file-invalid\", problem.message);\n case \"duplicate\":\n return fail(json, \"seed-duplicate-service\", problem.message);\n case \"unknown-subsystem\":\n return fail(json, \"seed-unknown-subsystem\", problem.message);\n case \"unknown-endpoint\":\n // The existing code, not a fifth new one: the caller's action is the\n // same as for every other unknown service — fix the name, re-run.\n return fail(json, \"unknown-service\", problem.message);\n }\n // Exhaustiveness checked, not assumed, and the branch is genuinely dead:\n // without it a fifth `kind` falls out of the switch printing nothing and\n // leaving `process.exitCode` unset — a refused fleet file exiting 0, which\n // is the worst answer available.\n const unreachable: never = problem;\n throw new Error(`refuseFleetFile: no code for fleet-file problem '${JSON.stringify(unreachable)}'`);\n}\n\nfunction refuseCommit(json: boolean, committed: Exclude<SeedCommit, { ok: true }>): void {\n if (\"repoGone\" in committed) return reportDocsRepoError(json, committed.repoGone);\n if (committed.code === \"seed-file-invalid\") {\n // The exact ids to paste ride as data beside the prose, so an agent can\n // repair fleet.yaml without parsing a sentence.\n if (json) {\n emitJsonError(\"seed-file-invalid\", committed.message, {\n missingServices: committed.missingServices,\n });\n return;\n }\n return fail(json, \"seed-file-invalid\", committed.message);\n }\n return fail(json, committed.code, committed.message);\n}\n"]}
@@ -1,6 +1,5 @@
1
- import { loadConfig } from "../../core/envelope/config.js";
2
1
  import { FleetContext } from "../../core/fleet-context.js";
3
- import { fail, reportNoConfig } from "../../core/envelope/json.js";
2
+ import { configOrRefuse, fail } from "../../core/envelope/json.js";
4
3
  import { DocsRepoUnavailableError } from "../../core/repo/state.js";
5
4
  import { listServices, resolveFeature } from "../../core/repo/repo.js";
6
5
  import { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from "../policy/gate.js";
@@ -20,12 +19,9 @@ export function registerShow(program) {
20
19
  return;
21
20
  }
22
21
  const forced = opts.type;
23
- const loaded = await loadConfig();
24
- if (loaded.kind !== "loaded") {
25
- reportNoConfig(json, loaded);
22
+ const config = await configOrRefuse(json);
23
+ if (config === null)
26
24
  return;
27
- }
28
- const config = loaded.config;
29
25
  const { docsDir } = config;
30
26
  // "No service or feature 'x' in <dir>" is a lie when <dir> is not a docs
31
27
  // repo at all — the same refusal validate and list owe (docsRepoReady).
@@ -1 +1 @@
1
- {"version":3,"file":"show.js","sourceRoot":"","sources":["../../../src/commands/show/show.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAS3C,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;SAChD,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CAAC,eAAe,EAAE,sCAAsC,CAAC;SAC/D,MAAM,CAAC,KAAK,EAAE,MAAc,EAAE,IAAiB,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAClF,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,mBAAmB,IAAI,CAAC,IAAI,iCAAiC,CAAC,CAAC;YAC5F,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAA8B,CAAC;QAEnD,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;QAClC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAC3B,yEAAyE;QACzE,wEAAwE;QACxE,kEAAkE;QAClE,wEAAwE;QACxE,4DAA4D;QAC5D,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC;YAAE,OAAO;QAClD,sEAAsE;QACtE,2EAA2E;QAC3E,4EAA4E;QAC5E,2EAA2E;QAC3E,2CAA2C;QAC3C,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;QAEnC,IAAI,CAAC;YACH,4EAA4E;YAC5E,0EAA0E;YAC1E,MAAM,OAAO,GACX,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;YAC1F,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;gBACnD,OAAO;YACT,CAAC;YACD,uEAAuE;YACvE,uEAAuE;YACvE,2DAA2D;YAC3D,MAAM,KAAK,GACT,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;YACzG,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,MAAM,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC;YACtD,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,MAAM,KAAK,MAAM,QAAQ,OAAO,GAAG,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,wBAAwB,EAAE,CAAC;gBAC5C,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YACD,uEAAuE;YACvE,yEAAyE;YACzE,0EAA0E;YAC1E,2BAA2B,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,MAAM,mBAAmB,EAAE,OAAO,CAAC,CAAC;QACjF,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE","sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { fail, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { listServices, resolveFeature } from \"../../core/repo/repo.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"../policy/gate.js\";\nimport { showFeature } from \"./feature.js\";\nimport { showService } from \"./service.js\";\n\ntype TargetType = \"service\" | \"feature\";\n\ninterface ShowOptions {\n json?: boolean;\n type?: string;\n}\n\nexport function registerShow(program: Command): void {\n program\n .command(\"show\")\n .argument(\"<target>\", \"service id or feature id\")\n .description(\"Show everything loam knows about a service or a feature\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--type <kind>\", \"force the reading: service | feature\")\n .action(async (target: string, opts: ShowOptions) => {\n const json = opts.json === true;\n if (opts.type !== undefined && opts.type !== \"service\" && opts.type !== \"feature\") {\n fail(json, \"invalid-option\", `Unknown --type '${opts.type}'. Expected: service | feature.`);\n return;\n }\n const forced = opts.type as TargetType | undefined;\n\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // \"No service or feature 'x' in <dir>\" is a lie when <dir> is not a docs\n // repo at all — the same refusal validate and list owe (docsRepoReady).\n // `docs`, not `services`: a feature is readable from a repo whose\n // services/ is missing, and if the target turns out to be a service the\n // enumeration below refuses with `services-missing` anyway.\n if (!docsRepoReady(json, docsDir, \"docs\")) return;\n // One read index for the invocation. Deciding the target is a service\n // enumerates the fleet, and so does resolving the landscape's edges to the\n // services that own them — `loam show <service>` walked services/ twice for\n // one answer. The context memoises only within this invocation, so a later\n // run still sees whatever is on disk then.\n const context = new FleetContext();\n\n try {\n // A feature id is distinctive (FEAT-101); a service name is arbitrary. When\n // both could match, the feature wins and --type forces the other reading.\n const feature =\n forced === \"service\" ? null : await resolveFeature(docsDir, target, \"include\", context);\n if (feature) {\n await showFeature(docsDir, feature, json, context);\n return;\n }\n // The enumeration's own id travels on — `entry.id` rather than the raw\n // argument (repo/service-target.ts's rule): equal as strings, and only\n // one of them carries the fact that a readdir produced it.\n const entry =\n forced === \"feature\" ? undefined : (await listServices(docsDir, context)).find((s) => s.id === target);\n if (entry !== undefined) {\n await showService(docsDir, entry.id, json, context);\n return;\n }\n\n const looked = forced ? forced : \"service or feature\";\n fail(json, \"unknown-target\", `No ${looked} '${target}' in ${docsDir}.`);\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // An artifact that exists but cannot be read used to escape as a stack\n // trace (`internal` in --json), naming nothing. `show` reads one target,\n // so it has nothing to fall back on — but it can at least say WHICH file.\n reportRepositoryUnavailable(json, err, `'${target}' cannot be shown`, docsDir);\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n"]}
1
+ {"version":3,"file":"show.js","sourceRoot":"","sources":["../../../src/commands/show/show.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { configOrRefuse, fail } from \"../../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { listServices, resolveFeature } from \"../../core/repo/repo.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"../policy/gate.js\";\nimport { showFeature } from \"./feature.js\";\nimport { showService } from \"./service.js\";\n\ntype TargetType = \"service\" | \"feature\";\n\ninterface ShowOptions {\n json?: boolean;\n type?: string;\n}\n\nexport function registerShow(program: Command): void {\n program\n .command(\"show\")\n .argument(\"<target>\", \"service id or feature id\")\n .description(\"Show everything loam knows about a service or a feature\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--type <kind>\", \"force the reading: service | feature\")\n .action(async (target: string, opts: ShowOptions) => {\n const json = opts.json === true;\n if (opts.type !== undefined && opts.type !== \"service\" && opts.type !== \"feature\") {\n fail(json, \"invalid-option\", `Unknown --type '${opts.type}'. Expected: service | feature.`);\n return;\n }\n const forced = opts.type as TargetType | undefined;\n\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // \"No service or feature 'x' in <dir>\" is a lie when <dir> is not a docs\n // repo at all — the same refusal validate and list owe (docsRepoReady).\n // `docs`, not `services`: a feature is readable from a repo whose\n // services/ is missing, and if the target turns out to be a service the\n // enumeration below refuses with `services-missing` anyway.\n if (!docsRepoReady(json, docsDir, \"docs\")) return;\n // One read index for the invocation. Deciding the target is a service\n // enumerates the fleet, and so does resolving the landscape's edges to the\n // services that own them — `loam show <service>` walked services/ twice for\n // one answer. The context memoises only within this invocation, so a later\n // run still sees whatever is on disk then.\n const context = new FleetContext();\n\n try {\n // A feature id is distinctive (FEAT-101); a service name is arbitrary. When\n // both could match, the feature wins and --type forces the other reading.\n const feature =\n forced === \"service\" ? null : await resolveFeature(docsDir, target, \"include\", context);\n if (feature) {\n await showFeature(docsDir, feature, json, context);\n return;\n }\n // The enumeration's own id travels on — `entry.id` rather than the raw\n // argument (repo/service-target.ts's rule): equal as strings, and only\n // one of them carries the fact that a readdir produced it.\n const entry =\n forced === \"feature\" ? undefined : (await listServices(docsDir, context)).find((s) => s.id === target);\n if (entry !== undefined) {\n await showService(docsDir, entry.id, json, context);\n return;\n }\n\n const looked = forced ? forced : \"service or feature\";\n fail(json, \"unknown-target\", `No ${looked} '${target}' in ${docsDir}.`);\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // An artifact that exists but cannot be read used to escape as a stack\n // trace (`internal` in --json), naming nothing. `show` reads one target,\n // so it has nothing to fall back on — but it can at least say WHICH file.\n reportRepositoryUnavailable(json, err, `'${target}' cannot be shown`, docsDir);\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n"]}
@@ -5,6 +5,7 @@
5
5
  * one of them is a contract. This side may be reworded freely; the payload
6
6
  * beside it may not.
7
7
  */
8
+ import { answeredSplit } from "../../core/verify/record.js";
8
9
  import { SEVERITY_MARK } from "../../core/vocabulary/report.js";
9
10
  function pad(values) {
10
11
  return Math.max(0, ...values.map((v) => v.length));
@@ -33,6 +34,10 @@ function artifactLabel(a) {
33
34
  * because they answer different questions and only together are they honest:
34
35
  * `recorded 4/4 confirmed` used to be the whole line for a feature `verify`
35
36
  * calls `attested` — complete, and resting on somebody's word about a test.
37
+ * Who answered the confirmed ones rides right after the count for the same
38
+ * reason one step further: `verified` turns on the scenario claims alone, and
39
+ * `verified 19/19 confirmed` read as nineteen runs over a record where six
40
+ * answers were an agent's word about a permission or an edge.
36
41
  */
37
42
  function verificationLine(v) {
38
43
  if (v.state === "absent")
@@ -40,6 +45,7 @@ function verificationLine(v) {
40
45
  if (v.state === "unreadable")
41
46
  return "unreadable";
42
47
  return (`${v.state} · ${v.verdict} ${v.confirmed}/${v.claims} confirmed` +
48
+ (v.confirmed > 0 ? ` (${answeredSplit(v.answered)})` : "") +
43
49
  (v.unanswered > 0 ? ` · ${v.unanswered} unanswered` : "") +
44
50
  (v.attested > 0 ? ` · ${v.attested} scenario(s) on an agent's word, not a test run` : "") +
45
51
  ` (recorded ${v.recorded ?? "?"})`);
@@ -130,11 +136,14 @@ export function printFleet(r) {
130
136
  printInterrupted(r.interrupted);
131
137
  const s = r.services;
132
138
  console.log(`loam status — ${s.total} service(s) · ${r.features.length} feature(s) in flight`);
133
- console.log(
134
- // The sampled count inside the vouched one, the scorecard's own layout: a
135
- // reader who stops at the first number must not have been told the
139
+ // The sampled and stale counts inside the vouched one, the scorecard's own
140
+ // layout: a reader who stops at the first number must not have been told the
136
141
  // stronger of the two.
137
- ` services ${s.undocumented} undocumented · ${s.draft} draft · ${s.vouched} vouched${s.sampledVouched > 0 ? ` (${s.sampledVouched} sampled)` : ""}`);
142
+ const notes = [
143
+ ...(s.sampledVouched > 0 ? [`${s.sampledVouched} sampled`] : []),
144
+ ...(s.contentStale > 0 ? [`${s.contentStale} stale`] : []),
145
+ ];
146
+ console.log(` services ${s.undocumented} undocumented · ${s.draft} draft · ${s.vouched} vouched${notes.length > 0 ? ` (${notes.join(", ")})` : ""}`);
138
147
  if (r.service !== null)
139
148
  console.log(` narrowed to ${r.service}`);
140
149
  if (r.order.length > 1)
@@ -1 +1 @@
1
- {"version":3,"file":"print.js","sourceRoot":"","sources":["../../../src/commands/status/print.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAUhE,SAAS,GAAG,CAAC,MAAgB;IAC3B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,CAAoB;IAC5C,OAAO,CAAC,KAAK,CACX,CAAC,CAAC,UAAU;QACV,CAAC,CAAC,sLAAsL;QACxL,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,6BAA6B,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,iCAAiC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,0CAA0C,CACnM,CAAC;IACF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED,oFAAoF;AACpF,SAAS,aAAa,CAAC,CAAgB;IACrC,OAAO,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,CAAoB;IAC5C,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,CAAC,CAAC,KAAK,KAAK,YAAY;QAAE,OAAO,YAAY,CAAC;IAClD,OAAO,CACL,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,YAAY;QACjE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,UAAU,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,iDAAiD,CAAC,CAAC,CAAC,EAAE,CAAC;QACzF,eAAe,CAAC,CAAC,QAAQ,IAAI,GAAG,GAAG,CACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,SAAS,CAAC,KAAiB;IAClC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACxB,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,aAAa,CAAC,CAAkC;IACvD,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,uGAAuG,CAAC,CAAC;QACrH,OAAO;IACT,CAAC;IACD,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxE,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,GAAG,CAAC;YACjE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,CAAsB,EAAE,SAAmB;IACtE,IAAI,CAAC,CAAC,WAAW,KAAK,IAAI;QAAE,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;IACpB,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3F,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CACT,mBACE,CAAC,CAAC,MAAM,CAAC,GAAG;QACV,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,eAAe,CAAC,CAAC,MAAM,CAAC,QAAQ,iBAAiB,CAAC,CAAC,MAAM,CAAC,MAAM,eAAe;QACnG,CAAC,CAAC,oBACN,EAAE,CACH,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,mBAAmB,gBAAgB,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACnE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,EAAE,gBAAgB,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7G,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,WAAW,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;QAC3C,0EAA0E;QAC1E,yEAAyE;QACzE,uEAAuE;QACvE,8DAA8D;QAC9D,MAAM,IAAI,GACR,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;YACpB,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACvC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;gBAChC,CAAC,CAAC,6BAA6B;gBAC/B,CAAC,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CACT,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,MAAM,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,OAAO,EAAE,CACpG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,OAAO,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IAED,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC1B,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAoB;IAC7C,IAAI,CAAC,CAAC,WAAW,KAAK,IAAI;QAAE,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IACrB,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,KAAK,iBAAiB,CAAC,CAAC,QAAQ,CAAC,MAAM,uBAAuB,CAAC,CAAC;IAC/F,OAAO,CAAC,GAAG;IACT,0EAA0E;IAC1E,mEAAmE;IACnE,uBAAuB;IACvB,mBAAmB,CAAC,CAAC,YAAY,mBAAmB,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,cAAc,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAC1J,CAAC;IACF,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAE9E,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACnD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,MAAM,GACV,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACpB,CAAC,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACxC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;oBACpB,CAAC,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACnC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAED,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC","sourcesContent":["/**\n * The human view of a status: one feature, or the whole fleet.\n *\n * Separate from `./json.ts` because the two answer different readers and only\n * one of them is a contract. This side may be reworded freely; the payload\n * beside it may not.\n */\nimport { SEVERITY_MARK } from \"../../core/vocabulary/report.js\";\nimport type {\n ArtifactState,\n FeatureStatusReport,\n FleetStatusReport,\n InterruptedCommit,\n NextStep,\n VerificationState,\n} from \"../../core/status/report.js\";\n\nfunction pad(values: string[]): number {\n return Math.max(0, ...values.map((v) => v.length));\n}\n\n/**\n * The half-merged-repo banner, printed FIRST and on stderr.\n *\n * stderr, alone among this command's output, because it is not part of the\n * answer: it is the reason the answer may be wrong. A reader piping `status`\n * into a pager still sees it, and the human view stays the shape every other\n * run has. `--json` carries the same fact in `interrupted`.\n */\nfunction printInterrupted(i: InterruptedCommit): void {\n console.error(\n i.unreadable\n ? \"⚠ this docs repo holds a .loam-commit that cannot be read — a commit was interrupted and nothing here can be trusted until a human compares the living docs against version control.\"\n : `⚠ a \\`${i.recover}\\` was killed mid-commit (${i.host}, pid ${i.pid}, ${i.at}) — ${i.files.length} file(s) may be half-written: ${i.files.join(\", \")}. Everything below is derived from them.`,\n );\n console.error(\"\");\n}\n\n/** `spec (payment-service)` — the artifact id plus the service, when it has one. */\nfunction artifactLabel(a: ArtifactState): string {\n return a.service === null ? a.id : `${a.id} (${a.service})`;\n}\n\n/**\n * The counts come from the record's `claims:` array (verify's `tallyRecord`),\n * never from its `summary:` block. The verdict is printed beside the state\n * because they answer different questions and only together are they honest:\n * `recorded 4/4 confirmed` used to be the whole line for a feature `verify`\n * calls `attested` — complete, and resting on somebody's word about a test.\n */\nfunction verificationLine(v: VerificationState): string {\n if (v.state === \"absent\") return \"none\";\n if (v.state === \"unreadable\") return \"unreadable\";\n return (\n `${v.state} · ${v.verdict} ${v.confirmed}/${v.claims} confirmed` +\n (v.unanswered > 0 ? ` · ${v.unanswered} unanswered` : \"\") +\n (v.attested > 0 ? ` · ${v.attested} scenario(s) on an agent's word, not a test run` : \"\") +\n ` (recorded ${v.recorded ?? \"?\"})`\n );\n}\n\n/**\n * The steps, numbered, with the command on its own indented line — doctor's\n * layout, for doctor's reason: what to do and what to type are two different\n * sentences, and someone scanning a half-built feature reads down the `run:`\n * column.\n */\nfunction printNext(steps: NextStep[]): void {\n console.log(\"\\n next\");\n for (const [i, step] of steps.entries()) {\n console.log(` ${i + 1}. ${step.code}: ${step.statement}`);\n console.log(` run: ${step.command}`);\n }\n}\n\n/**\n * The business flows the services in view already appear in.\n *\n * Silent when the fleet declares none, unlike the delta brief's version of the\n * same section: this report is a table of what is OWED, and a \"(none)\" row for\n * a fleet that has never drawn a use case would be one more line between the\n * reader and the next step. The unreadable arm is not silent, because that one\n * is a hole rather than an absence.\n */\nfunction printUseCases(u: FeatureStatusReport[\"useCases\"]): void {\n if (u.unreadable) {\n console.log(\"\\n use cases architecture/ does not parse, so the flows through these services could not be read\");\n return;\n }\n if (u.flows.length === 0) return;\n console.log(\"\\n use cases\");\n for (const flow of u.flows) {\n console.log(` ${flow.title ?? flow.id} [${flow.id}] ${flow.file}`);\n for (const step of flow.steps) {\n const label = step.title === undefined ? \"\" : ` '${step.title}'`;\n console.log(` step ${step.ordinal}${label}: ${step.source} -> ${step.target}`);\n }\n }\n}\n\nexport function printFeature(r: FeatureStatusReport, ambiguous: string[]): void {\n if (r.interrupted !== null) printInterrupted(r.interrupted);\n const f = r.feature;\n console.log(`loam status ${f.id} — ${f.stage}${f.archived ? \" (archived)\" : \"\"}`);\n console.log(` feature ${f.dirName} ${f.path}`);\n console.log(` services ${f.services.length > 0 ? f.services.join(\", \") : \"(none)\"}`);\n if (r.service !== null) console.log(` narrowed to ${r.service}`);\n if (f.blockedBy.length > 0) console.log(` waiting on ${f.blockedBy.join(\", \")}`);\n console.log(\n ` coherence ${\n r.checks.ran\n ? `${r.checks.errors} error(s) · ${r.checks.warnings} warning(s) · ${r.checks.gating} gate archive`\n : \"not run (archived)\"\n }`,\n );\n console.log(` verification ${verificationLine(r.verification)}`);\n if (ambiguous.length > 0) {\n console.log(` note '${f.id}' also names ${ambiguous.filter((d) => d !== f.dirName).join(\", \")}`);\n }\n\n console.log(\"\\n artifacts\");\n const labels = r.artifacts.map(artifactLabel);\n const labelWidth = pad(labels);\n const statusWidth = pad(r.artifacts.map((a) => a.status));\n for (const [i, a] of r.artifacts.entries()) {\n // `done` on a file that is not there is the one row a reader misreads: it\n // means the feature owes no such artifact, not that one was written. The\n // JSON contract says it with `exists`; the human view has to say it in\n // words, or the table quietly claims files that do not exist.\n const note =\n a.blockedBy.length > 0\n ? ` (needs ${a.blockedBy.join(\", \")})`\n : !a.exists && a.status === \"done\"\n ? \" (not written — none owed)\"\n : \"\";\n console.log(\n ` ${a.status.padEnd(statusWidth)} ${labels[i]!.padEnd(labelWidth)} ${a.path}${note}`.trimEnd(),\n );\n }\n\n if (r.checks.issues.length > 0) {\n console.log(\"\\n findings\");\n for (const issue of r.checks.issues) {\n console.log(` ${SEVERITY_MARK[issue.severity]} ${issue.code}: ${issue.message}`);\n }\n }\n\n printUseCases(r.useCases);\n printNext(r.next);\n}\n\nexport function printFleet(r: FleetStatusReport): void {\n if (r.interrupted !== null) printInterrupted(r.interrupted);\n const s = r.services;\n console.log(`loam status — ${s.total} service(s) · ${r.features.length} feature(s) in flight`);\n console.log(\n // The sampled count inside the vouched one, the scorecard's own layout: a\n // reader who stops at the first number must not have been told the\n // stronger of the two.\n ` services ${s.undocumented} undocumented · ${s.draft} draft · ${s.vouched} vouched${s.sampledVouched > 0 ? ` (${s.sampledVouched} sampled)` : \"\"}`,\n );\n if (r.service !== null) console.log(` narrowed to ${r.service}`);\n if (r.order.length > 1) console.log(` order ${r.order.join(\" → \")}`);\n\n if (r.features.length > 0) {\n console.log(\"\\n features\");\n const ids = pad(r.features.map((f) => f.id));\n const stages = pad(r.features.map((f) => f.stage));\n for (const f of r.features) {\n const detail =\n f.blockedBy.length > 0\n ? `waiting on ${f.blockedBy.join(\", \")}`\n : f.missing.length > 0\n ? `missing ${f.missing.join(\", \")}`\n : f.services.join(\", \");\n console.log(` ${f.stage.padEnd(stages)} ${f.id.padEnd(ids)} ${detail}`.trimEnd());\n }\n }\n\n printNext(r.next);\n}\n"]}
1
+ {"version":3,"file":"print.js","sourceRoot":"","sources":["../../../src/commands/status/print.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The human view of a status: one feature, or the whole fleet.\n *\n * Separate from `./json.ts` because the two answer different readers and only\n * one of them is a contract. This side may be reworded freely; the payload\n * beside it may not.\n */\nimport { answeredSplit } from \"../../core/verify/record.js\";\nimport { SEVERITY_MARK } from \"../../core/vocabulary/report.js\";\nimport type {\n ArtifactState,\n FeatureStatusReport,\n FleetStatusReport,\n InterruptedCommit,\n NextStep,\n VerificationState,\n} from \"../../core/status/report.js\";\n\nfunction pad(values: string[]): number {\n return Math.max(0, ...values.map((v) => v.length));\n}\n\n/**\n * The half-merged-repo banner, printed FIRST and on stderr.\n *\n * stderr, alone among this command's output, because it is not part of the\n * answer: it is the reason the answer may be wrong. A reader piping `status`\n * into a pager still sees it, and the human view stays the shape every other\n * run has. `--json` carries the same fact in `interrupted`.\n */\nfunction printInterrupted(i: InterruptedCommit): void {\n console.error(\n i.unreadable\n ? \"⚠ this docs repo holds a .loam-commit that cannot be read — a commit was interrupted and nothing here can be trusted until a human compares the living docs against version control.\"\n : `⚠ a \\`${i.recover}\\` was killed mid-commit (${i.host}, pid ${i.pid}, ${i.at}) — ${i.files.length} file(s) may be half-written: ${i.files.join(\", \")}. Everything below is derived from them.`,\n );\n console.error(\"\");\n}\n\n/** `spec (payment-service)` — the artifact id plus the service, when it has one. */\nfunction artifactLabel(a: ArtifactState): string {\n return a.service === null ? a.id : `${a.id} (${a.service})`;\n}\n\n/**\n * The counts come from the record's `claims:` array (verify's `tallyRecord`),\n * never from its `summary:` block. The verdict is printed beside the state\n * because they answer different questions and only together are they honest:\n * `recorded 4/4 confirmed` used to be the whole line for a feature `verify`\n * calls `attested` — complete, and resting on somebody's word about a test.\n * Who answered the confirmed ones rides right after the count for the same\n * reason one step further: `verified` turns on the scenario claims alone, and\n * `verified 19/19 confirmed` read as nineteen runs over a record where six\n * answers were an agent's word about a permission or an edge.\n */\nfunction verificationLine(v: VerificationState): string {\n if (v.state === \"absent\") return \"none\";\n if (v.state === \"unreadable\") return \"unreadable\";\n return (\n `${v.state} · ${v.verdict} ${v.confirmed}/${v.claims} confirmed` +\n (v.confirmed > 0 ? ` (${answeredSplit(v.answered)})` : \"\") +\n (v.unanswered > 0 ? ` · ${v.unanswered} unanswered` : \"\") +\n (v.attested > 0 ? ` · ${v.attested} scenario(s) on an agent's word, not a test run` : \"\") +\n ` (recorded ${v.recorded ?? \"?\"})`\n );\n}\n\n/**\n * The steps, numbered, with the command on its own indented line — doctor's\n * layout, for doctor's reason: what to do and what to type are two different\n * sentences, and someone scanning a half-built feature reads down the `run:`\n * column.\n */\nfunction printNext(steps: NextStep[]): void {\n console.log(\"\\n next\");\n for (const [i, step] of steps.entries()) {\n console.log(` ${i + 1}. ${step.code}: ${step.statement}`);\n console.log(` run: ${step.command}`);\n }\n}\n\n/**\n * The business flows the services in view already appear in.\n *\n * Silent when the fleet declares none, unlike the delta brief's version of the\n * same section: this report is a table of what is OWED, and a \"(none)\" row for\n * a fleet that has never drawn a use case would be one more line between the\n * reader and the next step. The unreadable arm is not silent, because that one\n * is a hole rather than an absence.\n */\nfunction printUseCases(u: FeatureStatusReport[\"useCases\"]): void {\n if (u.unreadable) {\n console.log(\"\\n use cases architecture/ does not parse, so the flows through these services could not be read\");\n return;\n }\n if (u.flows.length === 0) return;\n console.log(\"\\n use cases\");\n for (const flow of u.flows) {\n console.log(` ${flow.title ?? flow.id} [${flow.id}] ${flow.file}`);\n for (const step of flow.steps) {\n const label = step.title === undefined ? \"\" : ` '${step.title}'`;\n console.log(` step ${step.ordinal}${label}: ${step.source} -> ${step.target}`);\n }\n }\n}\n\nexport function printFeature(r: FeatureStatusReport, ambiguous: string[]): void {\n if (r.interrupted !== null) printInterrupted(r.interrupted);\n const f = r.feature;\n console.log(`loam status ${f.id} — ${f.stage}${f.archived ? \" (archived)\" : \"\"}`);\n console.log(` feature ${f.dirName} ${f.path}`);\n console.log(` services ${f.services.length > 0 ? f.services.join(\", \") : \"(none)\"}`);\n if (r.service !== null) console.log(` narrowed to ${r.service}`);\n if (f.blockedBy.length > 0) console.log(` waiting on ${f.blockedBy.join(\", \")}`);\n console.log(\n ` coherence ${\n r.checks.ran\n ? `${r.checks.errors} error(s) · ${r.checks.warnings} warning(s) · ${r.checks.gating} gate archive`\n : \"not run (archived)\"\n }`,\n );\n console.log(` verification ${verificationLine(r.verification)}`);\n if (ambiguous.length > 0) {\n console.log(` note '${f.id}' also names ${ambiguous.filter((d) => d !== f.dirName).join(\", \")}`);\n }\n\n console.log(\"\\n artifacts\");\n const labels = r.artifacts.map(artifactLabel);\n const labelWidth = pad(labels);\n const statusWidth = pad(r.artifacts.map((a) => a.status));\n for (const [i, a] of r.artifacts.entries()) {\n // `done` on a file that is not there is the one row a reader misreads: it\n // means the feature owes no such artifact, not that one was written. The\n // JSON contract says it with `exists`; the human view has to say it in\n // words, or the table quietly claims files that do not exist.\n const note =\n a.blockedBy.length > 0\n ? ` (needs ${a.blockedBy.join(\", \")})`\n : !a.exists && a.status === \"done\"\n ? \" (not written — none owed)\"\n : \"\";\n console.log(\n ` ${a.status.padEnd(statusWidth)} ${labels[i]!.padEnd(labelWidth)} ${a.path}${note}`.trimEnd(),\n );\n }\n\n if (r.checks.issues.length > 0) {\n console.log(\"\\n findings\");\n for (const issue of r.checks.issues) {\n console.log(` ${SEVERITY_MARK[issue.severity]} ${issue.code}: ${issue.message}`);\n }\n }\n\n printUseCases(r.useCases);\n printNext(r.next);\n}\n\nexport function printFleet(r: FleetStatusReport): void {\n if (r.interrupted !== null) printInterrupted(r.interrupted);\n const s = r.services;\n console.log(`loam status — ${s.total} service(s) · ${r.features.length} feature(s) in flight`);\n // The sampled and stale counts inside the vouched one, the scorecard's own\n // layout: a reader who stops at the first number must not have been told the\n // stronger of the two.\n const notes = [\n ...(s.sampledVouched > 0 ? [`${s.sampledVouched} sampled`] : []),\n ...(s.contentStale > 0 ? [`${s.contentStale} stale`] : []),\n ];\n console.log(\n ` services ${s.undocumented} undocumented · ${s.draft} draft · ${s.vouched} vouched${notes.length > 0 ? ` (${notes.join(\", \")})` : \"\"}`,\n );\n if (r.service !== null) console.log(` narrowed to ${r.service}`);\n if (r.order.length > 1) console.log(` order ${r.order.join(\" → \")}`);\n\n if (r.features.length > 0) {\n console.log(\"\\n features\");\n const ids = pad(r.features.map((f) => f.id));\n const stages = pad(r.features.map((f) => f.stage));\n for (const f of r.features) {\n const detail =\n f.blockedBy.length > 0\n ? `waiting on ${f.blockedBy.join(\", \")}`\n : f.missing.length > 0\n ? `missing ${f.missing.join(\", \")}`\n : f.services.join(\", \");\n console.log(` ${f.stage.padEnd(stages)} ${f.id.padEnd(ids)} ${detail}`.trimEnd());\n }\n }\n\n printNext(r.next);\n}\n"]}
@@ -1,6 +1,5 @@
1
- import { loadConfig } from "../../core/envelope/config.js";
2
1
  import { FleetContext } from "../../core/fleet-context.js";
3
- import { emitJson, fail, reportNoConfig } from "../../core/envelope/json.js";
2
+ import { configOrRefuse, emitJson, fail } from "../../core/envelope/json.js";
4
3
  import { DocsRepoUnavailableError } from "../../core/repo/state.js";
5
4
  import { featureCandidates, missingFeatureMessage, resolveFeature } from "../../core/repo/repo.js";
6
5
  import { featureStatus } from "../../core/status/feature/feature.js";
@@ -17,12 +16,9 @@ export function registerStatus(program) {
17
16
  .option("--service <id>", "narrow the per-service view to one service")
18
17
  .action(async (featureArg, opts) => {
19
18
  const json = opts.json === true;
20
- const loaded = await loadConfig();
21
- if (loaded.kind !== "loaded") {
22
- reportNoConfig(json, loaded);
19
+ const config = await configOrRefuse(json);
20
+ if (config === null)
23
21
  return;
24
- }
25
- const config = loaded.config;
26
22
  const { docsDir } = config;
27
23
  // A docsDir that is not a docs repo is refused, never rendered as a repo
28
24
  // with nothing in it — the doctrine docs-repo-gate.ts's docsRepoReady exists
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/commands/status/status.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { emitJson, fail, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { featureCandidates, missingFeatureMessage, resolveFeature } from \"../../core/repo/repo.js\";\nimport { featureStatus } from \"../../core/status/feature/feature.js\";\nimport { fleetStatus } from \"../../core/status/fleet/fleet.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"../policy/gate.js\";\nimport { featureJson, fleetJson } from \"./json.js\";\nimport { printFeature, printFleet } from \"./print.js\";\n\n/**\n * `loam status` — the read-only projection. Nothing here computes anything: the\n * derivation lives in core/status/ (and, underneath it, in the same coherence\n * and verification calls `validate`, `verify` and `archive` make). This file\n * resolves the target, refuses the arguments it cannot honour, and renders.\n *\n * It reports and does not gate, which is why it never sets a non-zero exit code\n * on a successful run — see `registerStatus`.\n */\n\ninterface StatusOptions {\n json?: boolean;\n service?: string;\n}\n\nexport function registerStatus(program: Command): void {\n program\n .command(\"status\")\n .argument(\"[feature]\", \"feature id or directory name; omit for the whole repository\")\n .description(\"Report where the work stands and what to do next, derived from the files\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--service <id>\", \"narrow the per-service view to one service\")\n .action(async (featureArg: string | undefined, opts: StatusOptions) => {\n const json = opts.json === true;\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // A docsDir that is not a docs repo is refused, never rendered as a repo\n // with nothing in it — the doctrine docs-repo-gate.ts's docsRepoReady exists\n // for. `status` counts services, so it needs services/ in both modes.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n const context = new FleetContext();\n // What the refusal below says it was answering when it gave up.\n let scope = \"this repository\";\n\n try {\n if (featureArg === undefined) {\n // `bound` is what THIS repo says it is, which is a different fact from\n // `--service` (which narrows the view). Passing it is what lets the\n // fleet form notice that the repository it is standing in has never\n // been adopted — the one thing a fleet count cannot see.\n const report = await fleetStatus(docsDir, {\n service: opts.service,\n bound: config.service,\n context,\n });\n // A `--service` that names nothing is refused rather than answered\n // with an empty fleet: \"no services and no features\" and \"you\n // misspelled it\" are opposite facts, and only one of them is worth\n // acting on.\n if (opts.service !== undefined && report.services.total === 0) {\n fail(json, \"unknown-service\", `No service '${opts.service}' under ${docsDir}/services/.`);\n return;\n }\n if (json) emitJson({ command: \"status\", docsDir, scope: \"fleet\", ...fleetJson(report) });\n else printFleet(report);\n return;\n }\n\n // Archived features are IN scope: an agent asking where FEAT-101 stands\n // after it shipped deserves \"it shipped\", not \"no such feature\" — the\n // same reading `show` takes. An argument naming two directories is\n // still answered (status writes nothing), with the tie broken the way\n // resolveFeature always breaks it.\n const feature = await resolveFeature(docsDir, featureArg, \"include\", context);\n if (!feature) {\n fail(json, \"unknown-target\", await missingFeatureMessage(docsDir, featureArg, context));\n return;\n }\n scope = feature.id;\n // `config.service` is not a lens like `--service`: it says which\n // repository this IS, and three of the steps (`loam gherkin`, `verify\n // --record --service`) refuse anywhere else. Without it status handed a\n // docs-repo reader commands only a service repo can run, and never told\n // a service repo that the work in front of it was its own.\n const report = await featureStatus(docsDir, feature, {\n service: opts.service,\n boundService: config.service,\n context,\n });\n if (opts.service !== undefined && !report.feature.services.includes(opts.service)) {\n fail(\n json,\n \"unknown-service\",\n `${feature.id} carries no delta for '${opts.service}'. It touches: ${\n report.feature.services.length > 0 ? report.feature.services.join(\", \") : \"(no services)\"\n }.`,\n );\n return;\n }\n const ambiguous = await featureCandidates(docsDir, featureArg, \"include\", context);\n if (json) {\n emitJson({ command: \"status\", docsDir, scope: \"feature\", ...featureJson(report) });\n } else {\n printFeature(report, ambiguous.length > 1 ? ambiguous.map((c) => c.dirName) : []);\n }\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // One unreadable artifact must not escape as a stack trace and an\n // `internal` envelope that names nothing: the projection is\n // all-or-nothing (a status missing one artifact reads as a feature that\n // does not owe it), so the honest answer is a refusal naming the path.\n // The errno-vs-path reading this branch depends on — the reason one\n // directory where a file belongs used to kill BOTH forms of status —\n // now lives once, with its reasoning, on the shared helper.\n reportRepositoryUnavailable(\n json,\n err,\n `the status of ${scope} would be missing an artifact`,\n docsDir,\n );\n }\n // No exit code is set on any path above that succeeded, deliberately.\n // `doctor` exits 1 because a broken installation makes every later\n // command lie, and `validate` exits 1 because it IS the fleet gate. This\n // command is a question, and its answer is equally true whether the\n // feature is finished or not — an agent runs it precisely BECAUSE work is\n // outstanding, and exiting 1 there would make every `set -e` script and\n // every CI step read \"there is work to do\" as a failure.\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* JSON */\n/* ------------------------------------------------------------------ */\n"]}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/commands/status/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAiBtD,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,QAAQ,CAAC,WAAW,EAAE,6DAA6D,CAAC;SACpF,WAAW,CAAC,0EAA0E,CAAC;SACvF,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CAAC,gBAAgB,EAAE,4CAA4C,CAAC;SACtE,MAAM,CAAC,KAAK,EAAE,UAA8B,EAAE,IAAmB,EAAE,EAAE;QACpE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAC5B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAC3B,yEAAyE;QACzE,6EAA6E;QAC7E,sEAAsE;QACtE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC;YAAE,OAAO;QACtD,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;QACnC,gEAAgE;QAChE,IAAI,KAAK,GAAG,iBAAiB,CAAC;QAE9B,IAAI,CAAC;YACH,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,uEAAuE;gBACvE,oEAAoE;gBACpE,oEAAoE;gBACpE,yDAAyD;gBACzD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE;oBACxC,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,KAAK,EAAE,MAAM,CAAC,OAAO;oBACrB,OAAO;iBACR,CAAC,CAAC;gBACH,mEAAmE;gBACnE,8DAA8D;gBAC9D,mEAAmE;gBACnE,aAAa;gBACb,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;oBAC9D,IAAI,CAAC,IAAI,EAAE,iBAAiB,EAAE,eAAe,IAAI,CAAC,OAAO,WAAW,OAAO,aAAa,CAAC,CAAC;oBAC1F,OAAO;gBACT,CAAC;gBACD,IAAI,IAAI;oBAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;;oBACpF,UAAU,CAAC,MAAM,CAAC,CAAC;gBACxB,OAAO;YACT,CAAC;YAED,wEAAwE;YACxE,sEAAsE;YACtE,mEAAmE;YACnE,sEAAsE;YACtE,mCAAmC;YACnC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;YAC9E,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;gBACxF,OAAO;YACT,CAAC;YACD,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC;YACnB,iEAAiE;YACjE,sEAAsE;YACtE,wEAAwE;YACxE,wEAAwE;YACxE,2DAA2D;YAC3D,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE;gBACnD,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,YAAY,EAAE,MAAM,CAAC,OAAO;gBAC5B,OAAO;aACR,CAAC,CAAC;YACH,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClF,IAAI,CACF,IAAI,EACJ,iBAAiB,EACjB,GAAG,OAAO,CAAC,EAAE,0BAA0B,IAAI,CAAC,OAAO,kBACjD,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAC5E,GAAG,CACJ,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;YACnF,IAAI,IAAI,EAAE,CAAC;gBACT,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACrF,CAAC;iBAAM,CAAC;gBACN,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,wBAAwB,EAAE,CAAC;gBAC5C,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YACD,kEAAkE;YAClE,4DAA4D;YAC5D,wEAAwE;YACxE,uEAAuE;YACvE,oEAAoE;YACpE,qEAAqE;YACrE,4DAA4D;YAC5D,2BAA2B,CACzB,IAAI,EACJ,GAAG,EACH,iBAAiB,KAAK,+BAA+B,EACrD,OAAO,CACR,CAAC;QACJ,CAAC;QACD,sEAAsE;QACtE,mEAAmE;QACnE,yEAAyE;QACzE,oEAAoE;QACpE,0EAA0E;QAC1E,wEAAwE;QACxE,yDAAyD;IAC3D,CAAC,CAAC,CAAC;AACP,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE","sourcesContent":["import type { Command } from \"commander\";\nimport { FleetContext } from \"../../core/fleet-context.js\";\nimport { configOrRefuse, emitJson, fail } from \"../../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { featureCandidates, missingFeatureMessage, resolveFeature } from \"../../core/repo/repo.js\";\nimport { featureStatus } from \"../../core/status/feature/feature.js\";\nimport { fleetStatus } from \"../../core/status/fleet/fleet.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"../policy/gate.js\";\nimport { featureJson, fleetJson } from \"./json.js\";\nimport { printFeature, printFleet } from \"./print.js\";\n\n/**\n * `loam status` — the read-only projection. Nothing here computes anything: the\n * derivation lives in core/status/ (and, underneath it, in the same coherence\n * and verification calls `validate`, `verify` and `archive` make). This file\n * resolves the target, refuses the arguments it cannot honour, and renders.\n *\n * It reports and does not gate, which is why it never sets a non-zero exit code\n * on a successful run — see `registerStatus`.\n */\n\ninterface StatusOptions {\n json?: boolean;\n service?: string;\n}\n\nexport function registerStatus(program: Command): void {\n program\n .command(\"status\")\n .argument(\"[feature]\", \"feature id or directory name; omit for the whole repository\")\n .description(\"Report where the work stands and what to do next, derived from the files\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--service <id>\", \"narrow the per-service view to one service\")\n .action(async (featureArg: string | undefined, opts: StatusOptions) => {\n const json = opts.json === true;\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // A docsDir that is not a docs repo is refused, never rendered as a repo\n // with nothing in it — the doctrine docs-repo-gate.ts's docsRepoReady exists\n // for. `status` counts services, so it needs services/ in both modes.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n const context = new FleetContext();\n // What the refusal below says it was answering when it gave up.\n let scope = \"this repository\";\n\n try {\n if (featureArg === undefined) {\n // `bound` is what THIS repo says it is, which is a different fact from\n // `--service` (which narrows the view). Passing it is what lets the\n // fleet form notice that the repository it is standing in has never\n // been adopted — the one thing a fleet count cannot see.\n const report = await fleetStatus(docsDir, {\n service: opts.service,\n bound: config.service,\n context,\n });\n // A `--service` that names nothing is refused rather than answered\n // with an empty fleet: \"no services and no features\" and \"you\n // misspelled it\" are opposite facts, and only one of them is worth\n // acting on.\n if (opts.service !== undefined && report.services.total === 0) {\n fail(json, \"unknown-service\", `No service '${opts.service}' under ${docsDir}/services/.`);\n return;\n }\n if (json) emitJson({ command: \"status\", docsDir, scope: \"fleet\", ...fleetJson(report) });\n else printFleet(report);\n return;\n }\n\n // Archived features are IN scope: an agent asking where FEAT-101 stands\n // after it shipped deserves \"it shipped\", not \"no such feature\" — the\n // same reading `show` takes. An argument naming two directories is\n // still answered (status writes nothing), with the tie broken the way\n // resolveFeature always breaks it.\n const feature = await resolveFeature(docsDir, featureArg, \"include\", context);\n if (!feature) {\n fail(json, \"unknown-target\", await missingFeatureMessage(docsDir, featureArg, context));\n return;\n }\n scope = feature.id;\n // `config.service` is not a lens like `--service`: it says which\n // repository this IS, and three of the steps (`loam gherkin`, `verify\n // --record --service`) refuse anywhere else. Without it status handed a\n // docs-repo reader commands only a service repo can run, and never told\n // a service repo that the work in front of it was its own.\n const report = await featureStatus(docsDir, feature, {\n service: opts.service,\n boundService: config.service,\n context,\n });\n if (opts.service !== undefined && !report.feature.services.includes(opts.service)) {\n fail(\n json,\n \"unknown-service\",\n `${feature.id} carries no delta for '${opts.service}'. It touches: ${\n report.feature.services.length > 0 ? report.feature.services.join(\", \") : \"(no services)\"\n }.`,\n );\n return;\n }\n const ambiguous = await featureCandidates(docsDir, featureArg, \"include\", context);\n if (json) {\n emitJson({ command: \"status\", docsDir, scope: \"feature\", ...featureJson(report) });\n } else {\n printFeature(report, ambiguous.length > 1 ? ambiguous.map((c) => c.dirName) : []);\n }\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // One unreadable artifact must not escape as a stack trace and an\n // `internal` envelope that names nothing: the projection is\n // all-or-nothing (a status missing one artifact reads as a feature that\n // does not owe it), so the honest answer is a refusal naming the path.\n // The errno-vs-path reading this branch depends on — the reason one\n // directory where a file belongs used to kill BOTH forms of status —\n // now lives once, with its reasoning, on the shared helper.\n reportRepositoryUnavailable(\n json,\n err,\n `the status of ${scope} would be missing an artifact`,\n docsDir,\n );\n }\n // No exit code is set on any path above that succeeded, deliberately.\n // `doctor` exits 1 because a broken installation makes every later\n // command lie, and `validate` exits 1 because it IS the fleet gate. This\n // command is a question, and its answer is equally true whether the\n // feature is finished or not — an agent runs it precisely BECAUSE work is\n // outstanding, and exiting 1 there would make every `set -e` script and\n // every CI step read \"there is work to do\" as a failure.\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* JSON */\n/* ------------------------------------------------------------------ */\n"]}
@@ -1,9 +1,8 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { relative } from "node:path";
4
- import { loadConfig } from "../../core/envelope/config.js";
5
4
  import { decodeDocument, NotUtf8DocumentError } from "../../core/kernel/document-bytes.js";
6
- import { emitJson, fail, NO_SERVICE_MESSAGE, reportNoConfig } from "../../core/envelope/json.js";
5
+ import { configOrRefuse, emitJson, fail, NO_SERVICE_MESSAGE } from "../../core/envelope/json.js";
7
6
  import { SPEC_AXES } from "../../core/repo/paths.js";
8
7
  import { locateServicePaths, resolveServiceTarget } from "../../core/repo/service-target.js";
9
8
  import { parseRequirements } from "../../core/document/parse.js";
@@ -19,12 +18,9 @@ export function registerSteps(program) {
19
18
  .option("--json", "emit the machine contract instead of the human view")
20
19
  .action(async (opts) => {
21
20
  const json = opts.json === true;
22
- const loaded = await loadConfig();
23
- if (loaded.kind !== "loaded") {
24
- reportNoConfig(json, loaded);
21
+ const config = await configOrRefuse(json);
22
+ if (config === null)
25
23
  return;
26
- }
27
- const config = loaded.config;
28
24
  const named = opts.service ?? config.service;
29
25
  if (named === undefined)
30
26
  return fail(json, "invalid-option", NO_SERVICE_MESSAGE);
@@ -1 +1 @@
1
- {"version":3,"file":"steps.js","sourceRoot":"","sources":["../../../src/commands/steps/steps.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam steps` — the step-phrase inventory of one service's living specs.\n *\n * A READ, and that is the whole design. It emits no finding, sets no exit code\n * beyond a refusal, writes nothing, and needs no service repo to stand in: it\n * parses `spec.md` and `arch.spec.md` from the docs repo exactly as `validate\n * --service` does. `loam gherkin` refuses outside the service's own repository\n * because it WRITES there; this answers a question about documents, so the\n * restriction would buy nothing and cost the analyst who wants the number\n * before anybody has scaffolded a repo.\n *\n * What it is for is in `core/gherkin/steps/inventory.ts`. In one line: the glue\n * layer is where a generated suite dies, and until this existed nobody could\n * tell a suite needing forty step definitions from one needing four hundred.\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport { emitJson, fail, NO_SERVICE_MESSAGE, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { SPEC_AXES } from \"../../core/repo/paths.js\";\nimport { locateServicePaths, resolveServiceTarget } from \"../../core/repo/service-target.js\";\nimport { parseRequirements } from \"../../core/document/parse.js\";\nimport { axisLabel } from \"../../core/gherkin/emit.js\";\nimport {\n coveringPhrases,\n stepInventory,\n type InventoryAxis,\n type StepInventory,\n} from \"../../core/gherkin/steps/inventory.js\";\nimport {\n compareToCatalogue,\n readStepCatalogue,\n type CatalogueComparison,\n type StepCatalogue,\n} from \"../../core/gherkin/steps/catalogue.js\";\n\ninterface StepsOptions {\n service?: string;\n duplicates?: boolean;\n json?: boolean;\n}\n\nexport function registerSteps(program: Command): void {\n program\n .command(\"steps\")\n .description(\"Inventory the step phrases of a service's scenarios — how many step definitions its suite needs\")\n .option(\"--service <id>\", \"service to inventory (defaults to the configured service)\")\n .option(\"--duplicates\", \"list only the near-duplicate groups — phrases that differ by an article or a trailing clause\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: StepsOptions) => {\n const json = opts.json === true;\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const named = opts.service ?? config.service;\n if (named === undefined) return fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n // The enumeration's own id, not the string the caller typed: only one of\n // them carries the fact that a `readdir` produced it, and it is what makes\n // the name pathable. Same resolution `validate --service` performs, so the\n // two commands cannot disagree about which directory a name means.\n const resolved = await resolveServiceTarget(config.docsDir, named, \"--service\");\n if (!resolved.ok) return fail(json, \"invalid-option\", resolved.problem);\n const service = resolved.id;\n\n const paths = await locateServicePaths(config.docsDir, service);\n if (!existsSync(paths.spec)) {\n return fail(\n json,\n \"unknown-target\",\n `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${service}' first.`,\n );\n }\n const axes: InventoryAxis[] = [];\n try {\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n // An absent arch.spec.md is not a finding anywhere else and is not one\n // here: partial adoption is supported, and an empty axis contributes\n // no phrases rather than an empty section nobody asked for.\n const reqs = existsSync(path)\n ? parseRequirements(decodeDocument(await readFile(path), path))\n : [];\n axes.push({ axis: axisLabel(axis), reqs });\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"repository-unavailable\", `Cannot inventory steps: ${err.message}`);\n }\n\n const inv = stepInventory(axes);\n // The catalogue is what makes the report a DIFF rather than a\n // recomputation: without it every run says the same thing about a suite\n // whose glue somebody has already written. Absent is the ordinary state\n // and costs one `existsSync` (`core/gherkin/steps/catalogue.ts`).\n const catalogue = await readStepCatalogue(paths.steps);\n const against = catalogue.present && catalogue.unreadable === undefined\n ? compareToCatalogue(inv, catalogue)\n : null;\n // Computed ONCE, above the branch, and derived from the path\n // `locateServicePaths` resolved rather than joined from the id. A filed\n // service lives wherever the tree walk found it — `services/commerce/\n // checkout/`, not `services/checkout/` — and `core/repo/service-target.ts`\n // says so at length. Two outputs of one command spelling the path two\n // ways is how the human view came to name a directory that does not\n // exist, and here that would be worse than cosmetic: `steps.yaml` is a\n // service artifact now, so an author who created the file where this told\n // them to would mint a second, empty service in the enumeration.\n const where = relative(config.docsDir, paths.steps).split(/[\\\\/]/).join(\"/\");\n if (json) {\n emitJson({\n command: \"steps\",\n docsDir: config.docsDir,\n service,\n steps: inv.steps,\n phrases: inv.phrases.length,\n covering80: coveringPhrases(inv),\n rows: inv.phrases.map((p) => ({\n key: p.key,\n count: p.count,\n keywords: p.keywords,\n uses: p.uses,\n })),\n nearDuplicates: inv.nearDuplicates,\n // Additive, and one key rather than columns spread through `rows`: a\n // consumer that has not adopted the catalogue reads exactly what it\n // read before. `present: false` and `unreadable` are different\n // answers and both are real — \"nobody has catalogued anything\" is not\n // \"loam could not look\".\n catalogue: {\n present: catalogue.present,\n path: where,\n entries: catalogue.entries.length,\n ...(catalogue.unreadable === undefined ? {} : { unreadable: catalogue.unreadable }),\n ...(against === null ? {} : against),\n },\n });\n return;\n }\n print(inv, service, opts.duplicates === true);\n printCatalogue({ catalogue, against, where });\n });\n}\n\n/**\n * The human view leads with the two numbers a team plans from — how many steps\n * are written, and how few definitions cover most of them — because the list\n * underneath is long by construction and the headline is what decides whether\n * anybody reads it.\n */\nfunction print(inv: StepInventory, service: string, duplicatesOnly: boolean): void {\n if (inv.steps === 0) {\n console.log(`steps ${service} — the living specs hold no scenario steps yet.\\n`);\n return;\n }\n const covering = coveringPhrases(inv);\n const share = Math.round((100 * inv.phrases.reduce((a, p, i) => (i < covering ? a + p.count : a), 0)) / inv.steps);\n console.log(`steps ${service} — ${inv.steps} written step(s), ${inv.phrases.length} distinct phrase(s)`);\n console.log(` ${covering} phrase(s) cover ${share}% of them — that is the size of the step-definition registry\\n`);\n\n if (!duplicatesOnly) {\n for (const p of inv.phrases) {\n console.log(` ${String(p.count).padStart(4)} ${p.key}`);\n console.log(` ${p.keywords.join(\"/\")} · ${[...new Set(p.uses.map((u) => u.requirement))].join(\", \")}`);\n }\n console.log();\n }\n\n if (inv.nearDuplicates.length === 0) {\n console.log(\" no near-duplicate phrases.\");\n return;\n }\n console.log(` ${inv.nearDuplicates.length} near-duplicate group(s) — one definition was probably meant:`);\n for (const g of inv.nearDuplicates) {\n console.log(` ${g.family}`);\n for (const k of g.keys) console.log(` · ${k}`);\n }\n}\n\n/** What the catalogue columns print from — the read and, when it was worth making, the comparison. */\ninterface CatalogueView {\n catalogue: StepCatalogue;\n /** Null when there was nothing readable to compare against. */\n against: CatalogueComparison | null;\n /**\n * The catalogue's docs-relative path, RESOLVED — never `services/<id>/…`\n * rejoined from the service name, which is right for an unfiled service and\n * wrong for every filed one. The same string `--json` reports.\n */\n where: string;\n}\n\n/**\n * The catalogue columns, printed after the inventory rather than woven into\n * them.\n *\n * Separate because the two answers have different lifetimes: the inventory is a\n * fact about the specs and is always true, while this is a diff against a\n * decision that may not have been made yet. Weaving a \"not catalogued\" marker\n * through every phrase row of a service with no `steps.yaml` would mark the\n * whole report as work owed, which is the opposite of what an absent catalogue\n * means.\n *\n * Nothing here is a finding and nothing changes the exit code. `loam steps` is a\n * read, and a phrase written before its glue is the normal order of work.\n */\nfunction printCatalogue({ catalogue, against, where }: CatalogueView): void {\n if (!catalogue.present) {\n console.log(`\\n no ${where} — nothing records which phrases this suite has decided to define.`);\n console.log(\" Write one (a `steps:` list of step texts) and this report becomes a diff instead of a recount.\");\n return;\n }\n if (catalogue.unreadable !== undefined) {\n console.log(`\\n ${where} could not be read — ${catalogue.unreadable}.`);\n console.log(\" The catalogue columns are blank: loam did not look, which is not the same as nothing being catalogued.\");\n return;\n }\n if (against === null) return;\n console.log(`\\n ${catalogue.entries.length} phrase(s) catalogued in ${where}`);\n console.log(\n ` ${against.uncatalogued.length} written but not catalogued · ${against.unwritten.length} catalogued but not written`,\n );\n for (const key of against.uncatalogued) console.log(` + ${key}`);\n for (const key of against.unwritten) console.log(` - ${key}`);\n if (against.duplicated.length === 0) return;\n console.log(` ${against.duplicated.length} catalogue entr(ies) collapse onto a phrase already listed:`);\n for (const dup of against.duplicated) {\n console.log(` ${dup.key}`);\n for (const text of dup.texts) console.log(` · ${text}`);\n }\n}\n"]}
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../../../src/commands/steps/steps.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam steps` — the step-phrase inventory of one service's living specs.\n *\n * A READ, and that is the whole design. It emits no finding, sets no exit code\n * beyond a refusal, writes nothing, and needs no service repo to stand in: it\n * parses `spec.md` and `arch.spec.md` from the docs repo exactly as `validate\n * --service` does. `loam gherkin` refuses outside the service's own repository\n * because it WRITES there; this answers a question about documents, so the\n * restriction would buy nothing and cost the analyst who wants the number\n * before anybody has scaffolded a repo.\n *\n * What it is for is in `core/gherkin/steps/inventory.ts`. In one line: the glue\n * layer is where a generated suite dies, and until this existed nobody could\n * tell a suite needing forty step definitions from one needing four hundred.\n */\nimport type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport { decodeDocument, NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport { configOrRefuse, emitJson, fail, NO_SERVICE_MESSAGE } from \"../../core/envelope/json.js\";\nimport { SPEC_AXES } from \"../../core/repo/paths.js\";\nimport { locateServicePaths, resolveServiceTarget } from \"../../core/repo/service-target.js\";\nimport { parseRequirements } from \"../../core/document/parse.js\";\nimport { axisLabel } from \"../../core/gherkin/emit.js\";\nimport {\n coveringPhrases,\n stepInventory,\n type InventoryAxis,\n type StepInventory,\n} from \"../../core/gherkin/steps/inventory.js\";\nimport {\n compareToCatalogue,\n readStepCatalogue,\n type CatalogueComparison,\n type StepCatalogue,\n} from \"../../core/gherkin/steps/catalogue.js\";\n\ninterface StepsOptions {\n service?: string;\n duplicates?: boolean;\n json?: boolean;\n}\n\nexport function registerSteps(program: Command): void {\n program\n .command(\"steps\")\n .description(\"Inventory the step phrases of a service's scenarios — how many step definitions its suite needs\")\n .option(\"--service <id>\", \"service to inventory (defaults to the configured service)\")\n .option(\"--duplicates\", \"list only the near-duplicate groups — phrases that differ by an article or a trailing clause\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: StepsOptions) => {\n const json = opts.json === true;\n const config = await configOrRefuse(json);\n if (config === null) return;\n const named = opts.service ?? config.service;\n if (named === undefined) return fail(json, \"invalid-option\", NO_SERVICE_MESSAGE);\n // The enumeration's own id, not the string the caller typed: only one of\n // them carries the fact that a `readdir` produced it, and it is what makes\n // the name pathable. Same resolution `validate --service` performs, so the\n // two commands cannot disagree about which directory a name means.\n const resolved = await resolveServiceTarget(config.docsDir, named, \"--service\");\n if (!resolved.ok) return fail(json, \"invalid-option\", resolved.problem);\n const service = resolved.id;\n\n const paths = await locateServicePaths(config.docsDir, service);\n if (!existsSync(paths.spec)) {\n return fail(\n json,\n \"unknown-target\",\n `No living spec at ${paths.spec}. Run \\`loam adopt\\` for '${service}' first.`,\n );\n }\n const axes: InventoryAxis[] = [];\n try {\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n // An absent arch.spec.md is not a finding anywhere else and is not one\n // here: partial adoption is supported, and an empty axis contributes\n // no phrases rather than an empty section nobody asked for.\n const reqs = existsSync(path)\n ? parseRequirements(decodeDocument(await readFile(path), path))\n : [];\n axes.push({ axis: axisLabel(axis), reqs });\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n return fail(json, \"repository-unavailable\", `Cannot inventory steps: ${err.message}`);\n }\n\n const inv = stepInventory(axes);\n // The catalogue is what makes the report a DIFF rather than a\n // recomputation: without it every run says the same thing about a suite\n // whose glue somebody has already written. Absent is the ordinary state\n // and costs one `existsSync` (`core/gherkin/steps/catalogue.ts`).\n const catalogue = await readStepCatalogue(paths.steps);\n const against = catalogue.present && catalogue.unreadable === undefined\n ? compareToCatalogue(inv, catalogue)\n : null;\n // Computed ONCE, above the branch, and derived from the path\n // `locateServicePaths` resolved rather than joined from the id. A filed\n // service lives wherever the tree walk found it — `services/commerce/\n // checkout/`, not `services/checkout/` — and `core/repo/service-target.ts`\n // says so at length. Two outputs of one command spelling the path two\n // ways is how the human view came to name a directory that does not\n // exist, and here that would be worse than cosmetic: `steps.yaml` is a\n // service artifact now, so an author who created the file where this told\n // them to would mint a second, empty service in the enumeration.\n const where = relative(config.docsDir, paths.steps).split(/[\\\\/]/).join(\"/\");\n if (json) {\n emitJson({\n command: \"steps\",\n docsDir: config.docsDir,\n service,\n steps: inv.steps,\n phrases: inv.phrases.length,\n covering80: coveringPhrases(inv),\n rows: inv.phrases.map((p) => ({\n key: p.key,\n count: p.count,\n keywords: p.keywords,\n uses: p.uses,\n })),\n nearDuplicates: inv.nearDuplicates,\n // Additive, and one key rather than columns spread through `rows`: a\n // consumer that has not adopted the catalogue reads exactly what it\n // read before. `present: false` and `unreadable` are different\n // answers and both are real — \"nobody has catalogued anything\" is not\n // \"loam could not look\".\n catalogue: {\n present: catalogue.present,\n path: where,\n entries: catalogue.entries.length,\n ...(catalogue.unreadable === undefined ? {} : { unreadable: catalogue.unreadable }),\n ...(against === null ? {} : against),\n },\n });\n return;\n }\n print(inv, service, opts.duplicates === true);\n printCatalogue({ catalogue, against, where });\n });\n}\n\n/**\n * The human view leads with the two numbers a team plans from — how many steps\n * are written, and how few definitions cover most of them — because the list\n * underneath is long by construction and the headline is what decides whether\n * anybody reads it.\n */\nfunction print(inv: StepInventory, service: string, duplicatesOnly: boolean): void {\n if (inv.steps === 0) {\n console.log(`steps ${service} — the living specs hold no scenario steps yet.\\n`);\n return;\n }\n const covering = coveringPhrases(inv);\n const share = Math.round((100 * inv.phrases.reduce((a, p, i) => (i < covering ? a + p.count : a), 0)) / inv.steps);\n console.log(`steps ${service} — ${inv.steps} written step(s), ${inv.phrases.length} distinct phrase(s)`);\n console.log(` ${covering} phrase(s) cover ${share}% of them — that is the size of the step-definition registry\\n`);\n\n if (!duplicatesOnly) {\n for (const p of inv.phrases) {\n console.log(` ${String(p.count).padStart(4)} ${p.key}`);\n console.log(` ${p.keywords.join(\"/\")} · ${[...new Set(p.uses.map((u) => u.requirement))].join(\", \")}`);\n }\n console.log();\n }\n\n if (inv.nearDuplicates.length === 0) {\n console.log(\" no near-duplicate phrases.\");\n return;\n }\n console.log(` ${inv.nearDuplicates.length} near-duplicate group(s) — one definition was probably meant:`);\n for (const g of inv.nearDuplicates) {\n console.log(` ${g.family}`);\n for (const k of g.keys) console.log(` · ${k}`);\n }\n}\n\n/** What the catalogue columns print from — the read and, when it was worth making, the comparison. */\ninterface CatalogueView {\n catalogue: StepCatalogue;\n /** Null when there was nothing readable to compare against. */\n against: CatalogueComparison | null;\n /**\n * The catalogue's docs-relative path, RESOLVED — never `services/<id>/…`\n * rejoined from the service name, which is right for an unfiled service and\n * wrong for every filed one. The same string `--json` reports.\n */\n where: string;\n}\n\n/**\n * The catalogue columns, printed after the inventory rather than woven into\n * them.\n *\n * Separate because the two answers have different lifetimes: the inventory is a\n * fact about the specs and is always true, while this is a diff against a\n * decision that may not have been made yet. Weaving a \"not catalogued\" marker\n * through every phrase row of a service with no `steps.yaml` would mark the\n * whole report as work owed, which is the opposite of what an absent catalogue\n * means.\n *\n * Nothing here is a finding and nothing changes the exit code. `loam steps` is a\n * read, and a phrase written before its glue is the normal order of work.\n */\nfunction printCatalogue({ catalogue, against, where }: CatalogueView): void {\n if (!catalogue.present) {\n console.log(`\\n no ${where} — nothing records which phrases this suite has decided to define.`);\n console.log(\" Write one (a `steps:` list of step texts) and this report becomes a diff instead of a recount.\");\n return;\n }\n if (catalogue.unreadable !== undefined) {\n console.log(`\\n ${where} could not be read — ${catalogue.unreadable}.`);\n console.log(\" The catalogue columns are blank: loam did not look, which is not the same as nothing being catalogued.\");\n return;\n }\n if (against === null) return;\n console.log(`\\n ${catalogue.entries.length} phrase(s) catalogued in ${where}`);\n console.log(\n ` ${against.uncatalogued.length} written but not catalogued · ${against.unwritten.length} catalogued but not written`,\n );\n for (const key of against.uncatalogued) console.log(` + ${key}`);\n for (const key of against.unwritten) console.log(` - ${key}`);\n if (against.duplicated.length === 0) return;\n console.log(` ${against.duplicated.length} catalogue entr(ies) collapse onto a phrase already listed:`);\n for (const dup of against.duplicated) {\n console.log(` ${dup.key}`);\n for (const text of dup.texts) console.log(` · ${text}`);\n }\n}\n"]}
@@ -19,7 +19,7 @@ import { stringify } from "yaml";
19
19
  import { parseSubsystemName } from "../../core/kernel/ids/subsystem.js";
20
20
  import { emitJson, fail, repoPath } from "../../core/envelope/json.js";
21
21
  import { servicesDir, subsystemPathUnder } from "../../core/repo/paths.js";
22
- import { findInTree, nearestTreeNames, servicesUnder, subsystemsUnder } from "../../core/repo/tree/find.js";
22
+ import { closeNamesHint, findInTree, servicesUnder, subsystemsUnder } from "../../core/repo/tree/find.js";
23
23
  import { entryIs } from "../../core/repo/tree/fs.js";
24
24
  import { SUBSYSTEM_MARKER } from "../../core/repo/tree/marker.js";
25
25
  import { planWrite } from "../../core/staging/writes.js";
@@ -47,9 +47,8 @@ export async function runNew(docsDir, name, opts, json) {
47
47
  if (opts.under !== undefined) {
48
48
  const parent = findInTree(tree, opts.under);
49
49
  if (parent === null) {
50
- const close = nearestTreeNames(opts.under, tree.subsystems.map((s) => s.name));
51
50
  fail(json, "unknown-target", `No subsystem '${opts.under}' in the tree.` +
52
- (close.length > 0 ? ` Close names: ${close.join(", ")}.` : " `loam subsystem list` shows what exists."));
51
+ closeNamesHint(opts.under, tree.subsystems.map((s) => s.name), " `loam subsystem list` shows what exists."));
53
52
  return null;
54
53
  }
55
54
  if (parent.kind === "service") {
@@ -98,7 +97,8 @@ export async function runNew(docsDir, name, opts, json) {
98
97
  command: "subsystem",
99
98
  created: name,
100
99
  path: repoPath(docsDir, dir),
101
- views,
100
+ views: views.subsystems,
101
+ serviceViews: views.services,
102
102
  ...(recovered === null ? {} : { recovered }),
103
103
  });
104
104
  return;
@@ -113,9 +113,8 @@ export async function runRm(docsDir, name, json) {
113
113
  await commitWindow(docsDir, json, async (tree) => {
114
114
  const hit = findInTree(tree, name);
115
115
  if (hit === null) {
116
- const close = nearestTreeNames(name, tree.subsystems.map((s) => s.name));
117
116
  fail(json, "unknown-target", `No subsystem '${name}' in the tree.` +
118
- (close.length > 0 ? ` Close names: ${close.join(", ")}.` : " `loam subsystem list` shows what exists."));
117
+ closeNamesHint(name, tree.subsystems.map((s) => s.name), " `loam subsystem list` shows what exists."));
119
118
  return null;
120
119
  }
121
120
  if (hit.kind === "service") {
@@ -175,7 +174,8 @@ export async function runRm(docsDir, name, json) {
175
174
  removed: name,
176
175
  path: repoPath(docsDir, sub.dir),
177
176
  directoryRemoved,
178
- views,
177
+ views: views.subsystems,
178
+ serviceViews: views.services,
179
179
  ...(recovered === null ? {} : { recovered }),
180
180
  });
181
181
  return;