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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"inventory.js","sourceRoot":"","sources":["../../../src/core/openspec/inventory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One whole OpenSpec workspace, read and graded in one pass.\n *\n * The order below is the contract: everything is SCANNED before anything is\n * DECIDED, because a decision graded against a half-read corpus is worse than\n * no decision — it reads as reviewed. So this module locates the root, walks\n * it, reads every document through `./scan/`, and only then hands the result to\n * `./decide/`, which is the half a human's mapping can change.\n *\n * The digest is taken over the non-archive artifacts only. It binds a mapping\n * to the truth it was reviewed against, and frozen archive history is neither\n * migrated nor allowed to block readiness — covering it meant a colleague's typo\n * fix under `changes/archive/` killed a completed mapping, with no `--force` and\n * no indication of which file had moved.\n */\nimport { isUtf8 } from \"node:buffer\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { compareIds } from \"../repo/entries.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport { emptyMapping, type OpenSpecMapping } from \"./model/mapping.js\";\nimport { type OpenSpecInventory, type OpenSpecRenamedUsage, type OpenSpecUnsupportedShape } from \"./model/model.js\";\nimport { locateOpenSpecRoot, type Ingest } from \"./scan/workspace.js\";\nimport {\n digestInventory,\n hiddenSubdirs,\n isSymbolicLink,\n portable,\n sourceInventoryPath,\n subdirs,\n walkFiles,\n walkSymlinks,\n} from \"./scan/walk.js\";\nimport { inspectSpecFile, issue } from \"./scan/shape.js\";\nimport { inspectConfig } from \"./scan/config.js\";\nimport { artifactFor, artifactScope, inventoryChanges } from \"./scan/changes.js\";\nimport { capabilityDecisions } from \"./decide/capabilities.js\";\nimport { resolveRenames } from \"./decide/renames.js\";\nimport { artifactDispositions } from \"./decide/dispositions.js\";\nimport { inventoryVerdict } from \"./decide/verdict.js\";\nimport { isDirectory } from \"./scan/walk.js\";\nimport { addCounts } from \"./scan/shape.js\";\nimport { type OpenSpecCapability, type OpenSpecCounts } from \"./model/model.js\";\n\nexport async function inventoryOpenSpec(\n input: string,\n options: { mapping?: OpenSpecMapping } = {},\n): Promise<OpenSpecInventory> {\n const located = await locateOpenSpecRoot(input);\n const { inputRoot, root } = located;\n const mapping = emptyMapping(options.mapping);\n const renamed: OpenSpecRenamedUsage[] = [];\n const blockers: OpenSpecUnsupportedShape[] = [];\n // The read in progress. Every reader below appends to these two lists rather\n // than returning findings, because the ORDER they land in is what the `--json`\n // contract pins — five arrays merged afterwards would be a different report.\n const ingest: Ingest = { root, mapping, renamed, unsupported: blockers };\n const archiveDiagnostics: OpenSpecUnsupportedShape[] = [];\n for (const absolute of await walkSymlinks(root)) {\n const path = portable(root, absolute);\n const scope = artifactScope(path);\n issue(scope === \"archive\" ? archiveDiagnostics : blockers, scope, {\n code: \"openspec.symlink-unsupported\",\n path: path,\n message: \"Symbolic links are not followed by migration inventory; replace this link with an in-tree regular file or directory.\",\n });\n }\n const config = await inspectConfig(root, blockers);\n const specsRoot = join(root, \"specs\");\n const changesRoot = join(root, \"changes\");\n const inputStoreMetadata = join(inputRoot, \".openspec-store\", \"store.yaml\");\n const parentStoreMetadata = basename(root) === \"openspec\"\n ? join(dirname(root), \".openspec-store\", \"store.yaml\")\n : inputStoreMetadata;\n const storeMetadata = existsSync(inputStoreMetadata) ? inputStoreMetadata : parentStoreMetadata;\n const storeMetadataExists = existsSync(storeMetadata);\n const storeMetadataSymlink = storeMetadataExists && await isSymbolicLink(storeMetadata);\n if (storeMetadataSymlink) {\n issue(blockers, \"workspace\", {\n code: \"openspec.symlink-unsupported\",\n path: sourceInventoryPath(root, storeMetadata),\n message: \"Symbolic Store metadata is not followed; replace it with an in-tree regular file.\",\n });\n }\n const hasModernEmptyShape = config !== null || (storeMetadataExists && !storeMetadataSymlink);\n if (!await isDirectory(specsRoot) && !hasModernEmptyShape) {\n issue(blockers, \"living\", {\n code: \"openspec.specs-missing\",\n path: \"specs\",\n message: \"Living specs/ directory is missing.\",\n });\n }\n if (config !== null && config.store !== null && !await isDirectory(specsRoot) && !await isDirectory(changesRoot)) {\n issue(blockers, \"workspace\", {\n code: \"openspec.external-store-pointer\",\n path: \"config.yaml\",\n message: `Planning is externalized to Store '${config.store}'; audit that registered Store checkout directly.`,\n });\n }\n\n const capabilities: OpenSpecCapability[] = [];\n const livingRequirements = new Map<string, ReturnType<typeof parseRequirements>>();\n const livingCounts: OpenSpecCounts = { specFiles: 0, requirements: 0, scenarios: 0 };\n const livingTree = await walkFiles(specsRoot);\n const livingFiles = livingTree.filter((path) => basename(path) === \"spec.md\");\n // The living twin of openspec.nonstandard-change-spec. Selecting living specs\n // by exact basename is right, but without the mirror a mis-cased or renamed\n // file simply disappears: specs/payments/Spec.md audited as a clean, empty\n // corpus while the identical shape under a change was reported.\n for (const file of livingTree.filter((path) =>\n /\\.md$/i.test(path) && basename(path) !== \"spec.md\" && basename(path) !== \"design.md\")) {\n blockers.push({\n code: \"openspec.nonstandard-living-spec\",\n path: portable(root, file),\n message: \"Markdown under specs/ is named neither spec.md nor design.md, so no capability reads it.\",\n scope: \"living\",\n });\n }\n for (const specFile of livingFiles) {\n const id = portable(specsRoot, dirname(specFile));\n const inspected = await inspectSpecFile(ingest, specFile, \"living\", null);\n livingRequirements.set(id, inspected.requirements);\n const capability = { id, files: [inspected.path], ...inspected.counts };\n capabilities.push(capability);\n addCounts(livingCounts, inspected.counts);\n }\n capabilities.sort((a, b) => compareIds(a.id, b.id));\n\n const activeIds = (await subdirs(changesRoot)).filter((id) => id !== \"archive\");\n const archiveRoot = join(changesRoot, \"archive\");\n const archivedIds = await subdirs(archiveRoot);\n for (const id of await hiddenSubdirs(changesRoot)) {\n blockers.push({\n code: \"openspec.hidden-change-directory\",\n path: portable(root, join(changesRoot, id)),\n message: \"A dot-prefixed change directory is not enumerated as a change, so nothing under it is migrated; rename it or move it out of changes/.\",\n scope: \"active\",\n });\n }\n for (const id of await hiddenSubdirs(archiveRoot)) {\n archiveDiagnostics.push({\n code: \"openspec.hidden-change-directory\",\n path: portable(root, join(archiveRoot, id)),\n message: \"A dot-prefixed archive directory is not enumerated as a change; frozen history is retained read-only where it is.\",\n scope: \"archive\",\n });\n }\n const fallbackSchema = config?.schema ?? \"spec-driven\";\n const active = await inventoryChanges(ingest, {\n base: changesRoot,\n ids: activeIds,\n scope: \"active\",\n fallbackSchema,\n findings: blockers,\n });\n const archived = await inventoryChanges(ingest, {\n base: archiveRoot,\n ids: archivedIds,\n scope: \"archive\",\n fallbackSchema,\n findings: archiveDiagnostics,\n });\n\n // Last-resort backstop under the whole \"audited past the corpus\" family, and\n // the one check that does not depend on root selection being right: an\n // inventory that read no living spec AND no active change describes nothing,\n // so no verdict over it can be `ready`. That is exactly the shape the Store\n // misdetection produced — ready, mechanically compatible, zero capabilities,\n // zero unsupported shapes — where the failure verdict was more confident than\n // any success verdict. A greenfield workspace whose only requirements live\n // under changes/ has an active change and is not caught here.\n if (livingCounts.specFiles === 0 && active.length === 0) {\n blockers.push({\n code: \"openspec.workspace-empty\",\n path: \".\",\n message: `Planning root ${root} holds no living spec and no active change; there is nothing to migrate, so confirm this is the directory that really holds the corpus.`,\n scope: \"workspace\",\n });\n }\n\n const artifactFiles = await walkFiles(root);\n if (storeMetadataExists && !storeMetadataSymlink && !artifactFiles.includes(storeMetadata)) {\n artifactFiles.push(storeMetadata);\n }\n for (const absolute of artifactFiles) {\n const bytes = await readFile(absolute);\n if (isUtf8(bytes)) continue;\n const rootPath = portable(root, absolute);\n const artifactPath = sourceInventoryPath(root, absolute);\n const scope = absolute === storeMetadata ? \"workspace\" : artifactScope(rootPath);\n issue(scope === \"archive\" ? archiveDiagnostics : blockers, scope, {\n code: \"openspec.non-utf8-artifact\",\n path: artifactPath,\n message: \"Migration can preserve only valid UTF-8 authored artifacts; convert this file to UTF-8 before apply.\",\n });\n }\n // The digest binds a mapping to the truth it was reviewed against, and frozen\n // archive history is neither migrated nor allowed to block readiness. Covering\n // it meant a colleague's typo fix under changes/archive/ killed a completed\n // mapping, with no --force and no indication of which file moved.\n const inventoryDigest = await digestInventory(\n root,\n artifactFiles.filter((absolute) => artifactScope(portable(root, absolute)) !== \"archive\"),\n );\n const artifacts = artifactFiles\n .map((path) => artifactFor(root, path))\n .sort((a, b) => compareIds(a.path, b.path));\n\n const { mappingIssues, mappingDecisions, changeDecisions, routing } = capabilityDecisions({\n root,\n mapping,\n // An audit grades no source binding: there is no mapping to bind yet.\n bound: options.mapping !== undefined,\n inventoryDigest,\n livingRequirements,\n active,\n renamed,\n });\n resolveRenames(\n { root, mapping, bound: options.mapping !== undefined, inventoryDigest, livingRequirements, active, renamed },\n routing,\n mappingIssues,\n );\n const artifactDecisions = artifactDispositions(\n artifacts,\n mapping,\n new Set(active.map((change) => change.id)),\n mappingIssues,\n );\n\n return inventoryVerdict({\n located,\n mapping,\n inventoryDigest,\n config,\n storeMetadataPath: storeMetadataExists ? sourceInventoryPath(root, storeMetadata) : null,\n livingCounts,\n capabilities,\n active,\n archived,\n renamed,\n blockers,\n archiveDiagnostics,\n artifacts,\n mappingDecisions,\n changeDecisions,\n mappingIssues,\n artifactDecisions,\n });\n}\n"]}
1
+ {"version":3,"file":"inventory.js","sourceRoot":"","sources":["../../../src/core/openspec/inventory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One whole OpenSpec workspace, read and graded in one pass.\n *\n * The order below is the contract: everything is SCANNED before anything is\n * DECIDED, because a decision graded against a half-read corpus is worse than\n * no decision — it reads as reviewed. So this module locates the root, walks\n * it, reads every document through `./scan/`, and only then hands the result to\n * `./decide/`, which is the half a human's mapping can change.\n *\n * The digest is taken over the non-archive artifacts only. It binds a mapping\n * to the truth it was reviewed against, and frozen archive history is neither\n * migrated nor allowed to block readiness — covering it meant a colleague's typo\n * fix under `changes/archive/` killed a completed mapping, with no `--force` and\n * no indication of which file had moved.\n */\nimport { isUtf8 } from \"node:buffer\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { basename, dirname, join } from \"node:path\";\nimport { compareIds } from \"../repo/entries.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport { emptyMapping, type OpenSpecMapping } from \"./model/mapping.js\";\nimport { type OpenSpecInventory, type OpenSpecRenamedUsage, type OpenSpecUnsupportedShape } from \"./model/model.js\";\nimport { locateOpenSpecRoot, type Ingest } from \"./scan/workspace.js\";\nimport {\n digestInventory,\n hiddenSubdirs,\n isSymbolicLink,\n portable,\n sourceInventoryPath,\n subdirs,\n walkTree,\n} from \"./scan/walk.js\";\nimport { inspectSpecFile, issue } from \"./scan/shape.js\";\nimport { inspectConfig } from \"./scan/config.js\";\nimport { artifactFor, artifactScope, inventoryChanges } from \"./scan/changes.js\";\nimport { capabilityDecisions } from \"./decide/capabilities.js\";\nimport { resolveRenames } from \"./decide/renames.js\";\nimport { artifactDispositions } from \"./decide/dispositions.js\";\nimport { inventoryVerdict } from \"./decide/verdict.js\";\nimport { isDirectory } from \"./scan/walk.js\";\nimport { addCounts } from \"./scan/shape.js\";\nimport { type OpenSpecCapability, type OpenSpecCounts } from \"./model/model.js\";\n\nexport async function inventoryOpenSpec(\n input: string,\n options: { mapping?: OpenSpecMapping } = {},\n): Promise<OpenSpecInventory> {\n const located = await locateOpenSpecRoot(input);\n const { inputRoot, root } = located;\n const mapping = emptyMapping(options.mapping);\n const renamed: OpenSpecRenamedUsage[] = [];\n const blockers: OpenSpecUnsupportedShape[] = [];\n // The read in progress. Every reader below appends to these two lists rather\n // than returning findings, because the ORDER they land in is what the `--json`\n // contract pins — five arrays merged afterwards would be a different report.\n const ingest: Ingest = { root, mapping, renamed, unsupported: blockers };\n const archiveDiagnostics: OpenSpecUnsupportedShape[] = [];\n for (const absolute of (await walkTree(root)).links) {\n const path = portable(root, absolute);\n const scope = artifactScope(path);\n issue(scope === \"archive\" ? archiveDiagnostics : blockers, scope, {\n code: \"openspec.symlink-unsupported\",\n path: path,\n message: \"Symbolic links are not followed by migration inventory; replace this link with an in-tree regular file or directory.\",\n });\n }\n const config = await inspectConfig(root, blockers);\n const specsRoot = join(root, \"specs\");\n const changesRoot = join(root, \"changes\");\n const inputStoreMetadata = join(inputRoot, \".openspec-store\", \"store.yaml\");\n const parentStoreMetadata = basename(root) === \"openspec\"\n ? join(dirname(root), \".openspec-store\", \"store.yaml\")\n : inputStoreMetadata;\n const storeMetadata = existsSync(inputStoreMetadata) ? inputStoreMetadata : parentStoreMetadata;\n const storeMetadataExists = existsSync(storeMetadata);\n const storeMetadataSymlink = storeMetadataExists && await isSymbolicLink(storeMetadata);\n if (storeMetadataSymlink) {\n issue(blockers, \"workspace\", {\n code: \"openspec.symlink-unsupported\",\n path: sourceInventoryPath(root, storeMetadata),\n message: \"Symbolic Store metadata is not followed; replace it with an in-tree regular file.\",\n });\n }\n const hasModernEmptyShape = config !== null || (storeMetadataExists && !storeMetadataSymlink);\n if (!await isDirectory(specsRoot) && !hasModernEmptyShape) {\n issue(blockers, \"living\", {\n code: \"openspec.specs-missing\",\n path: \"specs\",\n message: \"Living specs/ directory is missing.\",\n });\n }\n if (config !== null && config.store !== null && !await isDirectory(specsRoot) && !await isDirectory(changesRoot)) {\n issue(blockers, \"workspace\", {\n code: \"openspec.external-store-pointer\",\n path: \"config.yaml\",\n message: `Planning is externalized to Store '${config.store}'; audit that registered Store checkout directly.`,\n });\n }\n\n const capabilities: OpenSpecCapability[] = [];\n const livingRequirements = new Map<string, ReturnType<typeof parseRequirements>>();\n const livingCounts: OpenSpecCounts = { specFiles: 0, requirements: 0, scenarios: 0 };\n const livingTree = (await walkTree(specsRoot)).files;\n const livingFiles = livingTree.filter((path) => basename(path) === \"spec.md\");\n // The living twin of openspec.nonstandard-change-spec. Selecting living specs\n // by exact basename is right, but without the mirror a mis-cased or renamed\n // file simply disappears: specs/payments/Spec.md audited as a clean, empty\n // corpus while the identical shape under a change was reported.\n for (const file of livingTree.filter((path) =>\n /\\.md$/i.test(path) && basename(path) !== \"spec.md\" && basename(path) !== \"design.md\")) {\n blockers.push({\n code: \"openspec.nonstandard-living-spec\",\n path: portable(root, file),\n message: \"Markdown under specs/ is named neither spec.md nor design.md, so no capability reads it.\",\n scope: \"living\",\n });\n }\n for (const specFile of livingFiles) {\n const id = portable(specsRoot, dirname(specFile));\n const inspected = await inspectSpecFile(ingest, specFile, \"living\", null);\n livingRequirements.set(id, inspected.requirements);\n const capability = { id, files: [inspected.path], ...inspected.counts };\n capabilities.push(capability);\n addCounts(livingCounts, inspected.counts);\n }\n capabilities.sort((a, b) => compareIds(a.id, b.id));\n\n const activeIds = (await subdirs(changesRoot)).filter((id) => id !== \"archive\");\n const archiveRoot = join(changesRoot, \"archive\");\n const archivedIds = await subdirs(archiveRoot);\n for (const id of await hiddenSubdirs(changesRoot)) {\n blockers.push({\n code: \"openspec.hidden-change-directory\",\n path: portable(root, join(changesRoot, id)),\n message: \"A dot-prefixed change directory is not enumerated as a change, so nothing under it is migrated; rename it or move it out of changes/.\",\n scope: \"active\",\n });\n }\n for (const id of await hiddenSubdirs(archiveRoot)) {\n archiveDiagnostics.push({\n code: \"openspec.hidden-change-directory\",\n path: portable(root, join(archiveRoot, id)),\n message: \"A dot-prefixed archive directory is not enumerated as a change; frozen history is retained read-only where it is.\",\n scope: \"archive\",\n });\n }\n const fallbackSchema = config?.schema ?? \"spec-driven\";\n const active = await inventoryChanges(ingest, {\n base: changesRoot,\n ids: activeIds,\n scope: \"active\",\n fallbackSchema,\n findings: blockers,\n });\n const archived = await inventoryChanges(ingest, {\n base: archiveRoot,\n ids: archivedIds,\n scope: \"archive\",\n fallbackSchema,\n findings: archiveDiagnostics,\n });\n\n // Last-resort backstop under the whole \"audited past the corpus\" family, and\n // the one check that does not depend on root selection being right: an\n // inventory that read no living spec AND no active change describes nothing,\n // so no verdict over it can be `ready`. That is exactly the shape the Store\n // misdetection produced — ready, mechanically compatible, zero capabilities,\n // zero unsupported shapes — where the failure verdict was more confident than\n // any success verdict. A greenfield workspace whose only requirements live\n // under changes/ has an active change and is not caught here.\n if (livingCounts.specFiles === 0 && active.length === 0) {\n blockers.push({\n code: \"openspec.workspace-empty\",\n path: \".\",\n message: `Planning root ${root} holds no living spec and no active change; there is nothing to migrate, so confirm this is the directory that really holds the corpus.`,\n scope: \"workspace\",\n });\n }\n\n const artifactFiles = (await walkTree(root)).files;\n if (storeMetadataExists && !storeMetadataSymlink && !artifactFiles.includes(storeMetadata)) {\n artifactFiles.push(storeMetadata);\n }\n for (const absolute of artifactFiles) {\n const bytes = await readFile(absolute);\n if (isUtf8(bytes)) continue;\n const rootPath = portable(root, absolute);\n const artifactPath = sourceInventoryPath(root, absolute);\n const scope = absolute === storeMetadata ? \"workspace\" : artifactScope(rootPath);\n issue(scope === \"archive\" ? archiveDiagnostics : blockers, scope, {\n code: \"openspec.non-utf8-artifact\",\n path: artifactPath,\n message: \"Migration can preserve only valid UTF-8 authored artifacts; convert this file to UTF-8 before apply.\",\n });\n }\n // The digest binds a mapping to the truth it was reviewed against, and frozen\n // archive history is neither migrated nor allowed to block readiness. Covering\n // it meant a colleague's typo fix under changes/archive/ killed a completed\n // mapping, with no --force and no indication of which file moved.\n const inventoryDigest = await digestInventory(\n root,\n artifactFiles.filter((absolute) => artifactScope(portable(root, absolute)) !== \"archive\"),\n );\n const artifacts = artifactFiles\n .map((path) => artifactFor(root, path))\n .sort((a, b) => compareIds(a.path, b.path));\n\n const { mappingIssues, mappingDecisions, changeDecisions, routing } = capabilityDecisions({\n root,\n mapping,\n // An audit grades no source binding: there is no mapping to bind yet.\n bound: options.mapping !== undefined,\n inventoryDigest,\n livingRequirements,\n active,\n renamed,\n });\n resolveRenames(\n { root, mapping, bound: options.mapping !== undefined, inventoryDigest, livingRequirements, active, renamed },\n routing,\n mappingIssues,\n );\n const artifactDecisions = artifactDispositions(\n artifacts,\n mapping,\n new Set(active.map((change) => change.id)),\n mappingIssues,\n );\n\n return inventoryVerdict({\n located,\n inventoryDigest,\n config,\n storeMetadataPath: storeMetadataExists ? sourceInventoryPath(root, storeMetadata) : null,\n livingCounts,\n capabilities,\n active,\n archived,\n renamed,\n blockers,\n archiveDiagnostics,\n artifacts,\n mappingDecisions,\n changeDecisions,\n mappingIssues,\n artifactDecisions,\n });\n}\n"]}
@@ -10,23 +10,15 @@ import { dictionary } from "../../kernel/records.js";
10
10
  import {} from "./model.js";
