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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (648) hide show
  1. package/CHANGELOG.md +1315 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +83 -7
  8. package/SCHEMA.md +569 -98
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +33 -11
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +21 -25
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/seed.js +6 -9
  118. package/dist/commands/seed/seed.js.map +1 -1
  119. package/dist/commands/show/show.js +3 -7
  120. package/dist/commands/show/show.js.map +1 -1
  121. package/dist/commands/status/print.js +13 -4
  122. package/dist/commands/status/print.js.map +1 -1
  123. package/dist/commands/status/status.js +3 -7
  124. package/dist/commands/status/status.js.map +1 -1
  125. package/dist/commands/steps/steps.js +3 -7
  126. package/dist/commands/steps/steps.js.map +1 -1
  127. package/dist/commands/subsystem/edit.js +3 -5
  128. package/dist/commands/subsystem/edit.js.map +1 -1
  129. package/dist/commands/subsystem/move.js +5 -8
  130. package/dist/commands/subsystem/move.js.map +1 -1
  131. package/dist/commands/subsystem/query.js +2 -4
  132. package/dist/commands/subsystem/query.js.map +1 -1
  133. package/dist/commands/subsystem/subsystem.js +4 -7
  134. package/dist/commands/subsystem/subsystem.js.map +1 -1
  135. package/dist/commands/subsystem/sync.js +14 -41
  136. package/dist/commands/subsystem/sync.js.map +1 -1
  137. package/dist/commands/subsystem/txn/txn.js +17 -38
  138. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  139. package/dist/commands/subsystem/txn/views.js +32 -5
  140. package/dist/commands/subsystem/txn/views.js.map +1 -1
  141. package/dist/commands/tasks/render.js +116 -0
  142. package/dist/commands/tasks/render.js.map +1 -0
  143. package/dist/commands/tasks/tasks.js +113 -0
  144. package/dist/commands/tasks/tasks.js.map +1 -0
  145. package/dist/commands/unarchive/manifest.js +6 -9
  146. package/dist/commands/unarchive/manifest.js.map +1 -1
  147. package/dist/commands/unarchive/restore.js +17 -28
  148. package/dist/commands/unarchive/restore.js.map +1 -1
  149. package/dist/commands/unarchive/unarchive.js +22 -30
  150. package/dist/commands/unarchive/unarchive.js.map +1 -1
  151. package/dist/commands/validate/arch-coverage.js +6 -15
  152. package/dist/commands/validate/arch-coverage.js.map +1 -1
  153. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  154. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  155. package/dist/commands/validate/checks/requirements.js +18 -8
  156. package/dist/commands/validate/checks/requirements.js.map +1 -1
  157. package/dist/commands/validate/checks/sources.js +6 -2
  158. package/dist/commands/validate/checks/sources.js.map +1 -1
  159. package/dist/commands/validate/feature.js +23 -19
  160. package/dist/commands/validate/feature.js.map +1 -1
  161. package/dist/commands/validate/fleet/landscape.js +23 -23
  162. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  163. package/dist/commands/validate/fleet/map/attest.js +6 -10
  164. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  165. package/dist/commands/validate/fleet/map/bindings.js +1 -3
  166. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  167. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  168. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  169. package/dist/commands/validate/fleet/obligations.js +1 -5
  170. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  171. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  172. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  173. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  174. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  175. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  176. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  177. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  178. package/dist/commands/validate/fleet/views/root-documents.js +8 -28
  179. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  180. package/dist/commands/validate/links/corpus.js +1 -4
  181. package/dist/commands/validate/links/corpus.js.map +1 -1
  182. package/dist/commands/validate/links/glossary.js.map +1 -1
  183. package/dist/commands/validate/report.js +5 -4
  184. package/dist/commands/validate/report.js.map +1 -1
  185. package/dist/commands/validate/scope.js +113 -0
  186. package/dist/commands/validate/scope.js.map +1 -0
  187. package/dist/commands/validate/service/api.js +16 -104
  188. package/dist/commands/validate/service/api.js.map +1 -1
  189. package/dist/commands/validate/service/contracts/generated.js +134 -0
  190. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  191. package/dist/commands/validate/service/events/events.js +1 -1
  192. package/dist/commands/validate/service/events/events.js.map +1 -1
  193. package/dist/commands/validate/service/events/message-producers.js +1 -1
  194. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  195. package/dist/commands/validate/service/evidence-pins.js +56 -10
  196. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  197. package/dist/commands/validate/service/service.js +16 -26
  198. package/dist/commands/validate/service/service.js.map +1 -1
  199. package/dist/commands/validate/service/specs.js +31 -38
  200. package/dist/commands/validate/service/specs.js.map +1 -1
  201. package/dist/commands/validate/service/spine.js +8 -11
  202. package/dist/commands/validate/service/spine.js.map +1 -1
  203. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  204. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  205. package/dist/commands/validate/validate.js +44 -26
  206. package/dist/commands/validate/validate.js.map +1 -1
  207. package/dist/commands/verify/cross/diff.js +2 -6
  208. package/dist/commands/verify/cross/diff.js.map +1 -1
  209. package/dist/commands/verify/evidence/contract.js +1 -1
  210. package/dist/commands/verify/evidence/contract.js.map +1 -1
  211. package/dist/commands/verify/evidence/read.js +12 -3
  212. package/dist/commands/verify/evidence/read.js.map +1 -1
  213. package/dist/commands/verify/frozen.js +45 -23
  214. package/dist/commands/verify/frozen.js.map +1 -1
  215. package/dist/commands/verify/record.js +69 -17
  216. package/dist/commands/verify/record.js.map +1 -1
  217. package/dist/commands/verify/report.js +27 -44
  218. package/dist/commands/verify/report.js.map +1 -1
  219. package/dist/commands/verify/results.js +112 -30
  220. package/dist/commands/verify/results.js.map +1 -1
  221. package/dist/commands/verify/verify.js +54 -51
  222. package/dist/commands/verify/verify.js.map +1 -1
  223. package/dist/commands/vouch/pack/pack.js +2 -2
  224. package/dist/commands/vouch/pack/pack.js.map +1 -1
  225. package/dist/commands/vouch/pack/print.js +1 -6
  226. package/dist/commands/vouch/pack/print.js.map +1 -1
  227. package/dist/commands/vouch/pack/sections.js +1 -2
  228. package/dist/commands/vouch/pack/sections.js.map +1 -1
  229. package/dist/commands/vouch/run.js +26 -81
  230. package/dist/commands/vouch/run.js.map +1 -1
  231. package/dist/commands/vouch/sample/plan.js +20 -34
  232. package/dist/commands/vouch/sample/plan.js.map +1 -1
  233. package/dist/commands/vouch/vet/verify.js +39 -7
  234. package/dist/commands/vouch/vet/verify.js.map +1 -1
  235. package/dist/commands/vouch/vouch.js +10 -17
  236. package/dist/commands/vouch/vouch.js.map +1 -1
  237. package/dist/core/agent/agents-md/command-map.js +56 -101
  238. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  239. package/dist/core/agent/agents-md/map/explain.js +4 -3
  240. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  241. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  242. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  243. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  244. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  245. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  246. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  247. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  248. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  249. package/dist/core/agent/agents-md/map/status.js +9 -3
  250. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  251. package/dist/core/agent/agents-md/map/subsystem.js +4 -3
  252. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  253. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  254. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  255. package/dist/core/agent/agents-md/refusals.js +32 -18
  256. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  257. package/dist/core/agent/agents-md.js +9 -3
  258. package/dist/core/agent/agents-md.js.map +1 -1
  259. package/dist/core/agent/agents-stamp.js +10 -0
  260. package/dist/core/agent/agents-stamp.js.map +1 -1
  261. package/dist/core/agent/scaffold.js +10 -15
  262. package/dist/core/agent/scaffold.js.map +1 -1
  263. package/dist/core/agent/tools/dialects.js +1 -0
  264. package/dist/core/agent/tools/dialects.js.map +1 -1
  265. package/dist/core/agent/tools/registry.js +41 -76
  266. package/dist/core/agent/tools/registry.js.map +1 -1
  267. package/dist/core/agent/workflows/adopt.js +5 -3
  268. package/dist/core/agent/workflows/adopt.js.map +1 -1
  269. package/dist/core/agent/workflows/check.js +20 -16
  270. package/dist/core/agent/workflows/check.js.map +1 -1
  271. package/dist/core/agent/workflows/closing.js +54 -21
  272. package/dist/core/agent/workflows/closing.js.map +1 -1
  273. package/dist/core/agent/workflows/feature.js +79 -11
  274. package/dist/core/agent/workflows/feature.js.map +1 -1
  275. package/dist/core/agent/workflows/implement.js +91 -13
  276. package/dist/core/agent/workflows/implement.js.map +1 -1
  277. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  278. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  279. package/dist/core/agent/workflows/reference/codes.js +8 -6
  280. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  281. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  282. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  283. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  284. package/dist/core/agent/workflows/reference/spine.js +50 -18
  285. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  286. package/dist/core/asyncapi/baseline/plan.js +14 -81
  287. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  288. package/dist/core/asyncapi/digest.js +30 -25
  289. package/dist/core/asyncapi/digest.js.map +1 -1
  290. package/dist/core/asyncapi/merge/components.js +5 -4
  291. package/dist/core/asyncapi/merge/components.js.map +1 -1
  292. package/dist/core/asyncapi/merge/error.js +7 -2
  293. package/dist/core/asyncapi/merge/error.js.map +1 -1
  294. package/dist/core/asyncapi/merge/markers.js +30 -50
  295. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  296. package/dist/core/asyncapi/merge/merge.js +31 -53
  297. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  298. package/dist/core/asyncapi/merge/pin.js +10 -36
  299. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  300. package/dist/core/asyncapi/model.js.map +1 -1
  301. package/dist/core/asyncapi/read.js +36 -12
  302. package/dist/core/asyncapi/read.js.map +1 -1
  303. package/dist/core/brief/brief.js +6 -18
  304. package/dist/core/brief/brief.js.map +1 -1
  305. package/dist/core/brief/landscape.js +8 -12
  306. package/dist/core/brief/landscape.js.map +1 -1
  307. package/dist/core/brief/map/extend.js +8 -5
  308. package/dist/core/brief/map/extend.js.map +1 -1
  309. package/dist/core/brief/map/owed.js +8 -26
  310. package/dist/core/brief/map/owed.js.map +1 -1
  311. package/dist/core/brief/targets.js +15 -5
  312. package/dist/core/brief/targets.js.map +1 -1
  313. package/dist/core/c4/likec4.js +147 -16
  314. package/dist/core/c4/likec4.js.map +1 -1
  315. package/dist/core/c4/locate/unlocated.js +150 -0
  316. package/dist/core/c4/locate/unlocated.js.map +1 -0
  317. package/dist/core/c4/parsed/values.js +54 -1
  318. package/dist/core/c4/parsed/values.js.map +1 -1
  319. package/dist/core/c4/prefetch/fleet.js +1 -9
  320. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  321. package/dist/core/c4/project/documents.js +31 -6
  322. package/dist/core/c4/project/documents.js.map +1 -1
  323. package/dist/core/c4/project/load.js +6 -17
  324. package/dist/core/c4/project/load.js.map +1 -1
  325. package/dist/core/c4/resolve/external.js +51 -0
  326. package/dist/core/c4/resolve/external.js.map +1 -0
  327. package/dist/core/c4/resolve/service.js +25 -1
  328. package/dist/core/c4/resolve/service.js.map +1 -1
  329. package/dist/core/c4/seed/template.js +15 -1
  330. package/dist/core/c4/seed/template.js.map +1 -1
  331. package/dist/core/c4/service-model/shape.js +2 -22
  332. package/dist/core/c4/service-model/shape.js.map +1 -1
  333. package/dist/core/c4/source-mask.js +288 -4
  334. package/dist/core/c4/source-mask.js.map +1 -1
  335. package/dist/core/c4/source-scan.js +203 -147
  336. package/dist/core/c4/source-scan.js.map +1 -1
  337. package/dist/core/c4/splice/authored-source.js +19 -0
  338. package/dist/core/c4/splice/authored-source.js.map +1 -1
  339. package/dist/core/c4/splice/delta-blocks.js +5 -18
  340. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  341. package/dist/core/c4/splice/landscape-merge.js +13 -8
  342. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  343. package/dist/core/c4/splice/model/merge.js +10 -6
  344. package/dist/core/c4/splice/model/merge.js.map +1 -1
  345. package/dist/core/c4/splice/placement.js +108 -18
  346. package/dist/core/c4/splice/placement.js.map +1 -1
  347. package/dist/core/c4/workspace.js +13 -22
  348. package/dist/core/c4/workspace.js.map +1 -1
  349. package/dist/core/capabilities/capabilities.js +6 -6
  350. package/dist/core/capabilities/capabilities.js.map +1 -1
  351. package/dist/core/capabilities/delta/doc.js +7 -10
  352. package/dist/core/capabilities/delta/doc.js.map +1 -1
  353. package/dist/core/capabilities/findings.js +15 -20
  354. package/dist/core/capabilities/findings.js.map +1 -1
  355. package/dist/core/capabilities/realizes/findings.js +12 -19
  356. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  357. package/dist/core/capabilities/realizes/join.js +0 -20
  358. package/dist/core/capabilities/realizes/join.js.map +1 -1
  359. package/dist/core/capabilities/rollup.js +6 -0
  360. package/dist/core/capabilities/rollup.js.map +1 -1
  361. package/dist/core/capabilities/tree.js +6 -28
  362. package/dist/core/capabilities/tree.js.map +1 -1
  363. package/dist/core/capabilities/usecase-join.js +3 -13
  364. package/dist/core/capabilities/usecase-join.js.map +1 -1
  365. package/dist/core/coherence/authoring/scaffold.js +11 -3
  366. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  367. package/dist/core/coherence/authoring/sentinels.js +2 -1
  368. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  369. package/dist/core/coherence/coherence.js +30 -16
  370. package/dist/core/coherence/coherence.js.map +1 -1
  371. package/dist/core/coherence/consumed/operations.js +70 -0
  372. package/dist/core/coherence/consumed/operations.js.map +1 -0
  373. package/dist/core/coherence/consumed/refusal.js +38 -0
  374. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  375. package/dist/core/coherence/declared.js +60 -34
  376. package/dist/core/coherence/declared.js.map +1 -1
  377. package/dist/core/coherence/events/declared.js +44 -59
  378. package/dist/core/coherence/events/declared.js.map +1 -1
  379. package/dist/core/coherence/events/events.js +119 -8
  380. package/dist/core/coherence/events/events.js.map +1 -1
  381. package/dist/core/coherence/events/grades.js +31 -8
  382. package/dist/core/coherence/events/grades.js.map +1 -1
  383. package/dist/core/coherence/events/lookups.js +14 -82
  384. package/dist/core/coherence/events/lookups.js.map +1 -1
  385. package/dist/core/coherence/lookups.js +18 -78
  386. package/dist/core/coherence/lookups.js.map +1 -1
  387. package/dist/core/coherence/retired/archived.js +285 -0
  388. package/dist/core/coherence/retired/archived.js.map +1 -0
  389. package/dist/core/coherence/retired/event-pair.js +62 -0
  390. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  391. package/dist/core/coherence/retired/merged.js +79 -0
  392. package/dist/core/coherence/retired/merged.js.map +1 -0
  393. package/dist/core/coherence/retired/retirement.js +49 -0
  394. package/dist/core/coherence/retired/retirement.js.map +1 -0
  395. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  396. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  397. package/dist/core/coherence/retired/revival/revived.js +297 -0
  398. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  399. package/dist/core/consumers/after.js +113 -0
  400. package/dist/core/consumers/after.js.map +1 -0
  401. package/dist/core/consumers/consumers.js +296 -0
  402. package/dist/core/consumers/consumers.js.map +1 -0
  403. package/dist/core/consumers/current.js +211 -0
  404. package/dist/core/consumers/current.js.map +1 -0
  405. package/dist/core/consumers/scan.js +46 -0
  406. package/dist/core/consumers/scan.js.map +1 -0
  407. package/dist/core/consumers/state.js +27 -0
  408. package/dist/core/consumers/state.js.map +1 -0
  409. package/dist/core/delta/delta.js +1 -1
  410. package/dist/core/delta/delta.js.map +1 -1
  411. package/dist/core/deployment/delta.js +3 -6
  412. package/dist/core/deployment/delta.js.map +1 -1
  413. package/dist/core/diff/base-git.js +94 -12
  414. package/dist/core/diff/base-git.js.map +1 -1
  415. package/dist/core/diff/base-state.js +28 -16
  416. package/dist/core/diff/base-state.js.map +1 -1
  417. package/dist/core/diff/joins.js +136 -0
  418. package/dist/core/diff/joins.js.map +1 -0
  419. package/dist/core/diff/scope/changed-paths.js +22 -6
  420. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  421. package/dist/core/diff/scope/edges.js +82 -0
  422. package/dist/core/diff/scope/edges.js.map +1 -0
  423. package/dist/core/diff/semantic.js +100 -31
  424. package/dist/core/diff/semantic.js.map +1 -1
  425. package/dist/core/doctor/agents.js +34 -18
  426. package/dist/core/doctor/agents.js.map +1 -1
  427. package/dist/core/doctor/report.js.map +1 -1
  428. package/dist/core/document/apply.js +10 -2
  429. package/dist/core/document/apply.js.map +1 -1
  430. package/dist/core/document/frontmatter.js +55 -0
  431. package/dist/core/document/frontmatter.js.map +1 -1
  432. package/dist/core/document/parse.js +29 -6
  433. package/dist/core/document/parse.js.map +1 -1
  434. package/dist/core/document/scenarios.js +17 -0
  435. package/dist/core/document/scenarios.js.map +1 -1
  436. package/dist/core/document/spec.js +4 -11
  437. package/dist/core/document/spec.js.map +1 -1
  438. package/dist/core/envelope/config.js +10 -0
  439. package/dist/core/envelope/config.js.map +1 -1
  440. package/dist/core/envelope/json.js +34 -0
  441. package/dist/core/envelope/json.js.map +1 -1
  442. package/dist/core/explain/families.js +17 -12
  443. package/dist/core/explain/families.js.map +1 -1
  444. package/dist/core/explain/refusals.js +3 -3
  445. package/dist/core/explain/refusals.js.map +1 -1
  446. package/dist/core/explain/terms.js +18 -9
  447. package/dist/core/explain/terms.js.map +1 -1
  448. package/dist/core/explore/explore.js +2 -7
  449. package/dist/core/explore/explore.js.map +1 -1
  450. package/dist/core/fleet-context.js +34 -95
  451. package/dist/core/fleet-context.js.map +1 -1
  452. package/dist/core/gate/checks.js +5 -2
  453. package/dist/core/gate/checks.js.map +1 -1
  454. package/dist/core/gate/partners.js +4 -2
  455. package/dist/core/gate/partners.js.map +1 -1
  456. package/dist/core/gate/report.js.map +1 -1
  457. package/dist/core/gate/verification.js +75 -12
  458. package/dist/core/gate/verification.js.map +1 -1
  459. package/dist/core/gherkin/stale.js +13 -37
  460. package/dist/core/gherkin/stale.js.map +1 -1
  461. package/dist/core/glossary/tree.js +6 -19
  462. package/dist/core/glossary/tree.js.map +1 -1
  463. package/dist/core/kernel/concurrency.js +19 -0
  464. package/dist/core/kernel/concurrency.js.map +1 -1
  465. package/dist/core/kernel/records.js +11 -0
  466. package/dist/core/kernel/records.js.map +1 -1
  467. package/dist/core/links/backlinks.js +1 -3
  468. package/dist/core/links/backlinks.js.map +1 -1
  469. package/dist/core/links/corpus.js +5 -36
  470. package/dist/core/links/corpus.js.map +1 -1
  471. package/dist/core/mcp/argv.js +101 -0
  472. package/dist/core/mcp/argv.js.map +1 -1
  473. package/dist/core/mcp/author-tools.js +2 -1
  474. package/dist/core/mcp/author-tools.js.map +1 -1
  475. package/dist/core/mcp/protocol.js +2 -2
  476. package/dist/core/mcp/protocol.js.map +1 -1
  477. package/dist/core/mcp/tools.js +43 -33
  478. package/dist/core/mcp/tools.js.map +1 -1
  479. package/dist/core/openapi/baseline/gate.js +53 -12
  480. package/dist/core/openapi/baseline/gate.js.map +1 -1
  481. package/dist/core/openapi/baseline/plan.js +22 -8
  482. package/dist/core/openapi/baseline/plan.js.map +1 -1
  483. package/dist/core/openapi/baseline/record.js +0 -1
  484. package/dist/core/openapi/baseline/record.js.map +1 -1
  485. package/dist/core/openapi/contract-text.js +33 -0
  486. package/dist/core/openapi/contract-text.js.map +1 -0
  487. package/dist/core/openapi/contract-tree.js +56 -0
  488. package/dist/core/openapi/contract-tree.js.map +1 -0
  489. package/dist/core/openapi/depth.js +38 -24
  490. package/dist/core/openapi/depth.js.map +1 -1
  491. package/dist/core/openapi/digest.js +52 -2
  492. package/dist/core/openapi/digest.js.map +1 -1
  493. package/dist/core/openapi/doc.js +12 -4
  494. package/dist/core/openapi/doc.js.map +1 -1
  495. package/dist/core/openapi/merge/components.js +25 -60
  496. package/dist/core/openapi/merge/components.js.map +1 -1
  497. package/dist/core/openapi/merge/error.js +3 -13
  498. package/dist/core/openapi/merge/error.js.map +1 -1
  499. package/dist/core/openapi/merge/markers.js +26 -30
  500. package/dist/core/openapi/merge/markers.js.map +1 -1
  501. package/dist/core/openapi/merge/merge.js +26 -52
  502. package/dist/core/openapi/merge/merge.js.map +1 -1
  503. package/dist/core/openapi/merge/pin.js +15 -35
  504. package/dist/core/openapi/merge/pin.js.map +1 -1
  505. package/dist/core/openspec/decide/verdict.js.map +1 -1
  506. package/dist/core/openspec/inventory.js +4 -5
  507. package/dist/core/openspec/inventory.js.map +1 -1
  508. package/dist/core/openspec/model/mapping.js +30 -28
  509. package/dist/core/openspec/model/mapping.js.map +1 -1
  510. package/dist/core/openspec/model/model.js +12 -7
  511. package/dist/core/openspec/model/model.js.map +1 -1
  512. package/dist/core/openspec/scan/changes.js +10 -4
  513. package/dist/core/openspec/scan/changes.js.map +1 -1
  514. package/dist/core/openspec/scan/config.js +7 -16
  515. package/dist/core/openspec/scan/config.js.map +1 -1
  516. package/dist/core/openspec/scan/shape.js +63 -19
  517. package/dist/core/openspec/scan/shape.js.map +1 -1
  518. package/dist/core/openspec/scan/walk.js +21 -38
  519. package/dist/core/openspec/scan/walk.js.map +1 -1
  520. package/dist/core/pack/living.js +2 -16
  521. package/dist/core/pack/living.js.map +1 -1
  522. package/dist/core/provenance/findings.js +48 -12
  523. package/dist/core/provenance/findings.js.map +1 -1
  524. package/dist/core/provenance/git.js +33 -57
  525. package/dist/core/provenance/git.js.map +1 -1
  526. package/dist/core/provenance/gitq/moves.js +71 -114
  527. package/dist/core/provenance/gitq/moves.js.map +1 -1
  528. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  529. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  530. package/dist/core/provenance/sources.js +23 -6
  531. package/dist/core/provenance/sources.js.map +1 -1
  532. package/dist/core/provenance/stamp.js +5 -17
  533. package/dist/core/provenance/stamp.js.map +1 -1
  534. package/dist/core/repo/entries.js.map +1 -1
  535. package/dist/core/repo/repo.js +24 -14
  536. package/dist/core/repo/repo.js.map +1 -1
  537. package/dist/core/repo/tree/find.js +12 -3
  538. package/dist/core/repo/tree/find.js.map +1 -1
  539. package/dist/core/repo/tree/fs.js +27 -1
  540. package/dist/core/repo/tree/fs.js.map +1 -1
  541. package/dist/core/repo/tree/walk.js +8 -30
  542. package/dist/core/repo/tree/walk.js.map +1 -1
  543. package/dist/core/results.js +47 -12
  544. package/dist/core/results.js.map +1 -1
  545. package/dist/core/staging/commit.js +10 -12
  546. package/dist/core/staging/commit.js.map +1 -1
  547. package/dist/core/staging/lock.js +74 -12
  548. package/dist/core/staging/lock.js.map +1 -1
  549. package/dist/core/staging/recovery/intent.js +2 -5
  550. package/dist/core/staging/recovery/intent.js.map +1 -1
  551. package/dist/core/staging/recovery/recover.js +60 -1
  552. package/dist/core/staging/recovery/recover.js.map +1 -1
  553. package/dist/core/staging/snapshot/manifest.js +152 -0
  554. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  555. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  556. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  557. package/dist/core/status/actions/execution.js +11 -0
  558. package/dist/core/status/actions/execution.js.map +1 -1
  559. package/dist/core/status/feature/artifacts.js +2 -2
  560. package/dist/core/status/feature/artifacts.js.map +1 -1
  561. package/dist/core/status/feature/feature.js +5 -11
  562. package/dist/core/status/feature/feature.js.map +1 -1
  563. package/dist/core/status/fleet/fleet.js +6 -2
  564. package/dist/core/status/fleet/fleet.js.map +1 -1
  565. package/dist/core/status/fleet/next.js +26 -3
  566. package/dist/core/status/fleet/next.js.map +1 -1
  567. package/dist/core/status/report.js.map +1 -1
  568. package/dist/core/status/verification.js +30 -15
  569. package/dist/core/status/verification.js.map +1 -1
  570. package/dist/core/tasks/packet.js +174 -0
  571. package/dist/core/tasks/packet.js.map +1 -0
  572. package/dist/core/tasks/record.js +43 -0
  573. package/dist/core/tasks/record.js.map +1 -0
  574. package/dist/core/tasks/sources.js +181 -0
  575. package/dist/core/tasks/sources.js.map +1 -0
  576. package/dist/core/tasks/stamps.js +76 -0
  577. package/dist/core/tasks/stamps.js.map +1 -0
  578. package/dist/core/tasks/unit.js +0 -0
  579. package/dist/core/tasks/unit.js.map +1 -0
  580. package/dist/core/usecases/capability.js +11 -14
  581. package/dist/core/usecases/capability.js.map +1 -1
  582. package/dist/core/usecases/delta/flows.js +5 -7
  583. package/dist/core/usecases/delta/flows.js.map +1 -1
  584. package/dist/core/usecases/fleet.js +178 -31
  585. package/dist/core/usecases/fleet.js.map +1 -1
  586. package/dist/core/usecases/operations.js +5 -4
  587. package/dist/core/usecases/operations.js.map +1 -1
  588. package/dist/core/usecases/service/flows.js +9 -6
  589. package/dist/core/usecases/service/flows.js.map +1 -1
  590. package/dist/core/usecases/suites/participants.js +83 -0
  591. package/dist/core/usecases/suites/participants.js.map +1 -0
  592. package/dist/core/usecases/suites/suites.js +45 -0
  593. package/dist/core/usecases/suites/suites.js.map +1 -0
  594. package/dist/core/verify/answers.js +12 -5
  595. package/dist/core/verify/answers.js.map +1 -1
  596. package/dist/core/verify/build.js +102 -82
  597. package/dist/core/verify/build.js.map +1 -1
  598. package/dist/core/verify/checklist.js +3 -0
  599. package/dist/core/verify/checklist.js.map +1 -1
  600. package/dist/core/verify/location.js +67 -0
  601. package/dist/core/verify/location.js.map +1 -0
  602. package/dist/core/verify/pins/lint.js +3 -2
  603. package/dist/core/verify/pins/lint.js.map +1 -1
  604. package/dist/core/verify/pins/pin.js +1 -1
  605. package/dist/core/verify/pins/pin.js.map +1 -1
  606. package/dist/core/verify/read/file.js +224 -0
  607. package/dist/core/verify/read/file.js.map +1 -0
  608. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  609. package/dist/core/verify/read/shape.js.map +1 -0
  610. package/dist/core/verify/read/union.js +105 -0
  611. package/dist/core/verify/read/union.js.map +1 -0
  612. package/dist/core/verify/record.js +35 -12
  613. package/dist/core/verify/record.js.map +1 -1
  614. package/dist/core/verify/store/commit.js +40 -30
  615. package/dist/core/verify/store/commit.js.map +1 -1
  616. package/dist/core/verify/store/render.js +8 -4
  617. package/dist/core/verify/store/render.js.map +1 -1
  618. package/dist/core/vocabulary/issue.js +1 -0
  619. package/dist/core/vocabulary/issue.js.map +1 -1
  620. package/dist/core/vocabulary/maturity.js +12 -2
  621. package/dist/core/vocabulary/maturity.js.map +1 -1
  622. package/examples/README.md +21 -6
  623. package/examples/docs/architecture/landscape.likec4 +8 -0
  624. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  625. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  626. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  627. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  628. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  629. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  630. package/package.json +1 -1
  631. package/dist/commands/validate/checks/vocabulary.js +0 -16
  632. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  633. package/dist/core/agent/agents-md/artifacts.js +0 -221
  634. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  635. package/dist/core/agent/agents-md/cycle.js +0 -180
  636. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  637. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  638. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  639. package/dist/core/agent/agents-md/spine.js +0 -40
  640. package/dist/core/agent/agents-md/spine.js.map +0 -1
  641. package/dist/core/coherence/events/merged.js +0 -40
  642. package/dist/core/coherence/events/merged.js.map +0 -1
  643. package/dist/core/diff/current-state.js +0 -96
  644. package/dist/core/diff/current-state.js.map +0 -1
  645. package/dist/core/diff/victims.js +0 -256
  646. package/dist/core/diff/victims.js.map +0 -1
  647. package/dist/core/staging/snapshot.js.map +0 -1
  648. package/dist/core/verify/file.js.map +0 -1
