specrails-desktop 2.25.0 → 2.26.1

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 (140) hide show
  1. package/client/dist/assets/{ActivityFeedPage-BGRTOOhT.js → ActivityFeedPage-C1LftFSE.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-DvDLxsUy.js → AgentBrowserCapture-B5prW-V0.js} +1 -1
  3. package/client/dist/assets/{AgentModeCodePane-DfwctjzN.js → AgentModeCodePane-b6UmdjwB.js} +2 -2
  4. package/client/dist/assets/{AgentModeJobsPane-G3WrS-sE.js → AgentModeJobsPane-DW3Wj5oi.js} +1 -1
  5. package/client/dist/assets/{AgentsPage-DGFFijMC.js → AgentsPage-DBMnhdlq.js} +3 -3
  6. package/client/dist/assets/{AnalyticsPage-hoJmlEpk.js → AnalyticsPage-DjFf1OxU.js} +1 -1
  7. package/client/dist/assets/{BarChart-BEhAXVK-.js → BarChart-nmDbbUiR.js} +1 -1
  8. package/client/dist/assets/{CodePage-B4zB9DVC.js → CodePage-DHd46Sm7.js} +1 -1
  9. package/client/dist/assets/{DesktopAnalyticsPage-CBX-Qpxd.js → DesktopAnalyticsPage-ee0bpk-F.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-lTsnNdAM.js → DocsDialog-CGUzB1zX.js} +1 -1
  11. package/client/dist/assets/{DocsPage-CkvdGroT.js → DocsPage-96iws84I.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-Da98gR-P.js → ExportDropdown-Dzk2Lkxw.js} +1 -1
  13. package/client/dist/assets/{IntegrationsPage-Cf9o4eZq.js → IntegrationsPage-Bta2lAhq.js} +1 -1
  14. package/client/dist/assets/{InteractiveJobComposer-C8N-Bbnn.js → InteractiveJobComposer-B9S_ZGJS.js} +5 -5
  15. package/client/dist/assets/{JobDetailModal-BZVwIIEx.js → JobDetailModal-DhrwU2z0.js} +1 -1
  16. package/client/dist/assets/{JobDetailPage-CQewQ1zy.js → JobDetailPage-BnhoXkql.js} +1 -1
  17. package/client/dist/assets/{JobsPage-CB4wOxmE.js → JobsPage-BBT9fyhg.js} +1 -1
  18. package/client/dist/assets/{LoopBuilderPage-B9Y5gt08.js → LoopBuilderPage-5ucN9Hq9.js} +2 -2
  19. package/client/dist/assets/{LoopPreviewModal-kxQ5kV1t.js → LoopPreviewModal-wKAts-Wf.js} +1 -1
  20. package/client/dist/assets/{LoopsPage-RysW-6sC.js → LoopsPage-COyxf6Pa.js} +1 -1
  21. package/client/dist/assets/{ProjectSettingsDialog-BHN5qsGT.js → ProjectSettingsDialog-CcToBI-Z.js} +1 -1
  22. package/client/dist/assets/{TemplatePreviewModal-DW6lZCIs.js → TemplatePreviewModal-_Dy6ceXg.js} +1 -1
  23. package/client/dist/assets/agent-BmH52J4j.js +1 -0
  24. package/client/dist/assets/agent-Bxb9RRNW.js +1 -0
  25. package/client/dist/assets/agent-Cg49-TGZ.js +1 -0
  26. package/client/dist/assets/agent-DgDtg-0Y2.js +1 -0
  27. package/client/dist/assets/agent-DolxzTol.js +1 -0
  28. package/client/dist/assets/agent-DwDR2Exf2.js +1 -0
  29. package/client/dist/assets/agent-_tfCgFUk.js +1 -0
  30. package/client/dist/assets/agent-gFi-qvxO2.js +1 -0
  31. package/client/dist/assets/{brain-B9dkGuAN.js → brain-BJ2Gd98j.js} +1 -1
  32. package/client/dist/assets/code-CwnBPPyA.js +1 -0
  33. package/client/dist/assets/dashboard-90r0QcUc2.js +1 -0
  34. package/client/dist/assets/dashboard-B0C6mJP-.js +1 -0
  35. package/client/dist/assets/dashboard-BOx0FR1L2.js +1 -0
  36. package/client/dist/assets/dashboard-BcjUOLJr.js +1 -0
  37. package/client/dist/assets/dashboard-C27kNpjn.js +1 -0
  38. package/client/dist/assets/dashboard-DJAj2EbH.js +1 -0
  39. package/client/dist/assets/dashboard-DNd1Gobs.js +1 -0
  40. package/client/dist/assets/dashboard-DZWrDAYm2.js +1 -0
  41. package/client/dist/assets/{dist-js-Bbd7Wzdw.js → dist-js-CXAsO0Z8.js} +1 -1
  42. package/client/dist/assets/{dist-js-BW5-IZTP.js → dist-js-D7smLA35.js} +1 -1
  43. package/client/dist/assets/index-B3ejvKSt.js +156 -0
  44. package/client/dist/assets/index-C695aPdV.css +2 -0
  45. package/client/dist/assets/{lib-EIkIPf26.js → lib-BIHmUkcn.js} +1 -1
  46. package/client/dist/assets/{loops-YYV2bwjd.js → loops-Brnx4Fji.js} +1 -1
  47. package/client/dist/assets/{loops-hXgncEpF2.js → loops-BvqQ-bHR2.js} +1 -1
  48. package/client/dist/assets/{loops-BvEenWBy.js → loops-CvOWsi5r.js} +1 -1
  49. package/client/dist/assets/{loops-DjOtVXRo.js → loops-D6D3XHjj.js} +1 -1
  50. package/client/dist/assets/{loops-HTHf-xu32.js → loops-DJNhp17p2.js} +1 -1
  51. package/client/dist/assets/{loops-kYbydDyN.js → loops-K5_HXfJa.js} +1 -1
  52. package/client/dist/assets/loops-LIgO_-eM.js +1 -0
  53. package/client/dist/assets/{loops-DNGiYKll2.js → loops-vhJdbcz92.js} +1 -1
  54. package/client/dist/assets/{settings-BsJMsWGc.js → settings-BHdPchJP.js} +1 -1
  55. package/client/dist/assets/{settings-lFoaYT-s2.js → settings-BeCczBFS2.js} +1 -1
  56. package/client/dist/assets/{settings-DZJeXoRV.js → settings-BlLi719f.js} +1 -1
  57. package/client/dist/assets/{settings-BOHOmHkb.js → settings-C_nY14Yz.js} +1 -1
  58. package/client/dist/assets/{settings-D6qY9_UI.js → settings-CaZ26a0S.js} +1 -1
  59. package/client/dist/assets/{settings-BlEA_7h02.js → settings-D54_cgl12.js} +1 -1
  60. package/client/dist/assets/settings-D9bDDe2O.js +1 -0
  61. package/client/dist/assets/{settings-TNga5gIF2.js → settings-cn0tbve62.js} +1 -1
  62. package/client/dist/assets/{setup-iY8vH8pB.js → setup-7J8TjG4O.js} +1 -1
  63. package/client/dist/assets/{setup-D674kWQH.js → setup-BXpw07r-.js} +1 -1
  64. package/client/dist/assets/{setup-B590KsTX.js → setup-BiduSX9d.js} +1 -1
  65. package/client/dist/assets/{setup-CmU6bTCV2.js → setup-BuQNKztt2.js} +1 -1
  66. package/client/dist/assets/{setup-CEj6uY3-2.js → setup-BwMovM9z2.js} +1 -1
  67. package/client/dist/assets/{setup-BkyGoby6.js → setup-D84y3wx-.js} +1 -1
  68. package/client/dist/assets/{setup-CbIerFGf2.js → setup-DU0ULX8q2.js} +1 -1
  69. package/client/dist/assets/{setup-VNqXyB6Y.js → setup-RiUHzq7E.js} +1 -1
  70. package/client/dist/assets/{upload-BCMl-nCg.js → upload-xdQuJvXN.js} +1 -1
  71. package/client/dist/assets/{value-CG-amSq1.js → value-Bk2f9AUg.js} +1 -1
  72. package/client/dist/index.html +22 -9
  73. package/docs/agy-cli-provider-study.md +1 -1
  74. package/docs/codex.md +2 -2
  75. package/docs/gemini-cli-provider-study.md +2 -2
  76. package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  77. package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  78. package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  79. package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  80. package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  81. package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  82. package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  83. package/docs/guide/pt/pipeline/5-the-loop-builder.md +1 -1
  84. package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  85. package/docs/internals/api-reference.md +3 -3
  86. package/docs/internals/companion-rails-as-loops-contract.md +6 -6
  87. package/docs/internals/configuration.md +1 -1
  88. package/docs/internals/interactive-jobs.md +3 -3
  89. package/docs/internals/safe-pr-review-flow.md +1 -1
  90. package/docs/internals/visual-design-audit.md +2 -2
  91. package/docs/running-pipelines.md +1 -1
  92. package/package.json +1 -1
  93. package/server/dist/agent-chat-manager.js +16 -2
  94. package/server/dist/agent-chat-router.js +66 -1
  95. package/server/dist/agent-context-resolver.js +301 -0
  96. package/server/dist/agent-operator-prompt.js +58 -11
  97. package/server/dist/agent-store.js +24 -12
  98. package/server/dist/db.js +17 -17
  99. package/server/dist/desktop-db.js +6 -0
  100. package/server/dist/desktop-router.js +9 -2
  101. package/server/dist/feature-flags.js +2 -2
  102. package/server/dist/index.js +1 -1
  103. package/server/dist/interactive-job-session.js +3 -3
  104. package/server/dist/loop-command-catalog.js +11 -11
  105. package/server/dist/loop-factory.js +7 -7
  106. package/server/dist/loop-graph.js +2 -2
  107. package/server/dist/loop-run-manager.js +1 -1
  108. package/server/dist/mcp/guide.js +41 -10
  109. package/server/dist/mcp/tools/app-settings.js +18 -2
  110. package/server/dist/mcp/tools/catalog.js +2 -0
  111. package/server/dist/mcp/tools/rails.js +29 -9
  112. package/server/dist/mcp/tools/support.js +324 -0
  113. package/server/dist/mobile/mobile-router.js +1 -1
  114. package/server/dist/project-router-jobs.js +1 -1
  115. package/server/dist/project-router-loop-runs.js +1 -1
  116. package/server/dist/project-router-settings.js +5 -5
  117. package/server/dist/queue-manager.js +27 -27
  118. package/server/dist/rail-isolated-launch.js +5 -1
  119. package/server/dist/rails-router.js +76 -27
  120. package/client/dist/assets/agent-4ARs0uac2.js +0 -1
  121. package/client/dist/assets/agent-C55wwsUX.js +0 -1
  122. package/client/dist/assets/agent-C_kqyrXo.js +0 -1
  123. package/client/dist/assets/agent-Df3lnHMu.js +0 -1
  124. package/client/dist/assets/agent-G6Zn2gpr.js +0 -1
  125. package/client/dist/assets/agent-MQhun7cv.js +0 -1
  126. package/client/dist/assets/agent-NqlER4Yh2.js +0 -1
  127. package/client/dist/assets/agent-OTGI6t7e2.js +0 -1
  128. package/client/dist/assets/code-CqCcRThR.js +0 -1
  129. package/client/dist/assets/dashboard-4WX7agHF2.js +0 -1
  130. package/client/dist/assets/dashboard-5Z_esXIB.js +0 -1
  131. package/client/dist/assets/dashboard-DQ-po1MG2.js +0 -1
  132. package/client/dist/assets/dashboard-IbEztmJ52.js +0 -1
  133. package/client/dist/assets/dashboard-nc-Raovv.js +0 -1
  134. package/client/dist/assets/dashboard-ojWdDq6S.js +0 -1
  135. package/client/dist/assets/dashboard-oqS7VRrK.js +0 -1
  136. package/client/dist/assets/dashboard-zfoJcdFW.js +0 -1
  137. package/client/dist/assets/index-BtAks9nm.css +0 -2
  138. package/client/dist/assets/index-DC-REAcL.js +0 -155
  139. package/client/dist/assets/loops-CBiWd9Hf.js +0 -1
  140. package/client/dist/assets/settings-Mr7MnJbf.js +0 -1
