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.
- package/client/dist/assets/{ActivityFeedPage-BGRTOOhT.js → ActivityFeedPage-C1LftFSE.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-DvDLxsUy.js → AgentBrowserCapture-B5prW-V0.js} +1 -1
- package/client/dist/assets/{AgentModeCodePane-DfwctjzN.js → AgentModeCodePane-b6UmdjwB.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-G3WrS-sE.js → AgentModeJobsPane-DW3Wj5oi.js} +1 -1
- package/client/dist/assets/{AgentsPage-DGFFijMC.js → AgentsPage-DBMnhdlq.js} +3 -3
- package/client/dist/assets/{AnalyticsPage-hoJmlEpk.js → AnalyticsPage-DjFf1OxU.js} +1 -1
- package/client/dist/assets/{BarChart-BEhAXVK-.js → BarChart-nmDbbUiR.js} +1 -1
- package/client/dist/assets/{CodePage-B4zB9DVC.js → CodePage-DHd46Sm7.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-CBX-Qpxd.js → DesktopAnalyticsPage-ee0bpk-F.js} +1 -1
- package/client/dist/assets/{DocsDialog-lTsnNdAM.js → DocsDialog-CGUzB1zX.js} +1 -1
- package/client/dist/assets/{DocsPage-CkvdGroT.js → DocsPage-96iws84I.js} +1 -1
- package/client/dist/assets/{ExportDropdown-Da98gR-P.js → ExportDropdown-Dzk2Lkxw.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-Cf9o4eZq.js → IntegrationsPage-Bta2lAhq.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-C8N-Bbnn.js → InteractiveJobComposer-B9S_ZGJS.js} +5 -5
- package/client/dist/assets/{JobDetailModal-BZVwIIEx.js → JobDetailModal-DhrwU2z0.js} +1 -1
- package/client/dist/assets/{JobDetailPage-CQewQ1zy.js → JobDetailPage-BnhoXkql.js} +1 -1
- package/client/dist/assets/{JobsPage-CB4wOxmE.js → JobsPage-BBT9fyhg.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-B9Y5gt08.js → LoopBuilderPage-5ucN9Hq9.js} +2 -2
- package/client/dist/assets/{LoopPreviewModal-kxQ5kV1t.js → LoopPreviewModal-wKAts-Wf.js} +1 -1
- package/client/dist/assets/{LoopsPage-RysW-6sC.js → LoopsPage-COyxf6Pa.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-BHN5qsGT.js → ProjectSettingsDialog-CcToBI-Z.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-DW6lZCIs.js → TemplatePreviewModal-_Dy6ceXg.js} +1 -1
- package/client/dist/assets/agent-BmH52J4j.js +1 -0
- package/client/dist/assets/agent-Bxb9RRNW.js +1 -0
- package/client/dist/assets/agent-Cg49-TGZ.js +1 -0
- package/client/dist/assets/agent-DgDtg-0Y2.js +1 -0
- package/client/dist/assets/agent-DolxzTol.js +1 -0
- package/client/dist/assets/agent-DwDR2Exf2.js +1 -0
- package/client/dist/assets/agent-_tfCgFUk.js +1 -0
- package/client/dist/assets/agent-gFi-qvxO2.js +1 -0
- package/client/dist/assets/{brain-B9dkGuAN.js → brain-BJ2Gd98j.js} +1 -1
- package/client/dist/assets/code-CwnBPPyA.js +1 -0
- package/client/dist/assets/dashboard-90r0QcUc2.js +1 -0
- package/client/dist/assets/dashboard-B0C6mJP-.js +1 -0
- package/client/dist/assets/dashboard-BOx0FR1L2.js +1 -0
- package/client/dist/assets/dashboard-BcjUOLJr.js +1 -0
- package/client/dist/assets/dashboard-C27kNpjn.js +1 -0
- package/client/dist/assets/dashboard-DJAj2EbH.js +1 -0
- package/client/dist/assets/dashboard-DNd1Gobs.js +1 -0
- package/client/dist/assets/dashboard-DZWrDAYm2.js +1 -0
- package/client/dist/assets/{dist-js-Bbd7Wzdw.js → dist-js-CXAsO0Z8.js} +1 -1
- package/client/dist/assets/{dist-js-BW5-IZTP.js → dist-js-D7smLA35.js} +1 -1
- package/client/dist/assets/index-B3ejvKSt.js +156 -0
- package/client/dist/assets/index-C695aPdV.css +2 -0
- package/client/dist/assets/{lib-EIkIPf26.js → lib-BIHmUkcn.js} +1 -1
- package/client/dist/assets/{loops-YYV2bwjd.js → loops-Brnx4Fji.js} +1 -1
- package/client/dist/assets/{loops-hXgncEpF2.js → loops-BvqQ-bHR2.js} +1 -1
- package/client/dist/assets/{loops-BvEenWBy.js → loops-CvOWsi5r.js} +1 -1
- package/client/dist/assets/{loops-DjOtVXRo.js → loops-D6D3XHjj.js} +1 -1
- package/client/dist/assets/{loops-HTHf-xu32.js → loops-DJNhp17p2.js} +1 -1
- package/client/dist/assets/{loops-kYbydDyN.js → loops-K5_HXfJa.js} +1 -1
- package/client/dist/assets/loops-LIgO_-eM.js +1 -0
- package/client/dist/assets/{loops-DNGiYKll2.js → loops-vhJdbcz92.js} +1 -1
- package/client/dist/assets/{settings-BsJMsWGc.js → settings-BHdPchJP.js} +1 -1
- package/client/dist/assets/{settings-lFoaYT-s2.js → settings-BeCczBFS2.js} +1 -1
- package/client/dist/assets/{settings-DZJeXoRV.js → settings-BlLi719f.js} +1 -1
- package/client/dist/assets/{settings-BOHOmHkb.js → settings-C_nY14Yz.js} +1 -1
- package/client/dist/assets/{settings-D6qY9_UI.js → settings-CaZ26a0S.js} +1 -1
- package/client/dist/assets/{settings-BlEA_7h02.js → settings-D54_cgl12.js} +1 -1
- package/client/dist/assets/settings-D9bDDe2O.js +1 -0
- package/client/dist/assets/{settings-TNga5gIF2.js → settings-cn0tbve62.js} +1 -1
- package/client/dist/assets/{setup-iY8vH8pB.js → setup-7J8TjG4O.js} +1 -1
- package/client/dist/assets/{setup-D674kWQH.js → setup-BXpw07r-.js} +1 -1
- package/client/dist/assets/{setup-B590KsTX.js → setup-BiduSX9d.js} +1 -1
- package/client/dist/assets/{setup-CmU6bTCV2.js → setup-BuQNKztt2.js} +1 -1
- package/client/dist/assets/{setup-CEj6uY3-2.js → setup-BwMovM9z2.js} +1 -1
- package/client/dist/assets/{setup-BkyGoby6.js → setup-D84y3wx-.js} +1 -1
- package/client/dist/assets/{setup-CbIerFGf2.js → setup-DU0ULX8q2.js} +1 -1
- package/client/dist/assets/{setup-VNqXyB6Y.js → setup-RiUHzq7E.js} +1 -1
- package/client/dist/assets/{upload-BCMl-nCg.js → upload-xdQuJvXN.js} +1 -1
- package/client/dist/assets/{value-CG-amSq1.js → value-Bk2f9AUg.js} +1 -1
- package/client/dist/index.html +22 -9
- package/docs/agy-cli-provider-study.md +1 -1
- package/docs/codex.md +2 -2
- package/docs/gemini-cli-provider-study.md +2 -2
- package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/guide/pt/pipeline/5-the-loop-builder.md +1 -1
- package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +1 -1
- package/docs/internals/api-reference.md +3 -3
- package/docs/internals/companion-rails-as-loops-contract.md +6 -6
- package/docs/internals/configuration.md +1 -1
- package/docs/internals/interactive-jobs.md +3 -3
- package/docs/internals/safe-pr-review-flow.md +1 -1
- package/docs/internals/visual-design-audit.md +2 -2
- package/docs/running-pipelines.md +1 -1
- package/package.json +1 -1
- package/server/dist/agent-chat-manager.js +16 -2
- package/server/dist/agent-chat-router.js +66 -1
- package/server/dist/agent-context-resolver.js +301 -0
- package/server/dist/agent-operator-prompt.js +58 -11
- package/server/dist/agent-store.js +24 -12
- package/server/dist/db.js +17 -17
- package/server/dist/desktop-db.js +6 -0
- package/server/dist/desktop-router.js +9 -2
- package/server/dist/feature-flags.js +2 -2
- package/server/dist/index.js +1 -1
- package/server/dist/interactive-job-session.js +3 -3
- package/server/dist/loop-command-catalog.js +11 -11
- package/server/dist/loop-factory.js +7 -7
- package/server/dist/loop-graph.js +2 -2
- package/server/dist/loop-run-manager.js +1 -1
- package/server/dist/mcp/guide.js +41 -10
- package/server/dist/mcp/tools/app-settings.js +18 -2
- package/server/dist/mcp/tools/catalog.js +2 -0
- package/server/dist/mcp/tools/rails.js +29 -9
- package/server/dist/mcp/tools/support.js +324 -0
- package/server/dist/mobile/mobile-router.js +1 -1
- package/server/dist/project-router-jobs.js +1 -1
- package/server/dist/project-router-loop-runs.js +1 -1
- package/server/dist/project-router-settings.js +5 -5
- package/server/dist/queue-manager.js +27 -27
- package/server/dist/rail-isolated-launch.js +5 -1
- package/server/dist/rails-router.js +76 -27
- package/client/dist/assets/agent-4ARs0uac2.js +0 -1
- package/client/dist/assets/agent-C55wwsUX.js +0 -1
- package/client/dist/assets/agent-C_kqyrXo.js +0 -1
- package/client/dist/assets/agent-Df3lnHMu.js +0 -1
- package/client/dist/assets/agent-G6Zn2gpr.js +0 -1
- package/client/dist/assets/agent-MQhun7cv.js +0 -1
- package/client/dist/assets/agent-NqlER4Yh2.js +0 -1
- package/client/dist/assets/agent-OTGI6t7e2.js +0 -1
- package/client/dist/assets/code-CqCcRThR.js +0 -1
- package/client/dist/assets/dashboard-4WX7agHF2.js +0 -1
- package/client/dist/assets/dashboard-5Z_esXIB.js +0 -1
- package/client/dist/assets/dashboard-DQ-po1MG2.js +0 -1
- package/client/dist/assets/dashboard-IbEztmJ52.js +0 -1
- package/client/dist/assets/dashboard-nc-Raovv.js +0 -1
- package/client/dist/assets/dashboard-ojWdDq6S.js +0 -1
- package/client/dist/assets/dashboard-oqS7VRrK.js +0 -1
- package/client/dist/assets/dashboard-zfoJcdFW.js +0 -1
- package/client/dist/assets/index-BtAks9nm.css +0 -2
- package/client/dist/assets/index-DC-REAcL.js +0 -155
- package/client/dist/assets/loops-CBiWd9Hf.js +0 -1
- 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 (
|
|
41
|
-
\`
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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".
|
|
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
|
|
347
|
-
with its own stored mode/engine/profile, and returns per-rail
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
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
|
|
1230
|
-
* has no per-project override.
|
|
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.
|
|
1235
|
-
'You are operating in
|
|
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
|
|
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
|
-
|
|
1251
|
+
freestylePrePrompt: freestylePrePromptRow?.value ?? '',
|
|
1252
1252
|
integrationBranch: integrationBranchRow?.value ?? '',
|
|
1253
1253
|
};
|
|
1254
1254
|
}
|
|
1255
|
-
/** Resolve the effective
|
|
1255
|
+
/** Resolve the effective Freestyle pre-prompt: the per-project override when
|
|
1256
1256
|
* set, otherwise the built-in default. */
|
|
1257
|
-
function
|
|
1258
|
-
const override = getProjectSettings(db).
|
|
1259
|
-
return override || exports.
|
|
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.
|
|
1277
|
-
if (patch.
|
|
1278
|
-
db.prepare(`DELETE FROM queue_state WHERE key = 'config.
|
|
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.
|
|
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', '
|
|
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
|
|
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
|
|
33
|
-
* toggle, an
|
|
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
|
package/server/dist/index.js
CHANGED
|
@@ -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' (
|
|
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-
|
|
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
|
|
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
|
|
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.
|
|
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 (
|
|
28
|
-
* LoopRunManager path. Factory
|
|
29
|
-
* `
|
|
30
|
-
const
|
|
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: '
|
|
72
|
-
label: '
|
|
73
|
-
description: '
|
|
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
|
|
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.
|
|
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:
|
|
34
|
-
//
|
|
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
|
|
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: '
|
|
39
|
+
mode: 'freestyle',
|
|
40
40
|
claudeOnly: true,
|
|
41
|
-
graph: (0, loop_templates_1.fixLoopGraph)(['{{cmd:
|
|
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 === '
|
|
71
|
-
return FACTORY_BY_ID.get('factory:
|
|
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|
|
|
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|
|
|
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/
|
|
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}` : ''}`;
|
package/server/dist/mcp/guide.js
CHANGED
|
@@ -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 /
|
|
109
|
-
PR decision in one call, each with its stored
|
|
110
|
-
per-rail outcomes (launched / skipped with
|
|
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
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
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
|
-
|
|
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', '
|
|
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
|
|
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;
|