specrails-desktop 2.26.0 → 2.26.2

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 (91) hide show
  1. package/client/dist/assets/{ActivityFeedPage-1eXnoNAo.js → ActivityFeedPage-bDe4VitF.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-BHW_6Vm0.js → AgentBrowserCapture-DQyeEm2V.js} +1 -1
  3. package/client/dist/assets/{AgentModeCodePane-C89SAJzI.js → AgentModeCodePane-Ci39DcOo.js} +2 -2
  4. package/client/dist/assets/{AgentModeJobsPane-BG_4r4Dm.js → AgentModeJobsPane-C-g1WNVG.js} +1 -1
  5. package/client/dist/assets/{AgentsPage-BrkWfc9s.js → AgentsPage-BWgglK7V.js} +3 -3
  6. package/client/dist/assets/{AnalyticsPage-CMZRUPrT.js → AnalyticsPage-Da21wWWw.js} +1 -1
  7. package/client/dist/assets/{BarChart-BtF3zJHa.js → BarChart-BiLTq3uC.js} +1 -1
  8. package/client/dist/assets/{CodePage-Cime5FRs.js → CodePage-BUcAp7SC.js} +1 -1
  9. package/client/dist/assets/{DesktopAnalyticsPage-CSKHSBBF.js → DesktopAnalyticsPage-COkzgMmd.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-ClTq6Fzj.js → DocsDialog-PZHMN18B.js} +1 -1
  11. package/client/dist/assets/{DocsPage-DTxfOpTu.js → DocsPage-D3fChfkj.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-Ho9zdPvK.js → ExportDropdown-DDhJH-Jr.js} +1 -1
  13. package/client/dist/assets/{IntegrationsPage-DWNyo6jk.js → IntegrationsPage-F87uxxVH.js} +1 -1
  14. package/client/dist/assets/{InteractiveJobComposer-CYxXOKld.js → InteractiveJobComposer-DqDCiCa0.js} +5 -5
  15. package/client/dist/assets/{JobDetailModal-DrNafHY5.js → JobDetailModal-BWJR60fi.js} +1 -1
  16. package/client/dist/assets/{JobDetailPage-4VKw33GW.js → JobDetailPage-CjWE_jyd.js} +1 -1
  17. package/client/dist/assets/{JobsPage-cFr6wWaH.js → JobsPage-LmnXkyCa.js} +1 -1
  18. package/client/dist/assets/{LoopBuilderPage-Bk8zVvEX.js → LoopBuilderPage-DsLLrtHa.js} +2 -2
  19. package/client/dist/assets/{LoopPreviewModal-XFSO2Vcs.js → LoopPreviewModal-C-fDEgnu.js} +1 -1
  20. package/client/dist/assets/{LoopsPage-DWMwDj9g.js → LoopsPage-DDNO9h9T.js} +1 -1
  21. package/client/dist/assets/{ProjectSettingsDialog-DnSe_yWm.js → ProjectSettingsDialog-CX854C8e.js} +1 -1
  22. package/client/dist/assets/{TemplatePreviewModal-B30-TY_v.js → TemplatePreviewModal-DcHFbLA8.js} +1 -1
  23. package/client/dist/assets/agent-BI6vr-To.js +1 -0
  24. package/client/dist/assets/{agent-D5QNn9np2.js → agent-BUGXI3Ok2.js} +1 -1
  25. package/client/dist/assets/agent-Bph_-xt7.js +1 -0
  26. package/client/dist/assets/agent-Bvy2Csyd.js +1 -0
  27. package/client/dist/assets/{agent-DgFoLgrF2.js → agent-Bwkyg2oX2.js} +1 -1
  28. package/client/dist/assets/agent-CLCyDaZh.js +1 -0
  29. package/client/dist/assets/{agent-6mhkV25_2.js → agent-Cqs14QMr2.js} +1 -1
  30. package/client/dist/assets/{agent-CRYdi9-t.js → agent-DJ0EBUuP.js} +1 -1
  31. package/client/dist/assets/{brain-DcXkQwzO.js → brain-BBNUUrUI.js} +1 -1
  32. package/client/dist/assets/dashboard-BjjS1OrL.js +1 -0
  33. package/client/dist/assets/dashboard-Bk6oSk1H2.js +1 -0
  34. package/client/dist/assets/dashboard-BrH-GdOg2.js +1 -0
  35. package/client/dist/assets/dashboard-BurhUI8_.js +1 -0
  36. package/client/dist/assets/dashboard-C6w0rICe.js +1 -0
  37. package/client/dist/assets/dashboard-CAUnfDQL2.js +1 -0
  38. package/client/dist/assets/dashboard-C_ljpeYO.js +1 -0
  39. package/client/dist/assets/dashboard-D6OqDdj6.js +1 -0
  40. package/client/dist/assets/{dist-js-BAWwLSIj.js → dist-js-BxjTIvdT.js} +1 -1
  41. package/client/dist/assets/{dist-js-6U6hyD6z.js → dist-js-DdP_Gl3p.js} +1 -1
  42. package/client/dist/assets/{index-cvOunQkD.css → index-C695aPdV.css} +1 -1
  43. package/client/dist/assets/index-Dw1N2-ci.js +156 -0
  44. package/client/dist/assets/{lib-Bsny0zZp.js → lib-BviHZ_gK.js} +1 -1
  45. package/client/dist/assets/{settings-DHd1lQ1k.js → settings-BHdPchJP.js} +1 -1
  46. package/client/dist/assets/{settings-FJXpJ3k92.js → settings-BeCczBFS2.js} +1 -1
  47. package/client/dist/assets/{settings-BWIf3vv4.js → settings-BlLi719f.js} +1 -1
  48. package/client/dist/assets/{settings-DTYQ5COE.js → settings-C_nY14Yz.js} +1 -1
  49. package/client/dist/assets/{settings-DE8t6aYv.js → settings-CaZ26a0S.js} +1 -1
  50. package/client/dist/assets/{settings-Dgv_q4qf2.js → settings-D54_cgl12.js} +1 -1
  51. package/client/dist/assets/{settings-DXgPh9-k.js → settings-D9bDDe2O.js} +1 -1
  52. package/client/dist/assets/{settings-BhuXe7ek2.js → settings-cn0tbve62.js} +1 -1
  53. package/client/dist/assets/{setup-iY8vH8pB.js → setup-7J8TjG4O.js} +1 -1
  54. package/client/dist/assets/{setup-D674kWQH.js → setup-BXpw07r-.js} +1 -1
  55. package/client/dist/assets/{setup-B590KsTX.js → setup-BiduSX9d.js} +1 -1
  56. package/client/dist/assets/{setup-cnqZsQSX2.js → setup-BuQNKztt2.js} +1 -1
  57. package/client/dist/assets/{setup-CEj6uY3-2.js → setup-BwMovM9z2.js} +1 -1
  58. package/client/dist/assets/{setup-BkyGoby6.js → setup-D84y3wx-.js} +1 -1
  59. package/client/dist/assets/{setup-CbIerFGf2.js → setup-DU0ULX8q2.js} +1 -1
  60. package/client/dist/assets/{setup-VNqXyB6Y.js → setup-RiUHzq7E.js} +1 -1
  61. package/client/dist/assets/{upload-49vX83IZ.js → upload-CRuuzzvp.js} +1 -1
  62. package/client/dist/assets/{value-Bo8EiTZE.js → value-CIr5hDMt.js} +1 -1
  63. package/client/dist/index.html +21 -8
  64. package/package.json +1 -1
  65. package/server/dist/agent-operator-prompt.js +45 -5
  66. package/server/dist/desktop-router.js +9 -2
  67. package/server/dist/mcp/guide.js +33 -4
  68. package/server/dist/mcp/tools/app-settings.js +18 -2
  69. package/server/dist/mcp/tools/catalog.js +2 -0
  70. package/server/dist/mcp/tools/rails.js +27 -6
  71. package/server/dist/mcp/tools/support.js +324 -0
  72. package/server/dist/pr-publisher.js +17 -15
  73. package/server/dist/project-git.js +57 -0
  74. package/server/dist/rail-isolated-launch.js +27 -3
  75. package/server/dist/rail-pr-decision.js +69 -1
  76. package/server/dist/rail-worktree-release.js +25 -0
  77. package/server/dist/rail-worktrees-store.js +2 -2
  78. package/server/dist/rails-router.js +136 -7
  79. package/client/dist/assets/agent-BZqfQhI_.js +0 -1
  80. package/client/dist/assets/agent-Bjy1SlT5.js +0 -1
  81. package/client/dist/assets/agent-Bn6ZmjHj.js +0 -1
  82. package/client/dist/assets/agent-DgwPsxWQ.js +0 -1
  83. package/client/dist/assets/dashboard-CuGuy2__2.js +0 -1
  84. package/client/dist/assets/dashboard-CxP3TZ3I2.js +0 -1
  85. package/client/dist/assets/dashboard-DWHVfP7U.js +0 -1
  86. package/client/dist/assets/dashboard-D_Pgs6QH2.js +0 -1
  87. package/client/dist/assets/dashboard-Dwnssayh.js +0 -1
  88. package/client/dist/assets/dashboard-S_pT1yxN.js +0 -1
  89. package/client/dist/assets/dashboard-tCxOFOxx.js +0 -1
  90. package/client/dist/assets/dashboard-zefbBNyG.js +0 -1
  91. package/client/dist/assets/index-DnkfABxY.js +0 -156