11
11
  import {} from "./model.js";
12
12
  export function emptyMapping(mapping) {
13
- if (mapping === undefined) {
14
- return {
15
- source: null,
16
- capabilities: dictionary(),
17
- changes: dictionary(),
18
- renames: dictionary(),
19
- artifacts: dictionary(),
20
- };
21
- }
22
13
  // Keep the core inventory tolerant of programmatic v0 callers while the CLI
23
14
  // parser enforces the complete v1 document, including explicit `changes`.
15
+ // No mapping at all is the same answer with every field defaulted.
24
16
  return {
25
- source: mapping.source ?? null,
26
- capabilities: mapping.capabilities ?? dictionary(),
27
- changes: mapping.changes ?? dictionary(),
28
- renames: mapping.renames ?? dictionary(),
29
- artifacts: mapping.artifacts ?? dictionary(),
17
+ source: mapping?.source ?? null,
18
+ capabilities: mapping?.capabilities ?? dictionary(),
19
+ changes: mapping?.changes ?? dictionary(),
20
+ renames: mapping?.renames ?? dictionary(),
21
+ artifacts: mapping?.artifacts ?? dictionary(),
30
22
  };
31
23
  }
32
24
  export function createOpenSpecMappingSkeleton(inventory) {
@@ -49,20 +41,7 @@ export function createOpenSpecMappingSkeleton(inventory) {
49
41
  title: decision.title,
50
42
  },
51
43
  ])),
52
- renames: Object.fromEntries(inventory.renamed
53
- .filter((rename) => rename.scope === "active")
54
- .map((rename) => [rename.key, {
55
- from: rename.from,
56
- to: rename.to,
57
- existingRequirementId: rename.existingRequirementId,
58
- // The identity the inventory settled on, which is the field the
59
- // skeleton is asking a human to fill in. `existingRequirementId` is
60
- // the living source's own id and happens to equal it on the only path
61
- // that reaches here today — but a caller that audits WITH a mapping
62
- // would have a settled id and no existing one, and the skeleton would
63
- // hand that decision back as unmade.
64
- requirementId: rename.requirementId,
65
- }])),
44
+ renames: activeRenameEntries(inventory),
66
45
  artifacts: Object.fromEntries(inventory.artifactDecisions.map((decision) => [
67
46
  decision.path,
68
47
  {
@@ -73,4 +52,27 @@ export function createOpenSpecMappingSkeleton(inventory) {
73
52
  ])),
74
53
  };
75
54
  }
