smol-symphony 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (627) hide show
  1. package/AGENTS.md +125 -39
  2. package/DESIGN.md +494 -273
  3. package/PRODUCT.md +2 -1
  4. package/README.md +259 -110
  5. package/SPEC.md +565 -1928
  6. package/WORKFLOW.minimal.yaml +34 -0
  7. package/WORKFLOW.template.yaml +1100 -0
  8. package/WORKFLOW.yaml +487 -0
  9. package/assets/skills/symphony-issues/SKILL.md +136 -0
  10. package/assets/symphony-mise.system.toml +68 -0
  11. package/dist/src/bin/symphony.js +30 -0
  12. package/dist/src/bin/symphony.js.map +1 -0
  13. package/dist/src/core/actions/context.js +109 -0
  14. package/dist/src/core/actions/context.js.map +1 -0
  15. package/dist/src/core/actions/parse.js +192 -0
  16. package/dist/src/core/actions/parse.js.map +1 -0
  17. package/dist/src/core/actions/plan.js +197 -0
  18. package/dist/src/core/actions/plan.js.map +1 -0
  19. package/dist/src/core/actions/predicates.js +111 -0
  20. package/dist/src/core/actions/predicates.js.map +1 -0
  21. package/dist/src/core/actions/run-fold.js +248 -0
  22. package/dist/src/core/actions/run-fold.js.map +1 -0
  23. package/dist/src/core/actions/template.js +118 -0
  24. package/dist/src/core/actions/template.js.map +1 -0
  25. package/dist/src/core/cli/args.js +116 -0
  26. package/dist/src/core/cli/args.js.map +1 -0
  27. package/dist/src/core/coerce.js +75 -0
  28. package/dist/src/core/coerce.js.map +1 -0
  29. package/dist/src/core/credential/account-id.js +20 -0
  30. package/dist/src/core/credential/account-id.js.map +1 -0
  31. package/dist/src/core/credential/adapter-config.js +136 -0
  32. package/dist/src/core/credential/adapter-config.js.map +1 -0
  33. package/dist/src/core/credential/availability.js +98 -0
  34. package/dist/src/core/credential/availability.js.map +1 -0
  35. package/dist/src/core/credential/extract.js +228 -0
  36. package/dist/src/core/credential/extract.js.map +1 -0
  37. package/dist/src/core/credential/fake-creds.js +171 -0
  38. package/dist/src/core/credential/fake-creds.js.map +1 -0
  39. package/dist/src/core/credential/identity.js +125 -0
  40. package/dist/src/core/credential/identity.js.map +1 -0
  41. package/dist/src/core/credential/shape.js +230 -0
  42. package/dist/src/core/credential/shape.js.map +1 -0
  43. package/dist/src/core/credential/strings.js +15 -0
  44. package/dist/src/core/credential/strings.js.map +1 -0
  45. package/dist/src/core/doctor/checks.js +303 -0
  46. package/dist/src/core/doctor/checks.js.map +1 -0
  47. package/dist/src/core/git/result.js +107 -0
  48. package/dist/src/core/git/result.js.map +1 -0
  49. package/dist/src/core/http/decisions.js +225 -0
  50. package/dist/src/core/http/decisions.js.map +1 -0
  51. package/dist/src/core/http/render.js +1635 -0
  52. package/dist/src/core/http/render.js.map +1 -0
  53. package/dist/src/core/http/routes.js +145 -0
  54. package/dist/src/core/http/routes.js.map +1 -0
  55. package/dist/src/core/http/views.js +181 -0
  56. package/dist/src/core/http/views.js.map +1 -0
  57. package/dist/src/core/image/managed-image.js +95 -0
  58. package/dist/src/core/image/managed-image.js.map +1 -0
  59. package/dist/src/core/issue/file.js +149 -0
  60. package/dist/src/core/issue/file.js.map +1 -0
  61. package/dist/src/core/issue/parse.js +210 -0
  62. package/dist/src/core/issue/parse.js.map +1 -0
  63. package/dist/src/core/mcp/dispatch.js +239 -0
  64. package/dist/src/core/mcp/dispatch.js.map +1 -0
  65. package/dist/src/core/mcp/post-move.js +92 -0
  66. package/dist/src/core/mcp/post-move.js.map +1 -0
  67. package/dist/src/core/mcp/protocol.js +293 -0
  68. package/dist/src/core/mcp/protocol.js.map +1 -0
  69. package/dist/src/core/mcp/url.js +162 -0
  70. package/dist/src/core/mcp/url.js.map +1 -0
  71. package/dist/src/core/path.js +63 -0
  72. package/dist/src/core/path.js.map +1 -0
  73. package/dist/src/core/reconcile/image-decide.js +48 -0
  74. package/dist/src/core/reconcile/image-decide.js.map +1 -0
  75. package/dist/src/core/reconcile/ledger.js +142 -0
  76. package/dist/src/core/reconcile/ledger.js.map +1 -0
  77. package/dist/src/core/reconcile/pr-classify.js +62 -0
  78. package/dist/src/core/reconcile/pr-classify.js.map +1 -0
  79. package/dist/src/core/reconcile/pr-decide.js +180 -0
  80. package/dist/src/core/reconcile/pr-decide.js.map +1 -0
  81. package/dist/src/core/reconcile/pr-loop.js +161 -0
  82. package/dist/src/core/reconcile/pr-loop.js.map +1 -0
  83. package/dist/src/core/reconcile/pr-notes.js +35 -0
  84. package/dist/src/core/reconcile/pr-notes.js.map +1 -0
  85. package/dist/src/core/reconcile/vm-decide.js +70 -0
  86. package/dist/src/core/reconcile/vm-decide.js.map +1 -0
  87. package/dist/src/core/reconcile/vm-reap.js +207 -0
  88. package/dist/src/core/reconcile/vm-reap.js.map +1 -0
  89. package/dist/src/core/reconcile/workspace-decide.js +162 -0
  90. package/dist/src/core/reconcile/workspace-decide.js.map +1 -0
  91. package/dist/src/core/runlog/summary.js +231 -0
  92. package/dist/src/core/runlog/summary.js.map +1 -0
  93. package/dist/src/core/runner/dispatch-config.js +95 -0
  94. package/dist/src/core/runner/dispatch-config.js.map +1 -0
  95. package/dist/src/core/runner/injection.js +61 -0
  96. package/dist/src/core/runner/injection.js.map +1 -0
  97. package/dist/src/core/runner/mise.js +210 -0
  98. package/dist/src/core/runner/mise.js.map +1 -0
  99. package/dist/src/core/runner/prompt.js +720 -0
  100. package/dist/src/core/runner/prompt.js.map +1 -0
  101. package/dist/src/core/runner/turn.js +242 -0
  102. package/dist/src/core/runner/turn.js.map +1 -0
  103. package/dist/src/core/runner/vm-plan.js +390 -0
  104. package/dist/src/core/runner/vm-plan.js.map +1 -0
  105. package/dist/src/core/schedule/admission.js +123 -0
  106. package/dist/src/core/schedule/admission.js.map +1 -0
  107. package/dist/src/core/schedule/circuit-breaker.js +111 -0
  108. package/dist/src/core/schedule/circuit-breaker.js.map +1 -0
  109. package/dist/src/core/schedule/eligibility.js +83 -0
  110. package/dist/src/core/schedule/eligibility.js.map +1 -0
  111. package/dist/src/core/schedule/reconcile-issue.js +82 -0
  112. package/dist/src/core/schedule/reconcile-issue.js.map +1 -0
  113. package/dist/src/core/schedule/retry.js +96 -0
  114. package/dist/src/core/schedule/retry.js.map +1 -0
  115. package/dist/src/core/schedule/sleep-cycle.js +133 -0
  116. package/dist/src/core/schedule/sleep-cycle.js.map +1 -0
  117. package/dist/src/core/schedule/slots.js +124 -0
  118. package/dist/src/core/schedule/slots.js.map +1 -0
  119. package/dist/src/core/schedule/tick.js +553 -0
  120. package/dist/src/core/schedule/tick.js.map +1 -0
  121. package/dist/src/core/schedule/token-fold.js +181 -0
  122. package/dist/src/core/schedule/token-fold.js.map +1 -0
  123. package/dist/src/core/state-resolve.js +86 -0
  124. package/dist/src/core/state-resolve.js.map +1 -0
  125. package/dist/src/core/vm-guards.js +278 -0
  126. package/dist/src/core/vm-guards.js.map +1 -0
  127. package/dist/src/core/workflow/derive.js +107 -0
  128. package/dist/src/core/workflow/derive.js.map +1 -0
  129. package/dist/src/core/workflow/parse.js +687 -0
  130. package/dist/src/core/workflow/parse.js.map +1 -0
  131. package/dist/src/core/workflow/prompt-probe.js +78 -0
  132. package/dist/src/core/workflow/prompt-probe.js.map +1 -0
  133. package/dist/src/core/workflow/validate.js +189 -0
  134. package/dist/src/core/workflow/validate.js.map +1 -0
  135. package/dist/src/core/workspace-key.js +19 -0
  136. package/dist/src/core/workspace-key.js.map +1 -0
  137. package/dist/src/shell/actions-runner.js +356 -0
  138. package/dist/src/shell/actions-runner.js.map +1 -0
  139. package/dist/src/shell/adapter/adapter-registry.js +45 -0
  140. package/dist/src/shell/adapter/adapter-registry.js.map +1 -0
  141. package/dist/src/shell/adapter/clock-random.js +96 -0
  142. package/dist/src/shell/adapter/clock-random.js.map +1 -0
  143. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  144. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  145. package/dist/src/shell/adapter/gondolin-dispatch.js +385 -0
  146. package/dist/src/shell/adapter/gondolin-dispatch.js.map +1 -0
  147. package/dist/src/shell/adapter/gondolin-image-converter.js +233 -0
  148. package/dist/src/shell/adapter/gondolin-image-converter.js.map +1 -0
  149. package/dist/src/shell/adapter/gondolin-image-fetch.js +180 -0
  150. package/dist/src/shell/adapter/gondolin-image-fetch.js.map +1 -0
  151. package/dist/src/shell/adapter/launcher-asset.js +57 -0
  152. package/dist/src/shell/adapter/launcher-asset.js.map +1 -0
  153. package/dist/src/shell/adapter/mise-config-asset.js +65 -0
  154. package/dist/src/shell/adapter/mise-config-asset.js.map +1 -0
  155. package/dist/src/shell/adapter/workflow-loader.js +304 -0
  156. package/dist/src/shell/adapter/workflow-loader.js.map +1 -0
  157. package/dist/src/shell/cli/doctor.js +268 -0
  158. package/dist/src/shell/cli/doctor.js.map +1 -0
  159. package/dist/src/shell/effect-interpreter-families.js +314 -0
  160. package/dist/src/shell/effect-interpreter-families.js.map +1 -0
  161. package/dist/src/shell/effect-interpreter.js +29 -0
  162. package/dist/src/shell/effect-interpreter.js.map +1 -0
  163. package/dist/src/shell/interp/acp-frame.js +137 -0
  164. package/dist/src/shell/interp/acp-frame.js.map +1 -0
  165. package/dist/src/shell/interp/acp-ws-conn.js +320 -0
  166. package/dist/src/shell/interp/acp-ws-conn.js.map +1 -0
  167. package/dist/src/shell/interp/acp-ws-frames.js +159 -0
  168. package/dist/src/shell/interp/acp-ws-frames.js.map +1 -0
  169. package/dist/src/shell/interp/acp-ws.js +197 -0
  170. package/dist/src/shell/interp/acp-ws.js.map +1 -0
  171. package/dist/src/shell/interp/acp.js +319 -0
  172. package/dist/src/shell/interp/acp.js.map +1 -0
  173. package/dist/src/shell/interp/credential-defaults.js +128 -0
  174. package/dist/src/shell/interp/credential-defaults.js.map +1 -0
  175. package/dist/src/shell/interp/credential-hooks.js +149 -0
  176. package/dist/src/shell/interp/credential-hooks.js.map +1 -0
  177. package/dist/src/shell/interp/credential-registry.js +226 -0
  178. package/dist/src/shell/interp/credential-registry.js.map +1 -0
  179. package/dist/src/shell/interp/credential.js +103 -0
  180. package/dist/src/shell/interp/credential.js.map +1 -0
  181. package/dist/src/shell/interp/gh.js +163 -0
  182. package/dist/src/shell/interp/gh.js.map +1 -0
  183. package/dist/src/shell/interp/git.js +28 -0
  184. package/dist/src/shell/interp/git.js.map +1 -0
  185. package/dist/src/shell/interp/log.js +213 -0
  186. package/dist/src/shell/interp/log.js.map +1 -0
  187. package/dist/src/shell/interp/process.js +178 -0
  188. package/dist/src/shell/interp/process.js.map +1 -0
  189. package/dist/src/shell/interp/runlog.js +193 -0
  190. package/dist/src/shell/interp/runlog.js.map +1 -0
  191. package/dist/src/shell/interp/timer.js +64 -0
  192. package/dist/src/shell/interp/timer.js.map +1 -0
  193. package/dist/src/shell/interp/tracker-disk.js +99 -0
  194. package/dist/src/shell/interp/tracker-disk.js.map +1 -0
  195. package/dist/src/shell/interp/tracker-parse.js +71 -0
  196. package/dist/src/shell/interp/tracker-parse.js.map +1 -0
  197. package/dist/src/shell/interp/tracker-scan.js +238 -0
  198. package/dist/src/shell/interp/tracker-scan.js.map +1 -0
  199. package/dist/src/shell/interp/tracker-write.js +91 -0
  200. package/dist/src/shell/interp/tracker-write.js.map +1 -0
  201. package/dist/src/shell/interp/tracker.js +41 -0
  202. package/dist/src/shell/interp/tracker.js.map +1 -0
  203. package/dist/src/shell/interp/tty.js +48 -0
  204. package/dist/src/shell/interp/tty.js.map +1 -0
  205. package/dist/src/shell/interp/vm.js +199 -0
  206. package/dist/src/shell/interp/vm.js.map +1 -0
  207. package/dist/src/shell/interp/workspace.js +310 -0
  208. package/dist/src/shell/interp/workspace.js.map +1 -0
  209. package/dist/src/shell/main-acp.js +78 -0
  210. package/dist/src/shell/main-acp.js.map +1 -0
  211. package/dist/src/shell/main-adapters.js +222 -0
  212. package/dist/src/shell/main-adapters.js.map +1 -0
  213. package/dist/src/shell/main-credential.js +122 -0
  214. package/dist/src/shell/main-credential.js.map +1 -0
  215. package/dist/src/shell/main-doctor.js +22 -0
  216. package/dist/src/shell/main-doctor.js.map +1 -0
  217. package/dist/src/shell/main-entry.js +46 -0
  218. package/dist/src/shell/main-entry.js.map +1 -0
  219. package/dist/src/shell/main-http-csrf.js +45 -0
  220. package/dist/src/shell/main-http-csrf.js.map +1 -0
  221. package/dist/src/shell/main-http-handler.js +389 -0
  222. package/dist/src/shell/main-http-handler.js.map +1 -0
  223. package/dist/src/shell/main-http-mcp.js +122 -0
  224. package/dist/src/shell/main-http-mcp.js.map +1 -0
  225. package/dist/src/shell/main-http-views.js +253 -0
  226. package/dist/src/shell/main-http-views.js.map +1 -0
  227. package/dist/src/shell/main-http.js +76 -0
  228. package/dist/src/shell/main-http.js.map +1 -0
  229. package/dist/src/shell/main-loops.js +130 -0
  230. package/dist/src/shell/main-loops.js.map +1 -0
  231. package/dist/src/shell/main-mcp.js +129 -0
  232. package/dist/src/shell/main-mcp.js.map +1 -0
  233. package/dist/src/shell/main-orchestrator.js +120 -0
  234. package/dist/src/shell/main-orchestrator.js.map +1 -0
  235. package/dist/src/shell/main-preflight.js +43 -0
  236. package/dist/src/shell/main-preflight.js.map +1 -0
  237. package/dist/src/shell/main-reconcilers-helpers.js +244 -0
  238. package/dist/src/shell/main-reconcilers-helpers.js.map +1 -0
  239. package/dist/src/shell/main-reconcilers-pr.js +148 -0
  240. package/dist/src/shell/main-reconcilers-pr.js.map +1 -0
  241. package/dist/src/shell/main-reconcilers.js +225 -0
  242. package/dist/src/shell/main-reconcilers.js.map +1 -0
  243. package/dist/src/shell/main-runner.js +355 -0
  244. package/dist/src/shell/main-runner.js.map +1 -0
  245. package/dist/src/shell/main-scaffold.js +116 -0
  246. package/dist/src/shell/main-scaffold.js.map +1 -0
  247. package/dist/src/shell/main-shutdown.js +115 -0
  248. package/dist/src/shell/main-shutdown.js.map +1 -0
  249. package/dist/src/shell/main-startup.js +48 -0
  250. package/dist/src/shell/main-startup.js.map +1 -0
  251. package/dist/src/shell/main-substrates.js +43 -0
  252. package/dist/src/shell/main-substrates.js.map +1 -0
  253. package/dist/src/shell/main.js +385 -0
  254. package/dist/src/shell/main.js.map +1 -0
  255. package/dist/src/shell/orchestrator-feedback.js +69 -0
  256. package/dist/src/shell/orchestrator-feedback.js.map +1 -0
  257. package/dist/src/shell/orchestrator-image.js +167 -0
  258. package/dist/src/shell/orchestrator-image.js.map +1 -0
  259. package/dist/src/shell/orchestrator-loop.js +468 -0
  260. package/dist/src/shell/orchestrator-loop.js.map +1 -0
  261. package/dist/src/shell/orchestrator-reconcile.js +36 -0
  262. package/dist/src/shell/orchestrator-reconcile.js.map +1 -0
  263. package/dist/src/shell/reconciler-loop.js +228 -0
  264. package/dist/src/shell/reconciler-loop.js.map +1 -0
  265. package/dist/src/shell/runner-loop-turn.js +301 -0
  266. package/dist/src/shell/runner-loop-turn.js.map +1 -0
  267. package/dist/src/shell/runner-loop.js +338 -0
  268. package/dist/src/shell/runner-loop.js.map +1 -0
  269. package/dist/src/shell/server/http.js +208 -0
  270. package/dist/src/shell/server/http.js.map +1 -0
  271. package/dist/src/shell/server/mcp-runtime-effects.js +237 -0
  272. package/dist/src/shell/server/mcp-runtime-effects.js.map +1 -0
  273. package/dist/src/shell/server/mcp-runtime.js +99 -0
  274. package/dist/src/shell/server/mcp-runtime.js.map +1 -0
  275. package/dist/src/shell/workspace-key.js +14 -0
  276. package/dist/src/shell/workspace-key.js.map +1 -0
  277. package/dist/src/types/acp.js +8 -0
  278. package/dist/src/types/acp.js.map +1 -0
  279. package/dist/src/types/actions/plan.js +6 -0
  280. package/dist/src/types/actions/plan.js.map +1 -0
  281. package/dist/src/types/actions/predicates.js +6 -0
  282. package/dist/src/types/actions/predicates.js.map +1 -0
  283. package/dist/src/types/actions/run-fold.js +8 -0
  284. package/dist/src/types/actions/run-fold.js.map +1 -0
  285. package/dist/src/types/actions.js +7 -0
  286. package/dist/src/types/actions.js.map +1 -0
  287. package/dist/src/types/adapter/clock-random.js +4 -0
  288. package/dist/src/types/adapter/clock-random.js.map +1 -0
  289. package/dist/src/types/adapter/gondolin-image-converter.js +5 -0
  290. package/dist/src/types/adapter/gondolin-image-converter.js.map +1 -0
  291. package/dist/src/types/adapter/gondolin-image-fetch.js +5 -0
  292. package/dist/src/types/adapter/gondolin-image-fetch.js.map +1 -0
  293. package/dist/src/types/adapter/workflow-loader.js +4 -0
  294. package/dist/src/types/adapter/workflow-loader.js.map +1 -0
  295. package/dist/src/types/cli/args.js +8 -0
  296. package/dist/src/types/cli/args.js.map +1 -0
  297. package/dist/src/types/config.js +8 -0
  298. package/dist/src/types/config.js.map +1 -0
  299. package/dist/src/types/credential-interp.js +6 -0
  300. package/dist/src/types/credential-interp.js.map +1 -0
  301. package/dist/src/types/credentials.js +10 -0
  302. package/dist/src/types/credentials.js.map +1 -0
  303. package/dist/src/types/doctor.js +7 -0
  304. package/dist/src/types/doctor.js.map +1 -0
  305. package/dist/src/types/domain.js +7 -0
  306. package/dist/src/types/domain.js.map +1 -0
  307. package/dist/src/types/effect.js +15 -0
  308. package/dist/src/types/effect.js.map +1 -0
  309. package/dist/src/types/errors.js +39 -0
  310. package/dist/src/types/errors.js.map +1 -0
  311. package/dist/src/types/http/decisions.js +6 -0
  312. package/dist/src/types/http/decisions.js.map +1 -0
  313. package/dist/src/types/http/render.js +10 -0
  314. package/dist/src/types/http/render.js.map +1 -0
  315. package/dist/src/types/http/views.js +6 -0
  316. package/dist/src/types/http/views.js.map +1 -0
  317. package/dist/src/types/http.js +9 -0
  318. package/dist/src/types/http.js.map +1 -0
  319. package/dist/src/types/image/managed-image.js +7 -0
  320. package/dist/src/types/image/managed-image.js.map +1 -0
  321. package/dist/src/types/interp/effect-interpreter.js +8 -0
  322. package/dist/src/types/interp/effect-interpreter.js.map +1 -0
  323. package/dist/src/types/interp/tracker.js +7 -0
  324. package/dist/src/types/interp/tracker.js.map +1 -0
  325. package/dist/src/types/issue/file.js +6 -0
  326. package/dist/src/types/issue/file.js.map +1 -0
  327. package/dist/src/types/issue/parse.js +8 -0
  328. package/dist/src/types/issue/parse.js.map +1 -0
  329. package/dist/src/types/main-acp.js +13 -0
  330. package/dist/src/types/main-acp.js.map +1 -0
  331. package/dist/src/types/main-adapters.js +5 -0
  332. package/dist/src/types/main-adapters.js.map +1 -0
  333. package/dist/src/types/main-credential.js +21 -0
  334. package/dist/src/types/main-credential.js.map +1 -0
  335. package/dist/src/types/main-doctor.js +6 -0
  336. package/dist/src/types/main-doctor.js.map +1 -0
  337. package/dist/src/types/main-http-handler.js +12 -0
  338. package/dist/src/types/main-http-handler.js.map +1 -0
  339. package/dist/src/types/main-http.js +5 -0
  340. package/dist/src/types/main-http.js.map +1 -0
  341. package/dist/src/types/main-loops.js +5 -0
  342. package/dist/src/types/main-loops.js.map +1 -0
  343. package/dist/src/types/main-mcp.js +12 -0
  344. package/dist/src/types/main-mcp.js.map +1 -0
  345. package/dist/src/types/main-orchestrator.js +5 -0
  346. package/dist/src/types/main-orchestrator.js.map +1 -0
  347. package/dist/src/types/main-reconcilers.js +11 -0
  348. package/dist/src/types/main-reconcilers.js.map +1 -0
  349. package/dist/src/types/main-runner.js +13 -0
  350. package/dist/src/types/main-runner.js.map +1 -0
  351. package/dist/src/types/main-startup.js +5 -0
  352. package/dist/src/types/main-startup.js.map +1 -0
  353. package/dist/src/types/main-substrates.js +5 -0
  354. package/dist/src/types/main-substrates.js.map +1 -0
  355. package/dist/src/types/mcp/dispatch.js +4 -0
  356. package/dist/src/types/mcp/dispatch.js.map +1 -0
  357. package/dist/src/types/mcp/post-move.js +7 -0
  358. package/dist/src/types/mcp/post-move.js.map +1 -0
  359. package/dist/src/types/mcp.js +9 -0
  360. package/dist/src/types/mcp.js.map +1 -0
  361. package/dist/src/types/ports.js +12 -0
  362. package/dist/src/types/ports.js.map +1 -0
  363. package/dist/src/types/reconcile/image-decide.js +5 -0
  364. package/dist/src/types/reconcile/image-decide.js.map +1 -0
  365. package/dist/src/types/reconcile/ledger.js +7 -0
  366. package/dist/src/types/reconcile/ledger.js.map +1 -0
  367. package/dist/src/types/reconcile/pr-loop.js +8 -0
  368. package/dist/src/types/reconcile/pr-loop.js.map +1 -0
  369. package/dist/src/types/reconcile/vm-reap.js +8 -0
  370. package/dist/src/types/reconcile/vm-reap.js.map +1 -0
  371. package/dist/src/types/reconcile/workspace-decide.js +7 -0
  372. package/dist/src/types/reconcile/workspace-decide.js.map +1 -0
  373. package/dist/src/types/reconcile.js +9 -0
  374. package/dist/src/types/reconcile.js.map +1 -0
  375. package/dist/src/types/runlog.js +7 -0
  376. package/dist/src/types/runlog.js.map +1 -0
  377. package/dist/src/types/runner/actions-runner.js +12 -0
  378. package/dist/src/types/runner/actions-runner.js.map +1 -0
  379. package/dist/src/types/runner/gondolin-dispatch.js +5 -0
  380. package/dist/src/types/runner/gondolin-dispatch.js.map +1 -0
  381. package/dist/src/types/runner/injection.js +6 -0
  382. package/dist/src/types/runner/injection.js.map +1 -0
  383. package/dist/src/types/runner/runner-loop.js +5 -0
  384. package/dist/src/types/runner/runner-loop.js.map +1 -0
  385. package/dist/src/types/runner/turn.js +4 -0
  386. package/dist/src/types/runner/turn.js.map +1 -0
  387. package/dist/src/types/runner/vm-plan.js +4 -0
  388. package/dist/src/types/runner/vm-plan.js.map +1 -0
  389. package/dist/src/types/runtime.js +9 -0
  390. package/dist/src/types/runtime.js.map +1 -0
  391. package/dist/src/types/schedule/admission.js +7 -0
  392. package/dist/src/types/schedule/admission.js.map +1 -0
  393. package/dist/src/types/schedule/circuit-breaker.js +2 -0
  394. package/dist/src/types/schedule/circuit-breaker.js.map +1 -0
  395. package/dist/src/types/schedule/eligibility.js +9 -0
  396. package/dist/src/types/schedule/eligibility.js.map +1 -0
  397. package/dist/src/types/schedule/orchestrator-loop.js +10 -0
  398. package/dist/src/types/schedule/orchestrator-loop.js.map +1 -0
  399. package/dist/src/types/schedule/sleep-cycle.js +4 -0
  400. package/dist/src/types/schedule/sleep-cycle.js.map +1 -0
  401. package/dist/src/types/schedule/slots.js +8 -0
  402. package/dist/src/types/schedule/slots.js.map +1 -0
  403. package/dist/src/types/schedule/tick.js +9 -0
  404. package/dist/src/types/schedule/tick.js.map +1 -0
  405. package/dist/src/types/server/mcp-runtime.js +8 -0
  406. package/dist/src/types/server/mcp-runtime.js.map +1 -0
  407. package/dist/src/types/workflow/parse.js +4 -0
  408. package/dist/src/types/workflow/parse.js.map +1 -0
  409. package/dist/tests/core/account-id.test.js +35 -0
  410. package/dist/tests/core/account-id.test.js.map +1 -0
  411. package/dist/tests/core/actions-parse.test.js +176 -0
  412. package/dist/tests/core/actions-parse.test.js.map +1 -0
  413. package/dist/tests/core/adapter-config.test.js +133 -0
  414. package/dist/tests/core/adapter-config.test.js.map +1 -0
  415. package/dist/tests/core/admission.test.js +215 -0
  416. package/dist/tests/core/admission.test.js.map +1 -0
  417. package/dist/tests/core/args.test.js +132 -0
  418. package/dist/tests/core/args.test.js.map +1 -0
  419. package/dist/tests/core/availability.test.js +62 -0
  420. package/dist/tests/core/availability.test.js.map +1 -0
  421. package/dist/tests/core/checks.test.js +395 -0
  422. package/dist/tests/core/checks.test.js.map +1 -0
  423. package/dist/tests/core/circuit-breaker.test.js +172 -0
  424. package/dist/tests/core/circuit-breaker.test.js.map +1 -0
  425. package/dist/tests/core/coerce.test.js +87 -0
  426. package/dist/tests/core/coerce.test.js.map +1 -0
  427. package/dist/tests/core/context.test.js +228 -0
  428. package/dist/tests/core/context.test.js.map +1 -0
  429. package/dist/tests/core/decisions.test.js +310 -0
  430. package/dist/tests/core/decisions.test.js.map +1 -0
  431. package/dist/tests/core/derive.test.js +205 -0
  432. package/dist/tests/core/derive.test.js.map +1 -0
  433. package/dist/tests/core/dispatch-config.test.js +164 -0
  434. package/dist/tests/core/dispatch-config.test.js.map +1 -0
  435. package/dist/tests/core/dispatch.test.js +302 -0
  436. package/dist/tests/core/dispatch.test.js.map +1 -0
  437. package/dist/tests/core/eligibility.test.js +163 -0
  438. package/dist/tests/core/eligibility.test.js.map +1 -0
  439. package/dist/tests/core/extract.test.js +139 -0
  440. package/dist/tests/core/extract.test.js.map +1 -0
  441. package/dist/tests/core/fake-creds.test.js +134 -0
  442. package/dist/tests/core/fake-creds.test.js.map +1 -0
  443. package/dist/tests/core/file.test.js +197 -0
  444. package/dist/tests/core/file.test.js.map +1 -0
  445. package/dist/tests/core/git-result.test.js +113 -0
  446. package/dist/tests/core/git-result.test.js.map +1 -0
  447. package/dist/tests/core/identity.test.js +180 -0
  448. package/dist/tests/core/identity.test.js.map +1 -0
  449. package/dist/tests/core/image-decide.test.js +59 -0
  450. package/dist/tests/core/image-decide.test.js.map +1 -0
  451. package/dist/tests/core/injection.test.js +163 -0
  452. package/dist/tests/core/injection.test.js.map +1 -0
  453. package/dist/tests/core/ledger.test.js +218 -0
  454. package/dist/tests/core/ledger.test.js.map +1 -0
  455. package/dist/tests/core/managed-image.test.js +68 -0
  456. package/dist/tests/core/managed-image.test.js.map +1 -0
  457. package/dist/tests/core/mise.test.js +138 -0
  458. package/dist/tests/core/mise.test.js.map +1 -0
  459. package/dist/tests/core/parse.test.js +174 -0
  460. package/dist/tests/core/parse.test.js.map +1 -0
  461. package/dist/tests/core/path.test.js +50 -0
  462. package/dist/tests/core/path.test.js.map +1 -0
  463. package/dist/tests/core/plan.test.js +218 -0
  464. package/dist/tests/core/plan.test.js.map +1 -0
  465. package/dist/tests/core/post-move.test.js +162 -0
  466. package/dist/tests/core/post-move.test.js.map +1 -0
  467. package/dist/tests/core/pr-classify.test.js +117 -0
  468. package/dist/tests/core/pr-classify.test.js.map +1 -0
  469. package/dist/tests/core/pr-decide.test.js +298 -0
  470. package/dist/tests/core/pr-decide.test.js.map +1 -0
  471. package/dist/tests/core/pr-loop.test.js +301 -0
  472. package/dist/tests/core/pr-loop.test.js.map +1 -0
  473. package/dist/tests/core/pr-notes.test.js +165 -0
  474. package/dist/tests/core/pr-notes.test.js.map +1 -0
  475. package/dist/tests/core/predicates.test.js +154 -0
  476. package/dist/tests/core/predicates.test.js.map +1 -0
  477. package/dist/tests/core/prompt.test.js +189 -0
  478. package/dist/tests/core/prompt.test.js.map +1 -0
  479. package/dist/tests/core/protocol.test.js +195 -0
  480. package/dist/tests/core/protocol.test.js.map +1 -0
  481. package/dist/tests/core/reconcile-issue.test.js +116 -0
  482. package/dist/tests/core/reconcile-issue.test.js.map +1 -0
  483. package/dist/tests/core/render.test.js +549 -0
  484. package/dist/tests/core/render.test.js.map +1 -0
  485. package/dist/tests/core/retry.test.js +186 -0
  486. package/dist/tests/core/retry.test.js.map +1 -0
  487. package/dist/tests/core/routes.test.js +247 -0
  488. package/dist/tests/core/routes.test.js.map +1 -0
  489. package/dist/tests/core/run-fold.test.js +299 -0
  490. package/dist/tests/core/run-fold.test.js.map +1 -0
  491. package/dist/tests/core/shape.test.js +185 -0
  492. package/dist/tests/core/shape.test.js.map +1 -0
  493. package/dist/tests/core/sleep-cycle.test.js +150 -0
  494. package/dist/tests/core/sleep-cycle.test.js.map +1 -0
  495. package/dist/tests/core/slots.test.js +201 -0
  496. package/dist/tests/core/slots.test.js.map +1 -0
  497. package/dist/tests/core/state-resolve.test.js +80 -0
  498. package/dist/tests/core/state-resolve.test.js.map +1 -0
  499. package/dist/tests/core/summary.test.js +200 -0
  500. package/dist/tests/core/summary.test.js.map +1 -0
  501. package/dist/tests/core/template.test.js +116 -0
  502. package/dist/tests/core/template.test.js.map +1 -0
  503. package/dist/tests/core/tick.test.js +558 -0
  504. package/dist/tests/core/tick.test.js.map +1 -0
  505. package/dist/tests/core/token-fold.test.js +176 -0
  506. package/dist/tests/core/token-fold.test.js.map +1 -0
  507. package/dist/tests/core/turn.test.js +388 -0
  508. package/dist/tests/core/turn.test.js.map +1 -0
  509. package/dist/tests/core/url.test.js +118 -0
  510. package/dist/tests/core/url.test.js.map +1 -0
  511. package/dist/tests/core/validate.test.js +247 -0
  512. package/dist/tests/core/validate.test.js.map +1 -0
  513. package/dist/tests/core/views.test.js +252 -0
  514. package/dist/tests/core/views.test.js.map +1 -0
  515. package/dist/tests/core/vm-decide.test.js +110 -0
  516. package/dist/tests/core/vm-decide.test.js.map +1 -0
  517. package/dist/tests/core/vm-guards.test.js +153 -0
  518. package/dist/tests/core/vm-guards.test.js.map +1 -0
  519. package/dist/tests/core/vm-plan.test.js +332 -0
  520. package/dist/tests/core/vm-plan.test.js.map +1 -0
  521. package/dist/tests/core/vm-reap.test.js +196 -0
  522. package/dist/tests/core/vm-reap.test.js.map +1 -0
  523. package/dist/tests/core/workflow-parse.test.js +493 -0
  524. package/dist/tests/core/workflow-parse.test.js.map +1 -0
  525. package/dist/tests/core/workspace-decide.test.js +236 -0
  526. package/dist/tests/core/workspace-decide.test.js.map +1 -0
  527. package/dist/tests/helpers/fixtures.js +167 -0
  528. package/dist/tests/helpers/fixtures.js.map +1 -0
  529. package/dist/tests/shell/acp-substrate.test.js +101 -0
  530. package/dist/tests/shell/acp-substrate.test.js.map +1 -0
  531. package/dist/tests/shell/actions-runner-push.test.js +203 -0
  532. package/dist/tests/shell/actions-runner-push.test.js.map +1 -0
  533. package/dist/tests/shell/credential-hooks.test.js +36 -0
  534. package/dist/tests/shell/credential-hooks.test.js.map +1 -0
  535. package/dist/tests/shell/credential-registry.test.js +165 -0
  536. package/dist/tests/shell/credential-registry.test.js.map +1 -0
  537. package/dist/tests/shell/credential-substrate.test.js +179 -0
  538. package/dist/tests/shell/credential-substrate.test.js.map +1 -0
  539. package/dist/tests/shell/dockerfile-mise-pin.test.js +51 -0
  540. package/dist/tests/shell/dockerfile-mise-pin.test.js.map +1 -0
  541. package/dist/tests/shell/doctor.test.js +101 -0
  542. package/dist/tests/shell/doctor.test.js.map +1 -0
  543. package/dist/tests/shell/effect-vm-create.test.js +52 -0
  544. package/dist/tests/shell/effect-vm-create.test.js.map +1 -0
  545. package/dist/tests/shell/gh-port.test.js +63 -0
  546. package/dist/tests/shell/gh-port.test.js.map +1 -0
  547. package/dist/tests/shell/gondolin-dispatch-guard.test.js +144 -0
  548. package/dist/tests/shell/gondolin-dispatch-guard.test.js.map +1 -0
  549. package/dist/tests/shell/gondolin-dispatch-shquote.test.js +168 -0
  550. package/dist/tests/shell/gondolin-dispatch-shquote.test.js.map +1 -0
  551. package/dist/tests/shell/gondolin-image-converter.test.js +208 -0
  552. package/dist/tests/shell/gondolin-image-converter.test.js.map +1 -0
  553. package/dist/tests/shell/gondolin-image-fetch.test.js +93 -0
  554. package/dist/tests/shell/gondolin-image-fetch.test.js.map +1 -0
  555. package/dist/tests/shell/http-handler.test.js +608 -0
  556. package/dist/tests/shell/http-handler.test.js.map +1 -0
  557. package/dist/tests/shell/http-server.test.js +53 -0
  558. package/dist/tests/shell/http-server.test.js.map +1 -0
  559. package/dist/tests/shell/mcp-runtime.test.js +366 -0
  560. package/dist/tests/shell/mcp-runtime.test.js.map +1 -0
  561. package/dist/tests/shell/mise-config-asset.test.js +87 -0
  562. package/dist/tests/shell/mise-config-asset.test.js.map +1 -0
  563. package/dist/tests/shell/orchestrator-loop.test.js +583 -0
  564. package/dist/tests/shell/orchestrator-loop.test.js.map +1 -0
  565. package/dist/tests/shell/reconciler-passes.test.js +314 -0
  566. package/dist/tests/shell/reconciler-passes.test.js.map +1 -0
  567. package/dist/tests/shell/runner-loop-turn.test.js +97 -0
  568. package/dist/tests/shell/runner-loop-turn.test.js.map +1 -0
  569. package/dist/tests/shell/runner-slice.test.js +536 -0
  570. package/dist/tests/shell/runner-slice.test.js.map +1 -0
  571. package/dist/tests/shell/scaffold.test.js +65 -0
  572. package/dist/tests/shell/scaffold.test.js.map +1 -0
  573. package/dist/tests/shell/tick-config.test.js +83 -0
  574. package/dist/tests/shell/tick-config.test.js.map +1 -0
  575. package/dist/tests/shell/tracker-parse-dates.test.js +44 -0
  576. package/dist/tests/shell/tracker-parse-dates.test.js.map +1 -0
  577. package/dist/tests/shell/tracker-write-issue.test.js +154 -0
  578. package/dist/tests/shell/tracker-write-issue.test.js.map +1 -0
  579. package/dist/tests/shell/workflow-prompt-split.test.js +208 -0
  580. package/dist/tests/shell/workflow-prompt-split.test.js.map +1 -0
  581. package/dist/tests/shell/workspace-live-config.test.js +140 -0
  582. package/dist/tests/shell/workspace-live-config.test.js.map +1 -0
  583. package/package.json +31 -11
  584. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  585. package/prompts/Reflect.md +91 -0
  586. package/prompts/Review.md +97 -0
  587. package/prompts/Todo.md +96 -0
  588. package/prompts/_footer.md +41 -0
  589. package/prompts/_preamble.md +42 -0
  590. package/prompts-minimal/Todo.md +26 -0
  591. package/scripts/postinstall.mjs +63 -0
  592. package/scripts/vm-agent.mjs +433 -0
  593. package/WORKFLOW.md +0 -269
  594. package/WORKFLOW.template.md +0 -307
  595. package/dist/agent/acp.js +0 -304
  596. package/dist/agent/acp.js.map +0 -1
  597. package/dist/agent/adapters.js +0 -275
  598. package/dist/agent/adapters.js.map +0 -1
  599. package/dist/agent/codex.js +0 -439
  600. package/dist/agent/codex.js.map +0 -1
  601. package/dist/agent/runner.js +0 -394
  602. package/dist/agent/runner.js.map +0 -1
  603. package/dist/agent/smolvm.js +0 -174
  604. package/dist/agent/smolvm.js.map +0 -1
  605. package/dist/bin/symphony.js +0 -205
  606. package/dist/bin/symphony.js.map +0 -1
  607. package/dist/http.js +0 -1189
  608. package/dist/http.js.map +0 -1
  609. package/dist/logging.js +0 -45
  610. package/dist/logging.js.map +0 -1
  611. package/dist/mcp.js +0 -478
  612. package/dist/mcp.js.map +0 -1
  613. package/dist/orchestrator.js +0 -683
  614. package/dist/orchestrator.js.map +0 -1
  615. package/dist/prompt.js +0 -65
  616. package/dist/prompt.js.map +0 -1
  617. package/dist/trackers/local.js +0 -344
  618. package/dist/trackers/local.js.map +0 -1
  619. package/dist/trackers/types.js +0 -10
  620. package/dist/trackers/types.js.map +0 -1
  621. package/dist/types.js +0 -3
  622. package/dist/types.js.map +0 -1
  623. package/dist/workflow.js +0 -385
  624. package/dist/workflow.js.map +0 -1
  625. package/dist/workspace.js +0 -196
  626. package/dist/workspace.js.map +0 -1
  627. package/scripts/build-vm.sh +0 -67