@@ -37,11 +37,12 @@ coding pipelines over them.
37
37
  - **Rail** — a persistent numbered launch slot that REMEMBERS its config across
38
38
  launches (assigned tickets, mode, profile, engine, name). Modes: \`implement\`
39
39
  (one pipeline job — Architect → Developer → Reviewer → Ship — over the rail's
40
- tickets), \`batch-implement\` (dependency-aware waves), Freestyle (wire value
41
- \`ultracode\` — hands the spec straight to the model, one job per ticket,
42
- Claude-only, optional interactive in-job chat), \`loop\` (runs a saved
43
- workflow graph per ticket). Launching spawns AI CLI processes that WRITE CODE,
44
- RUN TESTS and COMMIT in the repo — it costs money and runs for minutes.
40
+ tickets), \`batch-implement\` (dependency-aware waves), Freestyle (API mode
41
+ value \`freestyle\` only — call it \`Freestyle\` in prose; it sends a free-form
42
+ autonomous prompt straight to Claude, one job per ticket, Claude-only,
43
+ optional interactive in-job chat), \`loop\` (runs a saved workflow graph per
44
+ ticket). Launching spawns AI CLI processes that WRITE CODE, RUN TESTS and
45
+ COMMIT in the repo — it costs money and runs for minutes.
45
46
  - **Job** — one spawned run. Outcomes mutate spec status AUTOMATICALLY: launch →
46
47
  \`in_progress\`; success → \`done\`; revert → back to \`todo\`; partial confidence →
47
48
  \`done\` + \`needs_review\`. NEVER patch a status the pipeline manages; only
@@ -76,6 +77,11 @@ coding pipelines over them.
76
77
  outcome. Never claim success from the 202 acceptance alone.
77
78
  - Prefer \`specrails_guide\` / \`specrails_search\` / \`specrails_describe\` to
78
79
  discover the exact action and arguments before calling an unfamiliar tool.
80
+ - **User-facing naming:** always call the free-form autonomous rail mode
81
+ \`Freestyle\`. The canonical API / id / token values are \`freestyle\`,
82
+ \`factory:freestyle\`, and \`{{cmd:freestyle}}\`; use those exact values inside
83
+ tool arguments or when quoting raw data. Do not invent or use another name for
84
+ this capability.
79
85
  - Ground claims about the user's code with
80
86
  \`specrails_code(tree | read_file | summary)\` before asserting how the codebase
81
87
  works; \`specrails_projects(get)\` returns the repo's absolute path.
@@ -112,6 +118,39 @@ tool for exactly that, and never refuse or push this back to the user's terminal
112
118
  (\`rm -rf\`, force-push, branch deletion, history rewrite) to "make something
113
119
  work" — surface the blocker instead.
114
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
+
115
154
  ## Permission ladder & confirmation rules
116
155
 
117
156
  Your actions are gated by a cumulative level the user steers live with
@@ -141,7 +180,9 @@ above the current level, say so up front.
141
180
 
142
181
  ## Think in specs (default stance)
143
182
 
144
- 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.
145
186
 
146
187
  1. Check the backlog for duplicates first: \`specrails_specs(list)\`.
147
188
  2. Capture the work as a spec — pick the right creation path (next section).
@@ -331,8 +372,14 @@ summary later.
331
372
  is explicit, covering Jira projects whose Review status has not been mapped to
332
373
  Specrails \`on_review\`. You do NOT need to know or pass the branch name. If
333
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.
334
380
  - Launch proposal shape: tickets (ids + titles), rail number, mode, engine and
335
- model/profile, plus "runs for minutes and costs money". Wait for yes.
381
+ model/profile, plus "runs for minutes and costs money". If the API mode is
382
+ \`freestyle\`, write "Freestyle" to the user. Wait for yes.
336
383
  - **Parallel launches are safe and normal.** Every rail launch runs its work in
337
384
  its own isolated git worktree, so launching several rails at the same time
338
385
  never makes them collide — spread independent specs across rails and launch
@@ -343,9 +390,9 @@ summary later.
343
390
  tickets to the returned \`railIndex\`, and launch. Never wedge waiting for a
344
391
  slot and never steal a configured rail's tickets to free one.
345
392
  - **Launching many rails**: \`specrails_rails(launch_all)\` launches every rail
346
- that has tickets and no active run / pending PR decision in ONE call, each
347
- with its own stored mode/engine/profile, and returns per-rail outcomes
348
- (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
349
396
  ai-spawn: propose it with the total rail + spec count and cost framing
350
397
  first. One yes covers the whole batch when the user asked for the batch
351
398
  ("launch everything") — do not re-ask per rail.
@@ -424,4 +471,4 @@ When the user has no projects yet, or asks to add one:
424
471
  Setup is QUICK-only (fast, offline). Do NOT offer, mention, or attempt a "full"
425
472
  or "enrich" install — that flow is deprecated and not available through you.
426
473
  `;
