@dsdenes/devboard 0.1.560 → 0.1.563

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 +729 -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,437 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import {
4
+ conceptTitle,
5
+ contextTitle,
6
+ labelText,
7
+ labelTexts,
8
+ relativeTime,
9
+ TABS,
10
+ } from "../discover-format";
11
+ import { discover } from "../discover.svelte";
12
+ import type { WorkspaceTab } from "../layout.svelte";
13
+ import ConceptKind from "./ConceptKind.svelte";
14
+ import DataTable from "./DataTable.svelte";
15
+ import DiscoverOverview from "./DiscoverOverview.svelte";
16
+ import MonacoEditor from "./MonacoEditor.svelte";
17
+ import Button from "./ui/button.svelte";
18
+ import Input from "./ui/input.svelte";
19
+
20
+ interface Props {
21
+ tab: WorkspaceTab | null;
22
+ }
23
+
24
+ let { tab }: Props = $props();
25
+
26
+ const GLOSSARY_COLUMNS = [
27
+ { id: "kind", header: "Kind", accessorKey: "kind" },
28
+ { id: "title", header: "Term", accessorKey: "title" },
29
+ { id: "significance", header: "Significance", accessorKey: "significance" },
30
+ { id: "definition", header: "Definition", accessorKey: "definition" },
31
+ { id: "examples", header: "", accessorKey: "examples" },
32
+ ];
33
+ const GLOSSARY_WIDTHS = {
34
+ kind: "92px",
35
+ title: "210px",
36
+ significance: "104px",
37
+ examples: "240px",
38
+ };
39
+
40
+ const activeSkill = $derived(tab?.kind === "skill" ? tab.resourceId : null);
41
+ const skill = $derived(activeSkill === null ? null : discover.skill(activeSkill));
42
+ const skillDraft = $derived(activeSkill === null ? "" : discover.skillDraft(activeSkill));
43
+ const skillTab = $derived(
44
+ activeSkill === null ? null : (TABS.find((entry) => entry.skill === activeSkill) ?? null),
45
+ );
46
+ const skillState = $derived(skill?.state ?? "missing");
47
+ const skillDirty = $derived(activeSkill === null ? false : discover.skillDirty(activeSkill));
48
+
49
+ const totalTerms = $derived(
50
+ discover.contexts.reduce(
51
+ (total, context) =>
52
+ total + context.concepts.filter((concept) => concept.status !== "invalidated").length,
53
+ 0,
54
+ ),
55
+ );
56
+ const visibleTerms = $derived(
57
+ discover.filteredContexts.reduce((total, context) => total + context.concepts.length, 0),
58
+ );
59
+
60
+ function clearGlossaryFilters(): void {
61
+ discover.resetGlossaryFilters();
62
+ }
63
+
64
+ let now = $state(Date.now());
65
+ const startedLabel = $derived(
66
+ discover.startedAt === null ? "" : relativeTime(discover.startedAt, now),
67
+ );
68
+ const lastActivity = $derived(
69
+ [...discover.activity].reverse().find((line) => line.trim().length > 0)?.trim() ?? "",
70
+ );
71
+
72
+ onMount(() => {
73
+ const timer = setInterval(() => (now = Date.now()), 15_000);
74
+ return () => clearInterval(timer);
75
+ });
76
+ </script>
77
+
78
+ {#snippet runTag()}
79
+ {#if discover.active}
80
+ <div class="discover-run-tag" data-testid="discover-run-tag" role="status" aria-live="polite">
81
+ <span class="discover-run__dot" aria-hidden="true"></span>
82
+ <strong>Discover run in progress</strong>
83
+ {#if discover.startedAt !== null}
84
+ <span class="muted" title={discover.startedAt}>started {startedLabel}</span>
85
+ {/if}
86
+ {#if discover.runId !== null}
87
+ <code class="code-chip">{discover.runId}</code>
88
+ {/if}
89
+ {#if lastActivity.length > 0}
90
+ <span class="discover-run-tag__activity" title={lastActivity}>{lastActivity}</span>
91
+ {/if}
92
+ <button
93
+ type="button"
94
+ class="discover-run__stop"
95
+ title="Stop this Discover run"
96
+ data-command="discover.stop"
97
+ data-testid="discover-run-tag-stop"
98
+ onclick={() => discover.stop()}
99
+ >
100
+ Stop
101
+ </button>
102
+ </div>
103
+ {/if}
104
+ {/snippet}
105
+
106
+ {#snippet glossaryCell(row: Record<string, unknown>, columnId: string)}
107
+ {#if columnId === "kind"}
108
+ <ConceptKind kind={String(row.kind ?? "")} />
109
+ {:else if columnId === "title"}
110
+ <strong>{String(row.title ?? "")}</strong>
111
+ {:else if columnId === "significance"}
112
+ {#if typeof row.significance === "number"}
113
+ <span title={String(row.significanceReason ?? "")}>{row.significance}</span>
114
+ {:else}
115
+ <span class="muted">—</span>
116
+ {/if}
117
+ {:else if columnId === "examples"}
118
+ {#if Array.isArray(row.examples) && row.examples.length > 0}
119
+ {@const examples = row.examples as string[]}
120
+ <span class="discover-examples">
121
+ {#each examples as example, index (example)}
122
+ <span class="discover-examples__item">
123
+ <code class="code-chip">{example}</code>
124
+ {#if index < examples.length - 1}<span aria-hidden="true">,</span>{/if}
125
+ </span>
126
+ {/each}
127
+ </span>
128
+ {:else}
129
+ <span class="muted">—</span>
130
+ {/if}
131
+ {:else}
132
+ {String(row[columnId] ?? "")}
133
+ {/if}
134
+ {/snippet}
135
+
136
+ {#snippet notices()}
137
+ {#if discover.displayError !== null || discover.interrupted || discover.transition !== null}
138
+ <div class="discover-notices">
139
+ {#if discover.displayError !== null}
140
+ <p class="message" data-severity="error" data-testid="discover-error" role="alert">
141
+ {discover.displayError}
142
+ </p>
143
+ {/if}
144
+ {#if discover.interrupted}
145
+ <p class="message" data-severity="warning" data-testid="discover-interrupted">
146
+ The last Discover run was interrupted. Nothing was published; the previous artifacts are
147
+ unchanged.
148
+ </p>
149
+ {/if}
150
+ {#if discover.transition !== null && !discover.active}
151
+ <p class="message" data-severity="notice" data-testid="discover-transition" role="status">
152
+ {discover.transition}
153
+ </p>
154
+ {/if}
155
+ </div>
156
+ {/if}
157
+ {/snippet}
158
+
159
+ {#snippet glossaryPanel()}
160
+ <div
161
+ class="scroll-column discover-page"
162
+ data-testid="discover-panel"
163
+ data-tab="glossary"
164
+ aria-label="Discover glossary"
165
+ >
166
+ <header class="discover-page__header">
167
+ <div class="discover-page__heading">
168
+ <h1 class="discover-page__title">Glossary</h1>
169
+ <p class="discover-page__subtitle">
170
+ {visibleTerms} of {totalTerms} terms
171
+ {#if discover.glossaryHasScores && discover.glossarySignificantOnly}
172
+ at or above a significance floor of {discover.glossaryThreshold}
173
+ {:else if discover.glossaryHasScores}
174
+ with no significance floor applied
175
+ {:else}
176
+ with no significance scores published
177
+ {/if}
178
+ across {discover.contexts.length}
179
+ {discover.contexts.length === 1 ? "bounded context" : "bounded contexts"}.
180
+ </p>
181
+ </div>
182
+ <div class="discover-page__actions discover-glossary-controls">
183
+ <Input
184
+ id="discover-glossary-search"
185
+ class="discover-search"
186
+ type="search"
187
+ value={discover.glossarySearch}
188
+ oninput={(event) => discover.setGlossarySearch(event.currentTarget.value)}
189
+ placeholder="Search terms"
190
+ aria-label="Search glossary terms"
191
+ data-testid="discover-glossary-search"
192
+ />
193
+ <div class="discover-glossary-filters">
194
+ <label class="discover-toggle">
195
+ <input
196
+ type="checkbox"
197
+ checked={discover.glossarySignificantOnly}
198
+ disabled={!discover.glossaryHasScores}
199
+ aria-label="Significant only"
200
+ data-testid="discover-glossary-significant"
201
+ onchange={(event) => discover.setGlossarySignificantOnly(event.currentTarget.checked)}
202
+ />
203
+ <span class="discover-toggle__track" aria-hidden="true"></span>
204
+ <span class="discover-toggle__label">Significant only</span>
205
+ </label>
206
+ <span class="discover-glossary-divider" aria-hidden="true"></span>
207
+ <label class="discover-floor">
208
+ <span class="discover-floor__label">Floor</span>
209
+ <input
210
+ class="discover-floor__input"
211
+ type="range"
212
+ min="0"
213
+ max="100"
214
+ step="1"
215
+ value={discover.glossaryThreshold}
216
+ disabled={!discover.glossaryHasScores || !discover.glossarySignificantOnly}
217
+ aria-label="Significance floor a term needs to be listed"
218
+ data-testid="discover-glossary-threshold"
219
+ oninput={(event) => discover.setGlossaryThreshold(event.currentTarget.valueAsNumber)}
220
+ />
221
+ <output class="discover-floor__value">≥ {discover.glossaryThreshold}</output>
222
+ </label>
223
+ </div>
224
+ </div>
225
+ </header>
226
+
227
+ {#if discover.filteredContexts.length === 0}
228
+ <section class="panel discover-filter-empty" data-testid="discover-glossary-empty">
229
+ <div class="panel__body discover-filter-empty__body">
230
+ <p class="empty">
231
+ No term matches the current search and significance filter. The glossary is published;
232
+ only this view is filtered.
233
+ </p>
234
+ <Button variant="outline" onclick={clearGlossaryFilters}>Clear filters</Button>
235
+ </div>
236
+ </section>
237
+ {:else}
238
+ {#each discover.filteredContexts as context (context.boundedContext)}
239
+ <section
240
+ class="panel discover-card"
241
+ data-testid="discover-context"
242
+ data-context={context.boundedContext}
243
+ >
244
+ <div class="panel__header">
245
+ <h2 class="panel__title">{contextTitle(context)}</h2>
246
+ <span class="panel__subtitle">
247
+ {context.concepts.length} {context.concepts.length === 1 ? "term" : "terms"}
248
+ </span>
249
+ </div>
250
+ <div class="panel__body panel__body--flush">
251
+ <DataTable
252
+ columns={GLOSSARY_COLUMNS}
253
+ rows={context.concepts.map((concept) => {
254
+ const usage = discover.usageFor(context.boundedContext, concept.id);
255
+ const score = discover.significanceFor(context.boundedContext, concept.id);
256
+ return {
257
+ ...concept,
258
+ title: conceptTitle(concept),
259
+ definition: labelText(concept.definitions) || "No definition recorded.",
260
+ significance: score?.score ?? null,
261
+ significanceReason: score?.reason ?? "",
262
+ examples: usage.examples,
263
+ };
264
+ })}
265
+ rowTestId="discover-term"
266
+ rowAttrs={(row) => ({
267
+ "data-context": context.boundedContext,
268
+ "data-id": String(row.id ?? ""),
269
+ "data-status": String(row.status ?? ""),
270
+ "data-kind": String(row.kind ?? ""),
271
+ "data-significance":
272
+ typeof row.significance === "number" ? String(row.significance) : "",
273
+ "data-examples": Array.isArray(row.examples) ? row.examples.join(",") : "",
274
+ })}
275
+ cell={glossaryCell}
276
+ columnWidths={GLOSSARY_WIDTHS}
277
+ full
278
+ wrap
279
+ />
280
+ </div>
281
+ </section>
282
+ {/each}
283
+ {/if}
284
+ </div>
285
+ {/snippet}
286
+
287
+ {#snippet skillPanel()}
288
+ {#if activeSkill === null || skillTab === null}
289
+ <p class="empty">No skill selected.</p>
290
+ {:else}
291
+ <div
292
+ class="discover-skill-page"
293
+ data-testid="discover-panel"
294
+ data-tab={skillTab.id}
295
+ aria-label={skillTab.label}
296
+ >
297
+ <header class="discover-page__header discover-page__header--compact">
298
+ <div class="discover-page__heading">
299
+ <h1 class="discover-page__title">{skillTab.label}</h1>
300
+ </div>
301
+ </header>
302
+
303
+ <div class="discover-skill" data-testid="discover-skill" data-name={activeSkill}>
304
+ <div class="panel discover-skill__panel">
305
+ <div class="panel__header discover-skill__header">
306
+ <div class="discover-skill__identity">
307
+ <h2
308
+ class="panel__title mono discover-skill__title"
309
+ title={`${activeSkill}/SKILL.md`}
310
+ >
311
+ <span>{activeSkill}</span><span class="discover-skill__file">/SKILL.md</span>
312
+ </h2>
313
+ {#if skillDirty}
314
+ <span
315
+ class="discover-skill__state"
316
+ data-state="dirty"
317
+ data-testid="discover-skill-dirty"
318
+ >
319
+ Unsaved changes
320
+ </span>
321
+ {:else if skillState === "missing"}
322
+ <span
323
+ class="discover-skill__state"
324
+ data-state="missing"
325
+ data-testid="discover-skill-status"
326
+ >
327
+ Live file missing
328
+ </span>
329
+ {:else}
330
+ <span
331
+ class="discover-skill__state"
332
+ data-state="clean"
333
+ data-testid="discover-skill-status"
334
+ >
335
+ No unsaved changes
336
+ </span>
337
+ {/if}
338
+ </div>
339
+ <div class="row-sm discover-skill__actions">
340
+ <Button
341
+ variant="default"
342
+ data-command={`discover.skill.save.${activeSkill}`}
343
+ data-testid="discover-skill-save"
344
+ disabled={!skillDirty || skillState === "missing"}
345
+ title={skillState === "missing"
346
+ ? "Recreate the generated version before saving."
347
+ : skillDirty
348
+ ? "Save the skill file."
349
+ : "No unsaved changes."}
350
+ onclick={() => discover.saveSkill(activeSkill)}
351
+ >
352
+ Save
353
+ </Button>
354
+ {#if skillState !== "generated"}
355
+ <Button
356
+ variant="outline"
357
+ data-command={`discover.skill.restore.${activeSkill}`}
358
+ data-testid="discover-skill-restore"
359
+ title={skillState === "missing"
360
+ ? "Recreate the skill from its generated version."
361
+ : "Discard your edits and return to the generated version."}
362
+ onclick={() => discover.restoreSkill(activeSkill)}
363
+ >
364
+ {skillState === "missing" ? "Recreate generated version" : "Discard edits"}
365
+ </Button>
366
+ {/if}
367
+ </div>
368
+ </div>
369
+ <div class="grow">
370
+ <MonacoEditor
371
+ value={skillDraft}
372
+ label={`${activeSkill} skill text`}
373
+ data-testid="discover-skill-text"
374
+ dirty={skillDirty}
375
+ onchange={(text) => discover.updateDraft(activeSkill, text)}
376
+ onsave={() => discover.saveSkill(activeSkill)}
377
+ />
378
+ </div>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ {/if}
383
+ {/snippet}
384
+
385
+ {#snippet warningsPanel()}
386
+ <div
387
+ class="scroll-column discover-page"
388
+ data-testid="discover-panel"
389
+ data-tab="warnings"
390
+ aria-label="Discover warnings"
391
+ >
392
+ <header class="discover-page__header">
393
+ <div class="discover-page__heading">
394
+ <h1 class="discover-page__title">Warnings</h1>
395
+ <p class="discover-page__subtitle">
396
+ {discover.displayedWarnings.length}
397
+ {discover.displayedWarnings.length === 1 ? "warning" : "warnings"} recorded while
398
+ validating the published artifacts.
399
+ </p>
400
+ </div>
401
+ </header>
402
+
403
+ <section class="panel">
404
+ <div class="panel__header">
405
+ <h2 class="panel__title">Artifact warnings</h2>
406
+ </div>
407
+ <div class="panel__body panel__body--flush">
408
+ {#if discover.displayedWarnings.length === 0}
409
+ <p class="empty">No warnings. The published artifacts validated cleanly.</p>
410
+ {:else}
411
+ <ol class="discover-warning-list">
412
+ {#each discover.displayedWarnings as warning, index (index)}
413
+ <li class="discover-warning" data-testid="discover-warning">{warning}</li>
414
+ {/each}
415
+ </ol>
416
+ {/if}
417
+ </div>
418
+ </section>
419
+ </div>
420
+ {/snippet}
421
+
422
+ <div class="mode-body discover-body">
423
+ {@render runTag()}
424
+ {@render notices()}
425
+
426
+ {#if tab === null}
427
+ <p class="empty">No page is open. Choose Overview or Glossary from the sidebar.</p>
428
+ {:else if tab.kind === "overview"}
429
+ <DiscoverOverview />
430
+ {:else if tab.kind === "glossary"}
431
+ {@render glossaryPanel()}
432
+ {:else if tab.kind === "skill"}
433
+ {@render skillPanel()}
434
+ {:else if tab.kind === "warnings"}
435
+ {@render warningsPanel()}
436
+ {/if}
437
+ </div>
@@ -0,0 +1,186 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import { draftProposal } from "../draft-format";
4
+ import { issues, markDraftsPage } from "../issues.svelte";
5
+ import { statusTone } from "../status-tone";
6
+ import Button from "./ui/button.svelte";
7
+ import IssuesAgentLog from "./IssuesAgentLog.svelte";
8
+ import IssuesQuestion from "./IssuesQuestion.svelte";
9
+
10
+ /**
11
+ * The Draft goals surface. It mounts only while the Draft goals tab is active, so its
12
+ * refresh loop stops with the page. Opening the page restores the stored
13
+ * draft path; a path whose file vanished resolves to the list.
14
+ */
15
+ const proposal = $derived(draftProposal(issues.draft?.text ?? ""));
16
+
17
+ // The header reports this draft's run only; another draft's run is not this draft's state.
18
+ const run = $derived(
19
+ issues.draft !== null && issues.draftRun?.path === issues.draft.path ? issues.draftRun : null,
20
+ );
21
+ const status = $derived(run?.status ?? "idle");
22
+ const running = $derived(run?.status === "running");
23
+ const question = $derived(run?.question ?? null);
24
+ const error = $derived(issues.draftError ?? run?.error ?? null);
25
+
26
+ onMount(() => {
27
+ markDraftsPage(true);
28
+ const restore = (): void => {
29
+ const saved = issues.draftPath;
30
+ if (saved !== null && issues.draft === null) issues.openDraft(saved);
31
+ };
32
+ issues.refreshDrafts();
33
+ restore();
34
+ const timer = setInterval(() => {
35
+ issues.refreshDrafts();
36
+ restore();
37
+ }, 2000);
38
+ return () => {
39
+ clearInterval(timer);
40
+ markDraftsPage(false);
41
+ };
42
+ });
43
+ </script>
44
+
45
+ <section class="issues-drafts-page" data-testid="issues-drafts" aria-label="Goal drafts">
46
+ <header class="issues-overview__header">
47
+ <div>
48
+ <h1 class="issues-overview__title">Draft goals</h1>
49
+ <p class="issues-overview__repo mono">
50
+ {issues.draftStatus} · {issues.drafts.length} draft(s)
51
+ </p>
52
+ </div>
53
+ </header>
54
+ <div class="issues-drafts__body">
55
+ <section class="panel">
56
+ <div class="panel__header">
57
+ <h2 class="panel__title">Proposed goals</h2>
58
+ <span class="panel__subtitle">{issues.repoLabel}</span>
59
+ </div>
60
+ <div class="panel__body panel__body--flush">
61
+ <ol class="issues-drafts" data-testid="issues-draft-list">
62
+ {#each issues.drafts as draft (draft.path)}
63
+ {@const blocker = issues.draftStartBlocker(draft.path)}
64
+ <li>
65
+ <button
66
+ type="button"
67
+ class="issues-draft"
68
+ data-testid="issues-draft"
69
+ data-path={draft.path}
70
+ data-command="issues.draft.open"
71
+ aria-current={issues.draftPath === draft.path}
72
+ onclick={() => issues.openDraft(draft.path)}
73
+ >
74
+ <span class="mono">{draft.name}</span>
75
+ <span class="issues-draft__heading">{draft.heading || "No heading"}</span>
76
+ {#if blocker !== null}
77
+ <span class="issues-draft__blocked" data-testid="issues-draft-blocked">{blocker}</span>
78
+ {/if}
79
+ </button>
80
+ </li>
81
+ {:else}
82
+ <li class="issues-drafts__empty muted" data-testid="issues-drafts-empty">
83
+ No drafts exist.
84
+ </li>
85
+ {/each}
86
+ </ol>
87
+ </div>
88
+ </section>
89
+
90
+ {#if issues.draft !== null}
91
+ <section
92
+ class="panel issues-draft-detail"
93
+ data-testid="issues-draft-detail"
94
+ data-path={issues.draft.path}
95
+ >
96
+ <div class="issues-draft-detail__header">
97
+ <div class="issues-draft-detail__identity">
98
+ <span class="issues-draft-detail__path mono" title={issues.draft.path}>
99
+ {issues.draft.name}
100
+ </span>
101
+ </div>
102
+ <div class="issues-draft-detail__actions">
103
+ {#if status !== "idle"}
104
+ <span
105
+ class="badge"
106
+ data-tone={statusTone(status)}
107
+ data-testid="issues-draft-state"
108
+ >
109
+ {status}
110
+ </span>
111
+ {/if}
112
+ <Button
113
+ variant="default"
114
+ data-testid="issues-draft-start"
115
+ data-command="issues.draft.start"
116
+ disabled={issues.draftRunning}
117
+ title={issues.draftStartBlocker(issues.draft.path) ?? "Start the elaboration agent"}
118
+ onclick={() => issues.startDraft()}
119
+ >
120
+ Start elaboration
121
+ </Button>
122
+ {#if running}
123
+ <Button
124
+ variant="ghost"
125
+ class="issues-draft-detail__stop"
126
+ data-testid="issues-draft-stop"
127
+ data-command="issues.draft.stop"
128
+ onclick={() => issues.stopDraft()}
129
+ >
130
+ Stop
131
+ </Button>
132
+ {/if}
133
+ </div>
134
+ </div>
135
+ <div class="issues-draft-detail__body">
136
+ {#if error !== null}
137
+ <p class="issues-error" data-testid="issues-draft-error" role="alert">{error}</p>
138
+ {/if}
139
+ {#if question !== null}
140
+ <section
141
+ class="issues-draft-detail__question"
142
+ aria-label="Elaboration question"
143
+ >
144
+ <h3 class="issues-draft-detail__label issues-draft-detail__label--warning">
145
+ Elaboration question
146
+ </h3>
147
+ <IssuesQuestion
148
+ question={question}
149
+ testid="issues-draft-question"
150
+ onanswer={(value) => issues.submitDraftAnswer(value)}
151
+ choiceTestid="issues-draft-choice"
152
+ inputTestid="issues-draft-answer-input"
153
+ submitTestid="issues-draft-answer-submit"
154
+ command="issues.draft.answer"
155
+ />
156
+ </section>
157
+ {/if}
158
+ <section class="issues-draft-detail__section" aria-label="Draft">
159
+ {#if proposal.length > 0}
160
+ <pre class="issues-draft-detail__prose mono">{proposal}</pre>
161
+ {:else}
162
+ <p class="issues-draft-detail__empty">This draft holds no proposal text yet.</p>
163
+ {/if}
164
+ </section>
165
+ {#if run !== null}
166
+ <section
167
+ class="issues-draft-detail__section issues-draft-detail__section--activity"
168
+ aria-label="Elaboration activity"
169
+ >
170
+ <h3 class="issues-draft-detail__label">Elaboration activity</h3>
171
+ <IssuesAgentLog lines={run.activity} testid="issues-draft-activity" />
172
+ </section>
173
+ {/if}
174
+ </div>
175
+ </section>
176
+ {:else}
177
+ <section class="panel">
178
+ <div class="panel__body">
179
+ <p class="muted" data-testid="issues-drafts-selection">
180
+ Select a draft to read it and elaborate it.
181
+ </p>
182
+ </div>
183
+ </section>
184
+ {/if}
185
+ </div>
186
+ </section>