package/DESIGN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: smol-symphony
3
- description: The orchestrator's console. A small dark workshop for dispatching and watching coding agents.
3
+ description: The orchestrator's console. A small dark workshop for dispatching and watching coding agents, laid out as a kanban bench.
4
4
  colors:
5
5
  surface-inset: "#0c0f15"
6
6
  surface-base: "#0f1115"
@@ -12,26 +12,31 @@ colors:
12
12
  text-muted: "#9aa4b2"
13
13
  text-base: "#dfe2e7"
14
14
  text-strong: "#e6ebf2"
15
- text-on-accent: "#ffffff"
15
+ text-on-accent: "#f4f6fb"
16
16
  dispatch-blue: "#2a6df4"
17
+ link-hover: "#9cc0ff"
17
18
  running-bg: "#1f3a26"
18
19
  running-fg: "#58d68d"
19
20
  retrying-bg: "#3a2f1f"
20
21
  retrying-fg: "#f0c060"
21
22
  idle-bg: "#20242c"
22
23
  idle-fg: "#9aa4b2"
24
+ await-bg: "#1f2a36"
25
+ await-fg: "#7fb5d4"
26
+ done-bg: "#1c2a1f"
27
+ done-fg: "#82c896"
23
28
  error-fg: "#ff7676"