427
- 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); 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.`;
@@ -11,24 +11,35 @@ exports.findAgentSystemMessage = findAgentSystemMessage;
11
11
  exports.updateAgentMessageContent = updateAgentMessageContent;
12
12
  const crypto_1 = require("crypto");
13
13
  const agent_tier_1 = require("./agent-tier");
14
- function mapMessage(row) {
15
- let ids = [];
16
- if (row.attachment_ids) {
17
- try {
18
- const parsed = JSON.parse(row.attachment_ids);
19
- if (Array.isArray(parsed))
20
- ids = parsed.filter((x) => typeof x === 'string');
21
- }
22
- catch {
23
- /* ignore malformed */
24
- }
14
+ function parseJsonArray(raw, predicate) {
15
+ if (!raw)
16
+ return [];
17
+ try {
18
+ const parsed = JSON.parse(raw);
19
+ return Array.isArray(parsed) ? parsed.filter(predicate) : [];
20
+ }
21
+ catch {
22
+ return [];
25
23
  }
24
+ }
25
+ function isContextRef(value) {
26
+ if (!value || typeof value !== 'object')
27
+ return false;
28
+ const row = value;
29
+ return (typeof row.kind === 'string' &&
30
+ typeof row.id === 'string' &&
31
+ typeof row.label === 'string' &&
32
+ typeof row.token === 'string');
33
+ }
34
+ function mapMessage(row) {
35
+ const ids = parseJsonArray(row.attachment_ids, (x) => typeof x === 'string');
26
36
  return {
27
37
  id: row.id,
28
38
  conversation_id: row.conversation_id,
29
39
  role: row.role,
30
40
  content: row.content,
31
41
  attachment_ids: ids,
42
+ context_refs: parseJsonArray(row.context_refs, isContextRef),
32
43
  created_at: row.created_at,
33
44
  };
34
45
  }
@@ -71,7 +82,8 @@ function deleteAgentConversation(db, id) {
71
82
  function addAgentMessage(db, input) {
72
83
  const id = (0, crypto_1.randomUUID)();
73
84
  const attachmentIds = input.attachmentIds && input.attachmentIds.length ? JSON.stringify(input.attachmentIds) : null;
74
- db.prepare('INSERT INTO agent_messages (id, conversation_id, role, content, attachment_ids) VALUES (?, ?, ?, ?, ?)').run(id, input.conversationId, input.role, input.content, attachmentIds);
85
+ const contextRefs = input.contextRefs && input.contextRefs.length ? JSON.stringify(input.contextRefs) : null;
86
+ db.prepare('INSERT INTO agent_messages (id, conversation_id, role, content, attachment_ids, context_refs) VALUES (?, ?, ?, ?, ?, ?)').run(id, input.conversationId, input.role, input.content, attachmentIds, contextRefs);
75
87
  // Touch the parent so the list stays ordered by latest activity.
76
88
  db.prepare("UPDATE agent_conversations SET updated_at = datetime('now') WHERE id = ?").run(input.conversationId);
77
89
  return mapMessage(db.prepare('SELECT * FROM agent_messages WHERE id = ?').get(id));
package/server/dist/db.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_ULTRACODE_PRE_PROMPT = void 0;
6
+ exports.DEFAULT_FREESTYLE_PRE_PROMPT = void 0;
7
7
  exports.initDb = initDb;
8
8
  exports.createJob = createJob;
9
9
  exports.markJobInteractive = markJobInteractive;
@@ -39,7 +39,7 @@ exports.skipJob = skipJob;
39
39
  exports.getPipelineJobs = getPipelineJobs;
40
40
  exports.getStats = getStats;
41
41
  exports.getProjectSettings = getProjectSettings;
42
- exports.getUltracodePrePrompt = getUltracodePrePrompt;
42
+ exports.getFreestylePrePrompt = getFreestylePrePrompt;
43
43
  exports.updateProjectSettings = updateProjectSettings;
44
44
  exports.getQuickContractRefineLast = getQuickContractRefineLast;
45
45
  exports.hasQuickContractRefineLast = hasQuickContractRefineLast;
@@ -643,7 +643,7 @@ const MIGRATIONS = [
643
643
  }
644
644
  },
645
645
  // Migration 32: jobs.interactive — 1 when the job is an interactive persistent
646
- // ultracode session (the user sends multiple prompts across turns; the job
646
+ // freestyle session (the user sends multiple prompts across turns; the job
647
647
  // stays 'running' until an explicit finalize, at which point every turn's real
648
648
  // tokens/cost/num_turns are already summed into the row and status flips to
649
649
  // 'completed'); 0 (default) for standard autonomous jobs. Additive + idempotent
@@ -1226,13 +1226,13 @@ function getStats(db) {
1226
1226
  }
1227
1227
  // ─── Project settings ─────────────────────────────────────────────────────────
1228
1228
  /**
1229
- * Default pre-prompt used by Ultracode (Claude-only rails) when the project
1230
- * has no per-project override. Ultracode skips the OpenSpec pipeline entirely:
1229
+ * Default pre-prompt used by Freestyle (Claude-only rails) when the project
1230
+ * has no per-project override. Freestyle skips the OpenSpec pipeline entirely:
1231
1231
  * it hands Claude the spec text plus this instruction and lets it work
1232
1232
  * autonomously end-to-end.
1233
1233
  */
1234
- exports.DEFAULT_ULTRACODE_PRE_PROMPT = [
1235
- 'You are operating in ULTRACODE: fully autonomous, end-to-end implementation.',
1234
+ exports.DEFAULT_FREESTYLE_PRE_PROMPT = [
1235
+ 'You are operating in FREESTYLE: fully autonomous, end-to-end implementation.',
1236
1236
  'Implement the following spec COMPLETELY in this repository. You have full access to the codebase and tools.',
1237
1237
  'Work independently until the feature is done: write the code, the tests, update docs as needed, and make sure everything builds and the test suite passes.',
1238
1238
  'Do NOT follow any structured architect/developer/reviewer pipeline — use your own judgement and the repo conventions.',
@@ -1242,21 +1242,21 @@ function getProjectSettings(db) {
1242
1242
  const telemetryRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.pipeline_telemetry_enabled'`).get();
