light-plan 0.0.0-stage → 0.1.0

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 (963) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3181 -2
  3. package/assets/README.md +111 -0
  4. package/assets/agents/lpm-developer.md +468 -0
  5. package/assets/agents/lpm-planner.md +532 -0
  6. package/assets/harnesses/claude.yml +43 -0
  7. package/assets/harnesses/copilot.yml +54 -0
  8. package/assets/harnesses/reasonix.yml +49 -0
  9. package/assets/hcm/light-plan.yml +45 -0
  10. package/assets/skills/lpm-board-health.md +243 -0
  11. package/assets/skills/lpm-board-setup.md +354 -0
  12. package/assets/skills/lpm-delivery.md +344 -0
  13. package/assets/skills/lpm-planning.md +411 -0
  14. package/assets/skills/lpm-templates.md +144 -0
  15. package/assets/skills/lpm.md +267 -0
  16. package/dist/cli/agent-view.d.ts +27 -0
  17. package/dist/cli/agent-view.js +247 -0
  18. package/dist/cli/agent-view.js.map +1 -0
  19. package/dist/cli/commands/agent/assets.d.ts +64 -0
  20. package/dist/cli/commands/agent/assets.js +106 -0
  21. package/dist/cli/commands/agent/assets.js.map +1 -0
  22. package/dist/cli/commands/agent/glob.d.ts +25 -0
  23. package/dist/cli/commands/agent/glob.js +82 -0
  24. package/dist/cli/commands/agent/glob.js.map +1 -0
  25. package/dist/cli/commands/agent/index.d.ts +11 -0
  26. package/dist/cli/commands/agent/index.js +284 -0
  27. package/dist/cli/commands/agent/index.js.map +1 -0
  28. package/dist/cli/commands/agent/install.d.ts +59 -0
  29. package/dist/cli/commands/agent/install.js +131 -0
  30. package/dist/cli/commands/agent/install.js.map +1 -0
  31. package/dist/cli/commands/agent/mapping.d.ts +175 -0
  32. package/dist/cli/commands/agent/mapping.js +256 -0
  33. package/dist/cli/commands/agent/mapping.js.map +1 -0
  34. package/dist/cli/commands/check.d.ts +2 -0
  35. package/dist/cli/commands/check.js +81 -0
  36. package/dist/cli/commands/check.js.map +1 -0
  37. package/dist/cli/commands/comment.d.ts +2 -0
  38. package/dist/cli/commands/comment.js +104 -0
  39. package/dist/cli/commands/comment.js.map +1 -0
  40. package/dist/cli/commands/convert.d.ts +2 -0
  41. package/dist/cli/commands/convert.js +115 -0
  42. package/dist/cli/commands/convert.js.map +1 -0
  43. package/dist/cli/commands/copy.d.ts +2 -0
  44. package/dist/cli/commands/copy.js +69 -0
  45. package/dist/cli/commands/copy.js.map +1 -0
  46. package/dist/cli/commands/export.d.ts +11 -0
  47. package/dist/cli/commands/export.js +226 -0
  48. package/dist/cli/commands/export.js.map +1 -0
  49. package/dist/cli/commands/flag.d.ts +2 -0
  50. package/dist/cli/commands/flag.js +172 -0
  51. package/dist/cli/commands/flag.js.map +1 -0
  52. package/dist/cli/commands/git.d.ts +11 -0
  53. package/dist/cli/commands/git.js +357 -0
  54. package/dist/cli/commands/git.js.map +1 -0
  55. package/dist/cli/commands/hcm/bundle.d.ts +60 -0
  56. package/dist/cli/commands/hcm/bundle.js +166 -0
  57. package/dist/cli/commands/hcm/bundle.js.map +1 -0
  58. package/dist/cli/commands/hcm/index.d.ts +11 -0
  59. package/dist/cli/commands/hcm/index.js +162 -0
  60. package/dist/cli/commands/hcm/index.js.map +1 -0
  61. package/dist/cli/commands/init.d.ts +2 -0
  62. package/dist/cli/commands/init.js +81 -0
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/insert.d.ts +2 -0
  65. package/dist/cli/commands/insert.js +70 -0
  66. package/dist/cli/commands/insert.js.map +1 -0
  67. package/dist/cli/commands/instructions.d.ts +7 -0
  68. package/dist/cli/commands/instructions.js +256 -0
  69. package/dist/cli/commands/instructions.js.map +1 -0
  70. package/dist/cli/commands/link.d.ts +2 -0
  71. package/dist/cli/commands/link.js +121 -0
  72. package/dist/cli/commands/link.js.map +1 -0
  73. package/dist/cli/commands/mcp/config.d.ts +71 -0
  74. package/dist/cli/commands/mcp/config.js +119 -0
  75. package/dist/cli/commands/mcp/config.js.map +1 -0
  76. package/dist/cli/commands/mcp/index.d.ts +9 -0
  77. package/dist/cli/commands/mcp/index.js +92 -0
  78. package/dist/cli/commands/mcp/index.js.map +1 -0
  79. package/dist/cli/commands/mcp/serve.d.ts +1 -0
  80. package/dist/cli/commands/mcp/serve.js +76 -0
  81. package/dist/cli/commands/mcp/serve.js.map +1 -0
  82. package/dist/cli/commands/mcp/setup.d.ts +1 -0
  83. package/dist/cli/commands/mcp/setup.js +83 -0
  84. package/dist/cli/commands/mcp/setup.js.map +1 -0
  85. package/dist/cli/commands/me.d.ts +2 -0
  86. package/dist/cli/commands/me.js +85 -0
  87. package/dist/cli/commands/me.js.map +1 -0
  88. package/dist/cli/commands/move.d.ts +2 -0
  89. package/dist/cli/commands/move.js +81 -0
  90. package/dist/cli/commands/move.js.map +1 -0
  91. package/dist/cli/commands/new.d.ts +2 -0
  92. package/dist/cli/commands/new.js +222 -0
  93. package/dist/cli/commands/new.js.map +1 -0
  94. package/dist/cli/commands/open.d.ts +2 -0
  95. package/dist/cli/commands/open.js +43 -0
  96. package/dist/cli/commands/open.js.map +1 -0
  97. package/dist/cli/commands/period.d.ts +10 -0
  98. package/dist/cli/commands/period.js +165 -0
  99. package/dist/cli/commands/period.js.map +1 -0
  100. package/dist/cli/commands/profile.d.ts +2 -0
  101. package/dist/cli/commands/profile.js +133 -0
  102. package/dist/cli/commands/profile.js.map +1 -0
  103. package/dist/cli/commands/queue.d.ts +2 -0
  104. package/dist/cli/commands/queue.js +582 -0
  105. package/dist/cli/commands/queue.js.map +1 -0
  106. package/dist/cli/commands/remote.d.ts +10 -0
  107. package/dist/cli/commands/remote.js +3142 -0
  108. package/dist/cli/commands/remote.js.map +1 -0
  109. package/dist/cli/commands/rm.d.ts +2 -0
  110. package/dist/cli/commands/rm.js +63 -0
  111. package/dist/cli/commands/rm.js.map +1 -0
  112. package/dist/cli/commands/set.d.ts +2 -0
  113. package/dist/cli/commands/set.js +139 -0
  114. package/dist/cli/commands/set.js.map +1 -0
  115. package/dist/cli/commands/split.d.ts +2 -0
  116. package/dist/cli/commands/split.js +90 -0
  117. package/dist/cli/commands/split.js.map +1 -0
  118. package/dist/cli/commands/task.d.ts +2 -0
  119. package/dist/cli/commands/task.js +298 -0
  120. package/dist/cli/commands/task.js.map +1 -0
  121. package/dist/cli/commands/team.d.ts +2 -0
  122. package/dist/cli/commands/team.js +114 -0
  123. package/dist/cli/commands/team.js.map +1 -0
  124. package/dist/cli/commands/template.d.ts +2 -0
  125. package/dist/cli/commands/template.js +338 -0
  126. package/dist/cli/commands/template.js.map +1 -0
  127. package/dist/cli/commands/ui.d.ts +2 -0
  128. package/dist/cli/commands/ui.js +98 -0
  129. package/dist/cli/commands/ui.js.map +1 -0
  130. package/dist/cli/commands/upstream.d.ts +2 -0
  131. package/dist/cli/commands/upstream.js +218 -0
  132. package/dist/cli/commands/upstream.js.map +1 -0
  133. package/dist/cli/context.d.ts +24 -0
  134. package/dist/cli/context.js +52 -0
  135. package/dist/cli/context.js.map +1 -0
  136. package/dist/cli/index.d.ts +2 -0
  137. package/dist/cli/index.js +208 -0
  138. package/dist/cli/index.js.map +1 -0
  139. package/dist/cli/live.d.ts +71 -0
  140. package/dist/cli/live.js +182 -0
  141. package/dist/cli/live.js.map +1 -0
  142. package/dist/cli/plan.d.ts +28 -0
  143. package/dist/cli/plan.js +76 -0
  144. package/dist/cli/plan.js.map +1 -0
  145. package/dist/cli/prompt.d.ts +60 -0
  146. package/dist/cli/prompt.js +185 -0
  147. package/dist/cli/prompt.js.map +1 -0
  148. package/dist/cli/ui.d.ts +65 -0
  149. package/dist/cli/ui.js +79 -0
  150. package/dist/cli/ui.js.map +1 -0
  151. package/dist/core/board/dependency-rollup.d.ts +54 -0
  152. package/dist/core/board/dependency-rollup.js +72 -0
  153. package/dist/core/board/dependency-rollup.js.map +1 -0
  154. package/dist/core/board/flag-rollup.d.ts +33 -0
  155. package/dist/core/board/flag-rollup.js +56 -0
  156. package/dist/core/board/flag-rollup.js.map +1 -0
  157. package/dist/core/board/index.d.ts +21 -0
  158. package/dist/core/board/index.js +22 -0
  159. package/dist/core/board/index.js.map +1 -0
  160. package/dist/core/board/load/cache.d.ts +52 -0
  161. package/dist/core/board/load/cache.js +105 -0
  162. package/dist/core/board/load/cache.js.map +1 -0
  163. package/dist/core/board/load/fields.d.ts +33 -0
  164. package/dist/core/board/load/fields.js +154 -0
  165. package/dist/core/board/load/fields.js.map +1 -0
  166. package/dist/core/board/load/scan.d.ts +39 -0
  167. package/dist/core/board/load/scan.js +88 -0
  168. package/dist/core/board/load/scan.js.map +1 -0
  169. package/dist/core/board/load/tree.d.ts +10 -0
  170. package/dist/core/board/load/tree.js +114 -0
  171. package/dist/core/board/load/tree.js.map +1 -0
  172. package/dist/core/board/load.d.ts +69 -0
  173. package/dist/core/board/load.js +204 -0
  174. package/dist/core/board/load.js.map +1 -0
  175. package/dist/core/board/query.d.ts +126 -0
  176. package/dist/core/board/query.js +291 -0
  177. package/dist/core/board/query.js.map +1 -0
  178. package/dist/core/board/registry.d.ts +51 -0
  179. package/dist/core/board/registry.js +98 -0
  180. package/dist/core/board/registry.js.map +1 -0
  181. package/dist/core/board/remote-scopes.d.ts +71 -0
  182. package/dist/core/board/remote-scopes.js +103 -0
  183. package/dist/core/board/remote-scopes.js.map +1 -0
  184. package/dist/core/board/rollup.d.ts +32 -0
  185. package/dist/core/board/rollup.js +67 -0
  186. package/dist/core/board/rollup.js.map +1 -0
  187. package/dist/core/board/scope.d.ts +81 -0
  188. package/dist/core/board/scope.js +134 -0
  189. package/dist/core/board/scope.js.map +1 -0
  190. package/dist/core/board/simulate.d.ts +138 -0
  191. package/dist/core/board/simulate.js +141 -0
  192. package/dist/core/board/simulate.js.map +1 -0
  193. package/dist/core/board/tasks/index.d.ts +25 -0
  194. package/dist/core/board/tasks/index.js +26 -0
  195. package/dist/core/board/tasks/index.js.map +1 -0
  196. package/dist/core/board/tasks/ranking.d.ts +155 -0
  197. package/dist/core/board/tasks/ranking.js +374 -0
  198. package/dist/core/board/tasks/ranking.js.map +1 -0
  199. package/dist/core/board/tasks/roster.d.ts +42 -0
  200. package/dist/core/board/tasks/roster.js +82 -0
  201. package/dist/core/board/tasks/roster.js.map +1 -0
  202. package/dist/core/board/tasks.d.ts +1 -0
  203. package/dist/core/board/tasks.js +2 -0
  204. package/dist/core/board/tasks.js.map +1 -0
  205. package/dist/core/config/index.d.ts +9 -0
  206. package/dist/core/config/index.js +10 -0
  207. package/dist/core/config/index.js.map +1 -0
  208. package/dist/core/config/lookup.d.ts +114 -0
  209. package/dist/core/config/lookup.js +221 -0
  210. package/dist/core/config/lookup.js.map +1 -0
  211. package/dist/core/config/remote-blocks.d.ts +17 -0
  212. package/dist/core/config/remote-blocks.js +47 -0
  213. package/dist/core/config/remote-blocks.js.map +1 -0
  214. package/dist/core/config/schema.d.ts +10 -0
  215. package/dist/core/config/schema.js +451 -0
  216. package/dist/core/config/schema.js.map +1 -0
  217. package/dist/core/errors.d.ts +23 -0
  218. package/dist/core/errors.js +31 -0
  219. package/dist/core/errors.js.map +1 -0
  220. package/dist/core/gitsync/hosts.d.ts +40 -0
  221. package/dist/core/gitsync/hosts.js +115 -0
  222. package/dist/core/gitsync/hosts.js.map +1 -0
  223. package/dist/core/gitsync/index.d.ts +25 -0
  224. package/dist/core/gitsync/index.js +25 -0
  225. package/dist/core/gitsync/index.js.map +1 -0
  226. package/dist/core/gitsync/integrate.d.ts +72 -0
  227. package/dist/core/gitsync/integrate.js +115 -0
  228. package/dist/core/gitsync/integrate.js.map +1 -0
  229. package/dist/core/gitsync/repo.d.ts +61 -0
  230. package/dist/core/gitsync/repo.js +213 -0
  231. package/dist/core/gitsync/repo.js.map +1 -0
  232. package/dist/core/gitsync/run.d.ts +58 -0
  233. package/dist/core/gitsync/run.js +102 -0
  234. package/dist/core/gitsync/run.js.map +1 -0
  235. package/dist/core/gitsync/status.d.ts +59 -0
  236. package/dist/core/gitsync/status.js +81 -0
  237. package/dist/core/gitsync/status.js.map +1 -0
  238. package/dist/core/gitsync/sync.d.ts +111 -0
  239. package/dist/core/gitsync/sync.js +275 -0
  240. package/dist/core/gitsync/sync.js.map +1 -0
  241. package/dist/core/index.d.ts +31 -0
  242. package/dist/core/index.js +32 -0
  243. package/dist/core/index.js.map +1 -0
  244. package/dist/core/instructions/analyze.d.ts +70 -0
  245. package/dist/core/instructions/analyze.js +275 -0
  246. package/dist/core/instructions/analyze.js.map +1 -0
  247. package/dist/core/instructions/builtin.d.ts +21 -0
  248. package/dist/core/instructions/builtin.js +133 -0
  249. package/dist/core/instructions/builtin.js.map +1 -0
  250. package/dist/core/instructions/context.d.ts +155 -0
  251. package/dist/core/instructions/context.js +209 -0
  252. package/dist/core/instructions/context.js.map +1 -0
  253. package/dist/core/instructions/index.d.ts +29 -0
  254. package/dist/core/instructions/index.js +30 -0
  255. package/dist/core/instructions/index.js.map +1 -0
  256. package/dist/core/instructions/instructions.d.ts +90 -0
  257. package/dist/core/instructions/instructions.js +179 -0
  258. package/dist/core/instructions/instructions.js.map +1 -0
  259. package/dist/core/instructions/template.d.ts +108 -0
  260. package/dist/core/instructions/template.js +252 -0
  261. package/dist/core/instructions/template.js.map +1 -0
  262. package/dist/core/model/attributes.d.ts +16 -0
  263. package/dist/core/model/attributes.js +119 -0
  264. package/dist/core/model/attributes.js.map +1 -0
  265. package/dist/core/model/index.d.ts +10 -0
  266. package/dist/core/model/index.js +11 -0
  267. package/dist/core/model/index.js.map +1 -0
  268. package/dist/core/model/links.d.ts +34 -0
  269. package/dist/core/model/links.js +114 -0
  270. package/dist/core/model/links.js.map +1 -0
  271. package/dist/core/model/profile.d.ts +31 -0
  272. package/dist/core/model/profile.js +8 -0
  273. package/dist/core/model/profile.js.map +1 -0
  274. package/dist/core/model/types.d.ts +441 -0
  275. package/dist/core/model/types.js +167 -0
  276. package/dist/core/model/types.js.map +1 -0
  277. package/dist/core/model/zod.d.ts +6 -0
  278. package/dist/core/model/zod.js +11 -0
  279. package/dist/core/model/zod.js.map +1 -0
  280. package/dist/core/operations/board-index.d.ts +45 -0
  281. package/dist/core/operations/board-index.js +147 -0
  282. package/dist/core/operations/board-index.js.map +1 -0
  283. package/dist/core/operations/claim.d.ts +69 -0
  284. package/dist/core/operations/claim.js +115 -0
  285. package/dist/core/operations/claim.js.map +1 -0
  286. package/dist/core/operations/comment.d.ts +30 -0
  287. package/dist/core/operations/comment.js +46 -0
  288. package/dist/core/operations/comment.js.map +1 -0
  289. package/dist/core/operations/create.d.ts +82 -0
  290. package/dist/core/operations/create.js +306 -0
  291. package/dist/core/operations/create.js.map +1 -0
  292. package/dist/core/operations/flag.d.ts +58 -0
  293. package/dist/core/operations/flag.js +95 -0
  294. package/dist/core/operations/flag.js.map +1 -0
  295. package/dist/core/operations/git-sync.d.ts +148 -0
  296. package/dist/core/operations/git-sync.js +341 -0
  297. package/dist/core/operations/git-sync.js.map +1 -0
  298. package/dist/core/operations/index.d.ts +44 -0
  299. package/dist/core/operations/index.js +45 -0
  300. package/dist/core/operations/index.js.map +1 -0
  301. package/dist/core/operations/init.d.ts +25 -0
  302. package/dist/core/operations/init.js +122 -0
  303. package/dist/core/operations/init.js.map +1 -0
  304. package/dist/core/operations/link.d.ts +40 -0
  305. package/dist/core/operations/link.js +131 -0
  306. package/dist/core/operations/link.js.map +1 -0
  307. package/dist/core/operations/move.d.ts +43 -0
  308. package/dist/core/operations/move.js +103 -0
  309. package/dist/core/operations/move.js.map +1 -0
  310. package/dist/core/operations/profile.d.ts +23 -0
  311. package/dist/core/operations/profile.js +26 -0
  312. package/dist/core/operations/profile.js.map +1 -0
  313. package/dist/core/operations/remotes-off.d.ts +12 -0
  314. package/dist/core/operations/remotes-off.js +67 -0
  315. package/dist/core/operations/remotes-off.js.map +1 -0
  316. package/dist/core/operations/remove.d.ts +18 -0
  317. package/dist/core/operations/remove.js +114 -0
  318. package/dist/core/operations/remove.js.map +1 -0
  319. package/dist/core/operations/retype.d.ts +25 -0
  320. package/dist/core/operations/retype.js +59 -0
  321. package/dist/core/operations/retype.js.map +1 -0
  322. package/dist/core/operations/rollup.d.ts +74 -0
  323. package/dist/core/operations/rollup.js +121 -0
  324. package/dist/core/operations/rollup.js.map +1 -0
  325. package/dist/core/operations/shared.d.ts +133 -0
  326. package/dist/core/operations/shared.js +383 -0
  327. package/dist/core/operations/shared.js.map +1 -0
  328. package/dist/core/operations/update.d.ts +43 -0
  329. package/dist/core/operations/update.js +161 -0
  330. package/dist/core/operations/update.js.map +1 -0
  331. package/dist/core/operations/user.d.ts +27 -0
  332. package/dist/core/operations/user.js +68 -0
  333. package/dist/core/operations/user.js.map +1 -0
  334. package/dist/core/profile/current.d.ts +35 -0
  335. package/dist/core/profile/current.js +31 -0
  336. package/dist/core/profile/current.js.map +1 -0
  337. package/dist/core/profile/index.d.ts +14 -0
  338. package/dist/core/profile/index.js +15 -0
  339. package/dist/core/profile/index.js.map +1 -0
  340. package/dist/core/profile/schema.d.ts +9 -0
  341. package/dist/core/profile/schema.js +100 -0
  342. package/dist/core/profile/schema.js.map +1 -0
  343. package/dist/core/storage/activity.d.ts +78 -0
  344. package/dist/core/storage/activity.js +131 -0
  345. package/dist/core/storage/activity.js.map +1 -0
  346. package/dist/core/storage/atomic.d.ts +34 -0
  347. package/dist/core/storage/atomic.js +117 -0
  348. package/dist/core/storage/atomic.js.map +1 -0
  349. package/dist/core/storage/comments.d.ts +39 -0
  350. package/dist/core/storage/comments.js +88 -0
  351. package/dist/core/storage/comments.js.map +1 -0
  352. package/dist/core/storage/document.d.ts +16 -0
  353. package/dist/core/storage/document.js +98 -0
  354. package/dist/core/storage/document.js.map +1 -0
  355. package/dist/core/storage/frontmatter.d.ts +16 -0
  356. package/dist/core/storage/frontmatter.js +33 -0
  357. package/dist/core/storage/frontmatter.js.map +1 -0
  358. package/dist/core/storage/git.d.ts +8 -0
  359. package/dist/core/storage/git.js +48 -0
  360. package/dist/core/storage/git.js.map +1 -0
  361. package/dist/core/storage/index.d.ts +27 -0
  362. package/dist/core/storage/index.js +28 -0
  363. package/dist/core/storage/index.js.map +1 -0
  364. package/dist/core/storage/local.d.ts +57 -0
  365. package/dist/core/storage/local.js +133 -0
  366. package/dist/core/storage/local.js.map +1 -0
  367. package/dist/core/storage/lock.d.ts +85 -0
  368. package/dist/core/storage/lock.js +364 -0
  369. package/dist/core/storage/lock.js.map +1 -0
  370. package/dist/core/storage/paths.d.ts +125 -0
  371. package/dist/core/storage/paths.js +202 -0
  372. package/dist/core/storage/paths.js.map +1 -0
  373. package/dist/core/storage/state.d.ts +32 -0
  374. package/dist/core/storage/state.js +71 -0
  375. package/dist/core/storage/state.js.map +1 -0
  376. package/dist/core/storage/templates.d.ts +29 -0
  377. package/dist/core/storage/templates.js +62 -0
  378. package/dist/core/storage/templates.js.map +1 -0
  379. package/dist/core/storage/views.d.ts +18 -0
  380. package/dist/core/storage/views.js +62 -0
  381. package/dist/core/storage/views.js.map +1 -0
  382. package/dist/core/validation/check.d.ts +4 -0
  383. package/dist/core/validation/check.js +22 -0
  384. package/dist/core/validation/check.js.map +1 -0
  385. package/dist/core/validation/checks/collection.d.ts +4 -0
  386. package/dist/core/validation/checks/collection.js +152 -0
  387. package/dist/core/validation/checks/collection.js.map +1 -0
  388. package/dist/core/validation/checks/counters.d.ts +3 -0
  389. package/dist/core/validation/checks/counters.js +23 -0
  390. package/dist/core/validation/checks/counters.js.map +1 -0
  391. package/dist/core/validation/checks/dependencies.d.ts +3 -0
  392. package/dist/core/validation/checks/dependencies.js +58 -0
  393. package/dist/core/validation/checks/dependencies.js.map +1 -0
  394. package/dist/core/validation/checks/gitignore.d.ts +10 -0
  395. package/dist/core/validation/checks/gitignore.js +22 -0
  396. package/dist/core/validation/checks/gitignore.js.map +1 -0
  397. package/dist/core/validation/checks/index-file.d.ts +8 -0
  398. package/dist/core/validation/checks/index-file.js +26 -0
  399. package/dist/core/validation/checks/index-file.js.map +1 -0
  400. package/dist/core/validation/checks/index.d.ts +20 -0
  401. package/dist/core/validation/checks/index.js +21 -0
  402. package/dist/core/validation/checks/index.js.map +1 -0
  403. package/dist/core/validation/checks/issues.d.ts +3 -0
  404. package/dist/core/validation/checks/issues.js +152 -0
  405. package/dist/core/validation/checks/issues.js.map +1 -0
  406. package/dist/core/validation/checks/periods.d.ts +3 -0
  407. package/dist/core/validation/checks/periods.js +89 -0
  408. package/dist/core/validation/checks/periods.js.map +1 -0
  409. package/dist/core/validation/checks/remotes.d.ts +20 -0
  410. package/dist/core/validation/checks/remotes.js +42 -0
  411. package/dist/core/validation/checks/remotes.js.map +1 -0
  412. package/dist/core/validation/checks/resources.d.ts +3 -0
  413. package/dist/core/validation/checks/resources.js +77 -0
  414. package/dist/core/validation/checks/resources.js.map +1 -0
  415. package/dist/core/validation/checks/rollup.d.ts +27 -0
  416. package/dist/core/validation/checks/rollup.js +53 -0
  417. package/dist/core/validation/checks/rollup.js.map +1 -0
  418. package/dist/core/validation/checks/squads.d.ts +3 -0
  419. package/dist/core/validation/checks/squads.js +42 -0
  420. package/dist/core/validation/checks/squads.js.map +1 -0
  421. package/dist/core/validation/checks/templates.d.ts +14 -0
  422. package/dist/core/validation/checks/templates.js +119 -0
  423. package/dist/core/validation/checks/templates.js.map +1 -0
  424. package/dist/core/validation/fix.d.ts +7 -0
  425. package/dist/core/validation/fix.js +273 -0
  426. package/dist/core/validation/fix.js.map +1 -0
  427. package/dist/core/validation/index.d.ts +7 -0
  428. package/dist/core/validation/index.js +8 -0
  429. package/dist/core/validation/index.js.map +1 -0
  430. package/dist/core/validation/shared.d.ts +47 -0
  431. package/dist/core/validation/shared.js +66 -0
  432. package/dist/core/validation/shared.js.map +1 -0
  433. package/dist/mcp/context.d.ts +70 -0
  434. package/dist/mcp/context.js +110 -0
  435. package/dist/mcp/context.js.map +1 -0
  436. package/dist/mcp/index.d.ts +49 -0
  437. package/dist/mcp/index.js +90 -0
  438. package/dist/mcp/index.js.map +1 -0
  439. package/dist/mcp/reply.d.ts +28 -0
  440. package/dist/mcp/reply.js +34 -0
  441. package/dist/mcp/reply.js.map +1 -0
  442. package/dist/mcp/tools/plan/create.d.ts +3 -0
  443. package/dist/mcp/tools/plan/create.js +128 -0
  444. package/dist/mcp/tools/plan/create.js.map +1 -0
  445. package/dist/mcp/tools/plan/delete.d.ts +3 -0
  446. package/dist/mcp/tools/plan/delete.js +35 -0
  447. package/dist/mcp/tools/plan/delete.js.map +1 -0
  448. package/dist/mcp/tools/plan/graph.d.ts +3 -0
  449. package/dist/mcp/tools/plan/graph.js +53 -0
  450. package/dist/mcp/tools/plan/graph.js.map +1 -0
  451. package/dist/mcp/tools/plan/index.d.ts +15 -0
  452. package/dist/mcp/tools/plan/index.js +16 -0
  453. package/dist/mcp/tools/plan/index.js.map +1 -0
  454. package/dist/mcp/tools/plan/link.d.ts +3 -0
  455. package/dist/mcp/tools/plan/link.js +46 -0
  456. package/dist/mcp/tools/plan/link.js.map +1 -0
  457. package/dist/mcp/tools/plan/registrar.d.ts +10 -0
  458. package/dist/mcp/tools/plan/registrar.js +24 -0
  459. package/dist/mcp/tools/plan/registrar.js.map +1 -0
  460. package/dist/mcp/tools/plan/reshape.d.ts +3 -0
  461. package/dist/mcp/tools/plan/reshape.js +95 -0
  462. package/dist/mcp/tools/plan/reshape.js.map +1 -0
  463. package/dist/mcp/tools/plan/timeline.d.ts +3 -0
  464. package/dist/mcp/tools/plan/timeline.js +68 -0
  465. package/dist/mcp/tools/plan/timeline.js.map +1 -0
  466. package/dist/mcp/tools/plan/upstream.d.ts +14 -0
  467. package/dist/mcp/tools/plan/upstream.js +69 -0
  468. package/dist/mcp/tools/plan/upstream.js.map +1 -0
  469. package/dist/mcp/tools/plan.d.ts +1 -0
  470. package/dist/mcp/tools/plan.js +2 -0
  471. package/dist/mcp/tools/plan.js.map +1 -0
  472. package/dist/mcp/tools/read.d.ts +3 -0
  473. package/dist/mcp/tools/read.js +339 -0
  474. package/dist/mcp/tools/read.js.map +1 -0
  475. package/dist/mcp/tools/remote.d.ts +3 -0
  476. package/dist/mcp/tools/remote.js +254 -0
  477. package/dist/mcp/tools/remote.js.map +1 -0
  478. package/dist/mcp/tools/templates.d.ts +12 -0
  479. package/dist/mcp/tools/templates.js +165 -0
  480. package/dist/mcp/tools/templates.js.map +1 -0
  481. package/dist/mcp/tools/work.d.ts +16 -0
  482. package/dist/mcp/tools/work.js +359 -0
  483. package/dist/mcp/tools/work.js.map +1 -0
  484. package/dist/remote/accounts.d.ts +158 -0
  485. package/dist/remote/accounts.js +207 -0
  486. package/dist/remote/accounts.js.map +1 -0
  487. package/dist/remote/adopt.d.ts +139 -0
  488. package/dist/remote/adopt.js +181 -0
  489. package/dist/remote/adopt.js.map +1 -0
  490. package/dist/remote/anchor.d.ts +35 -0
  491. package/dist/remote/anchor.js +53 -0
  492. package/dist/remote/anchor.js.map +1 -0
  493. package/dist/remote/attributes.d.ts +103 -0
  494. package/dist/remote/attributes.js +245 -0
  495. package/dist/remote/attributes.js.map +1 -0
  496. package/dist/remote/audit.d.ts +112 -0
  497. package/dist/remote/audit.js +215 -0
  498. package/dist/remote/audit.js.map +1 -0
  499. package/dist/remote/capabilities.d.ts +267 -0
  500. package/dist/remote/capabilities.js +225 -0
  501. package/dist/remote/capabilities.js.map +1 -0
  502. package/dist/remote/check.d.ts +49 -0
  503. package/dist/remote/check.js +356 -0
  504. package/dist/remote/check.js.map +1 -0
  505. package/dist/remote/comments.d.ts +86 -0
  506. package/dist/remote/comments.js +87 -0
  507. package/dist/remote/comments.js.map +1 -0
  508. package/dist/remote/config-file.d.ts +237 -0
  509. package/dist/remote/config-file.js +640 -0
  510. package/dist/remote/config-file.js.map +1 -0
  511. package/dist/remote/conflicts.d.ts +110 -0
  512. package/dist/remote/conflicts.js +188 -0
  513. package/dist/remote/conflicts.js.map +1 -0
  514. package/dist/remote/connection-catalogue.d.ts +65 -0
  515. package/dist/remote/connection-catalogue.js +66 -0
  516. package/dist/remote/connection-catalogue.js.map +1 -0
  517. package/dist/remote/connection.d.ts +84 -0
  518. package/dist/remote/connection.js +229 -0
  519. package/dist/remote/connection.js.map +1 -0
  520. package/dist/remote/coverage.d.ts +65 -0
  521. package/dist/remote/coverage.js +244 -0
  522. package/dist/remote/coverage.js.map +1 -0
  523. package/dist/remote/credentials.d.ts +95 -0
  524. package/dist/remote/credentials.js +210 -0
  525. package/dist/remote/credentials.js.map +1 -0
  526. package/dist/remote/execute.d.ts +178 -0
  527. package/dist/remote/execute.js +1245 -0
  528. package/dist/remote/execute.js.map +1 -0
  529. package/dist/remote/fingerprint.d.ts +172 -0
  530. package/dist/remote/fingerprint.js +337 -0
  531. package/dist/remote/fingerprint.js.map +1 -0
  532. package/dist/remote/fixtures.d.ts +176 -0
  533. package/dist/remote/fixtures.js +432 -0
  534. package/dist/remote/fixtures.js.map +1 -0
  535. package/dist/remote/guard.d.ts +90 -0
  536. package/dist/remote/guard.js +83 -0
  537. package/dist/remote/guard.js.map +1 -0
  538. package/dist/remote/hierarchy.d.ts +141 -0
  539. package/dist/remote/hierarchy.js +129 -0
  540. package/dist/remote/hierarchy.js.map +1 -0
  541. package/dist/remote/index.d.ts +145 -0
  542. package/dist/remote/index.js +146 -0
  543. package/dist/remote/index.js.map +1 -0
  544. package/dist/remote/inspect.d.ts +103 -0
  545. package/dist/remote/inspect.js +152 -0
  546. package/dist/remote/inspect.js.map +1 -0
  547. package/dist/remote/labels.d.ts +75 -0
  548. package/dist/remote/labels.js +137 -0
  549. package/dist/remote/labels.js.map +1 -0
  550. package/dist/remote/ladder.d.ts +145 -0
  551. package/dist/remote/ladder.js +387 -0
  552. package/dist/remote/ladder.js.map +1 -0
  553. package/dist/remote/ledger.d.ts +97 -0
  554. package/dist/remote/ledger.js +148 -0
  555. package/dist/remote/ledger.js.map +1 -0
  556. package/dist/remote/lifecycle.d.ts +175 -0
  557. package/dist/remote/lifecycle.js +274 -0
  558. package/dist/remote/lifecycle.js.map +1 -0
  559. package/dist/remote/links.d.ts +411 -0
  560. package/dist/remote/links.js +748 -0
  561. package/dist/remote/links.js.map +1 -0
  562. package/dist/remote/managed-block.d.ts +182 -0
  563. package/dist/remote/managed-block.js +406 -0
  564. package/dist/remote/managed-block.js.map +1 -0
  565. package/dist/remote/managed-comment.d.ts +104 -0
  566. package/dist/remote/managed-comment.js +92 -0
  567. package/dist/remote/managed-comment.js.map +1 -0
  568. package/dist/remote/mapping.d.ts +282 -0
  569. package/dist/remote/mapping.js +336 -0
  570. package/dist/remote/mapping.js.map +1 -0
  571. package/dist/remote/merge.d.ts +172 -0
  572. package/dist/remote/merge.js +221 -0
  573. package/dist/remote/merge.js.map +1 -0
  574. package/dist/remote/periods.d.ts +209 -0
  575. package/dist/remote/periods.js +224 -0
  576. package/dist/remote/periods.js.map +1 -0
  577. package/dist/remote/plan.d.ts +671 -0
  578. package/dist/remote/plan.js +1210 -0
  579. package/dist/remote/plan.js.map +1 -0
  580. package/dist/remote/policy.d.ts +67 -0
  581. package/dist/remote/policy.js +67 -0
  582. package/dist/remote/policy.js.map +1 -0
  583. package/dist/remote/preflight.d.ts +112 -0
  584. package/dist/remote/preflight.js +342 -0
  585. package/dist/remote/preflight.js.map +1 -0
  586. package/dist/remote/prerequisites.d.ts +113 -0
  587. package/dist/remote/prerequisites.js +161 -0
  588. package/dist/remote/prerequisites.js.map +1 -0
  589. package/dist/remote/provider.d.ts +802 -0
  590. package/dist/remote/provider.js +27 -0
  591. package/dist/remote/provider.js.map +1 -0
  592. package/dist/remote/providers/github/config.d.ts +84 -0
  593. package/dist/remote/providers/github/config.js +161 -0
  594. package/dist/remote/providers/github/config.js.map +1 -0
  595. package/dist/remote/providers/github/connector.d.ts +33 -0
  596. package/dist/remote/providers/github/connector.js +774 -0
  597. package/dist/remote/providers/github/connector.js.map +1 -0
  598. package/dist/remote/providers/github/edges.d.ts +29 -0
  599. package/dist/remote/providers/github/edges.js +56 -0
  600. package/dist/remote/providers/github/edges.js.map +1 -0
  601. package/dist/remote/providers/github/hierarchy.d.ts +21 -0
  602. package/dist/remote/providers/github/hierarchy.js +46 -0
  603. package/dist/remote/providers/github/hierarchy.js.map +1 -0
  604. package/dist/remote/providers/github/index.d.ts +26 -0
  605. package/dist/remote/providers/github/index.js +69 -0
  606. package/dist/remote/providers/github/index.js.map +1 -0
  607. package/dist/remote/providers/github/project-iteration.d.ts +71 -0
  608. package/dist/remote/providers/github/project-iteration.js +203 -0
  609. package/dist/remote/providers/github/project-iteration.js.map +1 -0
  610. package/dist/remote/providers/github/project-provision.d.ts +130 -0
  611. package/dist/remote/providers/github/project-provision.js +247 -0
  612. package/dist/remote/providers/github/project-provision.js.map +1 -0
  613. package/dist/remote/providers/github/project-status.d.ts +115 -0
  614. package/dist/remote/providers/github/project-status.js +317 -0
  615. package/dist/remote/providers/github/project-status.js.map +1 -0
  616. package/dist/remote/providers/github/projects.d.ts +181 -0
  617. package/dist/remote/providers/github/projects.js +481 -0
  618. package/dist/remote/providers/github/projects.js.map +1 -0
  619. package/dist/remote/providers/github/translator.d.ts +103 -0
  620. package/dist/remote/providers/github/translator.js +400 -0
  621. package/dist/remote/providers/github/translator.js.map +1 -0
  622. package/dist/remote/providers/jira/config.d.ts +96 -0
  623. package/dist/remote/providers/jira/config.js +200 -0
  624. package/dist/remote/providers/jira/config.js.map +1 -0
  625. package/dist/remote/providers/jira/connector.d.ts +42 -0
  626. package/dist/remote/providers/jira/connector.js +1688 -0
  627. package/dist/remote/providers/jira/connector.js.map +1 -0
  628. package/dist/remote/providers/jira/fields.d.ts +292 -0
  629. package/dist/remote/providers/jira/fields.js +463 -0
  630. package/dist/remote/providers/jira/fields.js.map +1 -0
  631. package/dist/remote/providers/jira/hierarchy.d.ts +28 -0
  632. package/dist/remote/providers/jira/hierarchy.js +60 -0
  633. package/dist/remote/providers/jira/hierarchy.js.map +1 -0
  634. package/dist/remote/providers/jira/index.d.ts +29 -0
  635. package/dist/remote/providers/jira/index.js +100 -0
  636. package/dist/remote/providers/jira/index.js.map +1 -0
  637. package/dist/remote/providers/jira/links.d.ts +41 -0
  638. package/dist/remote/providers/jira/links.js +102 -0
  639. package/dist/remote/providers/jira/links.js.map +1 -0
  640. package/dist/remote/providers/jira/sprints.d.ts +80 -0
  641. package/dist/remote/providers/jira/sprints.js +107 -0
  642. package/dist/remote/providers/jira/sprints.js.map +1 -0
  643. package/dist/remote/providers/jira/translator.d.ts +61 -0
  644. package/dist/remote/providers/jira/translator.js +382 -0
  645. package/dist/remote/providers/jira/translator.js.map +1 -0
  646. package/dist/remote/providers/jira/types.d.ts +112 -0
  647. package/dist/remote/providers/jira/types.js +160 -0
  648. package/dist/remote/providers/jira/types.js.map +1 -0
  649. package/dist/remote/providers/jira/vocabulary.d.ts +55 -0
  650. package/dist/remote/providers/jira/vocabulary.js +91 -0
  651. package/dist/remote/providers/jira/vocabulary.js.map +1 -0
  652. package/dist/remote/providers/jsonfile/config.d.ts +61 -0
  653. package/dist/remote/providers/jsonfile/config.js +83 -0
  654. package/dist/remote/providers/jsonfile/config.js.map +1 -0
  655. package/dist/remote/providers/jsonfile/connector.d.ts +34 -0
  656. package/dist/remote/providers/jsonfile/connector.js +251 -0
  657. package/dist/remote/providers/jsonfile/connector.js.map +1 -0
  658. package/dist/remote/providers/jsonfile/index.d.ts +38 -0
  659. package/dist/remote/providers/jsonfile/index.js +90 -0
  660. package/dist/remote/providers/jsonfile/index.js.map +1 -0
  661. package/dist/remote/providers/jsonfile/store.d.ts +59 -0
  662. package/dist/remote/providers/jsonfile/store.js +48 -0
  663. package/dist/remote/providers/jsonfile/store.js.map +1 -0
  664. package/dist/remote/providers/jsonfile/translator.d.ts +54 -0
  665. package/dist/remote/providers/jsonfile/translator.js +199 -0
  666. package/dist/remote/providers/jsonfile/translator.js.map +1 -0
  667. package/dist/remote/providers/linear/config.d.ts +83 -0
  668. package/dist/remote/providers/linear/config.js +140 -0
  669. package/dist/remote/providers/linear/config.js.map +1 -0
  670. package/dist/remote/providers/linear/connector.d.ts +36 -0
  671. package/dist/remote/providers/linear/connector.js +438 -0
  672. package/dist/remote/providers/linear/connector.js.map +1 -0
  673. package/dist/remote/providers/linear/estimate.d.ts +63 -0
  674. package/dist/remote/providers/linear/estimate.js +112 -0
  675. package/dist/remote/providers/linear/estimate.js.map +1 -0
  676. package/dist/remote/providers/linear/index.d.ts +31 -0
  677. package/dist/remote/providers/linear/index.js +75 -0
  678. package/dist/remote/providers/linear/index.js.map +1 -0
  679. package/dist/remote/providers/linear/labels.d.ts +51 -0
  680. package/dist/remote/providers/linear/labels.js +129 -0
  681. package/dist/remote/providers/linear/labels.js.map +1 -0
  682. package/dist/remote/providers/linear/states.d.ts +97 -0
  683. package/dist/remote/providers/linear/states.js +124 -0
  684. package/dist/remote/providers/linear/states.js.map +1 -0
  685. package/dist/remote/providers/linear/translator.d.ts +48 -0
  686. package/dist/remote/providers/linear/translator.js +343 -0
  687. package/dist/remote/providers/linear/translator.js.map +1 -0
  688. package/dist/remote/providers/linear/vocabulary.d.ts +27 -0
  689. package/dist/remote/providers/linear/vocabulary.js +42 -0
  690. package/dist/remote/providers/linear/vocabulary.js.map +1 -0
  691. package/dist/remote/provision.d.ts +45 -0
  692. package/dist/remote/provision.js +81 -0
  693. package/dist/remote/provision.js.map +1 -0
  694. package/dist/remote/pull.d.ts +52 -0
  695. package/dist/remote/pull.js +116 -0
  696. package/dist/remote/pull.js.map +1 -0
  697. package/dist/remote/readiness.d.ts +67 -0
  698. package/dist/remote/readiness.js +336 -0
  699. package/dist/remote/readiness.js.map +1 -0
  700. package/dist/remote/rebase.d.ts +157 -0
  701. package/dist/remote/rebase.js +255 -0
  702. package/dist/remote/rebase.js.map +1 -0
  703. package/dist/remote/reconcile.d.ts +115 -0
  704. package/dist/remote/reconcile.js +140 -0
  705. package/dist/remote/reconcile.js.map +1 -0
  706. package/dist/remote/redact.d.ts +70 -0
  707. package/dist/remote/redact.js +132 -0
  708. package/dist/remote/redact.js.map +1 -0
  709. package/dist/remote/registry.d.ts +32 -0
  710. package/dist/remote/registry.js +54 -0
  711. package/dist/remote/registry.js.map +1 -0
  712. package/dist/remote/remotes.d.ts +94 -0
  713. package/dist/remote/remotes.js +182 -0
  714. package/dist/remote/remotes.js.map +1 -0
  715. package/dist/remote/render.d.ts +147 -0
  716. package/dist/remote/render.js +683 -0
  717. package/dist/remote/render.js.map +1 -0
  718. package/dist/remote/report.d.ts +63 -0
  719. package/dist/remote/report.js +345 -0
  720. package/dist/remote/report.js.map +1 -0
  721. package/dist/remote/resolutions.d.ts +115 -0
  722. package/dist/remote/resolutions.js +217 -0
  723. package/dist/remote/resolutions.js.map +1 -0
  724. package/dist/remote/scaffold.d.ts +134 -0
  725. package/dist/remote/scaffold.js +370 -0
  726. package/dist/remote/scaffold.js.map +1 -0
  727. package/dist/remote/scope.d.ts +57 -0
  728. package/dist/remote/scope.js +91 -0
  729. package/dist/remote/scope.js.map +1 -0
  730. package/dist/remote/selection.d.ts +38 -0
  731. package/dist/remote/selection.js +50 -0
  732. package/dist/remote/selection.js.map +1 -0
  733. package/dist/remote/shape.d.ts +141 -0
  734. package/dist/remote/shape.js +220 -0
  735. package/dist/remote/shape.js.map +1 -0
  736. package/dist/remote/status.d.ts +104 -0
  737. package/dist/remote/status.js +238 -0
  738. package/dist/remote/status.js.map +1 -0
  739. package/dist/remote/sync.d.ts +182 -0
  740. package/dist/remote/sync.js +523 -0
  741. package/dist/remote/sync.js.map +1 -0
  742. package/dist/remote/transport/budget.d.ts +48 -0
  743. package/dist/remote/transport/budget.js +68 -0
  744. package/dist/remote/transport/budget.js.map +1 -0
  745. package/dist/remote/transport/connector.d.ts +115 -0
  746. package/dist/remote/transport/connector.js +21 -0
  747. package/dist/remote/transport/connector.js.map +1 -0
  748. package/dist/remote/transport/error.d.ts +88 -0
  749. package/dist/remote/transport/error.js +196 -0
  750. package/dist/remote/transport/error.js.map +1 -0
  751. package/dist/remote/transport/gh.d.ts +20 -0
  752. package/dist/remote/transport/gh.js +41 -0
  753. package/dist/remote/transport/gh.js.map +1 -0
  754. package/dist/remote/transport/graphql.d.ts +25 -0
  755. package/dist/remote/transport/graphql.js +81 -0
  756. package/dist/remote/transport/graphql.js.map +1 -0
  757. package/dist/remote/transport/http.d.ts +47 -0
  758. package/dist/remote/transport/http.js +195 -0
  759. package/dist/remote/transport/http.js.map +1 -0
  760. package/dist/remote/transport/index.d.ts +18 -0
  761. package/dist/remote/transport/index.js +19 -0
  762. package/dist/remote/transport/index.js.map +1 -0
  763. package/dist/remote/transport/process.d.ts +39 -0
  764. package/dist/remote/transport/process.js +121 -0
  765. package/dist/remote/transport/process.js.map +1 -0
  766. package/dist/remote/transport/rest.d.ts +22 -0
  767. package/dist/remote/transport/rest.js +57 -0
  768. package/dist/remote/transport/rest.js.map +1 -0
  769. package/dist/remote/transport/retry.d.ts +91 -0
  770. package/dist/remote/transport/retry.js +138 -0
  771. package/dist/remote/transport/retry.js.map +1 -0
  772. package/dist/remote/transport-error.d.ts +19 -0
  773. package/dist/remote/transport-error.js +22 -0
  774. package/dist/remote/transport-error.js.map +1 -0
  775. package/dist/remote/vocabulary.d.ts +126 -0
  776. package/dist/remote/vocabulary.js +57 -0
  777. package/dist/remote/vocabulary.js.map +1 -0
  778. package/dist/runner/config.d.ts +7 -0
  779. package/dist/runner/config.js +61 -0
  780. package/dist/runner/config.js.map +1 -0
  781. package/dist/runner/git.d.ts +19 -0
  782. package/dist/runner/git.js +26 -0
  783. package/dist/runner/git.js.map +1 -0
  784. package/dist/runner/index.d.ts +20 -0
  785. package/dist/runner/index.js +21 -0
  786. package/dist/runner/index.js.map +1 -0
  787. package/dist/runner/loop.d.ts +46 -0
  788. package/dist/runner/loop.js +243 -0
  789. package/dist/runner/loop.js.map +1 -0
  790. package/dist/runner/pi.d.ts +39 -0
  791. package/dist/runner/pi.js +276 -0
  792. package/dist/runner/pi.js.map +1 -0
  793. package/dist/runner/shell.d.ts +44 -0
  794. package/dist/runner/shell.js +174 -0
  795. package/dist/runner/shell.js.map +1 -0
  796. package/dist/runner/stats.d.ts +19 -0
  797. package/dist/runner/stats.js +58 -0
  798. package/dist/runner/stats.js.map +1 -0
  799. package/dist/runner/types.d.ts +218 -0
  800. package/dist/runner/types.js +37 -0
  801. package/dist/runner/types.js.map +1 -0
  802. package/dist/server/git.d.ts +8 -0
  803. package/dist/server/git.js +50 -0
  804. package/dist/server/git.js.map +1 -0
  805. package/dist/server/http/respond.d.ts +13 -0
  806. package/dist/server/http/respond.js +59 -0
  807. package/dist/server/http/respond.js.map +1 -0
  808. package/dist/server/http/router.d.ts +27 -0
  809. package/dist/server/http/router.js +48 -0
  810. package/dist/server/http/router.js.map +1 -0
  811. package/dist/server/http/static.d.ts +14 -0
  812. package/dist/server/http/static.js +55 -0
  813. package/dist/server/http/static.js.map +1 -0
  814. package/dist/server/index.d.ts +58 -0
  815. package/dist/server/index.js +124 -0
  816. package/dist/server/index.js.map +1 -0
  817. package/dist/server/routes/board.d.ts +8 -0
  818. package/dist/server/routes/board.js +19 -0
  819. package/dist/server/routes/board.js.map +1 -0
  820. package/dist/server/routes/comments.d.ts +13 -0
  821. package/dist/server/routes/comments.js +36 -0
  822. package/dist/server/routes/comments.js.map +1 -0
  823. package/dist/server/routes/flags.d.ts +16 -0
  824. package/dist/server/routes/flags.js +37 -0
  825. package/dist/server/routes/flags.js.map +1 -0
  826. package/dist/server/routes/git.d.ts +13 -0
  827. package/dist/server/routes/git.js +84 -0
  828. package/dist/server/routes/git.js.map +1 -0
  829. package/dist/server/routes/remotes.d.ts +36 -0
  830. package/dist/server/routes/remotes.js +749 -0
  831. package/dist/server/routes/remotes.js.map +1 -0
  832. package/dist/server/routes/views.d.ts +10 -0
  833. package/dist/server/routes/views.js +63 -0
  834. package/dist/server/routes/views.js.map +1 -0
  835. package/dist/server/views/schema.d.ts +2 -0
  836. package/dist/server/views/schema.js +121 -0
  837. package/dist/server/views/schema.js.map +1 -0
  838. package/dist/server/views/store.d.ts +17 -0
  839. package/dist/server/views/store.js +67 -0
  840. package/dist/server/views/store.js.map +1 -0
  841. package/dist/shared/adf.d.ts +83 -0
  842. package/dist/shared/adf.js +795 -0
  843. package/dist/shared/adf.js.map +1 -0
  844. package/dist/shared/blocking.d.ts +130 -0
  845. package/dist/shared/blocking.js +179 -0
  846. package/dist/shared/blocking.js.map +1 -0
  847. package/dist/shared/changes.d.ts +100 -0
  848. package/dist/shared/changes.js +112 -0
  849. package/dist/shared/changes.js.map +1 -0
  850. package/dist/shared/cohesion.d.ts +75 -0
  851. package/dist/shared/cohesion.js +132 -0
  852. package/dist/shared/cohesion.js.map +1 -0
  853. package/dist/shared/dependency-rollup.d.ts +91 -0
  854. package/dist/shared/dependency-rollup.js +132 -0
  855. package/dist/shared/dependency-rollup.js.map +1 -0
  856. package/dist/shared/errors.d.ts +23 -0
  857. package/dist/shared/errors.js +24 -0
  858. package/dist/shared/errors.js.map +1 -0
  859. package/dist/shared/flag-rollup.d.ts +88 -0
  860. package/dist/shared/flag-rollup.js +119 -0
  861. package/dist/shared/flag-rollup.js.map +1 -0
  862. package/dist/shared/git-sync.d.ts +90 -0
  863. package/dist/shared/git-sync.js +9 -0
  864. package/dist/shared/git-sync.js.map +1 -0
  865. package/dist/shared/index.d.ts +40 -0
  866. package/dist/shared/index.js +41 -0
  867. package/dist/shared/index.js.map +1 -0
  868. package/dist/shared/model.d.ts +218 -0
  869. package/dist/shared/model.js +95 -0
  870. package/dist/shared/model.js.map +1 -0
  871. package/dist/shared/period-query.d.ts +21 -0
  872. package/dist/shared/period-query.js +26 -0
  873. package/dist/shared/period-query.js.map +1 -0
  874. package/dist/shared/period-stance.d.ts +45 -0
  875. package/dist/shared/period-stance.js +50 -0
  876. package/dist/shared/period-stance.js.map +1 -0
  877. package/dist/shared/plans/breakdown.d.ts +66 -0
  878. package/dist/shared/plans/breakdown.js +186 -0
  879. package/dist/shared/plans/breakdown.js.map +1 -0
  880. package/dist/shared/plans/index.d.ts +21 -0
  881. package/dist/shared/plans/index.js +22 -0
  882. package/dist/shared/plans/index.js.map +1 -0
  883. package/dist/shared/plans/instantiate.d.ts +61 -0
  884. package/dist/shared/plans/instantiate.js +184 -0
  885. package/dist/shared/plans/instantiate.js.map +1 -0
  886. package/dist/shared/plans/reading.d.ts +79 -0
  887. package/dist/shared/plans/reading.js +149 -0
  888. package/dist/shared/plans/reading.js.map +1 -0
  889. package/dist/shared/plans/reparent.d.ts +54 -0
  890. package/dist/shared/plans/reparent.js +217 -0
  891. package/dist/shared/plans/reparent.js.map +1 -0
  892. package/dist/shared/plans/timeline.d.ts +93 -0
  893. package/dist/shared/plans/timeline.js +224 -0
  894. package/dist/shared/plans/timeline.js.map +1 -0
  895. package/dist/shared/plans/upstream.d.ts +67 -0
  896. package/dist/shared/plans/upstream.js +73 -0
  897. package/dist/shared/plans/upstream.js.map +1 -0
  898. package/dist/shared/remote-api.d.ts +413 -0
  899. package/dist/shared/remote-api.js +11 -0
  900. package/dist/shared/remote-api.js.map +1 -0
  901. package/dist/shared/remote-coverage.d.ts +120 -0
  902. package/dist/shared/remote-coverage.js +98 -0
  903. package/dist/shared/remote-coverage.js.map +1 -0
  904. package/dist/shared/remote-readiness.d.ts +162 -0
  905. package/dist/shared/remote-readiness.js +49 -0
  906. package/dist/shared/remote-readiness.js.map +1 -0
  907. package/dist/shared/remote-status.d.ts +268 -0
  908. package/dist/shared/remote-status.js +79 -0
  909. package/dist/shared/remote-status.js.map +1 -0
  910. package/dist/shared/rollup.d.ts +90 -0
  911. package/dist/shared/rollup.js +119 -0
  912. package/dist/shared/rollup.js.map +1 -0
  913. package/dist/shared/static.d.ts +65 -0
  914. package/dist/shared/static.js +107 -0
  915. package/dist/shared/static.js.map +1 -0
  916. package/dist/shared/template-params.d.ts +80 -0
  917. package/dist/shared/template-params.js +141 -0
  918. package/dist/shared/template-params.js.map +1 -0
  919. package/dist/shared/view.d.ts +105 -0
  920. package/dist/shared/view.js +44 -0
  921. package/dist/shared/view.js.map +1 -0
  922. package/dist/shared/work-unit.d.ts +34 -0
  923. package/dist/shared/work-unit.js +47 -0
  924. package/dist/shared/work-unit.js.map +1 -0
  925. package/dist/sync/apply.d.ts +21 -0
  926. package/dist/sync/apply.js +149 -0
  927. package/dist/sync/apply.js.map +1 -0
  928. package/dist/sync/dto.d.ts +9 -0
  929. package/dist/sync/dto.js +162 -0
  930. package/dist/sync/dto.js.map +1 -0
  931. package/dist/sync/index.d.ts +24 -0
  932. package/dist/sync/index.js +25 -0
  933. package/dist/sync/index.js.map +1 -0
  934. package/dist/sync/patch.d.ts +13 -0
  935. package/dist/sync/patch.js +212 -0
  936. package/dist/sync/patch.js.map +1 -0
  937. package/dist/sync/session.d.ts +31 -0
  938. package/dist/sync/session.js +55 -0
  939. package/dist/sync/session.js.map +1 -0
  940. package/dist/sync/static.d.ts +11 -0
  941. package/dist/sync/static.js +33 -0
  942. package/dist/sync/static.js.map +1 -0
  943. package/package.json +95 -4
  944. package/templates/blank.yml +71 -0
  945. package/templates/context/bug.md +107 -0
  946. package/templates/context/default.md +119 -0
  947. package/templates/context/epic.md +81 -0
  948. package/templates/context/feature.md +91 -0
  949. package/templates/context/research.md +87 -0
  950. package/templates/context/review.md +81 -0
  951. package/templates/context/story.md +94 -0
  952. package/templates/context/sub_task.md +108 -0
  953. package/templates/context/task.md +86 -0
  954. package/templates/context/test.md +73 -0
  955. package/templates/context/user_story.md +147 -0
  956. package/templates/kanban.yml +226 -0
  957. package/templates/scrum.yml +567 -0
  958. package/web/dist/assets/index-D0j8OnHq.js +35 -0
  959. package/web/dist/assets/index-DFL7v8aP.css +1 -0
  960. package/web/dist/index.html +13 -0
  961. package/web/dist-viewer/assets/index-Bcns6OuC.css +1 -0
  962. package/web/dist-viewer/assets/index-vVXzeiFo.js +7 -0
  963. package/web/dist-viewer/index.html +14 -0
