@dsdenes/devboard 0.1.560 → 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,1042 @@
1
+ <script lang="ts">
2
+ import { connection } from "../connection.svelte";
3
+ import { goal } from "../goal.svelte";
4
+ import ChevronRight from "lucide-svelte/icons/chevron-right";
5
+ import Trash2 from "lucide-svelte/icons/trash-2";
6
+ import type { WorkspaceTab } from "../layout.svelte";
7
+ import { recordStatusTone } from "../record-status";
8
+ import { renderSkillDocument } from "../skill-document";
9
+ import { statusTone } from "../status-tone";
10
+ import type { AdrRecord, Lane } from "../types";
11
+ import AdrTable from "./AdrTable.svelte";
12
+ import ConfirmDialog from "./ConfirmDialog.svelte";
13
+ import MonacoEditor from "./MonacoEditor.svelte";
14
+ import Terminal from "./Terminal.svelte";
15
+ import Button from "./ui/button.svelte";
16
+ import Input from "./ui/input.svelte";
17
+ import PageHeader from "./ui/page-header.svelte";
18
+ import StageTrack from "./ui/stage-track.svelte";
19
+
20
+ interface Props {
21
+ tab: WorkspaceTab | null;
22
+ openTab: (tab: WorkspaceTab) => void;
23
+ closeTab: (tab: WorkspaceTab) => void;
24
+ }
25
+
26
+ let { tab, openTab, closeTab }: Props = $props();
27
+
28
+ type FilterMode = "all" | "pending" | "resolved";
29
+ let filterMode = $state<FilterMode>("all");
30
+ let filterQuery = $state("");
31
+ let skillsList = $state<HTMLUListElement | undefined>();
32
+ let deleteTarget = $state<string | null>(null);
33
+
34
+ function skillPanelId(name: string): string {
35
+ return `skill-content-${name.replace(/[^A-Za-z0-9_-]/g, "-")}`;
36
+ }
37
+
38
+ const createGoalTitle = $derived(
39
+ goal.creationActive
40
+ ? "Restart goal creation."
41
+ : goal.goalReady
42
+ ? "Start a new goal creation and replace the current goal."
43
+ : goal.running || goal.lifecycleActive || goal.revisionActive
44
+ ? "Stop the active run and start goal creation."
45
+ : "Start goal creation.",
46
+ );
47
+
48
+ const runStartTitle = $derived(
49
+ goal.lifecycleActive
50
+ ? "Interrupt the lifecycle and resolve the ADRs manually."
51
+ : undefined,
52
+ );
53
+
54
+ const resolvedCount = $derived(goal.records.filter((record) => !record.pending).length);
55
+ const pendingCount = $derived(goal.pendingCount);
56
+ const progressLine = $derived(
57
+ `${resolvedCount} of ${goal.records.length} resolved · ${pendingCount} ADR${
58
+ pendingCount === 1 ? "" : "s"
59
+ } pending`,
60
+ );
61
+ const nextRecord = $derived(
62
+ goal.next === null ? null : (goal.records.find((record) => record.file === goal.next) ?? null),
63
+ );
64
+ const query = $derived(filterQuery.trim().toLowerCase());
65
+ const filteredRecords = $derived(
66
+ goal.records.filter((record) => {
67
+ if (filterMode === "pending" && !record.pending) return false;
68
+ if (filterMode === "resolved" && record.pending) return false;
69
+ if (query.length === 0) return true;
70
+ return [record.title, record.file, record.summary, record.status]
71
+ .join(" ")
72
+ .toLowerCase()
73
+ .includes(query);
74
+ }),
75
+ );
76
+
77
+ const lane = $derived(tab?.kind === "lane" ? goal.lane(tab.resourceId) : null);
78
+ const record = $derived(
79
+ tab?.kind === "adr" ? (goal.records.find((entry) => entry.file === tab?.resourceId) ?? null) : null,
80
+ );
81
+
82
+ const filters: Array<{ id: FilterMode; label: string }> = [
83
+ { id: "all", label: "All" },
84
+ { id: "pending", label: "Pending" },
85
+ { id: "resolved", label: "Resolved" },
86
+ ];
87
+
88
+ const operationText = $derived(
89
+ goal.creationActive
90
+ ? "Creating the goal"
91
+ : goal.revisionActive
92
+ ? "Revising the goal"
93
+ : goal.skillsPrepActive
94
+ ? "Preparing skills"
95
+ : goal.running
96
+ ? `Manual run: ${goal.runTitle ?? "ADR"}`
97
+ : null,
98
+ );
99
+
100
+ const documentState = $derived(
101
+ goal.goalEditingDisabled
102
+ ? "Read only while work runs"
103
+ : goal.goalDirty
104
+ ? "Unsaved changes"
105
+ : goal.goalReady
106
+ ? "Saved"
107
+ : "Not written yet",
108
+ );
109
+
110
+ const runActionLabel = $derived(
111
+ goal.runAction === "implementing"
112
+ ? "implementing"
113
+ : goal.runAction === "generating"
114
+ ? "generating an epic"
115
+ : "resolving",
116
+ );
117
+
118
+ function openAdr(entry: AdrRecord): void {
119
+ openTab({ id: `adr:${entry.file}`, kind: "adr", resourceId: entry.file, title: entry.title });
120
+ }
121
+
122
+ function openLane(entry: Lane): void {
123
+ openTab({
124
+ id: `lane:${entry.id}`,
125
+ kind: "lane",
126
+ resourceId: entry.id,
127
+ title: goal.laneLabel(entry),
128
+ });
129
+ }
130
+
131
+ function openOverview(): void {
132
+ openTab({ id: "overview", kind: "overview", resourceId: "overview", title: "Implementation" });
133
+ }
134
+
135
+ function lanePanel(entry: Lane) {
136
+ const activity = entry.activity;
137
+ return {
138
+ current: activity.length > 0 ? [activity[activity.length - 1] ?? ""] : [],
139
+ history: activity.slice(0, -1),
140
+ identity: goal.laneIdentity(entry),
141
+ label: goal.laneLabel(entry),
142
+ };
143
+ }
144
+
145
+ function submitAnswer(value: string): void {
146
+ goal.submitAnswer(value);
147
+ }
148
+
149
+ function clearFilters(): void {
150
+ filterMode = "all";
151
+ filterQuery = "";
152
+ }
153
+
154
+ $effect(() => {
155
+ if (tab?.kind !== "skills" || goal.selectedSkill === null) return;
156
+ const selected = goal.selectedSkill;
157
+ const item = [...(skillsList?.querySelectorAll<HTMLElement>("[data-name]") ?? [])].find(
158
+ (entry) => entry.dataset.name === selected,
159
+ );
160
+ item?.scrollIntoView({ block: "nearest" });
161
+ });
162
+ </script>
163
+
164
+ <div class="mode-body">
165
+ {#snippet runQuestion()}
166
+ {#if goal.question !== null}
167
+ {@const pending = goal.question}
168
+ <section class="panel goal-question" data-testid="question">
169
+ <div class="panel__header">
170
+ <h2 class="panel__title">Waiting on you</h2>
171
+ <span class="badge" data-tone="accent">run blocked</span>
172
+ </div>
173
+ <div class="panel__body stack-sm">
174
+ <p class="muted" style="margin: 0;">{pending.message}</p>
175
+ {#if pending.options.length > 0}
176
+ <div class="choice-list">
177
+ {#each pending.options as option (option.label)}
178
+ <button
179
+ type="button"
180
+ class="choice"
181
+ data-testid="choice"
182
+ data-command="goal.answer.submit"
183
+ onclick={() => submitAnswer(option.label)}
184
+ >
185
+ <span class="choice__label">
186
+ {option.label}
187
+ {#if option.recommended}
188
+ <span class="choice__recommended" data-testid="recommended">Recommended</span>
189
+ {/if}
190
+ </span>
191
+ {#if option.description}
192
+ <span class="choice__description">{option.description}</span>
193
+ {/if}
194
+ </button>
195
+ {/each}
196
+ </div>
197
+ {/if}
198
+ <form
199
+ class="answer-form"
200
+ onsubmit={(event) => {
201
+ event.preventDefault();
202
+ goal.submitAnswer(goal.draft);
203
+ }}
204
+ >
205
+ <input
206
+ id="goal-answer-input"
207
+ class="goal-answer-input"
208
+ style="min-height: 28px; padding: 4px 8px;"
209
+ type="text"
210
+ bind:value={goal.draft}
211
+ placeholder="Your answer"
212
+ aria-label="Your answer"
213
+ data-testid="answer-input"
214
+ />
215
+ <Button
216
+ variant="default"
217
+ type="submit"
218
+ data-command="goal.answer.submit"
219
+ data-testid="submit-answer"
220
+ disabled={goal.draft.trim().length === 0}
221
+ >
222
+ Submit answer
223
+ </Button>
224
+ </form>
225
+ </div>
226
+ </section>
227
+ {/if}
228
+ {/snippet}
229
+
230
+ {#if tab !== null && tab.kind !== "overview" && goal.question !== null}
231
+ <div class="goal-global-question">{@render runQuestion()}</div>
232
+ {/if}
233
+
234
+ {#if tab === null}
235
+ <div class="goal-page">
236
+ <section class="panel goal-empty-state" data-testid="no-open-page">
237
+ <div class="panel__body stack-sm">
238
+ <h1 class="goal-empty-state__title">No page is open</h1>
239
+ <p class="empty" style="padding: 0;">
240
+ Choose a surface from the sidebar, or return to the lifecycle console.
241
+ </p>
242
+ <div class="row-sm">
243
+ <Button
244
+ variant="default"
245
+ data-command="open.goal.overview"
246
+ onclick={openOverview}
247
+ >
248
+ Open implementation
249
+ </Button>
250
+ </div>
251
+ </div>
252
+ </section>
253
+ </div>
254
+ {:else if tab.kind === "overview"}
255
+ <div class="goal-page goal-page--console" data-testid="goal-overview">
256
+ {#if connection.lastError !== null || connection.upgradePending || operationText !== null || goal.runState === "stopped"}
257
+ <div class="goal-notices" data-testid="goal-notices">
258
+ {#if connection.lastError !== null}
259
+ <p class="message" data-severity="error" role="alert">{connection.lastError}</p>
260
+ {/if}
261
+ {#if goal.creationActive}
262
+ <p class="message" data-testid="goal-creation-active" data-severity="notice" role="status">
263
+ Creating the goal. The document is being written by the agent.
264
+ </p>
265
+ {/if}
266
+ {#if goal.revisionActive}
267
+ <p class="message" data-testid="goal-revision-active" data-severity="notice" role="status">
268
+ Revising the goal with your guidance.
269
+ </p>
270
+ {/if}
271
+ {#if goal.skillsPrepActive}
272
+ <p class="message" data-testid="skills-preparation-active" data-severity="notice" role="status">
273
+ Preparing skills from the goal.
274
+ </p>
275
+ {/if}
276
+ {#if goal.runState === "stopped" && !goal.lifecycleActive}
277
+ <p class="message" data-testid="run-stopped" data-severity="notice" role="status">
278
+ Run stopped. The last action is kept below.
279
+ </p>
280
+ {/if}
281
+ {#if connection.upgradePending}
282
+ <p class="message" data-testid="upgrade-pending" data-severity="notice" role="status">
283
+ An update is waiting for the current work to finish. This dashboard restarts when it
284
+ ends.
285
+ </p>
286
+ {/if}
287
+ </div>
288
+ {/if}
289
+
290
+ <PageHeader
291
+ title="Implementation"
292
+ subtitle="Drive the lifecycle, or resolve a single decision record by hand."
293
+ testid="implementation-header"
294
+ >
295
+ {#snippet meta()}
296
+ <span class="muted" data-testid="pending-summary">
297
+ {pendingCount} ADR{pendingCount === 1 ? "" : "s"} pending
298
+ </span>
299
+ {#if nextRecord !== null && pendingCount > 0}
300
+ <span class="muted">Next: <span class="mono">{nextRecord.title}</span></span>
301
+ {/if}
302
+ {/snippet}
303
+ {#snippet actions()}
304
+ {#if goal.lifecycleActive}
305
+ <Button
306
+ variant="secondary"
307
+ data-command={goal.lifecycle.paused ? "goal.lifecycle.resume" : "goal.lifecycle.pause"}
308
+ onclick={() => (goal.lifecycle.paused ? goal.resumeLifecycle() : goal.pauseLifecycle())}
309
+ >
310
+ {goal.lifecycle.paused ? "Resume" : "Pause"}
311
+ </Button>
312
+ {/if}
313
+ <Button
314
+ variant="outline"
315
+ data-command="goal.adr.start-next"
316
+ data-testid="start-next"
317
+ title={runStartTitle}
318
+ disabled={goal.runStartDisabled || goal.next === null}
319
+ onclick={() => goal.startNext()}
320
+ >
321
+ Resolve next ADR
322
+ </Button>
323
+ <Button
324
+ variant="outline"
325
+ data-command="goal.reset"
326
+ data-testid="reset-implementation"
327
+ disabled={goal.resetDisabled}
328
+ onclick={() => goal.armReset()}
329
+ >
330
+ Reset implementation
331
+ </Button>
332
+ <Button
333
+ variant={goal.lifecycleActive ? "destructive" : "default"}
334
+ data-command="goal.lifecycle.toggle"
335
+ data-testid="lifecycle-control"
336
+ title={goal.lifecycleButtonTitle}
337
+ disabled={goal.lifecycleButtonDisabled}
338
+ onclick={() => goal.toggleLifecycle()}
339
+ >
340
+ {goal.lifecycleActive ? "Stop implementation" : "Start implementation"}
341
+ </Button>
342
+ {/snippet}
343
+ </PageHeader>
344
+
345
+ {#if goal.resetting || goal.resetStatus !== null}
346
+ <p
347
+ class="message"
348
+ data-severity="warning"
349
+ data-testid={goal.resetting ? "reset-running" : "reset-status"}
350
+ role="status"
351
+ >
352
+ {goal.resetting ? "Resetting implementation…" : goal.resetStatus}
353
+ </p>
354
+ {/if}
355
+
356
+ {@render runQuestion()}
357
+
358
+ <section class="panel" data-testid="lifecycle">
359
+ <div class="panel__header">
360
+ <h2 class="panel__title">Lifecycle</h2>
361
+ </div>
362
+ <div class="panel__body">
363
+ <StageTrack
364
+ stage={goal.lifecycle.stage}
365
+ pass={goal.lifecycle.pass}
366
+ paused={goal.lifecycle.paused}
367
+ seen={goal.lifecycleSeen || goal.lifecycleActive}
368
+ workLabel={goal.running && !goal.lifecycleActive ? goal.runTitle : goal.lifecycleWorkTitle}
369
+ workUrl={goal.running && !goal.lifecycleActive ? null : goal.lifecycleWorkUrl}
370
+ manual={goal.running && !goal.lifecycleActive}
371
+ testid="lifecycle-stages"
372
+ />
373
+ </div>
374
+ </section>
375
+
376
+ {#if goal.running && !goal.lifecycleActive}
377
+ <section class="panel" data-testid="now">
378
+ <div class="panel__body row-sm">
379
+ <span class="muted">Manual run</span>
380
+ <strong>{goal.runTitle ?? "ADR"}</strong>
381
+ <span class="muted">{runActionLabel}</span>
382
+ </div>
383
+ </section>
384
+ {/if}
385
+
386
+ <section
387
+ class="panel goal-activity"
388
+ class:goal-activity--idle={goal.activity.length === 0 && !goal.activityActive}
389
+ data-testid="activity"
390
+ >
391
+ <div class="panel__header">
392
+ <h2 class="panel__title">Activity</h2>
393
+ <span class="badge" data-tone={statusTone(goal.activityActive ? "running" : "idle")}>
394
+ {goal.activityActive ? "live" : "idle"}
395
+ </span>
396
+ </div>
397
+ {#if goal.activity.length === 0 && !goal.activityActive}
398
+ <div class="panel__body" data-testid="activity-current-empty">
399
+ <p class="empty" style="padding: 0;">
400
+ The next action appears here. Start the lifecycle or resolve a record.
401
+ </p>
402
+ </div>
403
+ {:else}
404
+ <div class="panel__body term-split">
405
+ <Terminal
406
+ lines={[goal.currentAction]}
407
+ data-testid="activity-current"
408
+ label="Current activity"
409
+ />
410
+ {#if goal.previousActions.length > 0}
411
+ <details class="activity-history">
412
+ <summary data-testid="activity-history">{goal.previousActionsLabel}</summary>
413
+ <div style="height: 140px;">
414
+ <Terminal
415
+ lines={goal.previousActions}
416
+ data-testid="activity-log"
417
+ label="Activity history"
418
+ />
419
+ </div>
420
+ </details>
421
+ {/if}
422
+ </div>
423
+ {/if}
424
+ </section>
425
+
426
+ </div>
427
+ {:else if tab.kind === "adrs"}
428
+ <div class="goal-page">
429
+ <PageHeader
430
+ title="Decision records"
431
+ testid="adrs-header"
432
+ >
433
+ {#snippet meta()}
434
+ <span class="muted" data-testid="pending-summary">{progressLine}</span>
435
+ {/snippet}
436
+ </PageHeader>
437
+
438
+ {#if goal.records.length === 0}
439
+ <section class="panel" data-testid="adrs-overlay">
440
+ <div class="panel__body stack-sm goal-empty-state">
441
+ <h2 class="goal-empty-state__title">No decision records yet</h2>
442
+ <p class="empty" style="padding: 0;">
443
+ Records appear under <code>adrs/</code> when goal creation or the lifecycle writes them.
444
+ </p>
445
+ <div class="row-sm">
446
+ <Button variant="default" data-command="open.goal.overview" onclick={openOverview}>
447
+ Open implementation
448
+ </Button>
449
+ </div>
450
+ </div>
451
+ </section>
452
+ {:else}
453
+ <section class="panel goal-filters" aria-label="Record filters">
454
+ <div class="panel__body goal-filters__body">
455
+ <Input
456
+ type="search"
457
+ placeholder="Filter by title, file, or summary"
458
+ aria-label="Filter decision records"
459
+ data-testid="adr-search"
460
+ value={filterQuery}
461
+ oninput={(event) => (filterQuery = event.currentTarget.value)}
462
+ />
463
+ <div class="filter-chips" role="group" aria-label="Implementation state">
464
+ {#each filters as entry (entry.id)}
465
+ <button
466
+ type="button"
467
+ class="filter-chip"
468
+ aria-pressed={filterMode === entry.id}
469
+ data-testid={`adr-filter-${entry.id}`}
470
+ onclick={() => (filterMode = entry.id)}
471
+ >
472
+ {entry.label}
473
+ </button>
474
+ {/each}
475
+ {#if filterMode !== "all" || filterQuery.trim().length > 0}
476
+ <Button variant="ghost" size="sm" data-testid="clear-adr-filter" onclick={clearFilters}>
477
+ Clear
478
+ </Button>
479
+ {/if}
480
+ </div>
481
+ </div>
482
+ </section>
483
+
484
+ <section class="panel goal-adrs-panel" data-testid="adrs-overlay">
485
+ <AdrTable
486
+ records={filteredRecords}
487
+ onopen={openAdr}
488
+ onstart={(entry) => goal.startRecord(entry)}
489
+ />
490
+ </section>
491
+ {/if}
492
+ </div>
493
+ {:else if tab.kind === "goal-document"}
494
+ <div class="goal-page goal-page--fill">
495
+ <PageHeader title="Goal document" subtitle={documentState} testid="document-header">
496
+ {#snippet meta()}
497
+ {#if operationText !== null}
498
+ <span class="badge" data-tone="accent">{operationText}</span>
499
+ {/if}
500
+ {/snippet}
501
+ {#snippet actions()}
502
+ {#if goal.skills.length > 0}
503
+ <Button
504
+ variant="outline"
505
+ data-command="goal.skills.open"
506
+ data-testid="show-skills"
507
+ onclick={() =>
508
+ openTab({ id: "skills", kind: "skills", resourceId: "skills", title: "Skills" })}
509
+ >
510
+ Show skills ({goal.skills.length})
511
+ </Button>
512
+ {:else}
513
+ <Button
514
+ variant="outline"
515
+ data-command="goal.skills.prepare"
516
+ data-testid="prepare-skills"
517
+ disabled={goal.prepareDisabled}
518
+ onclick={() => goal.prepareSkills()}
519
+ >
520
+ Prepare skills
521
+ </Button>
522
+ {/if}
523
+ <Button
524
+ variant="secondary"
525
+ data-command="goal.create"
526
+ data-testid="create-goal"
527
+ title={createGoalTitle}
528
+ disabled={!connection.open}
529
+ onclick={() => goal.createGoal()}
530
+ >
531
+ {goal.goalReady ? "Recreate goal" : "Create goal"}
532
+ </Button>
533
+ <Button
534
+ variant="default"
535
+ data-command="goal.save"
536
+ data-testid="save-goal"
537
+ title={goal.goalEditingDisabled ? "The goal is read only during work." : undefined}
538
+ disabled={goal.goalEditingDisabled}
539
+ onclick={() => goal.saveGoal()}
540
+ >
541
+ Save goal
542
+ </Button>
543
+ {/snippet}
544
+ </PageHeader>
545
+
546
+ <section class="panel goal-document" data-testid="goal-editor">
547
+ <div class="panel__body goal-details">
548
+ <MonacoEditor
549
+ value={goal.goalDraft}
550
+ label="Current goal"
551
+ data-testid="goal-text"
552
+ readOnly={goal.goalEditingDisabled}
553
+ dirty={goal.goalDirty}
554
+ onchange={(text) => goal.setGoalDraft(text)}
555
+ onsave={() => goal.saveGoal()}
556
+ />
557
+ </div>
558
+ <div class="panel__footer goal-revision">
559
+ <div class="goal-revision__heading">
560
+ <label class="field__label" for="revision-request">Revision guidance</label>
561
+ <Button
562
+ variant="secondary"
563
+ data-command="goal.revise"
564
+ data-testid="revise-goal"
565
+ disabled={goal.revisionDisabled}
566
+ onclick={() => goal.reviseGoal()}
567
+ >
568
+ Revise goal
569
+ </Button>
570
+ </div>
571
+ <p class="goal-revision__hint">
572
+ Revision writes a draft; save only when you accept it. Guidance is optional.
573
+ </p>
574
+ <textarea
575
+ id="revision-request"
576
+ class="textarea goal-revision__input"
577
+ rows="3"
578
+ bind:value={goal.revisionRequest}
579
+ placeholder="Optional guidance for the agent"
580
+ data-testid="revision-request"
581
+ disabled={goal.revisionDisabled}
582
+ ></textarea>
583
+ </div>
584
+ </section>
585
+ </div>
586
+ {:else if tab.kind === "skills"}
587
+ <div class="goal-page goal-page--fill">
588
+ <PageHeader title="Skills" testid="skills-header">
589
+ {#snippet meta()}
590
+ {#if goal.contradictions.length > 0}
591
+ <span class="badge" data-tone="error">
592
+ {goal.contradictions.length} contradiction{goal.contradictions.length === 1 ? "" : "s"}
593
+ </span>
594
+ {/if}
595
+ {/snippet}
596
+ {#snippet actions()}
597
+ <Button
598
+ variant="secondary"
599
+ data-command="goal.skills.prepare"
600
+ data-testid="prepare-skills-overlay"
601
+ disabled={goal.prepareDisabled}
602
+ onclick={() => goal.prepareSkills()}
603
+ >
604
+ Prepare skills again
605
+ </Button>
606
+ <Button
607
+ variant="outline"
608
+ data-command={`tab.close.goal.${tab.id}`}
609
+ data-testid="close-skills"
610
+ onclick={() => closeTab(tab)}
611
+ >
612
+ Close
613
+ </Button>
614
+ {/snippet}
615
+ </PageHeader>
616
+
617
+ <section class="panel goal-skills" data-testid="skills-overlay">
618
+ <div class="panel__header">
619
+ <h2 class="panel__title">
620
+ Prepared skills <span class="mono" data-testid="skills-count">{goal.skills.length}</span>
621
+ </h2>
622
+ <span class="muted">Read by implementing agents</span>
623
+ </div>
624
+ <div class="panel__body stack-sm">
625
+ {#if goal.skills.length === 0}
626
+ <p class="empty" style="padding: 0;">
627
+ No skills prepared yet. Prepare them from the saved goal before starting implementation.
628
+ </p>
629
+ {:else}
630
+ <ul class="history-list" bind:this={skillsList}>
631
+ {#each goal.skills as skill (skill.name)}
632
+ {@const expanded = goal.selectedSkill === skill.name}
633
+ {@const panelId = skillPanelId(skill.name)}
634
+ {@const skillHtml =
635
+ skill.content === undefined ? "" : renderSkillDocument(skill.content)}
636
+ <li class="skill">
637
+ <div class="skill__row">
638
+ <button
639
+ type="button"
640
+ class="skill__button"
641
+ data-testid="skill"
642
+ data-name={skill.name}
643
+ data-selected={expanded}
644
+ aria-current={expanded ? "true" : undefined}
645
+ aria-expanded={expanded}
646
+ aria-controls={expanded ? panelId : undefined}
647
+ onclick={() => goal.selectSkill(expanded ? null : skill.name)}
648
+ >
649
+ <span class="skill__text">
650
+ <span class="mono skill__name">{skill.name}</span>
651
+ <span class="muted skill__description">{skill.description}</span>
652
+ </span>
653
+ <ChevronRight
654
+ size={14}
655
+ aria-hidden="true"
656
+ class={expanded ? "skill__chevron skill__chevron--open" : "skill__chevron"}
657
+ />
658
+ </button>
659
+ {#if (skill.family ?? "prepare") === "prepare"}
660
+ <button
661
+ type="button"
662
+ class="skill__delete"
663
+ data-command={`goal.skills.delete.${skill.name}`}
664
+ data-testid="delete-skill"
665
+ data-name={skill.name}
666
+ aria-label={`Delete ${skill.name}`}
667
+ title={`Delete ${skill.name} from .omp/skills/`}
668
+ disabled={goal.skillDeleteDisabled}
669
+ onclick={() => (deleteTarget = skill.name)}
670
+ >
671
+ <Trash2 size={14} aria-hidden="true" />
672
+ </button>
673
+ {/if}
674
+ </div>
675
+ {#if expanded}
676
+ <div
677
+ class="skill__detail"
678
+ id={panelId}
679
+ data-testid="skill-content"
680
+ data-name={skill.name}
681
+ role="region"
682
+ aria-label={`${skill.name} content`}
683
+ >
684
+ <p class="skill__path" title={`.omp/skills/${skill.name}/SKILL.md`}>
685
+ .omp/skills/{skill.name}/SKILL.md
686
+ </p>
687
+ {#if skill.content !== undefined && skill.content.length > 0}
688
+ {#if skillHtml.length > 0}
689
+ <div class="skill__document" data-testid="skill-content-text">
690
+ {@html skillHtml}
691
+ </div>
692
+ {:else}
693
+ <p class="empty" style="padding: 0;">
694
+ This skill's file carries only preparation metadata; there are no
695
+ instructions to read.
696
+ </p>
697
+ {/if}
698
+ {:else}
699
+ <p class="empty" style="padding: 0;">
700
+ This skill has no file on disk; prepare the skills again to write it.
701
+ </p>
702
+ {/if}
703
+ </div>
704
+ {/if}
705
+ </li>
706
+ {/each}
707
+ </ul>
708
+ {/if}
709
+
710
+ {#if goal.contradictions.length > 0}
711
+ <ul class="history-list goal-contradictions" data-testid="skills-contradictions">
712
+ {#each goal.contradictions as contradiction (`${contradiction.skills[0]}:${contradiction.skills[1]}`)}
713
+ <li class="message" data-severity="warning">
714
+ <strong>{contradiction.skills[0]}</strong> and
715
+ <strong>{contradiction.skills[1]}</strong> state opposite policies:
716
+ “{contradiction.directives[0]}” vs “{contradiction.directives[1]}”.
717
+ </li>
718
+ {/each}
719
+ </ul>
720
+ {/if}
721
+ </div>
722
+ </section>
723
+
724
+
725
+ </div>
726
+ {:else if tab.kind === "adr"}
727
+ {#if record === null}
728
+ <div class="goal-page">
729
+ <section class="panel">
730
+ <div class="panel__body stack-sm">
731
+ <p class="empty" style="padding: 0;">This record is no longer listed.</p>
732
+ <div class="row-sm">
733
+ <Button
734
+ variant="default"
735
+ data-command="goal.adrs.open"
736
+ onclick={() =>
737
+ openTab({ id: "adrs", kind: "adrs", resourceId: "adrs", title: "ADRs" })}
738
+ >
739
+ Back to records
740
+ </Button>
741
+ </div>
742
+ </div>
743
+ </section>
744
+ </div>
745
+ {:else}
746
+ <div class="goal-page">
747
+ <PageHeader eyebrow="Decision record" title={record.title} testid="record-header">
748
+ {#snippet meta()}
749
+ {#if goal.next === record.file}
750
+ <span class="badge" data-tone="accent" data-testid="next-marker">Next</span>
751
+ {/if}
752
+ <span
753
+ class="badge"
754
+ data-tone={recordStatusTone(record.status)}
755
+ data-testid="record-status"
756
+ >
757
+ {record.status || "Unknown"}
758
+ </span>
759
+ {/snippet}
760
+ {#snippet actions()}
761
+ <Button
762
+ variant="outline"
763
+ data-command="goal.adrs.open"
764
+ data-testid="back-to-records"
765
+ onclick={() =>
766
+ openTab({ id: "adrs", kind: "adrs", resourceId: "adrs", title: "ADRs" })}
767
+ >
768
+ Back to records
769
+ </Button>
770
+ <Button
771
+ variant="outline"
772
+ data-command={`tab.close.goal.${tab.id}`}
773
+ data-testid="close-record"
774
+ onclick={() => closeTab(tab)}
775
+ >
776
+ Close
777
+ </Button>
778
+ {/snippet}
779
+ </PageHeader>
780
+
781
+ <section class="panel" data-testid="record-meta">
782
+ <div class="panel__body">
783
+ <dl class="kv-grid">
784
+ <dt>File</dt>
785
+ <dd><code data-testid="record-file">{record.file}</code></dd>
786
+ <dt>Document status</dt>
787
+ <dd>{record.status || "Unknown"}</dd>
788
+ <dt>Implementation</dt>
789
+ <dd>{record.pending ? "Pending" : "Resolved"}</dd>
790
+ </dl>
791
+ </div>
792
+ </section>
793
+
794
+ <section class="panel">
795
+ <div class="panel__header">
796
+ <h2 class="panel__title">Decision summary</h2>
797
+ </div>
798
+ <div class="panel__body">
799
+ <p data-testid="record-summary" style="margin: 0;">
800
+ {record.summary || "No decision summary recorded."}
801
+ </p>
802
+ </div>
803
+ </section>
804
+
805
+ {#if record.pending}
806
+ <section class="panel" data-testid="record-action">
807
+ <div class="panel__body row-sm">
808
+ <Button
809
+ variant="default"
810
+ data-command={`goal.adr.start.${record.file}`}
811
+ data-testid="start"
812
+ data-file={record.file}
813
+ title={runStartTitle}
814
+ disabled={goal.runStartDisabled}
815
+ onclick={() => goal.startRecord(record)}
816
+ >
817
+ Start this record
818
+ </Button>
819
+ <span class="muted">
820
+ Runs one record outside the lifecycle. Stop the lifecycle first when it is active.
821
+ </span>
822
+ </div>
823
+ </section>
824
+ {/if}
825
+ </div>
826
+ {/if}
827
+ {:else if tab.kind === "lane"}
828
+ {#if lane === null}
829
+ <div class="goal-page">
830
+ <section class="panel">
831
+ <div class="panel__body stack-sm">
832
+ <p class="empty" style="padding: 0;">This lane is no longer active.</p>
833
+ <div class="row-sm">
834
+ <Button variant="default" data-command="open.goal.overview" onclick={openOverview}>
835
+ Back to implementation
836
+ </Button>
837
+ </div>
838
+ </div>
839
+ </section>
840
+ </div>
841
+ {:else}
842
+ {@const panel = lanePanel(lane)}
843
+ <div class="goal-page">
844
+ <PageHeader eyebrow="Lane" title={panel.label} testid="lane-header">
845
+ {#snippet meta()}
846
+ <span
847
+ class="badge"
848
+ data-testid="lane-status"
849
+ data-kind={lane.kind}
850
+ data-status={lane.status}
851
+ data-tone={statusTone(lane.status)}
852
+ >
853
+ {lane.status}
854
+ </span>
855
+ <span class="muted">Attempt <span class="mono">{lane.attempt}</span></span>
856
+ {/snippet}
857
+ {#snippet actions()}
858
+ <Button
859
+ variant="outline"
860
+ data-command={`tab.close.goal.${tab.id}`}
861
+ data-testid="close-lane"
862
+ onclick={() => closeTab(tab)}
863
+ >
864
+ Close
865
+ </Button>
866
+ {/snippet}
867
+ </PageHeader>
868
+
869
+ <section class="panel" data-testid="lane-panel" data-lane={lane.id}>
870
+ <div class="panel__header">
871
+ <p class="panel__title" data-testid="lane-identity">
872
+ {#if panel.identity.text.length > 0}
873
+ {panel.identity.text}{#if panel.identity.url !== null}{" — "}{/if}
874
+ {/if}
875
+ {#if panel.identity.url !== null}
876
+ <a
877
+ href={panel.identity.url}
878
+ target="_blank"
879
+ rel="noopener noreferrer"
880
+ data-testid="lane-identity-link">{panel.identity.url}</a>
881
+ {/if}
882
+ </p>
883
+ </div>
884
+ <div class="panel__body">
885
+ <dl class="kv-grid">
886
+ <dt>Kind</dt>
887
+ <dd>
888
+ {lane.kind === "generation"
889
+ ? "Epic generation"
890
+ : lane.kind === "adr"
891
+ ? "ADR resolution"
892
+ : "Epic implementation"}
893
+ </dd>
894
+ <dt>Record</dt>
895
+ <dd>
896
+ {#if lane.record === null}
897
+ <span class="muted">—</span>
898
+ {:else}
899
+ <code>{lane.record.file}</code>
900
+ {/if}
901
+ </dd>
902
+ <dt>Epic</dt>
903
+ <dd>
904
+ {#if lane.epic === null}
905
+ <span class="muted">—</span>
906
+ {:else}
907
+ #{lane.epic.number} {lane.epic.title}
908
+ {/if}
909
+ </dd>
910
+ </dl>
911
+ </div>
912
+ </section>
913
+
914
+ {#if lane.question !== null}
915
+ {@const pending = lane.question}
916
+ <section class="panel goal-question" data-testid="lane-question" data-lane={lane.id}>
917
+ <div class="panel__header">
918
+ <h2 class="panel__title">Waiting on you</h2>
919
+ <span class="badge" data-tone="accent">lane blocked</span>
920
+ </div>
921
+ <div class="panel__body stack-sm">
922
+ <p class="muted" style="margin: 0;">{pending.message}</p>
923
+ {#if pending.options.length > 0}
924
+ <div class="choice-list">
925
+ {#each pending.options as option (option.label)}
926
+ <button
927
+ type="button"
928
+ class="choice"
929
+ data-testid="lane-choice"
930
+ data-command={`goal.lane.answer.submit.${lane.id}`}
931
+ onclick={() => goal.submitLaneAnswer(lane.id, option.label)}
932
+ >
933
+ <span class="choice__label">
934
+ {option.label}
935
+ {#if option.recommended}
936
+ <span class="choice__recommended">Recommended</span>
937
+ {/if}
938
+ </span>
939
+ {#if option.description}
940
+ <span class="choice__description">{option.description}</span>
941
+ {/if}
942
+ </button>
943
+ {/each}
944
+ </div>
945
+ {/if}
946
+ <form
947
+ class="answer-form"
948
+ onsubmit={(event) => {
949
+ event.preventDefault();
950
+ goal.submitLaneAnswer(lane.id, goal.laneDraft);
951
+ }}
952
+ >
953
+ <input
954
+ id={`lane-answer-${lane.id}`}
955
+ class="goal-answer-input"
956
+ style="min-height: 28px; padding: 4px 8px;"
957
+ type="text"
958
+ bind:value={goal.laneDraft}
959
+ placeholder="Your answer"
960
+ aria-label="Your answer"
961
+ data-testid="lane-answer-input"
962
+ />
963
+ <Button
964
+ variant="default"
965
+ type="submit"
966
+ data-command={`goal.lane.answer.submit.${lane.id}`}
967
+ data-testid="lane-submit-answer"
968
+ disabled={goal.laneDraft.trim().length === 0}
969
+ >
970
+ Submit answer
971
+ </Button>
972
+ </form>
973
+ </div>
974
+ </section>
975
+ {/if}
976
+
977
+ <section class="panel goal-activity" data-testid="lane-activity">
978
+ <div class="panel__header">
979
+ <h2 class="panel__title">Activity</h2>
980
+ <span class="badge" data-tone={statusTone(lane.status)}>
981
+ {lane.status === "running" || lane.status === "retrying" ? "live" : lane.status}
982
+ </span>
983
+ </div>
984
+ <div class="panel__body term-split">
985
+ <Terminal
986
+ lines={panel.current.length === 0 ? ["No activity yet."] : panel.current}
987
+ data-testid="lane-current"
988
+ label="Lane current activity"
989
+ />
990
+ {#if panel.history.length > 0}
991
+ <details class="activity-history">
992
+ <summary data-testid="lane-history">
993
+ {panel.history.length === 1
994
+ ? "1 previous action"
995
+ : `${panel.history.length} previous actions`}
996
+ </summary>
997
+ <div style="height: 140px;">
998
+ <Terminal
999
+ lines={panel.history}
1000
+ data-testid="lane-history-log"
1001
+ label="Lane activity history"
1002
+ />
1003
+ </div>
1004
+ </details>
1005
+ {/if}
1006
+ </div>
1007
+ </section>
1008
+ </div>
1009
+ {/if}
1010
+ {/if}
1011
+ </div>
1012
+
1013
+ <ConfirmDialog
1014
+ open={goal.resetArmed}
1015
+ title="Reset implementation"
1016
+ message="Reset deletes every top-level entry except .devboard, .omp, AGENTS.md, .gitignore, and GOAL.md. The internal .git and .worktree directories are preserved. Any active run, lifecycle, goal creation, revision, or skills preparation stops first."
1017
+ confirmLabel="Confirm reset"
1018
+ destructive
1019
+ testid="reset-confirmation"
1020
+ confirmCommand="goal.reset.confirm"
1021
+ cancelCommand="goal.reset.cancel"
1022
+ onconfirm={() => goal.confirmReset()}
1023
+ oncancel={() => goal.cancelReset()}
1024
+ />
1025
+
1026
+ <ConfirmDialog
1027
+ open={deleteTarget !== null}
1028
+ title="Delete skill"
1029
+ message={deleteTarget === null
1030
+ ? ""
1031
+ : `Delete "${deleteTarget}" from .omp/skills/? Implementing agents will no longer read it.`}
1032
+ confirmLabel="Delete skill"
1033
+ destructive
1034
+ testid="delete-skill-confirmation"
1035
+ confirmCommand="goal.skills.delete.confirm"
1036
+ cancelCommand="goal.skills.delete.cancel"
1037
+ onconfirm={() => {
1038
+ if (deleteTarget !== null) goal.deleteSkill(deleteTarget);
1039
+ deleteTarget = null;
1040
+ }}
1041
+ oncancel={() => (deleteTarget = null)}
1042
+ />