1243
1243
  const modelRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.orchestrator_model'`).get();
1244
1244
  const prePromptRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.pre_prompt'`).get();
1245
- const ultraPrePromptRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.ultracode_pre_prompt'`).get();
1245
+ const freestylePrePromptRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.freestyle_pre_prompt'`).get();
1246
1246
  const integrationBranchRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.integration_branch'`).get();
1247
1247
  return {
1248
1248
  pipelineTelemetryEnabled: telemetryRow?.value === 'true',
1249
1249
  orchestratorModel: modelRow?.value ?? 'sonnet',
1250
1250
  prePrompt: prePromptRow?.value ?? '',
1251
- ultraPrePrompt: ultraPrePromptRow?.value ?? '',
1251
+ freestylePrePrompt: freestylePrePromptRow?.value ?? '',
1252
1252
  integrationBranch: integrationBranchRow?.value ?? '',
1253
1253
  };
1254
1254
  }
1255
- /** Resolve the effective Ultracode pre-prompt: the per-project override when
1255
+ /** Resolve the effective Freestyle pre-prompt: the per-project override when
1256
1256
  * set, otherwise the built-in default. */
1257
- function getUltracodePrePrompt(db) {
1258
- const override = getProjectSettings(db).ultraPrePrompt.trim();
1259
- return override || exports.DEFAULT_ULTRACODE_PRE_PROMPT;
1257
+ function getFreestylePrePrompt(db) {
1258
+ const override = getProjectSettings(db).freestylePrePrompt.trim();
1259
+ return override || exports.DEFAULT_FREESTYLE_PRE_PROMPT;
1260
1260
  }
1261
1261
  function updateProjectSettings(db, patch) {
1262
1262
  if (patch.pipelineTelemetryEnabled !== undefined) {
@@ -1273,12 +1273,12 @@ function updateProjectSettings(db, patch) {
1273
1273
  db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.pre_prompt', ?)`).run(patch.prePrompt);
1274
1274
  }
1275
1275
  }
1276
- if (patch.ultraPrePrompt !== undefined) {
1277
- if (patch.ultraPrePrompt.trim() === '') {
1278
- db.prepare(`DELETE FROM queue_state WHERE key = 'config.ultracode_pre_prompt'`).run();
1276
+ if (patch.freestylePrePrompt !== undefined) {
1277
+ if (patch.freestylePrePrompt.trim() === '') {
1278
+ db.prepare(`DELETE FROM queue_state WHERE key = 'config.freestyle_pre_prompt'`).run();
1279
1279
  }
1280
1280
  else {
1281
- db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.ultracode_pre_prompt', ?)`).run(patch.ultraPrePrompt);
1281
+ db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.freestyle_pre_prompt', ?)`).run(patch.freestylePrePrompt);
1282
1282
  }
1283
1283
  }
1284
1284
  if (patch.integrationBranch !== undefined) {
@@ -430,6 +430,12 @@ function applyDesktopMigrations(db) {
430
430
  CREATE INDEX IF NOT EXISTS idx_agent_inv_conv ON agent_invocations(conversation_id);
431
431
  `);
432
432
  },
433
+ // 21: structured context refs selected in the Agent Composer (`@`, `#`, `/`,
434
+ // and the `+` palette). Persisted with the user row so conversation bubbles
435
+ // can re-render exact chips after refresh without guessing from text.
436
+ () => {
437
+ db.exec(`ALTER TABLE agent_messages ADD COLUMN context_refs TEXT;`);
438
+ },
433
439
  ];
434
440
  for (let i = 0; i < migrations.length; i++) {
435
441
  const version = i + 1;
@@ -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) => {
@@ -29,8 +29,8 @@ function isJiraEnabled() {
29
29
  return process.env.SPECRAILS_JIRA_SECTION !== 'false';
30
30
  }
31
31
  /**
32
- * Interactive ultracode jobs: when launched with the rail's "Interactive"
33
- * toggle, an ultracode (Claude-only) job becomes a persistent chat session —
32
+ * Interactive freestyle jobs: when launched with the rail's "Interactive"
33
+ * toggle, an freestyle (Claude-only) job becomes a persistent chat session —
34
34
  * the user sends multiple prompts across turns, the job stays resident until an
35
35
  * explicit "Finalize Job" action, and every turn's real token spend is summed
36
36
  * into the single job row. Server-side default ON; set
@@ -541,7 +541,7 @@ function applyPtyWsRateLimiting(ws) {
541
541
  mcpManager.start().catch((err) => console.error('[mcp] boot start failed:', err));
542
542
  }
543
543
  // ─── App-global agent chat (drives the app via its own MCP) ────────────────
544
- const agentChatManager = new agent_chat_manager_1.AgentChatManager(broadcast, registry.desktopDb, port);
544
+ const agentChatManager = new agent_chat_manager_1.AgentChatManager(broadcast, registry.desktopDb, port, registry);
545
545
  _agentChatManager = agentChatManager;
546
546
  // Publish the instance to the process-wide registry so the rails layer can
547
547
  // post PR-decision cards (safe-pr-review-flow). Left null when agent chat is
@@ -8,9 +8,9 @@
8
8
  // newline-delimited stream-json message written to stdin, the agent works, and
9
9
  // the turn ends on a `result` event WITHOUT killing the child. Two settle modes:
10
10
  //
11
- // 'finalize' (ultracode) — the session idles between turns until the user
11
+ // 'finalize' (freestyle) — the session idles between turns until the user
12
12
  // explicitly finalizes (SIGTERM), at which point QueueManager flips the job
13
- // to a terminal status. Byte-identical to the original interactive-ultracode
13
+ // to a terminal status. Byte-identical to the original interactive-freestyle
14
14
  // behaviour.
15
15
  // 'auto' (every other command — the default-interactive flip) — the session
16
16
  // settles ITSELF the moment it goes QUIESCENT: a turn `result` arrived and
@@ -163,7 +163,7 @@ class InteractiveJobSession {
163
163
  this._nextEventSeq = deps.nextEventSeq ?? null;
164
164
  this._spawn = deps.spawn ?? cli_prompt_1.spawnAiCli;
165
165
  }
166
- /** Spawn the resident child and run the first turn (the ultracode prompt). */
166
+ /** Spawn the resident child and run the first turn (the freestyle prompt). */
167
167
  start(spec, firstPrompt) {
168
168
  const child = this._spawn(spec.binary, spec.args, {
169
169
  env: spec.env ?? process.env,
@@ -8,12 +8,12 @@
8
8
  * claude `/specrails:<name>`, codex `$<name>` (skill), gemini `/specrails:<name>`.
9
9
  * Mirrors QueueManager's rail invocation (`/specrails:implement #1 #2 --yes`).
10
10
  * - `template`: a provider-invariant curated prompt (no core coupling).
11
- * - `native`: a raw autonomous command (ultracode) — claude-only, NOT a slash
11
+ * - `native`: a raw autonomous Freestyle command — claude-only, NOT a slash
12
12
  * command; it expands to a self-contained autonomous prompt.
13
13
  *
14
14
  * Each command declares its TICKET SCOPE:
15
15
  * - `all` → one run over ALL the rail's tickets (`#1 #2 #3`). implement, batch.
16
- * - `per-ticket` → one run per ticket. ultracode (and the default).
16
+ * - `per-ticket` → one run per ticket. Freestyle (and the default).
17
17
  *
18
18
  * Expansion order in the engine: `expandCommands()` FIRST (injects the ticket ids),
19
19
  * then `interpolateSpec()` resolves any remaining `{{spec.*}}` data tokens.
@@ -24,10 +24,10 @@ exports.getLoopCommand = getLoopCommand;
24
24
  exports.expandCommands = expandCommands;
25
25
  exports.dominantTicketScope = dominantTicketScope;
26
26
  exports.referencesClaudeOnlyCommand = referencesClaudeOnlyCommand;
27
- /** The autonomous prompt a `native` command (ultracode) expands to for the
28
- * LoopRunManager path. Factory ultracode loops route to QueueManager's real
29
- * `_buildUltracodePrompt` instead (phase A); this is the custom-loop fallback. */
30
- const ULTRACODE_PROMPT = [
27
+ /** The autonomous prompt a `native` command (Freestyle) expands to for the
28
+ * LoopRunManager path. Factory Freestyle loops route to QueueManager's real
29
+ * `_buildFreestylePrompt` instead (phase A); this is the custom-loop fallback. */
30
+ const FREESTYLE_PROMPT = [
31
31
  'Implement the following spec completely and autonomously. Explore the codebase first, then write the code and tests and make the full test suite pass. Work end-to-end without stopping for confirmation; do not open a pipeline — just do it.',
32
32
  '',
33
33
  'Title: {{spec.title}}',
@@ -68,9 +68,9 @@ exports.LOOP_COMMANDS = [
68
68
  ticketScope: 'all',
69
69
  },
70
70
  {
71
- name: 'ultracode',
72
- label: 'ultracode',
73
- description: 'Autonomous per-ticket implementation — Claude works the spec end-to-end with no pipeline. Claude only.',
71
+ name: 'freestyle',
72
+ label: 'Freestyle',
73
+ description: 'Free-form autonomous per-ticket implementation — Claude receives the spec as a prompt and works it end-to-end with no pipeline. Claude only. Internal token: {{cmd:freestyle}}.',
74
74
  native: true,
75
75
  claudeOnly: true,
76
76
  ticketScope: 'per-ticket',
@@ -241,7 +241,7 @@ function expandCommands(text, opts) {
241
241
  if (cmd.coreCommand)
242
242
  return nativeInvocation(cmd.coreCommand, opts.provider, ids);
243
243
  if (cmd.native)
244
- return ULTRACODE_PROMPT;
244
+ return FREESTYLE_PROMPT;
245
245
  // Provider-native prefix (e.g. {{cmd:loop}} → /loop | $goal) wins for the
246
246
  // providers it lists; everyone else falls back to the generic template.
247
247
  if (cmd.providerNative) {
@@ -267,7 +267,7 @@ function dominantTicketScope(text) {
267
267
  }
268
268
  return sawPerTicket ? 'per-ticket' : 'per-ticket';
269
269
  }
270
- /** True if a prompt references any claude-only command (e.g. ultracode). */
270
+ /** True if a prompt references any claude-only command (e.g. Freestyle's canonical token). */
271
271
  function referencesClaudeOnlyCommand(text) {
272
272
  for (const m of text.matchAll(CMD_TOKEN_RE)) {
273
273
  if (COMMANDS_BY_NAME.get(m[1])?.claudeOnly)
@@ -30,15 +30,15 @@ exports.FACTORY_LOOPS = [
30
30
  graph: (0, loop_templates_1.fixLoopGraph)(['{{cmd:batch}}'], GREEN_GOAL, FACTORY_MAX_ITERATIONS, FACTORY_LOOP_TIMEOUT_MIN, FACTORY_AI_STEP_TIMEOUT_MIN),
31
31
  },
32
32
  {
33
- id: 'factory:ultracode',
34
- // Renamed from "Ultracode": the mode hands the spec straight to the model
33
+ id: 'factory:freestyle',
34
+ // User-facing name for the canonical freestyle mode: it hands the spec straight to the model
35
35
  // with full freedom — no pipeline, it works like a regular coding agent.
36
- // The id/mode strings stay 'ultracode' (wire + rail back-compat).
36
+ // The id/mode strings are the canonical rail contract.
37
37
  name: 'Freestyle',
38
38
  description: 'Hands the spec straight to the model with full freedom — no pipeline, it works like a regular coding agent, then verify + refine until green. Claude only.',
39
- mode: 'ultracode',
39
+ mode: 'freestyle',
40
40
  claudeOnly: true,
41
- graph: (0, loop_templates_1.fixLoopGraph)(['{{cmd:ultracode}}'], GREEN_GOAL, FACTORY_MAX_ITERATIONS, FACTORY_LOOP_TIMEOUT_MIN, FACTORY_AI_STEP_TIMEOUT_MIN),
41
+ graph: (0, loop_templates_1.fixLoopGraph)(['{{cmd:freestyle}}'], GREEN_GOAL, FACTORY_MAX_ITERATIONS, FACTORY_LOOP_TIMEOUT_MIN, FACTORY_AI_STEP_TIMEOUT_MIN),
42
42
  },
43
43
  {
44
44
  id: 'factory:openspec',
@@ -67,7 +67,7 @@ function factoryLoopForMode(mode) {
67
67
  return FACTORY_BY_ID.get('factory:implement');
68
68
  if (mode === 'batch-implement')
69
69
  return FACTORY_BY_ID.get('factory:batch');
70
- if (mode === 'ultracode')
71
- return FACTORY_BY_ID.get('factory:ultracode');
70
+ if (mode === 'freestyle')
71
+ return FACTORY_BY_ID.get('factory:freestyle');
72
72
  return undefined;
73
73
  }
@@ -129,13 +129,13 @@ function successors(graph, nodeId) {
129
129
  .filter((n) => n !== undefined);
130
130
  }
131
131
  /** Whether a loop "needs a ticket/spec" — it references a `{{spec.*}}` token or a
132
- * ticket-consuming command (`{{cmd:implement|batch|ultracode}}`). Ticket-needing
132
+ * ticket-consuming command (`{{cmd:implement|batch|freestyle}}`). Ticket-needing
133
133
  * loops belong on a rail (the rail provides the spec). Ticket-LESS loops are
134
134
  * standalone (repo-wide CI watch, lint, audit…) and run from the Loops page's
135
135
  * "Run" action, NOT a rail — launching one on a rail would just re-run the same
136
136
  * spec-less loop once per ticket. Mirrors the client's `loop-ticket-need.ts`. */
137
137
  const SPEC_TOKEN_RE = /\{\{\s*spec\./;
138
- const TICKET_CMD_RE = /\{\{\s*cmd:(implement|batch|ultracode)\b/;
138
+ const TICKET_CMD_RE = /\{\{\s*cmd:(implement|batch|freestyle)\b/;
139
139
  function loopNeedsTicket(graph) {
140
140
  if (!graph)
141
141
  return false;
@@ -220,7 +220,7 @@ class LoopRunManager {
220
220
  railIndex: req.railIndex ?? null,
221
221
  });
222
222
  // Surface the run as a JOB so the full session streams live in the Jobs list
223
- // + JobDetail, exactly like implement/ultracode. The job id IS the run id;
223
+ // + JobDetail, exactly like implement/freestyle. The job id IS the run id;
224
224
  // createJob is idempotent. Synchronous (before the first await) so the row
225
225
  // exists by the time the launch responds → "View Log" never 404s.
226
226
  const jobCommand = `loop: ${req.loopName ?? req.loopId}${req.ticketId != null ? ` #${req.ticketId}` : ''}`;
@@ -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.
@@ -123,9 +135,11 @@ heading inside the description); \`labels\`; \`priority\`. Spec content is Engli
123
135
  - \`implement\`: one pipeline job (Architect → Developer → Reviewer → Ship)
124
136
  over the rail's tickets.
125
137
  - \`batch-implement\`: dependency-aware waves across many tickets.
126
- - Freestyle (wire value \`ultracode\`): hands the spec straight to the model —
127
- one job per ticket; Claude-only; \`model\` picker; optional
128
- \`interactive\` in-job chat (settle with \`specrails_jobs(finalize)\`).
138
+ - Freestyle: sends a free-form autonomous prompt straight to Claude — one job
139
+ per ticket; Claude-only; \`model\` picker; optional \`interactive\` in-job
140
+ chat (settle with \`specrails_jobs(finalize)\`). To launch it through the API,
141
+ pass the canonical mode value \`freestyle\`. In user-facing language, call
142
+ the feature "Freestyle".
129
143
  - \`loop\`: runs a published loop graph per ticket (\`loopId\`,
130
144
  \`reasoning_effort\`).
131
145
  - Profiles are Claude-only: a profile set on a rail that is then pointed at
@@ -169,7 +183,10 @@ Tools cannot raise their own permissions in either regime. Common tiers:
169
183
  list/get/spending/watch = read; commit_draft, from_prompt, update, set_tickets,
170
184
  create_rail, plugin install, Jira connect = write; spec create/generate, rail
171
185
  launch/launch_all, chat send, job spawn = ai-spawn; spec delete, rail stop, job
172
- 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
173
190
  embedded spec-refinement happy path (investigate + commit_draft) needs only
174
191
  read + write — ai-spawn is required only to launch work or spawn a nested AI.
175
192
 
@@ -177,9 +194,9 @@ read + write — ai-spawn is required only to launch work or spawn a nested AI.
177
194
 
178
195
  - Installed providers are per-project; AI-spawning calls may pick any installed
179
196
  one (\`aiEngine\`); rails carry a per-rail engine.
180
- - CLAUDE-ONLY: agent profiles, Contract Refine, SMASH, Freestyle mode
181
- (\`ultracode\`), interactive jobs. They are rejected or inert when the
182
- effective engine is codex or gemini.
197
+ - CLAUDE-ONLY: agent profiles, Contract Refine, SMASH, Freestyle mode (API mode
198
+ \`freestyle\`; say "Freestyle" to users), interactive jobs. They are
199
+ rejected or inert when the effective engine is codex or gemini.
183
200
  - Cost figures are authoritative for claude and estimated for codex/gemini
184
201
  (flagged as estimated in analytics).
185
202
 
@@ -214,6 +231,20 @@ read + write — ai-spawn is required only to launch work or spawn a nested AI.
214
231
  \`install_config\` per provider → ONE \`install\` (provisions all chosen
215
232
  providers; quick-only, offline) → poll \`checkpoints\` until
216
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.
217
248
  - **Loops lifecycle** (\`specrails_loops\`): Draft → \`publish\` (graph-validated)
218
249
  → runnable; \`update\` reverts to Draft and returns 409 while running;
219
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;