@dsdenes/devboard 0.1.559 → 0.1.562

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 (323) hide show
  1. package/AGENTS.md +59 -0
  2. package/README.md +2497 -26
  3. package/bin/devboard.mjs +352 -118
  4. package/client/App.svelte +523 -0
  5. package/client/FeedbackOverlay.svelte +1125 -0
  6. package/client/FlowMode.svelte +1015 -0
  7. package/client/app.css +3922 -0
  8. package/client/env.d.ts +23 -0
  9. package/client/lib/commands.ts +836 -0
  10. package/client/lib/components/ActivityBar.svelte +295 -0
  11. package/client/lib/components/AdrTable.svelte +241 -0
  12. package/client/lib/components/CommandPalette.svelte +198 -0
  13. package/client/lib/components/ConceptKind.svelte +66 -0
  14. package/client/lib/components/ConfirmDialog.svelte +62 -0
  15. package/client/lib/components/DataTable.svelte +243 -0
  16. package/client/lib/components/DetailSplit.svelte +68 -0
  17. package/client/lib/components/DiscoverOverview.svelte +898 -0
  18. package/client/lib/components/DiscoverWorkspace.svelte +437 -0
  19. package/client/lib/components/DraftsPage.svelte +186 -0
  20. package/client/lib/components/GoalWorkspace.svelte +1042 -0
  21. package/client/lib/components/IssuesAgentFocus.svelte +117 -0
  22. package/client/lib/components/IssuesAgentLog.svelte +50 -0
  23. package/client/lib/components/IssuesQuestion.svelte +95 -0
  24. package/client/lib/components/IssuesWorkspace.svelte +577 -0
  25. package/client/lib/components/MermaidDiagram.svelte +89 -0
  26. package/client/lib/components/MonacoEditor.svelte +190 -0
  27. package/client/lib/components/NotificationRegion.svelte +195 -0
  28. package/client/lib/components/PaneHandle.svelte +25 -0
  29. package/client/lib/components/ScoutIssues.svelte +215 -0
  30. package/client/lib/components/ScoutWorkspace.svelte +180 -0
  31. package/client/lib/components/Sidebar.svelte +622 -0
  32. package/client/lib/components/StatusBar.svelte +118 -0
  33. package/client/lib/components/Terminal.svelte +193 -0
  34. package/client/lib/components/WatchdogWorkspace.svelte +537 -0
  35. package/client/lib/components/ui/button.svelte +56 -0
  36. package/client/lib/components/ui/icon-button.svelte +56 -0
  37. package/client/lib/components/ui/input.svelte +19 -0
  38. package/client/lib/components/ui/page-header.svelte +120 -0
  39. package/client/lib/components/ui/stage-track.svelte +141 -0
  40. package/client/lib/components/watchdog-indicator.svelte +189 -0
  41. package/client/lib/connection.svelte.ts +210 -0
  42. package/client/lib/dependency-diagram.test.ts +90 -0
  43. package/client/lib/dependency-diagram.ts +75 -0
  44. package/client/lib/discover-format.test.ts +265 -0
  45. package/client/lib/discover-format.ts +394 -0
  46. package/client/lib/discover.svelte.ts +412 -0
  47. package/client/lib/draft-format.test.ts +48 -0
  48. package/client/lib/draft-format.ts +13 -0
  49. package/client/lib/flow-save.test.ts +148 -0
  50. package/client/lib/flow.svelte.ts +445 -0
  51. package/client/lib/goal-reconnect.test.ts +282 -0
  52. package/client/lib/goal.svelte.ts +652 -0
  53. package/client/lib/issues.svelte.ts +439 -0
  54. package/client/lib/layout.svelte.ts +234 -0
  55. package/client/lib/notification-issue.test.ts +53 -0
  56. package/client/lib/notification-issue.ts +27 -0
  57. package/client/lib/notifications.svelte.ts +62 -0
  58. package/client/lib/poll-status.test.ts +141 -0
  59. package/client/lib/poll-status.ts +88 -0
  60. package/client/lib/record-status.test.ts +19 -0
  61. package/client/lib/record-status.ts +25 -0
  62. package/client/lib/scout.svelte.ts +164 -0
  63. package/client/lib/skill-document.test.ts +77 -0
  64. package/client/lib/skill-document.ts +57 -0
  65. package/client/lib/status-tone.test.ts +40 -0
  66. package/client/lib/status-tone.ts +33 -0
  67. package/client/lib/types.ts +578 -0
  68. package/client/lib/utils.ts +6 -0
  69. package/client/lib/viewport.svelte.ts +20 -0
  70. package/client/lib/watchdog.svelte.ts +69 -0
  71. package/client/main.ts +7 -0
  72. package/dist/apple-touch-icon.png +0 -0
  73. package/dist/assets/abnfDiagram-VCTEODGH-CmBSMArZ.js +1 -0
  74. package/dist/assets/arc-BZgCSX2H.js +1 -0
  75. package/dist/assets/architecture-7GRP2DOG-CF9MHlfo.js +1 -0
  76. package/dist/assets/architectureDiagram-5GKGNRK7-DzWB6xiQ.js +36 -0
  77. package/dist/assets/array-BifhSqXX.js +1 -0
  78. package/dist/assets/blockDiagram-I7D4REHJ-BtmGJ0hn.js +129 -0
  79. package/dist/assets/c4Diagram-7LVT6UL2-D5CHrrRX.js +38 -0
  80. package/dist/assets/channel-CgM4lcUW.js +1 -0
  81. package/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  82. package/dist/assets/chunk-4HAMMTFA-DeIufjFl.js +62 -0
  83. package/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  84. package/dist/assets/chunk-75Z2AOVW-DbtzyKUg.js +2 -0
  85. package/dist/assets/chunk-DU6HZSFF-C0fdbx3K.js +127 -0
  86. package/dist/assets/chunk-F27PBJKO-HObL2IvF.js +1 -0
  87. package/dist/assets/chunk-FOHPRMQF-DD4dKubD.js +161 -0
  88. package/dist/assets/chunk-GMAD6QVW-fg8wvtPY.js +72 -0
  89. package/dist/assets/chunk-GVQU2GXP-C0TaHOrD.js +1 -0
  90. package/dist/assets/chunk-IMKFNOWR-BVoIdx20.js +231 -0
  91. package/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  92. package/dist/assets/chunk-L3NEJ4N5-B_kmqfk5.js +1 -0
  93. package/dist/assets/chunk-OSK3NFVY-DO0vhxHH.js +10 -0
  94. package/dist/assets/chunk-P2QGCYS3-CjljLvFj.js +1 -0
  95. package/dist/assets/chunk-POPQ4Y6H-BU0C7tw6.js +1 -0
  96. package/dist/assets/chunk-PWAF6VOD-Dn0OjBt0.js +1 -0
  97. package/dist/assets/chunk-SHT3W25Y-CMCZE2xx.js +168 -0
  98. package/dist/assets/chunk-SVP7TREG-BIRmRy_W.js +88 -0
  99. package/dist/assets/chunk-TICWLB2K-C1bc2CDE.js +206 -0
  100. package/dist/assets/chunk-XXDRQBXY-Cc38QUmR.js +1 -0
  101. package/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  102. package/dist/assets/classDiagram-ZZMXUADV-BDh5w6Nc.js +1 -0
  103. package/dist/assets/classDiagram-v2-VYDZK3BY-BDh5w6Nc.js +1 -0
  104. package/dist/assets/cose-bilkent-JH36ORCC-B7pU7qhM.js +1 -0
  105. package/dist/assets/cynefin-OW5HDTMX-76dswSqS.js +1 -0
  106. package/dist/assets/cynefinDiagram-5FMLGOSQ-2kS4q01d.js +62 -0
  107. package/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
  108. package/dist/assets/dagre-D7JM2Yhv.js +1 -0
  109. package/dist/assets/dagre-GXQ25YYZ-B5Y21TQM.js +4 -0
  110. package/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  111. package/dist/assets/diagram-S7CK7UJ4-CY6td7Et.js +30 -0
  112. package/dist/assets/diagram-UQ7AKVKN-DmDqnGZf.js +41 -0
  113. package/dist/assets/diagram-VSXAHHWV-KGLWOOcE.js +3 -0
  114. package/dist/assets/diagram-VX7I27RA-D5syUraJ.js +24 -0
  115. package/dist/assets/diagram-Z3DM3KII-Dtrbs1Om.js +24 -0
  116. package/dist/assets/dist-Dce-2YbA.js +1 -0
  117. package/dist/assets/ebnfDiagram-PWID7BFC-CQpJLtU3.js +1 -0
  118. package/dist/assets/editor.worker-BmdWdrHZ.js +33 -0
  119. package/dist/assets/erDiagram-RLTQ6QDP-cMTzKLu_.js +99 -0
  120. package/dist/assets/eventmodeling-NTZA5JFV-CMzLdNaL.js +1 -0
  121. package/dist/assets/flowDiagram-HODETNUW-BMUajB-G.js +1 -0
  122. package/dist/assets/ganttDiagram-EL5Y4UJY-DseBS5Fs.js +292 -0
  123. package/dist/assets/gitGraph-4MIJSDKK-CUQZRpxJ.js +1 -0
  124. package/dist/assets/gitGraphDiagram-WWUBYQGX-CfMfsJ3b.js +106 -0
  125. package/dist/assets/graphlib-DS17s2tU.js +1 -0
  126. package/dist/assets/index-ByQ-XX38.css +1 -0
  127. package/dist/assets/index-CIf5TpfY.js +214 -0
  128. package/dist/assets/info-A6RAGUB7-D3nAKZ8M.js +1 -0
  129. package/dist/assets/infoDiagram-27XIBGKW-zu1O0WOy.js +2 -0
  130. package/dist/assets/init-C-OQMol4.js +1 -0
  131. package/dist/assets/ishikawaDiagram-5VMMS53U-DKC7BO9p.js +70 -0
  132. package/dist/assets/journeyDiagram-3NMN7TZE-Ld5mXse7.js +139 -0
  133. package/dist/assets/kanban-definition-UXKFOSKX-CH7_QlHL.js +89 -0
  134. package/dist/assets/katex-DJhrJSbw.js +257 -0
  135. package/dist/assets/line-N9XiCREw.js +1 -0
  136. package/dist/assets/linear-C9hnqmJv.js +1 -0
  137. package/dist/assets/mermaid-parser.core-DgPwV5Bh.js +7 -0
  138. package/dist/assets/mermaid.core-BKqynXLR.js +44 -0
  139. package/dist/assets/mindmap-definition-YA3MSWOX-DkiL-wiW.js +96 -0
  140. package/dist/assets/monaco-DjpsE8to.css +1 -0
  141. package/dist/assets/monaco-DqzJaNIe.js +908 -0
  142. package/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  143. package/dist/assets/packet-AYTQ26CC-DuMTvpEJ.js +1 -0
  144. package/dist/assets/path-fybaL0A-.js +1 -0
  145. package/dist/assets/pegDiagram-XKGWAZYB-DvMl_xSe.js +1 -0
  146. package/dist/assets/pie-WAS4IAKB-Da0xPaD8.js +1 -0
  147. package/dist/assets/pieDiagram-E7YTZNPT-amANNRJY.js +39 -0
  148. package/dist/assets/quadrantDiagram-AXDQQJYC-BrgvDMnk.js +7 -0
  149. package/dist/assets/radar-RG4KPBEZ-Dz3Ih5-N.js +1 -0
  150. package/dist/assets/railroad-74A4TZTK-CKMW-IV2.js +1 -0
  151. package/dist/assets/railroad-abnf-HS5TGJTU-ChqmSWSF.js +1 -0
  152. package/dist/assets/railroad-ebnf-LZEXJU2U-V3R4fFdb.js +1 -0
  153. package/dist/assets/railroad-peg-WCYAUIDC-B-RuhUQW.js +1 -0
  154. package/dist/assets/railroadDiagram-O6MQD6OU-FjG-6OLu.js +1 -0
  155. package/dist/assets/requirementDiagram-BXWQKSXE-Cnj8RfE4.js +84 -0
  156. package/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  157. package/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  158. package/dist/assets/sankeyDiagram-P5KCCOFB-DMu1fnEh.js +40 -0
  159. package/dist/assets/sequenceDiagram-WJ2MYXX4-CtCtaRpW.js +162 -0
  160. package/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  161. package/dist/assets/src-CGp3bfio.js +1 -0
  162. package/dist/assets/stateDiagram-D77RDMKH-xcJbLNhz.js +1 -0
  163. package/dist/assets/stateDiagram-v2-MP3YSRHH-DYHrQmJJ.js +1 -0
  164. package/dist/assets/swimlanes-42K2YHIH-BwLln_ga.js +1 -0
  165. package/dist/assets/swimlanesDiagram-VR7AAH4N-BcDfUQ0A.js +8 -0
  166. package/dist/assets/timeline-definition-24CTP7MA-ucNGsUUz.js +120 -0
  167. package/dist/assets/treeView-Q6P3EWNA-3uOomRue.js +1 -0
  168. package/dist/assets/treemap-WGGIJYW6-iGK7vwID.js +1 -0
  169. package/dist/assets/vennDiagram-4TSXK5OY-B9LgGe16.js +34 -0
  170. package/dist/assets/wardley-WFR3VGLG-FupMOf6k.js +1 -0
  171. package/dist/assets/wardleyDiagram-VM6X3IG4-CGZdEXM2.js +78 -0
  172. package/dist/assets/xterm-BrP-ENHg.css +1 -0
  173. package/dist/assets/xterm-C8zLTNGk.js +16 -0
  174. package/dist/assets/xychartDiagram-S5SC5T6Z-DafZrSBJ.js +7 -0
  175. package/dist/favicon.ico +0 -0
  176. package/dist/favicon.svg +6 -0
  177. package/dist/index.html +34 -0
  178. package/extensions/implement-goal-issue.ts +368 -0
  179. package/extensions/implement-next-epic.ts +835 -0
  180. package/index.html +28 -0
  181. package/install.sh +111 -0
  182. package/issues/control.ts +91 -0
  183. package/issues/guards.ts +12 -0
  184. package/issues/ledger.ts +122 -0
  185. package/issues/legacy.ts +68 -0
  186. package/issues/lock.ts +305 -0
  187. package/issues/paths.ts +76 -0
  188. package/issues/preflight.ts +189 -0
  189. package/issues/repository.ts +203 -0
  190. package/issues/scout/actions.ts +255 -0
  191. package/issues/scout/control.ts +80 -0
  192. package/issues/scout/github.ts +358 -0
  193. package/issues/scout/paths.ts +129 -0
  194. package/issues/scout/preflight.ts +277 -0
  195. package/issues/scout/recommendation.ts +400 -0
  196. package/issues/scout/scan.ts +143 -0
  197. package/issues/scout/state.ts +448 -0
  198. package/issues/scout/worker.ts +1282 -0
  199. package/issues/state.ts +584 -0
  200. package/issues/tracker.ts +271 -0
  201. package/issues/worker.ts +1065 -0
  202. package/lib/dependencies.ts +61 -0
  203. package/lib/forge-auth.ts +953 -0
  204. package/lib/forge.ts +141 -0
  205. package/lib/gitea.ts +709 -0
  206. package/lib/github-release.ts +318 -0
  207. package/lib/inventory.test.ts +79 -0
  208. package/lib/inventory.ts +53 -0
  209. package/lib/release.test.ts +269 -0
  210. package/lib/release.ts +801 -0
  211. package/omp/commands/adr-resolve.md +8 -0
  212. package/omp/commands/discover.md +6 -0
  213. package/omp/commands/epic-next.md +8 -0
  214. package/omp/commands/flow-change.md +8 -0
  215. package/omp/commands/flow-goal.md +8 -0
  216. package/omp/commands/flow-implement.md +8 -0
  217. package/omp/commands/flow-widget.md +8 -0
  218. package/omp/commands/goal-draft.md +8 -0
  219. package/omp/commands/goal-outcome.md +8 -0
  220. package/omp/commands/implement-issue.md +8 -0
  221. package/omp/commands/prepare-skills.md +6 -0
  222. package/omp/commands/scout-recommendations.md +8 -0
  223. package/omp/commands/setup-devboard.md +24 -0
  224. package/omp/config.yml +12 -0
  225. package/omp/extensions/implement-goal-issue.ts +2 -0
  226. package/omp/extensions/implement-next-epic.ts +2 -0
  227. package/omp/mcp.json +9 -0
  228. package/package.json +86 -7
  229. package/prompts/adr-resolve.md +130 -0
  230. package/prompts/discover.md +541 -0
  231. package/prompts/epic-next.md +533 -0
  232. package/prompts/flow-change.md +76 -0
  233. package/prompts/flow-goal.md +126 -0
  234. package/prompts/flow-implement.md +103 -0
  235. package/prompts/flow-widget.md +54 -0
  236. package/prompts/goal-draft.md +147 -0
  237. package/prompts/goal-outcome.md +87 -0
  238. package/prompts/implement-issue.md +66 -0
  239. package/prompts/prepare-skills.md +142 -0
  240. package/prompts/scout-recommendations.md +143 -0
  241. package/public/apple-touch-icon.png +0 -0
  242. package/public/favicon.ico +0 -0
  243. package/public/favicon.svg +6 -0
  244. package/server/acp.ts +540 -0
  245. package/server/activity.test.ts +96 -0
  246. package/server/activity.ts +48 -0
  247. package/server/app.test.ts +882 -0
  248. package/server/app.ts +6179 -0
  249. package/server/ask.test.ts +543 -0
  250. package/server/ask.ts +589 -0
  251. package/server/dependencies.test.ts +118 -0
  252. package/server/discover-remote.test.ts +1602 -0
  253. package/server/discover-sheet.test.ts +176 -0
  254. package/server/discover.test.ts +1510 -0
  255. package/server/discover.ts +3198 -0
  256. package/server/feedback.test.ts +458 -0
  257. package/server/feedback.ts +369 -0
  258. package/server/fixtures/adr-source.ts +71 -0
  259. package/server/fixtures/hang-download.ts +14 -0
  260. package/server/fixtures/stub-agent.ts +1870 -0
  261. package/server/fixtures/stub-forge.ts +141 -0
  262. package/server/fixtures/stub-gh.ts +57 -0
  263. package/server/flow-pr.test.ts +976 -0
  264. package/server/flow.test.ts +1632 -0
  265. package/server/flow.ts +2655 -0
  266. package/server/forge-pr.ts +387 -0
  267. package/server/goal-creation.test.ts +957 -0
  268. package/server/goal-draft.test.ts +348 -0
  269. package/server/goal-drafts.ts +449 -0
  270. package/server/goal-proposal.test.ts +479 -0
  271. package/server/goal-proposal.ts +232 -0
  272. package/server/goal-revision.test.ts +613 -0
  273. package/server/goal-slug.test.ts +56 -0
  274. package/server/goal-slug.ts +96 -0
  275. package/server/goal.ts +20 -0
  276. package/server/index.ts +147 -0
  277. package/server/issue-close.test.ts +100 -0
  278. package/server/issue-close.ts +178 -0
  279. package/server/issues.test.ts +167 -0
  280. package/server/issues.ts +479 -0
  281. package/server/journal.test.ts +350 -0
  282. package/server/journal.ts +329 -0
  283. package/server/lifecycle-pause.test.ts +1795 -0
  284. package/server/lifecycle.test.ts +1879 -0
  285. package/server/parallel-lifecycle.test.ts +1029 -0
  286. package/server/persistence-restart.test.ts +723 -0
  287. package/server/persistence.test.ts +1935 -0
  288. package/server/records.test.ts +180 -0
  289. package/server/records.ts +99 -0
  290. package/server/reset-implementation.test.ts +997 -0
  291. package/server/run.test.ts +611 -0
  292. package/server/scout.test.ts +877 -0
  293. package/server/scout.ts +459 -0
  294. package/server/skills-preparation.test.ts +1073 -0
  295. package/server/state.ts +249 -0
  296. package/server/tracker.ts +369 -0
  297. package/server/updates.test.ts +444 -0
  298. package/server/updates.ts +198 -0
  299. package/server/version.test.ts +140 -0
  300. package/server/version.ts +151 -0
  301. package/server/watchdog.ts +389 -0
  302. package/svelte.config.js +5 -0
  303. package/templates/host-AGENTS.md +235 -0
  304. package/tsconfig.json +21 -0
  305. package/vite.config.ts +21 -0
  306. package/watchdog/cleanup-main.ts +98 -0
  307. package/watchdog/cleanup.ts +301 -0
  308. package/watchdog/collect.ts +311 -0
  309. package/watchdog/coordinator.ts +397 -0
  310. package/watchdog/delivery.ts +107 -0
  311. package/watchdog/discovery.ts +209 -0
  312. package/watchdog/findings.ts +211 -0
  313. package/watchdog/github.ts +189 -0
  314. package/watchdog/history.ts +138 -0
  315. package/watchdog/install.ts +103 -0
  316. package/watchdog/ledger.ts +188 -0
  317. package/watchdog/lock.ts +63 -0
  318. package/watchdog/main.ts +106 -0
  319. package/watchdog/status.ts +71 -0
  320. package/watchdog/systemd.ts +96 -0
  321. package/watchdog/uninstall.ts +30 -0
  322. package/watchdog/units/devboard-watchdog.service +9 -0
  323. package/watchdog/units/devboard-watchdog.timer +10 -0
