@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,836 @@
1
+ import { TABS } from "./discover-format";
2
+ import type { GoalStore } from "./goal.svelte";
3
+ import type { FlowStore } from "./flow.svelte";
4
+ import type { DiscoverStore } from "./discover.svelte";
5
+ import type { IssuesStore } from "./issues.svelte";
6
+ import type { LayoutStore, WorkspaceTab } from "./layout.svelte";
7
+ import type { ConnectionStore } from "./connection.svelte";
8
+ import type { NotificationStore } from "./notifications.svelte";
9
+ import type { Mode } from "./types";
10
+
11
+ export interface CommandDef {
12
+ id: string;
13
+ title: string;
14
+ group: string;
15
+ keywords?: string;
16
+ shortcut?: string;
17
+ disabled?: () => string | null;
18
+ run: () => void | Promise<void>;
19
+ }
20
+
21
+ export interface CommandContext {
22
+ goal: GoalStore;
23
+ flow: FlowStore;
24
+ discover: DiscoverStore;
25
+ issues: IssuesStore;
26
+ layout: LayoutStore;
27
+ connection: ConnectionStore;
28
+ notifications: NotificationStore;
29
+ openTab: (mode: Mode, tab: WorkspaceTab) => void;
30
+ closeTab: (mode: Mode, id: string) => void;
31
+ focusLater: (mode: Mode, selector: string) => void;
32
+ resetLayout: () => void;
33
+ reportIssue: () => void;
34
+ removeElement: () => void;
35
+ redesignElement: () => void;
36
+ cancelReport: () => void;
37
+ canCloseTab: () => boolean;
38
+ confirmCloseTab: () => void;
39
+ cancelCloseTab: () => void;
40
+ }
41
+
42
+ function disabledWhen(condition: boolean, reason: string): string | null {
43
+ return condition ? reason : null;
44
+ }
45
+
46
+ function tabId(kind: string, resourceId: string): string {
47
+ return `${kind}:${resourceId}`;
48
+ }
49
+
50
+ /** Every palette entry; buttons use the same ids through `data-command`. */
51
+ export function buildCommands(ctx: CommandContext): CommandDef[] {
52
+ const { goal, flow, discover, issues, layout, connection, notifications } = ctx;
53
+ const commands: CommandDef[] = [];
54
+
55
+ const add = (command: CommandDef): void => {
56
+ commands.push(command);
57
+ };
58
+
59
+ // Navigation and shell.
60
+ add({
61
+ id: "view.mode.goal",
62
+ title: "Switch to Goal",
63
+ group: "View",
64
+ shortcut: "Alt+1",
65
+ run: () => layout.setMode("goal"),
66
+ });
67
+ add({
68
+ id: "view.mode.issues",
69
+ title: "Switch to Issues",
70
+ group: "View",
71
+ shortcut: "Alt+2",
72
+ run: () => layout.setMode("issues"),
73
+ });
74
+ add({
75
+ id: "view.mode.flow",
76
+ title: "Switch to Flow",
77
+ group: "View",
78
+ shortcut: "Alt+3",
79
+ run: () => layout.setMode("flow"),
80
+ });
81
+ add({
82
+ id: "view.mode.discover",
83
+ title: "Switch to Discover",
84
+ group: "View",
85
+ shortcut: "Alt+4",
86
+ run: () => layout.setMode("discover"),
87
+ });
88
+ add({
89
+ id: "view.theme.toggle",
90
+ title: layout.theme === "dark" ? "Switch to light theme" : "Switch to dark theme",
91
+ group: "View",
92
+ run: () => layout.toggleTheme(),
93
+ });
94
+ add({
95
+ id: "view.layout.reset",
96
+ title: "Reset layout",
97
+ group: "View",
98
+ keywords: "restore defaults page splits theme",
99
+ run: () => ctx.resetLayout(),
100
+ });
101
+ add({
102
+ id: "app.reload",
103
+ title: "Reload dashboard",
104
+ group: "Application",
105
+ disabled: () => disabledWhen(connection.version?.stale !== true, "No update is installed."),
106
+ run: () => connection.requestRestart(),
107
+ });
108
+ add({
109
+ id: "app.notifications.open",
110
+ title: "Show notification history",
111
+ group: "Application",
112
+ run: () => notifications.openHistory(),
113
+ });
114
+ add({
115
+ id: "app.report",
116
+ title: "Report a UI issue",
117
+ group: "Application",
118
+ keywords: "element selection feedback panel",
119
+ disabled: () =>
120
+ disabledWhen(
121
+ !connection.feedbackEnabled,
122
+ "Harness reporting is disabled; set DEVBOARD_GITEA_URL.",
123
+ ),
124
+ run: () => ctx.reportIssue(),
125
+ });
126
+ add({
127
+ id: "app.remove",
128
+ title: "Remove a UI element",
129
+ group: "Application",
130
+ keywords: "delete element issue one click",
131
+ disabled: () =>
132
+ disabledWhen(
133
+ !connection.feedbackEnabled,
134
+ "Harness reporting is disabled; set DEVBOARD_GITEA_URL.",
135
+ ),
136
+ run: () => ctx.removeElement(),
137
+ });
138
+ add({
139
+ id: "app.redesign",
140
+ title: "Redesign a UI element",
141
+ group: "Application",
142
+ keywords: "restyle element issue design system one click",
143
+ disabled: () =>
144
+ disabledWhen(
145
+ !connection.feedbackEnabled,
146
+ "Harness reporting is disabled; set DEVBOARD_GITEA_URL.",
147
+ ),
148
+ run: () => ctx.redesignElement(),
149
+ });
150
+ add({
151
+ id: "app.report.cancel",
152
+ title: "Cancel the UI issue action",
153
+ group: "Application",
154
+ run: () => ctx.cancelReport(),
155
+ });
156
+ add({
157
+ id: "app.notifications.dismiss",
158
+ title: "Dismiss the newest notification",
159
+ group: "Application",
160
+ disabled: () => disabledWhen(notifications.items.length === 0, "No notifications are waiting."),
161
+ run: () => {
162
+ const newest = notifications.items[0];
163
+ if (newest !== undefined) notifications.dismiss(newest.id);
164
+ },
165
+ });
166
+ add({
167
+ id: "app.notifications.clear",
168
+ title: "Clear the notification history",
169
+ group: "Application",
170
+ disabled: () => disabledWhen(notifications.items.length === 0, "No notifications are waiting."),
171
+ run: () => notifications.clear(),
172
+ });
173
+ add({
174
+ id: "tab.close.confirm",
175
+ title: "Discard unsaved changes and close the tab",
176
+ group: "Tabs",
177
+ disabled: () => disabledWhen(!ctx.canCloseTab(), "No dirty tab is waiting to close."),
178
+ run: () => ctx.confirmCloseTab(),
179
+ });
180
+ add({
181
+ id: "tab.close.cancel",
182
+ title: "Keep the dirty tab open",
183
+ group: "Tabs",
184
+ disabled: () => disabledWhen(!ctx.canCloseTab(), "No dirty tab is waiting to close."),
185
+ run: () => ctx.cancelCloseTab(),
186
+ });
187
+ add({
188
+ id: "app.palette.open",
189
+ title: "Show all commands",
190
+ group: "Application",
191
+ shortcut: "Ctrl+K",
192
+ run: () => {},
193
+ });
194
+
195
+ // Open workspace tabs, so the palette can reach every surface.
196
+ const tabCommands: Array<[Mode, WorkspaceTab]> = [
197
+ ["goal", { id: "overview", kind: "overview", resourceId: "overview", title: "Implementation" }],
198
+ ["goal", { id: "adrs", kind: "adrs", resourceId: "adrs", title: "ADRs" }],
199
+ ["goal", { id: "goal-document", kind: "goal-document", resourceId: "goal-document", title: "Goal document" }],
200
+ ["goal", { id: "skills", kind: "skills", resourceId: "skills", title: "Skills" }],
201
+ ["issues", { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" }],
202
+ ["issues", { id: "agents", kind: "agents", resourceId: "agents", title: "Agents" }],
203
+ ["issues", { id: "scout", kind: "scout", resourceId: "scout", title: "Auto Scout" }],
204
+ ["issues", { id: "watchdog", kind: "watchdog", resourceId: "watchdog", title: "Watchdog" }],
205
+ ["issues", { id: "issues", kind: "queue", resourceId: "issues", title: "Issues" }],
206
+ ["issues", { id: "runs", kind: "runs", resourceId: "runs", title: "Runs" }],
207
+ ["issues", { id: "drafts", kind: "drafts", resourceId: "drafts", title: "Draft goals" }],
208
+ ["flow", { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" }],
209
+ ["flow", { id: "decisions", kind: "decisions", resourceId: "decisions", title: "Decision board" }],
210
+ ["flow", { id: "steering", kind: "steering", resourceId: "steering", title: "Steering" }],
211
+ ["flow", { id: "history", kind: "history", resourceId: "history", title: "History" }],
212
+ ["discover", { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" }],
213
+ ["discover", { id: "glossary", kind: "glossary", resourceId: "glossary", title: "Glossary" }],
214
+ ["discover", { id: "warnings", kind: "warnings", resourceId: "warnings", title: "Warnings" }],
215
+ ];
216
+ tabCommands.forEach(([mode, tab]) => {
217
+ add({
218
+ id: `open.${mode}.${tab.id}`,
219
+ title: `Open ${tab.title}`,
220
+ group: "Workspace",
221
+ keywords: `tab ${mode}`,
222
+ run: () => {
223
+ layout.setMode(mode);
224
+ ctx.openTab(mode, tab);
225
+ },
226
+ });
227
+ });
228
+
229
+ // Close commands mirror the close controls a surface renders itself.
230
+ for (const mode of ["goal", "issues", "flow", "discover"] as Mode[]) {
231
+ for (const tab of layout.tabs[mode]) {
232
+ add({
233
+ id: `tab.close.${mode}.${tab.id}`,
234
+ title: `Close ${tab.title}`,
235
+ group: "Workspace",
236
+ keywords: `surface ${mode}`,
237
+ run: () => ctx.closeTab(mode, tab.id),
238
+ });
239
+ }
240
+ }
241
+
242
+ // Issues mode.
243
+ add({
244
+ id: "issues.start",
245
+ title: "Start issue automation",
246
+ group: "Issues",
247
+ keywords: "worker begin poll implement every issue",
248
+ disabled: () => disabledWhen(!issues.canStart, "Start is unavailable while the worker is enabled."),
249
+ run: () => issues.start(),
250
+ });
251
+ add({
252
+ id: "issues.pause",
253
+ title: "Pause issue automation",
254
+ group: "Issues",
255
+ keywords: "drain active run stop polling",
256
+ disabled: () => disabledWhen(!issues.canPause, "Pause is unavailable."),
257
+ run: () => issues.pause(),
258
+ });
259
+ add({
260
+ id: "issues.resume",
261
+ title: "Resume issue automation",
262
+ group: "Issues",
263
+ keywords: "continue poll",
264
+ disabled: () => disabledWhen(!issues.canResume, "Resume is unavailable."),
265
+ run: () => issues.resume(),
266
+ });
267
+ add({
268
+ id: "issues.stop",
269
+ title: "Stop issue automation",
270
+ group: "Issues",
271
+ keywords: "kill peer release lock",
272
+ disabled: () => disabledWhen(!issues.canStop, "Stop is unavailable."),
273
+ run: () => issues.stop(),
274
+ });
275
+ add({
276
+ id: "issues.check",
277
+ title: "Check issues now",
278
+ group: "Issues",
279
+ keywords: "poll immediately bypass cooldown",
280
+ disabled: () => disabledWhen(!issues.canCheck, "Check now is unavailable while paused or stopped."),
281
+ run: () => issues.check(),
282
+ });
283
+ add({
284
+ id: "issues.run.open",
285
+ title: "Open the selected issues run",
286
+ group: "Issues",
287
+ disabled: () => disabledWhen(issues.selectedRun === null, "No run is selected."),
288
+ run: () => {
289
+ if (issues.selectedRun !== null) issues.openRun(issues.selectedRun);
290
+ },
291
+ });
292
+
293
+ // Goal mode.
294
+ add({
295
+ id: "goal.adrs.open",
296
+ title: "Open ADRs",
297
+ group: "Goal",
298
+ run: () => ctx.openTab("goal", { id: "adrs", kind: "adrs", resourceId: "adrs", title: "ADRs" }),
299
+ });
300
+ add({
301
+ id: "goal.document.open",
302
+ title: "Open the goal document",
303
+ group: "Goal",
304
+ run: () =>
305
+ ctx.openTab("goal", {
306
+ id: "goal-document",
307
+ kind: "goal-document",
308
+ resourceId: "goal-document",
309
+ title: "Goal document",
310
+ }),
311
+ });
312
+ add({
313
+ id: "goal.skills.open",
314
+ title: "Show skills",
315
+ group: "Goal",
316
+ run: () => ctx.openTab("goal", { id: "skills", kind: "skills", resourceId: "skills", title: "Skills" }),
317
+ });
318
+ add({
319
+ id: "goal.save",
320
+ title: "Save goal",
321
+ group: "Goal",
322
+ shortcut: "Ctrl+S",
323
+ disabled: () => disabledWhen(goal.goalEditingDisabled, "The goal is read only during work."),
324
+ run: () => {
325
+ ctx.openTab("goal", {
326
+ id: "goal-document",
327
+ kind: "goal-document",
328
+ resourceId: "goal-document",
329
+ title: "Goal document",
330
+ });
331
+ goal.saveGoal();
332
+ },
333
+ });
334
+ add({
335
+ id: "goal.create",
336
+ title: goal.goalReady ? "Recreate goal" : "Create goal",
337
+ group: "Goal",
338
+ run: () => goal.createGoal(),
339
+ });
340
+ add({
341
+ id: "goal.revise",
342
+ title: "Revise goal",
343
+ group: "Goal",
344
+ disabled: () => disabledWhen(goal.revisionDisabled, "A run is using the goal right now."),
345
+ run: () => goal.reviseGoal(),
346
+ });
347
+ add({
348
+ id: "goal.revision.focus",
349
+ title: "Focus revision guidance",
350
+ group: "Goal",
351
+ run: () => {
352
+ ctx.openTab("goal", {
353
+ id: "goal-document",
354
+ kind: "goal-document",
355
+ resourceId: "goal-document",
356
+ title: "Goal document",
357
+ });
358
+ return ctx.focusLater("goal", "#revision-request");
359
+ },
360
+ });
361
+ add({
362
+ id: "goal.skills.prepare",
363
+ title: "Prepare skills",
364
+ group: "Goal",
365
+ disabled: () => disabledWhen(goal.prepareDisabled, "A goal and an idle session are required."),
366
+ run: () => goal.prepareSkills(),
367
+ });
368
+ add({
369
+ id: "goal.adr.start-next",
370
+ title: "Start the next ADR",
371
+ group: "Goal",
372
+ disabled: () =>
373
+ disabledWhen(goal.next === null, "No ADR is pending.") ??
374
+ disabledWhen(goal.runStartDisabled, "Another run is active."),
375
+ run: () => goal.startNext(),
376
+ });
377
+ add({
378
+ id: "goal.lifecycle.start",
379
+ title: "Start implementation",
380
+ group: "Goal",
381
+ disabled: () =>
382
+ disabledWhen(goal.lifecycleActive, "Implementation is already running.") ??
383
+ disabledWhen(goal.lifecycleButtonDisabled, "The goal and skills must be ready."),
384
+ run: () => goal.toggleLifecycle(),
385
+ });
386
+ add({
387
+ id: "goal.lifecycle.toggle",
388
+ title: goal.lifecycleActive ? "Stop implementation" : "Start implementation",
389
+ group: "Goal",
390
+ disabled: () =>
391
+ goal.lifecycleButtonDisabled
392
+ ? (goal.lifecycleButtonTitle ?? "The goal and skills must be ready.")
393
+ : null,
394
+ run: () => goal.toggleLifecycle(),
395
+ });
396
+ add({
397
+ id: "goal.lifecycle.pause",
398
+ title: "Pause implementation",
399
+ group: "Goal",
400
+ disabled: () =>
401
+ disabledWhen(!goal.lifecycleActive, "Implementation is not running.") ??
402
+ disabledWhen(goal.lifecycle.paused, "Implementation is already paused."),
403
+ run: () => goal.pauseLifecycle(),
404
+ });
405
+ add({
406
+ id: "goal.lifecycle.resume",
407
+ title: "Resume implementation",
408
+ group: "Goal",
409
+ disabled: () => disabledWhen(!goal.lifecycleActive || !goal.lifecycle.paused, "Implementation is not paused."),
410
+ run: () => goal.resumeLifecycle(),
411
+ });
412
+ add({
413
+ id: "goal.lifecycle.stop",
414
+ title: "Stop implementation",
415
+ group: "Goal",
416
+ disabled: () => disabledWhen(!goal.lifecycleActive, "Implementation is not running."),
417
+ run: () => goal.stopLifecycle(),
418
+ });
419
+ add({
420
+ id: "goal.reset",
421
+ title: "Reset implementation",
422
+ group: "Goal",
423
+ disabled: () => disabledWhen(goal.resetDisabled, "The connection is not open."),
424
+ run: () => goal.armReset(),
425
+ });
426
+ add({
427
+ id: "goal.reset.confirm",
428
+ title: "Confirm implementation reset",
429
+ group: "Goal",
430
+ disabled: () => disabledWhen(!goal.resetArmed, "Arm the reset first."),
431
+ run: () => goal.confirmReset(),
432
+ });
433
+ add({
434
+ id: "goal.reset.cancel",
435
+ title: "Cancel implementation reset",
436
+ group: "Goal",
437
+ disabled: () => disabledWhen(!goal.resetArmed, "No reset is armed."),
438
+ run: () => goal.cancelReset(),
439
+ });
440
+ add({
441
+ id: "goal.answer.submit",
442
+ title: "Submit the answer",
443
+ group: "Goal",
444
+ disabled: () => disabledWhen(goal.question === null, "No question is waiting."),
445
+ run: () => {
446
+ if (goal.question === null) {
447
+ ctx.openTab("goal", { id: "overview", kind: "overview", resourceId: "overview", title: "Implementation" });
448
+ return ctx.focusLater("goal", "#goal-answer-input");
449
+ }
450
+ goal.submitAnswer(goal.draft);
451
+ },
452
+ });
453
+ for (const record of goal.records) {
454
+ add({
455
+ id: `goal.adr.open.${record.file}`,
456
+ title: `Open ADR ${record.title}`,
457
+ group: "ADRs",
458
+ keywords: `${record.file} ${record.status}`,
459
+ run: () =>
460
+ ctx.openTab("goal", {
461
+ id: tabId("adr", record.file),
462
+ kind: "adr",
463
+ resourceId: record.file,
464
+ title: record.title,
465
+ }),
466
+ });
467
+ add({
468
+ id: `goal.adr.start.${record.file}`,
469
+ title: `Start ADR ${record.title}`,
470
+ group: "ADRs",
471
+ disabled: () =>
472
+ disabledWhen(!record.pending, "The record is already resolved.") ??
473
+ disabledWhen(goal.runStartDisabled, "Another run is active."),
474
+ run: () => goal.startRecord(record),
475
+ });
476
+ }
477
+ for (const skill of goal.skills) {
478
+ add({
479
+ id: `goal.skill.open.${skill.name}`,
480
+ title: `Show skill ${skill.name}`,
481
+ group: "Skills",
482
+ run: () => {
483
+ goal.selectSkill(skill.name);
484
+ ctx.openTab("goal", { id: "skills", kind: "skills", resourceId: "skills", title: "Skills" });
485
+ },
486
+ });
487
+ }
488
+ for (const lane of goal.lanes) {
489
+ const label = goal.laneLabel(lane);
490
+ add({
491
+ id: `goal.lane.open.${lane.id}`,
492
+ title: `Open lane ${label}`,
493
+ group: "Lanes",
494
+ keywords: lane.kind,
495
+ run: () =>
496
+ ctx.openTab("goal", {
497
+ id: tabId("lane", lane.id),
498
+ kind: "lane",
499
+ resourceId: lane.id,
500
+ title: label,
501
+ }),
502
+ });
503
+ add({
504
+ id: `goal.lane.answer.submit.${lane.id}`,
505
+ title: `Submit the answer for lane ${label}`,
506
+ group: "Lanes",
507
+ disabled: () => disabledWhen(lane.question === null, "The lane has no pending question."),
508
+ run: () => {
509
+ const current = goal.lane(lane.id);
510
+ if (current?.question == null) {
511
+ ctx.focusLater("goal", `#lane-answer-${lane.id}`);
512
+ return;
513
+ }
514
+ goal.submitLaneAnswer(lane.id, goal.laneDraft);
515
+ },
516
+ });
517
+ }
518
+
519
+ // Flow mode.
520
+ add({
521
+ id: "flow.goal.create",
522
+ title: flow.goal.trim().length > 0 ? "Recreate flow goal" : "Create flow goal",
523
+ group: "Flow",
524
+ run: () => flow.createGoal(),
525
+ });
526
+ add({
527
+ id: "flow.goal.save",
528
+ title: "Save flow goal",
529
+ group: "Flow",
530
+ shortcut: "Ctrl+S",
531
+ run: () => {
532
+ ctx.openTab("flow", { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" });
533
+ flow.saveGoal();
534
+ },
535
+ });
536
+ add({
537
+ id: "flow.start",
538
+ title: "Start Flow implementation",
539
+ group: "Flow",
540
+ disabled: () => disabledWhen(flow.startDisabled, "A flow goal and an idle session are required."),
541
+ run: () => flow.startImplementation(),
542
+ });
543
+ add({
544
+ id: "flow.stop",
545
+ title: "Stop Flow implementation",
546
+ group: "Flow",
547
+ disabled: () => disabledWhen(flow.stopDisabled, "Implementation is not running."),
548
+ run: () => flow.stopImplementation(),
549
+ });
550
+ add({
551
+ id: "flow.decisions.save",
552
+ title: "Save Flow decisions",
553
+ group: "Flow",
554
+ shortcut: "Ctrl+S",
555
+ run: () => {
556
+ ctx.openTab("flow", { id: "decisions", kind: "decisions", resourceId: "decisions", title: "Decision board" });
557
+ flow.saveDecisions();
558
+ },
559
+ });
560
+ add({
561
+ id: "flow.steering.focus",
562
+ title: "Focus Flow steering",
563
+ group: "Flow",
564
+ run: () => {
565
+ ctx.openTab("flow", { id: "steering", kind: "steering", resourceId: "steering", title: "Steering" });
566
+ return ctx.focusLater("flow", "#flow-steering-text");
567
+ },
568
+ });
569
+ add({
570
+ id: "flow.steering.send",
571
+ title: "Send Flow steering note",
572
+ group: "Flow",
573
+ disabled: () => disabledWhen(flow.steeringDraft.trim().length === 0, "Type a steering note first."),
574
+ run: () => flow.sendSteering(),
575
+ });
576
+ add({
577
+ id: "flow.pr.retry",
578
+ title: "Retry the Flow pull request",
579
+ group: "Flow",
580
+ disabled: () => disabledWhen(flow.prError === null, "There is no pull request error."),
581
+ run: () => flow.retryPr(),
582
+ });
583
+ add({
584
+ id: "flow.pr.ready",
585
+ title: "Mark the Flow pull request ready",
586
+ group: "Flow",
587
+ disabled: () => disabledWhen(flow.readyDisabled, "Only a stopped session with a draft PR can be marked ready."),
588
+ run: () => flow.markPrReady(),
589
+ });
590
+ add({
591
+ id: "flow.pr.merge",
592
+ title: "Merge the Flow pull request",
593
+ group: "Flow",
594
+ disabled: () => disabledWhen(flow.mergeDisabled, "Only a stopped session with a live PR can be merged."),
595
+ run: () => flow.mergePr(),
596
+ });
597
+ add({
598
+ id: "flow.conversation.send",
599
+ title: "Send the conversation message",
600
+ group: "Flow",
601
+ disabled: () => disabledWhen(flow.conversation === null, "No conversation is open."),
602
+ run: () => flow.sendConversation(),
603
+ });
604
+ add({
605
+ id: "flow.conclusion.send",
606
+ title: "Send the conclusion to implementation",
607
+ group: "Flow",
608
+ disabled: () =>
609
+ disabledWhen(flow.conclusionDraft.trim().length === 0, "Type a conclusion first."),
610
+ run: () => flow.sendConclusion(),
611
+ });
612
+ add({
613
+ id: "flow.conversation.close",
614
+ title: "Close the widget conversation",
615
+ group: "Flow",
616
+ disabled: () => disabledWhen(flow.conversation === null, "No conversation is open."),
617
+ run: () => flow.closeConversation(),
618
+ });
619
+ add({
620
+ id: "flow.diff.close",
621
+ title: "Close the diff detail",
622
+ group: "Flow",
623
+ run: () => layout.activateTab("flow", "overview"),
624
+ });
625
+ for (const widget of flow.widgets) {
626
+ add({
627
+ id: `flow.widget.open.${widget.id}`,
628
+ title: `Open widget ${widget.concept}`,
629
+ group: "Flow widgets",
630
+ keywords: widget.category,
631
+ run: () =>
632
+ ctx.openTab("flow", {
633
+ id: tabId("widget", widget.id),
634
+ kind: "widget",
635
+ resourceId: widget.id,
636
+ title: widget.concept,
637
+ }),
638
+ });
639
+ add({
640
+ id: `flow.widget.accept.${widget.id}`,
641
+ title: `Toggle acceptance of ${widget.concept}`,
642
+ group: "Flow widgets",
643
+ run: () => flow.toggleWidget(widget),
644
+ });
645
+ add({
646
+ id: `flow.widget.discuss.${widget.id}`,
647
+ title: `Discuss ${widget.concept}`,
648
+ group: "Flow widgets",
649
+ run: () => flow.discuss(widget),
650
+ });
651
+ add({
652
+ id: `flow.widget.refuse.${widget.id}`,
653
+ title: `Refuse ${widget.concept}`,
654
+ group: "Flow widgets",
655
+ run: () => flow.startRefuse(widget),
656
+ });
657
+ add({
658
+ id: `flow.widget.refuse.confirm.${widget.id}`,
659
+ title: `Confirm refusing ${widget.concept}`,
660
+ group: "Flow widgets",
661
+ disabled: () => disabledWhen(flow.refusingId !== widget.id, "Start a refusal first."),
662
+ run: () => flow.confirmRefuse(widget),
663
+ });
664
+ add({
665
+ id: `flow.widget.refuse.cancel.${widget.id}`,
666
+ title: `Cancel refusing ${widget.concept}`,
667
+ group: "Flow widgets",
668
+ disabled: () => disabledWhen(flow.refusingId !== widget.id, "No refusal is open."),
669
+ run: () => flow.cancelRefuse(),
670
+ });
671
+ }
672
+ for (const file of flow.files) {
673
+ add({
674
+ id: `flow.file.open.${file.path}`,
675
+ title: `Open changed file ${file.path}`,
676
+ group: "Flow files",
677
+ keywords: file.status,
678
+ run: () =>
679
+ ctx.openTab("flow", {
680
+ id: tabId("file", file.path),
681
+ kind: "file",
682
+ resourceId: file.path,
683
+ title: file.path,
684
+ }),
685
+ });
686
+ }
687
+ for (const checkpoint of flow.checkpoints) {
688
+ add({
689
+ id: `flow.checkpoint.open.${checkpoint.id}`,
690
+ title: `Open checkpoint ${checkpoint.summary}`,
691
+ group: "Flow checkpoints",
692
+ run: () =>
693
+ ctx.openTab("flow", {
694
+ id: tabId("checkpoint", checkpoint.id),
695
+ kind: "checkpoint",
696
+ resourceId: checkpoint.id,
697
+ title: checkpoint.summary,
698
+ }),
699
+ });
700
+ }
701
+ for (const blocker of flow.blockers) {
702
+ add({
703
+ id: `flow.blocker.open.${blocker.id}`,
704
+ title: `Open blocker ${blocker.text}`,
705
+ group: "Flow blockers",
706
+ run: () =>
707
+ ctx.openTab("flow", { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" }),
708
+ });
709
+ }
710
+ for (const blocker of flow.blockers.filter((entry) => !entry.dismissed)) {
711
+ add({
712
+ id: `flow.blocker.dismiss.${blocker.id}`,
713
+ title: `Dismiss blocker ${blocker.text}`,
714
+ group: "Flow blockers",
715
+ keywords: "blocker",
716
+ run: () => flow.dismissBlocker(blocker),
717
+ });
718
+ }
719
+ for (const target of ["goal", "implementation", "conversation"] as const) {
720
+ add({
721
+ id: `flow.answer.submit.${target}`,
722
+ title: `Submit the ${target} answer`,
723
+ group: "Flow",
724
+ disabled: () =>
725
+ disabledWhen(
726
+ target === "goal"
727
+ ? flow.goalCreation?.question == null
728
+ : target === "conversation"
729
+ ? flow.conversation?.question == null
730
+ : flow.question === null,
731
+ "No question is waiting.",
732
+ ),
733
+ run: () => {
734
+ const tab =
735
+ target === "goal"
736
+ ? { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" }
737
+ : target === "conversation" && flow.conversation !== null
738
+ ? {
739
+ id: `conversation:${flow.conversation.widgetId}`,
740
+ kind: "conversation",
741
+ resourceId: flow.conversation.widgetId,
742
+ title: "Widget conversation",
743
+ }
744
+ : { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" };
745
+ ctx.openTab("flow", tab);
746
+ return ctx.focusLater("flow", `#flow-answer-${target}`);
747
+ },
748
+ });
749
+ }
750
+
751
+ // Discover mode.
752
+ add({
753
+ id: "discover.start",
754
+ title: discover.hasArtifacts ? "Re-discover the project" : "Discover the project",
755
+ group: "Discover",
756
+ disabled: () => disabledWhen(discover.startDisabled, discover.startTitle ?? "A run is active."),
757
+ run: () => discover.start(),
758
+ });
759
+ add({
760
+ id: "discover.stop",
761
+ title: "Stop the Discover run",
762
+ group: "Discover",
763
+ disabled: () => disabledWhen(!discover.active, "No Discover run is active."),
764
+ run: () => discover.stop(),
765
+ });
766
+ add({
767
+ id: "discover.glossary.focus",
768
+ title: "Focus the glossary search",
769
+ group: "Discover",
770
+ run: () => {
771
+ ctx.openTab("discover", { id: "glossary", kind: "glossary", resourceId: "glossary", title: "Glossary" });
772
+ return ctx.focusLater("discover", "#discover-glossary-search");
773
+ },
774
+ });
775
+ add({
776
+ id: "discover.answer.submit",
777
+ title: "Answer the Discover question",
778
+ group: "Discover",
779
+ disabled: () => disabledWhen(discover.question === null, "No question is waiting."),
780
+ run: () => {
781
+ if (discover.question === null) {
782
+ ctx.openTab("discover", { id: "overview", kind: "overview", resourceId: "overview", title: "Overview" });
783
+ return ctx.focusLater("discover", "#discover-answer-input");
784
+ }
785
+ discover.answer(discover.answerDraft);
786
+ },
787
+ });
788
+ for (const tab of TABS) {
789
+ add({
790
+ id: `discover.tab.${tab.id}`,
791
+ title: `Open ${tab.label}`,
792
+ group: "Discover sections",
793
+ run: () =>
794
+ ctx.openTab("discover", {
795
+ id: tab.id,
796
+ kind: tab.skill === null ? tab.id : "skill",
797
+ resourceId: tab.skill ?? tab.id,
798
+ title: tab.label,
799
+ }),
800
+ });
801
+ if (tab.skill !== null) {
802
+ add({
803
+ id: `discover.skill.save.${tab.skill}`,
804
+ title: `Save the ${tab.label}`,
805
+ group: "Discover skills",
806
+ shortcut: "Ctrl+S",
807
+ run: () => {
808
+ ctx.openTab("discover", {
809
+ id: tab.id,
810
+ kind: "skill",
811
+ resourceId: tab.skill as string,
812
+ title: tab.label,
813
+ });
814
+ discover.saveSkill(tab.skill as string);
815
+ },
816
+ });
817
+ add({
818
+ id: `discover.skill.restore.${tab.skill}`,
819
+ title: `Restore the generated ${tab.label}`,
820
+ group: "Discover skills",
821
+ disabled: () =>
822
+ disabledWhen(discover.skill(tab.skill as string)?.state === "generated", "The skill already matches its generated version."),
823
+ run: () => {
824
+ ctx.openTab("discover", {
825
+ id: tab.id,
826
+ kind: "skill",
827
+ resourceId: tab.skill as string,
828
+ title: tab.label,
829
+ });
830
+ discover.restoreSkill(tab.skill as string);
831
+ },
832
+ });
833
+ }
834
+ }
835
+ return commands;
836
+ }