@@ -0,0 +1,206 @@
1
+ /**
2
+ * `loam new --modifies <Req-ID>`: the small-change lane's one hand step,
3
+ * done by the scaffold.
4
+ *
5
+ * A change that rewrites a requirement the fleet already has used to cost a
6
+ * hand copy and a second command: the scaffold offered only the ADDED example
7
+ * inside its comment, so the author copied the living requirement into a
8
+ * `## MODIFIED Requirements` section by hand — most of a small delta is that
9
+ * copy — and then ran `loam rebase` to pin it. This module does both at
10
+ * scaffold time: it finds each named requirement in the living specs of the
11
+ * services the feature touches and writes it, UNCOMMENTED, under a MODIFIED
12
+ * heading with its `Based-On:` pin already set.
13
+ *
14
+ * THE PIN IS REBASE'S PIN, not a second rule for one. `requirementDigest` of
15
+ * the living requirement, selected by its `Requirement-ID:` exactly as
16
+ * `selectLiving` (commands/rebase/plan.ts) selects one, and written where
17
+ * `serializeRequirements` puts it — under the id line, where `pinEdit` inserts
18
+ * it too. So a scaffold followed by `loam rebase` reports every pin
19
+ * `unchanged`, and a living requirement that moves between this scaffold and
20
+ * the archive is `delta.baseline-stale`, exactly as if the author had copied
21
+ * and pinned it by hand at this moment. The gate is the same gate; only the
22
+ * typing went away.
23
+ *
24
+ * WHY THE BLOCK SHIPS LIVE, when every other template example ships inside a
25
+ * comment. Those examples are placeholder text nobody wrote, and a live one
26
+ * once archived a literal TODO into the living spec — so they declare nothing
27
+ * until a person copies them out. This block is the opposite: it is the
28
+ * fleet's own current text, and the one honest state for it to be in is the
29
+ * state archive would merge. Left unedited it merges as no change to the
30
+ * living wording, which is why the comment above it says to edit in place.
31
+ *
32
+ * ADDRESSED BY `Requirement-ID:` ONLY. A requirement without that line is
33
+ * addressed by its heading everywhere else, but a heading is free text with
34
+ * spaces in it and no grammar a flag value can be checked against; offering it
35
+ * here would teach identity-by-heading on the one path built to be typed.
36
+ */
37
+ import { existsSync } from "node:fs";
38
+ import { join } from "node:path";
39
+ import { repoPath } from "../../core/envelope/json.js";
40
+ import { NotUtf8DocumentError } from "../../core/kernel/document-bytes.js";
41
+ import { parseRequirements, readRequirementsDocument } from "../../core/document/parse.js";
42
+ import { REQUIREMENT_ID_RE, requirementDigest, serializeRequirements, withoutBaseline, } from "../../core/document/spec.js";
43
+ import { compareIds, nearestIds } from "../../core/repo/entries.js";
44
+ import { SPEC_AXES, servicePathsAt } from "../../core/repo/paths.js";
45
+ import { enumeratedServices } from "../../core/repo/service-target.js";
46
+ import { archSpecTemplate, specTemplate } from "./templates.js";
47
+ /** Every identified requirement in one service's living spec files, one entry per (axis, requirement). */
48
+ async function livingRequirements(docsDir, entry) {
49
+ const paths = servicePathsAt(entry.dir);
50
+ const found = [];
51
+ for (const axis of SPEC_AXES) {
52
+ const path = paths[axis.key];
53
+ if (!existsSync(path))
54
+ continue;
55
+ for (const r of parseRequirements(await readRequirementsDocument(path))) {
56
+ if (r.id === undefined)
57
+ continue;
58
+ // The pin is taken over the requirement WITHOUT any baseline it carries —
59
+ // a living document should hold none (archive strips them), and one that
60
+ // does must not end up with two `Based-On:` lines in the scaffold.
61
+ const living = withoutBaseline(r);
62
+ found.push({
63
+ id: r.id,
64
+ service: entry.id,
65
+ axis,
66
+ living: repoPath(docsDir, path),
67
+ requirement: { ...living, basedOn: requirementDigest(living) },
68
+ });
69
+ }
70
+ }
71
+ return found;
72
+ }
73
+ /**
74
+ * Which services OUTSIDE the touched set carry the id — read only on the
75
+ * refusal path, where a fleet-wide walk is paid once by a run that is about to
76
+ * stop anyway. The commonest way to miss is to name the right requirement and
77
+ * the wrong (or no) `--touches`, and the useful refusal names the service.
78
+ * An undecodable document there is skipped: this is a hint, not a grade.
79
+ */
80
+ async function carriersElsewhere(docsDir, id, skip) {
81
+ const carriers = [];
82
+ for (const entry of await enumeratedServices(docsDir)) {
83
+ if (skip.has(entry.id))
84
+ continue;
85
+ try {
86
+ if ((await livingRequirements(docsDir, entry)).some((t) => t.id === id))
87
+ carriers.push(entry.id);
88
+ }
89
+ catch (err) {
90
+ if (!(err instanceof NotUtf8DocumentError))
91
+ throw err;
92
+ }
93
+ }
94
+ return carriers.sort(compareIds);
95
+ }
96
+ function unknownMessage(id, touched, known) {
97
+ const where = touched.length === 0
98
+ ? "this feature touches no service — name the one that owns it with --touches"
99
+ : `it names no requirement in the living specs of ${touched.map((s) => `'${s}'`).join(", ")}`;
100
+ const near = nearestIds(id, [...known]);
101
+ return (`--modifies '${id}': ${where}.` +
102
+ (near.length === 0 ? "" : ` Did you mean ${near.map((n) => `'${n}'`).join(" or ")}?`) +
103
+ " A requirement is addressed by its `Requirement-ID:` line; one without that line cannot be named here.");
104
+ }
105
+ /**
106
+ * Resolve every `--modifies` id against the living specs of the touched
107
+ * services, before anything is written. In flag order, each id once.
108
+ */
109
+ export async function planModifies(docsDir, touched, ids) {
110
+ for (const id of ids) {
111
+ if (!REQUIREMENT_ID_RE.test(id)) {
112
+ return {
113
+ ok: false,
114
+ code: "invalid-option",
115
+ message: `--modifies '${id}' is not a Requirement-ID — it takes the value of a living requirement's \`Requirement-ID:\` line, not its heading.`,
116
+ };
117
+ }
118
+ }
119
+ const scope = new Set(touched);
120
+ let living = [];
121
+ try {
122
+ for (const entry of await enumeratedServices(docsDir)) {
123
+ if (scope.has(entry.id))
124
+ living = living.concat(await livingRequirements(docsDir, entry));
125
+ }
126
+ }
127
+ catch (err) {
128
+ if (!(err instanceof NotUtf8DocumentError))
129
+ throw err;
130
+ // A pin is a claim about text somebody read, and over a document loam could
131
+ // not decode it would be a digest of mojibake — `loam rebase` refuses the
132
+ // same document under the same code, and so does this, before any write.
133
+ return { ok: false, code: "merge-failed", message: `--modifies cannot copy from a living spec loam cannot read — nothing was scaffolded: ${err.message}` };
134
+ }
135
+ const targets = [];
136
+ for (const id of new Set(ids)) {
137
+ const hits = living.filter((t) => t.id === id);
138
+ if (hits.length === 1) {
139
+ targets.push(hits[0]);
140
+ continue;
141
+ }
142
+ if (hits.length > 1) {
143
+ // A Requirement-ID is unique inside ONE document, so two touched
144
+ // documents may both carry it (and a document that repeats one is its
145
+ // own finding, `delta.living-duplicate-requirement`). Picking one would
146
+ // be loam guessing which requirement the author means to rewrite.
147
+ return {
148
+ ok: false,
149
+ code: "invalid-option",
150
+ message: `--modifies '${id}' is a Requirement-ID in more than one living document this feature touches ` +
151
+ `(${[...new Set(hits.map((t) => t.living))].join(", ")}), and an id is unique only inside its own document. ` +
152
+ "Touch only the service whose requirement changes, and write the other service's delta by hand.",
153
+ };
154
+ }
155
+ const elsewhere = await carriersElsewhere(docsDir, id, scope);
156
+ const known = [...new Set(living.map((t) => t.id))];
157
+ return {
158
+ ok: false,
159
+ code: "unknown-target",
160
+ message: elsewhere.length > 0
161
+ ? `--modifies '${id}' is a requirement of ${elsewhere.map((s) => `'${s}'`).join(", ")}, which this feature does not touch — add --touches ${elsewhere[0]}.`
162
+ : unknownMessage(id, touched, known),
163
+ };
164
+ }
165
+ return { ok: true, targets };
166
+ }
167
+ /**
168
+ * The MODIFIED section one delta file carries, appended after the template's
169
+ * own comment. The guidance sits ABOVE the H2 on purpose: below it, before the
170
+ * first `### Requirement:`, it would be section prose of the very section the
171
+ * merge reads, and no comment there is worth that question.
172
+ */
173
+ export function modifiedSection(featureId, targets) {
174
+ return `
175
+ <!-- Each requirement below is the LIVING text, copied by \`loam new\` for an id you
176
+ named and pinned (Based-On:) to the version you are rewriting — the pin
177
+ \`loam rebase ${featureId}\` would write, so there is nothing to rebase unless the
178
+ living text moves first.
179
+ Edit it in place: a MODIFIED carries its full new text, and archive replaces the
180
+ living requirement with exactly what is here — left as copied, it changes nothing.
181
+ Keep the Requirement-ID: and Based-On: lines as they are. -->
182
+
183
+ ## MODIFIED Requirements
184
+
185
+ ${serializeRequirements(targets.map((t) => t.requirement))}`;
186
+ }
187
+ /**
188
+ * The delta files `--modifies` writes or extends, keyed as `new` keys its
189
+ * plan (relative to the feature directory). A business-axis target lands in
190
+ * the `spec.md` the touched service already gets; an architecture-axis target
191
+ * gets the `arch.spec.md` template a touched service is otherwise not handed,
192
+ * because the requirement it rewrites lives there.
193
+ */
194
+ export function modifiedFiles(featureId, targets, planned) {
195
+ const out = {};
196
+ for (const t of targets) {
197
+ const rel = join("specs", t.service, t.axis.file);
198
+ if (rel in out)
199
+ continue;
200
+ const mine = targets.filter((other) => other.service === t.service && other.axis.key === t.axis.key);
201
+ const base = planned[rel] ?? (t.axis.key === "spec" ? specTemplate(featureId, t.service) : archSpecTemplate(featureId, t.service));
202
+ out[rel] = base + modifiedSection(featureId, mine);
203
+ }
204
+ return out;
205
+ }
206
+ //# sourceMappingURL=modifies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modifies.js","sourceRoot":"","sources":["../../../src/commands/new/modifies.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam new --modifies <Req-ID>`: the small-change lane's one hand step,\n * done by the scaffold.\n *\n * A change that rewrites a requirement the fleet already has used to cost a\n * hand copy and a second command: the scaffold offered only the ADDED example\n * inside its comment, so the author copied the living requirement into a\n * `## MODIFIED Requirements` section by hand — most of a small delta is that\n * copy — and then ran `loam rebase` to pin it. This module does both at\n * scaffold time: it finds each named requirement in the living specs of the\n * services the feature touches and writes it, UNCOMMENTED, under a MODIFIED\n * heading with its `Based-On:` pin already set.\n *\n * THE PIN IS REBASE'S PIN, not a second rule for one. `requirementDigest` of\n * the living requirement, selected by its `Requirement-ID:` exactly as\n * `selectLiving` (commands/rebase/plan.ts) selects one, and written where\n * `serializeRequirements` puts it — under the id line, where `pinEdit` inserts\n * it too. So a scaffold followed by `loam rebase` reports every pin\n * `unchanged`, and a living requirement that moves between this scaffold and\n * the archive is `delta.baseline-stale`, exactly as if the author had copied\n * and pinned it by hand at this moment. The gate is the same gate; only the\n * typing went away.\n *\n * WHY THE BLOCK SHIPS LIVE, when every other template example ships inside a\n * comment. Those examples are placeholder text nobody wrote, and a live one\n * once archived a literal TODO into the living spec — so they declare nothing\n * until a person copies them out. This block is the opposite: it is the\n * fleet's own current text, and the one honest state for it to be in is the\n * state archive would merge. Left unedited it merges as no change to the\n * living wording, which is why the comment above it says to edit in place.\n *\n * ADDRESSED BY `Requirement-ID:` ONLY. A requirement without that line is\n * addressed by its heading everywhere else, but a heading is free text with\n * spaces in it and no grammar a flag value can be checked against; offering it\n * here would teach identity-by-heading on the one path built to be typed.\n */\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { repoPath } from \"../../core/envelope/json.js\";\nimport { NotUtf8DocumentError } from \"../../core/kernel/document-bytes.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../core/document/parse.js\";\nimport {\n REQUIREMENT_ID_RE,\n requirementDigest,\n serializeRequirements,\n withoutBaseline,\n type Requirement,\n} from \"../../core/document/spec.js\";\nimport { compareIds, nearestIds, type ServiceEntry } from \"../../core/repo/entries.js\";\nimport { SPEC_AXES, servicePathsAt, type SpecAxis } from \"../../core/repo/paths.js\";\nimport { enumeratedServices } from \"../../core/repo/service-target.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { archSpecTemplate, specTemplate } from \"./templates.js\";\n\n/** One living requirement `--modifies` named, where it lives, and the pin it is scaffolded with. */\nexport interface ModifiedTarget {\n readonly id: string;\n readonly service: string;\n readonly axis: SpecAxis;\n /** The living document it was copied from, repo-relative — what a message names. */\n readonly living: string;\n /** The living requirement, its pin set — the block the delta carries. */\n readonly requirement: Requirement;\n}\n\nexport type ModifiesPlan =\n | { readonly ok: true; readonly targets: readonly ModifiedTarget[] }\n | { readonly ok: false; readonly code: \"invalid-option\" | \"unknown-target\" | \"merge-failed\"; readonly message: string };\n\n/** Every identified requirement in one service's living spec files, one entry per (axis, requirement). */\nasync function livingRequirements(docsDir: DocsDir, entry: ServiceEntry): Promise<ModifiedTarget[]> {\n const paths = servicePathsAt(entry.dir);\n const found: ModifiedTarget[] = [];\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n if (!existsSync(path)) continue;\n for (const r of parseRequirements(await readRequirementsDocument(path))) {\n if (r.id === undefined) continue;\n // The pin is taken over the requirement WITHOUT any baseline it carries —\n // a living document should hold none (archive strips them), and one that\n // does must not end up with two `Based-On:` lines in the scaffold.\n const living = withoutBaseline(r);\n found.push({\n id: r.id,\n service: entry.id,\n axis,\n living: repoPath(docsDir, path),\n requirement: { ...living, basedOn: requirementDigest(living) },\n });\n }\n }\n return found;\n}\n\n/**\n * Which services OUTSIDE the touched set carry the id — read only on the\n * refusal path, where a fleet-wide walk is paid once by a run that is about to\n * stop anyway. The commonest way to miss is to name the right requirement and\n * the wrong (or no) `--touches`, and the useful refusal names the service.\n * An undecodable document there is skipped: this is a hint, not a grade.\n */\nasync function carriersElsewhere(docsDir: DocsDir, id: string, skip: ReadonlySet<string>): Promise<string[]> {\n const carriers: string[] = [];\n for (const entry of await enumeratedServices(docsDir)) {\n if (skip.has(entry.id)) continue;\n try {\n if ((await livingRequirements(docsDir, entry)).some((t) => t.id === id)) carriers.push(entry.id);\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n }\n }\n return carriers.sort(compareIds);\n}\n\nfunction unknownMessage(id: string, touched: readonly string[], known: readonly string[]): string {\n const where = touched.length === 0\n ? \"this feature touches no service — name the one that owns it with --touches\"\n : `it names no requirement in the living specs of ${touched.map((s) => `'${s}'`).join(\", \")}`;\n const near = nearestIds(id, [...known]);\n return (\n `--modifies '${id}': ${where}.` +\n (near.length === 0 ? \"\" : ` Did you mean ${near.map((n) => `'${n}'`).join(\" or \")}?`) +\n \" A requirement is addressed by its `Requirement-ID:` line; one without that line cannot be named here.\"\n );\n}\n\n/**\n * Resolve every `--modifies` id against the living specs of the touched\n * services, before anything is written. In flag order, each id once.\n */\nexport async function planModifies(\n docsDir: DocsDir,\n touched: readonly string[],\n ids: readonly string[],\n): Promise<ModifiesPlan> {\n for (const id of ids) {\n if (!REQUIREMENT_ID_RE.test(id)) {\n return {\n ok: false,\n code: \"invalid-option\",\n message: `--modifies '${id}' is not a Requirement-ID — it takes the value of a living requirement's \\`Requirement-ID:\\` line, not its heading.`,\n };\n }\n }\n const scope = new Set(touched);\n let living: ModifiedTarget[] = [];\n try {\n for (const entry of await enumeratedServices(docsDir)) {\n if (scope.has(entry.id)) living = living.concat(await livingRequirements(docsDir, entry));\n }\n } catch (err) {\n if (!(err instanceof NotUtf8DocumentError)) throw err;\n // A pin is a claim about text somebody read, and over a document loam could\n // not decode it would be a digest of mojibake — `loam rebase` refuses the\n // same document under the same code, and so does this, before any write.\n return { ok: false, code: \"merge-failed\", message: `--modifies cannot copy from a living spec loam cannot read — nothing was scaffolded: ${err.message}` };\n }\n const targets: ModifiedTarget[] = [];\n for (const id of new Set(ids)) {\n const hits = living.filter((t) => t.id === id);\n if (hits.length === 1) {\n targets.push(hits[0]!);\n continue;\n }\n if (hits.length > 1) {\n // A Requirement-ID is unique inside ONE document, so two touched\n // documents may both carry it (and a document that repeats one is its\n // own finding, `delta.living-duplicate-requirement`). Picking one would\n // be loam guessing which requirement the author means to rewrite.\n return {\n ok: false,\n code: \"invalid-option\",\n message:\n `--modifies '${id}' is a Requirement-ID in more than one living document this feature touches ` +\n `(${[...new Set(hits.map((t) => t.living))].join(\", \")}), and an id is unique only inside its own document. ` +\n \"Touch only the service whose requirement changes, and write the other service's delta by hand.\",\n };\n }\n const elsewhere = await carriersElsewhere(docsDir, id, scope);\n const known = [...new Set(living.map((t) => t.id))];\n return {\n ok: false,\n code: \"unknown-target\",\n message: elsewhere.length > 0\n ? `--modifies '${id}' is a requirement of ${elsewhere.map((s) => `'${s}'`).join(\", \")}, which this feature does not touch — add --touches ${elsewhere[0]}.`\n : unknownMessage(id, touched, known),\n };\n }\n return { ok: true, targets };\n}\n\n/**\n * The MODIFIED section one delta file carries, appended after the template's\n * own comment. The guidance sits ABOVE the H2 on purpose: below it, before the\n * first `### Requirement:`, it would be section prose of the very section the\n * merge reads, and no comment there is worth that question.\n */\nexport function modifiedSection(featureId: string, targets: readonly ModifiedTarget[]): string {\n return `\n<!-- Each requirement below is the LIVING text, copied by \\`loam new\\` for an id you\n named and pinned (Based-On:) to the version you are rewriting — the pin\n \\`loam rebase ${featureId}\\` would write, so there is nothing to rebase unless the\n living text moves first.\n Edit it in place: a MODIFIED carries its full new text, and archive replaces the\n living requirement with exactly what is here — left as copied, it changes nothing.\n Keep the Requirement-ID: and Based-On: lines as they are. -->\n\n## MODIFIED Requirements\n\n${serializeRequirements(targets.map((t) => t.requirement))}`;\n}\n\n/**\n * The delta files `--modifies` writes or extends, keyed as `new` keys its\n * plan (relative to the feature directory). A business-axis target lands in\n * the `spec.md` the touched service already gets; an architecture-axis target\n * gets the `arch.spec.md` template a touched service is otherwise not handed,\n * because the requirement it rewrites lives there.\n */\nexport function modifiedFiles(\n featureId: string,\n targets: readonly ModifiedTarget[],\n planned: Readonly<Record<string, string>>,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const t of targets) {\n const rel = join(\"specs\", t.service, t.axis.file);\n if (rel in out) continue;\n const mine = targets.filter((other) => other.service === t.service && other.axis.key === t.axis.key);\n const base = planned[rel] ?? (t.axis.key === \"spec\" ? specTemplate(featureId, t.service) : archSpecTemplate(featureId, t.service));\n out[rel] = base + modifiedSection(featureId, mine);\n }\n return out;\n}\n"]}
@@ -1,20 +1,24 @@
1
+ import { existsSync } from "node:fs";
1
2
  import {} from "../../core/staging/writes.js";