@@ -0,0 +1,652 @@
1
+ import { connection } from "./connection.svelte";
2
+ import type {
3
+ AdrRecord,
4
+ Lane,
5
+ LaneEpic,
6
+ LaneStatus,
7
+ LifecycleStage,
8
+ LifecycleState,
9
+ RunAction,
10
+ RunQuestion,
11
+ RunQuestionOption,
12
+ SkillContradiction,
13
+ SkillSummary,
14
+ } from "./types";
15
+
16
+ type Message = Record<string, unknown>;
17
+
18
+ const LIFECYCLE_STAGES = new Set<string>(["goal", "epic", "adr", "implementation", "finished"]);
19
+ const LANE_STATUSES = new Set<string>(["running", "paused", "closed", "failed", "retrying", "parked"]);
20
+
21
+ function isObject(value: unknown): value is Record<string, unknown> {
22
+ return typeof value === "object" && value !== null;
23
+ }
24
+
25
+ function isRecord(value: unknown): value is AdrRecord {
26
+ return (
27
+ isObject(value) &&
28
+ typeof value.file === "string" &&
29
+ typeof value.title === "string" &&
30
+ typeof value.status === "string" &&
31
+ typeof value.pending === "boolean" &&
32
+ typeof value.summary === "string"
33
+ );
34
+ }
35
+
36
+ function isOption(value: unknown): value is RunQuestionOption {
37
+ return (
38
+ isObject(value) &&
39
+ typeof value.label === "string" &&
40
+ (value.description === undefined || typeof value.description === "string") &&
41
+ typeof value.recommended === "boolean"
42
+ );
43
+ }
44
+
45
+ function isQuestion(value: unknown): value is RunQuestion {
46
+ return (
47
+ isObject(value) &&
48
+ (typeof value.id === "string" || typeof value.id === "number") &&
49
+ typeof value.message === "string" &&
50
+ Array.isArray(value.options) &&
51
+ value.options.every(isOption)
52
+ );
53
+ }
54
+
55
+ function isSkill(value: unknown): value is SkillSummary {
56
+ return (
57
+ isObject(value) &&
58
+ typeof value.name === "string" &&
59
+ typeof value.description === "string" &&
60
+ (value.family === undefined || value.family === "prepare" || value.family === "discover") &&
61
+ (value.state === undefined ||
62
+ value.state === "generated" ||
63
+ value.state === "edited" ||
64
+ value.state === "missing") &&
65
+ (value.content === undefined || typeof value.content === "string")
66
+ );
67
+ }
68
+
69
+ function isContradiction(value: unknown): value is SkillContradiction {
70
+ return (
71
+ isObject(value) &&
72
+ Array.isArray(value.skills) &&
73
+ value.skills.length === 2 &&
74
+ value.skills.every((name) => typeof name === "string") &&
75
+ Array.isArray(value.directives) &&
76
+ value.directives.length === 2 &&
77
+ value.directives.every((directive) => typeof directive === "string")
78
+ );
79
+ }
80
+
81
+ function isEpic(value: unknown): value is LaneEpic {
82
+ return (
83
+ isObject(value) &&
84
+ typeof value.number === "number" &&
85
+ typeof value.title === "string" &&
86
+ typeof value.url === "string"
87
+ );
88
+ }
89
+
90
+ function isLane(value: unknown): value is Lane {
91
+ return (
92
+ isObject(value) &&
93
+ typeof value.id === "string" &&
94
+ (value.kind === "generation" || value.kind === "implementation" || value.kind === "adr") &&
95
+ typeof value.attempt === "number" &&
96
+ typeof value.status === "string" &&
97
+ LANE_STATUSES.has(value.status) &&
98
+ (value.epic === null || isEpic(value.epic)) &&
99
+ (value.record === null ||
100
+ (isObject(value.record) &&
101
+ typeof value.record.file === "string" &&
102
+ typeof value.record.title === "string")) &&
103
+ Array.isArray(value.activity) &&
104
+ value.activity.every((line) => typeof line === "string") &&
105
+ (value.question === null || isQuestion(value.question))
106
+ );
107
+ }
108
+
109
+ function isLifecycle(value: unknown): value is LifecycleState & Message {
110
+ if (
111
+ !isObject(value) ||
112
+ typeof value.stage !== "string" ||
113
+ !LIFECYCLE_STAGES.has(value.stage) ||
114
+ typeof value.pass !== "number" ||
115
+ typeof value.paused !== "boolean"
116
+ ) {
117
+ return false;
118
+ }
119
+ if (value.work === null) return true;
120
+ return (
121
+ isObject(value.work) &&
122
+ (value.work.kind === "adr" || value.work.kind === "epic") &&
123
+ (typeof value.work.id === "string" || typeof value.work.id === "number")
124
+ );
125
+ }
126
+
127
+ function appendLine(lines: string[], text: string, append: boolean): string[] {
128
+ if (append && lines.length > 0) {
129
+ return [...lines.slice(0, -1), (lines[lines.length - 1] ?? "") + text];
130
+ }
131
+ return [...lines, text];
132
+ }
133
+
134
+ export class GoalStore {
135
+ records = $state<AdrRecord[]>([]);
136
+ next = $state<string | null>(null);
137
+
138
+ goalText = $state("");
139
+ goalDraft = $state("");
140
+ goalDirty = $state(false);
141
+ goalReady = $state(false);
142
+ creationActive = $state(false);
143
+ revisionActive = $state(false);
144
+ revisionRequest = $state("");
145
+ skillsPrepActive = $state(false);
146
+
147
+ runFile = $state<string | null>(null);
148
+ runTitle = $state<string | null>(null);
149
+ runAction = $state<RunAction>("resolving");
150
+ runState = $state<"idle" | "active" | "stopped">("idle");
151
+ manualRunActive = $state(false);
152
+ activity = $state<string[]>([]);
153
+ question = $state<RunQuestion | null>(null);
154
+ draft = $state("");
155
+ laneDraft = $state("");
156
+
157
+ lifecycle = $state<LifecycleState>({ stage: "goal", pass: 1, work: null, paused: false });
158
+ lifecycleSeen = $state(false);
159
+ lanes = $state<Lane[]>([]);
160
+ openEpics = $state(0);
161
+ runningLanes = $state(0);
162
+
163
+ skills = $state<SkillSummary[]>([]);
164
+ selectedSkill = $state<string | null>(null);
165
+ contradictions = $state<SkillContradiction[]>([]);
166
+
167
+ resetArmed = $state(false);
168
+ resetting = $state(false);
169
+ resetStatus = $state<string | null>(null);
170
+
171
+ /**
172
+ * Clears the operation flags before a reconnect's state requests are sent.
173
+ * A stop frame missed during the disconnect otherwise leaves the tab busy.
174
+ * The server replays the started frame of an operation that is still active
175
+ * before answering getGoal and getSkills, so a live operation returns.
176
+ */
177
+ resetActiveOperations(): void {
178
+ this.creationActive = false;
179
+ this.revisionActive = false;
180
+ this.skillsPrepActive = false;
181
+ this.question = null;
182
+ this.draft = "";
183
+ }
184
+
185
+ get pendingCount(): number {
186
+ return this.records.filter((record) => record.pending).length;
187
+ }
188
+
189
+ get running(): boolean {
190
+ return this.runState === "active";
191
+ }
192
+
193
+ get lifecycleActive(): boolean {
194
+ return (
195
+ this.lifecycleSeen &&
196
+ (this.lifecycle.paused ||
197
+ this.lifecycle.stage === "adr" ||
198
+ this.lifecycle.stage === "implementation" ||
199
+ (this.lifecycle.stage === "epic" && this.lifecycle.work === null))
200
+ );
201
+ }
202
+
203
+ get controlsDisabled(): boolean {
204
+ return (
205
+ this.running ||
206
+ this.lifecycleActive ||
207
+ this.revisionActive ||
208
+ this.creationActive ||
209
+ this.skillsPrepActive
210
+ );
211
+ }
212
+
213
+ get runStartDisabled(): boolean {
214
+ return (
215
+ this.creationActive ||
216
+ this.revisionActive ||
217
+ this.skillsPrepActive ||
218
+ this.manualRunActive ||
219
+ (!this.lifecycleActive && this.running)
220
+ );
221
+ }
222
+
223
+ get revisionDisabled(): boolean {
224
+ return (
225
+ this.creationActive ||
226
+ this.skillsPrepActive ||
227
+ this.manualRunActive ||
228
+ (this.lifecycleActive ? !this.lifecycle.paused : this.running)
229
+ );
230
+ }
231
+
232
+ get skillsPrepared(): boolean {
233
+ return this.skills.length > 0;
234
+ }
235
+
236
+ selectSkill(name: string | null): void {
237
+ this.selectedSkill = name;
238
+ }
239
+
240
+ private keepSelectedSkill(): void {
241
+ if (this.selectedSkill !== null && !this.skills.some((skill) => skill.name === this.selectedSkill)) {
242
+ this.selectedSkill = null;
243
+ }
244
+ }
245
+
246
+ get prepareDisabled(): boolean {
247
+ return this.controlsDisabled || !this.goalReady;
248
+ }
249
+
250
+ get lifecycleButtonDisabled(): boolean {
251
+ if (!connection.open) return true;
252
+ if (this.lifecycleActive) {
253
+ return this.resetting || this.creationActive || this.revisionActive || this.skillsPrepActive;
254
+ }
255
+ return (
256
+ this.resetting ||
257
+ this.controlsDisabled ||
258
+ !this.goalReady ||
259
+ !this.skillsPrepared
260
+ );
261
+ }
262
+
263
+ get startLifecycleTitle(): string | undefined {
264
+ if (!this.goalReady) return "Save a non-empty goal before starting the lifecycle.";
265
+ if (!this.skillsPrepared) return "Prepare skills before starting the lifecycle.";
266
+ return undefined;
267
+ }
268
+
269
+ get lifecycleButtonTitle(): string | undefined {
270
+ return this.lifecycleActive
271
+ ? "Stop the lifecycle and its current run."
272
+ : this.startLifecycleTitle;
273
+ }
274
+
275
+ get goalEditingDisabled(): boolean {
276
+ return (this.lifecycleActive && !this.lifecycle.paused) || this.skillsPrepActive;
277
+ }
278
+
279
+ get resetDisabled(): boolean {
280
+ return !connection.open || this.resetting;
281
+ }
282
+
283
+ get activityActive(): boolean {
284
+ return this.running || this.creationActive || this.revisionActive || this.skillsPrepActive;
285
+ }
286
+
287
+ get currentAction(): string {
288
+ return this.activity[this.activity.length - 1] ?? "";
289
+ }
290
+
291
+ get previousActions(): string[] {
292
+ return this.activity.slice(0, -1);
293
+ }
294
+
295
+ get previousActionsLabel(): string {
296
+ const count = this.previousActions.length;
297
+ return count === 1 ? "1 previous action" : `${count} previous actions`;
298
+ }
299
+
300
+ get lifecycleWorkTitle(): string | null {
301
+ if (this.lifecycle.work === null) return null;
302
+ if (this.lifecycle.work.kind === "epic") return `Epic #${this.lifecycle.work.id}`;
303
+ return (
304
+ this.records.find((record) => record.file === this.lifecycle.work?.id)?.title ??
305
+ this.lifecycle.work.id
306
+ );
307
+ }
308
+
309
+ get lifecycleWorkUrl(): string | null {
310
+ return this.lifecycle.work?.kind === "epic" ? (this.lifecycle.work.url ?? null) : null;
311
+ }
312
+
313
+ lane(id: string): Lane | null {
314
+ return this.lanes.find((lane) => lane.id === id) ?? null;
315
+ }
316
+
317
+ setGoalDraft(text: string): void {
318
+ this.goalDraft = text;
319
+ this.goalDirty = text !== this.goalText;
320
+ }
321
+
322
+ saveGoal(): void {
323
+ if (!connection.send({ type: "saveGoal", text: this.goalDraft })) return;
324
+ this.goalDirty = false;
325
+ }
326
+
327
+ discardDraft(): void {
328
+ this.goalDraft = this.goalText;
329
+ this.goalDirty = false;
330
+ }
331
+
332
+ createGoal(): void {
333
+ connection.send({ type: "createGoal" });
334
+ }
335
+
336
+ reviseGoal(): void {
337
+ const request = this.revisionRequest.trim();
338
+ connection.send(
339
+ request.length === 0 ? { type: "reviseGoal" } : { type: "reviseGoal", request },
340
+ );
341
+ }
342
+
343
+ prepareSkills(): void {
344
+ connection.send({ type: "prepareSkills" });
345
+ }
346
+
347
+ get skillDeleteDisabled(): boolean {
348
+ return !connection.open || this.controlsDisabled || this.resetting;
349
+ }
350
+
351
+ deleteSkill(name: string): void {
352
+ if (this.skillDeleteDisabled) return;
353
+ connection.send({ type: "deleteSkill", name });
354
+ }
355
+
356
+ start(target?: string): void {
357
+ connection.send(
358
+ target === undefined ? { type: "startRun" } : { type: "startRun", file: target },
359
+ );
360
+ }
361
+
362
+ startRecord(record: AdrRecord): void {
363
+ this.start(record.file === this.next ? undefined : record.file);
364
+ }
365
+
366
+ startNext(): void {
367
+ this.start();
368
+ }
369
+
370
+ toggleLifecycle(): void {
371
+ connection.send({ type: this.lifecycleActive ? "stopLifecycle" : "startLifecycle" });
372
+ }
373
+
374
+ pauseLifecycle(): void {
375
+ connection.send({ type: "pauseLifecycle" });
376
+ }
377
+
378
+ resumeLifecycle(): void {
379
+ connection.send({ type: "resumeLifecycle" });
380
+ }
381
+
382
+ stopLifecycle(): void {
383
+ connection.send({ type: "stopLifecycle" });
384
+ }
385
+
386
+ armReset(): void {
387
+ if (this.resetDisabled) return;
388
+ this.resetArmed = true;
389
+ }
390
+
391
+ cancelReset(): void {
392
+ this.resetArmed = false;
393
+ }
394
+
395
+ confirmReset(): void {
396
+ if (!this.resetArmed || this.resetDisabled) return;
397
+ connection.send({ type: "resetImplementation" });
398
+ }
399
+
400
+ resetImplementation(): void {
401
+ this.armReset();
402
+ }
403
+
404
+ submitAnswer(value: string): void {
405
+ if (this.question === null) return;
406
+ const trimmed = value.trim();
407
+ if (trimmed.length === 0) return;
408
+ const id = this.question.id;
409
+ this.question = null;
410
+ this.draft = "";
411
+ connection.send({ type: "answer", id, value: trimmed });
412
+ }
413
+
414
+ submitLaneAnswer(laneId: string, value: string): void {
415
+ const lane = this.lane(laneId);
416
+ if (lane === null || lane.question === null) return;
417
+ const trimmed = value.trim();
418
+ if (trimmed.length === 0) return;
419
+ const id = lane.question.id;
420
+ lane.question = null;
421
+ this.laneDraft = "";
422
+ connection.send({ type: "answer", lane: laneId, id, value: trimmed });
423
+ }
424
+
425
+ laneLabel(lane: Lane): string {
426
+ const base =
427
+ lane.kind === "generation"
428
+ ? "Epic generation"
429
+ : lane.kind === "adr"
430
+ ? (lane.record?.title ?? lane.record?.file ?? "ADR resolution")
431
+ : `Epic #${lane.epic?.number ?? ""}`;
432
+ return lane.attempt > 1 ? `${base} (attempt ${lane.attempt})` : base;
433
+ }
434
+
435
+ laneIdentity(lane: Lane): { text: string; url: string | null } {
436
+ if (lane.kind === "generation") return { text: "Epic generation", url: null };
437
+ if (lane.kind === "adr") {
438
+ return { text: `${lane.record?.title ?? ""} (${lane.record?.file ?? ""})`, url: null };
439
+ }
440
+ return { text: lane.epic?.title ?? "", url: lane.epic?.url ?? null };
441
+ }
442
+
443
+ handleEvent(message: Message): void {
444
+ const type = message.type;
445
+
446
+ if (type === "records" && Array.isArray(message.records) && message.records.every(isRecord)) {
447
+ this.records = message.records;
448
+ this.next = typeof message.next === "string" ? message.next : null;
449
+ return;
450
+ }
451
+
452
+ if (type === "goal" && typeof message.text === "string") {
453
+ if (!this.revisionActive && !this.creationActive) {
454
+ this.goalText = message.text;
455
+ if (!this.goalDirty) this.goalDraft = message.text;
456
+ this.goalReady = message.text.trim().length > 0;
457
+ }
458
+ return;
459
+ }
460
+
461
+ if (type === "goalCreationStarted") {
462
+ this.creationActive = true;
463
+ this.activity = [];
464
+ this.question = null;
465
+ this.draft = "";
466
+ return;
467
+ }
468
+ if (type === "goalCreationStopped") {
469
+ this.creationActive = false;
470
+ this.question = null;
471
+ this.draft = "";
472
+ if (message.completed === true && typeof message.text === "string") {
473
+ this.goalText = message.text;
474
+ this.goalDraft = message.text;
475
+ this.goalDirty = false;
476
+ this.goalReady = message.text.trim().length > 0;
477
+ }
478
+ return;
479
+ }
480
+ if (type === "goalRevisionStarted") {
481
+ this.revisionActive = true;
482
+ this.activity = [];
483
+ this.question = null;
484
+ this.draft = "";
485
+ return;
486
+ }
487
+ if (type === "goalRevisionStopped") {
488
+ this.revisionActive = false;
489
+ this.question = null;
490
+ this.draft = "";
491
+ if (message.completed === true && typeof message.text === "string") {
492
+ this.goalText = message.text;
493
+ this.goalDraft = message.text;
494
+ this.goalDirty = false;
495
+ this.goalReady = message.text.trim().length > 0;
496
+ }
497
+ return;
498
+ }
499
+
500
+ if (type === "skillsState") {
501
+ if (
502
+ !this.skillsPrepActive &&
503
+ Array.isArray(message.skills) &&
504
+ message.skills.every(isSkill) &&
505
+ Array.isArray(message.contradictions) &&
506
+ message.contradictions.every(isContradiction)
507
+ ) {
508
+ this.skills = message.skills;
509
+ this.contradictions = message.contradictions;
510
+ this.keepSelectedSkill();
511
+ }
512
+ return;
513
+ }
514
+ if (type === "skillsPreparationStarted") {
515
+ this.skillsPrepActive = true;
516
+ this.activity = [];
517
+ this.question = null;
518
+ this.draft = "";
519
+ return;
520
+ }
521
+ if (type === "skillsPreparationStopped") {
522
+ this.skillsPrepActive = false;
523
+ this.question = null;
524
+ this.draft = "";
525
+ if (
526
+ message.completed === true &&
527
+ Array.isArray(message.skills) &&
528
+ message.skills.every(isSkill)
529
+ ) {
530
+ this.skills = message.skills;
531
+ this.contradictions = Array.isArray(message.contradictions)
532
+ ? message.contradictions.filter(isContradiction)
533
+ : [];
534
+ this.keepSelectedSkill();
535
+ }
536
+ return;
537
+ }
538
+
539
+ if (type === "laneState") {
540
+ if (
541
+ Array.isArray(message.lanes) &&
542
+ message.lanes.every(isLane) &&
543
+ typeof message.openEpics === "number" &&
544
+ typeof message.runningLanes === "number"
545
+ ) {
546
+ this.lanes = message.lanes;
547
+ this.openEpics = message.openEpics;
548
+ this.runningLanes = message.runningLanes;
549
+ }
550
+ return;
551
+ }
552
+
553
+ if (type === "runStarted" && message.lane === undefined) {
554
+ if (typeof message.file === "string" && typeof message.title === "string") {
555
+ this.runFile = message.file;
556
+ this.runTitle = message.title;
557
+ this.runAction =
558
+ message.action === "implementing" || message.action === "generating"
559
+ ? message.action
560
+ : "resolving";
561
+ this.runState = "active";
562
+ this.manualRunActive = message.manual === true;
563
+ this.activity = [];
564
+ this.question = null;
565
+ this.draft = "";
566
+ }
567
+ return;
568
+ }
569
+
570
+ if (type === "activity" && typeof message.text === "string") {
571
+ if (typeof message.lane === "string") {
572
+ const laneId = message.lane;
573
+ this.lanes = this.lanes.map((lane) =>
574
+ lane.id === laneId
575
+ ? {
576
+ ...lane,
577
+ activity: appendLine(lane.activity, message.text as string, message.append === true),
578
+ }
579
+ : lane,
580
+ );
581
+ } else {
582
+ this.activity = appendLine(this.activity, message.text, message.append === true);
583
+ }
584
+ return;
585
+ }
586
+
587
+ if (type === "question" && isQuestion(message)) {
588
+ const pending: RunQuestion = {
589
+ id: message.id,
590
+ message: message.message,
591
+ options: message.options,
592
+ };
593
+ if (typeof message.lane === "string") {
594
+ const laneId = message.lane;
595
+ this.lanes = this.lanes.map((lane) =>
596
+ lane.id === laneId ? { ...lane, question: pending } : lane,
597
+ );
598
+ } else {
599
+ this.question = pending;
600
+ this.draft = "";
601
+ }
602
+ return;
603
+ }
604
+
605
+ if (type === "runStopped" && message.lane === undefined) {
606
+ this.runFile = null;
607
+ this.runTitle = null;
608
+ this.runState = "stopped";
609
+ this.manualRunActive = false;
610
+ this.question = null;
611
+ this.draft = "";
612
+ return;
613
+ }
614
+
615
+ if (type === "implementationResetStarted") {
616
+ this.resetting = true;
617
+ this.resetArmed = false;
618
+ this.resetStatus = null;
619
+ return;
620
+ }
621
+ if (type === "implementationResetStopped") {
622
+ this.resetting = false;
623
+ this.resetArmed = false;
624
+ this.resetStatus =
625
+ message.completed === true
626
+ ? `Reset committed as ${typeof message.commit === "string" ? message.commit : "an unknown commit"}.`
627
+ : "Reset did not complete.";
628
+ return;
629
+ }
630
+
631
+ if (type === "lifecycleState" && isLifecycle(message)) {
632
+ this.lifecycle = {
633
+ stage: message.stage as LifecycleStage,
634
+ pass: message.pass,
635
+ work:
636
+ message.work === null
637
+ ? null
638
+ : {
639
+ kind: message.work.kind as "adr" | "epic",
640
+ id: String((message.work as unknown as Record<string, unknown>).id),
641
+ ...(typeof (message.work as unknown as Record<string, unknown>).url === "string"
642
+ ? { url: (message.work as unknown as Record<string, unknown>).url as string }
643
+ : {}),
644
+ },
645
+ paused: message.paused,
646
+ } as LifecycleState;
647
+ if (this.lifecycle.stage !== "goal" || this.lifecycle.paused) this.lifecycleSeen = true;
648
+ }
649
+ }
650
+ }
651
+
652
+ export const goal = new GoalStore();