24
29
  success-fg: "#58d68d"
25
30
  typography:
26
31
  headline:
27
32
  fontFamily: "ui-sans-serif, system-ui, sans-serif"
28
33
  fontSize: "1.3rem"
29
- fontWeight: 400
30
- lineHeight: 1.2
34
+ fontWeight: 500
35
+ lineHeight: 1.3
31
36
  title:
32
37
  fontFamily: "ui-sans-serif, system-ui, sans-serif"
33
38
  fontSize: "1rem"
34
- fontWeight: 400
39
+ fontWeight: 500
35
40
  lineHeight: 1.3
36
41
  body:
37
42
  fontFamily: "ui-sans-serif, system-ui, sans-serif"
@@ -43,9 +48,14 @@ typography:
43
48
  fontSize: "14px"
44
49
  fontWeight: 400
45
50
  lineHeight: 1.4
51
+ colhead:
52
+ fontFamily: "ui-sans-serif, system-ui, sans-serif"
53
+ fontSize: "14px"
54
+ fontWeight: 500
55
+ lineHeight: 1.4
46
56
  pill:
47
57
  fontFamily: "ui-sans-serif, system-ui, sans-serif"
48
- fontSize: "0.85em"
58
+ fontSize: "0.78em"
49
59
  fontWeight: 400