@@ -0,0 +1,1210 @@
1
+ /**
2
+ * The push and pull planners — pure functions from a board to a plan.
3
+ *
4
+ * planPush(board, links, remote) → PushPlan (LP-280, this file)
5
+ * planPull(board, links, remote, options) → PullPlan (LP-281, this file)
6
+ *
7
+ * Both take a `BoardView` DTO rather than a `LoadedBoard`, so neither needs a
8
+ * filesystem handle and the plan is a plain data structure a dry-run can render
9
+ * and an executor can walk. Neither touches the disk or the network: the push
10
+ * plan is handed to the executor (`LP-490`), the pull plan to `applyChanges`
11
+ * from `src/sync`.
12
+ *
13
+ * ## Why a push plan exists at all
14
+ *
15
+ * A push is a list of *remote operations*, not a batch write. The remote is a
16
+ * separate system with its own ids, its own workflow and its own idea of a
17
+ * parent, so the plan has to say, in order:
18
+ *
19
+ * 1. what to create, parents before children (a parent must exist before its
20
+ * children are filed under it);
21
+ * 2. what to update / transition / close, for documents that already have a
22
+ * twin;
23
+ * 3. what to link and unlink, after every endpoint exists.
24
+ *
25
+ * Two rules make the order load-bearing. A `create` op carries no remote id —
26
+ * it is referenced by a **placeholder** (`new:1`) until the executor resolves
27
+ * it against the response — and an edge (`link` / `unlink`) is emitted only
28
+ * once both endpoints exist, so a document created in this same push can be an
29
+ * endpoint. That is the held-back-link trick `src/sync/patch.ts` uses for
30
+ * `depends_on`, expressed here as a rule about op order rather than a deferred
31
+ * patch: every edge is placed after every create and update, and it references
32
+ * its endpoints through a `RemoteRef` that is either a recorded twin or a
33
+ * placeholder.
34
+ *
35
+ * ## What this file deliberately does not do
36
+ *
37
+ * - **Conflict detection on the push side** (both sides edited a field) is
38
+ * `LP-257`'s; `planPush` diffs a document against its base and pushes what
39
+ * changed locally — it never consults the remote side for a pushed field.
40
+ * The pull planner *does* detect a both-sides edit: a field both sides
41
+ * changed is reported in `PullPlan.fieldConflicts` rather than written.
42
+ * - **Existence and shape** (the twin was deleted, moved or converted) is
43
+ * `LP-361`'s; `remote.issues` is part of the input so that story and the pull
44
+ * planner can read it, but this planner does not yet act on it.
45
+ * - **The mapping** is the translator's job at execution time. The planner
46
+ * decides *what* changed against the base snapshot and *in what order* it is
47
+ * pushed; it never turns a board status into a remote state.
48
+ * - **`relates_to`** is a non-gating edge: it is linked / unlinked natively
49
+ * only where the provider declares a native relate edge (Jira, LP-325), and
50
+ * rides the managed block everywhere else (LP-314).
51
+ */
52
+ import { tempId } from '../shared/changes.js';
53
+ import { counterFactory } from '../shared/plans/reading.js';
54
+ import { subtreeIds } from '../shared/plans/reading.js';
55
+ import { anchorResolver } from './anchor.js';
56
+ import { parentIsNative, carrierChanged } from './hierarchy.js';
57
+ import { getSyncedCommentIndexes, getSyncedRemoteCommentIds, getTombstone, hashBody, isManagedComment } from './links.js';
58
+ import { mergeValues, NO_BASE, valuesEqual } from './merge.js';
59
+ import { planCommentPull, planCommentPush, renderCommentForRemote } from './comments.js';
60
+ /** Fields `computeBase` treats as a list, in either of its two spellings. */
61
+ const BASE_LIST_KEYS = ['dependsOn', 'depends_on'];
62
+ const BASE_RELATES_KEYS = ['relatesTo', 'relates_to'];
63
+ /** Read a base's dependency list, whichever spelling it was stored under. */
64
+ function baseDepends(base) {
65
+ for (const key of BASE_LIST_KEYS) {
66
+ const value = base[key];
67
+ if (Array.isArray(value)) {
68
+ return value.filter((entry) => typeof entry === 'string').sort();
69
+ }
70
+ }
71
+ return [];
72
+ }
73
+ /** Read a base's relates list, whichever spelling it was stored under. */
74
+ function baseRelates(base) {
75
+ for (const key of BASE_RELATES_KEYS) {
76
+ const value = base[key];
77
+ if (Array.isArray(value)) {
78
+ return value.filter((entry) => typeof entry === 'string').sort();
79
+ }
80
+ }
81
+ return [];
82
+ }
83
+ /** The canonical, non-attribute fields a base snapshot may record. */
84
+ const BASE_FIELD_KEYS = new Set([
85
+ 'title',
86
+ 'body',
87
+ 'status',
88
+ 'assignee',
89
+ 'period',
90
+ 'dependsOn',
91
+ 'depends_on',
92
+ 'relatesTo',
93
+ 'relates_to',
94
+ 'relatedFiles',
95
+ 'related_files',
96
+ // Shape fields (LP-368): recorded in the base so a later sync can tell who
97
+ // moved a document. They are never pushed as `update` fields — reparenting
98
+ // is planned by the shared planners, not by the field diff — so `diffIssue`
99
+ // must ignore them rather than treat them as phantom attributes.
100
+ 'parent',
101
+ 'type',
102
+ ]);
103
+ /**
104
+ * Compare one issue against its base snapshot.
105
+ *
106
+ * With a base, a field is "changed" only when the base records it and the
107
+ * local value differs — a field the base does not record (a mapping change, a
108
+ * new attribute) is not tracked, so it cannot manufacture a phantom conflict.
109
+ * Without a base (a twin recorded but never confirmed), every field visible
110
+ * locally counts as changed, so the first push converges the twin onto the
111
+ * board's current state.
112
+ */
113
+ function diffIssue(issue, base, normalizeBody) {
114
+ if (base === undefined) {
115
+ // No base: push the whole document so the twin converges.
116
+ const fields = {
117
+ title: issue.title,
118
+ body: issue.body,
119
+ assignee: issue.assignee ?? null,
120
+ period: issue.period ?? null,
121
+ };
122
+ const attributes = {};
123
+ for (const [key, value] of Object.entries(issue.attributes)) {
124
+ if (value !== null && value !== undefined)
125
+ attributes[key] = value;
126
+ }
127
+ if (Object.keys(attributes).length > 0)
128
+ fields.attributes = attributes;
129
+ return {
130
+ fields,
131
+ statusChanged: true,
132
+ status: issue.status,
133
+ dependsAdded: [...issue.dependsOn],
134
+ dependsRemoved: [],
135
+ relatesAdded: [...issue.relatesTo],
136
+ relatesRemoved: [],
137
+ };
138
+ }
139
+ const fields = {};
140
+ if ('title' in base && issue.title !== base.title)
141
+ fields.title = issue.title;
142
+ // The base stores the body as a hash (`hashBody`), so a local edit is a hash
143
+ // mismatch, never a text comparison.
144
+ if ('body' in base && hashBody(issue.body, normalizeBody) !== base.body)
145
+ fields.body = issue.body;
146
+ if ('assignee' in base && (issue.assignee ?? null) !== base.assignee) {
147
+ fields.assignee = issue.assignee ?? null;
148
+ }
149
+ if ('period' in base && (issue.period ?? null) !== base.period) {
150
+ fields.period = issue.period ?? null;
151
+ }
152
+ // Attributes are the base keys that are not canonical fields — only those
153
+ // are mapped, so only those are compared. The comparison is canonical
154
+ // (`valuesEqual`), not reference equality: an `array` attribute reloaded
155
+ // from YAML is a fresh array each time, and `!==` would re-push it forever.
156
+ const attributes = {};
157
+ for (const [key, stored] of Object.entries(base)) {
158
+ if (BASE_FIELD_KEYS.has(key))
159
+ continue;
160
+ const local = issue.attributes[key] ?? null;
161
+ if (!valuesEqual(local, stored))
162
+ attributes[key] = local;
163
+ }
164
+ if (Object.keys(attributes).length > 0)
165
+ fields.attributes = attributes;
166
+ const statusChanged = 'status' in base && issue.status !== base.status;
167
+ const localDepends = [...issue.dependsOn].sort();
168
+ const remoteDepends = baseDepends(base);
169
+ const dependsAdded = localDepends.filter((id) => !remoteDepends.includes(id));
170
+ const dependsRemoved = remoteDepends.filter((id) => !localDepends.includes(id));
171
+ const localRelates = [...issue.relatesTo].sort();
172
+ const remoteRelates = baseRelates(base);
173
+ const relatesAdded = localRelates.filter((id) => !remoteRelates.includes(id));
174
+ const relatesRemoved = remoteRelates.filter((id) => !localRelates.includes(id));
175
+ return {
176
+ fields,
177
+ statusChanged,
178
+ status: issue.status,
179
+ dependsAdded,
180
+ dependsRemoved,
181
+ relatesAdded,
182
+ relatesRemoved,
183
+ };
184
+ }
185
+ /** The issue fields a create carries, in board vocabulary. */
186
+ function boardFields(issue) {
187
+ return {
188
+ title: issue.title,
189
+ body: issue.body,
190
+ type: issue.type,
191
+ status: issue.status,
192
+ assignee: issue.assignee,
193
+ period: issue.period,
194
+ attributes: issue.attributes,
195
+ };
196
+ }
197
+ /** The number of ancestors above an issue — the depth creates are ordered by. */
198
+ function issueDepth(board, issue) {
199
+ let depth = 0;
200
+ const seen = new Set();
201
+ let parent = issue.parentId;
202
+ while (parent && !seen.has(parent)) {
203
+ seen.add(parent);
204
+ depth += 1;
205
+ parent = board.nodes[parent]?.parentId ?? null;
206
+ }
207
+ return depth;
208
+ }
209
+ /**
210
+ * The parent reference a create / restore writes, split by the hierarchy
211
+ * encoding (LP-309): native `parent` while the document sits within the
212
+ * remote's native depth, `blockParent` beyond it. With no resolved encoding
213
+ * the parent stays native, the pre-encoding behaviour.
214
+ */
215
+ function parentRefFor(remote, depth, parent) {
216
+ if (parent === undefined)
217
+ return {};
218
+ const resolved = remote.hierarchy;
219
+ if (resolved === undefined)
220
+ return { parent };
221
+ return parentIsNative(depth, resolved) ? { parent } : { blockParent: parent };
222
+ }
223
+ /**
224
+ * The comment a policy close posts on the twin before closing it (LP-351): who
225
+ * closed it, why, and the local document that went away. Written for the
226
+ * person reading the remote tracker, who may never have seen the `.lpm` folder.
227
+ */
228
+ function pushDeleteNote(localId, remoteKey, deleted) {
229
+ const key = remoteKey !== '' ? ` (${remoteKey})` : '';
230
+ const why = deleted
231
+ ? `the local document ${localId} that tracked it was deleted`
232
+ : `the local document ${localId} that tracked it moved out of this remote's scope`;
233
+ return `Closed by light-plan: ${why}${key}. See the board's \`on_delete\` policy.`;
234
+ }
235
+ /**
236
+ * Plan a push: the remote operations needed to bring the remote's in-scope
237
+ * issues onto the board's current state, ordered so every reference resolves.
238
+ *
239
+ * Returns an empty `{ ops: [], skipped: [] }` when nothing changed, when the
240
+ * remote is pull-only, or when the scope holds no documents. The order inside
241
+ * a non-empty plan is:
242
+ *
243
+ * 1. `create` ops, parents before children (by depth);
244
+ * 2. `update` / `transition` / `close` ops for changed twins;
245
+ * 3. `link` / `unlink` ops, after every endpoint exists;
246
+ * 4. the `on_delete` resolution for linked twins whose local document went
247
+ * away (deleted, or left the scope subtree): `unlink` / `close` / `delete`.
248
+ *
249
+ * In-scope documents carrying a tombstone are never created; they are
250
+ * reported in `skipped` rather than silently omitted (LP-366).
251
+ *
252
+ * A `create` carries no remote id: it is referenced by a `new:` placeholder,
253
+ * which the executor resolves against each create's response before it reaches
254
+ * any op that points at it.
255
+ */
256
+ export function planPush(board, links, remote, localComments) {
257
+ if (remote.direction === 'pull')
258
+ return { ops: [], skipped: [] };
259
+ const issues = Object.values(board.nodes)
260
+ .filter((node) => node.kind === 'issue')
261
+ .sort((a, b) => a.id.localeCompare(b.id));
262
+ const scope = remote.scope ? new Set(subtreeIds(board, remote.scope)) : null;
263
+ const inScope = (id) => scope === null || scope.has(id);
264
+ // What this run is about, which is not the same question as what the remote
265
+ // owns — see `only` on RemoteSnapshot. Everything that *writes* asks
266
+ // `acting`; the gone pass and `refOf` ask `inScope`.
267
+ const only = remote.only ?? null;
268
+ const acting = (id) => inScope(id) && (only === null || only.has(id));
269
+ const ownedElsewhere = remote.ownedElsewhere ?? null;
270
+ // -- skipped: in-scope documents deliberately decoupled -------------------
271
+ // A tombstoned document is never created, and it is reported rather than
272
+ // silently omitted (LP-366). Its base snapshot is already gone — the link
273
+ // was dropped when the tombstone was written.
274
+ const skipped = [];
275
+ // -- creates: in-scope documents with no twin, parents before children ----
276
+ const toCreate = issues.filter((issue) => {
277
+ if (!acting(issue.id))
278
+ return false;
279
+ if (links.links.has(issue.id))
280
+ return false;
281
+ const claimed = ownedElsewhere?.get(issue.id);
282
+ if (claimed !== undefined) {
283
+ // One document, one remote. Reported rather than refused: a whole-board
284
+ // push to a second tracker is a normal thing to run on a board whose
285
+ // remotes divide the work between them.
286
+ skipped.push({ localId: issue.id, reason: 'owned_elsewhere', remoteKey: claimed });
287
+ return false;
288
+ }
289
+ const tombstone = getTombstone(links, issue.id);
290
+ if (tombstone !== undefined) {
291
+ skipped.push({
292
+ localId: issue.id,
293
+ reason: tombstone.reason || 'manual',
294
+ remoteKey: tombstone.remoteKey || '',
295
+ });
296
+ return false;
297
+ }
298
+ return true;
299
+ });
300
+ toCreate.sort((a, b) => issueDepth(board, a) - issueDepth(board, b) || a.id.localeCompare(b.id));
301
+ const placeholderOf = new Map();
302
+ toCreate.forEach((issue, index) => placeholderOf.set(issue.id, tempId(index + 1)));
303
+ /** The remote reference for a local id, when it is a resolvable endpoint. */
304
+ const refOf = (id) => {
305
+ const placeholder = placeholderOf.get(id);
306
+ if (placeholder !== undefined)
307
+ return { kind: 'created', localId: id, placeholder };
308
+ const link = links.links.get(id);
309
+ if (link && inScope(id))
310
+ return { kind: 'linked', localId: id, remoteId: link.remoteId };
311
+ return undefined;
312
+ };
313
+ const ops = [];
314
+ for (const issue of toCreate) {
315
+ const parent = issue.parentId ? refOf(issue.parentId) : undefined;
316
+ ops.push({
317
+ kind: 'create',
318
+ placeholder: placeholderOf.get(issue.id),
319
+ localId: issue.id,
320
+ fields: boardFields(issue),
321
+ ...parentRefFor(remote, issueDepth(board, issue), parent),
322
+ });
323
+ }
324
+ // The diff for every in-scope twin, computed once; absent for a create.
325
+ const diffOf = new Map();
326
+ for (const issue of issues) {
327
+ if (!inScope(issue.id))
328
+ continue;
329
+ const link = links.links.get(issue.id);
330
+ if (link)
331
+ diffOf.set(issue.id, diffIssue(issue, link.base, remote.normalizeBody));
332
+ }
333
+ // -- updates / transitions / closes for changed twins ---------------------
334
+ for (const issue of issues) {
335
+ if (!acting(issue.id))
336
+ continue;
337
+ const link = links.links.get(issue.id);
338
+ if (!link)
339
+ continue; // already planned as a create
340
+ const diff = diffOf.get(issue.id);
341
+ const ref = { kind: 'linked', localId: issue.id, remoteId: link.remoteId };
342
+ const parent = issue.parentId ? refOf(issue.parentId) : undefined;
343
+ const { blockParent } = parentRefFor(remote, issueDepth(board, issue), parent);
344
+ if (Object.keys(diff.fields).length > 0) {
345
+ ops.push({
346
+ kind: 'update',
347
+ localId: issue.id,
348
+ ref,
349
+ fields: diff.fields,
350
+ ...(blockParent !== undefined ? { blockParent } : {}),
351
+ });
352
+ }
353
+ if (diff.statusChanged) {
354
+ const terminal = board.config.statuses.find((status) => status.id === diff.status)?.terminal === true;
355
+ if (terminal) {
356
+ ops.push({ kind: 'close', localId: issue.id, ref });
357
+ }
358
+ else {
359
+ ops.push({ kind: 'transition', localId: issue.id, ref, status: diff.status });
360
+ }
361
+ }
362
+ }
363
+ // -- reparent: linked twins whose parent moved, switched carrier, or has
364
+ // only now arrived upstream -------------------------------------------
365
+ // A local reparent (LP-493 gap 2) is detected against the base snapshot's
366
+ // recorded `parent`; a hierarchy-encoding switch (gap 1) against the
367
+ // encoding the link store last recorded. Both emit a `reparent` op that
368
+ // re-writes the twin's parent on whichever carrier the *current* encoding
369
+ // uses — in place, never a re-file. A twin with no parent in either the
370
+ // base or the board is left alone, whatever the encoding.
371
+ //
372
+ // The third case is what makes a plan pushable a piece at a time. A story
373
+ // filed before its feature had a twin was filed at the remote's root, and
374
+ // neither of the first two cases ever fires for it: the local parent never
375
+ // moved and the encoding never changed, so pushing the feature afterwards
376
+ // left the story dangling. `parentRemoteId` on the link is the record of
377
+ // what was actually filed, so "the board says it has a parent, the parent
378
+ // now has a twin, and that is not the twin we filed it under" is a question
379
+ // that can be asked at all. An older link that never recorded one answers
380
+ // `undefined` — unknown, and unknown changes nothing.
381
+ const recordedHierarchy = links.hierarchy;
382
+ for (const issue of issues) {
383
+ if (!acting(issue.id))
384
+ continue;
385
+ const link = links.links.get(issue.id);
386
+ if (!link)
387
+ continue; // planned as a create, whose parent is already placed
388
+ const depth = issueDepth(board, issue);
389
+ const carrierSwitched = carrierChanged(recordedHierarchy, remote.hierarchy, depth);
390
+ const baseRecordsParent = link.base !== undefined && 'parent' in link.base;
391
+ const parentChanged = baseRecordsParent &&
392
+ (issue.parentId ?? null) !== (link.base['parent'] ?? null);
393
+ // The twin's filed parent, against the parent it should have now. Only a
394
+ // link that recorded what it filed takes part; `undefined` is "we do not
395
+ // know", never "none".
396
+ const filedParent = link.parentRemoteId;
397
+ const wantedRef = issue.parentId ? refOf(issue.parentId) : undefined;
398
+ const parentArrived = filedParent !== undefined &&
399
+ wantedRef !== undefined &&
400
+ // A parent this same push is creating has no remote id to compare yet,
401
+ // so the question is whether this twin was filed at the root — which is
402
+ // exactly the case pushing the parent is meant to repair, and it is
403
+ // repaired in the same run rather than on the one after.
404
+ (wantedRef.kind === 'created' ? filedParent === null : filedParent !== wantedRef.remoteId);
405
+ if (!carrierSwitched && !parentChanged && !parentArrived)
406
+ continue;
407
+ const parent = issue.parentId ? refOf(issue.parentId) : undefined;
408
+ ops.push({
409
+ kind: 'reparent',
410
+ localId: issue.id,
411
+ ref: { kind: 'linked', localId: issue.id, remoteId: link.remoteId },
412
+ ...parentRefFor(remote, depth, parent),
413
+ });
414
+ }
415
+ // -- edges, after every endpoint exists -----------------------------------
416
+ // A provider with a native blocking edge links/unlinks it directly; one
417
+ // without carries `depends_on` in the managed block. `relates_to` is native
418
+ // only where the provider declares it (Jira, LP-325); otherwise it rides the
419
+ // block (LP-314). The block is authoritative: its op carries the complete
420
+ // current set, and the executor omits any reference it cannot resolve
421
+ // (AC #2).
422
+ const nativeDepends = remote.edges?.dependsOn ?? true;
423
+ const nativeRelates = remote.edges?.relatesTo ?? false;
424
+ const sameIds = (a, b) => {
425
+ if (a.length !== b.length)
426
+ return false;
427
+ for (let i = 0; i < a.length; i += 1)
428
+ if (a[i] !== b[i])
429
+ return false;
430
+ return true;
431
+ };
432
+ for (const issue of issues) {
433
+ if (!acting(issue.id))
434
+ continue;
435
+ const diff = diffOf.get(issue.id) ?? null;
436
+ // Native link/unlink, for each edge kind the remote holds natively
437
+ // (`depends_on` LP-314, `relates_to` LP-325). A create has no base, so
438
+ // every in-scope edge is a new link; a removal references a twin that
439
+ // must still be linked.
440
+ const nativeKinds = [];
441
+ if (nativeDepends) {
442
+ nativeKinds.push({
443
+ linkKind: 'depends',
444
+ added: diff ? diff.dependsAdded : issue.dependsOn,
445
+ removed: diff ? diff.dependsRemoved : [],
446
+ });
447
+ }
448
+ if (nativeRelates) {
449
+ nativeKinds.push({
450
+ linkKind: 'relates',
451
+ added: diff ? diff.relatesAdded : issue.relatesTo,
452
+ removed: diff ? diff.relatesRemoved : [],
453
+ });
454
+ }
455
+ for (const { linkKind, added, removed } of nativeKinds) {
456
+ for (const target of removed) {
457
+ const targetRef = refOf(target);
458
+ if (!targetRef || targetRef.kind !== 'linked')
459
+ continue;
460
+ ops.push({
461
+ kind: 'unlink',
462
+ dependent: { kind: 'linked', localId: issue.id, remoteId: links.links.get(issue.id).remoteId },
463
+ dependency: targetRef,
464
+ linkKind,
465
+ });
466
+ }
467
+ const dependentRef = refOf(issue.id);
468
+ if (dependentRef) {
469
+ for (const target of added) {
470
+ const targetRef = refOf(target);
471
+ if (!targetRef)
472
+ continue;
473
+ ops.push({ kind: 'link', dependent: dependentRef, dependency: targetRef, linkKind });
474
+ }
475
+ }
476
+ }
477
+ // The managed block (LP-314): rewrite the complete edge set when the
478
+ // resolvable set changed. An out-of-scope dependency is not resolvable
479
+ // and never enters the block, exactly as it never enters a native link.
480
+ const currentDepends = nativeDepends
481
+ ? []
482
+ : [...issue.dependsOn].filter((id) => refOf(id) !== undefined).sort();
483
+ const currentRelates = nativeRelates
484
+ ? []
485
+ : [...issue.relatesTo].filter((id) => refOf(id) !== undefined).sort();
486
+ const base = links.links.get(issue.id)?.base;
487
+ const recordedDepends = nativeDepends ? [] : baseDepends(base ?? {});
488
+ const recordedRelates = nativeRelates ? [] : baseRelates(base ?? {});
489
+ if (sameIds(currentDepends, recordedDepends) && sameIds(currentRelates, recordedRelates)) {
490
+ continue;
491
+ }
492
+ const dependentRef = refOf(issue.id);
493
+ if (!dependentRef)
494
+ continue;
495
+ const parent = issue.parentId ? refOf(issue.parentId) : undefined;
496
+ const { blockParent } = parentRefFor(remote, issueDepth(board, issue), parent);
497
+ ops.push({
498
+ kind: 'edges',
499
+ localId: issue.id,
500
+ ref: dependentRef,
501
+ ...(blockParent !== undefined ? { parent: blockParent } : {}),
502
+ dependsOn: currentDepends
503
+ .map((id) => refOf(id))
504
+ .filter((r) => r !== undefined),
505
+ relatesTo: currentRelates
506
+ .map((id) => refOf(id))
507
+ .filter((r) => r !== undefined),
508
+ });
509
+ }
510
+ // -- gone documents: deleted locally, or left the scope subtree -----------
511
+ // A linked twin whose local document no longer exists (removed with `lpm rm`)
512
+ // or that moved out of `scope:` no longer qualifies for a twin. What happens
513
+ // to the remote side is the remote's `on_delete` policy (LP-351):
514
+ //
515
+ // - `unlink` (the default) leaves the twin alone and drops the link — a
516
+ // tombstone for a document that still exists (out of scope), a plain
517
+ // local unlink for one that is gone;
518
+ // - `close` closes the twin and posts a comment saying why;
519
+ // - `delete` deletes the twin (the connector degrades where the platform
520
+ // cannot hard-delete), with confirmation every run from the consent gate.
521
+ //
522
+ // `restore` and `manual` are pull-side answers to the *remote* going away and
523
+ // have no push meaning, so they fall back to `unlink` — a value meant for the
524
+ // opposite direction can never delete or close upstream by surprise.
525
+ const onDelete = remote.onDelete ?? 'unlink';
526
+ const goneLocalIds = [];
527
+ for (const [id] of links.links) {
528
+ if (board.nodes[id] === undefined)
529
+ goneLocalIds.push({ id, deleted: true });
530
+ }
531
+ for (const issue of issues) {
532
+ if (inScope(issue.id))
533
+ continue;
534
+ if (links.links.has(issue.id))
535
+ goneLocalIds.push({ id: issue.id, deleted: false });
536
+ }
537
+ goneLocalIds.sort((a, b) => a.id.localeCompare(b.id));
538
+ for (const { id, deleted } of goneLocalIds) {
539
+ const link = links.links.get(id);
540
+ if (link === undefined)
541
+ continue;
542
+ const ref = { kind: 'linked', localId: id, remoteId: link.remoteId };
543
+ if (onDelete === 'close') {
544
+ ops.push({
545
+ kind: 'close',
546
+ localId: id,
547
+ ref,
548
+ note: pushDeleteNote(id, link.remoteKey, deleted),
549
+ });
550
+ }
551
+ else if (onDelete === 'delete') {
552
+ ops.push({ kind: 'delete', localId: id, ref });
553
+ }
554
+ else if (deleted) {
555
+ // `unlink` (and the pull-only restore / manual): drop the dangling link.
556
+ ops.push({ kind: 'unlinkLocal', localId: id });
557
+ }
558
+ else {
559
+ ops.push({ kind: 'decouple', localId: id, reason: 'out_of_scope' });
560
+ }
561
+ }
562
+ // -- user comments, after every twin exists (LP-316) ----------------------
563
+ // Post the unsynced work-log entries. A comment references its endpoint the
564
+ // same way an edge does — a `created` placeholder for a document this same
565
+ // push files, a `linked` twin otherwise — so a comment on a brand-new issue
566
+ // is posted once its create lands. `planCommentPush` drops any entry whose
567
+ // index is already recorded (never posted twice) and any body that is a
568
+ // managed block (never re-posted as a user comment).
569
+ if (localComments) {
570
+ for (const issue of issues) {
571
+ if (!acting(issue.id))
572
+ continue;
573
+ const ref = refOf(issue.id);
574
+ if (ref === undefined)
575
+ continue; // decoupled, or outside the scope subtree
576
+ const local = localComments.get(issue.id);
577
+ if (!local || local.length === 0)
578
+ continue;
579
+ for (const item of planCommentPush(local, getSyncedCommentIndexes(links, issue.id))) {
580
+ ops.push({
581
+ kind: 'comment',
582
+ ref,
583
+ index: item.index,
584
+ author: item.author,
585
+ body: renderCommentForRemote(item.author, item.body),
586
+ });
587
+ }
588
+ }
589
+ }
590
+ return { ops, skipped };
591
+ }
592
+ // ---------------------------------------------------------------------------
593
+ // Pull planning
594
+ // ---------------------------------------------------------------------------
595
+ /**
596
+ * The linked descendant of `localId` whose twin would survive a delete of
597
+ * `localId`'s subtree — the child a `delete` must refuse to orphan. A child
598
+ * that is itself being deleted (its twin vanished too, and the policy is
599
+ * `delete`) is not orphaned, so it is skipped. Returns undefined when the
600
+ * delete would orphan nothing.
601
+ */
602
+ function orphanedLinkedChild(board, links, localId, deleting) {
603
+ const descendants = subtreeIds(board, localId).slice(1);
604
+ for (const id of descendants) {
605
+ if (deleting.has(id))
606
+ continue;
607
+ if (links.links.has(id))
608
+ return id;
609
+ }
610
+ return undefined;
611
+ }
612
+ /**
613
+ * The field-diff half of a pull for one linked twin that is still present
614
+ * upstream (LP-257 AC #2, built on LP-284).
615
+ *
616
+ * Returns the `update` patch carrying the fields the remote alone changed, and
617
+ * the conflicts for fields both sides changed. Fields the board alone changed
618
+ * are deliberately absent — they are `planPush`'s to carry upstream, never
619
+ * pulled here. A field the base snapshot does not record is skipped, exactly
620
+ * as `diffIssue` skips it on the push side, so a mapping change cannot
621
+ * manufacture a phantom conflict.
622
+ *
623
+ * With no base at all (a twin recorded but never confirmed) every field the
624
+ * remote carries is merged against `NO_BASE`: agreement is `none`, a
625
+ * difference is a conflict — the only honest answer short of clobbering one
626
+ * side.
627
+ */
628
+ function pullFieldDiff(localId, remoteId, doc, base, patch, normalizeBody) {
629
+ const update = {};
630
+ const conflicts = [];
631
+ /** The base value for a field: `NO_BASE` when there is no snapshot, otherwise the stored value. */
632
+ const baseOf = (field) => (base === undefined ? NO_BASE : base[field]);
633
+ const merge = (field, local, remote, carries, apply) => {
634
+ if (!carries)
635
+ return;
636
+ const baseValue = baseOf(field);
637
+ if (baseValue === undefined)
638
+ return; // base exists but records no such field
639
+ const outcome = mergeValues(local, remote, baseValue);
640
+ if (outcome === 'pull')
641
+ apply(remote);
642
+ else if (outcome === 'conflict')
643
+ conflicts.push({ localId, remoteId, field, local, remote });
644
+ };
645
+ merge('title', doc.title, patch.title, patch.title !== undefined, (value) => {
646
+ update.title = value;
647
+ });
648
+ // The base stores the body as a hash; both sides are hashed so the four-case
649
+ // table compares hash to hash, never raw text. The conflict carries the raw
650
+ // text so a renderer can show what each side wrote.
651
+ if (patch.body !== undefined) {
652
+ const baseValue = baseOf('body');
653
+ if (baseValue !== undefined) {
654
+ const outcome = mergeValues(hashBody(doc.body, normalizeBody), hashBody(patch.body), baseValue);
655
+ if (outcome === 'pull')
656
+ update.body = patch.body;
657
+ else if (outcome === 'conflict') {
658
+ conflicts.push({ localId, remoteId, field: 'body', local: doc.body, remote: patch.body });
659
+ }
660
+ }
661
+ }
662
+ merge('status', doc.status, patch.status, patch.status !== undefined, (value) => {
663
+ update.status = value;
664
+ });
665
+ merge('assignee', doc.assignee ?? null, patch.assignee ?? null, patch.assignee !== undefined, (value) => {
666
+ update.assignee = value;
667
+ });
668
+ merge('period', doc.period ?? null, patch.period ?? null, patch.period !== undefined, (value) => {
669
+ update.period = value;
670
+ });
671
+ // Attributes: every name the base records, plus any the remote carries, each
672
+ // merged on its own so one attribute's conflict never holds a sibling back.
673
+ const attrNames = new Set();
674
+ for (const key of Object.keys(base ?? {})) {
675
+ if (!BASE_FIELD_KEYS.has(key))
676
+ attrNames.add(key);
677
+ }
678
+ const attrPatch = patch.attributes;
679
+ if (attrPatch !== undefined) {
680
+ for (const key of Object.keys(attrPatch))
681
+ attrNames.add(key);
682
+ }
683
+ for (const name of [...attrNames].sort()) {
684
+ const carries = attrPatch !== undefined && name in attrPatch;
685
+ merge(name, doc.attributes[name] ?? null, carries ? attrPatch[name] : null, carries, (value) => {
686
+ update.attributes = { ...update.attributes, [name]: value };
687
+ });
688
+ }
689
+ return { update, conflicts };
690
+ }
691
+ /** True when two sorted id lists hold the same members in the same order. */
692
+ function listEquals(a, b) {
693
+ if (a.length !== b.length)
694
+ return false;
695
+ for (let i = 0; i < a.length; i += 1)
696
+ if (a[i] !== b[i])
697
+ return false;
698
+ return true;
699
+ }
700
+ /**
701
+ * The path `start` → … → `target` following `depends_on` edges in `graph`, or
702
+ * null when `target` is unreachable. Used to refuse a pulled edge that would
703
+ * close a cycle: adding `from` depends_on `to` closes a loop exactly when `to`
704
+ * already reaches `from`.
705
+ */
706
+ function pathFrom(graph, start, target) {
707
+ const previous = new Map();
708
+ const queue = [start];
709
+ const seen = new Set([start]);
710
+ while (queue.length > 0) {
711
+ const node = queue.shift();
712
+ for (const next of graph.get(node) ?? []) {
713
+ if (next === target) {
714
+ const path = [next];
715
+ let cursor = node;
716
+ while (cursor !== start) {
717
+ path.unshift(cursor);
718
+ cursor = previous.get(cursor);
719
+ }
720
+ path.unshift(start);
721
+ return path;
722
+ }
723
+ if (seen.has(next))
724
+ continue;
725
+ seen.add(next);
726
+ previous.set(next, node);
727
+ queue.push(next);
728
+ }
729
+ }
730
+ return null;
731
+ }
732
+ /**
733
+ * Plan a pull: the `Change[]` needed to bring the board's in-scope issues into
734
+ * agreement with the remote listing, plus the link-store effects that travel
735
+ * with it.
736
+ *
737
+ * The pull is an *existence* reconciliation, plus a field diff. A remote
738
+ * issue with no twin is planned as a `create` — carrying its hierarchy parent
739
+ * and its dependency edges, both referenced by temporary ids when the other
740
+ * endpoint is also new in this pull, so the existing hold-back-and-replay
741
+ * wires them. A linked twin absent from the listing is resolved by the
742
+ * remote's `on_delete` policy (`unlink`, `close`, `delete`, `restore` or
743
+ * `manual`), one document at a time (LP-365). A linked twin still present on
744
+ * both sides is merged field by field against its base snapshot (LP-284): a
745
+ * field the remote alone changed becomes an `update` pulling the remote value
746
+ * onto the board, a field both sides changed is reported in `fieldConflicts`
747
+ * and left untouched on both sides (LP-257 AC #2), and a field the board alone
748
+ * changed is left for the push planner to carry upstream.
749
+ *
750
+ * The plan is applied by `applyChanges` from `src/sync` — the same applier the
751
+ * web app and the CLI push a view through — so the pull inherits partial
752
+ * application, temporary-id remapping and the hold-back-and-replay of a create
753
+ * whose parent is created later in the same push. (Naming note: `PushSession`
754
+ * inside `src/sync` is named for pushing a *view* onto the board, which is the
755
+ * same direction a remote **pull** moves — remote → board.)
756
+ *
757
+ * Returns an empty plan for a `direction: push` remote (it never pulls), for a
758
+ * scope that names no documents, and when the listing agrees with the link
759
+ * store. Pure: no disk, no network.
760
+ */
761
+ export function planPull(board, links, remote, options) {
762
+ if (remote.direction === 'push')
763
+ return { changes: [], links: [] };
764
+ // An unreachable remote plans nothing: the twin may be deleted or merely
765
+ // hidden by a credential that lost scope, and guessing writes a deletion
766
+ // (LP-364). The caller reports the reason and distinguishes the exit code.
767
+ if (remote.unreachable === true)
768
+ return { changes: [], links: [] };
769
+ const onDelete = remote.onDelete ?? 'unlink';
770
+ const scopeSet = remote.scope ? new Set(subtreeIds(board, remote.scope)) : null;
771
+ const parentOf = options.parentIdOf;
772
+ const dependsOnOf = options.dependsOnOf;
773
+ const relatesToOf = options.relatesToOf;
774
+ const blockEdgesOf = options.blockEdgesOf;
775
+ /** Edge references a managed block carried that could not be resolved (LP-314). */
776
+ const edgeWarnings = [];
777
+ // The local id that anchors a remote issue in the local tree: its own twin
778
+ // when it has one, otherwise the anchor of its remote parent. A remote with
779
+ // no scope pulls everything; a scoped remote pulls only issues whose anchor
780
+ // sits under the scope root. The walk is `anchor.ts` because the drift
781
+ // report asks the same question of the same listing — "where would this land?"
782
+ // — and a report that answered it differently would offer work this planner
783
+ // then declined to adopt.
784
+ const anchorOf = anchorResolver(links, remote.issues, parentOf);
785
+ const inScope = (remoteId) => {
786
+ if (scopeSet === null)
787
+ return true;
788
+ const anchored = anchorOf(remoteId);
789
+ return anchored !== undefined && scopeSet.has(anchored);
790
+ };
791
+ // -- creates: remote issues with no twin ----------------------------------
792
+ const newIssues = [...remote.issues.entries()]
793
+ .filter(([remoteId]) => !links.byRemote.has(remoteId) && inScope(remoteId))
794
+ .sort(([a], [b]) => a.localeCompare(b));
795
+ // Temporary ids from a fresh counter, one per new remote issue, so a
796
+ // reference to one (a child's parent, a later edge) names the same temp id
797
+ // the create carries. Never derived from a view's pending list.
798
+ const nextTemp = counterFactory();
799
+ const tempIdOf = new Map();
800
+ for (const [remoteId] of newIssues)
801
+ tempIdOf.set(remoteId, nextTemp());
802
+ /** The hierarchy depth of a new issue, from its remote parent chain. */
803
+ const depthMemo = new Map();
804
+ const underParent = options.underParent;
805
+ /** The depth `underParent` puts an otherwise parentless issue at. */
806
+ const adoptedDepth = underParent ? (board.nodes[underParent]?.depth ?? 0) + 1 : 0;
807
+ const depthOf = (remoteId, record) => {
808
+ if (depthMemo.has(remoteId))
809
+ return depthMemo.get(remoteId);
810
+ depthMemo.set(remoteId, 0); // cycle guard
811
+ const parentRemote = parentOf ? parentOf(record) : undefined;
812
+ if (!parentRemote)
813
+ return adoptedDepth;
814
+ const linked = links.byRemote.get(parentRemote);
815
+ if (linked !== undefined) {
816
+ const depth = (board.nodes[linked]?.depth ?? 0) + 1;
817
+ depthMemo.set(remoteId, depth);
818
+ return depth;
819
+ }
820
+ const parentRecord = remote.issues.get(parentRemote);
821
+ if (!parentRecord)
822
+ return adoptedDepth;
823
+ const depth = depthOf(parentRemote, parentRecord) + 1;
824
+ depthMemo.set(remoteId, depth);
825
+ return depth;
826
+ };
827
+ /** The `parentId` a create writes: the parent's temp id, or its twin. */
828
+ const parentRefOf = (record) => {
829
+ const parentRemote = parentOf ? parentOf(record) : undefined;
830
+ if (!parentRemote)
831
+ return underParent ?? null;
832
+ const temp = tempIdOf.get(parentRemote);
833
+ if (temp !== undefined)
834
+ return temp;
835
+ return links.byRemote.get(parentRemote) ?? underParent ?? null;
836
+ };
837
+ /** The `dependsOn` ids a create writes, each resolved to its local document. */
838
+ const dependsRefsOf = (record) => {
839
+ if (!dependsOnOf)
840
+ return [];
841
+ const refs = [];
842
+ for (const remoteId of dependsOnOf(record)) {
843
+ const temp = tempIdOf.get(remoteId);
844
+ if (temp !== undefined)
845
+ refs.push(temp);
846
+ else {
847
+ const linked = links.byRemote.get(remoteId);
848
+ if (linked !== undefined)
849
+ refs.push(linked);
850
+ }
851
+ }
852
+ return refs;
853
+ };
854
+ /**
855
+ * Resolve a list of same-repo remote ids to local ids: the dependency's
856
+ * temporary id when it is also new in this pull, its twin otherwise.
857
+ * A remote id with neither is dropped — there is no local document for the
858
+ * edge to point at.
859
+ */
860
+ const resolveRemoteIds = (remoteIds) => {
861
+ const refs = [];
862
+ for (const remoteId of remoteIds) {
863
+ const temp = tempIdOf.get(remoteId);
864
+ if (temp !== undefined)
865
+ refs.push(temp);
866
+ else {
867
+ const linked = links.byRemote.get(remoteId);
868
+ if (linked !== undefined)
869
+ refs.push(linked);
870
+ }
871
+ }
872
+ return refs;
873
+ };
874
+ /** The `relatesTo` ids a create writes, resolved exactly like `dependsRefsOf`. */
875
+ const relatesRefsOf = (record) => {
876
+ if (!relatesToOf)
877
+ return [];
878
+ return resolveRemoteIds(relatesToOf(record));
879
+ };
880
+ /** Temp id → the remote id that created it, for naming a cycle legibly. */
881
+ const tempRemoteOf = new Map();
882
+ for (const [remoteId, temp] of tempIdOf)
883
+ tempRemoteOf.set(temp, remoteId);
884
+ /** A local id named as the remote id it came from, or itself when it has none. */
885
+ const labelOf = (id) => tempRemoteOf.get(id) ?? links.links.get(id)?.remoteId ?? id;
886
+ // The prospective dependency graph: the board's existing edges plus every
887
+ // edge the pull decides to write. A pulled dependency that would close a
888
+ // cycle is refused here and named, rather than handed to `applyChanges` —
889
+ // which would reject the whole change, not the one edge (LP-325 AC #5).
890
+ const prospective = new Map();
891
+ for (const node of Object.values(board.nodes)) {
892
+ if (node.kind === 'issue' || node.kind === 'template') {
893
+ prospective.set(node.id, [...node.dependsOn]);
894
+ }
895
+ }
896
+ /** The cycle adding `from` depends_on `to` would close, or null when safe.
897
+ * Returned as a closed loop (`[from, …, from]`), ready to join for naming. */
898
+ const cycleIfLinked = (from, to) => {
899
+ if (from === to)
900
+ return [from, from];
901
+ const path = pathFrom(prospective, to, from);
902
+ return path === null ? null : [from, ...path];
903
+ };
904
+ const changes = [];
905
+ const linkOps = [];
906
+ // The type a create falls back to when nothing on the remote says otherwise
907
+ // (LP-315): the deepest declared issue type — `hierarchy.flat().at(-1)`, the
908
+ // same "deepest declared level" the period mapping uses for its container.
909
+ // A triaged GitHub issue with no mapped label still lands as the board's most
910
+ // specific type, never a document with no type (which `applyChanges` refuses).
911
+ const deepestType = (board.config.hierarchy.issue ?? []).flat().at(-1);
912
+ for (const [remoteId, record] of newIssues) {
913
+ const temp = tempIdOf.get(remoteId);
914
+ const fields = options.toPatch(record, depthOf(remoteId, record));
915
+ const patch = {};
916
+ if (fields.type !== undefined)
917
+ patch.type = fields.type;
918
+ else if (deepestType !== undefined)
919
+ patch.type = deepestType;
920
+ if (fields.title !== undefined)
921
+ patch.title = fields.title;
922
+ if (fields.body !== undefined)
923
+ patch.body = fields.body;
924
+ if (fields.status !== undefined)
925
+ patch.status = fields.status;
926
+ if (fields.assignee !== undefined)
927
+ patch.assignee = fields.assignee;
928
+ if (fields.period !== undefined)
929
+ patch.period = fields.period;
930
+ if (fields.attributes !== undefined)
931
+ patch.attributes = fields.attributes;
932
+ const parentRef = parentRefOf(record);
933
+ if (parentRef !== null)
934
+ patch.parentId = parentRef;
935
+ const dependsRefs = dependsRefsOf(record);
936
+ const relatesRefs = relatesRefsOf(record);
937
+ // Managed-block edges (LP-314): a provider with no native edge reads the
938
+ // `depends_on` / `relates_to` rows back out of the block. Same-repo
939
+ // references resolve through the link store (or the pending-create map)
940
+ // exactly like a native edge; cross-repo and unknown references are
941
+ // reported in `edgeWarnings`, never resolved.
942
+ const blockEdges = blockEdgesOf ? blockEdgesOf(record) : undefined;
943
+ if (blockEdges !== undefined) {
944
+ dependsRefs.push(...resolveRemoteIds(blockEdges.dependsOn));
945
+ relatesRefs.push(...resolveRemoteIds(blockEdges.relatesTo));
946
+ for (const warning of blockEdges.crossRepo) {
947
+ edgeWarnings.push({ remoteId, ...warning, kind: 'cross_repo' });
948
+ }
949
+ for (const warning of blockEdges.unknown) {
950
+ edgeWarnings.push({ remoteId, ...warning, kind: 'unknown' });
951
+ }
952
+ }
953
+ // A pulled dependency that would close a cycle is refused and the cycle
954
+ // named, the rest of the pull continuing (LP-325 AC #5). Only the gating
955
+ // `depends_on` edge is checked — a loop of `relates_to` is not a stall.
956
+ const safeDepends = [];
957
+ for (const dependency of [...new Set(dependsRefs)].sort()) {
958
+ const cycle = cycleIfLinked(temp, dependency);
959
+ if (cycle !== null) {
960
+ edgeWarnings.push({
961
+ remoteId,
962
+ field: 'depends_on',
963
+ reference: labelOf(dependency),
964
+ kind: 'cycle',
965
+ cycle: cycle.map(labelOf).join(' -> '),
966
+ });
967
+ continue;
968
+ }
969
+ safeDepends.push(dependency);
970
+ }
971
+ if (safeDepends.length > 0)
972
+ patch.dependsOn = safeDepends;
973
+ if (relatesRefs.length > 0)
974
+ patch.relatesTo = [...new Set(relatesRefs)].sort();
975
+ // Record the create's edges so a later edge is checked against them.
976
+ prospective.set(temp, safeDepends);
977
+ changes.push({ kind: 'create', id: temp, nodeKind: 'issue', patch });
978
+ linkOps.push({ kind: 'record', tempId: temp, remoteId });
979
+ }
980
+ // -- gone twins: linked, in scope, and absent from the listing -------------
981
+ // A partial listing proves nothing about absence — see `partial` on the
982
+ // snapshot — so a targeted pull reconciles no existence at all.
983
+ const goneTwins = remote.partial === true
984
+ ? []
985
+ : [...links.links.entries()]
986
+ .filter(([localId, entry]) => (scopeSet === null || scopeSet.has(localId)) && !remote.issues.has(entry.remoteId))
987
+ .map(([localId]) => localId)
988
+ .sort();
989
+ const restore = [];
990
+ const conflicts = [];
991
+ // `delete` removes the document *and its subtree* — the ordinary `remove`
992
+ // operation — so a linked descendant whose twin still exists would be
993
+ // deleted locally while its twin survives, an orphan the board would only
994
+ // notice on the next pull. Refuse the delete and name the child; the
995
+ // document is left for a human rather than silently reshaped (LP-365).
996
+ const deleting = onDelete === 'delete' ? new Set(goneTwins) : new Set();
997
+ const refused = new Set();
998
+ if (onDelete === 'delete') {
999
+ for (const localId of goneTwins) {
1000
+ const child = orphanedLinkedChild(board, links, localId, deleting);
1001
+ if (child !== undefined) {
1002
+ refused.add(localId);
1003
+ conflicts.push({
1004
+ localId,
1005
+ remoteId: links.links.get(localId).remoteId,
1006
+ reason: `delete refused — would orphan linked child ${child}`,
1007
+ childId: child,
1008
+ });
1009
+ }
1010
+ }
1011
+ }
1012
+ // `restore` re-files the vanished twin upstream: a re-create with no remote
1013
+ // id, referenced by a placeholder the push executor resolves, after which the
1014
+ // link is repointed at the new id and the base rewritten from the response.
1015
+ // Parents are restored before children, exactly as a push creates them.
1016
+ if (onDelete === 'restore') {
1017
+ const toRestore = goneTwins
1018
+ .filter((localId) => board.nodes[localId]?.kind === 'issue')
1019
+ .sort((a, b) => (board.nodes[a]?.depth ?? 0) - (board.nodes[b]?.depth ?? 0) || a.localeCompare(b));
1020
+ const placeholderOf = new Map();
1021
+ toRestore.forEach((localId, index) => placeholderOf.set(localId, tempId(index + 1)));
1022
+ const refOf = (id) => {
1023
+ const placeholder = placeholderOf.get(id);
1024
+ if (placeholder !== undefined)
1025
+ return { kind: 'created', localId: id, placeholder };
1026
+ const link = links.links.get(id);
1027
+ if (link && (scopeSet === null || scopeSet.has(id))) {
1028
+ return { kind: 'linked', localId: id, remoteId: link.remoteId };
1029
+ }
1030
+ return undefined;
1031
+ };
1032
+ for (const localId of toRestore) {
1033
+ const issue = board.nodes[localId];
1034
+ const parent = issue.parentId ? refOf(issue.parentId) : undefined;
1035
+ restore.push({
1036
+ kind: 'restore',
1037
+ placeholder: placeholderOf.get(localId),
1038
+ localId,
1039
+ oldRemoteId: links.links.get(localId).remoteId,
1040
+ fields: boardFields(issue),
1041
+ ...parentRefFor(remote, issueDepth(board, issue), parent),
1042
+ });
1043
+ }
1044
+ }
1045
+ for (const localId of goneTwins) {
1046
+ switch (onDelete) {
1047
+ case 'delete': {
1048
+ if (refused.has(localId))
1049
+ continue; // left for a human, above
1050
+ changes.push({ kind: 'delete', id: localId, nodeKind: 'issue' });
1051
+ // `applyPull` skips the unlink when the `delete` change failed, so the
1052
+ // next pull retries the deletion rather than silently dropping it.
1053
+ linkOps.push({ kind: 'unlink', localId });
1054
+ break;
1055
+ }
1056
+ case 'close': {
1057
+ // Follow the remote by closing the local document. The status used is
1058
+ // the one the board declares terminal — never a hard-coded name — and
1059
+ // the document is decoupled so a later push does not re-file a closed
1060
+ // document upstream as a brand-new issue.
1061
+ const terminal = board.config.statuses.find((status) => status.terminal)?.id;
1062
+ if (terminal !== undefined) {
1063
+ changes.push({
1064
+ kind: 'update',
1065
+ id: localId,
1066
+ nodeKind: 'issue',
1067
+ patch: { status: terminal },
1068
+ });
1069
+ }
1070
+ linkOps.push({ kind: 'decouple', localId, reason: 'remote_deleted' });
1071
+ break;
1072
+ }
1073
+ case 'unlink': {
1074
+ // Decouple: the document is untouched, the link is dropped, and a
1075
+ // tombstone records the decision so the next push does not re-file it
1076
+ // (LP-366's tombstone, LP-365's resolution).
1077
+ linkOps.push({ kind: 'decouple', localId, reason: 'remote_deleted' });
1078
+ break;
1079
+ }
1080
+ case 'restore':
1081
+ // Handled above: the restore op repoints the link when it lands.
1082
+ break;
1083
+ case 'manual': {
1084
+ conflicts.push({
1085
+ localId,
1086
+ remoteId: links.links.get(localId).remoteId,
1087
+ reason: 'on_delete: manual — the twin is gone and a human must choose',
1088
+ });
1089
+ break;
1090
+ }
1091
+ }
1092
+ }
1093
+ // -- field diffs: linked twins still present on both sides -----------------
1094
+ // A linked twin still in the listing is merged field by field against its
1095
+ // base snapshot (LP-284). A field the remote alone changed is pulled onto
1096
+ // the board as an `update`; a field both sides changed is a conflict, left
1097
+ // for a human (LP-257 AC #2) — neither side's value is written for it.
1098
+ const fieldConflicts = [];
1099
+ for (const [localId, link] of [...links.links.entries()].sort(([a], [b]) => a.localeCompare(b))) {
1100
+ if (scopeSet !== null && !scopeSet.has(localId))
1101
+ continue;
1102
+ const record = remote.issues.get(link.remoteId);
1103
+ if (record === undefined)
1104
+ continue; // gone twin — the on_delete section handled it
1105
+ const doc = board.nodes[localId];
1106
+ if (!doc || doc.kind !== 'issue')
1107
+ continue;
1108
+ const { update, conflicts: mergedConflicts } = pullFieldDiff(localId, link.remoteId, doc, link.base, options.toPatch(record, doc.depth), remote.normalizeBody);
1109
+ fieldConflicts.push(...mergedConflicts);
1110
+ // -- native edges (LP-325) ----------------------------------------------
1111
+ // An edge changed on the remote alone is pulled onto the board: an edge the
1112
+ // remote removed since the base is removed locally, an edge the remote
1113
+ // added is added — `depends_on` cycle-checked, `relates_to` never (a loop
1114
+ // of associations is not a stall). An edge the board alone changed is left
1115
+ // for the push planner, exactly as a scalar field is.
1116
+ if (dependsOnOf || relatesToOf) {
1117
+ const remoteDepends = dependsOnOf ? new Set(resolveRemoteIds(dependsOnOf(record))) : new Set();
1118
+ const remoteRelates = relatesToOf ? new Set(resolveRemoteIds(relatesToOf(record))) : new Set();
1119
+ const baseDep = new Set(baseDepends(link.base ?? {}));
1120
+ const baseRel = new Set(baseRelates(link.base ?? {}));
1121
+ const nextDepends = new Set(doc.dependsOn);
1122
+ const nextRelates = new Set(doc.relatesTo);
1123
+ for (const id of baseDep)
1124
+ if (!remoteDepends.has(id))
1125
+ nextDepends.delete(id);
1126
+ for (const id of baseRel)
1127
+ if (!remoteRelates.has(id))
1128
+ nextRelates.delete(id);
1129
+ for (const id of remoteDepends) {
1130
+ if (baseDep.has(id) || nextDepends.has(id))
1131
+ continue;
1132
+ const cycle = cycleIfLinked(localId, id);
1133
+ if (cycle !== null) {
1134
+ edgeWarnings.push({
1135
+ remoteId: link.remoteId,
1136
+ field: 'depends_on',
1137
+ reference: labelOf(id),
1138
+ kind: 'cycle',
1139
+ cycle: cycle.map(labelOf).join(' -> '),
1140
+ });
1141
+ continue;
1142
+ }
1143
+ nextDepends.add(id);
1144
+ }
1145
+ for (const id of remoteRelates) {
1146
+ if (baseRel.has(id) || nextRelates.has(id))
1147
+ continue;
1148
+ nextRelates.add(id);
1149
+ }
1150
+ const sortedDepends = [...nextDepends].sort();
1151
+ const sortedRelates = [...nextRelates].sort();
1152
+ if (!listEquals(sortedDepends, [...doc.dependsOn].sort()))
1153
+ update.dependsOn = sortedDepends;
1154
+ if (!listEquals(sortedRelates, [...doc.relatesTo].sort()))
1155
+ update.relatesTo = sortedRelates;
1156
+ // The agreed edges become the graph a later twin's edge is checked
1157
+ // against, so a cycle spanning two linked twins is still caught.
1158
+ prospective.set(localId, sortedDepends);
1159
+ }
1160
+ if (Object.keys(update).length > 0) {
1161
+ changes.push({ kind: 'update', id: localId, nodeKind: 'issue', patch: update });
1162
+ }
1163
+ }
1164
+ // -- user comments (LP-316) -------------------------------------------------
1165
+ // Opt-in (`comments: both`): append remote comments to `_comments.md` for
1166
+ // every remote issue in scope, new and existing alike. A comment already
1167
+ // recorded in the link store is skipped, and the managed comment is excluded
1168
+ // by id (where the store records it) and by body (where the id was lost).
1169
+ // The ops carry a temp id for a new issue, resolved by `applyPull` against
1170
+ // `idMap` exactly like a `record` link op.
1171
+ const comments = [];
1172
+ if (remote.commentsMode === 'both' && remote.remoteComments) {
1173
+ const remoteComments = remote.remoteComments;
1174
+ const appendFor = (remoteId, targetId) => {
1175
+ const list = remoteComments.get(remoteId);
1176
+ if (!list || list.length === 0)
1177
+ return;
1178
+ const synced = getSyncedRemoteCommentIds(links, targetId);
1179
+ const toAppend = planCommentPull(list, synced, (id, body) => isManagedComment(links, targetId, id, body));
1180
+ for (const comment of toAppend) {
1181
+ comments.push({
1182
+ kind: 'append',
1183
+ targetId,
1184
+ remoteId: comment.id,
1185
+ author: comment.author,
1186
+ at: comment.createdAt,
1187
+ body: comment.body,
1188
+ });
1189
+ }
1190
+ };
1191
+ for (const [remoteId] of newIssues) {
1192
+ appendFor(remoteId, tempIdOf.get(remoteId));
1193
+ }
1194
+ for (const [localId, link] of [...links.links.entries()].sort(([a], [b]) => a.localeCompare(b))) {
1195
+ if (scopeSet !== null && !scopeSet.has(localId))
1196
+ continue;
1197
+ appendFor(link.remoteId, localId);
1198
+ }
1199
+ }
1200
+ return {
1201
+ changes,
1202
+ links: linkOps,
1203
+ ...(comments.length > 0 ? { comments } : {}),
1204
+ ...(restore.length > 0 ? { restore } : {}),
1205
+ ...(conflicts.length > 0 ? { conflicts } : {}),
1206
+ ...(fieldConflicts.length > 0 ? { fieldConflicts } : {}),
1207
+ ...(edgeWarnings.length > 0 ? { edgeWarnings } : {}),
1208
+ };
1209
+ }
1210
+ //# sourceMappingURL=plan.js.map