@software-dlc/cli 0.157.2

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 (701) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +105 -0
  3. package/README.md +31 -0
  4. package/dist/checkout.d.ts +43 -0
  5. package/dist/checkout.d.ts.map +1 -0
  6. package/dist/checkout.js +92 -0
  7. package/dist/checkout.js.map +1 -0
  8. package/dist/cli-arguments.d.ts +13 -0
  9. package/dist/cli-arguments.d.ts.map +1 -0
  10. package/dist/cli-arguments.js +67 -0
  11. package/dist/cli-arguments.js.map +1 -0
  12. package/dist/cli-branch-history.d.ts +48 -0
  13. package/dist/cli-branch-history.d.ts.map +1 -0
  14. package/dist/cli-branch-history.js +319 -0
  15. package/dist/cli-branch-history.js.map +1 -0
  16. package/dist/cli-branches.d.ts +22 -0
  17. package/dist/cli-branches.d.ts.map +1 -0
  18. package/dist/cli-branches.js +59 -0
  19. package/dist/cli-branches.js.map +1 -0
  20. package/dist/cli-deploy.d.ts +14 -0
  21. package/dist/cli-deploy.d.ts.map +1 -0
  22. package/dist/cli-deploy.js +164 -0
  23. package/dist/cli-deploy.js.map +1 -0
  24. package/dist/cli-health.d.ts +3 -0
  25. package/dist/cli-health.d.ts.map +1 -0
  26. package/dist/cli-health.js +73 -0
  27. package/dist/cli-health.js.map +1 -0
  28. package/dist/cli-help.d.ts +21 -0
  29. package/dist/cli-help.d.ts.map +1 -0
  30. package/dist/cli-help.js +176 -0
  31. package/dist/cli-help.js.map +1 -0
  32. package/dist/cli-login.d.ts +76 -0
  33. package/dist/cli-login.d.ts.map +1 -0
  34. package/dist/cli-login.js +634 -0
  35. package/dist/cli-login.js.map +1 -0
  36. package/dist/cli-operation.d.ts +2 -0
  37. package/dist/cli-operation.d.ts.map +1 -0
  38. package/dist/cli-operation.js +66 -0
  39. package/dist/cli-operation.js.map +1 -0
  40. package/dist/cli-org.d.ts +12 -0
  41. package/dist/cli-org.d.ts.map +1 -0
  42. package/dist/cli-org.js +218 -0
  43. package/dist/cli-org.js.map +1 -0
  44. package/dist/cli-rebuild.d.ts +20 -0
  45. package/dist/cli-rebuild.d.ts.map +1 -0
  46. package/dist/cli-rebuild.js +29 -0
  47. package/dist/cli-rebuild.js.map +1 -0
  48. package/dist/cli-reset.d.ts +27 -0
  49. package/dist/cli-reset.d.ts.map +1 -0
  50. package/dist/cli-reset.js +44 -0
  51. package/dist/cli-reset.js.map +1 -0
  52. package/dist/cli-snapshot.d.ts +398 -0
  53. package/dist/cli-snapshot.d.ts.map +1 -0
  54. package/dist/cli-snapshot.js +45 -0
  55. package/dist/cli-snapshot.js.map +1 -0
  56. package/dist/cli-task-context.d.ts +3 -0
  57. package/dist/cli-task-context.d.ts.map +1 -0
  58. package/dist/cli-task-context.js +112 -0
  59. package/dist/cli-task-context.js.map +1 -0
  60. package/dist/cli.d.ts +3 -0
  61. package/dist/cli.d.ts.map +1 -0
  62. package/dist/cli.js +1004 -0
  63. package/dist/cli.js.map +1 -0
  64. package/dist/graph-packages-lock.d.ts +169 -0
  65. package/dist/graph-packages-lock.d.ts.map +1 -0
  66. package/dist/graph-packages-lock.js +503 -0
  67. package/dist/graph-packages-lock.js.map +1 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +2 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/local/aggregate-preflight.d.ts +12 -0
  73. package/dist/local/aggregate-preflight.d.ts.map +1 -0
  74. package/dist/local/aggregate-preflight.js +22 -0
  75. package/dist/local/aggregate-preflight.js.map +1 -0
  76. package/dist/local/allow-mcp.d.ts +77 -0
  77. package/dist/local/allow-mcp.d.ts.map +1 -0
  78. package/dist/local/allow-mcp.js +394 -0
  79. package/dist/local/allow-mcp.js.map +1 -0
  80. package/dist/local/api-client.d.ts +15 -0
  81. package/dist/local/api-client.d.ts.map +1 -0
  82. package/dist/local/api-client.js +87 -0
  83. package/dist/local/api-client.js.map +1 -0
  84. package/dist/local/app-contract-readers.d.ts +229 -0
  85. package/dist/local/app-contract-readers.d.ts.map +1 -0
  86. package/dist/local/app-contract-readers.js +450 -0
  87. package/dist/local/app-contract-readers.js.map +1 -0
  88. package/dist/local/app-org-reader.d.ts +21 -0
  89. package/dist/local/app-org-reader.d.ts.map +1 -0
  90. package/dist/local/app-org-reader.js +66 -0
  91. package/dist/local/app-org-reader.js.map +1 -0
  92. package/dist/local/app-shell-readers.d.ts +96 -0
  93. package/dist/local/app-shell-readers.d.ts.map +1 -0
  94. package/dist/local/app-shell-readers.js +381 -0
  95. package/dist/local/app-shell-readers.js.map +1 -0
  96. package/dist/local/app-tasks-read.d.ts +202 -0
  97. package/dist/local/app-tasks-read.d.ts.map +1 -0
  98. package/dist/local/app-tasks-read.js +290 -0
  99. package/dist/local/app-tasks-read.js.map +1 -0
  100. package/dist/local/app-worktrees.d.ts +69 -0
  101. package/dist/local/app-worktrees.d.ts.map +1 -0
  102. package/dist/local/app-worktrees.js +554 -0
  103. package/dist/local/app-worktrees.js.map +1 -0
  104. package/dist/local/base-sync-manifest.d.ts +12 -0
  105. package/dist/local/base-sync-manifest.d.ts.map +1 -0
  106. package/dist/local/base-sync-manifest.js +57 -0
  107. package/dist/local/base-sync-manifest.js.map +1 -0
  108. package/dist/local/blast-radius.d.ts +191 -0
  109. package/dist/local/blast-radius.d.ts.map +1 -0
  110. package/dist/local/blast-radius.js +585 -0
  111. package/dist/local/blast-radius.js.map +1 -0
  112. package/dist/local/canonical.d.ts +2 -0
  113. package/dist/local/canonical.d.ts.map +1 -0
  114. package/dist/local/canonical.js +2 -0
  115. package/dist/local/canonical.js.map +1 -0
  116. package/dist/local/config.d.ts +22 -0
  117. package/dist/local/config.d.ts.map +1 -0
  118. package/dist/local/config.js +57 -0
  119. package/dist/local/config.js.map +1 -0
  120. package/dist/local/document-commands.d.ts +163 -0
  121. package/dist/local/document-commands.d.ts.map +1 -0
  122. package/dist/local/document-commands.js +620 -0
  123. package/dist/local/document-commands.js.map +1 -0
  124. package/dist/local/document-embeds.d.ts +21 -0
  125. package/dist/local/document-embeds.d.ts.map +1 -0
  126. package/dist/local/document-embeds.js +38 -0
  127. package/dist/local/document-embeds.js.map +1 -0
  128. package/dist/local/environment-context.d.ts +39 -0
  129. package/dist/local/environment-context.d.ts.map +1 -0
  130. package/dist/local/environment-context.js +193 -0
  131. package/dist/local/environment-context.js.map +1 -0
  132. package/dist/local/environment-exclude.d.ts +7 -0
  133. package/dist/local/environment-exclude.d.ts.map +1 -0
  134. package/dist/local/environment-exclude.js +82 -0
  135. package/dist/local/environment-exclude.js.map +1 -0
  136. package/dist/local/environment-sidecar.d.ts +126 -0
  137. package/dist/local/environment-sidecar.d.ts.map +1 -0
  138. package/dist/local/environment-sidecar.js +442 -0
  139. package/dist/local/environment-sidecar.js.map +1 -0
  140. package/dist/local/environment-switch.d.ts +22 -0
  141. package/dist/local/environment-switch.d.ts.map +1 -0
  142. package/dist/local/environment-switch.js +385 -0
  143. package/dist/local/environment-switch.js.map +1 -0
  144. package/dist/local/exact-remote-projection.d.ts +17 -0
  145. package/dist/local/exact-remote-projection.d.ts.map +1 -0
  146. package/dist/local/exact-remote-projection.js +53 -0
  147. package/dist/local/exact-remote-projection.js.map +1 -0
  148. package/dist/local/experimental/path-object-store.d.ts +66 -0
  149. package/dist/local/experimental/path-object-store.d.ts.map +1 -0
  150. package/dist/local/experimental/path-object-store.js +135 -0
  151. package/dist/local/experimental/path-object-store.js.map +1 -0
  152. package/dist/local/external-worktree-binding.d.ts +21 -0
  153. package/dist/local/external-worktree-binding.d.ts.map +1 -0
  154. package/dist/local/external-worktree-binding.js +56 -0
  155. package/dist/local/external-worktree-binding.js.map +1 -0
  156. package/dist/local/folder-members.d.ts +34 -0
  157. package/dist/local/folder-members.d.ts.map +1 -0
  158. package/dist/local/folder-members.js +168 -0
  159. package/dist/local/folder-members.js.map +1 -0
  160. package/dist/local/git-ref-authority.d.ts +11 -0
  161. package/dist/local/git-ref-authority.d.ts.map +1 -0
  162. package/dist/local/git-ref-authority.js +76 -0
  163. package/dist/local/git-ref-authority.js.map +1 -0
  164. package/dist/local/git.d.ts +88 -0
  165. package/dist/local/git.d.ts.map +1 -0
  166. package/dist/local/git.js +413 -0
  167. package/dist/local/git.js.map +1 -0
  168. package/dist/local/graph-read.d.ts +168 -0
  169. package/dist/local/graph-read.d.ts.map +1 -0
  170. package/dist/local/graph-read.js +360 -0
  171. package/dist/local/graph-read.js.map +1 -0
  172. package/dist/local/graph-sync.d.ts +39 -0
  173. package/dist/local/graph-sync.d.ts.map +1 -0
  174. package/dist/local/graph-sync.js +186 -0
  175. package/dist/local/graph-sync.js.map +1 -0
  176. package/dist/local/guide-commands.d.ts +79 -0
  177. package/dist/local/guide-commands.d.ts.map +1 -0
  178. package/dist/local/guide-commands.js +247 -0
  179. package/dist/local/guide-commands.js.map +1 -0
  180. package/dist/local/health-files.d.ts +24 -0
  181. package/dist/local/health-files.d.ts.map +1 -0
  182. package/dist/local/health-files.js +133 -0
  183. package/dist/local/health-files.js.map +1 -0
  184. package/dist/local/health-inspection.d.ts +12 -0
  185. package/dist/local/health-inspection.d.ts.map +1 -0
  186. package/dist/local/health-inspection.js +407 -0
  187. package/dist/local/health-inspection.js.map +1 -0
  188. package/dist/local/health-policy.d.ts +11 -0
  189. package/dist/local/health-policy.d.ts.map +1 -0
  190. package/dist/local/health-policy.js +257 -0
  191. package/dist/local/health-policy.js.map +1 -0
  192. package/dist/local/health-projection.d.ts +14 -0
  193. package/dist/local/health-projection.d.ts.map +1 -0
  194. package/dist/local/health-projection.js +102 -0
  195. package/dist/local/health-projection.js.map +1 -0
  196. package/dist/local/health-schema.d.ts +338 -0
  197. package/dist/local/health-schema.d.ts.map +1 -0
  198. package/dist/local/health-schema.js +213 -0
  199. package/dist/local/health-schema.js.map +1 -0
  200. package/dist/local/health-store.d.ts +61 -0
  201. package/dist/local/health-store.d.ts.map +1 -0
  202. package/dist/local/health-store.js +218 -0
  203. package/dist/local/health-store.js.map +1 -0
  204. package/dist/local/index.d.ts +66 -0
  205. package/dist/local/index.d.ts.map +1 -0
  206. package/dist/local/index.js +66 -0
  207. package/dist/local/index.js.map +1 -0
  208. package/dist/local/journey-commands.d.ts +65 -0
  209. package/dist/local/journey-commands.d.ts.map +1 -0
  210. package/dist/local/journey-commands.js +138 -0
  211. package/dist/local/journey-commands.js.map +1 -0
  212. package/dist/local/local-org-store.d.ts +46 -0
  213. package/dist/local/local-org-store.d.ts.map +1 -0
  214. package/dist/local/local-org-store.js +132 -0
  215. package/dist/local/local-org-store.js.map +1 -0
  216. package/dist/local/local-org.d.ts +389 -0
  217. package/dist/local/local-org.d.ts.map +1 -0
  218. package/dist/local/local-org.js +855 -0
  219. package/dist/local/local-org.js.map +1 -0
  220. package/dist/local/media-upload.d.ts +5 -0
  221. package/dist/local/media-upload.d.ts.map +1 -0
  222. package/dist/local/media-upload.js +41 -0
  223. package/dist/local/media-upload.js.map +1 -0
  224. package/dist/local/operation.d.ts +155 -0
  225. package/dist/local/operation.d.ts.map +1 -0
  226. package/dist/local/operation.js +340 -0
  227. package/dist/local/operation.js.map +1 -0
  228. package/dist/local/ownership-reader-schema.d.ts +117 -0
  229. package/dist/local/ownership-reader-schema.d.ts.map +1 -0
  230. package/dist/local/ownership-reader-schema.js +105 -0
  231. package/dist/local/ownership-reader-schema.js.map +1 -0
  232. package/dist/local/ownership-reader-worker.d.ts +2 -0
  233. package/dist/local/ownership-reader-worker.d.ts.map +1 -0
  234. package/dist/local/ownership-reader-worker.js +341 -0
  235. package/dist/local/ownership-reader-worker.js.map +1 -0
  236. package/dist/local/ownership-reader.d.ts +10 -0
  237. package/dist/local/ownership-reader.d.ts.map +1 -0
  238. package/dist/local/ownership-reader.js +93 -0
  239. package/dist/local/ownership-reader.js.map +1 -0
  240. package/dist/local/package-uses-check.d.ts +33 -0
  241. package/dist/local/package-uses-check.d.ts.map +1 -0
  242. package/dist/local/package-uses-check.js +62 -0
  243. package/dist/local/package-uses-check.js.map +1 -0
  244. package/dist/local/pending-recovery.d.ts +22 -0
  245. package/dist/local/pending-recovery.d.ts.map +1 -0
  246. package/dist/local/pending-recovery.js +171 -0
  247. package/dist/local/pending-recovery.js.map +1 -0
  248. package/dist/local/portable-commit-store.d.ts +28 -0
  249. package/dist/local/portable-commit-store.d.ts.map +1 -0
  250. package/dist/local/portable-commit-store.js +187 -0
  251. package/dist/local/portable-commit-store.js.map +1 -0
  252. package/dist/local/portable-commit.d.ts +109 -0
  253. package/dist/local/portable-commit.d.ts.map +1 -0
  254. package/dist/local/portable-commit.js +209 -0
  255. package/dist/local/portable-commit.js.map +1 -0
  256. package/dist/local/portable-remote.d.ts +432 -0
  257. package/dist/local/portable-remote.d.ts.map +1 -0
  258. package/dist/local/portable-remote.js +280 -0
  259. package/dist/local/portable-remote.js.map +1 -0
  260. package/dist/local/project-deleted.d.ts +4 -0
  261. package/dist/local/project-deleted.d.ts.map +1 -0
  262. package/dist/local/project-deleted.js +13 -0
  263. package/dist/local/project-deleted.js.map +1 -0
  264. package/dist/local/projection.d.ts +166 -0
  265. package/dist/local/projection.d.ts.map +1 -0
  266. package/dist/local/projection.js +709 -0
  267. package/dist/local/projection.js.map +1 -0
  268. package/dist/local/read-operation.d.ts +3 -0
  269. package/dist/local/read-operation.d.ts.map +1 -0
  270. package/dist/local/read-operation.js +19 -0
  271. package/dist/local/read-operation.js.map +1 -0
  272. package/dist/local/rebuild.d.ts +11 -0
  273. package/dist/local/rebuild.d.ts.map +1 -0
  274. package/dist/local/rebuild.js +42 -0
  275. package/dist/local/rebuild.js.map +1 -0
  276. package/dist/local/remote-base.d.ts +25 -0
  277. package/dist/local/remote-base.d.ts.map +1 -0
  278. package/dist/local/remote-base.js +68 -0
  279. package/dist/local/remote-base.js.map +1 -0
  280. package/dist/local/save-layout-worker.d.ts +19 -0
  281. package/dist/local/save-layout-worker.d.ts.map +1 -0
  282. package/dist/local/save-layout-worker.js +28 -0
  283. package/dist/local/save-layout-worker.js.map +1 -0
  284. package/dist/local/save-layout.d.ts +84 -0
  285. package/dist/local/save-layout.d.ts.map +1 -0
  286. package/dist/local/save-layout.js +221 -0
  287. package/dist/local/save-layout.js.map +1 -0
  288. package/dist/local/scope-impact.d.ts +31 -0
  289. package/dist/local/scope-impact.d.ts.map +1 -0
  290. package/dist/local/scope-impact.js +32 -0
  291. package/dist/local/scope-impact.js.map +1 -0
  292. package/dist/local/source-evidence/filesystem-worker.d.ts +2 -0
  293. package/dist/local/source-evidence/filesystem-worker.d.ts.map +1 -0
  294. package/dist/local/source-evidence/filesystem-worker.js +139 -0
  295. package/dist/local/source-evidence/filesystem-worker.js.map +1 -0
  296. package/dist/local/source-evidence/processes.d.ts +20 -0
  297. package/dist/local/source-evidence/processes.d.ts.map +1 -0
  298. package/dist/local/source-evidence/processes.js +133 -0
  299. package/dist/local/source-evidence/processes.js.map +1 -0
  300. package/dist/local/source-evidence/protocol.d.ts +52 -0
  301. package/dist/local/source-evidence/protocol.d.ts.map +1 -0
  302. package/dist/local/source-evidence/protocol.js +72 -0
  303. package/dist/local/source-evidence/protocol.js.map +1 -0
  304. package/dist/local/source-evidence/reader.d.ts +14 -0
  305. package/dist/local/source-evidence/reader.d.ts.map +1 -0
  306. package/dist/local/source-evidence/reader.js +308 -0
  307. package/dist/local/source-evidence/reader.js.map +1 -0
  308. package/dist/local/source-evidence/schema.d.ts +2504 -0
  309. package/dist/local/source-evidence/schema.d.ts.map +1 -0
  310. package/dist/local/source-evidence/schema.js +99 -0
  311. package/dist/local/source-evidence/schema.js.map +1 -0
  312. package/dist/local/source-policy/loader.d.ts +29 -0
  313. package/dist/local/source-policy/loader.d.ts.map +1 -0
  314. package/dist/local/source-policy/loader.js +209 -0
  315. package/dist/local/source-policy/loader.js.map +1 -0
  316. package/dist/local/source-policy/process.d.ts +7 -0
  317. package/dist/local/source-policy/process.d.ts.map +1 -0
  318. package/dist/local/source-policy/process.js +53 -0
  319. package/dist/local/source-policy/process.js.map +1 -0
  320. package/dist/local/source-policy/protocol.d.ts +27 -0
  321. package/dist/local/source-policy/protocol.d.ts.map +1 -0
  322. package/dist/local/source-policy/protocol.js +48 -0
  323. package/dist/local/source-policy/protocol.js.map +1 -0
  324. package/dist/local/source-policy/worker.d.ts +2 -0
  325. package/dist/local/source-policy/worker.d.ts.map +1 -0
  326. package/dist/local/source-policy/worker.js +100 -0
  327. package/dist/local/source-policy/worker.js.map +1 -0
  328. package/dist/local/state-directory.d.ts +17 -0
  329. package/dist/local/state-directory.d.ts.map +1 -0
  330. package/dist/local/state-directory.js +68 -0
  331. package/dist/local/state-directory.js.map +1 -0
  332. package/dist/local/store-database.d.ts +13 -0
  333. package/dist/local/store-database.d.ts.map +1 -0
  334. package/dist/local/store-database.js +187 -0
  335. package/dist/local/store-database.js.map +1 -0
  336. package/dist/local/store.d.ts +181 -0
  337. package/dist/local/store.d.ts.map +1 -0
  338. package/dist/local/store.js +585 -0
  339. package/dist/local/store.js.map +1 -0
  340. package/dist/local/sync.d.ts +47 -0
  341. package/dist/local/sync.d.ts.map +1 -0
  342. package/dist/local/sync.js +518 -0
  343. package/dist/local/sync.js.map +1 -0
  344. package/dist/local/task-context-inspection.d.ts +23 -0
  345. package/dist/local/task-context-inspection.d.ts.map +1 -0
  346. package/dist/local/task-context-inspection.js +165 -0
  347. package/dist/local/task-context-inspection.js.map +1 -0
  348. package/dist/local/task-context-observations.d.ts +9553 -0
  349. package/dist/local/task-context-observations.d.ts.map +1 -0
  350. package/dist/local/task-context-observations.js +232 -0
  351. package/dist/local/task-context-observations.js.map +1 -0
  352. package/dist/local/task-context-provider.d.ts +18 -0
  353. package/dist/local/task-context-provider.d.ts.map +1 -0
  354. package/dist/local/task-context-provider.js +231 -0
  355. package/dist/local/task-context-provider.js.map +1 -0
  356. package/dist/local/task-context-schema.d.ts +847 -0
  357. package/dist/local/task-context-schema.d.ts.map +1 -0
  358. package/dist/local/task-context-schema.js +305 -0
  359. package/dist/local/task-context-schema.js.map +1 -0
  360. package/dist/local/task-context-source.d.ts +16 -0
  361. package/dist/local/task-context-source.d.ts.map +1 -0
  362. package/dist/local/task-context-source.js +45 -0
  363. package/dist/local/task-context-source.js.map +1 -0
  364. package/dist/local/task-context.d.ts +14 -0
  365. package/dist/local/task-context.d.ts.map +1 -0
  366. package/dist/local/task-context.js +570 -0
  367. package/dist/local/task-context.js.map +1 -0
  368. package/dist/local/test-commands.d.ts +71 -0
  369. package/dist/local/test-commands.d.ts.map +1 -0
  370. package/dist/local/test-commands.js +292 -0
  371. package/dist/local/test-commands.js.map +1 -0
  372. package/dist/local/tree-save.d.ts +117 -0
  373. package/dist/local/tree-save.d.ts.map +1 -0
  374. package/dist/local/tree-save.js +615 -0
  375. package/dist/local/tree-save.js.map +1 -0
  376. package/dist/local/variants.d.ts +125 -0
  377. package/dist/local/variants.d.ts.map +1 -0
  378. package/dist/local/variants.js +267 -0
  379. package/dist/local/variants.js.map +1 -0
  380. package/dist/local/workspace-admission.d.ts +61 -0
  381. package/dist/local/workspace-admission.d.ts.map +1 -0
  382. package/dist/local/workspace-admission.js +112 -0
  383. package/dist/local/workspace-admission.js.map +1 -0
  384. package/dist/local/workspace-authority-guard.d.ts +18 -0
  385. package/dist/local/workspace-authority-guard.d.ts.map +1 -0
  386. package/dist/local/workspace-authority-guard.js +69 -0
  387. package/dist/local/workspace-authority-guard.js.map +1 -0
  388. package/dist/local/workspace-authority.d.ts +33 -0
  389. package/dist/local/workspace-authority.d.ts.map +1 -0
  390. package/dist/local/workspace-authority.js +33 -0
  391. package/dist/local/workspace-authority.js.map +1 -0
  392. package/dist/local/workspace-branch-runtime.d.ts +61 -0
  393. package/dist/local/workspace-branch-runtime.d.ts.map +1 -0
  394. package/dist/local/workspace-branch-runtime.js +216 -0
  395. package/dist/local/workspace-branch-runtime.js.map +1 -0
  396. package/dist/local/workspace-built-content.d.ts +137 -0
  397. package/dist/local/workspace-built-content.d.ts.map +1 -0
  398. package/dist/local/workspace-built-content.js +393 -0
  399. package/dist/local/workspace-built-content.js.map +1 -0
  400. package/dist/local/workspace-claim-status-read.d.ts +53 -0
  401. package/dist/local/workspace-claim-status-read.d.ts.map +1 -0
  402. package/dist/local/workspace-claim-status-read.js +118 -0
  403. package/dist/local/workspace-claim-status-read.js.map +1 -0
  404. package/dist/local/workspace-claim-status.d.ts +172 -0
  405. package/dist/local/workspace-claim-status.d.ts.map +1 -0
  406. package/dist/local/workspace-claim-status.js +721 -0
  407. package/dist/local/workspace-claim-status.js.map +1 -0
  408. package/dist/local/workspace-claim-views.d.ts +45 -0
  409. package/dist/local/workspace-claim-views.d.ts.map +1 -0
  410. package/dist/local/workspace-claim-views.js +45 -0
  411. package/dist/local/workspace-claim-views.js.map +1 -0
  412. package/dist/local/workspace-cli.d.ts +46 -0
  413. package/dist/local/workspace-cli.d.ts.map +1 -0
  414. package/dist/local/workspace-cli.js +1524 -0
  415. package/dist/local/workspace-cli.js.map +1 -0
  416. package/dist/local/workspace-commit-resolver.d.ts +19 -0
  417. package/dist/local/workspace-commit-resolver.d.ts.map +1 -0
  418. package/dist/local/workspace-commit-resolver.js +47 -0
  419. package/dist/local/workspace-commit-resolver.js.map +1 -0
  420. package/dist/local/workspace-decision-store.d.ts +184 -0
  421. package/dist/local/workspace-decision-store.d.ts.map +1 -0
  422. package/dist/local/workspace-decision-store.js +510 -0
  423. package/dist/local/workspace-decision-store.js.map +1 -0
  424. package/dist/local/workspace-environment-runtime.d.ts +107 -0
  425. package/dist/local/workspace-environment-runtime.d.ts.map +1 -0
  426. package/dist/local/workspace-environment-runtime.js +135 -0
  427. package/dist/local/workspace-environment-runtime.js.map +1 -0
  428. package/dist/local/workspace-evidence-drop.d.ts +21 -0
  429. package/dist/local/workspace-evidence-drop.d.ts.map +1 -0
  430. package/dist/local/workspace-evidence-drop.js +58 -0
  431. package/dist/local/workspace-evidence-drop.js.map +1 -0
  432. package/dist/local/workspace-evidence-sign-tool.d.ts +20 -0
  433. package/dist/local/workspace-evidence-sign-tool.d.ts.map +1 -0
  434. package/dist/local/workspace-evidence-sign-tool.js +85 -0
  435. package/dist/local/workspace-evidence-sign-tool.js.map +1 -0
  436. package/dist/local/workspace-evidence-status.d.ts +89 -0
  437. package/dist/local/workspace-evidence-status.d.ts.map +1 -0
  438. package/dist/local/workspace-evidence-status.js +141 -0
  439. package/dist/local/workspace-evidence-status.js.map +1 -0
  440. package/dist/local/workspace-evidence.d.ts +145 -0
  441. package/dist/local/workspace-evidence.d.ts.map +1 -0
  442. package/dist/local/workspace-evidence.js +347 -0
  443. package/dist/local/workspace-evidence.js.map +1 -0
  444. package/dist/local/workspace-graph-store.d.ts +32 -0
  445. package/dist/local/workspace-graph-store.d.ts.map +1 -0
  446. package/dist/local/workspace-graph-store.js +249 -0
  447. package/dist/local/workspace-graph-store.js.map +1 -0
  448. package/dist/local/workspace-handoff.d.ts +32 -0
  449. package/dist/local/workspace-handoff.d.ts.map +1 -0
  450. package/dist/local/workspace-handoff.js +144 -0
  451. package/dist/local/workspace-handoff.js.map +1 -0
  452. package/dist/local/workspace-history-journal.d.ts +70 -0
  453. package/dist/local/workspace-history-journal.d.ts.map +1 -0
  454. package/dist/local/workspace-history-journal.js +131 -0
  455. package/dist/local/workspace-history-journal.js.map +1 -0
  456. package/dist/local/workspace-history-store.d.ts +98 -0
  457. package/dist/local/workspace-history-store.d.ts.map +1 -0
  458. package/dist/local/workspace-history-store.js +632 -0
  459. package/dist/local/workspace-history-store.js.map +1 -0
  460. package/dist/local/workspace-history-types.d.ts +307 -0
  461. package/dist/local/workspace-history-types.d.ts.map +1 -0
  462. package/dist/local/workspace-history-types.js +2 -0
  463. package/dist/local/workspace-history-types.js.map +1 -0
  464. package/dist/local/workspace-integration-runtime.d.ts +48 -0
  465. package/dist/local/workspace-integration-runtime.d.ts.map +1 -0
  466. package/dist/local/workspace-integration-runtime.js +419 -0
  467. package/dist/local/workspace-integration-runtime.js.map +1 -0
  468. package/dist/local/workspace-lock.d.ts +7 -0
  469. package/dist/local/workspace-lock.d.ts.map +1 -0
  470. package/dist/local/workspace-lock.js +211 -0
  471. package/dist/local/workspace-lock.js.map +1 -0
  472. package/dist/local/workspace-merge.d.ts +77 -0
  473. package/dist/local/workspace-merge.d.ts.map +1 -0
  474. package/dist/local/workspace-merge.js +703 -0
  475. package/dist/local/workspace-merge.js.map +1 -0
  476. package/dist/local/workspace-mixed-history.d.ts +33 -0
  477. package/dist/local/workspace-mixed-history.d.ts.map +1 -0
  478. package/dist/local/workspace-mixed-history.js +107 -0
  479. package/dist/local/workspace-mixed-history.js.map +1 -0
  480. package/dist/local/workspace-overlap-allocation.d.ts +33 -0
  481. package/dist/local/workspace-overlap-allocation.d.ts.map +1 -0
  482. package/dist/local/workspace-overlap-allocation.js +69 -0
  483. package/dist/local/workspace-overlap-allocation.js.map +1 -0
  484. package/dist/local/workspace-overlap-impact.d.ts +46 -0
  485. package/dist/local/workspace-overlap-impact.d.ts.map +1 -0
  486. package/dist/local/workspace-overlap-impact.js +162 -0
  487. package/dist/local/workspace-overlap-impact.js.map +1 -0
  488. package/dist/local/workspace-overlap.d.ts +109 -0
  489. package/dist/local/workspace-overlap.d.ts.map +1 -0
  490. package/dist/local/workspace-overlap.js +327 -0
  491. package/dist/local/workspace-overlap.js.map +1 -0
  492. package/dist/local/workspace-paths.d.ts +4 -0
  493. package/dist/local/workspace-paths.d.ts.map +1 -0
  494. package/dist/local/workspace-paths.js +8 -0
  495. package/dist/local/workspace-paths.js.map +1 -0
  496. package/dist/local/workspace-pick-runtime.d.ts +70 -0
  497. package/dist/local/workspace-pick-runtime.d.ts.map +1 -0
  498. package/dist/local/workspace-pick-runtime.js +177 -0
  499. package/dist/local/workspace-pick-runtime.js.map +1 -0
  500. package/dist/local/workspace-pick-store.d.ts +33 -0
  501. package/dist/local/workspace-pick-store.d.ts.map +1 -0
  502. package/dist/local/workspace-pick-store.js +43 -0
  503. package/dist/local/workspace-pick-store.js.map +1 -0
  504. package/dist/local/workspace-pick.d.ts +73 -0
  505. package/dist/local/workspace-pick.d.ts.map +1 -0
  506. package/dist/local/workspace-pick.js +253 -0
  507. package/dist/local/workspace-pick.js.map +1 -0
  508. package/dist/local/workspace-portable-authoring.d.ts +43 -0
  509. package/dist/local/workspace-portable-authoring.d.ts.map +1 -0
  510. package/dist/local/workspace-portable-authoring.js +128 -0
  511. package/dist/local/workspace-portable-authoring.js.map +1 -0
  512. package/dist/local/workspace-prune.d.ts +63 -0
  513. package/dist/local/workspace-prune.d.ts.map +1 -0
  514. package/dist/local/workspace-prune.js +267 -0
  515. package/dist/local/workspace-prune.js.map +1 -0
  516. package/dist/local/workspace-remote-fetch.d.ts +82 -0
  517. package/dist/local/workspace-remote-fetch.d.ts.map +1 -0
  518. package/dist/local/workspace-remote-fetch.js +409 -0
  519. package/dist/local/workspace-remote-fetch.js.map +1 -0
  520. package/dist/local/workspace-remote-provider.d.ts +97 -0
  521. package/dist/local/workspace-remote-provider.d.ts.map +1 -0
  522. package/dist/local/workspace-remote-provider.js +400 -0
  523. package/dist/local/workspace-remote-provider.js.map +1 -0
  524. package/dist/local/workspace-remote-pull-request-journal.d.ts +49 -0
  525. package/dist/local/workspace-remote-pull-request-journal.d.ts.map +1 -0
  526. package/dist/local/workspace-remote-pull-request-journal.js +63 -0
  527. package/dist/local/workspace-remote-pull-request-journal.js.map +1 -0
  528. package/dist/local/workspace-remote-pull-request.d.ts +36 -0
  529. package/dist/local/workspace-remote-pull-request.d.ts.map +1 -0
  530. package/dist/local/workspace-remote-pull-request.js +158 -0
  531. package/dist/local/workspace-remote-pull-request.js.map +1 -0
  532. package/dist/local/workspace-remote-push.d.ts +88 -0
  533. package/dist/local/workspace-remote-push.d.ts.map +1 -0
  534. package/dist/local/workspace-remote-push.js +297 -0
  535. package/dist/local/workspace-remote-push.js.map +1 -0
  536. package/dist/local/workspace-remote-topic-journal.d.ts +56 -0
  537. package/dist/local/workspace-remote-topic-journal.d.ts.map +1 -0
  538. package/dist/local/workspace-remote-topic-journal.js +121 -0
  539. package/dist/local/workspace-remote-topic-journal.js.map +1 -0
  540. package/dist/local/workspace-remote-topic.d.ts +45 -0
  541. package/dist/local/workspace-remote-topic.d.ts.map +1 -0
  542. package/dist/local/workspace-remote-topic.js +204 -0
  543. package/dist/local/workspace-remote-topic.js.map +1 -0
  544. package/dist/local/workspace-remote-transfer.d.ts +76 -0
  545. package/dist/local/workspace-remote-transfer.d.ts.map +1 -0
  546. package/dist/local/workspace-remote-transfer.js +270 -0
  547. package/dist/local/workspace-remote-transfer.js.map +1 -0
  548. package/dist/local/workspace-resolution.d.ts +145 -0
  549. package/dist/local/workspace-resolution.d.ts.map +1 -0
  550. package/dist/local/workspace-resolution.js +479 -0
  551. package/dist/local/workspace-resolution.js.map +1 -0
  552. package/dist/local/workspace-resolver-provider.d.ts +72 -0
  553. package/dist/local/workspace-resolver-provider.d.ts.map +1 -0
  554. package/dist/local/workspace-resolver-provider.js +312 -0
  555. package/dist/local/workspace-resolver-provider.js.map +1 -0
  556. package/dist/local/workspace-runtime-handoff.d.ts +52 -0
  557. package/dist/local/workspace-runtime-handoff.d.ts.map +1 -0
  558. package/dist/local/workspace-runtime-handoff.js +237 -0
  559. package/dist/local/workspace-runtime-handoff.js.map +1 -0
  560. package/dist/local/workspace-runtime-lease.d.ts +26 -0
  561. package/dist/local/workspace-runtime-lease.d.ts.map +1 -0
  562. package/dist/local/workspace-runtime-lease.js +123 -0
  563. package/dist/local/workspace-runtime-lease.js.map +1 -0
  564. package/dist/local/workspace-runtime.d.ts +690 -0
  565. package/dist/local/workspace-runtime.d.ts.map +1 -0
  566. package/dist/local/workspace-runtime.js +2124 -0
  567. package/dist/local/workspace-runtime.js.map +1 -0
  568. package/dist/local/workspace-scope.d.ts +70 -0
  569. package/dist/local/workspace-scope.d.ts.map +1 -0
  570. package/dist/local/workspace-scope.js +206 -0
  571. package/dist/local/workspace-scope.js.map +1 -0
  572. package/dist/local/workspace-sources.d.ts +159 -0
  573. package/dist/local/workspace-sources.d.ts.map +1 -0
  574. package/dist/local/workspace-sources.js +570 -0
  575. package/dist/local/workspace-sources.js.map +1 -0
  576. package/dist/local/workspace-store-database.d.ts +5 -0
  577. package/dist/local/workspace-store-database.d.ts.map +1 -0
  578. package/dist/local/workspace-store-database.js +726 -0
  579. package/dist/local/workspace-store-database.js.map +1 -0
  580. package/dist/local/workspace-store.d.ts +261 -0
  581. package/dist/local/workspace-store.d.ts.map +1 -0
  582. package/dist/local/workspace-store.js +831 -0
  583. package/dist/local/workspace-store.js.map +1 -0
  584. package/dist/local/workspace-topology-runtime.d.ts +27 -0
  585. package/dist/local/workspace-topology-runtime.d.ts.map +1 -0
  586. package/dist/local/workspace-topology-runtime.js +396 -0
  587. package/dist/local/workspace-topology-runtime.js.map +1 -0
  588. package/dist/local/workspace-topology.d.ts +140 -0
  589. package/dist/local/workspace-topology.d.ts.map +1 -0
  590. package/dist/local/workspace-topology.js +359 -0
  591. package/dist/local/workspace-topology.js.map +1 -0
  592. package/dist/local/workspace-upstream-inspection.d.ts +60 -0
  593. package/dist/local/workspace-upstream-inspection.d.ts.map +1 -0
  594. package/dist/local/workspace-upstream-inspection.js +189 -0
  595. package/dist/local/workspace-upstream-inspection.js.map +1 -0
  596. package/dist/local/workspace-upstream-runtime.d.ts +91 -0
  597. package/dist/local/workspace-upstream-runtime.d.ts.map +1 -0
  598. package/dist/local/workspace-upstream-runtime.js +270 -0
  599. package/dist/local/workspace-upstream-runtime.js.map +1 -0
  600. package/dist/local/workspace-upstream-store.d.ts +51 -0
  601. package/dist/local/workspace-upstream-store.d.ts.map +1 -0
  602. package/dist/local/workspace-upstream-store.js +475 -0
  603. package/dist/local/workspace-upstream-store.js.map +1 -0
  604. package/dist/local/workspace-viewer-server.d.ts +136 -0
  605. package/dist/local/workspace-viewer-server.d.ts.map +1 -0
  606. package/dist/local/workspace-viewer-server.js +1097 -0
  607. package/dist/local/workspace-viewer-server.js.map +1 -0
  608. package/dist/local/workspace-viewer-snapshot.d.ts +49 -0
  609. package/dist/local/workspace-viewer-snapshot.d.ts.map +1 -0
  610. package/dist/local/workspace-viewer-snapshot.js +91 -0
  611. package/dist/local/workspace-viewer-snapshot.js.map +1 -0
  612. package/dist/local/worktree-projection.d.ts +76 -0
  613. package/dist/local/worktree-projection.d.ts.map +1 -0
  614. package/dist/local/worktree-projection.js +470 -0
  615. package/dist/local/worktree-projection.js.map +1 -0
  616. package/dist/mcp/client-identity.d.ts +8 -0
  617. package/dist/mcp/client-identity.d.ts.map +1 -0
  618. package/dist/mcp/client-identity.js +33 -0
  619. package/dist/mcp/client-identity.js.map +1 -0
  620. package/dist/mcp/graph-to-code.d.ts +53 -0
  621. package/dist/mcp/graph-to-code.d.ts.map +1 -0
  622. package/dist/mcp/graph-to-code.js +1624 -0
  623. package/dist/mcp/graph-to-code.js.map +1 -0
  624. package/dist/mcp/index.d.ts +3 -0
  625. package/dist/mcp/index.d.ts.map +1 -0
  626. package/dist/mcp/index.js +3 -0
  627. package/dist/mcp/index.js.map +1 -0
  628. package/dist/mcp/stdio/graph-to-code.d.ts +3 -0
  629. package/dist/mcp/stdio/graph-to-code.d.ts.map +1 -0
  630. package/dist/mcp/stdio/graph-to-code.js +55 -0
  631. package/dist/mcp/stdio/graph-to-code.js.map +1 -0
  632. package/dist/mcp/stdio/worktree.d.ts +3 -0
  633. package/dist/mcp/stdio/worktree.d.ts.map +1 -0
  634. package/dist/mcp/stdio/worktree.js +67 -0
  635. package/dist/mcp/stdio/worktree.js.map +1 -0
  636. package/dist/mcp/worktree-claim-status.d.ts +162 -0
  637. package/dist/mcp/worktree-claim-status.d.ts.map +1 -0
  638. package/dist/mcp/worktree-claim-status.js +113 -0
  639. package/dist/mcp/worktree-claim-status.js.map +1 -0
  640. package/dist/mcp/worktree-dispatch.d.ts +13 -0
  641. package/dist/mcp/worktree-dispatch.d.ts.map +1 -0
  642. package/dist/mcp/worktree-dispatch.js +52 -0
  643. package/dist/mcp/worktree-dispatch.js.map +1 -0
  644. package/dist/mcp/worktree-errors.d.ts +15 -0
  645. package/dist/mcp/worktree-errors.d.ts.map +1 -0
  646. package/dist/mcp/worktree-errors.js +17 -0
  647. package/dist/mcp/worktree-errors.js.map +1 -0
  648. package/dist/mcp/worktree-health.d.ts +12 -0
  649. package/dist/mcp/worktree-health.d.ts.map +1 -0
  650. package/dist/mcp/worktree-health.js +32 -0
  651. package/dist/mcp/worktree-health.js.map +1 -0
  652. package/dist/mcp/worktree-results.d.ts +15 -0
  653. package/dist/mcp/worktree-results.d.ts.map +1 -0
  654. package/dist/mcp/worktree-results.js +12 -0
  655. package/dist/mcp/worktree-results.js.map +1 -0
  656. package/dist/mcp/worktree-task-context.d.ts +57 -0
  657. package/dist/mcp/worktree-task-context.d.ts.map +1 -0
  658. package/dist/mcp/worktree-task-context.js +45 -0
  659. package/dist/mcp/worktree-task-context.js.map +1 -0
  660. package/dist/mcp/worktree-tools.d.ts +2648 -0
  661. package/dist/mcp/worktree-tools.d.ts.map +1 -0
  662. package/dist/mcp/worktree-tools.js +192 -0
  663. package/dist/mcp/worktree-tools.js.map +1 -0
  664. package/dist/mcp/worktree.d.ts +33 -0
  665. package/dist/mcp/worktree.d.ts.map +1 -0
  666. package/dist/mcp/worktree.js +551 -0
  667. package/dist/mcp/worktree.js.map +1 -0
  668. package/dist/own-package.d.ts +13 -0
  669. package/dist/own-package.d.ts.map +1 -0
  670. package/dist/own-package.js +32 -0
  671. package/dist/own-package.js.map +1 -0
  672. package/dist/packages-browse.d.ts +107 -0
  673. package/dist/packages-browse.d.ts.map +1 -0
  674. package/dist/packages-browse.js +646 -0
  675. package/dist/packages-browse.js.map +1 -0
  676. package/dist/packages-cli.d.ts +8 -0
  677. package/dist/packages-cli.d.ts.map +1 -0
  678. package/dist/packages-cli.js +184 -0
  679. package/dist/packages-cli.js.map +1 -0
  680. package/dist/packages-uses.d.ts +46 -0
  681. package/dist/packages-uses.d.ts.map +1 -0
  682. package/dist/packages-uses.js +737 -0
  683. package/dist/packages-uses.js.map +1 -0
  684. package/dist/portable.d.ts +7 -0
  685. package/dist/portable.d.ts.map +1 -0
  686. package/dist/portable.js +14 -0
  687. package/dist/portable.js.map +1 -0
  688. package/dist/quiet-warnings.d.ts +3 -0
  689. package/dist/quiet-warnings.d.ts.map +1 -0
  690. package/dist/quiet-warnings.js +21 -0
  691. package/dist/quiet-warnings.js.map +1 -0
  692. package/dist/view-seam.d.ts +62 -0
  693. package/dist/view-seam.d.ts.map +1 -0
  694. package/dist/view-seam.js +70 -0
  695. package/dist/view-seam.js.map +1 -0
  696. package/generated/graph-packages/index.json +5 -0
  697. package/graph-packages.lock.json +4 -0
  698. package/package.json +81 -0
  699. package/viewer/assets/index-BZKGDARF.css +1 -0
  700. package/viewer/assets/index-DWu0CbZZ.js +272 -0
  701. package/viewer/index.html +13 -0