50
60
  lineHeight: 1.4
51
61
  data:
@@ -54,14 +64,15 @@ typography:
54
64
  fontWeight: 400
55
65
  fontFeature: "tabular-nums"
56
66
  lineHeight: 1.45
57
- th:
58
- fontFamily: "ui-sans-serif, system-ui, sans-serif"
59
- fontSize: "14px"
60
- fontWeight: 500
67
+ mono:
68
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
69
+ fontSize: "13px"
70
+ fontWeight: 400
71
+ fontFeature: "tabular-nums"
61
72
  lineHeight: 1.4
62
73
  rounded:
74
+ xs: "3px"
63
75
  sm: "4px"
64
- md: "8px"
65
76
  pill: "999px"
66
77
  spacing:
67
78
  "2xs": "0.1rem"
@@ -75,53 +86,96 @@ components:
75
86
  backgroundColor: "{colors.dispatch-blue}"
76
87
  textColor: "{colors.text-on-accent}"
77
88
  rounded: "{rounded.sm}"
78
- padding: "0.5rem 1rem"
89
+ padding: "0.55rem 1.1rem"
79
90
  typography: "{typography.body}"
80
91
  button-ghost:
81
92
  backgroundColor: "{colors.surface-chip}"
82
93
  textColor: "{colors.text-base}"
94
+ border: "1px solid {colors.border-firm}"
83
95
  rounded: "{rounded.sm}"
84
- padding: "0.3rem 0.7rem"
96
+ padding: "0.35rem 0.85rem"
85
97
  typography: "{typography.body}"
98
+ button-ghost-sm:
99
+ backgroundColor: "transparent"
100
+ textColor: "{colors.text-muted}"
101
+ border: "1px solid {colors.border-firm}"
102
+ rounded: "{rounded.xs}"
103
+ padding: "0.18rem 0.6rem"
104
+ typography: "{typography.pill}"
105
+ icon-button:
106
+ backgroundColor: "{colors.surface-chip}"
107
+ textColor: "{colors.text-muted}"
108
+ border: "1px solid {colors.border-firm}"
109
+ rounded: "{rounded.sm}"
110
+ size: "22–28px square"
86
111
  input:
87
112
  backgroundColor: "{colors.surface-inset}"
88
113
  textColor: "{colors.text-strong}"
114
+ border: "1px solid {colors.border-firm}"
89
115
  rounded: "{rounded.sm}"
90
- padding: "0.4rem 0.6rem"
116
+ padding: "0.45rem 0.6rem"
117
+ focusOutline: "1px solid {colors.dispatch-blue}"
91
118
  typography: "{typography.body}"
92
- card-form:
119
+ new-issue-panel:
93
120
  backgroundColor: "{colors.surface-raised}"
94
121
  textColor: "{colors.text-base}"
95
- rounded: "{rounded.md}"
96
- padding: "1rem"
122
+ borderLeft: "1px solid {colors.border-firm}"
123
+ rounded: "0"
124
+ placement: "fixed, right edge, full height, slides in"
125
+ header-badge:
126
+ rounded: "{rounded.pill}"
127
+ padding: "0.1rem 0.55rem"
128
+ typography: "{typography.pill}"
129
+ transform: "uppercase"
130
+ variants:
131
+ idle: { backgroundColor: "{colors.idle-bg}", textColor: "{colors.idle-fg}" }
132
+ working: { backgroundColor: "{colors.running-bg}", textColor: "{colors.running-fg}" }
133
+ attention: { backgroundColor: "{colors.await-bg}", textColor: "{colors.await-fg}" }
97
134
  pill-running:
98
135
  backgroundColor: "{colors.running-bg}"
99
136
  textColor: "{colors.running-fg}"