55
+ /**
56
+ * The active RENAMED pairs as a mapping document records them, keyed by rename
57
+ * key — one shape for the blank skeleton the audit hands over and the
58
+ * normalized record a migration writes beside the staged docs
59
+ * (commands/migrate-openspec/openspec/decisions.ts), so the two cannot
60
+ * disagree about what a rename entry holds.
61
+ */
62
+ export function activeRenameEntries(inventory) {
63
+ return Object.fromEntries(inventory.renamed
64
+ .filter((rename) => rename.scope === "active")
65
+ .map((rename) => [rename.key, {
66
+ from: rename.from,
67
+ to: rename.to,
68
+ existingRequirementId: rename.existingRequirementId,
69
+ // The identity the inventory settled on, which is the field the
70
+ // skeleton is asking a human to fill in. `existingRequirementId` is
71
+ // the living source's own id and happens to equal it on the only path
72
+ // that reaches here today — but a caller that audits WITH a mapping
73
+ // would have a settled id and no existing one, and the skeleton would
74
+ // hand that decision back as unmade.
75
+ requirementId: rename.requirementId,
76
+ }]));
77
+ }
76
78
  //# sourceMappingURL=mapping.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../../../../src/core/openspec/model/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAA0B,MAAM,YAAY,CAAC;AACpD,OAAO,EAAoC,MAAM,YAAY,CAAC;AA4G9D,MAAM,UAAU,YAAY,CAAC,OAAyB;IACpD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,YAAY,EAAE,UAAU,EAAE;YAC1B,OAAO,EAAE,UAAU,EAAE;YACrB,OAAO,EAAE,UAAU,EAAE;YACrB,SAAS,EAAE,UAAU,EAAE;SACxB,CAAC;IACJ,CAAC;IACD,4EAA4E;IAC5E,0EAA0E;IAC1E,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;QAC9B,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,UAAU,EAAE;QAClD,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,UAAU,EAAE;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,UAAU,EAAE;QACxC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,UAAU,EAAE;KAC7C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,SAA4B;IACxE,OAAO;QACL,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS,CAAC,eAAe,EAAE;QAC5E,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC5E,QAAQ,CAAC,UAAU;YACnB;gBACE,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,iBAAiB,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBAC9C,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;aAClD;SACF,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YACtE,QAAQ,CAAC,MAAM;YACf;gBACE,OAAO,EAAE,IAAI;gBACb,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;gBAC3C,KAAK,EAAE,QAAQ,CAAC,KAAK;aACtB;SACF,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,CAAC,WAAW,CACzB,SAAS,CAAC,OAAO;aACd,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC;aAC7C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE;gBAC5B,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;gBACnD,gEAAgE;gBAChE,oEAAoE;gBACpE,sEAAsE;gBACtE,oEAAoE;gBACpE,sEAAsE;gBACtE,qCAAqC;gBACrC,aAAa,EAAE,MAAM,CAAC,aAAa;aACpC,CAAC,CAAC,CACN;QACD,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC1E,QAAQ,CAAC,IAAI;YACb;gBACE,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,WAAW,EAAE,IAAI;gBACjB,oBAAoB,EAAE,QAAQ,CAAC,oBAAoB;aACpD;SACF,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The human decisions an audit asks for, and the blank form it hands over.\n *\n * Separate from `./model.ts` because these are the only shapes a PERSON edits.\n * The skeleton is generated from a finished inventory rather than from the\n * workspace, so every slot in it is a decision the audit actually reached — a\n * form with a field nothing grades is a form somebody fills in for nothing.\n */\nimport { dictionary } from \"../../kernel/records.js\";\nimport { type OpenSpecInventory } from \"./model.js\";\nimport { type OpenSpecArtifactDisposition } from \"./model.js\";\n\nexport interface CapabilityMappingDecision {\n capability: string;\n /** Backward-compatible convenience for consumers that only understand one owner. */\n service: string | null;\n /** One capability may need to be split across multiple loam services. */\n services: string[];\n /** Required only when a capability is split across more than one service. */\n requirementServices: Record<string, string[]>;\n suggestedService: string;\n hasLivingSpec: boolean;\n activeChanges: string[];\n status: \"needsMapping\" | \"mapped\";\n}\n\nexport interface OpenSpecCapabilityMapping {\n services: string[];\n requirementServices: Record<string, string[]>;\n}\n\nexport interface OpenSpecChangeMapping {\n feature: string | null;\n title: string;\n}\n\nexport interface OpenSpecChangeDecision {\n change: string;\n feature: string | null;\n title: string;\n suggestedFeature: string;\n suggestedTitle: string;\n status: \"needsFeature\" | \"mapped\";\n}\n\nexport interface OpenSpecMapping {\n source: { root: string; inventoryDigest: string } | null;\n capabilities: Record<string, OpenSpecCapabilityMapping>;\n changes: Record<string, OpenSpecChangeMapping>;\n renames: Record<string, string>;\n /**\n * Whatever a human wrote, not what the type wishes they had written. Declaring\n * this as the disposition union meant the parser had to assert its way into\n * it, so an unrecognised disposition arrived typed as a valid one and only\n * `mapping.invalid-artifact-disposition` — a check downstream of the parse —\n * stood between it and code comparing it against a literal. The union is\n * recovered where it is needed, through `isOpenSpecArtifactDisposition`.\n */\n artifacts: Record<string, string>;\n}\n\nexport interface OpenSpecMappingIssue {\n code:\n | \"mapping.source-missing\"\n | \"mapping.source-root-mismatch\"\n | \"mapping.source-digest-mismatch\"\n | \"mapping.unknown-capability\"\n | \"mapping.unknown-requirement\"\n | \"mapping.requirement-allocation-missing\"\n | \"mapping.requirement-service-unknown\"\n | \"mapping.service-allocation-empty\"\n | \"mapping.unknown-change\"\n | \"mapping.change-title-missing\"\n | \"mapping.feature-id-invalid\"\n | \"mapping.feature-id-duplicate\"\n | \"mapping.unknown-rename\"\n | \"mapping.invalid-requirement-id\"\n | \"mapping.rename-source-missing\"\n | \"mapping.rename-source-ambiguous\"\n | \"mapping.rename-source-id-invalid\"\n | \"mapping.rename-target-conflict\"\n | \"mapping.rename-existing-id-conflict\"\n | \"mapping.rename-id-conflict\"\n | \"mapping.rename-double-source\"\n | \"mapping.rename-double-target\"\n | \"mapping.rename-chain\"\n | \"mapping.unknown-artifact\"\n | \"mapping.invalid-artifact-disposition\";\n key: string;\n message: string;\n}\n\nexport interface OpenSpecMappingSkeleton {\n version: 1;\n source: { root: string; inventoryDigest: string };\n capabilities: Record<string, {\n services: string[];\n suggestedServices: string[];\n requirementServices: Record<string, string[]>;\n }>;\n changes: Record<string, {\n feature: null;\n suggestedFeature: string;\n title: string;\n }>;\n renames: Record<string, {\n from: string | null;\n to: string | null;\n existingRequirementId: string | null;\n requirementId: string | null;\n }>;\n artifacts: Record<string, {\n kind: \"proposal\" | \"tasks\" | \"change-design\";\n disposition: null;\n suggestedDisposition: OpenSpecArtifactDisposition;\n }>;\n}\n\nexport function emptyMapping(mapping?: OpenSpecMapping): OpenSpecMapping {\n if (mapping === undefined) {\n return {\n source: null,\n capabilities: dictionary(),\n changes: dictionary(),\n renames: dictionary(),\n artifacts: dictionary(),\n };\n }\n // Keep the core inventory tolerant of programmatic v0 callers while the CLI\n // parser enforces the complete v1 document, including explicit `changes`.\n return {\n source: mapping.source ?? null,\n capabilities: mapping.capabilities ?? dictionary(),\n changes: mapping.changes ?? dictionary(),\n renames: mapping.renames ?? dictionary(),\n artifacts: mapping.artifacts ?? dictionary(),\n };\n}\n\nexport function createOpenSpecMappingSkeleton(inventory: OpenSpecInventory): OpenSpecMappingSkeleton {\n return {\n version: 1,\n source: { root: inventory.root, inventoryDigest: inventory.inventoryDigest },\n capabilities: Object.fromEntries(inventory.mappingDecisions.map((decision) => [\n decision.capability,\n {\n services: decision.services,\n suggestedServices: [decision.suggestedService],\n requirementServices: decision.requirementServices,\n },\n ])),\n changes: Object.fromEntries(inventory.changeDecisions.map((decision) => [\n decision.change,\n {\n feature: null,\n suggestedFeature: decision.suggestedFeature,\n title: decision.title,\n },\n ])),\n renames: Object.fromEntries(\n inventory.renamed\n .filter((rename) => rename.scope === \"active\")\n .map((rename) => [rename.key, {\n from: rename.from,\n to: rename.to,\n existingRequirementId: rename.existingRequirementId,\n // The identity the inventory settled on, which is the field the\n // skeleton is asking a human to fill in. `existingRequirementId` is\n // the living source's own id and happens to equal it on the only path\n // that reaches here today — but a caller that audits WITH a mapping\n // would have a settled id and no existing one, and the skeleton would\n // hand that decision back as unmade.\n requirementId: rename.requirementId,\n }]),\n ),\n artifacts: Object.fromEntries(inventory.artifactDecisions.map((decision) => [\n decision.path,\n {\n kind: decision.kind,\n disposition: null,\n suggestedDisposition: decision.suggestedDisposition,\n },\n ])),\n };\n}\n"]}
1
+ {"version":3,"file":"mapping.js","sourceRoot":"","sources":["../../../../src/core/openspec/model/mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAA0B,MAAM,YAAY,CAAC;AACpD,OAAO,EAAoC,MAAM,YAAY,CAAC;AA4G9D,MAAM,UAAU,YAAY,CAAC,OAAyB;IACpD,4EAA4E;IAC5E,0EAA0E;IAC1E,mEAAmE;IACnE,OAAO;QACL,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI;QAC/B,YAAY,EAAE,OAAO,EAAE,YAAY,IAAI,UAAU,EAAE;QACnD,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE;QACzC,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE;QACzC,SAAS,EAAE,OAAO,EAAE,SAAS,IAAI,UAAU,EAAE;KAC9C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,SAA4B;IACxE,OAAO;QACL,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS,CAAC,eAAe,EAAE;QAC5E,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC5E,QAAQ,CAAC,UAAU;YACnB;gBACE,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,iBAAiB,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBAC9C,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;aAClD;SACF,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YACtE,QAAQ,CAAC,MAAM;YACf;gBACE,OAAO,EAAE,IAAI;gBACb,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;gBAC3C,KAAK,EAAE,QAAQ,CAAC,KAAK;aACtB;SACF,CAAC,CAAC;QACH,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC;QACvC,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC1E,QAAQ,CAAC,IAAI;YACb;gBACE,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,WAAW,EAAE,IAAI;gBACjB,oBAAoB,EAAE,QAAQ,CAAC,oBAAoB;aACpD;SACF,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAA4B;IAC9D,OAAO,MAAM,CAAC,WAAW,CACvB,SAAS,CAAC,OAAO;SACd,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC;SAC7C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE;YAC5B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;YACnD,gEAAgE;YAChE,oEAAoE;YACpE,sEAAsE;YACtE,oEAAoE;YACpE,sEAAsE;YACtE,qCAAqC;YACrC,aAAa,EAAE,MAAM,CAAC,aAAa;SACpC,CAAC,CAAC,CACN,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The human decisions an audit asks for, and the blank form it hands over.\n *\n * Separate from `./model.ts` because these are the only shapes a PERSON edits.\n * The skeleton is generated from a finished inventory rather than from the\n * workspace, so every slot in it is a decision the audit actually reached — a\n * form with a field nothing grades is a form somebody fills in for nothing.\n */\nimport { dictionary } from \"../../kernel/records.js\";\nimport { type OpenSpecInventory } from \"./model.js\";\nimport { type OpenSpecArtifactDisposition } from \"./model.js\";\n\nexport interface CapabilityMappingDecision {\n capability: string;\n /** Backward-compatible convenience for consumers that only understand one owner. */\n service: string | null;\n /** One capability may need to be split across multiple loam services. */\n services: string[];\n /** Required only when a capability is split across more than one service. */\n requirementServices: Record<string, string[]>;\n suggestedService: string;\n hasLivingSpec: boolean;\n activeChanges: string[];\n status: \"needsMapping\" | \"mapped\";\n}\n\nexport interface OpenSpecCapabilityMapping {\n services: string[];\n requirementServices: Record<string, string[]>;\n}\n\nexport interface OpenSpecChangeMapping {\n feature: string | null;\n title: string;\n}\n\nexport interface OpenSpecChangeDecision {\n change: string;\n feature: string | null;\n title: string;\n suggestedFeature: string;\n suggestedTitle: string;\n status: \"needsFeature\" | \"mapped\";\n}\n\nexport interface OpenSpecMapping {\n source: { root: string; inventoryDigest: string } | null;\n capabilities: Record<string, OpenSpecCapabilityMapping>;\n changes: Record<string, OpenSpecChangeMapping>;\n renames: Record<string, string>;\n /**\n * Whatever a human wrote, not what the type wishes they had written. Declaring\n * this as the disposition union meant the parser had to assert its way into\n * it, so an unrecognised disposition arrived typed as a valid one and only\n * `mapping.invalid-artifact-disposition` — a check downstream of the parse —\n * stood between it and code comparing it against a literal. The union is\n * recovered where it is needed, through `isOpenSpecArtifactDisposition`.\n */\n artifacts: Record<string, string>;\n}\n\nexport interface OpenSpecMappingIssue {\n code:\n | \"mapping.source-missing\"\n | \"mapping.source-root-mismatch\"\n | \"mapping.source-digest-mismatch\"\n | \"mapping.unknown-capability\"\n | \"mapping.unknown-requirement\"\n | \"mapping.requirement-allocation-missing\"\n | \"mapping.requirement-service-unknown\"\n | \"mapping.service-allocation-empty\"\n | \"mapping.unknown-change\"\n | \"mapping.change-title-missing\"\n | \"mapping.feature-id-invalid\"\n | \"mapping.feature-id-duplicate\"\n | \"mapping.unknown-rename\"\n | \"mapping.invalid-requirement-id\"\n | \"mapping.rename-source-missing\"\n | \"mapping.rename-source-ambiguous\"\n | \"mapping.rename-source-id-invalid\"\n | \"mapping.rename-target-conflict\"\n | \"mapping.rename-existing-id-conflict\"\n | \"mapping.rename-id-conflict\"\n | \"mapping.rename-double-source\"\n | \"mapping.rename-double-target\"\n | \"mapping.rename-chain\"\n | \"mapping.unknown-artifact\"\n | \"mapping.invalid-artifact-disposition\";\n key: string;\n message: string;\n}\n\nexport interface OpenSpecMappingSkeleton {\n version: 1;\n source: { root: string; inventoryDigest: string };\n capabilities: Record<string, {\n services: string[];\n suggestedServices: string[];\n requirementServices: Record<string, string[]>;\n }>;\n changes: Record<string, {\n feature: null;\n suggestedFeature: string;\n title: string;\n }>;\n renames: Record<string, {\n from: string | null;\n to: string | null;\n existingRequirementId: string | null;\n requirementId: string | null;\n }>;\n artifacts: Record<string, {\n kind: \"proposal\" | \"tasks\" | \"change-design\";\n disposition: null;\n suggestedDisposition: OpenSpecArtifactDisposition;\n }>;\n}\n\nexport function emptyMapping(mapping?: OpenSpecMapping): OpenSpecMapping {\n // Keep the core inventory tolerant of programmatic v0 callers while the CLI\n // parser enforces the complete v1 document, including explicit `changes`.\n // No mapping at all is the same answer with every field defaulted.\n return {\n source: mapping?.source ?? null,\n capabilities: mapping?.capabilities ?? dictionary(),\n changes: mapping?.changes ?? dictionary(),\n renames: mapping?.renames ?? dictionary(),\n artifacts: mapping?.artifacts ?? dictionary(),\n };\n}\n\nexport function createOpenSpecMappingSkeleton(inventory: OpenSpecInventory): OpenSpecMappingSkeleton {\n return {\n version: 1,\n source: { root: inventory.root, inventoryDigest: inventory.inventoryDigest },\n capabilities: Object.fromEntries(inventory.mappingDecisions.map((decision) => [\n decision.capability,\n {\n services: decision.services,\n suggestedServices: [decision.suggestedService],\n requirementServices: decision.requirementServices,\n },\n ])),\n changes: Object.fromEntries(inventory.changeDecisions.map((decision) => [\n decision.change,\n {\n feature: null,\n suggestedFeature: decision.suggestedFeature,\n title: decision.title,\n },\n ])),\n renames: activeRenameEntries(inventory),\n artifacts: Object.fromEntries(inventory.artifactDecisions.map((decision) => [\n decision.path,\n {\n kind: decision.kind,\n disposition: null,\n suggestedDisposition: decision.suggestedDisposition,\n },\n ])),\n };\n}\n\n/**\n * The active RENAMED pairs as a mapping document records them, keyed by rename\n * key — one shape for the blank skeleton the audit hands over and the\n * normalized record a migration writes beside the staged docs\n * (commands/migrate-openspec/openspec/decisions.ts), so the two cannot\n * disagree about what a rename entry holds.\n */\nexport function activeRenameEntries(inventory: OpenSpecInventory): OpenSpecMappingSkeleton[\"renames\"] {\n return Object.fromEntries(\n inventory.renamed\n .filter((rename) => rename.scope === \"active\")\n .map((rename) => [rename.key, {\n from: rename.from,\n to: rename.to,\n existingRequirementId: rename.existingRequirementId,\n // The identity the inventory settled on, which is the field the\n // skeleton is asking a human to fill in. `existingRequirementId` is\n // the living source's own id and happens to equal it on the only path\n // that reaches here today — but a caller that audits WITH a mapping\n // would have a settled id and no existing one, and the skeleton would\n // hand that decision back as unmade.\n requirementId: rename.requirementId,\n }]),\n );\n}\n"]}
@@ -14,16 +14,21 @@ import {} from "./mapping.js";
14
14
  /**
15
15
  * The upstream commits this binary's OpenSpec compatibility is measured against:
16
16
  * `release` is the certified current release, swept file by file by the corpus
17
- * gate (`scripts/check-openspec-corpus.ts`) and vendored as the seven verbatim
18
- * fixtures. `mainCanary` is the older post-v1.7 canary, kept as a regression pin
19
- * rather than as a claim about where `main` points today — at the time of the
20
- * v1.9.0 pin, `main` and the release tag were the same commit.
17
+ * gate (`scripts/check-openspec-corpus.ts`; the `release` pin in
18
+ * `scripts/openspec-corpus.ts` reads this constant, so the banner and the gate
19
+ * cannot name different commits). The seven verbatim fixtures were vendored at
20
+ * the previous pin, v1.9.0, and stay there: that commit is now one of the
21
+ * gate's regression pins, and the v1.14.1 sweep exposed no shape the seven
22
+ * lack. `mainCanary` is the older
23
+ * post-v1.7 canary, kept as a regression pin rather than as a claim about where
24
+ * `main` points today — at the time of the v1.9.0 pin, `main` and the release
25
+ * tag were the same commit.
21
26
  */