@@ -47,8 +47,14 @@
47
47
  } catch (_) { /* storage unavailable — nothing to migrate */ }
48
48
  try {
49
49
  var k = 'specrails-desktop:ui-theme'
50
- var allowed = ['dracula', 'aurora-light', 'obsidian-dark', 'matrix', 'specrails']
50
+ var legacyThemeIds = { 'star-wars': 'galaxy', matrix: 'code-rain' }
51
+ var allowed = ['dracula', 'aurora-light', 'obsidian-dark', 'code-rain', 'specrails', 'galaxy']
51
52
  var v = localStorage.getItem(k)
53
+ var migrated = legacyThemeIds[v]
54
+ if (migrated) {
55
+ v = migrated
56
+ localStorage.setItem(k, migrated)
57
+ }
52
58
  document.documentElement.dataset.theme =
53
59
  v && allowed.indexOf(v) !== -1 ? v : 'specrails'
54
60
  } catch (_) {
@@ -85,7 +91,7 @@
85
91
  --splash-secondary: #f48fc7;
86
92
  --splash-muted: #8b8fa3;
87
93
  }
88
- html[data-theme="matrix"] {
94
+ html[data-theme="code-rain"] {
89
95
  --splash-bg: #08120e;
90
96
  --splash-fg: #b8ffd9;
91
97
  --splash-primary: #00ff66;
@@ -99,6 +105,13 @@
99
105
  --splash-secondary: #9a6fce;
100
106
  --splash-muted: #6b6f8c;
101
107
  }
108
+ html[data-theme="galaxy"] {
109
+ --splash-bg: hsl(220 20% 4%);
110
+ --splash-fg: hsl(210 30% 94%);
111
+ --splash-primary: hsl(212 100% 62%);
112
+ --splash-secondary: hsl(210 12% 65%);
113
+ --splash-muted: hsl(215 15% 60%);
114
+ }
102
115
  html, body {
103
116
  margin: 0;
104
117
  background: var(--splash-bg);
@@ -191,14 +204,14 @@
191
204
  .specrails-splash__mark .pill { opacity: 1; }
192
205
  }
193
206
  </style>
194
- <script type="module" crossorigin src="/assets/index-DnkfABxY.js"></script>
207
+ <script type="module" crossorigin src="/assets/index-Dw1N2-ci.js"></script>
195
208
  <link rel="modulepreload" crossorigin href="/assets/chunk-BNv3lrIs.js">
196
209
  <link rel="modulepreload" crossorigin href="/assets/preload-helper-DSXbuxSR.js">
197
210
  <link rel="modulepreload" crossorigin href="/assets/clsx-CNmW0Nip.js">
198
211
  <link rel="modulepreload" crossorigin href="/assets/auth-Cvsb7-Ek.js">
199
212
  <link rel="modulepreload" crossorigin href="/assets/activity-D8vQJrpW.js">
200
213
  <link rel="modulepreload" crossorigin href="/assets/addspec-DxW6oPsk.js">
201
- <link rel="modulepreload" crossorigin href="/assets/agent-Bn6ZmjHj.js">
214
+ <link rel="modulepreload" crossorigin href="/assets/agent-CLCyDaZh.js">
202
215
  <link rel="modulepreload" crossorigin href="/assets/agents-Cev2oFfX.js">
203
216
  <link rel="modulepreload" crossorigin href="/assets/agentstudio-CZm2rOzt.js">
204
217
  <link rel="modulepreload" crossorigin href="/assets/aiedit-DYtnZU9H.js">
@@ -209,7 +222,7 @@
209
222
  <link rel="modulepreload" crossorigin href="/assets/code-thSEptMx.js">
210
223
  <link rel="modulepreload" crossorigin href="/assets/commands-CckaBWdV.js">
211
224
  <link rel="modulepreload" crossorigin href="/assets/common-CYaT2yHp.js">
212
- <link rel="modulepreload" crossorigin href="/assets/dashboard-Dwnssayh.js">
225
+ <link rel="modulepreload" crossorigin href="/assets/dashboard-BurhUI8_.js">
213
226
  <link rel="modulepreload" crossorigin href="/assets/explore-BKVV5JSu.js">
214
227
  <link rel="modulepreload" crossorigin href="/assets/integrations-CuWnMR5L.js">
215
228
  <link rel="modulepreload" crossorigin href="/assets/jira-B3613-gj.js">
@@ -217,12 +230,12 @@
217
230
  <link rel="modulepreload" crossorigin href="/assets/loops-Brnx4Fji.js">
218
231
  <link rel="modulepreload" crossorigin href="/assets/mcp-CIH0pH1K.js">
219
232
  <link rel="modulepreload" crossorigin href="/assets/nav-CzKzuwH8.js">
220
- <link rel="modulepreload" crossorigin href="/assets/settings-DXgPh9-k.js">
221
- <link rel="modulepreload" crossorigin href="/assets/setup-VNqXyB6Y.js">
233
+ <link rel="modulepreload" crossorigin href="/assets/settings-D9bDDe2O.js">
234
+ <link rel="modulepreload" crossorigin href="/assets/setup-RiUHzq7E.js">
222
235
  <link rel="modulepreload" crossorigin href="/assets/specs-CmEZY57v.js">
223
236
  <link rel="modulepreload" crossorigin href="/assets/terminal-DFPNRLKp.js">
224
237
  <link rel="modulepreload" crossorigin href="/assets/tickets-CkfbISuK.js">
225
- <link rel="stylesheet" crossorigin href="/assets/index-cvOunQkD.css">
238
+ <link rel="stylesheet" crossorigin href="/assets/index-C695aPdV.css">
226
239
  </head>
227
240
  <body>
228
241
  <div id="specrails-splash" aria-hidden="false" role="status" aria-label="Loading Specrails">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.26.0",
3
+ "version": "2.26.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -118,6 +118,39 @@ tool for exactly that, and never refuse or push this back to the user's terminal
118
118
  (\`rm -rf\`, force-push, branch deletion, history rewrite) to "make something
119
119
  work" — surface the blocker instead.
120
120
 
121
+ ## Support & troubleshooting
122
+
123
+ When the user asks for help installing or using Specrails, asks "how do I...?",
124
+ reports a broken setup, provider CLI/auth issue, MCP/Agent Chat issue, failed
125
+ job, missing agents/skills/slash commands, PR-delivery confusion, costs, or
126
+ plugin trouble, this is SUPPORT — not backlog work. Call
127
+ \`specrails_support(action:'triage', question, projectId?)\` first and answer
128
+ from its diagnostics/playbook. Do NOT create or propose a spec unless the user
129
+ explicitly pivots from troubleshooting to product work.
130
+
131
+ For job failures that mention missing agents, missing skills, missing
132
+ \`/specrails:*\` commands, \`/opsx:*\` commands, "baseline agents", or a
133
+ command not installed, explain the storage model correctly: upstream agents,
134
+ skills and slash commands live in the app-global specrails-core framework, NOT
135
+ inside the selected project. Give the user the repair path:
136
+
137
+ - If global core may be stale, offer
138
+ \`specrails_support(action:'core_update_check')\`, then with confirmation
139
+ \`specrails_support(action:'core_update_apply')\`.
140
+ - If global core is current and there is no concrete failing job/error, say the
141
+ core installation looks healthy. Do NOT infer a MyProject problem from
142
+ \`setup/checkpoints\`.
143
+ - A setup checkpoint summary showing pending checkpoints, 0 agents, or
144
+ 0 \`specrails:*\` commands is NOT a specrails-core health signal and is NOT a
145
+ reason to run \`specrails_setup(install)\`. Do not say they "live in
146
+ MyProject", and do not claim MyProject's projection is broken from that alone.
147
+ - If a real job still reports missing core definitions while global core is
148
+ current, ask for/read \`specrails_jobs(get)\` or \`specrails_jobs(diagnostic)\`
149
+ and report it as a framework-loading/job diagnostic issue, not a project setup
150
+ checkpoint issue.
151
+ - Manual fallback for standalone/legacy core installs: from the project root run
152
+ \`npx specrails-core@latest update\`, then retry the job.
153
+
121
154
  ## Permission ladder & confirmation rules
122
155
 
123
156
  Your actions are gated by a cumulative level the user steers live with
@@ -147,7 +180,9 @@ above the current level, say so up front.
147
180
 
148
181
  ## Think in specs (default stance)
149
182
 
150
- When the user describes work to be done, the natural unit is a SPEC:
183
+ When the user describes product/code work to be done, the natural unit is a
184
+ SPEC. This default does NOT apply to support/troubleshooting questions; route
185
+ those through \`specrails_support\` instead.
151
186
 
152
187
  1. Check the backlog for duplicates first: \`specrails_specs(list)\`.
153
188
  2. Capture the work as a spec — pick the right creation path (next section).
@@ -337,6 +372,11 @@ summary later.
337
372
  is explicit, covering Jira projects whose Review status has not been mapped to
338
373
  Specrails \`on_review\`. You do NOT need to know or pass the branch name. If
339
374
  there is no confident open PR match, the normal new-work flow is preserved.
375
+ - A published PR card (\`decision:'pr_ready'\`) is STILL an open PR continuation
376
+ target, not a blocker. If the user asks for modifications on that PR, assign
377
+ the same spec(s) to a rail and launch again; do NOT tell them they must publish,
378
+ discard, or merge first. \`pr_decision_pending\` should only block unresolved
379
+ deliveries with no continuable PR head yet.
340
380
  - Launch proposal shape: tickets (ids + titles), rail number, mode, engine and
341
381
  model/profile, plus "runs for minutes and costs money". If the API mode is
342
382
  \`freestyle\`, write "Freestyle" to the user. Wait for yes.
@@ -350,9 +390,9 @@ summary later.
350
390
  tickets to the returned \`railIndex\`, and launch. Never wedge waiting for a
351
391
  slot and never steal a configured rail's tickets to free one.
352
392
  - **Launching many rails**: \`specrails_rails(launch_all)\` launches every rail
353
- that has tickets and no active run / pending PR decision in ONE call, each
354
- with its own stored mode/engine/profile, and returns per-rail outcomes
355
- (launched / skipped with reason / failed) — report them per rail. It is
393
+ that has tickets and no active run / uncontinuable pending PR decision in ONE
394
+ call, each with its own stored mode/engine/profile, and returns per-rail
395
+ outcomes (launched / skipped with reason / failed) — report them per rail. It is
356
396
  ai-spawn: propose it with the total rail + spec count and cost framing
357
397
  first. One yes covers the whole batch when the user asked for the batch
358
398
  ("launch everything") — do not re-ask per rail.
@@ -431,4 +471,4 @@ When the user has no projects yet, or asks to add one:
431
471
  Setup is QUICK-only (fast, offline). Do NOT offer, mention, or attempt a "full"
432
472
  or "enrich" install — that flow is deprecated and not available through you.
433
473
  `;
434
- exports.OPERATOR_SYSTEM_PROMPT = `You are the Specrails operator agent, embedded inside the Specrails Desktop app; drive it on the user's behalf using the specrails_* MCP tools. Your full operator manual is the CLAUDE.md auto-loaded from your working directory — follow it. Non-negotiables: target a project with specrails_select_project or the projectId argument, and ask rather than guess when none is pinned; short async 202 ops (spec generation, ai-edit) may be awaited with specrails_watch (projectId required), but after a rail/job LAUNCH is accepted end your reply immediately — progress streams live in the conversation's run card and the app; watch a launched run only when the user explicitly asks you to wait, with a bounded untilMs; never claim success from a 202 acceptance alone — verify from a terminal event or a domain read (e.g. specrails_jobs get); call the free-form autonomous rail mode Freestyle in prose — freestyle/factory:freestyle/{{cmd:freestyle}} are canonical API/id/token values for that same capability; respect the cumulative permission ladder observe / edit / operate / autonomous — if a tool is refused, name the level the user must Shift+Tab to, never work around it; when refining a spec, ground it in the real codebase FIRST (specrails_code tree/read_file/summary + specrails_specs list) and show the evolving draft as one fenced spec-draft JSON block (title, description, labels, priority, acceptanceCriteria) at the end of each turn that changed it — the app renders it as a live card; persist the refined spec with specrails_specs commit_draft (no conversationId) — never route it through create/generate, which regenerate the content with AI; commit_draft appends a Contract Layer by default via one short background AI pass — pass contractRefine false when the user declines it; report tool outputs faithfully, including failures. Format replies for easy reading: short paragraphs separated by blank lines, bullet lists for enumerations. When asking the user to pick between concrete choices, end the reply with a fenced options code block containing a JSON array of the 2-6 choice labels (rendered as clickable chips); never use that block otherwise.`;
474
+ exports.OPERATOR_SYSTEM_PROMPT = `You are the Specrails operator agent, embedded inside the Specrails Desktop app; drive it on the user's behalf using the specrails_* MCP tools. Your full operator manual is the CLAUDE.md auto-loaded from your working directory — follow it. Non-negotiables: target a project with specrails_select_project or the projectId argument, and ask rather than guess when none is pinned; support/troubleshooting/install/usage/job-failure questions use specrails_support first and never become specs unless the user pivots to product work; missing agents/skills/slash commands concern the app-global specrails-core framework, never project-owned files — never say agents/skills/commands live inside the project; project setup checkpoints are not a core health signal, so pending/0 agents/0 commands must not trigger specrails_setup(install) or a project repair recommendation; offer core_update_check/core_update_apply only for global core updates, and if global core is current ask for the concrete job error/diagnostic; an on_review spec with an open PR, including a published pr_ready card, can be relaunched to continue that PR branch — do not require publish/discard/merge first; short async 202 ops (spec generation, ai-edit) may be awaited with specrails_watch (projectId required), but after a rail/job LAUNCH is accepted end your reply immediately — progress streams live in the conversation's run card and the app; watch a launched run only when the user explicitly asks you to wait, with a bounded untilMs; never claim success from a 202 acceptance alone — verify from a terminal event or a domain read (e.g. specrails_jobs get); call the free-form autonomous rail mode Freestyle in prose — freestyle/factory:freestyle/{{cmd:freestyle}} are canonical API/id/token values for that same capability; respect the cumulative permission ladder observe / edit / operate / autonomous — if a tool is refused, name the level the user must Shift+Tab to, never work around it; when refining a spec, ground it in the real codebase FIRST (specrails_code tree/read_file/summary + specrails_specs list) and show the evolving draft as one fenced spec-draft JSON block (title, description, labels, priority, acceptanceCriteria) at the end of each turn that changed it — the app renders it as a live card; persist the refined spec with specrails_specs commit_draft (no conversationId) — never route it through create/generate, which regenerate the content with AI; commit_draft appends a Contract Layer by default via one short background AI pass — pass contractRefine false when the user declines it; report tool outputs faithfully, including failures. Format replies for easy reading: short paragraphs separated by blank lines, bullet lists for enumerations. When asking the user to pick between concrete choices, end the reply with a fenced options code block containing a JSON array of the 2-6 choice labels (rendered as clickable chips); never use that block otherwise.`;
@@ -64,7 +64,11 @@ function isGeminiBetaDisabled() {
64
64
  }
65
65
  // Theme allow-list. Mirror of THEME_IDS in `client/src/lib/themes.ts` —
66
66
  // kept duplicated to avoid pulling client code into the server bundle.
67
- const THEME_ID_ALLOWLIST = new Set(['dracula', 'aurora-light', 'obsidian-dark', 'matrix', 'specrails']);
67
+ const THEME_ID_ALLOWLIST = new Set(['dracula', 'aurora-light', 'obsidian-dark', 'code-rain', 'specrails', 'galaxy']);
68
+ const LEGACY_THEME_ID_MAP = {
69
+ 'star-wars': 'galaxy',
70
+ matrix: 'code-rain',
71
+ };
68
72
  // Language allow-list. Mirror of LANGUAGE_IDS in `client/src/lib/i18n.ts` —
69
73
  // kept duplicated to avoid pulling client code into the server bundle.
70
74
  const LANGUAGE_ID_ALLOWLIST = new Set(['en', 'es', 'fr', 'de', 'pt', 'it', 'zh', 'ja']);
@@ -840,7 +844,10 @@ function createDesktopRouter(registry, broadcast) {
840
844
  // Persisted under desktop_settings key `ui_theme`. Default seeded by migration 8.
841
845
  router.get('/theme', (_req, res) => {
842
846
  const stored = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'ui_theme');
843
- const theme = stored && THEME_ID_ALLOWLIST.has(stored) ? stored : 'specrails';
847
+ const migrated = stored ? LEGACY_THEME_ID_MAP[stored] : undefined;
848
+ const theme = migrated ?? (stored && THEME_ID_ALLOWLIST.has(stored) ? stored : 'specrails');
849
+ if (migrated)
850
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'ui_theme', migrated);
844
851
  res.json({ theme });
845
852
  });
846
853
  router.patch('/theme', (req, res) => {
@@ -53,6 +53,13 @@ Codex, Gemini) to implement specs.
53
53
 
54
54
  ## Creating specs — three paths, one decision rule
55
55
 
56
+ Before creating a spec, classify the user's intent. If they are asking for
57
+ help using Specrails, installation/setup, a failed job, missing agents/skills,
58
+ MCP connection, provider auth, GitHub/PR delivery, costs, or "how do I...?"
59
+ support, DO NOT create a spec. Call \`specrails_support(action:'triage')\`
60
+ and solve it conversationally first. Only move to spec creation when the user
61
+ explicitly asks for product/backlog work.
62
+
56
63
  1. **Quick (AI-generated)** — \`specrails_specs(action:'generate', idea, …)\`, or
57
64
  the simpler \`create\` with title/description: routes through the app's Quick
58
65
  Add Spec — an AI pass structures the request into a complete spec, so
@@ -105,15 +112,20 @@ heading inside the description); \`labels\`; \`priority\`. Spec content is Engli
105
112
  other work, create one and proceed; never wait for a slot.
106
113
  - Parallel launches are safe and normal: each launch isolates its work in
107
114
  per-ticket git worktrees, so several rails can run at once. \`launch_all\`
108
- (ai-spawn) launches EVERY rail that has tickets and no active run / pending
109
- PR decision in one call, each with its stored mode/engine/profile, returning
110
- per-rail outcomes (launched / skipped with reason / failed).
115
+ (ai-spawn) launches EVERY rail that has tickets and no active run /
116
+ uncontinuable pending PR decision in one call, each with its stored
117
+ mode/engine/profile, returning per-rail outcomes (launched / skipped with
118
+ reason / failed).
111
119
  - Relaunching an \`on_review\` ticket with a matching OPEN GitHub PR continues
112
120
  that PR's head branch automatically. Jira-linked \`in_progress\` tickets can
113
121
  also continue an open PR when the match is explicit, covering Jira projects
114
122
  whose Review status has not been mapped to Specrails \`on_review\`. New
115
123
  tickets, or tickets without a confident open-PR match, keep the normal
116
124
  branch-from-integration flow.
125
+ - A published PR delivery (\`decision:'pr_ready'\`) is still an open PR
126
+ continuation target when it has a PR URL/head branch covering the rail's
127
+ tickets. Do not tell the user to Publish/Discard/Merge first when they ask for
128
+ more changes on that same PR; assign the spec(s) to a rail and launch again.
117
129
  - Projects without Git cannot use isolated worktrees or PR continuation. A
118
130
  launch degrades to shared-cwd execution and returns \`isolationUnavailable\`;
119
131
  explain that it writes directly to files and no PR card/branch will appear.
@@ -171,7 +183,10 @@ Tools cannot raise their own permissions in either regime. Common tiers:
171
183
  list/get/spending/watch = read; commit_draft, from_prompt, update, set_tickets,
172
184
  create_rail, plugin install, Jira connect = write; spec create/generate, rail
173
185
  launch/launch_all, chat send, job spawn = ai-spawn; spec delete, rail stop, job
174
- purge, plugin uninstall, Jira disconnect, project unregister = destructive. Note the
186
+ purge, plugin uninstall, Jira disconnect, project unregister = destructive.
187
+ \`specrails_support(triage/core_update_status/core_update_check)\` is read;
188
+ \`specrails_support(core_update_apply)\` is ai-spawn because it runs longer
189
+ global update work. Note the
175
190
  embedded spec-refinement happy path (investigate + commit_draft) needs only
176
191
  read + write — ai-spawn is required only to launch work or spawn a nested AI.
177
192
 
@@ -216,6 +231,20 @@ read + write — ai-spawn is required only to launch work or spawn a nested AI.
216
231
  \`install_config\` per provider → ONE \`install\` (provisions all chosen
217
232
  providers; quick-only, offline) → poll \`checkpoints\` until
218
233
  \`isInstalling === false\`.
234
+ - **Support** (\`specrails_support\`): use FIRST for installation/usage help,
235
+ provider CLI/auth, MCP/Agent Chat issues, missing agents/skills/commands,
236
+ failed jobs, PR delivery confusion, costs, and plugin questions. It returns a
237
+ support playbook plus local diagnostics and never creates specs. When a job
238
+ says agents, skills, or slash commands are missing, explain that upstream
239
+ definitions live in the APP-GLOBAL specrails-core framework, not inside the
240
+ project. For specrails-core installation questions, project setup checkpoints
241
+ are NOT a health signal: pending checkpoints or 0 agents/commands do not prove
242
+ a core problem and must not trigger \`specrails_setup(install)\`. If global
243
+ core is stale, offer \`core_update_check\` + \`core_update_apply\`. If global
244
+ core is current but a real job still reports missing core definitions, ask for
245
+ the job error/diagnostic and give the manual fallback for standalone/legacy
246
+ core installs: from the project root, run \`npx specrails-core@latest update\`,
247
+ then retry the job.
219
248
  - **Loops lifecycle** (\`specrails_loops\`): Draft → \`publish\` (graph-validated)
220
249
  → runnable; \`update\` reverts to Draft and returns 409 while running;
221
250
  \`preview\` dry-runs token resolution without spawning; constants are shared
@@ -8,8 +8,12 @@ const mcp_tiers_1 = require("../mcp-tiers");
8
8
  // permission-tier toggles are intentionally NOT writable here — an external LLM
9
9
  // must never be able to escalate its own permissions. Those are user-only,
10
10
  // changed in the Settings ▸ MCP panel.
11
- const WRITABLE_THEMES = ['specrails', 'dracula', 'aurora-light', 'obsidian-dark', 'matrix'];
11
+ const WRITABLE_THEMES = ['specrails', 'dracula', 'aurora-light', 'obsidian-dark', 'code-rain', 'galaxy'];
12
12
  const WRITABLE_LANGS = ['en', 'es', 'fr', 'de', 'pt', 'it', 'zh', 'ja'];
13
+ const LEGACY_THEME_ID_MAP = {
14
+ 'star-wars': 'galaxy',
15
+ matrix: 'code-rain',
16
+ };
13
17
  function appTools() {
14
18
  return [
15
19
  {
@@ -44,8 +48,9 @@ function appTools() {
44
48
  handler: (ctx, args) => {
45
49
  const db = ctx.desktopDb;
46
50
  if (args.action === 'get') {
51
+ const theme = normalizeStoredTheme(db);
47
52
  return {
48
- theme: (0, desktop_db_1.getDesktopSetting)(db, 'ui_theme') ?? 'specrails',
53
+ theme,
49
54
  language: (0, desktop_db_1.getDesktopSetting)(db, 'ui_language') ?? null,
50
55
  dailyBudgetUsd: num((0, desktop_db_1.getDesktopSetting)(db, 'desktop_daily_budget_usd')),
51
56
  costAlertThresholdUsd: num((0, desktop_db_1.getDesktopSetting)(db, 'cost_alert_threshold_usd')),
@@ -102,6 +107,17 @@ function appTools() {
102
107
  },
103
108
  ];
104
109
  }
110
+ function normalizeStoredTheme(db) {
111
+ const stored = (0, desktop_db_1.getDesktopSetting)(db, 'ui_theme');
112
+ if (!stored)
113
+ return 'specrails';
114
+ const migrated = LEGACY_THEME_ID_MAP[stored];
115
+ if (migrated) {
116
+ (0, desktop_db_1.setDesktopSetting)(db, 'ui_theme', migrated);
117
+ return migrated;
118
+ }
119
+ return WRITABLE_THEMES.includes(stored) ? stored : 'specrails';
120
+ }
105
121
  function num(v) {
106
122
  if (v === undefined)
107
123
  return null;
@@ -17,6 +17,7 @@ const code_1 = require("./code");
17
17
  const setup_1 = require("./setup");
18
18
  const analytics_1 = require("./analytics");
19
19
  const git_1 = require("./git");
20
+ const support_1 = require("./support");
20
21
  /**
21
22
  * The full MCP tool catalog. Domain-facade tools each expose an `action` enum
22
23
  * and call the app's REST API over loopback (see apiCall). Meta tools receive a
@@ -41,6 +42,7 @@ function buildToolSpecs() {
41
42
  specs.push(...(0, setup_1.setupTools)());
42
43
  specs.push(...(0, analytics_1.analyticsTools)());
43
44
  specs.push(...(0, git_1.gitTools)());
45
+ specs.push(...(0, support_1.supportTools)());
44
46
  // Meta last (needs the full list for search/describe)
45
47
  specs.push(...(0, meta_1.metaTools)(() => specs));
46
48
  return specs;
@@ -7,6 +7,26 @@ const types_1 = require("./types");
7
7
  * efforts outside it (codex 'minimal', claude 'xhigh') are never defaulted —
8
8
  * a default must not 400 a launch that would succeed without it. */
9
9
  const RAIL_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
10
+ function isRecord(value) {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+ function prDeliveryContinuesTickets(raw, ticketIds) {
14
+ if (!isRecord(raw))
15
+ return false;
16
+ const decision = raw.decision;
17
+ if (decision !== 'pr_draft' && decision !== 'pr_ready')
18
+ return false;
19
+ if (typeof raw.prUrl !== 'string' || !raw.prUrl)
20
+ return false;
21
+ if (typeof raw.branch !== 'string' || !raw.branch)
22
+ return false;
23
+ if (raw.prState !== 'pr-created')
24
+ return false;
25
+ if (!Array.isArray(raw.ticketIds))
26
+ return false;
27
+ const covered = new Set(raw.ticketIds.filter((id) => typeof id === 'number'));
28
+ return ticketIds.length > 0 && ticketIds.every((id) => covered.has(id));
29
+ }
10
30
  /**
11
31
  * Rails domain facade. A "rail" is a numbered launch slot per project: it holds
12
32
  * a set of assigned ticket IDs plus a mode/profile/engine/name, and launching it
@@ -27,9 +47,9 @@ function railsTools() {
27
47
  'Actions: list (rails + active jobs/loop runs), create_rail (add a new rail slot; returns its railIndex), set_tickets (assign ticket IDs), set_profile (default agent profile, null=legacy), ' +
28
48
  'set_engine (provider override, null=primary), set_name (display label, null clears), ' +
29
49
  'launch (ai-spawn — spawns claude/codex/gemini CLI job(s) that WRITE CODE, RUN TESTS, COMMIT, and INCUR TOKEN COST; returns 202 with jobId/jobIds/loopRunIds), ' +
30
- 'launch_all (ai-spawn — launches EVERY rail that has tickets and no active run/pending PR decision, in parallel, using each rail\'s stored mode/engine/profile; returns per-rail outcomes with skip reasons), ' +
50
+ 'launch_all (ai-spawn — launches EVERY rail that has tickets and no active run/uncontinuable pending PR decision, in parallel, using each rail\'s stored mode/engine/profile; returns per-rail outcomes with skip reasons), ' +
31
51
  'stop (destructive — kills all active jobs and loop runs for the rail). ' +
32
- 'For on_review tickets with an already-open GitHub PR, launch automatically tries to continue that PR head branch; Jira-linked in_progress tickets can do the same when the PR match is explicit; fresh tickets still start from the project integration branch. ' +
52
+ 'For on_review tickets with an already-open GitHub PR (including a published pr_ready delivery), launch automatically tries to continue that PR head branch; Jira-linked in_progress tickets can do the same when the PR match is explicit; fresh tickets still start from the project integration branch. ' +
33
53
  'When launched from the in-app agent chat without an explicit aiEngine, the engine defaults to your conversation\'s provider (pass aiEngine to override; launch_all always uses each rail\'s stored engine). ' +
34
54
  'User-facing naming: call the free-form autonomous mode "Freestyle"; use "freestyle" as the canonical API enum value for that same capability. ' +
35
55
  'NAMING: railIndex is the 0-BASED internal identity; the dashboard shows rails 1-based ("Rail N" = railIndex N-1). When talking to the user, ALWAYS say "Rail <railIndex + 1>" (or the rail\'s custom name) — results include railLabel with the correct user-facing label.',
@@ -236,7 +256,7 @@ function railsTools() {
236
256
  return {
237
257
  ...r,
238
258
  railLabel,
239
- hint: `Launch accepted (202) on ${railLabel} (isolated worktree, PR flow active) — tell the user it runs on "${railLabel}" (UI labels are 1-based). If the assigned spec is on_review with a matching open PR, or is Jira-linked in_progress with an explicit PR match, Specrails continues that PR branch automatically; otherwise it starts a fresh branch from the integration branch. The PR-decision card will appear here and on the rail header when it settles. Use specrails_watch with the returned loopRunIds to await completion only if asked. Rails run for minutes; pass untilMs up to 600000 and re-watch on timeout.`,
259
+ hint: `Launch accepted (202) on ${railLabel} (isolated worktree, PR flow active) — tell the user it runs on "${railLabel}" (UI labels are 1-based). If the assigned spec is on_review with a matching open PR, including an already-published pr_ready PR, or is Jira-linked in_progress with an explicit PR match, Specrails continues that PR branch automatically; otherwise it starts a fresh branch from the integration branch. The PR-decision card will appear here and on the rail header when it settles. Use specrails_watch with the returned loopRunIds to await completion only if asked. Rails run for minutes; pass untilMs up to 600000 and re-watch on timeout.`,
240
260
  };
241
261
  }
242
262
  case 'launch_all': {
@@ -244,8 +264,9 @@ function railsTools() {
244
264
  // because each launch isolates its work in per-ticket git worktrees
245
265
  // (allocation is serialized per repo by the launch path itself).
246
266
  // Eligibility mirrors the dashboard's Launch-all control: the rail
247
- // must hold tickets, have no active job/loop run, and no undecided
248
- // PR delivery. Each rail launches with its OWN stored config
267
+ // must hold tickets, have no active job/loop run, and no uncontinuable
268
+ // PR delivery. A draft/published PR covering the rail's tickets is
269
+ // intentionally continuable. Each rail launches with its OWN stored config
249
270
  // (mode/engine/profile) — conversation-provider defaults do NOT
250
271
  // apply here (they would clobber per-rail engines).
251
272
  const snapshot = (await (0, types_1.apiCall)(ctx, 'GET', base));
@@ -267,7 +288,7 @@ function railsTools() {
267
288
  results.push({ railIndex: idx, outcome: 'skipped', reason: 'already-running', ticketIds });
268
289
  continue;
269
290
  }
270
- if (key in prDeliveries) {
291
+ if (key in prDeliveries && !prDeliveryContinuesTickets(prDeliveries[key], ticketIds)) {
271
292
  results.push({ railIndex: idx, outcome: 'skipped', reason: 'pr-decision-pending', ticketIds });
272
293
  continue;
273
294
  }