2
3
  import { commitScaffold } from "./commit.js";
3
4
  import { join } from "node:path";
4
- import { loadConfig } from "../../core/envelope/config.js";
5
5
  import { parseFeatureId } from "../../core/kernel/ids/feature.js";
6
6
  import { parseServiceIds } from "../../core/kernel/ids/service.js";
7
- import { emitJson, fail, repoPath, reportNoConfig } from "../../core/envelope/json.js";
7
+ import { configOrRefuse, emitJson, fail, repoPath } from "../../core/envelope/json.js";
8
8
  import { UnsafePathError, resolveInside } from "../../core/kernel/path-safety.js";
9
9
  import { featureIdFromDirName, nearestIds } from "../../core/repo/entries.js";
10
- import { featuresDir } from "../../core/repo/paths.js";
10
+ import { featuresDir, landscapePath } from "../../core/repo/paths.js";
11
+ import { loadArchitecture } from "../../core/c4/project/architecture.js";
12
+ import { drawnServiceElements } from "../../core/c4/resolve/service.js";
11
13
  import { DocsRepoUnavailableError } from "../../core/repo/state.js";
12
14
  import { listServices } from "../../core/repo/repo.js";
13
15
  import { docsRepoReady, reportDocsRepoError } from "../policy/gate.js";