22
27
  export const OPENSPEC_BASELINES = Object.freeze({
23
28
  release: Object.freeze({
24
- version: "1.9.0",
25
- ref: "v1.9.0",
26
- commit: "2826b8889e5223a9a8095d4428b60b56597e1020",
29
+ version: "1.14.1",
30
+ ref: "v1.14.1",
31
+ commit: "87c3595ace6a2e22957f39ebe5b74c2f2316e0cb",
27
32
  }),
28
33
  mainCanary: Object.freeze({
29
34
  ref: "main",
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../src/core/openspec/model/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAIN,MAAM,cAAc,CAAC;AAEtB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;QACrB,OAAO,EAAE,OAAO;QAChB,GAAG,EAAE,QAAQ;QACb,MAAM,EAAE,0CAA0C;KACnD,CAAC;IACF,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QACxB,GAAG,EAAE,MAAM;QACX,MAAM,EAAE,0CAA0C;KACnD,CAAC;CACH,CAAC,CAAC;AA4EH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,2BAA2B;IAC3B,+BAA+B;IAC/B,sBAAsB;IACtB,kBAAkB;IAClB,uBAAuB;IACvB,2BAA2B;IAC3B,mBAAmB;IACnB,8BAA8B;IAC9B,uBAAuB;IACvB,WAAW;IACX,wBAAwB;IACxB,kBAAkB;IAClB,eAAe;CACP,CAAC;AAGX,qFAAqF;AACrF,MAAM,UAAU,6BAA6B,CAAC,KAAa;IACzD,OAAQ,8BAAoD,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/E,CAAC;AA8ED,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF","sourcesContent":["/**\n * What an OpenSpec workspace IS, as loam models it.\n *\n * `core/openspec-inventory.ts` was the one foreign model in this codebase and\n * this package is still it: OpenSpec's vocabulary — capabilities, changes,\n * RENAMED pairs, `.openspec.yaml` — is named here and nowhere else, and nothing\n * outside `commands/migrate-openspec/` may import an `OpenSpec*` type. That\n * quarantine is the point of the package, not a side effect of its size.\n *\n * Types only, plus the one error the root search throws. Every module below\n * fills these in; none of them redefines one.\n */\nimport {\n type CapabilityMappingDecision,\n type OpenSpecChangeDecision,\n type OpenSpecMappingIssue,\n} from \"./mapping.js\";\n\n/**\n * The upstream commits this binary's OpenSpec compatibility is measured against:\n * `release` is the certified current release, swept file by file by the corpus\n * gate (`scripts/check-openspec-corpus.ts`) and vendored as the seven verbatim\n * fixtures. `mainCanary` is the older post-v1.7 canary, kept as a regression pin\n * rather than as a claim about where `main` points today — at the time of the\n * v1.9.0 pin, `main` and the release tag were the same commit.\n */\nexport const OPENSPEC_BASELINES = Object.freeze({\n release: Object.freeze({\n version: \"1.9.0\",\n ref: \"v1.9.0\",\n commit: \"2826b8889e5223a9a8095d4428b60b56597e1020\",\n }),\n mainCanary: Object.freeze({\n ref: \"main\",\n commit: \"45cca5db6137ed209117cc70510eb3e057fb981b\",\n }),\n});\n\nexport interface OpenSpecCounts {\n specFiles: number;\n requirements: number;\n scenarios: number;\n}\n\nexport interface OpenSpecCapability extends OpenSpecCounts {\n /** Nested capability ids retain their complete relative directory, e.g. payments/refunds. */\n id: string;\n files: string[];\n}\n\nexport interface OpenSpecChangeMetadata {\n path: string | null;\n schema: string | null;\n skipSpecs: boolean;\n created: string | null;\n fields: string[];\n}\n\nexport interface OpenSpecChangeSpec extends OpenSpecCounts {\n capability: string;\n path: string;\n requirementNames: string[];\n}\n\nexport interface OpenSpecChange extends OpenSpecCounts {\n id: string;\n files: string[];\n specs: OpenSpecChangeSpec[];\n artifacts: string[];\n metadata: OpenSpecChangeMetadata;\n}\n\nexport type OpenSpecIssueScope = \"workspace\" | \"living\" | \"active\" | \"archive\";\n\nexport interface OpenSpecRenamedUsage {\n /** Stable key used by the mapping file. */\n key: string;\n path: string;\n line: number;\n scope: \"active\" | \"archive\";\n changeId: string;\n capability: string;\n from: string | null;\n to: string | null;\n existingRequirementId: string | null;\n requirementId: string | null;\n status: \"needsIdentity\" | \"mapped\";\n}\n\nexport interface OpenSpecUnsupportedShape {\n code: string;\n path: string;\n message: string;\n scope: OpenSpecIssueScope;\n}\n\nexport type OpenSpecArtifactScope = \"workspace\" | \"living\" | \"active\" | \"archive\";\nexport type OpenSpecArtifactKind =\n | \"config\"\n | \"store-metadata\"\n | \"project-context\"\n | \"agent-instructions\"\n | \"living-spec\"\n | \"living-design\"\n | \"change-metadata\"\n | \"proposal\"\n | \"tasks\"\n | \"change-design\"\n | \"delta-spec\"\n | \"custom-schema\"\n | \"schema-template\"\n | \"other\";\n/**\n * The dispositions as values, not only as a type, because a mapping file names\n * one as a plain string and the parse boundary has to be able to ask whether\n * that string is one of these. A hand-written second spelling of the same list\n * is how a disposition added here would go on being unrecognised where it is\n * read from somebody else's YAML.\n */\nexport const OPENSPEC_ARTIFACT_DISPOSITIONS = [\n \"translate-project-context\",\n \"record-external-planning-root\",\n \"remove-after-cutover\",\n \"map-requirements\",\n \"review-as-service-adr\",\n \"translate-change-metadata\",\n \"convert-to-intent\",\n \"preserve-as-legacy-checklist\",\n \"review-as-feature-adr\",\n \"map-delta\",\n \"review-custom-workflow\",\n \"retain-read-only\",\n \"manual-review\",\n] as const;\nexport type OpenSpecArtifactDisposition = (typeof OPENSPEC_ARTIFACT_DISPOSITIONS)[number];\n\n/** Whether an authored string names a disposition this codebase actually defines. */\nexport function isOpenSpecArtifactDisposition(value: string): value is OpenSpecArtifactDisposition {\n return (OPENSPEC_ARTIFACT_DISPOSITIONS as readonly string[]).includes(value);\n}\n\nexport interface OpenSpecArtifact {\n /** Workspace-relative, so store metadata outside openspec/ remains representable. */\n path: string;\n scope: OpenSpecArtifactScope;\n kind: OpenSpecArtifactKind;\n disposition: OpenSpecArtifactDisposition;\n changeId?: string;\n capability?: string;\n}\n\nexport interface OpenSpecArtifactDecision {\n path: string;\n kind: \"proposal\" | \"tasks\" | \"change-design\";\n suggestedDisposition: OpenSpecArtifactDisposition;\n /**\n * What the mapping selected, verbatim. This is the field the invalid-value\n * report is written from, so it has to be able to hold the invalid value —\n * narrowing it to the union would only mean asserting one back into it.\n */\n disposition: string | null;\n status: \"needsDisposition\" | \"mapped\";\n}\n\nexport interface OpenSpecConfigSummary {\n path: string;\n schema: string | null;\n store: string | null;\n hasContext: boolean;\n ruleArtifacts: string[];\n references: string[];\n}\n\nexport interface OpenSpecInventory {\n baselines: typeof OPENSPEC_BASELINES;\n inputRoot: string;\n root: string;\n /** SHA-256 over every inventoried OpenSpec source path and byte sequence. */\n inventoryDigest: string;\n workspace: {\n kind: \"project\" | \"store\" | \"openspec-root\";\n config: OpenSpecConfigSummary | null;\n storeMetadataPath: string | null;\n };\n ready: boolean;\n mechanicallyCompatible: boolean;\n readiness: {\n living: { compatible: boolean; issueCount: number };\n active: { compatible: boolean; issueCount: number };\n sourceCurrent: boolean;\n mappingsResolved: boolean;\n changesResolved: boolean;\n renamesResolved: boolean;\n dispositionsResolved: boolean;\n migrationReady: boolean;\n };\n living: OpenSpecCounts & { capabilities: OpenSpecCapability[] };\n changes: {\n active: OpenSpecChange[];\n archived: OpenSpecChange[];\n counts: { active: number; archived: number };\n };\n renamed: OpenSpecRenamedUsage[];\n /** Living/active blockers only. Frozen archive history never blocks migration readiness. */\n unsupported: OpenSpecUnsupportedShape[];\n /** Historical anomalies are retained for review, but are not migration blockers. */\n archiveDiagnostics: OpenSpecUnsupportedShape[];\n mappingDecisions: CapabilityMappingDecision[];\n needsMapping: CapabilityMappingDecision[];\n changeDecisions: OpenSpecChangeDecision[];\n needsChangeMapping: OpenSpecChangeDecision[];\n mappingIssues: OpenSpecMappingIssue[];\n artifacts: OpenSpecArtifact[];\n artifactDecisions: OpenSpecArtifactDecision[];\n needsDisposition: OpenSpecArtifactDecision[];\n}\n\nexport class OpenSpecRootError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OpenSpecRootError\";\n }\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../src/core/openspec/model/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAIN,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;QACrB,OAAO,EAAE,QAAQ;QACjB,GAAG,EAAE,SAAS;QACd,MAAM,EAAE,0CAA0C;KACnD,CAAC;IACF,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QACxB,GAAG,EAAE,MAAM;QACX,MAAM,EAAE,0CAA0C;KACnD,CAAC;CACH,CAAC,CAAC;AA4EH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,2BAA2B;IAC3B,+BAA+B;IAC/B,sBAAsB;IACtB,kBAAkB;IAClB,uBAAuB;IACvB,2BAA2B;IAC3B,mBAAmB;IACnB,8BAA8B;IAC9B,uBAAuB;IACvB,WAAW;IACX,wBAAwB;IACxB,kBAAkB;IAClB,eAAe;CACP,CAAC;AAGX,qFAAqF;AACrF,MAAM,UAAU,6BAA6B,CAAC,KAAa;IACzD,OAAQ,8BAAoD,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/E,CAAC;AA8ED,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF","sourcesContent":["/**\n * What an OpenSpec workspace IS, as loam models it.\n *\n * `core/openspec-inventory.ts` was the one foreign model in this codebase and\n * this package is still it: OpenSpec's vocabulary — capabilities, changes,\n * RENAMED pairs, `.openspec.yaml` — is named here and nowhere else, and nothing\n * outside `commands/migrate-openspec/` may import an `OpenSpec*` type. That\n * quarantine is the point of the package, not a side effect of its size.\n *\n * Types only, plus the one error the root search throws. Every module below\n * fills these in; none of them redefines one.\n */\nimport {\n type CapabilityMappingDecision,\n type OpenSpecChangeDecision,\n type OpenSpecMappingIssue,\n} from \"./mapping.js\";\n\n/**\n * The upstream commits this binary's OpenSpec compatibility is measured against:\n * `release` is the certified current release, swept file by file by the corpus\n * gate (`scripts/check-openspec-corpus.ts`; the `release` pin in\n * `scripts/openspec-corpus.ts` reads this constant, so the banner and the gate\n * cannot name different commits). The seven verbatim fixtures were vendored at\n * the previous pin, v1.9.0, and stay there: that commit is now one of the\n * gate's regression pins, and the v1.14.1 sweep exposed no shape the seven\n * lack. `mainCanary` is the older\n * post-v1.7 canary, kept as a regression pin rather than as a claim about where\n * `main` points today — at the time of the v1.9.0 pin, `main` and the release\n * tag were the same commit.\n */\nexport const OPENSPEC_BASELINES = Object.freeze({\n release: Object.freeze({\n version: \"1.14.1\",\n ref: \"v1.14.1\",\n commit: \"87c3595ace6a2e22957f39ebe5b74c2f2316e0cb\",\n }),\n mainCanary: Object.freeze({\n ref: \"main\",\n commit: \"45cca5db6137ed209117cc70510eb3e057fb981b\",\n }),\n});\n\nexport interface OpenSpecCounts {\n specFiles: number;\n requirements: number;\n scenarios: number;\n}\n\nexport interface OpenSpecCapability extends OpenSpecCounts {\n /** Nested capability ids retain their complete relative directory, e.g. payments/refunds. */\n id: string;\n files: string[];\n}\n\nexport interface OpenSpecChangeMetadata {\n path: string | null;\n schema: string | null;\n skipSpecs: boolean;\n created: string | null;\n fields: string[];\n}\n\nexport interface OpenSpecChangeSpec extends OpenSpecCounts {\n capability: string;\n path: string;\n requirementNames: string[];\n}\n\nexport interface OpenSpecChange extends OpenSpecCounts {\n id: string;\n files: string[];\n specs: OpenSpecChangeSpec[];\n artifacts: string[];\n metadata: OpenSpecChangeMetadata;\n}\n\nexport type OpenSpecIssueScope = \"workspace\" | \"living\" | \"active\" | \"archive\";\n\nexport interface OpenSpecRenamedUsage {\n /** Stable key used by the mapping file. */\n key: string;\n path: string;\n line: number;\n scope: \"active\" | \"archive\";\n changeId: string;\n capability: string;\n from: string | null;\n to: string | null;\n existingRequirementId: string | null;\n requirementId: string | null;\n status: \"needsIdentity\" | \"mapped\";\n}\n\nexport interface OpenSpecUnsupportedShape {\n code: string;\n path: string;\n message: string;\n scope: OpenSpecIssueScope;\n}\n\nexport type OpenSpecArtifactScope = \"workspace\" | \"living\" | \"active\" | \"archive\";\nexport type OpenSpecArtifactKind =\n | \"config\"\n | \"store-metadata\"\n | \"project-context\"\n | \"agent-instructions\"\n | \"living-spec\"\n | \"living-design\"\n | \"change-metadata\"\n | \"proposal\"\n | \"tasks\"\n | \"change-design\"\n | \"delta-spec\"\n | \"custom-schema\"\n | \"schema-template\"\n | \"other\";\n/**\n * The dispositions as values, not only as a type, because a mapping file names\n * one as a plain string and the parse boundary has to be able to ask whether\n * that string is one of these. A hand-written second spelling of the same list\n * is how a disposition added here would go on being unrecognised where it is\n * read from somebody else's YAML.\n */\nexport const OPENSPEC_ARTIFACT_DISPOSITIONS = [\n \"translate-project-context\",\n \"record-external-planning-root\",\n \"remove-after-cutover\",\n \"map-requirements\",\n \"review-as-service-adr\",\n \"translate-change-metadata\",\n \"convert-to-intent\",\n \"preserve-as-legacy-checklist\",\n \"review-as-feature-adr\",\n \"map-delta\",\n \"review-custom-workflow\",\n \"retain-read-only\",\n \"manual-review\",\n] as const;\nexport type OpenSpecArtifactDisposition = (typeof OPENSPEC_ARTIFACT_DISPOSITIONS)[number];\n\n/** Whether an authored string names a disposition this codebase actually defines. */\nexport function isOpenSpecArtifactDisposition(value: string): value is OpenSpecArtifactDisposition {\n return (OPENSPEC_ARTIFACT_DISPOSITIONS as readonly string[]).includes(value);\n}\n\nexport interface OpenSpecArtifact {\n /** Workspace-relative, so store metadata outside openspec/ remains representable. */\n path: string;\n scope: OpenSpecArtifactScope;\n kind: OpenSpecArtifactKind;\n disposition: OpenSpecArtifactDisposition;\n changeId?: string;\n capability?: string;\n}\n\nexport interface OpenSpecArtifactDecision {\n path: string;\n kind: \"proposal\" | \"tasks\" | \"change-design\";\n suggestedDisposition: OpenSpecArtifactDisposition;\n /**\n * What the mapping selected, verbatim. This is the field the invalid-value\n * report is written from, so it has to be able to hold the invalid value —\n * narrowing it to the union would only mean asserting one back into it.\n */\n disposition: string | null;\n status: \"needsDisposition\" | \"mapped\";\n}\n\nexport interface OpenSpecConfigSummary {\n path: string;\n schema: string | null;\n store: string | null;\n hasContext: boolean;\n ruleArtifacts: string[];\n references: string[];\n}\n\nexport interface OpenSpecInventory {\n baselines: typeof OPENSPEC_BASELINES;\n inputRoot: string;\n root: string;\n /** SHA-256 over every inventoried OpenSpec source path and byte sequence. */\n inventoryDigest: string;\n workspace: {\n kind: \"project\" | \"store\" | \"openspec-root\";\n config: OpenSpecConfigSummary | null;\n storeMetadataPath: string | null;\n };\n ready: boolean;\n mechanicallyCompatible: boolean;\n readiness: {\n living: { compatible: boolean; issueCount: number };\n active: { compatible: boolean; issueCount: number };\n sourceCurrent: boolean;\n mappingsResolved: boolean;\n changesResolved: boolean;\n renamesResolved: boolean;\n dispositionsResolved: boolean;\n migrationReady: boolean;\n };\n living: OpenSpecCounts & { capabilities: OpenSpecCapability[] };\n changes: {\n active: OpenSpecChange[];\n archived: OpenSpecChange[];\n counts: { active: number; archived: number };\n };\n renamed: OpenSpecRenamedUsage[];\n /** Living/active blockers only. Frozen archive history never blocks migration readiness. */\n unsupported: OpenSpecUnsupportedShape[];\n /** Historical anomalies are retained for review, but are not migration blockers. */\n archiveDiagnostics: OpenSpecUnsupportedShape[];\n mappingDecisions: CapabilityMappingDecision[];\n needsMapping: CapabilityMappingDecision[];\n changeDecisions: OpenSpecChangeDecision[];\n needsChangeMapping: OpenSpecChangeDecision[];\n mappingIssues: OpenSpecMappingIssue[];\n artifacts: OpenSpecArtifact[];\n artifactDecisions: OpenSpecArtifactDecision[];\n needsDisposition: OpenSpecArtifactDecision[];\n}\n\nexport class OpenSpecRootError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OpenSpecRootError\";\n }\n}\n"]}
@@ -10,14 +10,20 @@
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
12
  import { basename, dirname, join } from "node:path";
13
- import { asRecord } from "../../kernel/records.js";
13
+ import { asRecord, isStringArray } from "../../kernel/records.js";
14
14
  import { compareIds } from "../../repo/entries.js";
15
15
  import { inspectSpecFile } from "./shape.js";
16
16
  import {} from "../model/model.js";
17
17
  import { schemaSpecPolicy } from "./config.js";
18
- import { isSymbolicLink, isStringArray, portable, sourceInventoryPath, walkFiles, yamlRecord, } from "./walk.js";
18
+ import { isSymbolicLink, portable, sourceInventoryPath, walkTree, yamlRecord, } from "./walk.js";
19
19
  import { addCounts, issue } from "./shape.js";
20
20
  import {} from "./workspace.js";
21
+ /**
22
+ * A calendar date, optionally with a time. OpenSpec's template writes the plain
23
+ * date, but a timestamp is still a date — and rejecting it blocked every change
24
+ * and every capability in the workspace over one field nobody reads, fixable
25
+ * only by editing the source migration promises not to touch.
26
+ */
21
27
  const CHANGE_CREATED_RE = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
22
28
  export async function inspectChangeMetadata(ingest, dir, scope, fallbackSchema) {
23
29
  const { root, unsupported } = ingest;
@@ -102,9 +108,9 @@ export async function inventoryChanges(ingest, tree) {
102
108
  const changes = [];
103
109
  for (const id of ids) {
104
110
  const dir = join(base, id);
105
- const allArtifacts = await walkFiles(dir);
111
+ const allArtifacts = (await walkTree(dir)).files;
106
112
  const specRoot = join(dir, "specs");
107
- const allSpecs = await walkFiles(specRoot);
113
+ const allSpecs = (await walkTree(specRoot)).files;
108
114
  const specFiles = allSpecs.filter((path) => basename(path) === "spec.md");
109
115
  const metadata = await inspectChangeMetadata(scoped, dir, scope, fallbackSchema);
110
116
  const counts = { specFiles: 0, requirements: 0, scenarios: 0 };
@@ -1 +1 @@
1
- {"version":3,"file":"changes.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/changes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One `changes/<id>/` directory read end to end: its metadata, its spec files,\n * and every artifact it holds.\n *\n * Active and archived changes come through the same reader with a different\n * scope, because they are the same shape and only the consequences differ — an\n * archive finding is a diagnostic, an active one blocks. Keeping that in the\n * scope rather than in two readers is what stops the two drifting into two\n * ideas of what a change looks like.\n */\nimport { existsSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { asRecord } from \"../../kernel/records.js\";\nimport { compareIds } from \"../../repo/entries.js\";\nimport { inspectSpecFile } from \"./shape.js\";\nimport {\n type OpenSpecArtifact,\n type OpenSpecArtifactDisposition,\n type OpenSpecArtifactKind,\n type OpenSpecArtifactScope,\n type OpenSpecChange,\n type OpenSpecChangeMetadata,\n type OpenSpecChangeSpec,\n type OpenSpecCounts,\n type OpenSpecUnsupportedShape,\n} from \"../model/model.js\";\nimport { schemaSpecPolicy } from \"./config.js\";\nimport {\n isSymbolicLink,\n isStringArray,\n portable,\n sourceInventoryPath,\n walkFiles,\n yamlRecord,\n} from \"./walk.js\";\nimport { addCounts, issue } from \"./shape.js\";\nimport { type Ingest } from \"./workspace.js\";\n\nconst CHANGE_CREATED_RE = /^\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\nexport async function inspectChangeMetadata(\n ingest: Ingest,\n dir: string,\n scope: \"active\" | \"archive\",\n fallbackSchema: string | null,\n): Promise<OpenSpecChangeMetadata> {\n const { root, unsupported } = ingest;\n const absolute = join(dir, \".openspec.yaml\");\n if (!existsSync(absolute)) {\n return { path: null, schema: fallbackSchema, skipSpecs: false, created: null, fields: [] };\n }\n const path = portable(root, absolute);\n if (await isSymbolicLink(absolute)) {\n return { path, schema: fallbackSchema, skipSpecs: false, created: null, fields: [] };\n }\n try {\n const metadata = await yamlRecord(absolute);\n let valid = true;\n const invalid = (message: string): void => {\n valid = false;\n issue(unsupported, scope, {\n code: \"openspec.change-metadata-invalid\",\n path: path,\n message: message,\n });\n };\n const schema = typeof metadata.schema === \"string\" && metadata.schema.trim() !== \"\"\n ? metadata.schema.trim()\n : null;\n if (schema === null) {\n invalid(\n \"Present .openspec.yaml metadata requires a non-empty string schema; fallback applies only when the file is absent.\",\n );\n }\n if (\"skip_specs\" in metadata && typeof metadata.skip_specs !== \"boolean\") {\n invalid(\"skip_specs must be a boolean when present.\");\n }\n if (\"created\" in metadata\n && (typeof metadata.created !== \"string\" || !CHANGE_CREATED_RE.test(metadata.created))) {\n invalid(\"created must be a string date in YYYY-MM-DD or ISO-8601 date-time form when present.\");\n }\n if (\"goal\" in metadata && (typeof metadata.goal !== \"string\" || metadata.goal.length === 0)) {\n invalid(\"goal must be a non-empty string when present.\");\n }\n if (\"affected_areas\" in metadata\n && (!isStringArray(metadata.affected_areas)\n || metadata.affected_areas.some((value) => value.length === 0))) {\n invalid(\"affected_areas must be an array of non-empty strings when present.\");\n }\n if (\"initiative\" in metadata) {\n const initiative = asRecord(metadata.initiative);\n const keys = initiative === null ? [] : Object.keys(initiative).sort(compareIds);\n const kebabId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;\n if (initiative === null\n || keys.length !== 2\n || keys[0] !== \"id\"\n || keys[1] !== \"store\"\n || typeof initiative.id !== \"string\"\n || !kebabId.test(initiative.id)\n || typeof initiative.store !== \"string\"\n || !kebabId.test(initiative.store)) {\n invalid(\"initiative must be exactly { store, id }, with both values portable kebab-case identifiers.\");\n }\n }\n return {\n path,\n schema,\n skipSpecs: valid && schema !== null && metadata.skip_specs === true,\n created: typeof metadata.created === \"string\" && CHANGE_CREATED_RE.test(metadata.created)\n ? metadata.created\n : null,\n fields: Object.keys(metadata).sort(compareIds),\n };\n } catch (error) {\n issue(unsupported, scope, {\n code: \"openspec.change-metadata-invalid\",\n path: path,\n message: `Change metadata is invalid: ${error instanceof Error ? error.message : String(error)}`,\n });\n return { path, schema: fallbackSchema, skipSpecs: false, created: null, fields: [] };\n }\n}\n\n/** One tree of change directories — `changes/` or `changes/archive/`. */\nexport interface ChangeTree {\n /** The directory the ids sit under. */\n base: string;\n ids: string[];\n /** Active findings block a migration; archive findings are diagnostics. */\n scope: \"active\" | \"archive\";\n /** The workspace schema a change inherits unless it overrides it. */\n fallbackSchema: string | null;\n /**\n * Where this tree's findings land, and the only reason the tree is a record\n * rather than four arguments: an ACTIVE finding blocks a migration while an\n * ARCHIVE one is a diagnostic over frozen history, so the two walks are the\n * same code writing to different lists. Reading the list off the `Ingest`\n * instead put an archive diagnostic among the blockers and made a corpus with\n * a specless archived change unmigratable.\n */\n findings: OpenSpecUnsupportedShape[];\n}\n\nexport async function inventoryChanges(ingest: Ingest, tree: ChangeTree): Promise<OpenSpecChange[]> {\n const { base, ids, scope, fallbackSchema, findings: unsupported } = tree;\n const { root } = ingest;\n const scoped: Ingest = { ...ingest, unsupported };\n const changes: OpenSpecChange[] = [];\n for (const id of ids) {\n const dir = join(base, id);\n const allArtifacts = await walkFiles(dir);\n const specRoot = join(dir, \"specs\");\n const allSpecs = await walkFiles(specRoot);\n const specFiles = allSpecs.filter((path) => basename(path) === \"spec.md\");\n const metadata = await inspectChangeMetadata(scoped, dir, scope, fallbackSchema);\n const counts: OpenSpecCounts = { specFiles: 0, requirements: 0, scenarios: 0 };\n const files: string[] = [];\n const specs: OpenSpecChangeSpec[] = [];\n for (const specFile of specFiles) {\n const inspected = await inspectSpecFile(scoped, specFile, \"change\", scope);\n files.push(inspected.path);\n specs.push({\n capability: portable(specRoot, dirname(specFile)),\n path: inspected.path,\n requirementNames: [...new Set(\n inspected.requirements.filter((item) => item.kind !== \"BASE\").map((item) => item.name),\n )].sort(compareIds),\n ...inspected.counts,\n });\n addCounts(counts, inspected.counts);\n }\n const schemaPolicy = await schemaSpecPolicy(root, metadata.schema);\n if (!schemaPolicy.resolved) {\n issue(unsupported, scope, {\n code: \"openspec.change-schema-unresolved\",\n path: metadata.path ?? portable(root, dir),\n message: `Change schema '${metadata.schema ?? \"<missing>\"}' cannot be resolved to a readable built-in or project schema.`,\n });\n }\n if (metadata.skipSpecs && allSpecs.length > 0) {\n issue(unsupported, scope, {\n code: \"openspec.skip-specs-with-specs\",\n path: portable(root, specRoot),\n message: \"skip_specs: true conflicts with authored content under specs/; remove the marker or review and remove the spec tree.\",\n });\n }\n const permitsNoSpecs = schemaPolicy.resolved && metadata.skipSpecs;\n if (specFiles.length === 0 && schemaPolicy.resolved && !permitsNoSpecs) {\n issue(unsupported, scope, {\n code: \"openspec.change-no-specs\",\n path: portable(root, dir),\n message: \"Change has no specs/<capability>/spec.md files and does not opt out with a valid, schema-resolved skip_specs: true marker.\",\n });\n }\n for (const file of allSpecs.filter((path) => path.endsWith(\".md\") && basename(path) !== \"spec.md\")) {\n issue(unsupported, scope, {\n code: \"openspec.nonstandard-change-spec\",\n path: portable(root, file),\n message: \"Markdown under a change specs/ tree is not named spec.md.\",\n });\n }\n changes.push({\n id,\n files,\n specs: specs.sort((a, b) => compareIds(a.capability, b.capability)),\n artifacts: allArtifacts.map((path) => portable(root, path)),\n metadata,\n ...counts,\n });\n }\n return changes;\n}\n\nexport function artifactScope(path: string): OpenSpecArtifactScope {\n if (path.startsWith(\"changes/archive/\")) return \"archive\";\n if (/^changes\\/[^/]+\\//.test(path)) return \"active\";\n if (path.startsWith(\"specs/\")) return \"living\";\n return \"workspace\";\n}\n\nexport function artifactFor(root: string, absolute: string): OpenSpecArtifact {\n const rootPath = portable(root, absolute);\n const path = sourceInventoryPath(root, absolute);\n const scope = artifactScope(rootPath);\n const pieces = rootPath.split(\"/\");\n const archived = scope === \"archive\";\n const changeId = scope === \"active\" ? pieces[1] : scope === \"archive\" ? pieces[2] : undefined;\n const deltaSpecIndex = pieces.indexOf(\"specs\");\n const capability = basename(absolute) === \"spec.md\" && deltaSpecIndex >= 0\n ? pieces.slice(deltaSpecIndex + 1, -1).join(\"/\")\n : undefined;\n let kind: OpenSpecArtifactKind = \"other\";\n let disposition: OpenSpecArtifactDisposition = \"manual-review\";\n\n // A Store checkout that keeps its planning shape at the checkout root holds\n // its own metadata inside the planning root, not beside it.\n if (path === \"@workspace/.openspec-store/store.yaml\" || rootPath === \".openspec-store/store.yaml\") {\n [kind, disposition] = [\"store-metadata\", \"record-external-planning-root\"];\n } else if (rootPath === \"config.yaml\") [kind, disposition] = [\"config\", \"translate-project-context\"];\n else if (rootPath === \"project.md\") [kind, disposition] = [\"project-context\", \"translate-project-context\"];\n else if (rootPath === \"AGENTS.md\") [kind, disposition] = [\"agent-instructions\", \"remove-after-cutover\"];\n else if (/^specs\\/.+\\/spec\\.md$/.test(rootPath)) [kind, disposition] = [\"living-spec\", \"map-requirements\"];\n else if (/^specs\\/.+\\/design\\.md$/.test(rootPath)) [kind, disposition] = [\"living-design\", \"review-as-service-adr\"];\n else if (/^changes\\/(?:archive\\/[^/]+|[^/]+)\\/\\.openspec\\.yaml$/.test(rootPath)) {\n [kind, disposition] = [\"change-metadata\", \"translate-change-metadata\"];\n } else if (basename(absolute) === \"proposal.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"proposal\", \"convert-to-intent\"];\n } else if (basename(absolute) === \"tasks.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"tasks\", \"preserve-as-legacy-checklist\"];\n } else if (basename(absolute) === \"design.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"change-design\", \"review-as-feature-adr\"];\n } else if (basename(absolute) === \"spec.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"delta-spec\", \"map-delta\"];\n } else if (/^schemas\\/[^/]+\\/schema\\.yaml$/.test(rootPath)) {\n [kind, disposition] = [\"custom-schema\", \"review-custom-workflow\"];\n } else if (/^schemas\\/[^/]+\\/templates\\//.test(rootPath)) {\n [kind, disposition] = [\"schema-template\", \"review-custom-workflow\"];\n }\n\n if (archived) disposition = \"retain-read-only\";\n return { path, scope, kind, disposition, ...(changeId ? { changeId } : {}), ...(capability ? { capability } : {}) };\n}\n\nexport function sortIssues(issues: OpenSpecUnsupportedShape[]): void {\n issues.sort((a, b) => compareIds(a.path, b.path) || compareIds(a.code, b.code));\n}\n\nexport function titleFromChangeId(id: string): string {\n const words = id.split(/[-_]+/).filter(Boolean);\n const text = words.join(\" \");\n return text === \"\" ? id : text[0]!.toUpperCase() + text.slice(1);\n}\n"]}
1
+ {"version":3,"file":"changes.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/changes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One `changes/<id>/` directory read end to end: its metadata, its spec files,\n * and every artifact it holds.\n *\n * Active and archived changes come through the same reader with a different\n * scope, because they are the same shape and only the consequences differ — an\n * archive finding is a diagnostic, an active one blocks. Keeping that in the\n * scope rather than in two readers is what stops the two drifting into two\n * ideas of what a change looks like.\n */\nimport { existsSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport { asRecord, isStringArray } from \"../../kernel/records.js\";\nimport { compareIds } from \"../../repo/entries.js\";\nimport { inspectSpecFile } from \"./shape.js\";\nimport {\n type OpenSpecArtifact,\n type OpenSpecArtifactDisposition,\n type OpenSpecArtifactKind,\n type OpenSpecArtifactScope,\n type OpenSpecChange,\n type OpenSpecChangeMetadata,\n type OpenSpecChangeSpec,\n type OpenSpecCounts,\n type OpenSpecUnsupportedShape,\n} from \"../model/model.js\";\nimport { schemaSpecPolicy } from \"./config.js\";\nimport {\n isSymbolicLink,\n portable,\n sourceInventoryPath,\n walkTree,\n yamlRecord,\n} from \"./walk.js\";\nimport { addCounts, issue } from \"./shape.js\";\nimport { type Ingest } from \"./workspace.js\";\n\n/**\n * A calendar date, optionally with a time. OpenSpec's template writes the plain\n * date, but a timestamp is still a date — and rejecting it blocked every change\n * and every capability in the workspace over one field nobody reads, fixable\n * only by editing the source migration promises not to touch.\n */\nconst CHANGE_CREATED_RE = /^\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\nexport async function inspectChangeMetadata(\n ingest: Ingest,\n dir: string,\n scope: \"active\" | \"archive\",\n fallbackSchema: string | null,\n): Promise<OpenSpecChangeMetadata> {\n const { root, unsupported } = ingest;\n const absolute = join(dir, \".openspec.yaml\");\n if (!existsSync(absolute)) {\n return { path: null, schema: fallbackSchema, skipSpecs: false, created: null, fields: [] };\n }\n const path = portable(root, absolute);\n if (await isSymbolicLink(absolute)) {\n return { path, schema: fallbackSchema, skipSpecs: false, created: null, fields: [] };\n }\n try {\n const metadata = await yamlRecord(absolute);\n let valid = true;\n const invalid = (message: string): void => {\n valid = false;\n issue(unsupported, scope, {\n code: \"openspec.change-metadata-invalid\",\n path: path,\n message: message,\n });\n };\n const schema = typeof metadata.schema === \"string\" && metadata.schema.trim() !== \"\"\n ? metadata.schema.trim()\n : null;\n if (schema === null) {\n invalid(\n \"Present .openspec.yaml metadata requires a non-empty string schema; fallback applies only when the file is absent.\",\n );\n }\n if (\"skip_specs\" in metadata && typeof metadata.skip_specs !== \"boolean\") {\n invalid(\"skip_specs must be a boolean when present.\");\n }\n if (\"created\" in metadata\n && (typeof metadata.created !== \"string\" || !CHANGE_CREATED_RE.test(metadata.created))) {\n invalid(\"created must be a string date in YYYY-MM-DD or ISO-8601 date-time form when present.\");\n }\n if (\"goal\" in metadata && (typeof metadata.goal !== \"string\" || metadata.goal.length === 0)) {\n invalid(\"goal must be a non-empty string when present.\");\n }\n if (\"affected_areas\" in metadata\n && (!isStringArray(metadata.affected_areas)\n || metadata.affected_areas.some((value) => value.length === 0))) {\n invalid(\"affected_areas must be an array of non-empty strings when present.\");\n }\n if (\"initiative\" in metadata) {\n const initiative = asRecord(metadata.initiative);\n const keys = initiative === null ? [] : Object.keys(initiative).sort(compareIds);\n const kebabId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;\n if (initiative === null\n || keys.length !== 2\n || keys[0] !== \"id\"\n || keys[1] !== \"store\"\n || typeof initiative.id !== \"string\"\n || !kebabId.test(initiative.id)\n || typeof initiative.store !== \"string\"\n || !kebabId.test(initiative.store)) {\n invalid(\"initiative must be exactly { store, id }, with both values portable kebab-case identifiers.\");\n }\n }\n return {\n path,\n schema,\n skipSpecs: valid && schema !== null && metadata.skip_specs === true,\n created: typeof metadata.created === \"string\" && CHANGE_CREATED_RE.test(metadata.created)\n ? metadata.created\n : null,\n fields: Object.keys(metadata).sort(compareIds),\n };\n } catch (error) {\n issue(unsupported, scope, {\n code: \"openspec.change-metadata-invalid\",\n path: path,\n message: `Change metadata is invalid: ${error instanceof Error ? error.message : String(error)}`,\n });\n return { path, schema: fallbackSchema, skipSpecs: false, created: null, fields: [] };\n }\n}\n\n/** One tree of change directories — `changes/` or `changes/archive/`. */\nexport interface ChangeTree {\n /** The directory the ids sit under. */\n base: string;\n ids: string[];\n /** Active findings block a migration; archive findings are diagnostics. */\n scope: \"active\" | \"archive\";\n /** The workspace schema a change inherits unless it overrides it. */\n fallbackSchema: string | null;\n /**\n * Where this tree's findings land, and the only reason the tree is a record\n * rather than four arguments: an ACTIVE finding blocks a migration while an\n * ARCHIVE one is a diagnostic over frozen history, so the two walks are the\n * same code writing to different lists. Reading the list off the `Ingest`\n * instead put an archive diagnostic among the blockers and made a corpus with\n * a specless archived change unmigratable.\n */\n findings: OpenSpecUnsupportedShape[];\n}\n\nexport async function inventoryChanges(ingest: Ingest, tree: ChangeTree): Promise<OpenSpecChange[]> {\n const { base, ids, scope, fallbackSchema, findings: unsupported } = tree;\n const { root } = ingest;\n const scoped: Ingest = { ...ingest, unsupported };\n const changes: OpenSpecChange[] = [];\n for (const id of ids) {\n const dir = join(base, id);\n const allArtifacts = (await walkTree(dir)).files;\n const specRoot = join(dir, \"specs\");\n const allSpecs = (await walkTree(specRoot)).files;\n const specFiles = allSpecs.filter((path) => basename(path) === \"spec.md\");\n const metadata = await inspectChangeMetadata(scoped, dir, scope, fallbackSchema);\n const counts: OpenSpecCounts = { specFiles: 0, requirements: 0, scenarios: 0 };\n const files: string[] = [];\n const specs: OpenSpecChangeSpec[] = [];\n for (const specFile of specFiles) {\n const inspected = await inspectSpecFile(scoped, specFile, \"change\", scope);\n files.push(inspected.path);\n specs.push({\n capability: portable(specRoot, dirname(specFile)),\n path: inspected.path,\n requirementNames: [...new Set(\n inspected.requirements.filter((item) => item.kind !== \"BASE\").map((item) => item.name),\n )].sort(compareIds),\n ...inspected.counts,\n });\n addCounts(counts, inspected.counts);\n }\n const schemaPolicy = await schemaSpecPolicy(root, metadata.schema);\n if (!schemaPolicy.resolved) {\n issue(unsupported, scope, {\n code: \"openspec.change-schema-unresolved\",\n path: metadata.path ?? portable(root, dir),\n message: `Change schema '${metadata.schema ?? \"<missing>\"}' cannot be resolved to a readable built-in or project schema.`,\n });\n }\n if (metadata.skipSpecs && allSpecs.length > 0) {\n issue(unsupported, scope, {\n code: \"openspec.skip-specs-with-specs\",\n path: portable(root, specRoot),\n message: \"skip_specs: true conflicts with authored content under specs/; remove the marker or review and remove the spec tree.\",\n });\n }\n const permitsNoSpecs = schemaPolicy.resolved && metadata.skipSpecs;\n if (specFiles.length === 0 && schemaPolicy.resolved && !permitsNoSpecs) {\n issue(unsupported, scope, {\n code: \"openspec.change-no-specs\",\n path: portable(root, dir),\n message: \"Change has no specs/<capability>/spec.md files and does not opt out with a valid, schema-resolved skip_specs: true marker.\",\n });\n }\n for (const file of allSpecs.filter((path) => path.endsWith(\".md\") && basename(path) !== \"spec.md\")) {\n issue(unsupported, scope, {\n code: \"openspec.nonstandard-change-spec\",\n path: portable(root, file),\n message: \"Markdown under a change specs/ tree is not named spec.md.\",\n });\n }\n changes.push({\n id,\n files,\n specs: specs.sort((a, b) => compareIds(a.capability, b.capability)),\n artifacts: allArtifacts.map((path) => portable(root, path)),\n metadata,\n ...counts,\n });\n }\n return changes;\n}\n\nexport function artifactScope(path: string): OpenSpecArtifactScope {\n if (path.startsWith(\"changes/archive/\")) return \"archive\";\n if (/^changes\\/[^/]+\\//.test(path)) return \"active\";\n if (path.startsWith(\"specs/\")) return \"living\";\n return \"workspace\";\n}\n\nexport function artifactFor(root: string, absolute: string): OpenSpecArtifact {\n const rootPath = portable(root, absolute);\n const path = sourceInventoryPath(root, absolute);\n const scope = artifactScope(rootPath);\n const pieces = rootPath.split(\"/\");\n const archived = scope === \"archive\";\n const changeId = scope === \"active\" ? pieces[1] : scope === \"archive\" ? pieces[2] : undefined;\n const deltaSpecIndex = pieces.indexOf(\"specs\");\n const capability = basename(absolute) === \"spec.md\" && deltaSpecIndex >= 0\n ? pieces.slice(deltaSpecIndex + 1, -1).join(\"/\")\n : undefined;\n let kind: OpenSpecArtifactKind = \"other\";\n let disposition: OpenSpecArtifactDisposition = \"manual-review\";\n\n // A Store checkout that keeps its planning shape at the checkout root holds\n // its own metadata inside the planning root, not beside it.\n if (path === \"@workspace/.openspec-store/store.yaml\" || rootPath === \".openspec-store/store.yaml\") {\n [kind, disposition] = [\"store-metadata\", \"record-external-planning-root\"];\n } else if (rootPath === \"config.yaml\") [kind, disposition] = [\"config\", \"translate-project-context\"];\n else if (rootPath === \"project.md\") [kind, disposition] = [\"project-context\", \"translate-project-context\"];\n else if (rootPath === \"AGENTS.md\") [kind, disposition] = [\"agent-instructions\", \"remove-after-cutover\"];\n else if (/^specs\\/.+\\/spec\\.md$/.test(rootPath)) [kind, disposition] = [\"living-spec\", \"map-requirements\"];\n else if (/^specs\\/.+\\/design\\.md$/.test(rootPath)) [kind, disposition] = [\"living-design\", \"review-as-service-adr\"];\n else if (/^changes\\/(?:archive\\/[^/]+|[^/]+)\\/\\.openspec\\.yaml$/.test(rootPath)) {\n [kind, disposition] = [\"change-metadata\", \"translate-change-metadata\"];\n } else if (basename(absolute) === \"proposal.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"proposal\", \"convert-to-intent\"];\n } else if (basename(absolute) === \"tasks.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"tasks\", \"preserve-as-legacy-checklist\"];\n } else if (basename(absolute) === \"design.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"change-design\", \"review-as-feature-adr\"];\n } else if (basename(absolute) === \"spec.md\" && (scope === \"active\" || scope === \"archive\")) {\n [kind, disposition] = [\"delta-spec\", \"map-delta\"];\n } else if (/^schemas\\/[^/]+\\/schema\\.yaml$/.test(rootPath)) {\n [kind, disposition] = [\"custom-schema\", \"review-custom-workflow\"];\n } else if (/^schemas\\/[^/]+\\/templates\\//.test(rootPath)) {\n [kind, disposition] = [\"schema-template\", \"review-custom-workflow\"];\n }\n\n if (archived) disposition = \"retain-read-only\";\n return { path, scope, kind, disposition, ...(changeId ? { changeId } : {}), ...(capability ? { capability } : {}) };\n}\n\nexport function sortIssues(issues: OpenSpecUnsupportedShape[]): void {\n issues.sort((a, b) => compareIds(a.path, b.path) || compareIds(a.code, b.code));\n}\n\nexport function titleFromChangeId(id: string): string {\n const words = id.split(/[-_]+/).filter(Boolean);\n const text = words.join(\" \");\n return text === \"\" ? id : text[0]!.toUpperCase() + text.slice(1);\n}\n"]}
@@ -9,11 +9,12 @@
9
9
  */
10
10
  import { existsSync } from "node:fs";
11
11
  import { lstat, readdir, realpath } from "node:fs/promises";
12
- import { isAbsolute, join, relative, sep } from "node:path";
13
- import { asRecord } from "../../kernel/records.js";
12
+ import { join } from "node:path";
13
+ import { isPathInside } from "../../kernel/path-safety.js";
14
+ import { asRecord, isStringArray } from "../../kernel/records.js";
14
15
  import { compareIds } from "../../repo/entries.js";
15
16
  import {} from "../model/model.js";
16
- import { isSymbolicLink, isStringArray, yamlRecord } from "./walk.js";
17
+ import { isSymbolicLink, yamlRecord } from "./walk.js";
17
18
  import { issue } from "./shape.js";
18
19
  export async function inspectConfig(root, unsupported) {
19
20
  const path = join(root, "config.yaml");
@@ -51,16 +52,6 @@ export async function inspectConfig(root, unsupported) {
51
52
  };
52
53
  }
53
54
  }
54
- /**
55
- * A calendar date, optionally with a time. OpenSpec's template writes the plain
56
- * date, but a timestamp is still a date — and rejecting it blocked every change
57
- * and every capability in the workspace over one field nobody reads, fixable
58
- * only by editing the source migration promises not to touch.
59
- */
60
- export function isContainedPath(parent, candidate) {
61
- const rel = relative(parent, candidate);
62
- return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
63
- }
64
55
  /**
65
56
  * Resolve only a schema that OpenSpec could enumerate as one direct, portable
66
57
  * member of this workspace's schemas/ directory. Constructing a path from the
@@ -101,9 +92,9 @@ export async function registeredProjectSchemaPath(root, schema) {
101
92
  realpath(schemaDir),
102
93
  realpath(schemaFile),
103
94
  ]);
104
- if (!isContainedPath(canonicalRoot, canonicalSchemasRoot)
105
- || !isContainedPath(canonicalSchemasRoot, canonicalSchemaDir)
106
- || !isContainedPath(canonicalSchemaDir, canonicalSchemaFile)) {
95
+ if (!isPathInside(canonicalRoot, canonicalSchemasRoot)
96
+ || !isPathInside(canonicalSchemasRoot, canonicalSchemaDir)
97
+ || !isPathInside(canonicalSchemaDir, canonicalSchemaFile)) {
107
98
  return { registered: true, path: null };
108
99
  }
109
100
  return { registered: true, path: schemaFile };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAA6D,MAAM,mBAAmB,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAY,EACZ,WAAuC;IAEvC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAM,cAAc,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;YAChE,KAAK,EAAE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;YAC7D,UAAU,EAAE,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE;YAC9E,aAAa,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;YACxE,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC1C,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;gBAC/F,CAAC,CAAC,EAAE;SACP,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,KAAK,CAAC,WAAW,EAAE,WAAW,EAAE;YAC9B,IAAI,EAAE,yBAAyB;YAC/B,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,oCAAoC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;SACtG,CAAC,CAAC;QACH,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,KAAK;YACjB,aAAa,EAAE,EAAE;YACjB,UAAU,EAAE,EAAE;SACf,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AAEH,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,SAAiB;IAC/D,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACxC,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,IAAY,EACZ,MAAc;IAEd,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC;IACxD,MAAM,eAAe,GAAG,yCAAyC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACpF,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,MAAM,CAAC;WAC3C,MAAM,KAAK,GAAG;WACd,MAAM,KAAK,IAAI;WACf,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;WACpB,eAAe,EAAE,CAAC;QACrB,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,CAAC;QAC7C,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,IAAI,WAAW,CAAC,cAAc,EAAE;YAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACzG,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QAClE,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACvE,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,UAAU,CAAC,cAAc,EAAE;YAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAEtG,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAClD,MAAM,CAAC,aAAa,EAAE,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACjG,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,aAAa,CAAC,cAAc,EAAE;eAC7D,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,cAAc,CAAC,cAAc,EAAE,EAAE,CAAC;YACjE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC1C,CAAC;QAED,MAAM,CAAC,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvG,QAAQ,CAAC,IAAI,CAAC;YACd,QAAQ,CAAC,WAAW,CAAC;YACrB,QAAQ,CAAC,SAAS,CAAC;YACnB,QAAQ,CAAC,UAAU,CAAC;SACrB,CAAC,CAAC;QACH,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE,oBAAoB,CAAC;eACpD,CAAC,eAAe,CAAC,oBAAoB,EAAE,kBAAkB,CAAC;eAC1D,CAAC,eAAe,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EAAE,CAAC;YAC/D,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC1C,CAAC;QACD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAiC;IACnE,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;WAC9D,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ;WACpC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;WACnC,QAAQ,CAAC,OAAO,IAAI,CAAC;WACrB,CAAC,aAAa,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC;WACvE,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;WAClC,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,SAAS,GAA8C,EAAE,CAAC;IAChE,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,QAAQ,KAAK,IAAI;eAChB,OAAO,QAAQ,CAAC,EAAE,KAAK,QAAQ;eAC/B,QAAQ,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC;eACxB,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ;eACtC,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;eAC/B,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ;eACxC,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ;eACrC,QAAQ,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;eAC9B,CAAC,aAAa,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC;eACvE,CAAC,UAAU,IAAI,QAAQ,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACnE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,SAAS,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,IAAI;eACb,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC;eAC9B,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;eAC3B,CAAC,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC;eAChF,CAAC,aAAa,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,EAAE,CAAC;YACvE,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;QACvC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvB,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAE7G,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAW,EAAE;QACpC,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QAClC,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;QACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,IAAI,EAAE,EAAE,CAAC;YACtD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;gBAAE,OAAO,KAAK,CAAC;QACvC,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAY,EACZ,MAAqB;IAErB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAChD,MAAM,aAAa,GAAG,MAAM,2BAA2B,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtE,0EAA0E;IAC1E,4EAA4E;IAC5E,IAAI,CAAC,aAAa,CAAC,UAAU;QAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,aAAa,EAAE,CAAC;IAC7E,IAAI,aAAa,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC5D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACtD,OAAO,EAAE,QAAQ,EAAE,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC","sourcesContent":["/**\n * `openspec/config.yaml` and the project schemas it can point at.\n *\n * The schema policy is here rather than with the change reader because it is a\n * WORKSPACE fact: a change inherits its schema from the config unless it\n * overrides it, so a per-change reader that resolved schemas itself would\n * resolve the same registered file once per change and could disagree with\n * itself about whether it validates.\n */\nimport { existsSync } from \"node:fs\";\nimport { lstat, readdir, realpath } from \"node:fs/promises\";\nimport { isAbsolute, join, relative, sep } from \"node:path\";\nimport { asRecord } from \"../../kernel/records.js\";\nimport { compareIds } from \"../../repo/entries.js\";\nimport { type OpenSpecConfigSummary, type OpenSpecUnsupportedShape } from \"../model/model.js\";\nimport { isSymbolicLink, isStringArray, yamlRecord } from \"./walk.js\";\nimport { issue } from \"./shape.js\";\n\nexport async function inspectConfig(\n root: string,\n unsupported: OpenSpecUnsupportedShape[],\n): Promise<OpenSpecConfigSummary | null> {\n const path = join(root, \"config.yaml\");\n if (!existsSync(path)) return null;\n if (await isSymbolicLink(path)) return null;\n try {\n const config = await yamlRecord(path);\n const rules = asRecord(config.rules);\n return {\n path: \"config.yaml\",\n schema: typeof config.schema === \"string\" ? config.schema : null,\n store: typeof config.store === \"string\" ? config.store : null,\n hasContext: typeof config.context === \"string\" && config.context.trim() !== \"\",\n ruleArtifacts: rules === null ? [] : Object.keys(rules).sort(compareIds),\n references: Array.isArray(config.references)\n ? config.references.filter((item): item is string => typeof item === \"string\").sort(compareIds)\n : [],\n };\n } catch (error) {\n issue(unsupported, \"workspace\", {\n code: \"openspec.config-invalid\",\n path: \"config.yaml\",\n message: `OpenSpec config.yaml is invalid: ${error instanceof Error ? error.message : String(error)}`,\n });\n return {\n path: \"config.yaml\",\n schema: null,\n store: null,\n hasContext: false,\n ruleArtifacts: [],\n references: [],\n };\n }\n}\n\n/**\n * A calendar date, optionally with a time. OpenSpec's template writes the plain\n * date, but a timestamp is still a date — and rejecting it blocked every change\n * and every capability in the workspace over one field nobody reads, fixable\n * only by editing the source migration promises not to touch.\n */\n\nexport function isContainedPath(parent: string, candidate: string): boolean {\n const rel = relative(parent, candidate);\n return rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\n/**\n * Resolve only a schema that OpenSpec could enumerate as one direct, portable\n * member of this workspace's schemas/ directory. Constructing a path from the\n * metadata value first would let `../` (or a platform-specific separator) read\n * bytes outside the inventoried tree and then authorize `skip_specs` with them.\n */\nexport async function registeredProjectSchemaPath(\n root: string,\n schema: string,\n): Promise<{ registered: boolean; path: string | null }> {\n const windowsStem = schema.split(\".\")[0]!.toUpperCase();\n const windowsReserved = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(windowsStem);\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(schema)\n || schema === \".\"\n || schema === \"..\"\n || schema.endsWith(\".\")\n || windowsReserved) {\n return { registered: false, path: null };\n }\n\n const schemasRoot = join(root, \"schemas\");\n try {\n const schemasStat = await lstat(schemasRoot);\n if (!schemasStat.isDirectory() || schemasStat.isSymbolicLink()) return { registered: false, path: null };\n const entries = await readdir(schemasRoot, { withFileTypes: true });\n const registered = entries.find((entry) => entry.name === schema);\n if (registered === undefined) return { registered: false, path: null };\n if (!registered.isDirectory() || registered.isSymbolicLink()) return { registered: true, path: null };\n\n const schemaDir = join(schemasRoot, registered.name);\n const schemaFile = join(schemaDir, \"schema.yaml\");\n const [schemaDirStat, schemaFileStat] = await Promise.all([lstat(schemaDir), lstat(schemaFile)]);\n if (!schemaDirStat.isDirectory() || schemaDirStat.isSymbolicLink()\n || !schemaFileStat.isFile() || schemaFileStat.isSymbolicLink()) {\n return { registered: true, path: null };\n }\n\n const [canonicalRoot, canonicalSchemasRoot, canonicalSchemaDir, canonicalSchemaFile] = await Promise.all([\n realpath(root),\n realpath(schemasRoot),\n realpath(schemaDir),\n realpath(schemaFile),\n ]);\n if (!isContainedPath(canonicalRoot, canonicalSchemasRoot)\n || !isContainedPath(canonicalSchemasRoot, canonicalSchemaDir)\n || !isContainedPath(canonicalSchemaDir, canonicalSchemaFile)) {\n return { registered: true, path: null };\n }\n return { registered: true, path: schemaFile };\n } catch {\n return { registered: false, path: null };\n }\n}\n\nexport function validArtifactSchema(document: Record<string, unknown>): boolean {\n if (typeof document.name !== \"string\" || document.name.length === 0\n || typeof document.version !== \"number\"\n || !Number.isInteger(document.version)\n || document.version <= 0\n || (\"description\" in document && typeof document.description !== \"string\")\n || !Array.isArray(document.artifacts)\n || document.artifacts.length === 0) {\n return false;\n }\n\n const artifacts: Array<{ id: string; requires: string[] }> = [];\n for (const value of document.artifacts) {\n const artifact = asRecord(value);\n if (artifact === null\n || typeof artifact.id !== \"string\"\n || artifact.id.length === 0\n || typeof artifact.generates !== \"string\"\n || artifact.generates.length === 0\n || typeof artifact.description !== \"string\"\n || typeof artifact.template !== \"string\"\n || artifact.template.length === 0\n || (\"instruction\" in artifact && typeof artifact.instruction !== \"string\")\n || (\"requires\" in artifact && !isStringArray(artifact.requires))) {\n return false;\n }\n artifacts.push({\n id: artifact.id,\n requires: isStringArray(artifact.requires) ? artifact.requires : [],\n });\n }\n\n if (\"apply\" in document) {\n const apply = asRecord(document.apply);\n if (apply === null\n || !isStringArray(apply.requires)\n || apply.requires.length === 0\n || (\"tracks\" in apply && apply.tracks !== null && typeof apply.tracks !== \"string\")\n || (\"instruction\" in apply && typeof apply.instruction !== \"string\")) {\n return false;\n }\n }\n\n const ids = new Set<string>();\n for (const artifact of artifacts) {\n if (ids.has(artifact.id)) return false;\n ids.add(artifact.id);\n }\n if (artifacts.some((artifact) => artifact.requires.some((dependency) => !ids.has(dependency)))) return false;\n\n const byId = new Map(artifacts.map((artifact) => [artifact.id, artifact]));\n const complete = new Set<string>();\n const visiting = new Set<string>();\n const visit = (id: string): boolean => {\n if (complete.has(id)) return true;\n if (visiting.has(id)) return false;\n visiting.add(id);\n for (const dependency of byId.get(id)?.requires ?? []) {\n if (!visit(dependency)) return false;\n }\n visiting.delete(id);\n complete.add(id);\n return true;\n };\n return artifacts.every((artifact) => visit(artifact.id));\n}\n\nexport async function schemaSpecPolicy(\n root: string,\n schema: string | null,\n): Promise<{ resolved: boolean }> {\n if (schema === null) return { resolved: false };\n const projectSchema = await registeredProjectSchemaPath(root, schema);\n // A project-local entry shadows the known built-in, but migration refuses\n // unsafe/symlinked entries instead of following them outside the inventory.\n if (!projectSchema.registered) return { resolved: schema === \"spec-driven\" };\n if (projectSchema.path === null) return { resolved: false };\n try {\n const document = await yamlRecord(projectSchema.path);\n return { resolved: validArtifactSchema(document) };\n } catch {\n return { resolved: false };\n }\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../src/core/openspec/scan/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `openspec/config.yaml` and the project schemas it can point at.\n *\n * The schema policy is here rather than with the change reader because it is a\n * WORKSPACE fact: a change inherits its schema from the config unless it\n * overrides it, so a per-change reader that resolved schemas itself would\n * resolve the same registered file once per change and could disagree with\n * itself about whether it validates.\n */\nimport { existsSync } from \"node:fs\";\nimport { lstat, readdir, realpath } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { isPathInside } from \"../../kernel/path-safety.js\";\nimport { asRecord, isStringArray } from \"../../kernel/records.js\";\nimport { compareIds } from \"../../repo/entries.js\";\nimport { type OpenSpecConfigSummary, type OpenSpecUnsupportedShape } from \"../model/model.js\";\nimport { isSymbolicLink, yamlRecord } from \"./walk.js\";\nimport { issue } from \"./shape.js\";\n\nexport async function inspectConfig(\n root: string,\n unsupported: OpenSpecUnsupportedShape[],\n): Promise<OpenSpecConfigSummary | null> {\n const path = join(root, \"config.yaml\");\n if (!existsSync(path)) return null;\n if (await isSymbolicLink(path)) return null;\n try {\n const config = await yamlRecord(path);\n const rules = asRecord(config.rules);\n return {\n path: \"config.yaml\",\n schema: typeof config.schema === \"string\" ? config.schema : null,\n store: typeof config.store === \"string\" ? config.store : null,\n hasContext: typeof config.context === \"string\" && config.context.trim() !== \"\",\n ruleArtifacts: rules === null ? [] : Object.keys(rules).sort(compareIds),\n references: Array.isArray(config.references)\n ? config.references.filter((item): item is string => typeof item === \"string\").sort(compareIds)\n : [],\n };\n } catch (error) {\n issue(unsupported, \"workspace\", {\n code: \"openspec.config-invalid\",\n path: \"config.yaml\",\n message: `OpenSpec config.yaml is invalid: ${error instanceof Error ? error.message : String(error)}`,\n });\n return {\n path: \"config.yaml\",\n schema: null,\n store: null,\n hasContext: false,\n ruleArtifacts: [],\n references: [],\n };\n }\n}\n\n/**\n * Resolve only a schema that OpenSpec could enumerate as one direct, portable\n * member of this workspace's schemas/ directory. Constructing a path from the\n * metadata value first would let `../` (or a platform-specific separator) read\n * bytes outside the inventoried tree and then authorize `skip_specs` with them.\n */\nexport async function registeredProjectSchemaPath(\n root: string,\n schema: string,\n): Promise<{ registered: boolean; path: string | null }> {\n const windowsStem = schema.split(\".\")[0]!.toUpperCase();\n const windowsReserved = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(windowsStem);\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(schema)\n || schema === \".\"\n || schema === \"..\"\n || schema.endsWith(\".\")\n || windowsReserved) {\n return { registered: false, path: null };\n }\n\n const schemasRoot = join(root, \"schemas\");\n try {\n const schemasStat = await lstat(schemasRoot);\n if (!schemasStat.isDirectory() || schemasStat.isSymbolicLink()) return { registered: false, path: null };\n const entries = await readdir(schemasRoot, { withFileTypes: true });\n const registered = entries.find((entry) => entry.name === schema);\n if (registered === undefined) return { registered: false, path: null };\n if (!registered.isDirectory() || registered.isSymbolicLink()) return { registered: true, path: null };\n\n const schemaDir = join(schemasRoot, registered.name);\n const schemaFile = join(schemaDir, \"schema.yaml\");\n const [schemaDirStat, schemaFileStat] = await Promise.all([lstat(schemaDir), lstat(schemaFile)]);\n if (!schemaDirStat.isDirectory() || schemaDirStat.isSymbolicLink()\n || !schemaFileStat.isFile() || schemaFileStat.isSymbolicLink()) {\n return { registered: true, path: null };\n }\n\n const [canonicalRoot, canonicalSchemasRoot, canonicalSchemaDir, canonicalSchemaFile] = await Promise.all([\n realpath(root),\n realpath(schemasRoot),\n realpath(schemaDir),\n realpath(schemaFile),\n ]);\n if (!isPathInside(canonicalRoot, canonicalSchemasRoot)\n || !isPathInside(canonicalSchemasRoot, canonicalSchemaDir)\n || !isPathInside(canonicalSchemaDir, canonicalSchemaFile)) {\n return { registered: true, path: null };\n }\n return { registered: true, path: schemaFile };\n } catch {\n return { registered: false, path: null };\n }\n}\n\nexport function validArtifactSchema(document: Record<string, unknown>): boolean {\n if (typeof document.name !== \"string\" || document.name.length === 0\n || typeof document.version !== \"number\"\n || !Number.isInteger(document.version)\n || document.version <= 0\n || (\"description\" in document && typeof document.description !== \"string\")\n || !Array.isArray(document.artifacts)\n || document.artifacts.length === 0) {\n return false;\n }\n\n const artifacts: Array<{ id: string; requires: string[] }> = [];\n for (const value of document.artifacts) {\n const artifact = asRecord(value);\n if (artifact === null\n || typeof artifact.id !== \"string\"\n || artifact.id.length === 0\n || typeof artifact.generates !== \"string\"\n || artifact.generates.length === 0\n || typeof artifact.description !== \"string\"\n || typeof artifact.template !== \"string\"\n || artifact.template.length === 0\n || (\"instruction\" in artifact && typeof artifact.instruction !== \"string\")\n || (\"requires\" in artifact && !isStringArray(artifact.requires))) {\n return false;\n }\n artifacts.push({\n id: artifact.id,\n requires: isStringArray(artifact.requires) ? artifact.requires : [],\n });\n }\n\n if (\"apply\" in document) {\n const apply = asRecord(document.apply);\n if (apply === null\n || !isStringArray(apply.requires)\n || apply.requires.length === 0\n || (\"tracks\" in apply && apply.tracks !== null && typeof apply.tracks !== \"string\")\n || (\"instruction\" in apply && typeof apply.instruction !== \"string\")) {\n return false;\n }\n }\n\n const ids = new Set<string>();\n for (const artifact of artifacts) {\n if (ids.has(artifact.id)) return false;\n ids.add(artifact.id);\n }\n if (artifacts.some((artifact) => artifact.requires.some((dependency) => !ids.has(dependency)))) return false;\n\n const byId = new Map(artifacts.map((artifact) => [artifact.id, artifact]));\n const complete = new Set<string>();\n const visiting = new Set<string>();\n const visit = (id: string): boolean => {\n if (complete.has(id)) return true;\n if (visiting.has(id)) return false;\n visiting.add(id);\n for (const dependency of byId.get(id)?.requires ?? []) {\n if (!visit(dependency)) return false;\n }\n visiting.delete(id);\n complete.add(id);\n return true;\n };\n return artifacts.every((artifact) => visit(artifact.id));\n}\n\nexport async function schemaSpecPolicy(\n root: string,\n schema: string | null,\n): Promise<{ resolved: boolean }> {\n if (schema === null) return { resolved: false };\n const projectSchema = await registeredProjectSchemaPath(root, schema);\n // A project-local entry shadows the known built-in, but migration refuses\n // unsafe/symlinked entries instead of following them outside the inventory.\n if (!projectSchema.registered) return { resolved: schema === \"spec-driven\" };\n if (projectSchema.path === null) return { resolved: false };\n try {\n const document = await yamlRecord(projectSchema.path);\n return { resolved: validArtifactSchema(document) };\n } catch {\n return { resolved: false };\n }\n}\n"]}