100
137
  rounded: "{rounded.pill}"
101
- padding: "0.1rem 0.5rem"
138
+ padding: "0.1rem 0.55rem"
102
139
  typography: "{typography.pill}"
103
140
  pill-retrying:
104
141
  backgroundColor: "{colors.retrying-bg}"
105
142
  textColor: "{colors.retrying-fg}"
106
143
  rounded: "{rounded.pill}"
107
- padding: "0.1rem 0.5rem"
144
+ padding: "0.1rem 0.55rem"
108
145
  typography: "{typography.pill}"
109
146
  pill-idle:
110
147
  backgroundColor: "{colors.idle-bg}"
111
148
  textColor: "{colors.idle-fg}"
112
149
  rounded: "{rounded.pill}"
113
- padding: "0.1rem 0.5rem"
150
+ padding: "0.1rem 0.55rem"
151
+ typography: "{typography.pill}"
152
+ pill-awaiting:
153
+ backgroundColor: "{colors.await-bg}"
154
+ textColor: "{colors.await-fg}"
155
+ rounded: "{rounded.pill}"
156
+ padding: "0.1rem 0.55rem"
157
+ typography: "{typography.pill}"
158
+ pill-done:
159
+ backgroundColor: "{colors.done-bg}"
160
+ textColor: "{colors.done-fg}"
161
+ rounded: "{rounded.pill}"
162
+ padding: "0.1rem 0.55rem"
114
163
  typography: "{typography.pill}"
115
- th:
116
- backgroundColor: "{colors.surface-base}"
164
+ kanban-column:
165
+ headerBorderBottom: "1px solid {colors.border-firm}"
166
+ nameColor: "{colors.text-muted}"
167
+ countColor: "{colors.text-dim}"
168
+ typography: "{typography.colhead}"
169
+ issue-row:
170
+ backgroundColor: "transparent"
171
+ borderBottom: "1px solid {colors.border-soft}"
172
+ rounded: "{rounded.xs}"
173
+ awaitTint: "rgba(127, 181, 212, 0.07)"
174
+ retryStuckTint: "rgba(240, 192, 96, 0.05)"
175
+ health-field:
117
176
  textColor: "{colors.text-muted}"
118
- typography: "{typography.th}"
119
- padding: "0.4rem 0.6rem"
120
- td:
121
- backgroundColor: "{colors.surface-base}"
122
- textColor: "{colors.text-base}"
123
- typography: "{typography.data}"
124
- padding: "0.4rem 0.6rem"
177
+ badColor: "{colors.error-fg}"
178
+ typography: "{typography.mono}"
125
179
  ---
126
180
 
127
181
  # Design System: smol-symphony
@@ -131,323 +185,490 @@ components:
131
185
  **Creative North Star: "The Quiet Workshop"**
132
186
 
133
187
  A tidy workbench, not a control room. The orchestrator does the real work; this
134
- surface only shows what is on the bench right now, and gives one place to put
135
- new work on. Everything you can see is there because the orchestrator actually
136
- knows it, and nothing is there for atmosphere.
188
+ surface only shows what is on the bench right now, lays it out as a kanban board
189
+ of states, and gives one place to put new work on. Everything you can see is
190
+ there because the orchestrator actually knows it, and nothing is there for
191
+ atmosphere.
137
192
 
138
193
  The system is dark because the user opens it next to a terminal, against an
139
194
  already-dark editor, in the middle of doing other things. It is flat because
140
195
  the workshop has no spotlights: depth is conveyed by stacking slightly lighter
141
196
  panels on a slightly darker bench, never by glow or lift. Numbers are tabular,
142
- labels are real field names, and pills mean exactly the three states the
143
- orchestrator tracks: running, retrying, idle.
197
+ identifiers are monospace, labels are real field names, and every status colour
198
+ encodes one distinct thing the orchestrator tracks.
144
199
 
145
200
  What this system rejects, drawn from PRODUCT.md: generic SaaS dashboard chrome
146
- (sidebar nav, gradient metric tiles, identical card grids), Jira-style ITSM
147
- heaviness (modal-on-modal flows, status pills whose colors don't encode
148
- distinct state), and AI-product chrome (violet gradients, glassmorphism,
149
- sparkle iconography). smol-symphony runs AI agents but is not an AI product;
150
- the agents are infrastructure, not the narrative.
201
+ (sidebar nav, gradient metric tiles, identical card grids, "Welcome back"
202
+ heroes), Jira-style ITSM heaviness (modal-on-modal flows, status pills whose
203
+ colours don't encode distinct state), and AI-product chrome (violet gradients,
204
+ glassmorphism, sparkle iconography). smol-symphony runs AI agents but is not an
205
+ AI product; the agents are infrastructure, not the narrative.
151
206
 
152
207
  **Key Characteristics:**
153
208
  - Dark, cool-blue-leaning neutrals stacked into four tonal layers.
154
- - One saturated accent (Dispatch Blue) reserved for the single primary verb.
155
- - Three semantic pill states, each a paired bg + fg color. No fourth state, ever.
156
- - No shadows. Depth is tonal stacking only.
157
- - Single sans family. Tabular numerics for any cell that holds a number.
158
- - Density over whitespace luxury: tables are dense, the form is grid-packed.
209
+ - One saturated brand accent (Dispatch Blue), used for the primary verb and the
210
+ keyboard-focus ring. No second saturated hue competes with it.
211
+ - Five semantic status pairs (running, retrying, idle, await, done), each a
212
+ paired bg + fg colour mapped to a real orchestrator state. New states earn a
213
+ new named pair; colours never get reused for a second meaning.
214
+ - The board is a **kanban**: one column per declared workflow state, each issue
215
+ a flat row. Transient runtime status (running / retrying / awaiting steering)
216
+ is overlaid on the row via a pill, a metadata trail, and tint.
217
+ - A sticky **header strip** carries a single tracker-state badge (idle /
218
+ working / attention), and a dense **system-health line** answers "is this
219
+ connected?" without a hero.
220
+ - New work is composed in a **slide-in panel** that enters from the right edge.
221
+ It is the one card-shaped surface; the board stays visible and interactive
222
+ behind it (non-modal).
223
+ - No shadows. Depth is tonal stacking, plus the one panel that slides in over
224
+ the bench.
225
+ - Two native font stacks: a system sans for prose, and a system monospace for
226
+ identifiers, code, paths, and the health line. No web fonts. Tabular numerics
227
+ on anything that holds a number.
228
+ - Density over whitespace luxury: columns are tight, rows are flat, the health
229
+ line is a single `key=value` strip.
230
+ - Motion exists only to encode or reveal state (the panel sliding in, a region
231
+ expanding when it has something to say, a hover affordance) — never as
232
+ decoration.
159
233
 
160
234
  ## 2. Colors: The Workshop Bench Palette
161
235
 
162
- A cool-blue-leaning dark stack with one saturated accent and three semantic
163
- status pairs. Each neutral is tinted toward the accent hue at near-zero
164
- chroma so no surface reads as pure grey.
236
+ A cool-blue-leaning dark stack with one saturated brand accent and five semantic
237
+ status pairs. Each neutral is tinted toward the accent hue at near-zero chroma
238
+ so no surface reads as pure grey. All values below are the live CSS custom
239
+ properties from the shipping dashboard (`:root` in `src/http.ts`).
165
240
 
166
241
  ### Primary