@@ -0,0 +1,2124 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ import { getLibraryOriginWarnings } from '@software-dlc/graph/validator';
5
+ import { composeGraphProjection, diffProjectionGraphs, extendScopedProjection, graphHasProjectionEntity, hasProjectionManifest, isScopedProjection, materializeGraphProjection, projectionDiffHasChanges, projectionHasManagedContent, projectionScopeOfTaskScope, recordScopedProjectionFiles, removeEmptyGraphProjection, removeProjectionEntity, validateProjectionGraph, writeJsonAtomically } from './projection.js';
6
+ import { heldPackages, packageUseCommitIssues } from './package-uses-check.js';
7
+ import { introducedBrokenIssues, introducedValidationWarnings } from './workspace-merge.js';
8
+ import { planEvidenceSign, signedMeta } from './workspace-evidence.js';
9
+ import { evidenceWith, ReferenceLimitError, sourceIndex, sourceReference } from './workspace-sources.js';
10
+ import { evidenceStatus } from './workspace-evidence-status.js';
11
+ import { droppedEvidenceReferences, evidenceDropWarning } from './workspace-evidence-drop.js';
12
+ import { assertLocalWorkspacePathsIgnored, createDetachedGitWorktree, discoverGitRemoteIdentity, gitRemoteNames, discoverGitWorktree, removeGitWorktree, namingRemote, repositoryNames, trackedRemoteName } from './git.js';
13
+ import { LocalWorkspaceGraphStore, localGraphRootHash, resolveLocalRepositoryStateDirectory } from './workspace-graph-store.js';
14
+ import { blastRadius } from './blast-radius.js';
15
+ import { findGraphEntities, showGraphEntities } from './graph-read.js';
16
+ import { readFolderMembers, repositoryTaskScope } from './folder-members.js';
17
+ import { variantMemberFormIds, variantSyncIds, variantsReport } from './variants.js';
18
+ import { withWorkspaceOperationLock } from './workspace-lock.js';
19
+ import { allocationUnits, compareLocalWorkspacePlans } from './workspace-overlap.js';
20
+ import { allocationUnitKey, combineValidationUnits, firstOwnChanges } from './workspace-overlap-allocation.js';
21
+ import { LocalWorkspaceCommitResolver } from './workspace-commit-resolver.js';
22
+ import { LocalWorkspaceAuthorityGuard } from './workspace-authority-guard.js';
23
+ import { readBuiltView as readBuiltGraph } from './workspace-built-content.js';
24
+ import { listClaims } from './workspace-claim-views.js';
25
+ import { claimStatus, gitClaimStatusReader } from './workspace-claim-status.js';
26
+ import { workspaceControlDirectory as controlDirectory, workspaceProjectionDirectory as projectionDirectory } from './workspace-paths.js';
27
+ import { LocalWorkspaceStore } from './workspace-store.js';
28
+ import { LocalWorkspaceHandoffRuntime } from './workspace-runtime-handoff.js';
29
+ import { LocalWorkspacePruner } from './workspace-prune.js';
30
+ import { inspectLocalWorkspaceAuthority } from './workspace-authority.js';
31
+ import { LocalWorkspaceResolverProviderStore } from './workspace-resolver-provider.js';
32
+ import { LocalWorkspaceRemoteProviderStore } from './workspace-remote-provider.js';
33
+ import { LocalWorkspaceRemoteFetcher } from './workspace-remote-fetch.js';
34
+ import { LocalWorkspaceBranchRuntime } from './workspace-branch-runtime.js';
35
+ import { LocalWorkspaceIntegrationRuntime } from './workspace-integration-runtime.js';
36
+ import { LocalWorkspacePickRuntime } from './workspace-pick-runtime.js';
37
+ import { LocalWorkspaceEnvironmentRuntime } from './workspace-environment-runtime.js';
38
+ import { LocalWorkspaceUpstreamRuntime } from './workspace-upstream-runtime.js';
39
+ import { LocalWorkspaceRemotePusher } from './workspace-remote-push.js';
40
+ import { LocalWorkspaceRemoteTopicPublisher } from './workspace-remote-topic.js';
41
+ import { LocalWorkspaceRemotePullRequestCreator } from './workspace-remote-pull-request.js';
42
+ import { LocalWorkspacePortableAuthoring } from './workspace-portable-authoring.js';
43
+ import { assertGraphChangesWithinTaskScope, assertNodeTypeRulesOnChangedNodes, assertNoRetiredKeysOnChangedNodes, graphChangeCoordinates, ruleCovers, local_workspace_task_scope_schema } from './workspace-scope.js';
44
+ import { assertIntroducedVocabularyAdmitted } from './workspace-admission.js';
45
+ import { canonicalizePortableGraph, stringifyCanonicalGraph } from './canonical.js';
46
+ import { introducedDanglingDocumentEmbeds } from './document-embeds.js';
47
+ import { readWorkspaceTopology, removeWorkspaceTopology, topologyBindingMatch, writeWorkspaceTopology } from './workspace-topology.js';
48
+ import { registerWorkspaceRuntimeLease, releaseWorkspaceRuntimeLease, withGraphRepositoryTopologyLock } from './workspace-runtime-lease.js';
49
+ /**
50
+ * When init and attach began binding the naming remote: e33d186f reached
51
+ * origin/main in the production merge 866a9426cd. A binding made on origin
52
+ * since by a CLI from before that release is not told apart and keeps origin.
53
+ */
54
+ const NAMING_REMOTE_RULE_AT = '2026-10-28T21:14:06.000Z';
55
+ /**
56
+ * The node types a graph starts from, outside in (the local-graph-start
57
+ * skill): one is missing when no node of it exists. Complete graph validation
58
+ * does not ask for them, so a commit only warns.
59
+ */
60
+ const STARTING_NODE_TYPES = [
61
+ ['system', 'the product'],
62
+ ['actor', 'the people and outside systems that use it']
63
+ ];
64
+ /** An outside system, or one nested in another node, is not the product. */
65
+ function startsTheGraph(node) {
66
+ if (node.type !== 'system')
67
+ return true;
68
+ const system = node.data?.data
69
+ ?.system;
70
+ return !node.parentId && system?.external !== true;
71
+ }
72
+ function graphShapeWarnings(graph) {
73
+ const types = new Set(graph.nodes.filter(startsTheGraph).map((node) => node.type));
74
+ return STARTING_NODE_TYPES.filter(([type]) => !types.has(type)).map(([type, what]) => `The graph has no ${type} node for ${what}.`);
75
+ }
76
+ function brokenValidationMessages(graph) {
77
+ return validateProjectionGraph(graph)
78
+ .issues.filter((issue) => issue.status === 'broken')
79
+ .map((issue) => `${issue.code}: ${issue.message}`);
80
+ }
81
+ export class LocalWorkspaceRuntime {
82
+ git;
83
+ state_dir;
84
+ store;
85
+ graphs;
86
+ topology;
87
+ runtime_lease;
88
+ closed = false;
89
+ resolver_store;
90
+ remote_store;
91
+ remote_fetcher;
92
+ remote_pusher;
93
+ remote_topic_publisher;
94
+ remote_pull_request_creator;
95
+ portable_authoring;
96
+ commits;
97
+ authority;
98
+ handoff_runtime;
99
+ pruner;
100
+ branch_runtime;
101
+ integration_runtime;
102
+ pick_runtime;
103
+ upstream_runtime;
104
+ environment_runtime;
105
+ materialize_projection;
106
+ constructor(input) {
107
+ this.git = input.git;
108
+ this.state_dir = input.state_dir;
109
+ this.store = input.store;
110
+ this.topology = input.topology;
111
+ this.runtime_lease = input.runtime_lease;
112
+ this.materialize_projection =
113
+ input.materialize_projection ?? materializeGraphProjection;
114
+ this.graphs = new LocalWorkspaceGraphStore(input.state_dir);
115
+ this.commits = new LocalWorkspaceCommitResolver(input.store, this.graphs);
116
+ this.authority = new LocalWorkspaceAuthorityGuard(input.git.repository_root, input.git.git_common_dir, input.store);
117
+ this.branch_runtime = new LocalWorkspaceBranchRuntime(input.git, input.store, this.authority, this.commits, this.materialize_projection);
118
+ this.integration_runtime = new LocalWorkspaceIntegrationRuntime(input.store, this.graphs, this.authority, this.commits, this.materialize_projection);
119
+ this.pick_runtime = new LocalWorkspacePickRuntime(input.store, this.graphs, this.authority, this.commits, this.materialize_projection);
120
+ this.upstream_runtime = new LocalWorkspaceUpstreamRuntime(input.store, this.authority, this.commits, this.materialize_projection);
121
+ this.resolver_store = new LocalWorkspaceResolverProviderStore(input.state_dir);
122
+ this.remote_store = new LocalWorkspaceRemoteProviderStore(input.state_dir);
123
+ this.remote_fetcher = new LocalWorkspaceRemoteFetcher(this.remote_store, input.store.remote_tracking, input.store.portable_commits, this.graphs);
124
+ this.remote_pusher = new LocalWorkspaceRemotePusher(this.remote_store, input.store.remote_push, input.store.portable_commits, this.graphs, input.state_dir);
125
+ this.remote_topic_publisher = new LocalWorkspaceRemoteTopicPublisher(this.remote_store, input.store.remote_topic, input.store.portable_commits, this.graphs, input.state_dir);
126
+ this.remote_pull_request_creator =
127
+ new LocalWorkspaceRemotePullRequestCreator(this.remote_store, input.store.remote_pull_request, input.state_dir);
128
+ this.portable_authoring = new LocalWorkspacePortableAuthoring(input.store.portable_commits, input.store.remote_tracking);
129
+ this.environment_runtime = new LocalWorkspaceEnvironmentRuntime(input.store, this.authority, this.resolver_store, this.remote_store, this.remote_fetcher, this.remote_pusher, this.remote_topic_publisher, this.remote_pull_request_creator, this.portable_authoring);
130
+ this.handoff_runtime = new LocalWorkspaceHandoffRuntime({
131
+ repository_root: input.git.repository_root,
132
+ git_common_dir: input.git.git_common_dir,
133
+ store: input.store,
134
+ graphs: this.graphs,
135
+ materialize_projection: this.materialize_projection
136
+ });
137
+ this.pruner = new LocalWorkspacePruner({
138
+ repository_root: input.git.repository_root,
139
+ current_worktree_path: input.git.repository_root,
140
+ store: input.store,
141
+ graphs: this.graphs,
142
+ assert_actor_authority: async (workspace_id) => {
143
+ await this.authority.requireCurrent(workspace_id);
144
+ }
145
+ });
146
+ }
147
+ static async open(input = {}) {
148
+ const git = await discoverGitWorktree(input.cwd);
149
+ const initial_topology = await readWorkspaceTopology(git.repository_root);
150
+ const open_runtime = async (topology) => {
151
+ if (topology &&
152
+ input.state_dir &&
153
+ resolve(input.state_dir) !== topology.state_dir) {
154
+ throw new Error('Explicit state directory conflicts with checkout topology.');
155
+ }
156
+ if (topology &&
157
+ input.graph_repository_id &&
158
+ input.graph_repository_id !== topology.graph_repository_id) {
159
+ throw new Error('Explicit graph repository identity conflicts with checkout topology.');
160
+ }
161
+ const state_dir = resolveLocalRepositoryStateDirectory({
162
+ git_common_dir: git.git_common_dir,
163
+ override: input.state_dir ?? topology?.state_dir
164
+ });
165
+ const store = await LocalWorkspaceStore.open({
166
+ state_dir,
167
+ git_common_dir: git.git_common_dir,
168
+ expected_repository_id: input.graph_repository_id ?? topology?.graph_repository_id
169
+ });
170
+ try {
171
+ if (topology) {
172
+ const workspace = store.getWorkspaceByGitWorktreePath(git.repository_root);
173
+ if (!workspace ||
174
+ workspace.status !== 'active' ||
175
+ workspace.id !== topology.workspace_id) {
176
+ throw new Error('Checkout topology does not identify its active graph workspace.');
177
+ }
178
+ const binding = store.getSourceRepositoryBinding(git.git_common_dir);
179
+ const match = topologyBindingMatch({ topology, binding });
180
+ if (match === 'mismatch') {
181
+ throw new Error('Checkout topology does not match its source repository binding.');
182
+ }
183
+ if (match === 'moved') {
184
+ // The binding moved to upstream; this checkout catches up
185
+ topology = {
186
+ ...topology,
187
+ source_remote_identity: binding.remote_identity
188
+ };
189
+ await writeWorkspaceTopology({
190
+ repository_root: git.repository_root,
191
+ topology
192
+ }).catch(() => undefined);
193
+ }
194
+ }
195
+ const runtime_lease = await registerWorkspaceRuntimeLease({
196
+ graph_repository_id: store.repository.repository_id,
197
+ state_dir,
198
+ repository_root: git.repository_root
199
+ });
200
+ try {
201
+ return new LocalWorkspaceRuntime({
202
+ git,
203
+ state_dir,
204
+ store,
205
+ topology,
206
+ runtime_lease,
207
+ materialize_projection: input.materialize_projection
208
+ });
209
+ }
210
+ catch (error) {
211
+ releaseWorkspaceRuntimeLease(runtime_lease);
212
+ throw error;
213
+ }
214
+ }
215
+ catch (error) {
216
+ store.close();
217
+ throw error;
218
+ }
219
+ };
220
+ const expected_repository_id = input.graph_repository_id ?? initial_topology?.graph_repository_id;
221
+ if (expected_repository_id) {
222
+ return withGraphRepositoryTopologyLock({
223
+ graph_repository_id: expected_repository_id,
224
+ operation: 'open-runtime',
225
+ run: async () => {
226
+ const topology = await readWorkspaceTopology(git.repository_root);
227
+ if (initial_topology &&
228
+ topology?.graph_repository_id !==
229
+ initial_topology.graph_repository_id) {
230
+ throw new Error('Checkout topology changed while opening runtime.');
231
+ }
232
+ return open_runtime(topology);
233
+ }
234
+ });
235
+ }
236
+ const runtime = await open_runtime(null);
237
+ return runtime;
238
+ }
239
+ close() {
240
+ if (this.closed)
241
+ return;
242
+ this.closed = true;
243
+ try {
244
+ this.store.close();
245
+ }
246
+ finally {
247
+ releaseWorkspaceRuntimeLease(this.runtime_lease);
248
+ }
249
+ }
250
+ async initializeCurrentWorkspace(input = {}) {
251
+ await assertLocalWorkspacePathsIgnored(this.git.repository_root);
252
+ const existing = this.store.getWorkspaceByGitWorktreePath(this.git.repository_root);
253
+ if (existing) {
254
+ if (existing.status !== 'active') {
255
+ throw new Error('The current Git worktree is registered as closed.');
256
+ }
257
+ await this.persistCurrentTopology(existing, input.storage_mode);
258
+ return this.getWorkspaceStatus(existing.id);
259
+ }
260
+ const projection_dir = join(this.git.repository_root, 'sdlc');
261
+ // A repository that never had a hosted checkout has no projection yet:
262
+ // start its local graph empty, so local-only use needs no account. A
263
+ // failed init removes that empty projection again, so the worktree is
264
+ // left as it was found.
265
+ const starts_empty = !(await projectionHasManagedContent(projection_dir));
266
+ try {
267
+ if (starts_empty) {
268
+ await materializeGraphProjection({
269
+ projection_dir,
270
+ graph: {
271
+ nodes: [],
272
+ edges: [],
273
+ tags: [],
274
+ paths: [],
275
+ path_relations: []
276
+ }
277
+ });
278
+ }
279
+ return await this.registerProjectionAsWorkspace(projection_dir, input);
280
+ }
281
+ catch (error) {
282
+ if (starts_empty)
283
+ await removeEmptyGraphProjection(projection_dir);
284
+ throw error;
285
+ }
286
+ }
287
+ async registerProjectionAsWorkspace(projection_dir, input) {
288
+ const { graph } = await composeGraphProjection(projection_dir);
289
+ const issues = brokenValidationMessages(graph);
290
+ if (issues.length > 0) {
291
+ throw new Error(`Cannot initialize invalid projection: ${issues.join('; ')}`);
292
+ }
293
+ const root_hash = await this.graphs.writeGraph(graph);
294
+ if (input.source_repository_association_id) {
295
+ await this.bindCurrentSourceRepository({
296
+ source_repository_association_id: input.source_repository_association_id,
297
+ remote_name: input.source_remote_name
298
+ });
299
+ }
300
+ const workspace = this.store.registerForegroundWorkspace({
301
+ actor_kind: input.actor_kind ?? 'human',
302
+ git_worktree_path: this.git.repository_root,
303
+ git_dir: this.git.git_dir,
304
+ git_common_dir: this.git.git_common_dir,
305
+ git_head_commit_id: this.git.head,
306
+ sdlc_head_commit_id: root_hash,
307
+ attached_ref: this.git.ref
308
+ });
309
+ try {
310
+ await mkdir(controlDirectory(workspace), {
311
+ recursive: true,
312
+ mode: 0o700
313
+ });
314
+ const status = await this.getWorkspaceStatus(workspace.id);
315
+ await this.persistCurrentTopology(workspace, input.storage_mode);
316
+ return status;
317
+ }
318
+ catch (error) {
319
+ this.store.closeWorkspace({
320
+ actor_workspace_id: workspace.id,
321
+ workspace_id: workspace.id,
322
+ expected_head_commit_id: workspace.head_sdlc_commit_id
323
+ });
324
+ const binding = this.store.getSourceRepositoryBinding(this.git.git_common_dir);
325
+ if (binding) {
326
+ this.store.unbindSourceRepository({
327
+ git_common_dir: this.git.git_common_dir,
328
+ expected_source_repository_association_id: binding.source_repository_association_id
329
+ });
330
+ }
331
+ throw error;
332
+ }
333
+ }
334
+ async persistCurrentTopology(workspace, storage_mode, repository_root = this.git.repository_root, git_common_dir = this.git.git_common_dir) {
335
+ const binding = this.store.getSourceRepositoryBinding(git_common_dir);
336
+ const default_state_dir = resolveLocalRepositoryStateDirectory({
337
+ git_common_dir
338
+ });
339
+ const now = new Date().toISOString();
340
+ const written = await writeWorkspaceTopology({
341
+ repository_root,
342
+ topology: {
343
+ version: 1,
344
+ graph_repository_id: this.store.repository.repository_id,
345
+ workspace_id: workspace.id,
346
+ state_dir: this.state_dir,
347
+ storage_mode: storage_mode ??
348
+ (this.state_dir === default_state_dir
349
+ ? 'self_contained'
350
+ : 'machine_shared'),
351
+ source_repository_association_id: binding?.source_repository_association_id ?? null,
352
+ source_remote_identity: binding?.remote_identity ?? null,
353
+ created_at: now,
354
+ updated_at: now
355
+ }
356
+ });
357
+ if (repository_root === this.git.repository_root) {
358
+ this.topology = written.topology;
359
+ }
360
+ }
361
+ async bindCurrentSourceRepository(input) {
362
+ const remote = await discoverGitRemoteIdentity(this.git.repository_root, input.remote_name ??
363
+ this.store.getSourceRepositoryBinding(this.git.git_common_dir)
364
+ ?.remote_name ??
365
+ (await this.defaultSourceRemote(input.source_repository_association_id)));
366
+ return this.store.bindSourceRepository({
367
+ git_common_dir: this.git.git_common_dir,
368
+ source_repository_association_id: input.source_repository_association_id,
369
+ remote_name: remote.remote_name,
370
+ remote_url: remote.remote_url,
371
+ remote_identity: remote.repository_identity
372
+ });
373
+ }
374
+ /**
375
+ * The remote a new binding takes when none is named: the one the
376
+ * association is bound to from another clone, when this repository has a
377
+ * remote of that identity; else the remote its references are named from
378
+ * (namingRemote), else origin. A binding the repository already has keeps
379
+ * its own remote (bindCurrentSourceRepository).
380
+ */
381
+ async defaultSourceRemote(source_repository_association_id) {
382
+ const root = this.git.repository_root;
383
+ const naming = (await namingRemote(root)) ?? 'origin';
384
+ const bound = this.store.getSourceAssociationBinding(source_repository_association_id);
385
+ if (!bound)
386
+ return naming;
387
+ for (const name of new Set([
388
+ naming,
389
+ bound.remote_name,
390
+ ...(await gitRemoteNames(root))
391
+ ])) {
392
+ const identity = await discoverGitRemoteIdentity(root, name).catch(() => null);
393
+ if (identity?.repository_identity === bound.remote_identity)
394
+ return name;
395
+ }
396
+ return naming;
397
+ }
398
+ /**
399
+ * A binding made before init and attach bound the naming remote (2026-10-28)
400
+ * may name a fork's origin while its references name upstream: this moves
401
+ * it to upstream, with the topology of every attached checkout of the
402
+ * repository, so a workspace commit's remote identity names the repository
403
+ * its references name. Only origin gives way, only to upstream, as in
404
+ * repositoryNames, and only for a binding made before that rule, so a
405
+ * remote chosen since is kept. Only the binding row moves: a checkout's
406
+ * topology still naming origin matches it as moved, and opening the
407
+ * checkout rewrites it. Returns the identities it moved between, or null.
408
+ */
409
+ async realignForkBinding(repository_root = this.git.repository_root, git_common_dir = this.git.git_common_dir) {
410
+ const binding = this.store.getSourceRepositoryBinding(git_common_dir);
411
+ if (binding?.remote_name !== 'origin')
412
+ return null;
413
+ if (binding.created_at >= NAMING_REMOTE_RULE_AT)
414
+ return null;
415
+ if (!(await gitRemoteNames(repository_root)).includes('upstream')) {
416
+ return null;
417
+ }
418
+ if ((await namingRemote(repository_root)) !== 'upstream')
419
+ return null;
420
+ const origin = await discoverGitRemoteIdentity(repository_root, 'origin');
421
+ // A changed origin is the binding mismatch, reported where it is checked
422
+ if (origin.repository_identity !== binding.remote_identity)
423
+ return null;
424
+ const upstream = await discoverGitRemoteIdentity(repository_root, 'upstream');
425
+ if (upstream.repository_identity === binding.remote_identity)
426
+ return null;
427
+ // One store transaction: every checkout's topology still naming origin is
428
+ // read as moved (topologyBindingMatch), and rewritten when it is opened
429
+ const moved = this.store.rebindSourceRepositoryRemote({
430
+ git_common_dir,
431
+ expected_remote_name: 'origin',
432
+ remote_name: upstream.remote_name,
433
+ remote_url: upstream.remote_url,
434
+ remote_identity: upstream.repository_identity
435
+ });
436
+ if (!moved)
437
+ return null;
438
+ if (this.topology && repository_root === this.git.repository_root) {
439
+ this.topology = {
440
+ ...this.topology,
441
+ source_remote_identity: moved.remote_identity
442
+ };
443
+ }
444
+ return { from: binding.remote_identity, to: moved.remote_identity };
445
+ }
446
+ async attachCurrentSourceWorkspace(input) {
447
+ await assertLocalWorkspacePathsIgnored(this.git.repository_root);
448
+ if (this.store.getWorkspaceByGitWorktreePath(this.git.repository_root)) {
449
+ throw new Error('The current Git worktree is already registered.');
450
+ }
451
+ const projection_dir = join(this.git.repository_root, 'sdlc');
452
+ try {
453
+ await stat(projection_dir);
454
+ throw new Error('Refusing to attach over an existing local graph projection.');
455
+ }
456
+ catch (error) {
457
+ if (!(error instanceof Error) ||
458
+ !('code' in error) ||
459
+ error.code !== 'ENOENT') {
460
+ throw error;
461
+ }
462
+ }
463
+ const existing_binding = this.store.getSourceRepositoryBinding(this.git.git_common_dir);
464
+ await this.bindCurrentSourceRepository({
465
+ source_repository_association_id: input.source_repository_association_id,
466
+ remote_name: input.source_remote_name
467
+ });
468
+ const workspace = this.store.forkWorkspace({
469
+ actor_workspace_id: input.source_workspace_id,
470
+ expected_source_sdlc_head_commit_id: input.expected_source_sdlc_head_commit_id,
471
+ git_worktree_path: this.git.repository_root,
472
+ git_dir: this.git.git_dir,
473
+ git_common_dir: this.git.git_common_dir,
474
+ git_head_commit_id: this.git.head,
475
+ actor_kind: input.actor_kind ?? 'human',
476
+ task_scope: input.task_scope
477
+ });
478
+ try {
479
+ const graph = await this.readCommitGraph(workspace.head_sdlc_commit_id);
480
+ await this.materialize_projection({
481
+ projection_dir,
482
+ graph,
483
+ ...(input.full_projection
484
+ ? {}
485
+ : { scope: projectionScopeOfTaskScope(input.task_scope) })
486
+ });
487
+ await mkdir(controlDirectory(workspace), {
488
+ recursive: true,
489
+ mode: 0o700
490
+ });
491
+ const status = await this.getWorkspaceStatus(workspace.id);
492
+ await this.persistCurrentTopology(workspace, input.storage_mode);
493
+ return status;
494
+ }
495
+ catch (error) {
496
+ this.store.closeWorkspace({
497
+ actor_workspace_id: input.source_workspace_id,
498
+ workspace_id: workspace.id,
499
+ expected_head_commit_id: workspace.head_sdlc_commit_id
500
+ });
501
+ this.store.removeClosedWorkspaceRegistration({
502
+ workspace_id: workspace.id,
503
+ source_workspace_id: input.source_workspace_id,
504
+ git_worktree_path: this.git.repository_root
505
+ });
506
+ if (!existing_binding) {
507
+ this.store.unbindSourceRepository({
508
+ git_common_dir: this.git.git_common_dir,
509
+ expected_source_repository_association_id: input.source_repository_association_id
510
+ });
511
+ }
512
+ await rm(projection_dir, { recursive: true, force: true });
513
+ await rm(controlDirectory(workspace), { recursive: true, force: true });
514
+ throw error;
515
+ }
516
+ }
517
+ /**
518
+ * Attaches every other repository of the folder whose home this workspace's
519
+ * checkout belongs to (run in its plan coordinator), each from this
520
+ * workspace's head with its whole-repository scope (repositoryTaskScope).
521
+ * A member that cannot attach now (already attached, no node cites it yet,
522
+ * or attach refuses) is skipped with its reason, so a rerun picks it up.
523
+ */
524
+ async attachFolderMembers(input = {}) {
525
+ const source = this.currentActiveWorkspace();
526
+ if (source.source_workspace_id !== null) {
527
+ // Every worktree of the home reads the folder; members attach to the coordinator, never a task
528
+ throw new Error(`Run attach --all-members in the plan coordinator: workspace ${source.id} is a child task workspace.`);
529
+ }
530
+ const folder = await readFolderMembers(this.git.git_common_dir);
531
+ const graph = await this.readCommitGraph(source.head_sdlc_commit_id);
532
+ const result = {
533
+ version: 1,
534
+ folder: folder.folder,
535
+ source_workspace_id: source.id,
536
+ head_revision: source.head_sdlc_commit_id,
537
+ attached: [],
538
+ skipped: [...folder.skipped],
539
+ behind: [],
540
+ rebound: []
541
+ };
542
+ for (const member of folder.members) {
543
+ const skip = (reason) => result.skipped.push({ ...member, reason });
544
+ let runtime;
545
+ try {
546
+ runtime = await LocalWorkspaceRuntime.open({
547
+ cwd: member.path,
548
+ state_dir: this.state_dir,
549
+ graph_repository_id: this.store.repository.repository_id
550
+ });
551
+ }
552
+ catch (error) {
553
+ skip(error.message);
554
+ continue;
555
+ }
556
+ try {
557
+ const attached = runtime.store.getWorkspaceByGitWorktreePath(runtime.git.repository_root);
558
+ if (attached?.status === 'active') {
559
+ let reason = `already attached as workspace ${attached.id}`;
560
+ try {
561
+ const realigned = await runtime.realignForkBinding();
562
+ if (realigned)
563
+ result.rebound.push({ ...member, ...realigned });
564
+ }
565
+ catch (error) {
566
+ reason += `; its binding was not moved to upstream: ${error.message}`;
567
+ }
568
+ skip(reason);
569
+ // Attached from an older head: name the reconcile that catches it up
570
+ if (attached.source_workspace_id === source.id &&
571
+ attached.head_sdlc_commit_id !== source.head_sdlc_commit_id &&
572
+ !runtime.store.history.isAncestor(source.head_sdlc_commit_id, attached.head_sdlc_commit_id)) {
573
+ result.behind.push({
574
+ ...member,
575
+ workspace_id: attached.id,
576
+ head_revision: attached.head_sdlc_commit_id,
577
+ catch_up: `sdlc integrate reconcile --source ${source.id}`
578
+ });
579
+ }
580
+ continue;
581
+ }
582
+ const names = await repositoryNames(runtime.git.repository_root);
583
+ if (names.length === 0) {
584
+ skip('no remote names its repository, so no source reference can cite it');
585
+ continue;
586
+ }
587
+ const task_scope = repositoryTaskScope(graph, names);
588
+ if (!task_scope) {
589
+ skip(`no node cites ${names.join(' or ')} yet; run attach --all-members again once one does`);
590
+ continue;
591
+ }
592
+ const status = await runtime.attachCurrentSourceWorkspace({
593
+ source_workspace_id: source.id,
594
+ expected_source_sdlc_head_commit_id: source.head_sdlc_commit_id,
595
+ source_repository_association_id: member.name,
596
+ actor_kind: input.actor_kind,
597
+ storage_mode: input.storage_mode,
598
+ task_scope,
599
+ full_projection: input.full_projection
600
+ });
601
+ result.attached.push({
602
+ ...member,
603
+ workspace_id: status.workspace.id,
604
+ projection_dir: status.projection_dir,
605
+ nodes: task_scope.rules.filter((rule) => rule.entity_kind === 'node')
606
+ .length,
607
+ edges: task_scope.rules.filter((rule) => rule.entity_kind === 'edge')
608
+ .length
609
+ });
610
+ }
611
+ catch (error) {
612
+ skip(error.message);
613
+ }
614
+ finally {
615
+ runtime.close();
616
+ }
617
+ }
618
+ return result;
619
+ }
620
+ async getMultiRepositoryContext() {
621
+ const workspace = this.currentActiveWorkspace();
622
+ if (!workspace) {
623
+ throw new Error('The current Git worktree is not an active local workspace.');
624
+ }
625
+ const { git } = await this.authority.requireCurrent(workspace.id);
626
+ return {
627
+ version: 1,
628
+ graph_repository: {
629
+ repository_id: this.store.repository.repository_id,
630
+ state_dir: this.state_dir
631
+ },
632
+ source_repository: {
633
+ binding: this.store.getSourceRepositoryBinding(git.git_common_dir),
634
+ git_ref: git.ref,
635
+ git_head: git.head,
636
+ git_worktree_path: git.repository_root
637
+ },
638
+ graph_workspace: {
639
+ workspace_id: workspace.id,
640
+ source_workspace_id: workspace.source_workspace_id,
641
+ sdlc_branch: workspace.attached_ref,
642
+ fork_revision: workspace.fork_sdlc_commit_id,
643
+ head_revision: workspace.head_sdlc_commit_id,
644
+ projection_dir: projectionDirectory(workspace)
645
+ },
646
+ topology: {
647
+ topology_path: join(this.git.repository_root, '.sdlc', 'topology-v1.json'),
648
+ storage_mode: this.topology?.storage_mode ??
649
+ (this.state_dir ===
650
+ resolveLocalRepositoryStateDirectory({
651
+ git_common_dir: this.git.git_common_dir
652
+ })
653
+ ? 'self_contained'
654
+ : 'machine_shared')
655
+ }
656
+ };
657
+ }
658
+ async detachCurrentSourceWorkspace() {
659
+ const workspace = this.currentActiveWorkspace();
660
+ if (!workspace || !this.topology) {
661
+ throw new Error('The current checkout is not attached.');
662
+ }
663
+ if (workspace.actor_kind !== 'human') {
664
+ throw new Error('Only a human-owned workspace may detach itself.');
665
+ }
666
+ await this.authority.requireCurrent(workspace.id);
667
+ const topology = this.topology;
668
+ // Under the operation lock: detach may rewrite the projection, so no
669
+ // commit or edit command may run between its clean check and the close.
670
+ const { closed, topology_path } = await withWorkspaceOperationLock({
671
+ control_dir: controlDirectory(workspace),
672
+ operation: 'detach',
673
+ run: async () => {
674
+ const current = this.requireWorkspace(workspace.id);
675
+ await this.assertProjectionClean(current);
676
+ // The preserved projection must read without its workspace: a scoped
677
+ // one becomes the whole head graph, which it equals once clean.
678
+ if (await isScopedProjection(projectionDirectory(current))) {
679
+ await this.materialize_projection({
680
+ projection_dir: projectionDirectory(current),
681
+ graph: await this.readCommitGraph(current.head_sdlc_commit_id),
682
+ full: true
683
+ });
684
+ }
685
+ const topology_path = await removeWorkspaceTopology({
686
+ repository_root: this.git.repository_root,
687
+ graph_repository_id: topology.graph_repository_id,
688
+ workspace_id: topology.workspace_id
689
+ });
690
+ try {
691
+ return {
692
+ topology_path,
693
+ closed: this.store.closeWorkspace({
694
+ actor_workspace_id: current.id,
695
+ workspace_id: current.id,
696
+ expected_head_commit_id: current.head_sdlc_commit_id
697
+ })
698
+ };
699
+ }
700
+ catch (error) {
701
+ await writeWorkspaceTopology({
702
+ repository_root: this.git.repository_root,
703
+ topology
704
+ });
705
+ throw error;
706
+ }
707
+ }
708
+ });
709
+ this.topology = null;
710
+ const binding = this.store.getSourceRepositoryBinding(this.git.git_common_dir);
711
+ const unbound = binding
712
+ ? this.store.unbindSourceRepository({
713
+ git_common_dir: this.git.git_common_dir,
714
+ expected_source_repository_association_id: binding.source_repository_association_id
715
+ })
716
+ : null;
717
+ return {
718
+ version: 1,
719
+ status: 'detached',
720
+ workspace: closed,
721
+ topology_path,
722
+ projection_dir: projectionDirectory(workspace),
723
+ projection_preserved: true,
724
+ source_binding_removed: unbound?.removed ?? false
725
+ };
726
+ }
727
+ async createChildWorkspace(input) {
728
+ const { workspace: source, git } = await this.authority.requireCurrent(input.actor_workspace_id);
729
+ return withWorkspaceOperationLock({
730
+ control_dir: controlDirectory(source),
731
+ operation: 'create-child',
732
+ run: async () => {
733
+ const current_source = this.requireWorkspace(source.id);
734
+ await this.assertProjectionClean(current_source);
735
+ const target_path = resolve(input.target_path);
736
+ let created_git = null;
737
+ let created_workspace = null;
738
+ try {
739
+ created_git = await createDetachedGitWorktree({
740
+ source_cwd: current_source.git_worktree_path,
741
+ target_path,
742
+ commit_id: git.head
743
+ });
744
+ await assertLocalWorkspacePathsIgnored(created_git.repository_root);
745
+ const child = this.store.forkWorkspace({
746
+ actor_workspace_id: current_source.id,
747
+ expected_source_sdlc_head_commit_id: current_source.head_sdlc_commit_id,
748
+ git_worktree_path: created_git.repository_root,
749
+ git_dir: created_git.git_dir,
750
+ git_common_dir: created_git.git_common_dir,
751
+ git_head_commit_id: created_git.head,
752
+ actor_kind: input.actor_kind ?? 'ai',
753
+ task_scope: input.task_scope
754
+ });
755
+ created_workspace = child;
756
+ const graph = await this.readCommitGraph(child.head_sdlc_commit_id);
757
+ await this.materialize_projection({
758
+ projection_dir: projectionDirectory(child),
759
+ graph
760
+ });
761
+ await mkdir(controlDirectory(child), {
762
+ recursive: true,
763
+ mode: 0o700
764
+ });
765
+ await this.persistCurrentTopology(child, this.topology?.storage_mode, created_git.repository_root, created_git.git_common_dir);
766
+ return this.getWorkspaceStatus(child.id);
767
+ }
768
+ catch (error) {
769
+ if (created_workspace) {
770
+ try {
771
+ this.store.closeWorkspace({
772
+ actor_workspace_id: current_source.id,
773
+ workspace_id: created_workspace.id,
774
+ expected_head_commit_id: created_workspace.head_sdlc_commit_id
775
+ });
776
+ }
777
+ catch {
778
+ // Preserve the creation failure; the record remains inspectable.
779
+ }
780
+ }
781
+ if (created_git) {
782
+ await removeGitWorktree({
783
+ repository_cwd: this.git.repository_root,
784
+ target_path: created_git.repository_root,
785
+ force: true
786
+ }).catch(() => undefined);
787
+ }
788
+ throw error;
789
+ }
790
+ }
791
+ });
792
+ }
793
+ listWorkspaces() {
794
+ return this.store.listWorkspaces();
795
+ }
796
+ /**
797
+ * Replaces a child workspace's declared task scope, for example when a plan
798
+ * grows. The new scope must still cover the workspace's own changes, which
799
+ * are measured from its merge base with its source.
800
+ */
801
+ async replaceTaskScope(input) {
802
+ const { workspace: actor } = await this.authority.requireCurrent(input.actor_workspace_id);
803
+ const workspace = this.requireWorkspace(input.workspace_id);
804
+ const task_scope = local_workspace_task_scope_schema.parse(input.task_scope);
805
+ return withWorkspaceOperationLock({
806
+ control_dir: controlDirectory(workspace),
807
+ operation: 'scope',
808
+ run: async () => {
809
+ const current = this.requireWorkspace(workspace.id);
810
+ const source = current.source_workspace_id
811
+ ? this.requireWorkspace(current.source_workspace_id)
812
+ : null;
813
+ let base_commit_id = current.fork_sdlc_commit_id;
814
+ if (source) {
815
+ try {
816
+ base_commit_id = this.store.history.findMergeBase(current.head_sdlc_commit_id, source.head_sdlc_commit_id);
817
+ }
818
+ catch {
819
+ // Keep the fork commit when the heads share no local ancestor.
820
+ }
821
+ }
822
+ assertGraphChangesWithinTaskScope({
823
+ base_graph: await this.readCommitGraph(base_commit_id),
824
+ current_graph: await this.readCommitGraph(current.head_sdlc_commit_id),
825
+ task_scope
826
+ });
827
+ const replaced = this.store.replaceWorkspaceTaskScope({
828
+ actor_workspace_id: actor.id,
829
+ workspace_id: current.id,
830
+ expected_head_commit_id: current.head_sdlc_commit_id,
831
+ task_scope
832
+ });
833
+ // A scoped projection gains files for the entities the scope adds.
834
+ const projection_dir = projectionDirectory(current);
835
+ if (await isScopedProjection(projection_dir)) {
836
+ await extendScopedProjection({
837
+ projection_dir,
838
+ graph: await this.readCommitGraph(current.head_sdlc_commit_id),
839
+ scope: projectionScopeOfTaskScope(task_scope)
840
+ });
841
+ }
842
+ return { version: 1, workspace_id: current.id, ...replaced };
843
+ }
844
+ });
845
+ }
846
+ /**
847
+ * Removes one entity file from the current workspace's projection and, when
848
+ * the head has that entity, records the deletion for the next commit, like
849
+ * hosted `sdlc rm`. The commit still enforces the task scope.
850
+ */
851
+ async removeEntity(input) {
852
+ const { workspace } = await this.authority.requireCurrent(input.actor_workspace_id);
853
+ return withWorkspaceOperationLock({
854
+ control_dir: controlDirectory(workspace),
855
+ operation: 'remove',
856
+ run: async () => {
857
+ const current = this.requireWorkspace(workspace.id);
858
+ const head_graph = await this.readCommitGraph(current.head_sdlc_commit_id);
859
+ const removed = await removeProjectionEntity({
860
+ projection_dir: projectionDirectory(current),
861
+ relative_path: input.relative_path,
862
+ allow_missing: (entry) => graphHasProjectionEntity(head_graph, entry)
863
+ });
864
+ const deletion_recorded = graphHasProjectionEntity(head_graph, removed);
865
+ if (deletion_recorded) {
866
+ this.store.recordWorkspaceDeletion({
867
+ workspace_id: current.id,
868
+ entity_kind: removed.entity_kind,
869
+ entity_id: removed.entity_id
870
+ });
871
+ }
872
+ return {
873
+ version: 1,
874
+ workspace_id: current.id,
875
+ relative_path: removed.relative_path,
876
+ deletion_recorded
877
+ };
878
+ }
879
+ });
880
+ }
881
+ /**
882
+ * Runs a `sdlc workspace doc` command against the current workspace's
883
+ * projection, under the same authority check and operation lock as `rm`
884
+ * and `commit`, so it never edits a projection another operation is using.
885
+ */
886
+ async withDocumentProjection(input) {
887
+ const { workspace } = await this.authority.requireCurrent(input.actor_workspace_id);
888
+ return withWorkspaceOperationLock({
889
+ control_dir: controlDirectory(workspace),
890
+ operation: `doc:${input.operation}`,
891
+ run: async () => {
892
+ const projection_dir = projectionDirectory(this.requireWorkspace(workspace.id));
893
+ // These commands read and write entity files directly, so they need
894
+ // every entity on disk.
895
+ if (await isScopedProjection(projection_dir)) {
896
+ throw new Error('This projection holds only its task scope (workspace attach); run doc, guide, journey and test commands in a workspace with the whole graph, such as a plan workspace from sdlc workspace create, or attach with --full-projection.');
897
+ }
898
+ return input.run(projection_dir);
899
+ }
900
+ });
901
+ }
902
+ /**
903
+ * Writes a workspace commit's graph as a canonical portable graph file, the
904
+ * format `sdlc save --graph-file` reads. Defaults to the workspace head.
905
+ */
906
+ async exportWorkspaceGraph(input) {
907
+ const workspace = this.requireWorkspace(input.workspace_id);
908
+ const view = input.view ?? 'planned';
909
+ const commit_id = input.commit_id ?? workspace.head_sdlc_commit_id;
910
+ if (commit_id !== workspace.head_sdlc_commit_id &&
911
+ !this.store.history.isAncestor(commit_id, workspace.head_sdlc_commit_id)) {
912
+ throw new Error('Export commit is not in the workspace history.');
913
+ }
914
+ if (view === 'built' && commit_id !== workspace.head_sdlc_commit_id) {
915
+ throw new Error('The built view reads the workspace head only.');
916
+ }
917
+ const built = view === 'built'
918
+ ? await this.integration_runtime.readBuiltView({
919
+ coordinator_workspace_id: workspace.id
920
+ })
921
+ : null;
922
+ const graph = built?.graph ?? (await this.readCommitGraph(commit_id));
923
+ const output_path = resolve(input.output_path);
924
+ const temporary_path = `${output_path}.tmp-${randomUUID()}`;
925
+ try {
926
+ await writeFile(temporary_path, `${stringifyCanonicalGraph(canonicalizePortableGraph(graph))}\n`, { encoding: 'utf8', flag: 'wx' });
927
+ await rename(temporary_path, output_path);
928
+ }
929
+ catch (error) {
930
+ await rm(temporary_path, { force: true });
931
+ throw error;
932
+ }
933
+ return {
934
+ version: 1,
935
+ view,
936
+ workspace_id: workspace.id,
937
+ commit_id,
938
+ root_hash: built
939
+ ? localGraphRootHash(canonicalizePortableGraph(built.graph))
940
+ : this.commitRoot(commit_id),
941
+ output_path,
942
+ ...(built
943
+ ? {
944
+ marked_for_removal: built.marked_for_removal,
945
+ left_out_orphans: built.left_out_orphans,
946
+ validation: built.validation
947
+ }
948
+ : {})
949
+ };
950
+ }
951
+ /** The workspace's pending claims: merged under a gate and not landed. */
952
+ listPendingClaims(input) {
953
+ const workspace = this.requireWorkspace(input.workspace_id);
954
+ return listClaims({
955
+ workspace_id: workspace.id,
956
+ pending: this.store.built_content.listPendingClaims(workspace.id)
957
+ });
958
+ }
959
+ /**
960
+ * Each node's claim status in a view (`sdlc workspace status`), read from a
961
+ * coordinator's committed head and its pending claims. A plan workspace (one
962
+ * with no active plans under it) reads its coordinator, whose head is what
963
+ * the plans merged into. Source
964
+ * references are checked against `commit` in this checkout's repository.
965
+ * Read-only.
966
+ */
967
+ async workspaceClaimStatus(input) {
968
+ const asked = this.requireWorkspace(input.workspace_id);
969
+ const workspace = this.claimReadWorkspace(asked);
970
+ const result = await claimStatus({
971
+ graph: await this.readCommitGraph(workspace.head_sdlc_commit_id),
972
+ pending: this.store.built_content.listPendingClaims(workspace.id),
973
+ workspace_id: workspace.id,
974
+ graph_commit_id: workspace.head_sdlc_commit_id,
975
+ git: gitClaimStatusReader(this.git.repository_root),
976
+ // Checked against main by default, as claim_status.py is: a plan's or the
977
+ // coordinator's HEAD is the planning branch, not the code.
978
+ commit: input.commit ?? 'main',
979
+ environments: input.environments ?? [],
980
+ cites_code: input.cites_code ?? [],
981
+ view: input.view,
982
+ nodes: input.nodes,
983
+ status: input.status,
984
+ stale_only: input.stale_only,
985
+ behind_only: input.behind_only
986
+ });
987
+ return workspace.id === asked.id
988
+ ? result
989
+ : { ...result, asked_workspace_id: asked.id };
990
+ }
991
+ /**
992
+ * The workspace a claim read answers from: a coordinator has active plans
993
+ * under it and reads itself; a plan reads the coordinator it merges into.
994
+ */
995
+ claimReadWorkspace(asked) {
996
+ const coordinates = this.listWorkspaces().some((other) => other.source_workspace_id === asked.id && other.status === 'active');
997
+ return asked.source_workspace_id && !coordinates
998
+ ? this.requireWorkspace(asked.source_workspace_id)
999
+ : asked;
1000
+ }
1001
+ /**
1002
+ * `sdlc variants`: each Variant Set in the committed graph with its gap and
1003
+ * its sync entities' claim status (docs/specs/graph-variants.md, P3a), and
1004
+ * with an environment, its members' forms by claim status. It reads the
1005
+ * graph claim status reads, so a plan reads its coordinator. Read-only.
1006
+ */
1007
+ async workspaceVariants(input) {
1008
+ const asked = this.requireWorkspace(input.workspace_id);
1009
+ const workspace = this.claimReadWorkspace(asked);
1010
+ const graph = await this.readCommitGraph(workspace.head_sdlc_commit_id);
1011
+ const environments = input.environments ?? [];
1012
+ const read = (view, nodes) => claimStatus({
1013
+ graph,
1014
+ pending: this.store.built_content.listPendingClaims(workspace.id),
1015
+ workspace_id: workspace.id,
1016
+ graph_commit_id: workspace.head_sdlc_commit_id,
1017
+ git: gitClaimStatusReader(this.git.repository_root),
1018
+ commit: input.commit ?? 'main',
1019
+ environments,
1020
+ view,
1021
+ cites_code: [],
1022
+ nodes
1023
+ });
1024
+ const claims = new Map();
1025
+ let form_claims;
1026
+ let claim_error;
1027
+ try {
1028
+ const sync_ids = variantSyncIds(graph);
1029
+ if (sync_ids.length) {
1030
+ for (const claim of (await read('planned', sync_ids)).claims ?? []) {
1031
+ claims.set(claim.node, {
1032
+ status: claim.status,
1033
+ environment: claim.environment
1034
+ });
1035
+ }
1036
+ }
1037
+ // Only an environment can read a form as released, so members are read
1038
+ // only with one, and as built: a plan's pending gate claim on a form, or
1039
+ // on a node realizing it, must not hide a release.
1040
+ const form_ids = environments.length ? variantMemberFormIds(graph) : [];
1041
+ if (form_ids.length) {
1042
+ const built = await read('built', form_ids);
1043
+ const unresolved = built.environments.filter((environment) => !environment.resolved);
1044
+ if (unresolved.length) {
1045
+ // Nothing reads released against a ref that does not resolve, which
1046
+ // would make every live member look contradicted.
1047
+ claim_error = `environment ${unresolved
1048
+ .map((environment) => `${environment.key}=${environment.ref}`)
1049
+ .join(', ')} does not resolve, so members are not checked`;
1050
+ }
1051
+ else {
1052
+ form_claims = new Map((built.claims ?? []).map((claim) => [
1053
+ claim.node,
1054
+ { status: claim.status, environment: claim.environment }
1055
+ ]));
1056
+ }
1057
+ }
1058
+ }
1059
+ catch (error) {
1060
+ // The gap stands without claim status, for example where main is no ref.
1061
+ claim_error = error instanceof Error ? error.message : String(error);
1062
+ form_claims = undefined;
1063
+ }
1064
+ return {
1065
+ ...variantsReport({ graph, claims, form_claims }),
1066
+ ...(claim_error ? { claim_error } : {}),
1067
+ workspace_id: workspace.id,
1068
+ ...(workspace.id === asked.id ? {} : { asked_workspace_id: asked.id })
1069
+ };
1070
+ }
1071
+ /**
1072
+ * Claim freshness of the nodes and edges with evidence in the current
1073
+ * workspace's projection (`sdlc workspace evidence status`): what sign
1074
+ * writes, uncommitted edits included. Read-only.
1075
+ */
1076
+ async workspaceEvidenceStatus(input) {
1077
+ const { workspace } = await this.authority.requireCurrent(input.actor_workspace_id);
1078
+ const current = this.requireWorkspace(workspace.id);
1079
+ const { graph } = await composeGraphProjection(projectionDirectory(current), undefined, { base: () => this.readCommitGraph(current.head_sdlc_commit_id) });
1080
+ return evidenceStatus({
1081
+ graph,
1082
+ workspace_id: workspace.id,
1083
+ nodes: input.nodes,
1084
+ edges: input.edges,
1085
+ behind_only: input.behind_only
1086
+ });
1087
+ }
1088
+ /**
1089
+ * `sdlc workspace evidence sign`: pins the selected entities' references to
1090
+ * their current object hash in the projection, after verifying each code
1091
+ * reference it signs at `commit` (default HEAD). Writes nothing when any
1092
+ * does not hold. `changed` selects every node and edge the plan changed
1093
+ * since its merge base with the coordinator. A signed entity outside a
1094
+ * plan's task scope widens the scope to its meta. Commits nothing.
1095
+ * `repository` is the Git repository whose code the references are
1096
+ * verified in (default this workspace's own), so a graph that cites
1097
+ * another repository signs that repository's references against its code.
1098
+ */
1099
+ async signEvidence(input) {
1100
+ const { workspace } = await this.authority.requireCurrent(input.actor_workspace_id);
1101
+ return withWorkspaceOperationLock({
1102
+ control_dir: controlDirectory(workspace),
1103
+ operation: 'evidence-sign',
1104
+ run: () => this.signEvidenceLocked(workspace.id, input)
1105
+ });
1106
+ }
1107
+ /** signEvidence's work, for a caller already holding the workspace operation lock. */
1108
+ async signEvidenceLocked(workspace_id, input) {
1109
+ const current = this.requireWorkspace(workspace_id);
1110
+ const projection_dir = projectionDirectory(current);
1111
+ const { graph } = await composeGraphProjection(projection_dir, undefined, {
1112
+ base: () => this.readCommitGraph(current.head_sdlc_commit_id)
1113
+ });
1114
+ const repository = input.repository
1115
+ ? resolve(input.repository)
1116
+ : this.git.repository_root;
1117
+ const git = gitClaimStatusReader(repository);
1118
+ const commit = await git.resolve(input.commit ?? 'HEAD');
1119
+ if (commit === null) {
1120
+ throw new Error(input.repository
1121
+ ? `${input.commit ?? 'HEAD'} is not a commit in ${repository}.`
1122
+ : `${input.commit ?? 'HEAD'} is not a commit in this repository.`);
1123
+ }
1124
+ const repository_names = await repositoryNames(repository, commit);
1125
+ const nodes = [...(input.nodes ?? [])];
1126
+ const edges = [...(input.edges ?? [])];
1127
+ if (input.changed) {
1128
+ if (!current.source_workspace_id) {
1129
+ throw new Error('sign --changed reads what a plan changed, and this workspace has no coordinator.');
1130
+ }
1131
+ const source = this.requireWorkspace(current.source_workspace_id);
1132
+ let base_commit_id = current.fork_sdlc_commit_id;
1133
+ try {
1134
+ base_commit_id = this.store.history.findMergeBase(current.head_sdlc_commit_id, source.head_sdlc_commit_id);
1135
+ }
1136
+ catch {
1137
+ // Keep the fork commit when the heads share no local ancestor.
1138
+ }
1139
+ const present = {
1140
+ node: new Set(graph.nodes.map((node) => node.id)),
1141
+ edge: new Set(graph.edges.map((edge) => edge.id))
1142
+ };
1143
+ for (const change of graphChangeCoordinates({
1144
+ base_graph: await this.readCommitGraph(base_commit_id),
1145
+ current_graph: graph
1146
+ })) {
1147
+ if (change.entity_kind === 'node' && present.node.has(change.entity_id))
1148
+ nodes.push(change.entity_id);
1149
+ if (change.entity_kind === 'edge' && present.edge.has(change.entity_id))
1150
+ edges.push(change.entity_id);
1151
+ }
1152
+ }
1153
+ const plan = await planEvidenceSign({
1154
+ graph,
1155
+ git,
1156
+ commit,
1157
+ now: (input.now ?? new Date()).toISOString().replace(/\.\d{3}Z$/, 'Z'),
1158
+ nodes: [...new Set(nodes)],
1159
+ edges: [...new Set(edges)],
1160
+ references: input.references,
1161
+ repository_names
1162
+ });
1163
+ const result = {
1164
+ version: 1,
1165
+ workspace_id: current.id,
1166
+ ...plan
1167
+ };
1168
+ if (plan.status !== 'signed')
1169
+ return result;
1170
+ // A scoped projection first gains the file of each entity it signs.
1171
+ if (await isScopedProjection(projection_dir)) {
1172
+ await extendScopedProjection({
1173
+ projection_dir,
1174
+ graph,
1175
+ scope: projectionScopeOfTaskScope({
1176
+ rules: plan.changes.map((change) => ({
1177
+ entity_kind: change.kind,
1178
+ entity_id: change.id
1179
+ }))
1180
+ })
1181
+ });
1182
+ }
1183
+ // Widen the scope next: a failure while writing then leaves signed
1184
+ // entities the scope already covers, never ones it refuses at commit.
1185
+ const scope = this.store.getWorkspaceTaskScope(current.id);
1186
+ const META = '/data/meta';
1187
+ const widened = scope
1188
+ ? plan.changes
1189
+ .map((change) => ({
1190
+ entity_kind: change.kind,
1191
+ entity_id: change.id,
1192
+ field_path: META
1193
+ }))
1194
+ .filter((candidate) => !scope.rules.some((rule) => ruleCovers(rule, candidate)))
1195
+ : [];
1196
+ if (scope && widened.length) {
1197
+ this.store.replaceWorkspaceTaskScope({
1198
+ actor_workspace_id: current.id,
1199
+ workspace_id: current.id,
1200
+ expected_head_commit_id: current.head_sdlc_commit_id,
1201
+ task_scope: { ...scope, rules: [...scope.rules, ...widened] }
1202
+ });
1203
+ }
1204
+ for (const change of plan.changes) {
1205
+ const path = join(projection_dir, change.kind === 'node' ? 'nodes' : 'edges', `${change.id}.json`);
1206
+ const entity = JSON.parse(await readFile(path, 'utf8'));
1207
+ entity.data = {
1208
+ ...entity.data,
1209
+ meta: signedMeta(entity.data?.meta, change.evidence)
1210
+ };
1211
+ await writeJsonAtomically(path, entity);
1212
+ }
1213
+ return {
1214
+ ...result,
1215
+ widened_scope: widened.map((rule) => ({
1216
+ kind: rule.entity_kind,
1217
+ id: rule.entity_id
1218
+ }))
1219
+ };
1220
+ }
1221
+ /**
1222
+ * `sdlc workspace evidence record`: writes each node's source references
1223
+ * (already verified at `commit` by the caller) into the current workspace's
1224
+ * projection, as sources.py record does, each replacing a reference of the
1225
+ * same identity under any name the repository goes by. With `sign`, signs
1226
+ * exactly those references under the same lock, and restores the node
1227
+ * files when the sign fails or is refused, so nothing is left recorded
1228
+ * unsigned. Refuses, before writing anything, a node with no file in the
1229
+ * projection or holding legacy source_evidence. A node whose references
1230
+ * would pass 100 is left out and returned under over_limit with the count
1231
+ * it would have (sources.py record). Commits nothing.
1232
+ */
1233
+ async recordSourceReferences(input) {
1234
+ const { workspace } = await this.authority.requireCurrent(input.actor_workspace_id);
1235
+ const repository = resolve(input.repository);
1236
+ return withWorkspaceOperationLock({
1237
+ control_dir: controlDirectory(workspace),
1238
+ operation: 'evidence-record',
1239
+ run: async () => {
1240
+ const current = this.requireWorkspace(workspace.id);
1241
+ const projection_dir = projectionDirectory(current);
1242
+ const names = await repositoryNames(repository, input.commit);
1243
+ if (!names.length)
1244
+ throw new Error(`the ${await trackedRemoteName(repository)} remote names no <owner>/<name>, so references have no repository identity`);
1245
+ const observed_at = (input.now ?? new Date())
1246
+ .toISOString()
1247
+ .replace(/\.\d{3}Z$/, 'Z');
1248
+ const updates = [];
1249
+ const ids = [];
1250
+ const over_limit = {};
1251
+ for (const [node_id, specs] of Object.entries(input.nodes)) {
1252
+ const path = join(projection_dir, 'nodes', `${node_id}.json`);
1253
+ let node;
1254
+ try {
1255
+ node = JSON.parse(await readFile(path, 'utf8'));
1256
+ }
1257
+ catch {
1258
+ throw new Error(`node ${node_id} is not in the workspace's projection`);
1259
+ }
1260
+ const data = { ...(node.data ?? {}) };
1261
+ const meta = { ...(data.meta ?? {}) };
1262
+ // As sources.py tests it: an empty legacy block holds nothing to convert.
1263
+ const legacy = meta.source_evidence;
1264
+ if (legacy &&
1265
+ (typeof legacy !== 'object' || Object.keys(legacy).length > 0))
1266
+ throw new Error(`node ${node_id} holds legacy source_evidence; convert it to meta.evidence (the current form) before recording on it`);
1267
+ const references = specs.map((spec) => sourceReference(node_id, spec, input.commit, names[0], observed_at));
1268
+ try {
1269
+ meta.evidence = evidenceWith(meta, references, names);
1270
+ }
1271
+ catch (error) {
1272
+ if (!(error instanceof ReferenceLimitError))
1273
+ throw error;
1274
+ over_limit[node_id] = error.count;
1275
+ continue;
1276
+ }
1277
+ ids.push(...references.map((ref) => String(ref.reference_id)));
1278
+ updates.push({ path, node: { ...node, data: { ...data, meta } } });
1279
+ }
1280
+ const recorded = Object.keys(input.nodes).filter((node_id) => !(node_id in over_limit));
1281
+ const before = new Map();
1282
+ for (const { path } of updates)
1283
+ before.set(path, await readFile(path));
1284
+ const restore = async () => {
1285
+ for (const [path, content] of before)
1286
+ await writeFile(path, content);
1287
+ };
1288
+ try {
1289
+ for (const { path, node } of updates)
1290
+ await writeJsonAtomically(path, node);
1291
+ if (!input.sign || !updates.length)
1292
+ return {
1293
+ workspace_id: current.id,
1294
+ repository: names[0],
1295
+ recorded,
1296
+ over_limit
1297
+ };
1298
+ const signed = await this.signEvidenceLocked(current.id, {
1299
+ references: ids,
1300
+ commit: input.commit,
1301
+ repository,
1302
+ now: input.now
1303
+ });
1304
+ if (signed.status === 'refused')
1305
+ throw new Error(`signing the recorded references was refused: ${signed.failures
1306
+ .map((f) => `${f.kind} ${f.id}: ${f.problem}`)
1307
+ .join('; ') || 'no failure named'}`);
1308
+ return {
1309
+ workspace_id: current.id,
1310
+ repository: names[0],
1311
+ recorded,
1312
+ over_limit,
1313
+ signed
1314
+ };
1315
+ }
1316
+ catch (error) {
1317
+ await restore();
1318
+ throw error;
1319
+ }
1320
+ }
1321
+ });
1322
+ }
1323
+ /**
1324
+ * The source references of a workspace's graph as built (`sdlc workspace
1325
+ * evidence lookup|check|coverage`): its projection as it stands, so
1326
+ * references just recorded count before a commit, with each pending claim
1327
+ * at its built content, as sources.py index reads it with
1328
+ * owners.built_claims. Only nodes with a pending claim come from the built
1329
+ * view: one left out there as an orphan, under a parent never built, still
1330
+ * names its files.
1331
+ */
1332
+ async workspaceSourceIndex(input) {
1333
+ const workspace = this.requireWorkspace(input.workspace_id);
1334
+ const { graph } = await composeGraphProjection(projectionDirectory(workspace), undefined, { base: () => this.readCommitGraph(workspace.head_sdlc_commit_id) });
1335
+ const pending = this.store.built_content.listPendingClaimContent(workspace.id);
1336
+ const built = await readBuiltGraph({
1337
+ graph,
1338
+ pending,
1339
+ readGraph: (commit_id) => this.readCommitGraph(commit_id)
1340
+ });
1341
+ const claimed = new Set(pending
1342
+ .filter((claim) => claim.entity_kind === 'node')
1343
+ .map((claim) => claim.entity_id));
1344
+ const nodes = [
1345
+ ...graph.nodes.filter((node) => !claimed.has(node.id)),
1346
+ ...built.graph.nodes.filter((node) => claimed.has(node.id))
1347
+ ];
1348
+ return { workspace_id: workspace.id, ...sourceIndex({ ...graph, nodes }) };
1349
+ }
1350
+ /**
1351
+ * What changed repository files reach in a workspace's committed head graph
1352
+ * (`sdlc workspace blast-radius`). Read-only: it answers from the head, so
1353
+ * uncommitted edits never change the answer. The planned view composes the
1354
+ * projection only to say when it differs (`projection_dirty` and a warning);
1355
+ * the built view, read by readiness on every landing, skips that compose.
1356
+ */
1357
+ async workspaceBlastRadius(input) {
1358
+ const workspace = this.requireWorkspace(input.workspace_id);
1359
+ const commit_id = workspace.head_sdlc_commit_id;
1360
+ const view = input.view ?? 'planned';
1361
+ const built = view === 'built'
1362
+ ? await this.integration_runtime.readBuiltView({
1363
+ coordinator_workspace_id: workspace.id
1364
+ })
1365
+ : null;
1366
+ const head = built ? null : await this.readCommitGraph(commit_id);
1367
+ const dirty = head ? await this.projectionDirty(workspace, head) : null;
1368
+ const result = await blastRadius({
1369
+ graph: built?.graph ?? head,
1370
+ files: input.files,
1371
+ // An unqualified changed file belongs to the repository read here.
1372
+ repository_names: await repositoryNames(this.git.repository_root),
1373
+ workspace_id: workspace.id,
1374
+ commit_id,
1375
+ view,
1376
+ ...(built
1377
+ ? {
1378
+ marked_for_removal: built.marked_for_removal,
1379
+ validation: built.validation
1380
+ }
1381
+ : {})
1382
+ });
1383
+ if (!dirty)
1384
+ return result;
1385
+ return {
1386
+ ...result,
1387
+ ...(dirty.dirty === null ? {} : { projection_dirty: dirty.dirty }),
1388
+ ...(dirty.warning ? { warnings: [dirty.warning] } : {})
1389
+ };
1390
+ }
1391
+ /** `sdlc workspace find`: nodes ranked by the words they match (graph-read.ts). */
1392
+ async workspaceFind(input) {
1393
+ const read = await this.readGraphForView(input.workspace_id, input.view, 'find');
1394
+ const found = findGraphEntities({
1395
+ graph: read.graph,
1396
+ terms: input.terms,
1397
+ types: input.types,
1398
+ origin: input.origin,
1399
+ limit: input.limit,
1400
+ view: read.view,
1401
+ workspace_id: read.workspace_id,
1402
+ commit_id: read.commit_id,
1403
+ removed: read.removed
1404
+ });
1405
+ const warnings = [...(read.warnings ?? []), ...(found.warnings ?? [])];
1406
+ return {
1407
+ ...found,
1408
+ ...read.extra,
1409
+ ...(warnings.length > 0 ? { warnings } : {})
1410
+ };
1411
+ }
1412
+ /** `sdlc workspace show`: nodes and edges read compactly with their relations (graph-read.ts). */
1413
+ async workspaceShow(input) {
1414
+ const read = await this.readGraphForView(input.workspace_id, input.view, 'show');
1415
+ return {
1416
+ ...showGraphEntities({
1417
+ graph: read.graph,
1418
+ ids: input.ids,
1419
+ budget: input.budget,
1420
+ view: read.view,
1421
+ workspace_id: read.workspace_id,
1422
+ commit_id: read.commit_id,
1423
+ removed: read.removed
1424
+ }),
1425
+ ...read.extra,
1426
+ ...(read.warnings ? { warnings: read.warnings } : {})
1427
+ };
1428
+ }
1429
+ /**
1430
+ * The graph a read command answers from: the committed head, or the built
1431
+ * view, as blast radius reads it, with what blast radius reports beside it.
1432
+ */
1433
+ async readGraphForView(workspace_id, requested, command) {
1434
+ const workspace = this.requireWorkspace(workspace_id);
1435
+ const commit_id = workspace.head_sdlc_commit_id;
1436
+ const view = requested ?? 'planned';
1437
+ if (view === 'built') {
1438
+ const built = await this.integration_runtime.readBuiltView({
1439
+ coordinator_workspace_id: workspace.id
1440
+ });
1441
+ return {
1442
+ graph: built.graph,
1443
+ view,
1444
+ workspace_id: workspace.id,
1445
+ commit_id,
1446
+ removed: new Set(built.marked_for_removal.map((entity) => entity.entity_id)),
1447
+ extra: {
1448
+ marked_for_removal: built.marked_for_removal,
1449
+ validation: built.validation
1450
+ }
1451
+ };
1452
+ }
1453
+ const graph = await this.readCommitGraph(commit_id);
1454
+ const dirty = await this.projectionDirty(workspace, graph, command);
1455
+ return {
1456
+ graph,
1457
+ view,
1458
+ workspace_id: workspace.id,
1459
+ commit_id,
1460
+ extra: dirty.dirty === null ? {} : { projection_dirty: dirty.dirty },
1461
+ ...(dirty.warning ? { warnings: [dirty.warning] } : {})
1462
+ };
1463
+ }
1464
+ /** Whether a workspace's projection differs from its committed head graph, with the warning blast radius gives. */
1465
+ async projectionDirty(workspace, head, command = 'blast-radius') {
1466
+ let projection;
1467
+ try {
1468
+ ({ graph: projection } = await composeGraphProjection(projectionDirectory(workspace), undefined, { base: async () => head }));
1469
+ }
1470
+ catch (error) {
1471
+ return {
1472
+ dirty: null,
1473
+ warning: `The workspace projection could not be compared with its committed head (${error instanceof Error ? error.message : String(error)}); this answer reads the committed head.`
1474
+ };
1475
+ }
1476
+ if (localGraphRootHash(projection) ===
1477
+ this.commitRoot(workspace.head_sdlc_commit_id)) {
1478
+ return { dirty: false, warning: null };
1479
+ }
1480
+ // commit takes the workspace of the directory it runs in, so name another one's worktree.
1481
+ const where = workspace.git_worktree_path === this.git.repository_root
1482
+ ? ''
1483
+ : ` in ${workspace.git_worktree_path}`;
1484
+ return {
1485
+ dirty: true,
1486
+ warning: `The workspace projection has uncommitted changes, such as source references sources record just wrote; this answer reads the committed head. Run sdlc workspace commit --message "<what changed>"${where}, then run ${command} again.`
1487
+ };
1488
+ }
1489
+ /**
1490
+ * Compares the committed plans of a coordinator's active child workspaces
1491
+ * pairwise. Read-only: it never locks, commits, or moves a workspace.
1492
+ */
1493
+ async compareChildWorkspacePlans(input) {
1494
+ const coordinator = this.requireWorkspace(input.coordinator_workspace_id);
1495
+ const coordinator_commit_id = coordinator.head_sdlc_commit_id;
1496
+ const children = this.store
1497
+ .listChildWorkspaces(coordinator.id)
1498
+ .filter((workspace) => workspace.status === 'active');
1499
+ const graphs = new Map();
1500
+ const readGraph = (commit_id) => {
1501
+ let graph = graphs.get(commit_id);
1502
+ if (!graph) {
1503
+ graph = this.readCommitGraph(commit_id);
1504
+ graphs.set(commit_id, graph);
1505
+ }
1506
+ return graph;
1507
+ };
1508
+ const coordinator_graph = await readGraph(coordinator_commit_id);
1509
+ const plans = [];
1510
+ const projection_dirty = new Map();
1511
+ for (const child of children) {
1512
+ let base_commit_id;
1513
+ try {
1514
+ // Integration and reconciliation record merge parents, so the merge
1515
+ // base excludes work this plan already landed or pulled in.
1516
+ base_commit_id = this.store.history.findMergeBase(child.head_sdlc_commit_id, coordinator_commit_id);
1517
+ }
1518
+ catch {
1519
+ base_commit_id = child.fork_sdlc_commit_id;
1520
+ }
1521
+ // A plan with no commits of its own (it landed, or was never edited)
1522
+ // compares as the coordinator head: neither of its graphs nor its
1523
+ // projection is read, and whether it has uncommitted edits is left
1524
+ // unknown. Most open plans are such plans, and composing each one's
1525
+ // projection (thousands of files, then a root hash) cost most of a run
1526
+ // once they numbered over a hundred (iss-130).
1527
+ const held = base_commit_id === child.head_sdlc_commit_id;
1528
+ const [base_graph, head_graph] = held
1529
+ ? [coordinator_graph, coordinator_graph]
1530
+ : await Promise.all([
1531
+ readGraph(base_commit_id),
1532
+ readGraph(child.head_sdlc_commit_id)
1533
+ ]);
1534
+ plans.push({
1535
+ workspace_id: child.id,
1536
+ base_commit_id,
1537
+ head_commit_id: child.head_sdlc_commit_id,
1538
+ base_graph,
1539
+ head_graph
1540
+ });
1541
+ if (held) {
1542
+ projection_dirty.set(child.id, null);
1543
+ continue;
1544
+ }
1545
+ try {
1546
+ // Root hashes alone say whether the projection moved: no graph is read.
1547
+ const { graph } = await composeGraphProjection(projectionDirectory(child), undefined, { base: async () => head_graph });
1548
+ projection_dirty.set(child.id, this.commitRoot(child.head_sdlc_commit_id) !==
1549
+ localGraphRootHash(graph));
1550
+ }
1551
+ catch {
1552
+ // A missing or unreadable projection does not change the committed plan.
1553
+ projection_dirty.set(child.id, null);
1554
+ }
1555
+ }
1556
+ const comparison = compareLocalWorkspacePlans({
1557
+ coordinator_commit_id,
1558
+ coordinator_graph,
1559
+ plans
1560
+ });
1561
+ const states = new Map(comparison.plans.map((state) => [state.workspace_id, state]));
1562
+ let coordinator_ancestors = null;
1563
+ for (const pair of comparison.pairs) {
1564
+ const units = allocationUnits(pair);
1565
+ if (units.length === 0)
1566
+ continue;
1567
+ coordinator_ancestors ??= this.store.history.ancestorIds(coordinator_commit_id);
1568
+ const reachable = coordinator_ancestors;
1569
+ const first_changed = await Promise.all(pair.workspace_ids.map((workspace_id) => firstOwnChanges({
1570
+ head_commit_id: plans.find((plan) => plan.workspace_id === workspace_id)
1571
+ ?.head_commit_id ?? '',
1572
+ units,
1573
+ getCommit: (commit_id) => this.store.history.getCommit(commit_id),
1574
+ reachableFromCoordinator: (commit_id) => reachable.has(commit_id),
1575
+ readGraph
1576
+ })));
1577
+ const timed = units.map((unit) => ({
1578
+ ...unit,
1579
+ first_changed_at: Object.fromEntries(pair.workspace_ids.map((workspace_id, index) => [
1580
+ workspace_id,
1581
+ first_changed[index]?.get(allocationUnitKey(unit)) ?? null
1582
+ ]))
1583
+ }));
1584
+ // A block only by validation is one unit: the combination of the
1585
+ // entities fails, so one plan owns it, by its earliest change to any.
1586
+ pair.allocation =
1587
+ pair.conflicts.length > 0 ? timed : [combineValidationUnits(timed)];
1588
+ }
1589
+ return {
1590
+ version: 1,
1591
+ coordinator_workspace_id: coordinator.id,
1592
+ coordinator_head_commit_id: coordinator_commit_id,
1593
+ verdict: comparison.verdict,
1594
+ stale_workspace_ids: comparison.plans
1595
+ .filter((state) => state.status === 'stale')
1596
+ .map((state) => state.workspace_id),
1597
+ workspaces: plans.map((plan) => {
1598
+ const child = children.find((workspace) => workspace.id === plan.workspace_id);
1599
+ const state = states.get(plan.workspace_id);
1600
+ if (!child || !state) {
1601
+ throw new Error('Workspace plan comparison lost a child workspace.');
1602
+ }
1603
+ return {
1604
+ ...state,
1605
+ git_worktree_path: child.git_worktree_path,
1606
+ base_commit_id: plan.base_commit_id,
1607
+ head_commit_id: plan.head_commit_id,
1608
+ projection_dirty: projection_dirty.get(child.id) ?? null
1609
+ };
1610
+ }),
1611
+ compared_pairs: comparison.compared_pairs,
1612
+ pairs: comparison.pairs
1613
+ };
1614
+ }
1615
+ inspectAuthority() {
1616
+ return inspectLocalWorkspaceAuthority({
1617
+ workspace: this.currentActiveWorkspace(),
1618
+ git: this.git
1619
+ });
1620
+ }
1621
+ resolverStatus() {
1622
+ return this.environment_runtime.resolverStatus();
1623
+ }
1624
+ async configureResolver(input) {
1625
+ return this.environment_runtime.configureResolver(input);
1626
+ }
1627
+ async configureResolverFromFile(input) {
1628
+ return this.environment_runtime.configureResolverFromFile(input);
1629
+ }
1630
+ async clearResolver(input) {
1631
+ return this.environment_runtime.clearResolver(input);
1632
+ }
1633
+ async loadResolver(input) {
1634
+ return this.environment_runtime.loadResolver(input);
1635
+ }
1636
+ listRemotes() {
1637
+ return this.environment_runtime.listRemotes();
1638
+ }
1639
+ inspectRemote(name) {
1640
+ return this.environment_runtime.inspectRemote(name);
1641
+ }
1642
+ async setRemote(input) {
1643
+ return this.environment_runtime.setRemote(input);
1644
+ }
1645
+ async removeRemote(input) {
1646
+ return this.environment_runtime.removeRemote(input);
1647
+ }
1648
+ async discoverRemote(input) {
1649
+ return this.environment_runtime.discoverRemote(input);
1650
+ }
1651
+ listRemoteTrackingRefs(name) {
1652
+ return this.environment_runtime.listRemoteTrackingRefs(name);
1653
+ }
1654
+ listLocalBranches() {
1655
+ return this.branch_runtime.list();
1656
+ }
1657
+ async createLocalBranchFromTrackingRef(input) {
1658
+ return this.branch_runtime.createFromTrackingRef(input);
1659
+ }
1660
+ async selectLocalBranch(input) {
1661
+ return this.branch_runtime.select(input);
1662
+ }
1663
+ async integrateBranchUpstream(input) {
1664
+ return this.upstream_runtime.integrate(input);
1665
+ }
1666
+ listBranchIntegrationAttempts(local_ref) {
1667
+ return this.upstream_runtime.listAttempts(local_ref);
1668
+ }
1669
+ listBranchIntegrationResolutionAttempts(attempt_id) {
1670
+ return this.upstream_runtime.listResolutionAttempts(attempt_id);
1671
+ }
1672
+ listBranchIntegrationRevertAttempts(local_ref) {
1673
+ return this.upstream_runtime.listRevertAttempts(local_ref);
1674
+ }
1675
+ inspectBranchUpstream(input) {
1676
+ return this.upstream_runtime.inspect(input);
1677
+ }
1678
+ revertBranchUpstream(input) {
1679
+ return this.upstream_runtime.revert(input);
1680
+ }
1681
+ async fetchRemote(input) {
1682
+ return this.environment_runtime.fetchRemote(input);
1683
+ }
1684
+ listRemotePushJournals(name) {
1685
+ return this.environment_runtime.listRemotePushJournals(name);
1686
+ }
1687
+ listRemoteTopicJournals(name) {
1688
+ return this.environment_runtime.listRemoteTopicJournals(name);
1689
+ }
1690
+ listRemotePullRequestJournals(name) {
1691
+ return this.environment_runtime.listRemotePullRequestJournals(name);
1692
+ }
1693
+ async planPortableHistory(input) {
1694
+ return this.environment_runtime.planPortableHistory(input);
1695
+ }
1696
+ async convertPortableHistory(input) {
1697
+ return this.environment_runtime.convertPortableHistory(input);
1698
+ }
1699
+ async pushRemote(input) {
1700
+ return this.environment_runtime.pushRemote(input);
1701
+ }
1702
+ async publishRemoteTopic(input) {
1703
+ return this.environment_runtime.publishRemoteTopic(input);
1704
+ }
1705
+ async createRemotePullRequest(input) {
1706
+ return this.environment_runtime.createRemotePullRequest(input);
1707
+ }
1708
+ async getWorkspaceStatus(workspace_id) {
1709
+ const workspace = this.requireWorkspace(workspace_id);
1710
+ const { graph } = await composeGraphProjection(projectionDirectory(workspace), undefined, { base: () => this.readCommitGraph(workspace.head_sdlc_commit_id) });
1711
+ const projection_root_hash = localGraphRootHash(graph);
1712
+ const head_root_hash = this.commitRoot(workspace.head_sdlc_commit_id);
1713
+ await this.graphs.readGraph(head_root_hash);
1714
+ const source = workspace.source_workspace_id
1715
+ ? this.store.getWorkspace(workspace.source_workspace_id)
1716
+ : null;
1717
+ return {
1718
+ version: 1,
1719
+ workspace,
1720
+ projection_dir: projectionDirectory(workspace),
1721
+ control_dir: controlDirectory(workspace),
1722
+ head_root_hash,
1723
+ projection_root_hash,
1724
+ projection_dirty: head_root_hash !== projection_root_hash,
1725
+ task_scope: this.store.getWorkspaceTaskScope(workspace.id),
1726
+ ...(source
1727
+ ? {
1728
+ source: {
1729
+ workspace_id: source.id,
1730
+ head_revision: source.head_sdlc_commit_id,
1731
+ behind: source.head_sdlc_commit_id !== workspace.head_sdlc_commit_id &&
1732
+ !this.store.history.isAncestor(source.head_sdlc_commit_id, workspace.head_sdlc_commit_id)
1733
+ }
1734
+ }
1735
+ : {})
1736
+ };
1737
+ }
1738
+ async commitCurrentProjection(input) {
1739
+ const { workspace, git } = await this.authority.requireCurrent(input.actor_workspace_id);
1740
+ // Before the commit reads the binding for its source context; a move that
1741
+ // fails changes nothing, and the commit keeps the binding it has
1742
+ await this.realignForkBinding(git.repository_root, git.git_common_dir).catch(() => null);
1743
+ return withWorkspaceOperationLock({
1744
+ control_dir: controlDirectory(workspace),
1745
+ operation: 'commit',
1746
+ run: async () => {
1747
+ const current_workspace = this.requireWorkspace(workspace.id);
1748
+ const base_graph = await this.readCommitGraph(current_workspace.head_sdlc_commit_id);
1749
+ const { graph } = await composeGraphProjection(projectionDirectory(current_workspace), undefined, { base: async () => base_graph });
1750
+ // A node parent the head already had is not this commit's to fix.
1751
+ const graph_issues = validateProjectionGraph(graph, {
1752
+ node_parents: true
1753
+ }).issues;
1754
+ const issues = introducedBrokenIssues(graph, [base_graph], graph_issues).map((issue) => `${issue.code}: ${issue.message}`);
1755
+ if (issues.length > 0) {
1756
+ throw new Error(`Cannot commit invalid projection: ${issues.join('; ')}`);
1757
+ }
1758
+ const diff = diffProjectionGraphs({
1759
+ base_graph,
1760
+ current_graph: graph,
1761
+ deletion_intents: this.store.listWorkspaceDeletions(current_workspace.id)
1762
+ });
1763
+ if (diff.missing_unrecorded.length > 0) {
1764
+ throw new Error(`Projection has unrecorded deletions: ${diff.missing_unrecorded
1765
+ .map((entry) => entry.relative_path)
1766
+ .join(', ')}; record them with sdlc workspace rm.`);
1767
+ }
1768
+ if (!projectionDiffHasChanges(diff)) {
1769
+ // Intents for entities that are still present no longer apply.
1770
+ this.store.clearWorkspaceDeletions(current_workspace.id);
1771
+ return {
1772
+ version: 1,
1773
+ status: 'unchanged',
1774
+ workspace: current_workspace,
1775
+ root_hash: this.commitRoot(current_workspace.head_sdlc_commit_id)
1776
+ };
1777
+ }
1778
+ assertGraphChangesWithinTaskScope({
1779
+ base_graph,
1780
+ current_graph: graph,
1781
+ task_scope: this.store.getWorkspaceTaskScope(current_workspace.id)
1782
+ });
1783
+ assertNoRetiredKeysOnChangedNodes({ base_graph, current_graph: graph });
1784
+ assertNodeTypeRulesOnChangedNodes({ base_graph, current_graph: graph });
1785
+ assertIntroducedVocabularyAdmitted({
1786
+ base_graph,
1787
+ graph,
1788
+ admission: this.store.admissions.get()
1789
+ });
1790
+ const use_issues = packageUseCommitIssues({
1791
+ base_graph,
1792
+ graph,
1793
+ source: () => heldPackages()
1794
+ });
1795
+ if (use_issues.length > 0) {
1796
+ throw new Error(`Cannot commit package uses that do not resolve: ${use_issues.join('; ')}`);
1797
+ }
1798
+ const embed_warnings = introducedDanglingDocumentEmbeds({
1799
+ base_graph,
1800
+ graph
1801
+ });
1802
+ const library_warnings = getLibraryOriginWarnings(graph, {
1803
+ node_ids: new Set([...diff.added, ...diff.modified]
1804
+ .filter((entry) => entry.entity_kind === 'node')
1805
+ .map((entry) => entry.entity_id))
1806
+ });
1807
+ const evidence_drop_warning = evidenceDropWarning(droppedEvidenceReferences({ base_graph, graph }));
1808
+ // What a hosted save of this graph would warn of, less what the head had.
1809
+ const validation_warnings = introducedValidationWarnings(graph, base_graph, graph_issues);
1810
+ const root_hash = await this.graphs.writeGraph(graph);
1811
+ const binding = this.store.getSourceRepositoryBinding(git.git_common_dir);
1812
+ const commit = this.store.history.commitWorkspace({
1813
+ actor_workspace_id: current_workspace.id,
1814
+ expected_head_commit_id: current_workspace.head_sdlc_commit_id,
1815
+ root_hash,
1816
+ message: input.message,
1817
+ validation_evidence: [
1818
+ 'complete graph validation passed',
1819
+ ...(embed_warnings.length > 0
1820
+ ? [
1821
+ `Document embeds reference missing entities: ${embed_warnings
1822
+ .map((issue) => `${issue.node_id}#${issue.block_id} -> ${issue.entity_kind} ${issue.entity_id}`)
1823
+ .join(', ')}`
1824
+ ]
1825
+ : []),
1826
+ ...library_warnings.map((warning) => warning.message),
1827
+ ...(evidence_drop_warning ? [evidence_drop_warning] : []),
1828
+ ...validation_warnings
1829
+ ],
1830
+ source_context: binding
1831
+ ? {
1832
+ source_repository_association_id: binding.source_repository_association_id,
1833
+ remote_identity: binding.remote_identity,
1834
+ git_ref: git.ref,
1835
+ git_head: git.head,
1836
+ evidence_scope: input.evidence_scope ?? {
1837
+ include_paths: [],
1838
+ exclude_paths: []
1839
+ }
1840
+ }
1841
+ : undefined
1842
+ });
1843
+ this.store.clearWorkspaceDeletions(current_workspace.id);
1844
+ await recordScopedProjectionFiles(projectionDirectory(current_workspace));
1845
+ const source_context = binding
1846
+ ? this.store.getWorkspaceCommitSourceContext(commit.id)
1847
+ : null;
1848
+ const warnings = [
1849
+ ...commit.validation_evidence.slice(1),
1850
+ ...graphShapeWarnings(graph)
1851
+ ];
1852
+ return {
1853
+ version: 1,
1854
+ status: 'committed',
1855
+ validation: {
1856
+ status: warnings.length > 0 ? 'passed_with_warnings' : 'passed',
1857
+ warnings
1858
+ },
1859
+ workspace: this.requireWorkspace(current_workspace.id),
1860
+ commit,
1861
+ source_context,
1862
+ ...(embed_warnings.length > 0
1863
+ ? { document_embed_warnings: embed_warnings }
1864
+ : {}),
1865
+ ...(library_warnings.length > 0
1866
+ ? { library_origin_warnings: library_warnings }
1867
+ : {})
1868
+ };
1869
+ }
1870
+ });
1871
+ }
1872
+ async enqueueChildCommit(input) {
1873
+ return this.integration_runtime.enqueue(input);
1874
+ }
1875
+ async readBuiltView(input) {
1876
+ return this.integration_runtime.readBuiltView(input);
1877
+ }
1878
+ async integrateNext(input) {
1879
+ return this.integration_runtime.integrate(input);
1880
+ }
1881
+ async reconcileSource(input) {
1882
+ return this.integration_runtime.reconcile(input);
1883
+ }
1884
+ prepareHandoff(input) {
1885
+ return this.handoff_runtime.prepare({
1886
+ ...input,
1887
+ reconcile_source: (reconciliation_input) => this.reconcileSource(reconciliation_input)
1888
+ });
1889
+ }
1890
+ listHandoffs(actor_workspace_id) {
1891
+ return this.handoff_runtime.list(actor_workspace_id);
1892
+ }
1893
+ inspectHandoff(input) {
1894
+ return this.handoff_runtime.inspect(input);
1895
+ }
1896
+ applyHandoff(input) {
1897
+ return this.handoff_runtime.apply(input);
1898
+ }
1899
+ /**
1900
+ * `sdlc workspace decision reopen`: reopens the Decision and, with
1901
+ * `revive_workspace_id`, checks out a new plan workspace at `target_path`
1902
+ * that starts at that closed candidate's last head and takes its place.
1903
+ */
1904
+ async reopenDecision(input) {
1905
+ await this.decisionActor();
1906
+ if (!input.revive_workspace_id) {
1907
+ const { decision } = this.store.reopenDecision(input);
1908
+ return { version: 1, decision, workspace: null };
1909
+ }
1910
+ if (!input.target_path) {
1911
+ throw new Error('Reopening a closed candidate needs --path for its new workspace.');
1912
+ }
1913
+ const closed = this.requireWorkspace(input.revive_workspace_id);
1914
+ if (closed.status !== 'closed' || !closed.source_workspace_id) {
1915
+ throw new Error(`Workspace ${closed.id} is not a closed plan workspace.`);
1916
+ }
1917
+ const coordinator = this.requireWorkspace(closed.source_workspace_id);
1918
+ return withWorkspaceOperationLock({
1919
+ control_dir: controlDirectory(coordinator),
1920
+ operation: 'decision-reopen',
1921
+ run: async () => {
1922
+ const target_path = resolve(input.target_path);
1923
+ const created_git = await createDetachedGitWorktree({
1924
+ source_cwd: coordinator.git_worktree_path,
1925
+ target_path,
1926
+ commit_id: closed.fork_git_commit_id
1927
+ });
1928
+ // Everything in the new checkout is written before the one store
1929
+ // transaction, so any failure removes the checkout and leaves the
1930
+ // Decision as it was.
1931
+ const id = randomUUID();
1932
+ let reopened;
1933
+ try {
1934
+ await assertLocalWorkspacePathsIgnored(created_git.repository_root);
1935
+ await this.materialize_projection({
1936
+ projection_dir: join(created_git.repository_root, 'sdlc'),
1937
+ graph: await this.readCommitGraph(closed.head_sdlc_commit_id)
1938
+ });
1939
+ await mkdir(join(created_git.repository_root, '.sdlc', 'state'), {
1940
+ recursive: true,
1941
+ mode: 0o700
1942
+ });
1943
+ await this.persistCurrentTopology({ id }, this.topology?.storage_mode, created_git.repository_root, created_git.git_common_dir);
1944
+ reopened = this.store.reopenDecision({
1945
+ decision_id: input.decision_id,
1946
+ reason: input.reason,
1947
+ revive: {
1948
+ id,
1949
+ workspace_id: closed.id,
1950
+ git_worktree_path: created_git.repository_root,
1951
+ git_dir: created_git.git_dir,
1952
+ git_common_dir: created_git.git_common_dir,
1953
+ git_head_commit_id: created_git.head
1954
+ }
1955
+ });
1956
+ }
1957
+ catch (error) {
1958
+ await removeGitWorktree({
1959
+ repository_cwd: this.git.repository_root,
1960
+ target_path: created_git.repository_root,
1961
+ force: true
1962
+ }).catch(() => undefined);
1963
+ throw error;
1964
+ }
1965
+ return {
1966
+ version: 1,
1967
+ decision: reopened.decision,
1968
+ workspace: await this.getWorkspaceStatus(reopened.workspace.id)
1969
+ };
1970
+ }
1971
+ });
1972
+ }
1973
+ /**
1974
+ * `sdlc workspace pick`: copies chosen entities from another workspace's
1975
+ * commit into the acting workspace as one commit with a receipt.
1976
+ */
1977
+ pickEntities(input) {
1978
+ return this.pick_runtime.pick(input);
1979
+ }
1980
+ /** The current workspace, authorized to run `sdlc workspace decision`. */
1981
+ async decisionActor() {
1982
+ const { workspace } = await this.authority.requireCurrent(this.currentActiveWorkspace().id);
1983
+ return workspace;
1984
+ }
1985
+ async closeWorkspace(input) {
1986
+ const { workspace: actor } = await this.authority.requireCurrent(input.actor_workspace_id);
1987
+ const workspace = this.requireWorkspace(input.workspace_id);
1988
+ return withWorkspaceOperationLock({
1989
+ control_dir: controlDirectory(workspace),
1990
+ operation: 'close',
1991
+ run: async () => {
1992
+ const current_workspace = this.requireWorkspace(workspace.id);
1993
+ const status = await this.getWorkspaceStatus(current_workspace.id);
1994
+ if (status.projection_dirty) {
1995
+ throw new Error('Workspace projection has uncommitted changes; commit them before closing.');
1996
+ }
1997
+ return this.store.closeWorkspace({
1998
+ actor_workspace_id: actor.id,
1999
+ workspace_id: current_workspace.id,
2000
+ expected_head_commit_id: current_workspace.head_sdlc_commit_id
2001
+ });
2002
+ }
2003
+ });
2004
+ }
2005
+ async planPrune(input = {}) {
2006
+ const current = this.currentActiveWorkspace();
2007
+ await this.authority.requireCurrent(current.id);
2008
+ return this.pruner.plan({
2009
+ actor_workspace_id: current.id,
2010
+ ...input
2011
+ });
2012
+ }
2013
+ async applyPrune(input) {
2014
+ const current = this.currentActiveWorkspace();
2015
+ await this.authority.requireCurrent(current.id);
2016
+ return this.pruner.apply({
2017
+ actor_workspace_id: current.id,
2018
+ ...input
2019
+ });
2020
+ }
2021
+ async recoverCurrentProjection(input) {
2022
+ const { workspace } = await this.authority.requireCurrent(input.actor_workspace_id);
2023
+ if (!input.expected_projection_root_hash &&
2024
+ !input.discard_unreadable_projection) {
2025
+ throw new Error('Recovery requires --expected-projection-root unless unreadable projection discard is explicit.');
2026
+ }
2027
+ return withWorkspaceOperationLock({
2028
+ control_dir: controlDirectory(workspace),
2029
+ operation: 'recover',
2030
+ run: async () => {
2031
+ const current_workspace = this.requireWorkspace(workspace.id);
2032
+ if (current_workspace.head_sdlc_commit_id !==
2033
+ input.expected_head_commit_id) {
2034
+ throw new Error('Workspace head is stale; refusing projection recovery.');
2035
+ }
2036
+ const head_graph = await this.readCommitGraph(current_workspace.head_sdlc_commit_id);
2037
+ const head_root_hash = this.commitRoot(current_workspace.head_sdlc_commit_id);
2038
+ let previous_projection_root_hash = null;
2039
+ let discarded_unreadable_projection = false;
2040
+ let readable_projection = null;
2041
+ try {
2042
+ const { graph } = await composeGraphProjection(projectionDirectory(current_workspace), undefined, { base: async () => head_graph });
2043
+ readable_projection = graph;
2044
+ }
2045
+ catch (error) {
2046
+ if (!input.discard_unreadable_projection)
2047
+ throw error;
2048
+ discarded_unreadable_projection = true;
2049
+ }
2050
+ if (readable_projection) {
2051
+ if (!input.expected_projection_root_hash) {
2052
+ throw new Error('Readable projection recovery requires its expected root.');
2053
+ }
2054
+ previous_projection_root_hash =
2055
+ localGraphRootHash(readable_projection);
2056
+ if (previous_projection_root_hash !==
2057
+ input.expected_projection_root_hash) {
2058
+ throw new Error('Projection root is stale; refusing projection recovery.');
2059
+ }
2060
+ await this.graphs.writeGraph(readable_projection);
2061
+ }
2062
+ if (previous_projection_root_hash === head_root_hash) {
2063
+ return {
2064
+ version: 1,
2065
+ status: 'unchanged',
2066
+ workspace: current_workspace,
2067
+ head_root_hash,
2068
+ previous_projection_root_hash,
2069
+ discarded_unreadable_projection
2070
+ };
2071
+ }
2072
+ // A discarded projection.json cannot name its scope: the task scope does.
2073
+ const rescope = discarded_unreadable_projection &&
2074
+ (await hasProjectionManifest(projectionDirectory(current_workspace)));
2075
+ const task_scope = this.store.getWorkspaceTaskScope(current_workspace.id);
2076
+ await this.materialize_projection({
2077
+ projection_dir: projectionDirectory(current_workspace),
2078
+ graph: head_graph,
2079
+ ...(rescope && task_scope
2080
+ ? { scope: projectionScopeOfTaskScope(task_scope) }
2081
+ : rescope
2082
+ ? { full: true }
2083
+ : {})
2084
+ });
2085
+ // The head projection restores every entity a deletion intent named.
2086
+ this.store.clearWorkspaceDeletions(current_workspace.id);
2087
+ return {
2088
+ version: 1,
2089
+ status: 'recovered',
2090
+ workspace: current_workspace,
2091
+ head_root_hash,
2092
+ previous_projection_root_hash,
2093
+ discarded_unreadable_projection
2094
+ };
2095
+ }
2096
+ });
2097
+ }
2098
+ requireWorkspace(workspace_id) {
2099
+ const workspace = this.store.getWorkspace(workspace_id);
2100
+ if (!workspace)
2101
+ throw new Error('Local workspace was not found.');
2102
+ return workspace;
2103
+ }
2104
+ currentActiveWorkspace() {
2105
+ const workspace = this.store.getWorkspaceByGitWorktreePath(this.git.repository_root);
2106
+ if (!workspace || workspace.status !== 'active') {
2107
+ throw new Error('The current Git worktree is not an active workspace.');
2108
+ }
2109
+ return workspace;
2110
+ }
2111
+ commitRoot(commit_id) {
2112
+ return this.commits.root(commit_id);
2113
+ }
2114
+ async readCommitGraph(commit_id) {
2115
+ return this.commits.readGraph(commit_id);
2116
+ }
2117
+ async assertProjectionClean(workspace) {
2118
+ const status = await this.getWorkspaceStatus(workspace.id);
2119
+ if (status.projection_dirty) {
2120
+ throw new Error('Workspace projection has uncommitted changes; commit them before this operation.');
2121
+ }
2122
+ }
2123
+ }
2124
+ //# sourceMappingURL=workspace-runtime.js.map