14
16
  import { sayRecovered } from "../policy/format.js";
15
17
  import { capabilityIdProblem } from "../../core/kernel/ids/capability.js";
16
18
  import { capabilityNotes } from "../../core/capabilities/authoring/notes.js";
17
- import { archSpecTemplate, capabilityDeltaTemplate, deltaTemplate, intentTemplate, openapiTemplate, specTemplate, } from "./templates.js";
19
+ import { modifiedFiles, planModifies } from "./modifies.js";
20
+ import { archSpecTemplate, deltaTemplate, intentTemplate, openapiTemplate, specTemplate } from "./templates.js";
21
+ import { capabilityDeltaTemplate } from "./capability-template.js";
18
22
  export function registerNew(program) {
19
23
  program
20
24
  .command("new")
@@ -42,6 +46,9 @@ export function registerNew(program) {
42
46
  // promises is as real as a feature touching two services, and the flag that
43
47
  // is not repeatable is the one that sends an author to mkdir by hand.
44
48
  .option("--capability <id>", "a business capability this feature changes (repeatable)", collect, [])
49
+ // The small-change lane (./modifies.ts): the living requirement copied
50
+ // into an uncommented MODIFIED block, pinned at scaffold time.
51
+ .option("--modifies <Req-ID>", "a living requirement of a --touches service this feature rewrites (repeatable): scaffolds its MODIFIED block, already pinned", collect, [])
45
52
  .option("--json", "emit the machine contract instead of the human view")
46
53
  .action(async (featureId, opts) => {
47
54
  const json = opts.json === true;
@@ -92,12 +99,9 @@ export function registerNew(program) {
92
99
  if (problem !== null)
93
100
  return fail(json, "invalid-option", problem);
94
101
  }
95
- const loaded = await loadConfig();
96
- if (loaded.kind !== "loaded") {
97
- reportNoConfig(json, loaded);
102
+ const config = await configOrRefuse(json);
103
+ if (config === null)
98
104
  return;
99
- }
100
- const config = loaded.config;
101
105
  const { docsDir } = config;
102
106
  // Before a single file is scaffolded: a docsDir that is not a docs repo is
103
107
  // refused, never written into. `features/<id>/**` lands happily in any
@@ -122,13 +126,26 @@ export function registerNew(program) {
122
126
  const created = new Set(newServices.ids);
123
127
  const touched = touches.ids.filter((s) => !created.has(s));
124
128
  const dir = join(featuresDir(docsDir), dirName);
125
- const files = {
126
- "intent.md": intentTemplate(featureId, opts.title),
127
- "delta.likec4": deltaTemplate(featureId, touched, [...created]),
128
- };
129
+ // Resolved before anything is written: an id naming no living
130
+ // requirement refuses the whole scaffold, as a bad --touches id does.
131
+ const modifies = await planModifies(docsDir, touched, opts.modifies);
132
+ if (!modifies.ok)
133
+ return fail(json, modifies.code, modifies.message);
134
+ // `--modifies` and nothing else — every touched service has a requirement
135
+ // rewritten, none is introduced, no promise opened — is a feature that
136
+ // changes no architecture, and the scaffold's own advice for that case is
137
+ // "delete delta.likec4". So it is not written; validate and archive read
138
+ // a requirements-only feature as complete without it.
139
+ const requirementsOnly = modifies.targets.length > 0 && created.size === 0 && capabilities.length === 0 &&
140
+ touched.every((svc) => modifies.targets.some((t) => t.service === svc));
141
+ const files = { "intent.md": intentTemplate(featureId, opts.title) };
142
+ if (!requirementsOnly) {
143
+ files["delta.likec4"] = deltaTemplate(featureId, touched, [...created], await drawnIds(docsDir, touched));
144
+ }
129
145
  for (const svc of [...touched, ...created]) {
130
146
  files[join("specs", svc, "spec.md")] = specTemplate(featureId, svc);
131
147
  }
148
+ Object.assign(files, modifiedFiles(featureId, modifies.targets, files));
132
149
  // The business axis, and the ONE file `--capability` adds. No service
133
150
  // spec rides with it, and that is the inversion rather than an omission:
134
151
  // the whole point of opening the document that changes first is that the
@@ -218,8 +235,17 @@ export function registerNew(program) {
218
235
  console.log(`${featureId} scaffolded at ${repoPath(docsDir, dir)}`);
219
236
  for (const w of written)
220
237
  console.log(` + ${w}`);
238
+ for (const t of modifies.targets)
239
+ console.log(` ~ MODIFIED ${t.id}: copied from ${t.living}, Based-On already pinned`);
221
240
  for (const note of notes)
222
241
  console.log(`\n note: ${note}`);
242
+ if (requirementsOnly) {
243
+ console.log(`\nNext: edit each MODIFIED requirement in place, write intent.md's Why, then\n` +
244
+ `\`loam validate --feature ${featureId}\` and \`loam archive ${featureId}\`. No delta.likec4 was written:\n` +
245
+ "rewriting requirements changes no architecture. If this change also moves an element or\n" +
246
+ "an edge, add one — `loam instructions loam-feature` shows its shape.");
247
+ return;
248
+ }
223
249
  console.log(`\nNext: fill in the delta, then \`loam validate --feature ${featureId}\`.`);
224
250
  console.log("If this feature changes no architecture, delete delta.likec4 — a requirements-only\n" +
225
251
  "feature is complete without it, and an empty `model {}` validates clean too.");
@@ -266,6 +292,26 @@ async function unknownServiceNotes(docsDir, touched) {
266
292
  }
267
293
  return notes;
268
294
  }
295
+ /**
296
+ * The element ids the fleet map already draws the touched services as, so the
297
+ * delta's context lines reuse them instead of guessing — the delta's own
298
+ * comment tells the author to reuse the map's identifiers, and it used to
299
+ * hand them a camel-cased guess that never matched a seeded map's `svc_<id>`.
300
+ *
301
+ * Best effort by design: `new` is a scaffolder, not a validator. No map, or
302
+ * one that does not parse, leaves every id to the seed convention, and
303
+ * `loam validate` reports the map on its own. The project loader and the
304
+ * resolver are the ones `loam adopt` reads, so the two commands cannot name
305
+ * one box two ways.
306
+ */
307
+ async function drawnIds(docsDir, touched) {
308
+ if (touched.length === 0 || !existsSync(landscapePath(docsDir)))
309
+ return new Map();
310
+ const land = await loadArchitecture(docsDir);
311
+ if (land.errors.length > 0)
312
+ return new Map();
313
+ return drawnServiceElements(land.elements, new Set((await listServices(docsDir)).map((s) => s.id)));
314
+ }
269
315
  /* ------------------------------------------------------------------ */
270
316
  /* Naming */
271
317
  /* ------------------------------------------------------------------ */
@@ -279,5 +325,4 @@ function slugify(text) {
279
325
  .replace(/[^a-z0-9]+/g, "-")
280
326
  .replace(/^-+|-+$/g, "");
281
327
  }
282
- /** A LikeC4 identifier for a service name: `payment-split-service` -> `paymentSplitService`. */
283
328
  //# sourceMappingURL=new.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"new.js","sourceRoot":"","sources":["../../../src/commands/new/new.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { commitScaffold } from \"./commit.js\";\nimport { join } from \"node:path\";\nimport { loadConfig } from \"../../core/envelope/config.js\";\nimport { type FeatureId, parseFeatureId } from \"../../core/kernel/ids/feature.js\";\nimport { parseServiceIds } from \"../../core/kernel/ids/service.js\";\nimport { emitJson, fail, repoPath, reportNoConfig } from \"../../core/envelope/json.js\";\nimport { UnsafePathError, resolveInside } from \"../../core/kernel/path-safety.js\";\nimport { featureIdFromDirName, nearestIds } from \"../../core/repo/entries.js\";\nimport { featuresDir } from \"../../core/repo/paths.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { listServices } from \"../../core/repo/repo.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"../policy/gate.js\";\nimport { sayRecovered } from \"../policy/format.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { capabilityIdProblem } from \"../../core/kernel/ids/capability.js\";\nimport { capabilityNotes } from \"../../core/capabilities/authoring/notes.js\";\nimport {\n archSpecTemplate,\n capabilityDeltaTemplate,\n deltaTemplate,\n intentTemplate,\n openapiTemplate,\n specTemplate,\n} from \"./templates.js\";\n\ninterface NewOptions {\n title?: string;\n touches: string[];\n newService: string[];\n capability: string[];\n json?: boolean;\n}\n\nexport function registerNew(program: Command): void {\n program\n .command(\"new\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"Scaffold a feature: intent, C4 delta, a requirement delta per service, a capability delta per promise\")\n .option(\"--title <text>\", \"human title; also becomes the directory slug\")\n // `--touches`, not `--service`: everywhere else `--service` selects the ONE\n // operating target (defaulting to config.service), while this is a repeatable\n // list of services the feature touches — a different arity and a different\n // meaning deserve a different name.\n .option(\"--touches <id>\", \"a service this feature touches (repeatable)\", collect, [])\n .option(\"--new-service <id>\", \"a service this feature introduces (repeatable)\", collect, [])\n // The INVERSION of the two flags above, and it composes with them rather\n // than excluding them. `--touches` asks which services change before the\n // business change has been written; `--capability` opens the document that\n // changes and lets the service work be derived from it. A feature that\n // does both — the analyst writes the promise, and one team already knows it\n // owns part of the mechanism — is an ordinary shape, and refusing the\n // combination would fight this axis's own archive gate: `capability.uncovered`\n // refuses a promise nothing in the same feature keeps, so the fix it names\n // is precisely a `--touches` service's `Realizes:` line living beside the\n // capability delta.\n //\n // Repeatable, like both flags above: a business change that moves two\n // promises is as real as a feature touching two services, and the flag that\n // is not repeatable is the one that sends an author to mkdir by hand.\n .option(\"--capability <id>\", \"a business capability this feature changes (repeatable)\", collect, [])\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureId: string, opts: NewOptions) => {\n const json = opts.json === true;\n\n // The grammar's verdict arrives as the brand: `featureDirName` demands\n // a FeatureId, so an argv string cannot reach the directory derivation\n // without passing through this parse — the producer the brand was\n // missing while `isFeatureId` narrowed to plain string.\n const parsedId = parseFeatureId(featureId, \"feature id\");\n if (!parsedId.ok) {\n return fail(json, \"invalid-option\", parsedId.problem);\n }\n const dirName = featureDirName(parsedId.id, opts.title);\n // The round-trip is checked beside the grammar, not inside it: the\n // grammar is a fact about the id, while `featureIdFromDirName` depends on\n // the TITLE this command is about to slug into the directory name. Only\n // `new` creates that directory, so only `new` owes that second half.\n if (featureIdFromDirName(dirName) !== featureId) {\n return fail(\n json,\n \"invalid-option\",\n `'${featureId}' does not survive the directory round-trip with this title.`,\n );\n }\n\n // Service ids are validated BEFORE the config is even loaded, and long\n // before anything is written: every one of them is interpolated into\n // `specs/<id>/` under the new feature directory, so `--touches ../../x`\n // was a writer pointed outside the docs repo. One grammar (core/kernel/ids/service.ts),\n // the same one adopt/init/vouch refuse on, so a service that is legal to\n // create is legal to name here and nowhere the two disagree.\n // The LIST, not each item: a loop narrows the element and leaves the\n // array unbranded, so what reached the writer below was `string[]` no\n // matter how thoroughly each id had been checked. --touches first, so the\n // id reported first is the one it was before.\n const touches = parseServiceIds(opts.touches, \"--touches\");\n if (!touches.ok) return fail(json, \"invalid-option\", touches.problem);\n const newServices = parseServiceIds(opts.newService, \"--new-service\");\n if (!newServices.ok) return fail(json, \"invalid-option\", newServices.problem);\n\n // Same reasoning as the two lists above, one tree over. A capability id\n // becomes `features/<dirName>/capabilities/<id>/` — a chain of\n // directories, one per `/`-separated segment — so `--capability\n // ../../evil` resolves to `features/evil/spec.md`, a directory\n // `listFeatures` then enumerates as a feature, and one `..` further\n // reaches the docs-repo root. `resolveInside` below cannot refuse either:\n // both are still inside the repo. Checked here for that reason, and\n // against the same directory-name grammar a service id passes\n // (core/kernel/ids/capability.ts).\n const capabilities = opts.capability;\n for (const id of capabilities) {\n const problem = capabilityIdProblem(id);\n if (problem !== null) return fail(json, \"invalid-option\", problem);\n }\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 // Before a single file is scaffolded: a docsDir that is not a docs repo is\n // refused, never written into. `features/<id>/**` lands happily in any\n // directory, so without this gate `new` reported `ok: true` over a\n // scaffold that no enumeration downstream will ever see — and a docsDir\n // that is simply absent escaped as `internal` (the enumeration below\n // throws) carrying the very sentence `list` and `show` report as\n // `docs-missing`. See docs-repo-gate.ts's docsRepoReady. `services`, not\n // `docs`: every id named by --touches/--new-service is a claim about\n // `services/<id>/`, and the near-miss note below is computed against that\n // directory, so a run without it is guessing.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n // No unlocked existence fast-path, deliberately: staging creates the\n // feature directory at plan time, so a HALF-scaffold from a killed run\n // resolves like a finished feature — and an unlocked refusal here made\n // \\`loam new <id>\\` answer already-exists over its own wreckage without\n // ever reaching the recovery that would complete it, while doctor's fix\n // printed exactly that re-run. Existence is asked once, under the lock,\n // after recovery has run.\n\n // A service named both ways is new — that is the more specific claim.\n const created = new Set(newServices.ids);\n const touched = touches.ids.filter((s) => !created.has(s));\n const dir = join(featuresDir(docsDir), dirName);\n\n const files: Record<string, string> = {\n \"intent.md\": intentTemplate(featureId, opts.title),\n \"delta.likec4\": deltaTemplate(featureId, touched, [...created]),\n };\n for (const svc of [...touched, ...created]) {\n files[join(\"specs\", svc, \"spec.md\")] = specTemplate(featureId, svc);\n }\n // The business axis, and the ONE file `--capability` adds. No service\n // spec rides with it, and that is the inversion rather than an omission:\n // the whole point of opening the document that changes first is that the\n // services realizing it are not known until the promise is written.\n // `--touches` is still how a service the author already knows about gets\n // its delta, which is why the two compose.\n //\n // Spelled relative, mirroring `featureCapabilityDeltasDir` +\n // `capabilityDocPathsAt` in core/repo/authored/paths.ts — the path builders take a\n // FeatureDir, whose provenance is an enumeration that read the directory,\n // and this directory is about to be created rather than read. One\n // directory per segment, exactly as the living tree spells nesting.\n for (const cap of capabilities) {\n files[join(\"capabilities\", ...cap.split(\"/\"), \"spec.md\")] = capabilityDeltaTemplate(featureId, cap);\n }\n for (const svc of created) {\n files[join(\"specs\", svc, \"openapi.yaml\")] = openapiTemplate(svc);\n // The architecture axis is scaffolded only for a service this feature\n // INTRODUCES, because that is the case where `c4.uncovered` will fire\n // the moment the delta is written: a brand-new tagged element nothing\n // covers. Handing an author a blank page for the outbox/retries/alerts\n // requirement is how that axis stays empty across a whole fleet.\n files[join(\"specs\", svc, \"arch.spec.md\")] = archSpecTemplate(featureId, svc);\n }\n\n // The COMPLETE plan in memory before a byte lands on disk: every path\n // resolved (belt and braces over the id grammar — resolveInside proves\n // containment at the moment of the write, symlink cases included), and\n // every write an exclusive create, because the feature must not exist.\n // The sequential writeFile loop this replaces could die mid-scaffold and\n // leave a partial feature the next run refused as already-exists.\n const writes: PlannedWrite[] = [];\n for (const [rel, content] of Object.entries(files)) {\n let path: string;\n try {\n path = resolveInside(docsDir, join(\"features\", dirName, rel), \"feature file\");\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(json, \"invalid-option\", err.message);\n }\n writes.push({ path, content, exclusive: true });\n }\n\n const committed = await commitScaffold(docsDir, featureId, writes);\n if (!committed.ok) {\n if (\"repoGone\" in committed) {\n reportDocsRepoError(json, committed.repoGone);\n return;\n }\n if (committed.code === \"already-exists\") {\n // After a recovery the refusal must SAY the repair happened: doctor\n // told the operator to re-run this exact command, and \"already\n // exists\" alone reads as the fix having failed.\n return fail(\n json,\n \"already-exists\",\n `Feature '${featureId}' already exists at ${repoPath(docsDir, committed.existing?.dir ?? dir)}.` +\n (committed.recovered === null ? \"\" : ` (${sayRecovered(committed.recovered)} The scaffold is complete.)`),\n );\n }\n return fail(json, committed.code, committed.message);\n }\n const recovered = committed.recovered;\n const written = writes.map((w) => repoPath(docsDir, w.path));\n\n // Both note families in ONE list, and the services first so an existing\n // `--touches` consumer reads the same first element it always did. They\n // are the same kind of statement — \"the thing you named is not there yet,\n // and here is what that means\" — so splitting them into two payload keys\n // would ask a consumer to learn a second shape for one idea.\n const notes = [\n ...(await unknownServiceNotes(docsDir, touched)),\n ...(await capabilityNotes(docsDir, { dirName, ids: capabilities })),\n ];\n\n if (json) {\n emitJson({\n command: \"new\",\n feature: featureId,\n path: repoPath(docsDir, dir),\n created: written,\n ...(recovered === null ? {} : { recovered }),\n // Not an error and not a finding: `--touches` on a service that does\n // not exist yet is legal (adopt it later, or say `--new-service`),\n // and `--capability` on a promise nobody has named is the ordinary\n // way a new business area starts. Both are reported because the\n // silent alternative is a delta that will never merge into anything,\n // or a near-miss spelling filed as a second capability.\n notes,\n });\n return;\n }\n console.log(`${featureId} scaffolded at ${repoPath(docsDir, dir)}`);\n for (const w of written) console.log(` + ${w}`);\n for (const note of notes) console.log(`\\n note: ${note}`);\n console.log(`\\nNext: fill in the delta, then \\`loam validate --feature ${featureId}\\`.`);\n console.log(\n \"If this feature changes no architecture, delete delta.likec4 — a requirements-only\\n\" +\n \"feature is complete without it, and an empty `model {}` validates clean too.\",\n );\n });\n}\n\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\n/* ------------------------------------------------------------------ */\n/* Hints */\n/* ------------------------------------------------------------------ */\n\n/**\n * `--touches <id>` naming neither a living `services/<id>/` nor a `--new-service`\n * is almost always a typo, and it is silent by construction: the scaffold writes\n * `specs/<id>/spec.md` happily, `loam validate` grades a delta against a living\n * spec that is not there, and the mistake surfaces at archive time as a service\n * appearing from nowhere. A note, not a refusal — naming a service that will be\n * adopted next week is legitimate — but the near-miss is spelled out, because\n * `order-api` vs `orders-api` is exactly the pair a person cannot see.\n */\nasync function unknownServiceNotes(docsDir: DocsDir, touched: string[]): Promise<string[]> {\n if (touched.length === 0) return [];\n let known: string[];\n try {\n known = (await listServices(docsDir)).map((s) => s.id);\n } catch (err) {\n // No docs repo to compare against is a different diagnosis entirely, and\n // `loam doctor` owns it. Saying nothing here beats inventing a typo hint\n // out of an enumeration that never ran.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return [];\n }\n const notes: string[] = [];\n for (const id of touched) {\n if (known.includes(id)) continue;\n const near = nearestIds(id, known);\n notes.push(\n `--touches '${id}' matches no services/${id}/ in the docs repo` +\n (near.length === 0 ? \"\" : ` — did you mean ${near.map((n) => `'${n}'`).join(\" or \")}?`) +\n ` If ${id} is introduced by this feature, pass --new-service ${id} instead.`,\n );\n }\n return notes;\n}\n\n/* ------------------------------------------------------------------ */\n/* Naming */\n/* ------------------------------------------------------------------ */\n\nfunction featureDirName(featureId: FeatureId, title: string | undefined): string {\n const slug = slugify(title ?? \"\");\n return slug ? `${featureId}-${slug}` : featureId;\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/** A LikeC4 identifier for a service name: `payment-split-service` -> `paymentSplitService`. */\n"]}
1
+ {"version":3,"file":"new.js","sourceRoot":"","sources":["../../../src/commands/new/new.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { commitScaffold } from \"./commit.js\";\nimport { join } from \"node:path\";\nimport { type FeatureId, parseFeatureId } from \"../../core/kernel/ids/feature.js\";\nimport { parseServiceIds } from \"../../core/kernel/ids/service.js\";\nimport { configOrRefuse, emitJson, fail, repoPath } from \"../../core/envelope/json.js\";\nimport { UnsafePathError, resolveInside } from \"../../core/kernel/path-safety.js\";\nimport { featureIdFromDirName, nearestIds } from \"../../core/repo/entries.js\";\nimport { featuresDir, landscapePath } from \"../../core/repo/paths.js\";\nimport { loadArchitecture } from \"../../core/c4/project/architecture.js\";\nimport { drawnServiceElements } from \"../../core/c4/resolve/service.js\";\nimport { DocsRepoUnavailableError } from \"../../core/repo/state.js\";\nimport { listServices } from \"../../core/repo/repo.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"../policy/gate.js\";\nimport { sayRecovered } from \"../policy/format.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\nimport { capabilityIdProblem } from \"../../core/kernel/ids/capability.js\";\nimport { capabilityNotes } from \"../../core/capabilities/authoring/notes.js\";\nimport { modifiedFiles, planModifies } from \"./modifies.js\";\nimport { archSpecTemplate, deltaTemplate, intentTemplate, openapiTemplate, specTemplate } from \"./templates.js\";\nimport { capabilityDeltaTemplate } from \"./capability-template.js\";\n\ninterface NewOptions {\n title?: string;\n touches: string[];\n newService: string[];\n capability: string[];\n modifies: string[];\n json?: boolean;\n}\n\nexport function registerNew(program: Command): void {\n program\n .command(\"new\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"Scaffold a feature: intent, C4 delta, a requirement delta per service, a capability delta per promise\")\n .option(\"--title <text>\", \"human title; also becomes the directory slug\")\n // `--touches`, not `--service`: everywhere else `--service` selects the ONE\n // operating target (defaulting to config.service), while this is a repeatable\n // list of services the feature touches — a different arity and a different\n // meaning deserve a different name.\n .option(\"--touches <id>\", \"a service this feature touches (repeatable)\", collect, [])\n .option(\"--new-service <id>\", \"a service this feature introduces (repeatable)\", collect, [])\n // The INVERSION of the two flags above, and it composes with them rather\n // than excluding them. `--touches` asks which services change before the\n // business change has been written; `--capability` opens the document that\n // changes and lets the service work be derived from it. A feature that\n // does both — the analyst writes the promise, and one team already knows it\n // owns part of the mechanism — is an ordinary shape, and refusing the\n // combination would fight this axis's own archive gate: `capability.uncovered`\n // refuses a promise nothing in the same feature keeps, so the fix it names\n // is precisely a `--touches` service's `Realizes:` line living beside the\n // capability delta.\n //\n // Repeatable, like both flags above: a business change that moves two\n // promises is as real as a feature touching two services, and the flag that\n // is not repeatable is the one that sends an author to mkdir by hand.\n .option(\"--capability <id>\", \"a business capability this feature changes (repeatable)\", collect, [])\n // The small-change lane (./modifies.ts): the living requirement copied\n // into an uncommented MODIFIED block, pinned at scaffold time.\n .option(\n \"--modifies <Req-ID>\",\n \"a living requirement of a --touches service this feature rewrites (repeatable): scaffolds its MODIFIED block, already pinned\",\n collect,\n [],\n )\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureId: string, opts: NewOptions) => {\n const json = opts.json === true;\n\n // The grammar's verdict arrives as the brand: `featureDirName` demands\n // a FeatureId, so an argv string cannot reach the directory derivation\n // without passing through this parse — the producer the brand was\n // missing while `isFeatureId` narrowed to plain string.\n const parsedId = parseFeatureId(featureId, \"feature id\");\n if (!parsedId.ok) {\n return fail(json, \"invalid-option\", parsedId.problem);\n }\n const dirName = featureDirName(parsedId.id, opts.title);\n // The round-trip is checked beside the grammar, not inside it: the\n // grammar is a fact about the id, while `featureIdFromDirName` depends on\n // the TITLE this command is about to slug into the directory name. Only\n // `new` creates that directory, so only `new` owes that second half.\n if (featureIdFromDirName(dirName) !== featureId) {\n return fail(\n json,\n \"invalid-option\",\n `'${featureId}' does not survive the directory round-trip with this title.`,\n );\n }\n\n // Service ids are validated BEFORE the config is even loaded, and long\n // before anything is written: every one of them is interpolated into\n // `specs/<id>/` under the new feature directory, so `--touches ../../x`\n // was a writer pointed outside the docs repo. One grammar (core/kernel/ids/service.ts),\n // the same one adopt/init/vouch refuse on, so a service that is legal to\n // create is legal to name here and nowhere the two disagree.\n // The LIST, not each item: a loop narrows the element and leaves the\n // array unbranded, so what reached the writer below was `string[]` no\n // matter how thoroughly each id had been checked. --touches first, so the\n // id reported first is the one it was before.\n const touches = parseServiceIds(opts.touches, \"--touches\");\n if (!touches.ok) return fail(json, \"invalid-option\", touches.problem);\n const newServices = parseServiceIds(opts.newService, \"--new-service\");\n if (!newServices.ok) return fail(json, \"invalid-option\", newServices.problem);\n\n // Same reasoning as the two lists above, one tree over. A capability id\n // becomes `features/<dirName>/capabilities/<id>/` — a chain of\n // directories, one per `/`-separated segment — so `--capability\n // ../../evil` resolves to `features/evil/spec.md`, a directory\n // `listFeatures` then enumerates as a feature, and one `..` further\n // reaches the docs-repo root. `resolveInside` below cannot refuse either:\n // both are still inside the repo. Checked here for that reason, and\n // against the same directory-name grammar a service id passes\n // (core/kernel/ids/capability.ts).\n const capabilities = opts.capability;\n for (const id of capabilities) {\n const problem = capabilityIdProblem(id);\n if (problem !== null) return fail(json, \"invalid-option\", problem);\n }\n\n const config = await configOrRefuse(json);\n if (config === null) return;\n const { docsDir } = config;\n // Before a single file is scaffolded: a docsDir that is not a docs repo is\n // refused, never written into. `features/<id>/**` lands happily in any\n // directory, so without this gate `new` reported `ok: true` over a\n // scaffold that no enumeration downstream will ever see — and a docsDir\n // that is simply absent escaped as `internal` (the enumeration below\n // throws) carrying the very sentence `list` and `show` report as\n // `docs-missing`. See docs-repo-gate.ts's docsRepoReady. `services`, not\n // `docs`: every id named by --touches/--new-service is a claim about\n // `services/<id>/`, and the near-miss note below is computed against that\n // directory, so a run without it is guessing.\n if (!docsRepoReady(json, docsDir, \"services\")) return;\n\n // No unlocked existence fast-path, deliberately: staging creates the\n // feature directory at plan time, so a HALF-scaffold from a killed run\n // resolves like a finished feature — and an unlocked refusal here made\n // \\`loam new <id>\\` answer already-exists over its own wreckage without\n // ever reaching the recovery that would complete it, while doctor's fix\n // printed exactly that re-run. Existence is asked once, under the lock,\n // after recovery has run.\n\n // A service named both ways is new — that is the more specific claim.\n const created = new Set(newServices.ids);\n const touched = touches.ids.filter((s) => !created.has(s));\n const dir = join(featuresDir(docsDir), dirName);\n\n // Resolved before anything is written: an id naming no living\n // requirement refuses the whole scaffold, as a bad --touches id does.\n const modifies = await planModifies(docsDir, touched, opts.modifies);\n if (!modifies.ok) return fail(json, modifies.code, modifies.message);\n // `--modifies` and nothing else — every touched service has a requirement\n // rewritten, none is introduced, no promise opened — is a feature that\n // changes no architecture, and the scaffold's own advice for that case is\n // \"delete delta.likec4\". So it is not written; validate and archive read\n // a requirements-only feature as complete without it.\n const requirementsOnly =\n modifies.targets.length > 0 && created.size === 0 && capabilities.length === 0 &&\n touched.every((svc) => modifies.targets.some((t) => t.service === svc));\n\n const files: Record<string, string> = { \"intent.md\": intentTemplate(featureId, opts.title) };\n if (!requirementsOnly) {\n files[\"delta.likec4\"] = deltaTemplate(featureId, touched, [...created], await drawnIds(docsDir, touched));\n }\n for (const svc of [...touched, ...created]) {\n files[join(\"specs\", svc, \"spec.md\")] = specTemplate(featureId, svc);\n }\n Object.assign(files, modifiedFiles(featureId, modifies.targets, files));\n // The business axis, and the ONE file `--capability` adds. No service\n // spec rides with it, and that is the inversion rather than an omission:\n // the whole point of opening the document that changes first is that the\n // services realizing it are not known until the promise is written.\n // `--touches` is still how a service the author already knows about gets\n // its delta, which is why the two compose.\n //\n // Spelled relative, mirroring `featureCapabilityDeltasDir` +\n // `capabilityDocPathsAt` in core/repo/authored/paths.ts — the path builders take a\n // FeatureDir, whose provenance is an enumeration that read the directory,\n // and this directory is about to be created rather than read. One\n // directory per segment, exactly as the living tree spells nesting.\n for (const cap of capabilities) {\n files[join(\"capabilities\", ...cap.split(\"/\"), \"spec.md\")] = capabilityDeltaTemplate(featureId, cap);\n }\n for (const svc of created) {\n files[join(\"specs\", svc, \"openapi.yaml\")] = openapiTemplate(svc);\n // The architecture axis is scaffolded only for a service this feature\n // INTRODUCES, because that is the case where `c4.uncovered` will fire\n // the moment the delta is written: a brand-new tagged element nothing\n // covers. Handing an author a blank page for the outbox/retries/alerts\n // requirement is how that axis stays empty across a whole fleet.\n files[join(\"specs\", svc, \"arch.spec.md\")] = archSpecTemplate(featureId, svc);\n }\n\n // The COMPLETE plan in memory before a byte lands on disk: every path\n // resolved (belt and braces over the id grammar — resolveInside proves\n // containment at the moment of the write, symlink cases included), and\n // every write an exclusive create, because the feature must not exist.\n // The sequential writeFile loop this replaces could die mid-scaffold and\n // leave a partial feature the next run refused as already-exists.\n const writes: PlannedWrite[] = [];\n for (const [rel, content] of Object.entries(files)) {\n let path: string;\n try {\n path = resolveInside(docsDir, join(\"features\", dirName, rel), \"feature file\");\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n return fail(json, \"invalid-option\", err.message);\n }\n writes.push({ path, content, exclusive: true });\n }\n\n const committed = await commitScaffold(docsDir, featureId, writes);\n if (!committed.ok) {\n if (\"repoGone\" in committed) {\n reportDocsRepoError(json, committed.repoGone);\n return;\n }\n if (committed.code === \"already-exists\") {\n // After a recovery the refusal must SAY the repair happened: doctor\n // told the operator to re-run this exact command, and \"already\n // exists\" alone reads as the fix having failed.\n return fail(\n json,\n \"already-exists\",\n `Feature '${featureId}' already exists at ${repoPath(docsDir, committed.existing?.dir ?? dir)}.` +\n (committed.recovered === null ? \"\" : ` (${sayRecovered(committed.recovered)} The scaffold is complete.)`),\n );\n }\n return fail(json, committed.code, committed.message);\n }\n const recovered = committed.recovered;\n const written = writes.map((w) => repoPath(docsDir, w.path));\n\n // Both note families in ONE list, and the services first so an existing\n // `--touches` consumer reads the same first element it always did. They\n // are the same kind of statement — \"the thing you named is not there yet,\n // and here is what that means\" — so splitting them into two payload keys\n // would ask a consumer to learn a second shape for one idea.\n const notes = [\n ...(await unknownServiceNotes(docsDir, touched)),\n ...(await capabilityNotes(docsDir, { dirName, ids: capabilities })),\n ];\n\n if (json) {\n emitJson({\n command: \"new\",\n feature: featureId,\n path: repoPath(docsDir, dir),\n created: written,\n ...(recovered === null ? {} : { recovered }),\n // Not an error and not a finding: `--touches` on a service that does\n // not exist yet is legal (adopt it later, or say `--new-service`),\n // and `--capability` on a promise nobody has named is the ordinary\n // way a new business area starts. Both are reported because the\n // silent alternative is a delta that will never merge into anything,\n // or a near-miss spelling filed as a second capability.\n notes,\n });\n return;\n }\n console.log(`${featureId} scaffolded at ${repoPath(docsDir, dir)}`);\n for (const w of written) console.log(` + ${w}`);\n for (const t of modifies.targets) console.log(` ~ MODIFIED ${t.id}: copied from ${t.living}, Based-On already pinned`);\n for (const note of notes) console.log(`\\n note: ${note}`);\n if (requirementsOnly) {\n console.log(\n `\\nNext: edit each MODIFIED requirement in place, write intent.md's Why, then\\n` +\n `\\`loam validate --feature ${featureId}\\` and \\`loam archive ${featureId}\\`. No delta.likec4 was written:\\n` +\n \"rewriting requirements changes no architecture. If this change also moves an element or\\n\" +\n \"an edge, add one — `loam instructions loam-feature` shows its shape.\",\n );\n return;\n }\n console.log(`\\nNext: fill in the delta, then \\`loam validate --feature ${featureId}\\`.`);\n console.log(\n \"If this feature changes no architecture, delete delta.likec4 — a requirements-only\\n\" +\n \"feature is complete without it, and an empty `model {}` validates clean too.\",\n );\n });\n}\n\nfunction collect(value: string, previous: string[]): string[] {\n return [...previous, value];\n}\n\n/* ------------------------------------------------------------------ */\n/* Hints */\n/* ------------------------------------------------------------------ */\n\n/**\n * `--touches <id>` naming neither a living `services/<id>/` nor a `--new-service`\n * is almost always a typo, and it is silent by construction: the scaffold writes\n * `specs/<id>/spec.md` happily, `loam validate` grades a delta against a living\n * spec that is not there, and the mistake surfaces at archive time as a service\n * appearing from nowhere. A note, not a refusal — naming a service that will be\n * adopted next week is legitimate — but the near-miss is spelled out, because\n * `order-api` vs `orders-api` is exactly the pair a person cannot see.\n */\nasync function unknownServiceNotes(docsDir: DocsDir, touched: string[]): Promise<string[]> {\n if (touched.length === 0) return [];\n let known: string[];\n try {\n known = (await listServices(docsDir)).map((s) => s.id);\n } catch (err) {\n // No docs repo to compare against is a different diagnosis entirely, and\n // `loam doctor` owns it. Saying nothing here beats inventing a typo hint\n // out of an enumeration that never ran.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return [];\n }\n const notes: string[] = [];\n for (const id of touched) {\n if (known.includes(id)) continue;\n const near = nearestIds(id, known);\n notes.push(\n `--touches '${id}' matches no services/${id}/ in the docs repo` +\n (near.length === 0 ? \"\" : ` — did you mean ${near.map((n) => `'${n}'`).join(\" or \")}?`) +\n ` If ${id} is introduced by this feature, pass --new-service ${id} instead.`,\n );\n }\n return notes;\n}\n\n/**\n * The element ids the fleet map already draws the touched services as, so the\n * delta's context lines reuse them instead of guessing — the delta's own\n * comment tells the author to reuse the map's identifiers, and it used to\n * hand them a camel-cased guess that never matched a seeded map's `svc_<id>`.\n *\n * Best effort by design: `new` is a scaffolder, not a validator. No map, or\n * one that does not parse, leaves every id to the seed convention, and\n * `loam validate` reports the map on its own. The project loader and the\n * resolver are the ones `loam adopt` reads, so the two commands cannot name\n * one box two ways.\n */\nasync function drawnIds(docsDir: DocsDir, touched: string[]): Promise<ReadonlyMap<string, string>> {\n if (touched.length === 0 || !existsSync(landscapePath(docsDir))) return new Map();\n const land = await loadArchitecture(docsDir);\n if (land.errors.length > 0) return new Map();\n return drawnServiceElements(land.elements, new Set((await listServices(docsDir)).map((s) => s.id)));\n}\n\n/* ------------------------------------------------------------------ */\n/* Naming */\n/* ------------------------------------------------------------------ */\n\nfunction featureDirName(featureId: FeatureId, title: string | undefined): string {\n const slug = slugify(title ?? \"\");\n return slug ? `${featureId}-${slug}` : featureId;\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n"]}