167
- - **Dispatch Blue** (`#2a6df4`, ≈`oklch(55% 0.22 261)`): the verb. Used only on
168
- the Create-issue submit button. Nowhere else on the page is this saturated.
169
- The button is the one place the user makes the orchestrator do something
170
- new; the color marks that action and only that action.
242
+ - **Dispatch Blue** (`#2a6df4`, ≈`oklch(55% 0.22 261)`): the brand accent. It
243
+ fills the one primary verb the **create** button in the new-issue panel
244
+ and draws the keyboard **focus ring** on every interactive control
245
+ (`outline solid var(--accent)`; inputs also take it as a focus
246
+ border-colour). It is the interaction accent, never a decorative one. Its
247
+ on-accent text is **`#f4f6fb`**, a near-white tinted toward the brand hue (the
248
+ old pure-`#fff` button label has been retired — see Don'ts).
249
+ - **Link Hover** (`#9cc0ff`): a lightened Dispatch Blue used for the hover /
250
+ focus state of identifiers and links (row identifiers, ticker IDs, the
251
+ steering panel's links, issue-detail body links). The resting state of those
252
+ links is a neutral text tone; the blue appears only on interaction.
171
253
 
172
254
  ### Neutral (the bench, four tonal layers)
173
- - **Inset** (`#0c0f15`, ≈`oklch(16% 0.008 260)`): input field interiors.
174
- Deeper than the body so fields read as sunken into the form, not floating.
175
- - **Bench** (`#0f1115`, ≈`oklch(18% 0.005 260)`): the page body. The default
176
- surface; everything else stacks on or beneath this.
177
- - **Raised Panel** (`#161a22`, ≈`oklch(21% 0.012 260)`): the create-issue form
178
- card. One step lighter than the bench, signalling "this is where you put new
255
+ - **Inset** (`#0c0f15`, ≈`oklch(16% 0.008 260)`): input field interiors and code
256
+ blocks. Deeper than the body so fields read as sunken slots, not floating.
257
+ - **Bench** (`#0f1115`, ≈`oklch(18% 0.005 260)`): the page body, the header
258
+ strip, the health line, and the ticker. The default surface; everything else
259
+ stacks on or beneath this.
260
+ - **Raised Panel** (`#161a22`, ≈`oklch(21% 0.012 260)`): the slide-in new-issue
261
+ panel. One step lighter than the bench, signalling "this is where you put new
179
262
  things."
180
- - **Chip** (`#20242c`, ≈`oklch(27% 0.012 260)`): the refresh button and the
181
- idle pill. The most-lifted neutral; reserved for small interactive surfaces
182
- and quiet status.
263
+ - **Chip** (`#20242c`, ≈`oklch(27% 0.012 260)`): ghost buttons, the icon buttons
264
+ (refresh, panel close), and the idle pill. The most-lifted neutral; reserved
265
+ for small interactive surfaces and quiet status.
183
266
 
184
267
  ### Border / Rule
185
- - **Soft Rule** (`#1c2029`, ≈`oklch(25% 0.010 260)`): horizontal table row
186
- dividers. Just barely visible; the eye should track columns, not rows.
187
- - **Firm Rule** (`#2a2e36`, ≈`oklch(31% 0.008 260)`): input borders and the
188
- underline beneath section headings. The structural lines.
268
+ - **Soft Rule** (`#1c2029`, ≈`oklch(25% 0.010 260)`): row dividers, and the
269
+ thin separators under the health line and ticker. Just barely visible.
270
+ - **Firm Rule** (`#2a2e36`, ≈`oklch(31% 0.008 260)`): control borders, the
271
+ header's bottom edge, the panel's left edge, and column-header underlines.
272
+ The structural lines.
189
273
 
190
274
  ### Text (the four-step ramp)
191
- - **Dim** (`#6b7280`): timestamps, `small.dim` annotations. The lowest reading
192
- layer; reachable but not pulling attention.
193
- - **Muted** (`#9aa4b2`): form labels, table headers, secondary copy.
194
- - **Base** (`#dfe2e7`): primary body text and table cell contents.
195
- - **Strong** (`#e6ebf2`): input field text and other content the user is
196
- actively typing or editing. One step brighter than base so what you're
197
- writing stands out from what you're reading.
198
-
199
- ### Status (three paired roles)
200
- Each pill carries both a background and a foreground; they are not
201
- interchangeable across states. The dark-on-color is what reads at a glance.
202
-
203
- - **Running** (`bg #1f3a26`, `fg #58d68d`): active sessions. The orchestrator
204
- is currently spending tokens on this issue.
205
- - **Retrying** (`bg #3a2f1f`, `fg #f0c060`): retry queue. The orchestrator
206
- hit a recoverable failure and will dispatch again on backoff.
275
+ - **Dim** (`#6b7280`): timestamps, the row "peek" line, column counts, the
276
+ totals footer, `.dim` annotations. The lowest reading layer.
277
+ - **Muted** (`#9aa4b2`): labels, column names, the health line, secondary copy.
278
+ - **Base** (`#dfe2e7`): primary body text, row titles, table-less cell content.
279
+ - **Strong** (`#e6ebf2`): text the user is typing (inputs), row identifiers,
280
+ section and panel titles. One step brighter than base.
281
+
282
+ ### Status (five paired roles)
283
+ Each carries both a background and a foreground; the pair is fixed per state and
284
+ never reused across states. The light-on-dark foreground is what reads at a
285
+ glance. The dashboard uses these as **pills** on rows, as the header **badge**,
286
+ and as faint full-row **tints**; the issue-detail page reuses the same pairs.
287
+
288
+ - **Running** (`bg #1f3a26`, `fg #58d68d`): a live session. The orchestrator is
289
+ currently spending tokens on this issue. A *continuation* (the slot-holding
290
+ resume of a clean handoff, e.g. Review Todo) deliberately reads as running,
291
+ not retrying, so a clean handoff never flashes an error colour (issue 146).
292
+ - **Retrying** (`bg #3a2f1f`, `fg #f0c060`): the failure-backoff queue. The
293
+ orchestrator hit a recoverable failure and will dispatch again on backoff.
294
+ Only `failure` retries are amber; continuations are not. Amber also tints the
295
+ name of a holding column and the "N retrying" ticker label.
207
296
  - **Idle** (`bg #20242c`, `fg #9aa4b2`): on disk, not running, not retrying.
208
297
  Deliberately the same hex as Chip + Muted, because idle is *the absence of
209
- status color*.
298
+ status colour*.
299
+ - **Await** (`bg #1f2a36`, `fg #7fb5d4`): an agent has called
300
+ `request_human_steering` and is blocked on the operator's reply. A calm blue,
301
+ not an alarm. Drives the `awaiting` pill, the "attention" header badge, the
302
+ "N awaiting" ticker label, and a faint blue wash on the awaiting row. The
303
+ same pair backs the (currently unused) `resuming` pill alias, kept calm-blue
304
+ for the same reason continuations stay green.
305
+ - **Done** (`bg #1c2a1f`, `fg #82c896`): a terminal state (Done, Cancelled). A
306
+ muted, settled green — quieter than running. Terminal columns are omitted from
307
+ the board, so this pair appears mainly on the issue-detail page and on any
308
+ trailing "undeclared" column.
210
309
 
211
310
  ### Inline message
212
- - **Error** (`#ff7676`): the create-form error line. Inline, not a toast, not
213
- a modal.
214
- - **Success** (`#58d68d`): the create-form OK line. Shares the running-fg
215
- green by design; success means "the orchestrator now knows about this."
311
+ - **Error** (`#ff7676`, ≈`oklch(70% 0.18 22)`): the panel's error line, a stuck
312
+ retry's error text, a bad health field (`hf-bad`), the "undeclared" column
313
+ badge, and the ticker error chip. Inline, never a toast, never a modal.
314
+ - **Success** (`#58d68d`): the panel's OK line. Shares the running-fg green by
315
+ design; success means "the orchestrator now knows about this."
216
316
 
217
317
  ### Named Rules
218
318
 
219
- **The One Verb Rule.** Dispatch Blue appears on exactly one element per page:
220
- the primary action. If a second saturated accent is needed, the design is
221
- wrong; reach for a status color (running/retrying/idle) or text weight first.
222
-
223
- **The Three-Pill Rule.** Status pills encode running, retrying, idle.
224
- Nothing else. Never invent a fourth pill color for taste; if a fourth state
225
- emerges from the orchestrator, name it semantically and pair a new bg + fg
226
- before adding it here.
319
+ **The One-Accent Rule** (was "The One Verb Rule"). There is exactly one
320
+ saturated brand hue: Dispatch Blue. It marks the single primary verb (the create
321
+ button) and the keyboard-focus ring both are *interaction*, not decoration. If
322
+ a second saturated accent is wanted, the design is wrong; reach for a status
323
+ pair or text weight first. (This rule was once "Dispatch Blue on exactly one
324
+ element per page"; the focus ring legitimately spreads the same hue across every
325
+ control, so the rule is now scoped to "one hue, interaction-only," not "one
326
+ element.")
327
+
328
+ **The Status-Pair Rule** (replaces "The Three-Pill Rule"). Status colour is a
329
+ closed vocabulary of *named bg + fg pairs*, each mapped to one real orchestrator
330
+ state: running, retrying, idle, await, done. Never invent a colour for taste; if
331
+ a new state emerges, name it semantically and add a new pair here before using
332
+ it. (The original "three pills — running/retrying/idle, no fourth ever" was
333
+ written before the board surfaced awaiting-steering and terminal state; the
334
+ *discipline* — one colour, one true state, no reuse — is unchanged, only the
335
+ count.)
227
336
 
228
337
  **The Tinted Neutral Rule.** No pure greys. Every neutral leans on the cool
229
338
  blue brand axis (hue ≈260) at near-zero chroma (0.005–0.012). Pure `#000` and
230
- pure `#fff` are prohibited.
339
+ pure `#fff` are prohibited (the create button's label is `#f4f6fb`, not `#fff`).
231
340
 
232
341
  ## 3. Typography
233
342
 
234
343
  **Body Font:** `ui-sans-serif, system-ui, sans-serif` (browser native).
235
- **Display Font:** none. The same family carries every role.
236
- **Mono / Data Font:** still the same family, with `font-variant-numeric:
237
- tabular-nums` on every cell that holds a number.
344
+ **Display Font:** none. The sans family carries every prose role.
345
+ **Mono / Data Font:** `ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`
346
+ a real, distinct (still native) monospace stack used for identifiers, code,
347
+ on-disk paths, the tracker root, the health line, and ticker IDs. Any cell that
348
+ holds a number — sans or mono — carries `font-variant-numeric: tabular-nums`.
238
349
 
239
- **Character:** the system font of whatever OS the user is on. No web fonts,
240
- no FOUT, no loading flash. The console reads like the operating system, not
241
- like a product. Numbers line up; text doesn't try to be elegant.
350
+ **Character:** the system fonts of whatever OS the user is on. No web fonts, no
351
+ FOUT, no loading flash. The console reads like the operating system, not like a
352
+ product. Identifiers and paths are monospace so they line up and read as
353
+ literal strings; prose stays sans.
242
354
 
243
355
  ### Hierarchy
244
356
 
245
- The scale is intentionally tight, with one density trick: section titles (h2)
246
- match body in size and depend on a divider rule plus generous top margin for
247
- hierarchy, not on type contrast. This keeps the page feeling dense and
248
- console-like rather than article-like.
249
-
250
- - **Headline** (h1, 400, `1.3rem` ≈ 20.8px, lh 1.2): the page title only.
251
- Appears once per page (`Symphony tracker.root = …`).
252
- - **Title** (h2, 400, `1rem` = 16px, lh 1.3, with a `1px` bottom rule in Firm
253
- Rule): section labels (`Create issue`, `Active sessions`, etc.). Same size
254
- as a paragraph; the rule beneath does the hierarchy work.
255
- - **Body** (400, `14px`, lh 1.45): default text, table cells.
256
- - **Label** (400, `14px`, color Muted): form labels and table headers.
257
- - **Data** (400, `14px`, `tabular-nums`): every number that appears in a
258
- cell. Token counts, attempt numbers, runtime seconds, time-of-day.
259
- - **TH** (500, `14px`, color Muted): table column headers. Heavier than body
260
- by one step (400 → 500), so columns name themselves.
261
- - **Pill** (400, `0.85em` ≈ 11.9px against 14px body): the three status pills.
262
- Smaller than body so pills read as inline metadata, not content.
357
+ The scale is intentionally tight. The dashboard has **no `h1`** its identity
358
+ is the header strip (`symphony · <workflow> · <tracker.root>`), not a page
359
+ title. The Headline role now lives on the **issue-detail page** (the issue
360
+ title) and the not-found page. Section titles match body size and lean on a
361
+ divider rule plus top margin for hierarchy, keeping the page console-like.
362
+
363
+ - **Headline** (h1, 500, `1.3rem` 20.8px, lh 1.3): the issue-detail page title
364
+ (and the not-found page). Once per page; absent on the dashboard.
365
+ - **Title** (h2, 500, `1rem` = 16px, with a `1px` Firm Rule underline): the
366
+ detail page's section labels and the slide-in panel's "new issue" heading.
367
+ Same size as a paragraph; the rule beneath does the hierarchy work.
368
+ - **Body** (400, `14px`, lh 1.45): default text, row titles.
369
+ - **Label** (400, `14px`, colour Muted): form labels in the new-issue panel.
370
+ - **Column header** (500, `14px`, colour Muted): kanban column names. One weight
371
+ step above body (400 500) so columns name themselves; a holding column's
372
+ name takes the amber (retrying) tone, a terminal/undeclared column's the Dim
373
+ tone.
374
+ - **Data** (400, `14px`, `tabular-nums`): every number token counts, attempt
375
+ numbers, runtime, time-of-day, counts.
376
+ - **Pill** (400, `0.78em` ≈ 10.9px against 14px body): status pills and the
377
+ header badge. Smaller than body so they read as inline metadata, not content.
378
+ (The issue-detail page renders its pill slightly larger, `0.82em`.)
379
+ - **Mono** (400, `13px`, `tabular-nums`): identifiers, the tracker root, the
380
+ health line (`12px`), and `<code>`. The CLI-in-HTML texture.
263
381
 
264
382
  ### Named Rules
265
383
 
266
- **The System-Font Rule.** No web fonts are loaded, ever. The console must
267
- look like the user's OS. If a custom face is added later, it has to justify
268
- the latency cost in writing.
384
+ **The System-Font Rule.** No web fonts are loaded, ever. Both stacks — the sans
385
+ and the monospace — are the user's native OS fonts. The console must look like
386
+ the user's OS. If a custom face is added later, it has to justify the latency
387
+ cost in writing.
269
388
 
270
- **The Tabular-Numbers Rule.** Any cell that contains a number applies
271
- `font-variant-numeric: tabular-nums`. Token counts, attempt counts,
272
- timestamps, IDs with digits: all of them. Columns of numbers must line up.
389
+ **The Tabular-Numbers Rule.** Any element that contains a number applies
390
+ `font-variant-numeric: tabular-nums`. Token counts, attempt counts, timestamps,
391
+ identifiers with digits, column counts: all of them. Columns of numbers must
392
+ line up.
273
393
 
274
- **The Same-Size Section-Header Rule.** Section titles (h2) are the same size
275
- as body. Hierarchy comes from the divider rule beneath and the top margin
276
- above, not from type scale. This keeps page density high.
394
+ **The Same-Size Section-Header Rule.** Section titles (h2) are the same size as
395
+ body. Hierarchy comes from the divider rule beneath and the margin above, not
396
+ from type scale. This keeps page density high.
277
397
 
278
398
  ## 4. Elevation
279
399
 
280
- **The system is flat.** No `box-shadow` rule appears anywhere in the
281
- stylesheet. Depth is conveyed entirely by tonal stacking of the neutrals:
282
- `surface-inset` (deepest) → `surface-base` (default) → `surface-raised` (form
283
- card) → `surface-chip` (refresh button, idle pill).
400
+ **The system is flat.** No `box-shadow` rule appears anywhere in the stylesheet.
401
+ Depth is conveyed by tonal stacking of the neutrals: `surface-inset` (deepest) →
402
+ `surface-base` (default) → `surface-raised` (the panel) → `surface-chip` (chips,
403
+ icon buttons, idle pill).
284
404
 
285
- There is no hover lift, no focus glow, no drop shadow on the form card. A
286
- panel reads as "raised" only because it is one step lighter than the bench
287
- beneath it.
405
+ The one surface that genuinely sits *above* the bench is the **slide-in
406
+ new-issue panel**: it is a Raised-Panel-coloured `aside` fixed to the right
407
+ edge, separated from the bench by a `1px` Firm Rule on its left and by **motion**
408
+ (it slides in on a 320ms ease), not by a shadow or a glow. There is no hover
409
+ lift and no focus glow anywhere; focus is an outline ring in Dispatch Blue.
288
410
 
289
411
  ### Named Rules
290
412
 
291
- **The Flat-By-Default Rule.** Surfaces are flat. Shadows are forbidden.
292
- Depth cues come from the four-step neutral stack and from the divider rules
293
- under section titles. If a future state needs visible elevation, introduce a
294
- fifth neutral step before reaching for `box-shadow`.
413
+ **The Flat-By-Default Rule.** Surfaces are flat. Shadows are forbidden. Depth
414
+ cues come from the four-step neutral stack, from the divider rules, and from the
415
+ one panel that translates in from the edge. If a future surface needs visible
416
+ elevation, introduce a fifth neutral step (or lean on the existing slide
417
+ motion) before reaching for `box-shadow`.
418
+
419
+ **The No-Glow Rule.** Focus, hover, and active states change colour, border, or
420
+ position — never glow. Focus is a crisp Dispatch Blue outline ring.
421
+ Glassmorphism, blur backdrops, and shimmer animations are prohibited (carries
422
+ PRODUCT.md's AI-product-chrome anti-reference).
295
423
 
296
- **The No-Glow Rule.** Focus, hover, and active states change color or
297
- border, never glow. Glassmorphism, blur backdrops, and shimmer animations
298
- are prohibited (carries PRODUCT.md's AI-product-chrome anti-reference).
424
+ **The State-Only-Motion Rule.** Motion encodes or reveals state: the panel
425
+ sliding in (it is open or closed), the ticker expanding when it has something to
426
+ say and collapsing to zero height when it doesn't, a hover border easing in as
427
+ an affordance cue, the 2-second poll refreshing. There is no motion that doesn't
428
+ mean something. No animated layout for flourish, no decorative loops, no
429
+ thinking-dots.
299
430
 
300
431
  ## 5. Components
301
432
 
302
- The doctrine is **crisp and exact**: minimal radius vocabulary (4px for
303
- controls, 8px for the form card, 999px for pills), 1px borders only where
304
- structurally necessary, hairline-tight alignment, no decorative motion.
433
+ The doctrine is **crisp and exact**: a minimal radius vocabulary (`3px` for
434
+ rows, `4px` for controls, `999px` for pills and badges), `1px` borders only
435
+ where structurally necessary, hairline-tight alignment, and motion only where it
436
+ encodes state.
437
+
438
+ ### Header strip
439
+
440
+ A sticky strip across the top of the dashboard, Bench-coloured with a `1px` Firm
441
+ Rule bottom edge. Left to right: the **brand** (`symphony`, weight 600, Strong),
442
+ a Dim `·`, the **workflow** name (Base), the **tracker root** (Mono, Dim,
443
+ ellipsised, takes the free space), the live **tracker-state badge**, and the
444
+ **refresh** icon button.
445
+
446
+ The **tracker-state badge** is the dashboard's single glance signal, polled
447
+ every 2s. It is a pill that takes one of three states from `trackerStatus()`:
448
+ - **idle** — idle pair (`bg #20242c` / `fg #9aa4b2`): nothing running.
449
+ - **working** — running pair (`bg #1f3a26` / `fg #58d68d`): at least one session
450
+ is live and nothing needs the operator.
451
+ - **attention** — await pair (`bg #1f2a36` / `fg #7fb5d4`): something is awaiting
452
+ steering, or a failure-backoff retry is queued. The badge uses the calm await
453
+ blue (it means "needs your eyes," not "broken"). Continuations never trip it.
454
+
455
+ The badge text is uppercased and letter-spaced; this is the one place type goes
456
+ uppercase, marking it as a status indicator rather than content.
457
+
458
+ ### System-health line
459
+
460
+ A dense, single-row `key=value` strip under the header (Bench background, Soft
461
+ Rule below, Mono `12px`, Muted, tabular-nums, wraps on narrow widths). It answers
462
+ the first-run question "is this thing actually connected?" without a hero or a
463
+ metric tile. Each field is a fact the orchestrator already knows from its health
464
+ probe:
465
+ - one `<State>=<adapter>/<model>` field per active state (per-state overrides
466
+ applied; an inherited model shows `/default`),
467
+ - `image=<short-digest>` (the gondolin image; full ref in the `title`),
468
+ - `cred=ok` / `cred=missing:<adapter>`,
469
+ - `tracker=writable` / `tracker=readonly`,
470
+ - `poll=<HH:MM:SS>` (the last poll time).
471
+
472
+ Only genuine problems — a missing credential, a read-only tracker — flag in the
473
+ Error colour (`hf-bad`). No new accent, no tiles, no hero. The region rides the
474
+ same 2s poll as everything else.
475
+
476
+ ### Attention ticker
477
+
478
+ A thin strip below the health line listing what currently needs eyes: pending
479
+ steering replies ("N awaiting", await-blue label) and stuck retries ("N retrying
480
+ · M with error", amber label). Each identifier is a monospace anchor that jumps
481
+ to the issue's row in the board. When nothing needs attention the strip
482
+ collapses to **zero height** (padding and border animate away) so an all-clear
483
+ board shows no empty chrome.
484
+
485
+ ### Board (kanban)
486
+
487
+ The dominant surface. A horizontally-scrolling grid of columns
488
+ (`grid-auto-flow: column`, min column width `260px`, `1.5rem` gap), **one column
489
+ per declared workflow state in declaration order**. Replaces the old flat
490
+ tables entirely.
491
+
492
+ - **Active** and **holding** states get a column. **Terminal** states (Done,
493
+ Cancelled) are deliberately omitted — the glance test is "what's running,
494
+ what's stuck," which finished work doesn't answer; terminal issues stay
495
+ reachable via their detail page.
496
+ - **Orphan columns**: an on-disk state directory whose name isn't in the
497
+ declared `states:` set (e.g. after a workflow rename) gets a trailing column
498
+ flagged with an Error-coloured `undeclared` badge, so stranded files stay
499
+ visible rather than silently dropping out.
500
+
501
+ Each **column** has a header — name (Column-header type; amber if holding, Dim
502
+ if terminal/orphan), a Dim tabular count, and, for active/holding columns, a
503
+ small `+` **add** icon button that opens the new-issue panel pre-set to that
504
+ column — over a body of rows. An empty active column shows a Dim hint, `drop
505
+ into <State>/`.
506
+
507
+ ### Issue row (the flat card)
508
+
509
+ Each on-disk issue is a flat row in its state's column — **not** a card. It has
510
+ a `1px` Soft Rule bottom divider, a small `3px` radius, and no fill at rest.
511
+ Runtime status is overlaid:
512
+ - **Row head**: the monospace identifier (Strong, links to the detail page), a
513
+ status **pill** (`awaiting` / `running` / `retrying`, in that priority), and a
514
+ Muted metadata trail (`turn N · X tok` while running, `attempt N · due
515
+ HH:MM:SS` while in failure backoff).
516
+ - **Title** (Base), then an optional Dim **peek** of the agent's last message
517
+ (clamped to two lines while running).
518
+ - A **stuck retry** shows its error text in the Error colour.
519
+ - A **triage** row (holding state, not otherwise busy) shows inline `approve` /
520
+ `discard` ghost-sm buttons.
521
+ - **Tints, not stripes**: an awaiting row gets a faint blue wash
522
+ (`rgba(127,181,212,0.07)`), a stuck retry a faint amber wash
523
+ (`rgba(240,192,96,0.05)`), an orphan row drops to 70% opacity. The whole row
524
+ tints; there is no colored left-border stripe.
525
+
526
+ Within a column, rows sort so anything needing attention floats up: awaiting →
527
+ running → retrying → idle.
528
+
529
+ ### Inline steering (on awaiting rows)
530
+
531
+ When an agent is awaiting steering, its row expands in place (a dashed Firm Rule
532
+ top separator) to show the agent's question (rendered Markdown), an optional
533
+ collapsible recap of the original task and the agent's context, and a reply
534
+ `<textarea>` + send button. Enter sends, Shift+Enter inserts a newline; the
535
+ draft is preserved across the 2s repoll. Steering happens **inline on the row**,
536
+ never in a modal.
537
+
538
+ ### Slide-in new-issue panel (the One Card)
539
+
540
+ The dashboard's only card-shaped surface. A Raised-Panel `aside` fixed to the
541
+ right edge (`400px`, max `90vw`, full height), separated from the bench by a
542
+ `1px` Firm Rule left edge. It is **non-modal**: it slides in on a 320ms ease when
543
+ the operator presses a column's `+` button (the originating column pre-selects
544
+ the state field), and the board stays fully visible and interactive on the left.
545
+ Esc closes it; a backdrop click does **not** (so an in-progress draft isn't lost
546
+ to a stray click).
547
+
548
+ It holds the create form: a **column** `<select>`, a **title** input, a
549
+ **description** `<textarea>` (all sunken inputs — Inset background, Strong text,
550
+ Firm Rule border, Dispatch Blue focus ring), an inline status line (`np-msg`:
551
+ Muted, Error, or success-green), and the one **primary** create button
552
+ (Dispatch Blue, `#f4f6fb` label).
553
+
554
+ ### Whole-board empty state (first run)
555
+
556
+ When the entire tracker holds zero issues, three terse lines sit on the bench
557
+ above the (empty) columns — no hero, no modal, no illustration:
558
+ - a Base lead, `no issues yet — dispatch your first`,
559
+ - a Muted how-to, `press + on a column, or add issues/<FirstActive>/<id>.md`,
560
+ with the `+` shown as a small keycap (a `1px` Firm-Rule box),
561
+ - a Dim note, `agents pick up <FirstActive> on the next poll`.
562
+
563
+ It teaches the two first-run affordances once and disappears the instant any
564
+ issue exists.
305
565
 
306
566
  ### Buttons
307
567
 
308
- Two variants. Both share radius `4px` and the body type role.
568
+ Radius `4px` (`3px` for ghost-sm), body type, no shadow. Hover changes
569
+ border-colour; focus draws a Dispatch Blue outline ring.
309
570
 
310
- - **Primary** (Create issue): background Dispatch Blue (`#2a6df4`), text
311
- on-accent (`#ffffff`, see Don'ts), padding `0.5rem 1rem`, no border. Placed
312
- inside the form grid as `grid-column: 2; justify-self: start`, so it
313
- aligns under the input column rather than spanning the form.
314
- - **Ghost / Refresh** (next to "Active sessions"): background Chip
315
- (`#20242c`), text Base (`#dfe2e7`), 1px Firm Rule border, padding
316
- `0.3rem 0.7rem`. Smaller than primary on every axis; used for
317
- console-style "refresh now" actions only.
318
-
319
- Hover and focus states are presently unstyled. The "crisp and exact"
320
- philosophy permits adding a `1px` Dispatch Blue focus-visible ring on both
321
- variants in a future pass; do not add hover lift or color shifts that imply
322
- elevation.
571
+ - **Primary** (create, in the panel): Dispatch Blue background, `#f4f6fb` label,
572
+ no border. The one primary verb.
573
+ - **Ghost** (send reply): Chip background, Base text, `1px` Firm Rule border.
574
+ - **Ghost-sm** (triage approve / discard): transparent, Muted text, `1px` Firm
575
+ Rule border, smaller; the `discard` variant hovers to the Error colour.
576
+ - **Icon buttons** (refresh, column `+`, panel close): square (22–28px),
577
+ Chip-or-transparent, `1px` Firm Rule border, a single glyph.
323
578
 
324
579
  ### Inputs
325
580
 
326
- `<input>`, `<select>`, `<textarea>` share one style.
327
-
328
- - **Background** Inset (`#0c0f15`), one step *deeper* than the form card.
329
- Inputs read as sunken slots, not floating tiles.
330
- - **Border** `1px solid` Firm Rule (`#2a2e36`).
331
- - **Text** Strong (`#e6ebf2`) so what is being typed sits one step above
332
- surrounding body text.
333
- - **Radius** `4px`.
334
- - **Padding** `0.4rem 0.6rem`.
335
- - **Width** `100%` of the form grid's value column.
336
- - **Textareas** add `min-height: 80px; resize: vertical`.
337
-
338
- Focus state is currently the browser default. Adding a `1px` Dispatch Blue
339
- focus-visible outline is the obvious next refinement, consistent with the
340
- philosophy.
341
-
342
- ### Card (the create-issue form)
343
-
344
- The only card-shaped surface in the system. There is exactly one.
581
+ `<input>`, `<select>`, `<textarea>` share one style: **Inset** background (one
582
+ step deeper than the panel, so they read as sunken slots), `1px` Firm Rule
583
+ border, **Strong** text, `4px` radius. Focus replaces the border with Dispatch
584
+ Blue and draws a `1px` Dispatch Blue outline. Textareas resize vertically only.
345
585
 
346
- - **Background** Raised Panel (`#161a22`).
347
- - **Radius** `8px` (the only place 8px appears).
348
- - **Padding** `1rem`.
349
- - **No border, no shadow.**
350
- - **Layout** `display: grid; grid-template-columns: max-content 1fr; gap:
351
- 0.5rem 1rem; align-items: center;` so labels (max-content) align with
352
- their inputs (1fr) on the same row.
586
+ ### Totals footer
353
587
 
354
- This is the workshop's input slot. The 8px corner is intentionally slightly
355
- softer than the rest of the system; on the next polish pass we may sharpen
356
- it to 4px or to a `1px` Firm Rule border with no radius. Either move would
357
- push the system further toward CLI-in-HTML.
358
-
359
- ### Pills (status chips)
360
-
361
- `display: inline-block`, `padding: 0.1rem 0.5rem`, `border-radius: 999px`,
362
- `font-size: 0.85em`. Three semantic instances, each a paired bg + fg:
363
-
364
- - **Running** — bg `#1f3a26`, fg `#58d68d`. Live session.
365
- - **Retrying** — bg `#3a2f1f`, fg `#f0c060`. Backoff queue.
366
- - **Idle** — bg `#20242c`, fg `#9aa4b2`. On disk, not active.
367
-
368
- No fourth pill exists. If a new orchestrator state appears, the new pill
369
- gets a named bg + fg pair entered into the colors block and a row added in
370
- this section. Do not reuse an existing pair for a new meaning.
371
-
372
- ### Tables
373
-
374
- The dominant content shape. Three tables on the page: Active sessions, Retry
375
- queue, All known issues.
376
-
377
- - `width: 100%`, `border-collapse: collapse`.
378
- - `th, td` padding `0.4rem 0.6rem`, `border-bottom: 1px solid` Soft Rule
379
- (`#1c2029`).
380
- - `th` text-align left, color Muted, weight 500.
381
- - Every cell inherits `font-variant-numeric: tabular-nums`.
382
- - No striping. The Soft Rule divider is enough.
383
-
384
- ### Inline status messages
385
-
386
- The form's `.msg` line is `grid-column: 1 / span 2; min-height: 1.2em`. It
387
- holds three states: neutral (color Muted), error (`#ff7676`), ok (`#58d68d`).
388
- Always inline, never a toast, never a modal.
588
+ A Dim, tabular `X in · Y out · Z total · runtime` strip pinned to the bottom,
589
+ hidden entirely when there's nothing to total. Numbers only, no chrome.
389
590
 
390
591
  ### Named Rules
391
592
 
392
- **The One-Card Rule.** The create-issue form is the only card-shaped
393
- surface. Other sections are unbordered blocks separated by section titles
394
- and divider rules. Resist adding cards to wrap tables or stats.
395
-
396
- **The Inline-Feedback Rule.** Success and error feedback land in the same
397
- inline `.msg` slot where the form sits. No toast notifications. No modal
398
- confirmations. The user already saw the issue land in the table below.
399
-
400
- **The Sunken-Input Rule.** Inputs are visually deeper than the surface they
401
- sit on (Inset against Raised Panel). They must not appear to float; if a
402
- new input style is introduced, it follows this depth rule.
593
+ **The One-Card Rule.** The slide-in new-issue panel is the only card-shaped
594
+ surface in the system. Everything else stands on the bench directly: columns are
595
+ unbordered, rows are flat dividers, the health line and ticker are bare strips.
596
+ Resist wrapping rows, columns, or stats in cards. (Originally the create-issue
597
+ *form card* was the one card; it became this slide-in panel, but the "exactly
598
+ one card" discipline holds.)
599
+
600
+ **The Non-Modal Composer Rule** (replaces the blanket "no modals / inline-first"
601
+ absolute). New-issue composition is a slide-in `aside`, not a modal: no backdrop
602
+ blocks the board, no scroll lock, Esc closes, and the board keeps polling and
603
+ responding behind it. The brand's anti-modal intent never a modal-on-modal
604
+ ITSM flow, never a blocking confirmation dialog — is preserved; a non-blocking
605
+ edge panel is the sanctioned exception, and it is the *only* one. Operator
606
+ feedback (steering replies, triage, errors) still lands **inline** on the row or
607
+ in the panel's own status line, never in a toast.
608
+
609
+ **The Sunken-Input Rule.** Inputs are visually deeper than the surface they sit
610
+ on (Inset against the Raised Panel). They must not appear to float; any new
611
+ input style follows this depth rule.
403
612
 
404
613
  ## 6. Do's and Don'ts
405
614
 
406
615
  ### Do:
407
- - **Do** stack neutrals in four steps (Inset → Bench → Raised Panel → Chip)
408
- to convey depth. No shadows.
409
- - **Do** keep Dispatch Blue (`#2a6df4`) on exactly one element per page: the
410
- primary verb. Everywhere else, status colors and text weight do the work.
411
- - **Do** apply `font-variant-numeric: tabular-nums` to every cell holding a
412
- number. Token counts and attempt numbers must align across rows.
413
- - **Do** name new colors semantically (`surface-chip`, `retrying-fg`), not
414
- by hue (`grey-3`, `green-400`). The token name is the contract.
415
- - **Do** put feedback inline in the `.msg` slot. Errors and successes never
416
- leave their grid cell.
417
- - **Do** keep the type scale tight: h1 1.3rem, h2 1rem (same as body),
418
- body 14px, pill 0.85em. Hierarchy through rules and weight, not scale.
419
- - **Do** keep section titles (h2) the same size as body, separated by a
420
- Firm Rule underline and 2rem of top margin.
616
+ - **Do** stack neutrals in four steps (Inset → Bench → Raised Panel → Chip) to
617
+ convey depth. No shadows.
618
+ - **Do** keep Dispatch Blue (`#2a6df4`) as the single saturated brand hue: the
619
+ primary verb and the focus ring. Everywhere else, status pairs and text weight
620
+ do the work.
621
+ - **Do** give every status colour a named bg + fg pair tied to a real
622
+ orchestrator state (running, retrying, idle, await, done). A new state earns a
623
+ new pair; colours are never reused for a second meaning.
624
+ - **Do** apply `font-variant-numeric: tabular-nums` to every element holding a
625
+ number token counts, attempts, timestamps, counts. They must align.
626
+ - **Do** use the monospace stack for literal strings (identifiers, paths, the
627
+ health line, code) and the sans stack for prose.
628
+ - **Do** put feedback inline on the awaiting row, or in the panel's `np-msg`
629
+ slot. Errors and successes never become toasts.
630
+ - **Do** keep the type scale tight: detail h1 1.3rem, h2 1rem (same as body),
631
+ body 14px, pill 0.78em. Hierarchy through rules and weight, not scale.
632
+ - **Do** let the board omit terminal columns and tint awaiting/stuck rows — show
633
+ real state, surface what needs eyes, and let finished work fall away.
421
634
 
422
635
  ### Don't:
423
- - **Don't** add a fourth saturated accent. There is one verb color. Status
424
- pills carry the only other saturation in the system, and they encode three
425
- named states. A fourth pill is a smell.
636
+ - **Don't** add a second saturated accent. There is one brand hue (Dispatch
637
+ Blue); status pairs carry the only other saturation, and each encodes a named
638
+ state.
639
+ - **Don't** invent a status colour for taste, or reuse an existing pair for a
640
+ new meaning. Name the state, add a bg + fg pair, then use it.
426
641
  - **Don't** ship a sidebar nav, breadcrumb trail, or a "Welcome back" hero.
427
- PRODUCT.md flags this as generic-SaaS-dashboard chrome; the workshop has
428
- one room. Add a tab strip only when a second page genuinely exists.
642
+ PRODUCT.md flags this as generic-SaaS-dashboard chrome; the workshop has one
643
+ room. (The detail page's `symphony · workflow · id` crumb is navigation back
644
+ to the board, not a nav chrome shelf.)
429
645
  - **Don't** use big-number-with-tiny-label metric tiles. The hero-metric
430
- template is explicitly prohibited.
431
- - **Don't** add violet gradients, glassmorphic blur, sparkle / star icons,
432
- or any "AI inside" iconography. smol-symphony runs AI agents but is not an
433
- AI product. (PRODUCT.md anti-reference, verbatim.)
434
- - **Don't** add Jira-style status pills whose colors do not map to distinct
435
- orchestrator state. Three pills, three states, three bg + fg pairs. That
436
- is the entire vocabulary.
437
- - **Don't** introduce `box-shadow`. Depth is tonal. If you reach for a
438
- shadow, add a fifth neutral step instead.
439
- - **Don't** use `border-left` greater than `1px` as a colored stripe on
440
- rows, cards, or alerts (carries impeccable's absolute ban).
441
- - **Don't** use `#000` or `#fff` as text or background. The single existing
442
- `color: white` on the primary button is a known soft spot to tint toward
443
- the brand hue (target ≈`oklch(98% 0.005 260)`) on the next polish pass.
444
- - **Don't** introduce a web font. The console reads like the OS;
445
- `ui-sans-serif, system-ui, sans-serif` is non-negotiable unless an
446
- intentional editorial change is being made.
447
- - **Don't** add modals. Inline-first, always. PRODUCT.md flags
448
- modal-on-modal flows as Jira-ITSM heaviness.
449
- - **Don't** animate layout properties or add motion that doesn't encode
450
- state. The 2-second polling refresh is the only "motion" the system has,
451
- and that is informational, not decorative.
452
- - **Don't** wrap content in a generic container. The form is the one card.
453
- Tables and stat lines stand on the bench directly.
646
+ template is explicitly prohibited; the totals footer is a plain text strip,
647
+ not a tile.
648
+ - **Don't** add violet gradients, glassmorphic blur, sparkle / star icons, or
649
+ any "AI inside" iconography. smol-symphony runs AI agents but is not an AI
650
+ product. (PRODUCT.md anti-reference, verbatim.)
651
+ - **Don't** add status pills whose colours don't map to distinct orchestrator
652
+ state. Five pairs, five real states. Status theater is banned.
653
+ - **Don't** introduce `box-shadow`. Depth is tonal, plus the one panel that
654
+ slides in. If you reach for a shadow, add a fifth neutral step instead.
655
+ - **Don't** use a `border-left` wider than `1px` as a *saturated status stripe*
656
+ on rows, cards, or alerts (the Linear/ITSM stripe pattern — impeccable's
657
+ absolute ban). Row status is a faint full-row tint, not a stripe. (Rendered
658
+ Markdown blockquotes use a 2px **neutral** Firm-Rule bar; that's typographic
659
+ quoting, not a status stripe.)
660
+ - **Don't** use `#000` or `#fff` as text or background. The create button's
661
+ label is `#f4f6fb`, tinted toward the brand hue.
662
+ - **Don't** introduce a web font. Both native stacks
663
+ (`ui-sans-serif, system-ui, sans-serif` and
664
+ `ui-monospace, …, monospace`) are non-negotiable unless an intentional
665
+ editorial change is being made.
666
+ - **Don't** add a modal. The non-modal slide-in composer is the one sanctioned
667
+ edge panel; everything else is inline. No blocking dialogs, no modal-on-modal
668
+ ITSM flows, no scroll-locking backdrops.
669
+ - **Don't** add motion that doesn't encode state. The panel slide, the ticker
670
+ collapse, hover affordances, and the 2s poll all mean something; decorative
671
+ animation does not belong.
672
+ - **Don't** wrap content in a generic container. The slide-in panel is the one
673
+ card. Columns, rows, the health line, and the totals strip stand on the bench
674
+ directly.