specrails-desktop 2.59.2 → 2.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/client/dist/assets/{ActivityFeedPage-vAY9C71W.js → ActivityFeedPage-3J7fR8yB.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-CpwjGXQm.js → AgentBrowserCapture-D4a3wYM1.js} +1 -1
  3. package/client/dist/assets/{AgentModeAnalyticsPane-BHeoJVxQ.js → AgentModeAnalyticsPane-Dps30tk9.js} +2 -2
  4. package/client/dist/assets/{AgentModeCodePane-B0Nf3YkN.js → AgentModeCodePane-CvLimey8.js} +2 -2
  5. package/client/dist/assets/{AgentModeJobsPane-CpbIItY_.js → AgentModeJobsPane-25tQlLpF.js} +1 -1
  6. package/client/dist/assets/{AgentsPage-BVJAFXn3.js → AgentsPage-CUDp_sfw.js} +2 -2
  7. package/client/dist/assets/{AnalyticsPage-DDWees-K.js → AnalyticsPage-DXWMGn8b.js} +1 -1
  8. package/client/dist/assets/{CodePage-CzV5tkvS.js → CodePage-D981zRHy.js} +1 -1
  9. package/client/dist/assets/{DesktopAnalyticsPage-DfRHNK7i.js → DesktopAnalyticsPage-BkFBq0E6.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-BxduBYbh.js → DocsDialog-BL7uSbIJ.js} +1 -1
  11. package/client/dist/assets/{DocsPage-DOVeRJn_.js → DocsPage-CsDLbmZX.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-BnKVV8UK.js → ExportDropdown-B4Olg1Fk.js} +1 -1
  13. package/client/dist/assets/{InteractiveJobComposer-Da9dvM-0.js → InteractiveJobComposer-Z6fl4791.js} +2 -2
  14. package/client/dist/assets/JobDetailModal-DS_Owxs6.js +1 -0
  15. package/client/dist/assets/JobDetailPage-1TUQ9vlE.js +1 -0
  16. package/client/dist/assets/{JobsPage-0vdlbNik.js → JobsPage-BSrl8P7u.js} +1 -1
  17. package/client/dist/assets/LoopBuilderPage-Dh5uk2Qy.js +1 -0
  18. package/client/dist/assets/{LoopPreviewModal-Df-S8NGf.js → LoopPreviewModal-CH1r-KhU.js} +1 -1
  19. package/client/dist/assets/{LoopWindowSurface-sdb7QbSo.js → LoopWindowSurface-CiZdI6aw.js} +1 -1
  20. package/client/dist/assets/{LoopsPage-CDfCC9Px.js → LoopsPage-BwFujdGi.js} +1 -1
  21. package/client/dist/assets/{MinimizedChatsContext-DKlPf54Y.js → MinimizedChatsContext-BD2bsRBe.js} +1 -1
  22. package/client/dist/assets/PluginsPage-B8f49Mvn.js +2 -0
  23. package/client/dist/assets/{ProjectSettingsDialog-DOkjT7xv.js → ProjectSettingsDialog-D7q-O6h3.js} +1 -1
  24. package/client/dist/assets/{RepositoryDeliveries-CJVpOUpZ.js → RepositoryDeliveries-NNorcGzT.js} +1 -1
  25. package/client/dist/assets/{RepositoryScopeSelector-BeM-2gIZ.js → RepositoryScopeSelector-D-jb4bI_.js} +1 -1
  26. package/client/dist/assets/{ReviewPacketPage-EBCim2w_.js → ReviewPacketPage-DpZSzF-Q.js} +1 -1
  27. package/client/dist/assets/{TemplatePreviewModal-1-92UGwN.js → TemplatePreviewModal-5CZdAZ0G.js} +1 -1
  28. package/client/dist/assets/{TicketDetailModalContext-CkVXuHxU.js → TicketDetailModalContext-BHmtj1As.js} +1 -1
  29. package/client/dist/assets/{Trans-CzhPjnjB.js → Trans-wmTUDVyC.js} +1 -1
  30. package/client/dist/assets/{agentRuntime-TXPQowKJ.js → agentRuntime-7Ydcvr6P.js} +1 -1
  31. package/client/dist/assets/{agentRuntime-Bpx3yWHK.js → agentRuntime-BHVw9orA.js} +1 -1
  32. package/client/dist/assets/{agentRuntime-15Pkk6we.js → agentRuntime-DCPDaQcV.js} +1 -1
  33. package/client/dist/assets/{agentRuntime-C78n13To.js → agentRuntime-DKTw4Ajg.js} +1 -1
  34. package/client/dist/assets/{agentRuntime-CCI7XF4c.js → agentRuntime-DVhij_i_.js} +1 -1
  35. package/client/dist/assets/{agentRuntime-Ddqvp2bP.js → agentRuntime-Doss-T8o.js} +1 -1
  36. package/client/dist/assets/{agentRuntime-CKDHe2-a.js → agentRuntime-eZSMUlkM.js} +1 -1
  37. package/client/dist/assets/{agentRuntime-hqZRy08E.js → agentRuntime-qSSU7fQk.js} +1 -1
  38. package/client/dist/assets/agents-B6ZsnARo.js +1 -0
  39. package/client/dist/assets/agents-BORdhe0K.js +1 -0
  40. package/client/dist/assets/agents-C6aq9Oqs.js +1 -0
  41. package/client/dist/assets/agents-Cbsc4bcX.js +1 -0
  42. package/client/dist/assets/agents-D0uYR6hq.js +1 -0
  43. package/client/dist/assets/agents-Doe1sHY5.js +1 -0
  44. package/client/dist/assets/agents-DyNi_I9m.js +1 -0
  45. package/client/dist/assets/agents-StKxiTg0.js +1 -0
  46. package/client/dist/assets/agentstudio-7cYCXn-L.js +1 -0
  47. package/client/dist/assets/{agentstudio-QdAJIDef.js → agentstudio-BHWQh8G3.js} +1 -1
  48. package/client/dist/assets/agentstudio-BjnWHyRs.js +1 -0
  49. package/client/dist/assets/{agentstudio-fLXpRGIi.js → agentstudio-BkV8vcK4.js} +1 -1
  50. package/client/dist/assets/{agentstudio-BWAKeGgB.js → agentstudio-BzugqB_N.js} +1 -1
  51. package/client/dist/assets/{agentstudio-Cja8Sjcp.js → agentstudio-CWbp7KvH.js} +1 -1
  52. package/client/dist/assets/agentstudio-DG6plf5o.js +1 -0
  53. package/client/dist/assets/agentstudio-DkZVF-Cc.js +1 -0
  54. package/client/dist/assets/{formatDistanceToNow-DlQaXc7c.js → formatDistanceToNow-Dax4zqPF.js} +1 -1
  55. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxUFGcVq.js → getTimezoneOffsetInMilliseconds-dpIo42JZ.js} +1 -1
  56. package/client/dist/assets/{index-Czov72g7.js → index-g8S62tpS.js} +7 -7
  57. package/client/dist/assets/jira-B6OYw4CJ.js +1 -0
  58. package/client/dist/assets/jira-BTF6RaOe.js +1 -0
  59. package/client/dist/assets/jira-CEetIB2q.js +1 -0
  60. package/client/dist/assets/jira-CIzsZLw5.js +1 -0
  61. package/client/dist/assets/{jira-BOoaUXbb.js → jira-Cq6yvN4q.js} +1 -1
  62. package/client/dist/assets/jira-Daw9tmpI.js +1 -0
  63. package/client/dist/assets/jira-FXr7u2lh.js +1 -0
  64. package/client/dist/assets/jira-LaNQn7yl.js +1 -0
  65. package/client/dist/assets/{jira-api-CbJvbRYx.js → jira-api-CLrIvXFm.js} +1 -1
  66. package/client/dist/assets/{jobs-DZAbh1m2.js → jobs-ARwD85B9.js} +1 -1
  67. package/client/dist/assets/{jobs-BD47Ov11.js → jobs-BMF1w4WT.js} +1 -1
  68. package/client/dist/assets/{jobs-tWjcDF_6.js → jobs-CBfzGLwm.js} +1 -1
  69. package/client/dist/assets/{jobs-CSrRx5lD.js → jobs-CPjiluBY.js} +1 -1
  70. package/client/dist/assets/{jobs-epP7gt8W.js → jobs-CQQd58pY.js} +1 -1
  71. package/client/dist/assets/{jobs-1G3wodhl.js → jobs-De3EqaO0.js} +1 -1
  72. package/client/dist/assets/{jobs-BLOoVRRl.js → jobs-Du9-Ei9a.js} +1 -1
  73. package/client/dist/assets/{jobs-B4pu8TdF.js → jobs-qMHbnxwC.js} +1 -1
  74. package/client/dist/assets/{project-repositories-BkBD3rxx.js → project-repositories-BkyT8AjU.js} +1 -1
  75. package/client/dist/assets/{spending-CYotH-9N.js → spending-Oov7p52A.js} +1 -1
  76. package/client/dist/assets/{useDesktop-DpxVGfUC.js → useDesktop-DpiS8Uov.js} +1 -1
  77. package/client/dist/assets/{useRuntimeRuns-CfGRbhr9.js → useRuntimeRuns-DBm1mul0.js} +1 -1
  78. package/client/dist/assets/{useSharedWebSocket-C17TGqT8.js → useSharedWebSocket-BZxKaVNT.js} +2 -2
  79. package/client/dist/index.html +19 -19
  80. package/docs/getting-started.md +2 -2
  81. package/docs/guide/de/agents/1-meet-the-agents.md +1 -1
  82. package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +1 -1
  83. package/docs/guide/de/agents/3-customizing-models-per-agent.md +1 -1
  84. package/docs/guide/de/agents/4-custom-agents-catalog.md +3 -3
  85. package/docs/guide/en/agents/1-meet-the-agents.md +1 -1
  86. package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +1 -1
  87. package/docs/guide/en/agents/3-customizing-models-per-agent.md +1 -1
  88. package/docs/guide/en/agents/4-custom-agents-catalog.md +3 -3
  89. package/docs/guide/es/agents/1-meet-the-agents.md +1 -1
  90. package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +1 -1
  91. package/docs/guide/es/agents/3-customizing-models-per-agent.md +1 -1
  92. package/docs/guide/es/agents/4-custom-agents-catalog.md +3 -3
  93. package/docs/guide/fr/agents/1-meet-the-agents.md +1 -1
  94. package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +1 -1
  95. package/docs/guide/fr/agents/3-customizing-models-per-agent.md +1 -1
  96. package/docs/guide/fr/agents/4-custom-agents-catalog.md +3 -3
  97. package/docs/guide/it/agents/1-meet-the-agents.md +1 -1
  98. package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +1 -1
  99. package/docs/guide/it/agents/3-customizing-models-per-agent.md +1 -1
  100. package/docs/guide/it/agents/4-custom-agents-catalog.md +3 -3
  101. package/docs/guide/ja/agents/1-meet-the-agents.md +1 -1
  102. package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +1 -1
  103. package/docs/guide/ja/agents/3-customizing-models-per-agent.md +1 -1
  104. package/docs/guide/ja/agents/4-custom-agents-catalog.md +3 -3
  105. package/docs/guide/pt/agents/1-meet-the-agents.md +1 -1
  106. package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +1 -1
  107. package/docs/guide/pt/agents/3-customizing-models-per-agent.md +1 -1
  108. package/docs/guide/pt/agents/4-custom-agents-catalog.md +3 -3
  109. package/docs/guide/zh/agents/1-meet-the-agents.md +1 -1
  110. package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +1 -1
  111. package/docs/guide/zh/agents/3-customizing-models-per-agent.md +1 -1
  112. package/docs/guide/zh/agents/4-custom-agents-catalog.md +3 -3
  113. package/docs/internals/adding-a-provider.md +7 -6
  114. package/docs/internals/configuration.md +45 -0
  115. package/docs/internals/core-runtime-updates.md +19 -0
  116. package/docs/internals/loop-step-log-explorer.md +27 -0
  117. package/docs/internals/mission-rail-cards.md +7 -0
  118. package/docs/internals/profiles.md +17 -10
  119. package/docs/internals/review-packet.md +1 -1
  120. package/docs/internals/safe-pr-review-flow.md +9 -0
  121. package/docs/internals/source-map.md +6 -2
  122. package/docs/jira-integration-plan.md +7 -4
  123. package/docs/running-pipelines.md +2 -2
  124. package/package.json +1 -1
  125. package/server/dist/core-compat.js +7 -3
  126. package/server/dist/core-execution.js +5 -18
  127. package/server/dist/jira/jira-db.js +33 -6
  128. package/server/dist/jira/jira-materializer.js +11 -9
  129. package/server/dist/jira/jira-status-resolver.js +64 -22
  130. package/server/dist/jira/jira-sync-manager.js +40 -1
  131. package/server/dist/jira-router.js +8 -41
  132. package/server/dist/mcp/guide.js +4 -3
  133. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +22 -70
  134. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +7 -4
  135. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +24 -1
  136. package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-log.js +252 -0
  137. package/server/dist/modules/agents/runtime/profiles-router.js +74 -36
  138. package/server/dist/modules/delivery/runtime/rail-relaunch.js +10 -2
  139. package/server/dist/modules/execution/runtime/queue-manager.js +7 -9
  140. package/server/dist/modules/loops/runtime/loop-agents.js +2 -5
  141. package/server/dist/modules/loops/runtime/loop-command-catalog.js +5 -5
  142. package/server/dist/modules/loops/runtime/loop-definition.js +1 -1
  143. package/server/dist/modules/loops/runtime/loop-executors.js +0 -2
  144. package/server/dist/modules/loops/runtime/loop-implement-recipe.js +5 -4
  145. package/server/dist/modules/loops/runtime/loop-run-manager.js +5 -3
  146. package/server/dist/modules/loops/runtime/loops-router.js +3 -1
  147. package/server/dist/path-resolver.js +41 -20
  148. package/server/dist/project-env.js +22 -6
  149. package/server/dist/providers/gemini-agent-ack.js +13 -10
  150. package/server/dist/setup-manager.js +28 -43
  151. package/server/dist/util/checkout-path.js +22 -0
  152. package/server/dist/workspace-manager.js +13 -71
  153. package/server/dist/worktree-node-modules.js +70 -34
  154. package/client/dist/assets/JobDetailModal-DhwKbZGe.js +0 -1
  155. package/client/dist/assets/JobDetailPage-MtPE-E_1.js +0 -1
  156. package/client/dist/assets/LoopBuilderPage-DCUHozXh.js +0 -1
  157. package/client/dist/assets/PluginsPage-iL0G3rQQ.js +0 -2
  158. package/client/dist/assets/agents-CA5eT1d0.js +0 -1
  159. package/client/dist/assets/agents-CAgy-5uS.js +0 -1
  160. package/client/dist/assets/agents-CNm6Qzlv.js +0 -1
  161. package/client/dist/assets/agents-CTwbx-fb.js +0 -1
  162. package/client/dist/assets/agents-DEGV1Vao.js +0 -1
  163. package/client/dist/assets/agents-HsDF1aQU.js +0 -1
  164. package/client/dist/assets/agents-IQjdA-z9.js +0 -1
  165. package/client/dist/assets/agents-RkGoteXh.js +0 -1
  166. package/client/dist/assets/agentstudio-B2CTtQgZ.js +0 -1
  167. package/client/dist/assets/agentstudio-DtfPcr99.js +0 -1
  168. package/client/dist/assets/agentstudio-X70BKf6u.js +0 -1
  169. package/client/dist/assets/agentstudio-qXKqVnWS.js +0 -1
  170. package/client/dist/assets/jira-BNZu8f9O.js +0 -1
  171. package/client/dist/assets/jira-BYVN4L2u.js +0 -1
  172. package/client/dist/assets/jira-CKJg3HZd.js +0 -1
  173. package/client/dist/assets/jira-DDDsZ4ub.js +0 -1
  174. package/client/dist/assets/jira-DXwYjVE4.js +0 -1
  175. package/client/dist/assets/jira-Dn6U2nRW.js +0 -1
  176. package/client/dist/assets/jira-j7hVolR5.js +0 -1
  177. package/server/dist/modules/loops/runtime/loop-agent-defaults.json +0 -6
@@ -61,7 +61,7 @@ export const exampleAdapter: ProviderAdapter = {
61
61
  buildArgs: (action: SpawnAction, opts: SpawnOptions): string[] => { /* per-action argv */ },
62
62
  parseStreamLine: (line: string): AdapterEvent | null => { /* line → canonical event */ },
63
63
  extractResult: (events): NormalisedResult => { /* events → tokens/cost/session */ },
64
- baselineAgents: () => ['sr-architect', 'sr-developer', 'sr-reviewer'],
64
+ baselineAgents: () => ['sr-architect', 'sr-developer', 'sr-reviewer'], // role identifiers, not installed files
65
65
  detectInstalled: async (): Promise<DetectionResult> => { /* `which` + `--version` */ },
66
66
  // Optional — see "(Optional) prepareHeadlessSpawn" below. Gemini uses it to
67
67
  // pre-acknowledge project subagents so they load in headless `gemini -p` rails.
@@ -79,11 +79,12 @@ the optional
79
79
  `prepareHeadlessSpawn` hook, `systemPromptArg: false` system-prompt folding
80
80
  (via the `GEMINI_SYSTEM_MD` env), native OTEL with `nativeCostUsd: false`,
81
81
  and a per-action `buildArgs` switch that throws defensively on the
82
- `chat-stream` action it doesn't support. Note the shipped baseline is the
83
- three-agent trio `['sr-architect', 'sr-developer', 'sr-reviewer']`;
84
- `ProfileManager` validation requires exactly your `baselineAgents()` to
85
- be present in every profile chain, so don't add agents your scaffold
86
- won't actually create.
82
+ `chat-stream` action it doesn't support. Note the baseline is the
83
+ three role identifiers `['sr-architect', 'sr-developer', 'sr-reviewer']`;
84
+ they name roles the Core runtime defines, not files your scaffold installs,
85
+ and installation detection is never derived from them. `ProfileManager`
86
+ validation requires exactly your `baselineAgents()` to be present in every
87
+ profile chain, so don't add identifiers the runtime won't actually route.
87
88
 
88
89
  The `SpawnAction` union also includes **`chat-stream`** — used by the
89
90
  Explore persistent-stdin fast-path and interactive jobs. Only providers
@@ -82,6 +82,31 @@ Project settings apply to a single project. Open them from the project's **Setti
82
82
 
83
83
  ---
84
84
 
85
+ ## Jira status mapping and pending updates
86
+
87
+ In the project's Jira connector, map **To Do**, **In Progress**, **On Review**,
88
+ **Done** and **Cancelled** to the real Jira statuses you want to use. Automatic
89
+ uses Jira's status categories; an explicit selection uses the selected status
90
+ identity, even when its category differs. The API accepts status names or status
91
+ IDs, never transition IDs. Both
92
+ outbound transitions and inbound polling honor the mapping. When several logical
93
+ states share a destination, On Review retains precedence; other ambiguities use
94
+ the category-derived state. Prefer distinct destinations when possible.
95
+
96
+ The pending-updates panel identifies the issue and requested destination. After
97
+ correcting a mapping or a Jira workflow restriction, retry the current failed
98
+ operation. A failed transition replaced by a later status intention is retained
99
+ as `superseded` history and removed from attention counts; retrying it cannot
100
+ revert newer work. Comments and field updates remain independently retryable.
101
+ Older pending retries are also suppressed when a newer status transition has
102
+ already completed. Normal pending operations retain their per-issue FIFO order.
103
+
104
+ Jira still determines which transitions the signed-in account can apply. A
105
+ missing reverse transition cannot be bypassed by selecting a target, and the
106
+ connector does not move through arbitrary statuses to discover possible paths.
107
+ If loading the status list fails, the connector preserves the saved mapping and
108
+ offers a retry instead of silently reducing the picker to Automatic.
109
+
85
110
  ## CLI
86
111
 
87
112
  The `specrails-desktop` CLI is documented in full in [../cli.md](../cli.md). Quick reference:
@@ -134,6 +159,26 @@ specrails-desktop --port 5000 implement "#42"
134
159
 
135
160
  ## Environment variables
136
161
 
162
+ ### Project execution environment
163
+
164
+ Project Settings stores the **names** of environment variables to pass through,
165
+ such as `NODE_AUTH_TOKEN`; it never stores their values. One parent project setting
166
+ applies to every repository and workspace admitted to its rails, including isolated
167
+ worktrees. The same policy is applied to retained runtime controls and recovery.
168
+
169
+ Non-empty values already inherited by Desktop take precedence. For missing configured names
170
+ on macOS/Linux, Desktop probes the login shell, using `SHELL` or the account's login
171
+ shell when a GUI launch does not supply it. A zsh login shell reads `~/.zprofile`
172
+ (and interactive configuration); configure the real credential there rather than
173
+ adding it to tracked repository files. Windows uses its inherited environment.
174
+
175
+ Recovered values stay in a project-owned in-memory overlay, not the server's global
176
+ environment, SQLite or runtime snapshots. Each project's lookup cache expires after
177
+ 30 seconds, including failed/empty results, so a repaired profile or rotated shell
178
+ credential can be picked up without restarting Desktop. Removing a configured name
179
+ drops its recovered value immediately. A missing value remains absent: Desktop never
180
+ substitutes a dummy credential or treats a blocked package-manager command as passed.
181
+
137
182
  A few env vars are read directly by the app. The two most useful operational ones:
138
183
 
139
184
  | Variable | Description |
@@ -68,6 +68,25 @@ all workspace refreshes and the final recovery-state write succeed.
68
68
  Core 6 registry updates and same-version runtime repairs are offered using the
69
69
  same supported-major policy as runtime selection.
70
70
 
71
+ ## Role definitions follow the engine
72
+
73
+ Core owns how the implement roles think: `rolePromptDefaults()` returns the
74
+ factory architect, developer, reviewer and fixer definitions (one shared senior
75
+ engineer identity, blast-radius discipline, quality bar and the mandatory
76
+ OpenSpec binding). Desktop owns the workflows that use those roles, and never
77
+ keeps a copy of those definitions.
78
+
79
+ - The Implement rail (engine 1) reads the defaults from the selected Core at
80
+ launch, merged with the user's global overrides from Settings.
81
+ - Loop recipes store `inherit` for the built-in definitions (and
82
+ `inherit:<role>` for declared recipe roles such as `plan`/`build`). The
83
+ launch bridge resolves them to the same Core defaults plus global overrides
84
+ and freezes the result with the run, so every loop follows the installed
85
+ Core automatically. Text the user edits in the loop editor is stored verbatim
86
+ and stays frozen with the loop; "Use the Core definition" returns a role to
87
+ `inherit`. `/api/loop-agent-defaults` returns `rolePromptDefaults` so the
88
+ editor can preview what `inherit` resolves to today.
89
+
71
90
  ## Version reporting
72
91
 
73
92
  Global status distinguishes the selected runtime and its source, the active
@@ -147,6 +147,33 @@ One chip per graph node, states `pending → running → ok / failed / interrupt
147
147
  - **Filtering** searches every section: while the filter is non-empty, all sections are treated as expanded; clearing it restores the previous mode.
148
148
  - **Copy**: each step header carries its own copy button (per-step lines only; the control lives outside the toggle so the header stays a valid button), and the toolbar copy grabs the whole log with `── Step N · title ──` separators.
149
149
 
150
+ ### Verbose verification and retained display
151
+
152
+ Desktop compacts verification output before publishing readable log lines. Passing
153
+ cases and repeated console stacks no longer flood the rail; bounded diagnostic
154
+ context, final test totals and recorded command outcomes remain visible. Omission
155
+ notices distinguish a concise display from the original runtime evidence. This
156
+ changes presentation only, including when tests print warnings or errors but exit
157
+ successfully; recorded exit codes and workflow completion remain authoritative.
158
+
159
+ Both job detail surfaces omit invisible raw `verification-output` events from
160
+ their in-memory display buffer and preserve lifecycle markers when bounding
161
+ ordinary output. A translated notice indicates when earlier readable output was
162
+ removed and that Copy contains only the retained view. Initial history and live
163
+ updates use the same retention rules. Persisted events are unchanged.
164
+
165
+ For detailed verification output, expand **Details → Verification evidence** and
166
+ open a check's stdout or stderr, using the next-page control as needed. These are
167
+ the retained Core artifacts: currently up to 1 MiB per stream, with truncation
168
+ reported explicitly. They follow the configured runtime retention policy and are
169
+ not an unlimited archive. Compaction does not rewrite historical logs in storage.
170
+
171
+ Readable runtime lines preserve an optional recorded `attemptId` in both stored
172
+ log payloads and live WebSocket frames. The existing attempt map uses it to attach
173
+ buffered verification summaries to their originating step, including parallel
174
+ attempts. Older lines without that identity retain their existing arrival-order
175
+ fallback; Desktop does not invent an attempt ID.
176
+
150
177
  ## Performance
151
178
 
152
179
  - **Collapsed sections render nothing** — the body subtree is not mounted, so a 10k-line settled step costs one header row.
@@ -48,6 +48,13 @@ with allocation rollback and the original failed checkout retained. A synchronou
48
48
  click guard prevents duplicate submissions; accepted relaunches stay disabled
49
49
  until the new card appears, and rejected admission details remain inline.
50
50
 
51
+ Workspace narrowing is optional: when a delivery manifest has no explicit
52
+ workspace selections, Relaunch omits `workspaceSelection` and uses registered
53
+ defaults for its recorded repositories. Partial selections remain partial and
54
+ saved explicit selections take precedence; invalid selections still fail normal
55
+ admission. Removing the last repository with a workspace override from a launch
56
+ card also omits the property, instead of sending an invalid empty map.
57
+
51
58
  ```rail-launch
52
59
  {
53
60
  "version": 1,
@@ -21,20 +21,22 @@ Dashboard/Jobs/Analytics/Settings).
21
21
  - **Profiles** tab — create and edit profiles.
22
22
  - **Usage** tab — see which profiles are actually being used (backed by
23
23
  the `/analytics` endpoint covered in section 8).
24
- - **Catalog** tab — read the upstream `sr-*` agents or author custom
25
- `custom-*` ones via the Studio.
24
+ - **Catalog** tab — read the runtime-defined baseline roles (`sr-architect`,
25
+ `sr-developer`, `sr-reviewer`, shown read-only with the definition the Core
26
+ runtime executes) or author custom `custom-*` ones via the Studio.
26
27
 
27
28
  When the project has no profiles yet, the empty state offers two entry
28
29
  points:
29
30
 
30
- - **Migrate from current agents** — reads the primary provider's native role
31
- catalog (`.claude/agents/` or `.kimi-code/skills/`) and creates a
32
- `default` profile mirroring today's behavior. It requires the baseline trio
33
- `sr-architect`, `sr-developer`, and `sr-reviewer` to be present — the
34
- server rejects the migration if any is missing. Provider-specific model
35
- identifiers are retained only when they are in that provider's catalog;
36
- otherwise migration uses its default and stamps the provider into the saved
37
- profile.
31
+ - **Migrate from current agents** — seeds the baseline trio `sr-architect`,
32
+ `sr-developer` and `sr-reviewer` as role identifiers with the provider's
33
+ default model, reads the primary provider's native role catalog
34
+ (`.claude/agents/` or `.kimi-code/skills/`) for custom roles and creates a
35
+ `default` profile mirroring today's behavior. No `sr-*` file has to exist:
36
+ a `model:` is read only from role files an older install still carries.
37
+ Provider-specific model identifiers are retained only when they are in that
38
+ provider's catalog; otherwise migration uses its default and stamps the
39
+ provider into the saved profile.
38
40
  - **Blank profile** — start from scratch.
39
41
 
40
42
  ## 2. Saved profiles vs selection
@@ -128,6 +130,11 @@ template/duplicate/edit and rail execution remain available.
128
130
  `sr-developer`. Fix the listed issues and Save re-enables.
129
131
  - **"agent 'xyz' already exists" (409)** — the name collides with an
130
132
  existing provider role. Pick a different name.
133
+ - **No `.claude/agents/sr-*.md` in the project** — expected. The baseline
134
+ roles are defined by the Core runtime, not installed as files; read them in
135
+ the Catalog tab and edit their definitions, provider and model in
136
+ **Settings → Agent runtime**. Files left behind by an older Core are
137
+ tolerated until Core prunes them.
131
138
  - **The whole Agents section is missing** — it can be disabled server-side
132
139
  with `SPECRAILS_AGENTS_SECTION=false`, which 404s the entire
133
140
  `/profiles` router. Unset it (or leave it at its default) to restore the
@@ -140,7 +140,7 @@ one-active-generation-per-rail all come free from the shipped machinery.
140
140
 
141
141
  **The loop.** `factory:revision` is the fourth factory loop: `{{cmd:revise}}` (a
142
142
  distilled step that applies the one requested change on top of the branch and
143
- then runs `sr-reviewer` over the resulting diff, so packet v2 keeps its reviewer
143
+ then runs the reviewer role (`sr-reviewer`) over the resulting diff, so packet v2 keeps its reviewer
144
144
  tier) followed by the standard `{{cmd:verify}}`. No Architect step — the plan and
145
145
  the code already exist. It is resolvable by id but deliberately NOT listed in the
146
146
  public catalog: its prompt consumes `{{const:REVISION_REQUEST}}`, which only a
@@ -124,6 +124,15 @@ Both temporary checkouts use non-force cleanup, including after a Git runner exc
124
124
  recording the failed replacement id so every client can safely undo its tombstone for that pair.
125
125
  - Isolation units remain `per-ticket` or one `all`-scope batch unit. Initial/final SHAs plus commit
126
126
  verification distinguish changed, resumed, and no-change results.
127
+ - Warm dependency preparation preserves a registered directory's position inside its Git checkout.
128
+ For example, registering `apps/catalog` prepares its installed packages and Yarn state beneath
129
+ `worktree/apps/catalog/node_modules`. Discovery stays relative to the registered directory
130
+ (depth at most two); unrelated root and sibling installations are not selected. Package entries
131
+ link to the source installation, while tool caches remain local. Existing real installations and
132
+ foreign links are preserved. Live cleanup uses the same projection and exact target fingerprints,
133
+ including proofs for historical misplaced links; symlinked destination ancestors confer no
134
+ authority. Registration paths, frozen manifests and Core scope remain unchanged. A failed run
135
+ keeps its old checkout; after updating Desktop, a fresh Relaunch receives the corrected layout.
127
136
  - Per-unit settlement returns structured execution + delivery results. `onLoopRunFinished` receives
128
137
  the engine outcome only; commit/status/ref/provenance/push failures cannot rewrite it.
129
138
  - Before staging, settlement blocks active OpenSpec change directories introduced relative to
@@ -5,7 +5,7 @@ architectural dependency declaration. Search a heading or filename and read only
5
5
  the relevant source and tests. Feature prefixes group the legacy server files;
6
6
  new bounded modules live under `server/modules/`.
7
7
 
8
- Includes 1014 source/build files. Nearby tests are linked where names
8
+ Includes 1018 source/build files. Nearby tests are linked where names
9
9
  match; integration suites may live elsewhere. Use `rg` to find other consumers.
10
10
 
11
11
  ## cli
@@ -79,7 +79,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
79
79
 
80
80
  - [AgentRoleFields.tsx](../../client/src/features/agents/components/AgentRoleFields.tsx) · [test](../../client/src/features/agents/components/__tests__/AgentRoleFields.test.tsx)
81
81
  - [AgentStudio.tsx](../../client/src/features/agents/components/AgentStudio.tsx)
82
- - [AgentsCatalogTab.tsx](../../client/src/features/agents/components/AgentsCatalogTab.tsx)
82
+ - [AgentsCatalogTab.tsx](../../client/src/features/agents/components/AgentsCatalogTab.tsx) · [test](../../client/src/features/agents/components/__tests__/AgentsCatalogTab.test.tsx)
83
83
  - [AiRefineOverlay.tsx](../../client/src/features/agents/components/AiRefineOverlay.tsx)
84
84
  - [ProfileAnalyticsCard.tsx](../../client/src/features/agents/components/ProfileAnalyticsCard.tsx)
85
85
  - [ProfileEditor.tsx](../../client/src/features/agents/components/ProfileEditor.tsx) · [test](../../client/src/features/agents/components/__tests__/ProfileEditor.test.tsx)
@@ -317,6 +317,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
317
317
  - [DiscardSpecDialog.tsx](../../client/src/features/integrations/components/jira/DiscardSpecDialog.tsx) · [test](../../client/src/features/integrations/components/jira/__tests__/DiscardSpecDialog.test.tsx)
318
318
  - [JiraConnectWizard.tsx](../../client/src/features/integrations/components/jira/JiraConnectWizard.tsx) · [test](../../client/src/features/integrations/components/jira/__tests__/JiraConnectWizard.test.tsx)
319
319
  - [JiraConnectedCard.tsx](../../client/src/features/integrations/components/jira/JiraConnectedCard.tsx)
320
+ - [JiraOutboxPanel.tsx](../../client/src/features/integrations/components/jira/JiraOutboxPanel.tsx) · [test](../../client/src/features/integrations/components/jira/__tests__/JiraOutboxPanel.test.tsx)
320
321
  - [JiraSpecDetailsPanel.tsx](../../client/src/features/integrations/components/jira/JiraSpecDetailsPanel.tsx) · [test](../../client/src/features/integrations/components/jira/__tests__/JiraSpecDetailsPanel.test.tsx)
321
322
 
322
323
  ## client/src/features/integrations/context
@@ -365,6 +366,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
365
366
  ## client/src/features/jobs/lib
366
367
 
367
368
  - [cancel-job.ts](../../client/src/features/jobs/lib/cancel-job.ts) · [test](../../client/src/features/jobs/lib/__tests__/cancel-job.test.ts)
369
+ - [job-event-buffer.ts](../../client/src/features/jobs/lib/job-event-buffer.ts) · [test](../../client/src/features/jobs/lib/__tests__/job-event-buffer.test.ts)
368
370
  - [job-log-mode.ts](../../client/src/features/jobs/lib/job-log-mode.ts)
369
371
  - [job-time.ts](../../client/src/features/jobs/lib/job-time.ts)
370
372
 
@@ -1093,6 +1095,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
1093
1095
  - [agent-runtime-settings-router.ts](../../server/modules/agent-runtime/runtime/agent-runtime-settings-router.ts)
1094
1096
  - [agent-runtime-settings.ts](../../server/modules/agent-runtime/runtime/agent-runtime-settings.ts) · [test](../../server/modules/agent-runtime/runtime/agent-runtime-settings.test.ts)
1095
1097
  - [agent-runtime-settlement.ts](../../server/modules/agent-runtime/runtime/agent-runtime-settlement.ts) · [test](../../server/modules/agent-runtime/runtime/agent-runtime-settlement.test.ts)
1098
+ - [agent-runtime-verification-log.ts](../../server/modules/agent-runtime/runtime/agent-runtime-verification-log.ts)
1096
1099
  - [agent-runtime-verification-suggestions.ts](../../server/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.ts) · [test](../../server/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.test.ts)
1097
1100
 
1098
1101
  ## server/modules/agents/runtime
@@ -1424,6 +1427,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
1424
1427
 
1425
1428
  ## server/util
1426
1429
 
1430
+ - [checkout-path.ts](../../server/util/checkout-path.ts)
1427
1431
  - [cli-prompt.ts](../../server/util/cli-prompt.ts) · [test](../../server/util/cli-prompt.test.ts)
1428
1432
  - [distribute-int.ts](../../server/util/distribute-int.ts)
1429
1433
  - [secure-fs.ts](../../server/util/secure-fs.ts) · [test](../../server/util/secure-fs.test.ts)
@@ -190,16 +190,18 @@ Por qué token‑paste y no OAuth en v1:
190
190
 
191
191
  ## 6. Ciclo de vida y mapeo de estados (la parte difícil)
192
192
 
193
- Los issues de Jira **no tienen un campo `status` asignable** — hay que **transicionar**, y las transiciones están **gateadas por el workflow** del cliente (que es arbitrario). Las 4 lógicas de Specrails (`todo/in_progress/done/cancelled`) deben caer sobre N estados de cliente repartidos en solo **3 categorías estables**: `new` / `indeterminate` / `done` (`statusCategory.key`).
193
+ Los issues de Jira **no tienen un campo `status` asignable** — hay que **transicionar**, y las transiciones están **gateadas por el workflow** del cliente (que es arbitrario). Las 5 lógicas de Specrails (`todo/in_progress/on_review/done/cancelled`) deben caer sobre N estados de cliente repartidos en solo **3 categorías estables**: `new` / `indeterminate` / `done` (`statusCategory.key`).
194
194
 
195
195
  ### Resolver de dos niveles
196
196
 
197
197
  1. **Mapa explícito por proyecto gana siempre.** En Settings el usuario elige, de la **lista real de estados** de su proyecto (fetched en vivo), el target para cada estado lógico. Esto resuelve la ambigüedad (p.ej. dos estados sobre categoría `done`: `Released` vs `Won't Do`).
198
198
  2. **Fallback por categoría** cuando no hay mapa: anclar en `statusCategory.key` (nunca en el **nombre** localizable del estado). Para `cancelled`, preferir un *cancel‑lexicon* (`won't do`, `cancelled`, `rejected`, `abandoned`, `invalid`, `duplicate`) y fijar `resolution`; para `done`/éxito, preferir un *ship‑lexicon* (`done`, `closed`, `released`, `resolved`, `complete`) y **alejarse** del cancel‑lexicon.
199
199
 
200
- ### Camino de transición — BFS por saltos
200
+ El mapa también se respeta al materializar Jira en la caché local para los cinco estados lógicos. Si varios estados comparten destino, `on_review` mantiene su prioridad histórica; para otras ambigüedades se conserva la interpretación por categoría.
201
201
 
202
- Como solo ves las transiciones salientes del estado **actual**, un workflow `Backlog → Selected for Dev → In Progress → Done` no ofrece arista directa a `Done`. Algoritmo: `GET /transitions` del issue vivo → aplicar la arista que reduce la distancia a la categoría objetivo (orden `new < indeterminate < done`) → re‑`GET` → repetir. Cap ~5 saltos, dedup de estados visitados (evitar bucles), parar si ninguna transición reduce distancia. **Idempotency‑first**: si la categoría actual ya es la objetivo, **no‑op**. Si no hay camino en N saltos → **dead‑letter** no‑fatal ("mover estado manualmente en Jira") y **jamás** se hace error del rail.
202
+ ### Camino de transición — recorrido acotado por categorías
203
+
204
+ Como solo ves las transiciones salientes del estado **actual**, un workflow `Backlog → Selected for Dev → In Progress → Done` no ofrece arista directa a `Done`. Algoritmo: `GET /transitions` del issue vivo → aplicar la arista que reduce la distancia a la categoría objetivo (orden `new < indeterminate < done`) → re‑`GET` → repetir. Cap ~5 saltos, dedup de estados visitados (evitar bucles), parar si ninguna transición reduce distancia. **Idempotency‑first**: si hay destino explícito, comparar primero su ID/nombre con el estado actual, incluso si pertenece a otra categoría. Sin destino explícito, la categoría objetivo permite el **no‑op**. Un estado intermedio de la categoría esperada no completa un destino explícito diferente. Este recorrido no explora estados laterales modificando Jira a ciegas. Si no hay camino en N saltos → **dead‑letter** no‑fatal ("mover estado manualmente en Jira") y **jamás** se hace error del rail.
203
205
 
204
206
  ### Pantallas de transición / campos requeridos
205
207
 
@@ -238,10 +240,11 @@ Diseño de poll:
238
240
  ### Outbound — outbox durable
239
241
 
240
242
  - Drenaje en worker de fondo: **FIFO por‑issue** (una transición debe aterrizar antes que el comentario que la describe), **paralelo entre issues distintos**, con cap de concurrencia.
241
- - **Idempotencia:** transiciones por no‑op‑si‑ya‑en‑categoría; **comentarios** con un *self‑marker* invisible embebido en el body ADF (`[specrails:job-<id>]`) — Jira no tiene idempotencia nativa de comentarios, así que antes de re‑postear se hace `GET .../comment` y se salta si el marker ya existe. El marker dobla como filtro de auto‑eco en el poll.
243
+ - **Idempotencia:** transiciones por identidad explícita ID/nombre y, sin mapa, por categoría; **comentarios** con un *self‑marker* invisible embebido en el body ADF (`[specrails:job-<id>]`) — Jira no tiene idempotencia nativa de comentarios, así que antes de re‑postear se hace `GET .../comment` y se salta si el marker ya existe. El marker dobla como filtro de auto‑eco en el poll.
242
244
  - **Rate limits:** honrar `Retry-After` en 429 exacto; si ausente, backoff exponencial con jitter (base 2s, cap 30s, ~4 reintentos); respetar el techo ~20 writes/2s por issue. Token‑bucket por debajo de los burst caps.
243
245
  - **Clasificación de errores:** `401` = credencial → **pausar** outbox del proyecto + banner re‑auth (no reintentar en bucle); `403` = permiso de operación concreta → dead‑letter nombrando la operación ("tu cuenta no puede transicionar en PROJ"), sin inferir fallo global; `404` sobre issue conocido = terminal (issue borrado/movido) → marcar link `orphaned`, parar la op; solo `429/5xx/timeout` son reintentables.
244
246
  - **Dead‑letter visible** con reintento manual: `GET /jira/outbox`, `POST /jira/outbox/:id/retry`, indicador `JiraSyncIndicator` en UI. Un workflow‑gap o un 403 **nunca** es un drop silencioso.
247
+ - **Operaciones reemplazadas:** las transiciones fallidas con una intención posterior para el mismo issue se conservan como `superseded`, fuera del contador de atención y sin reintento. Un reintento antiguo ya encolado tampoco se aplica si una transición posterior ya terminó. Se conservan el FIFO normal y los comentarios/ediciones independientes. El panel identifica el issue y el destino, y muestra errores al cargar los estados con una acción de reintento.
245
248
 
246
249
  ---
247
250
 
@@ -173,9 +173,9 @@ Without profiles, every rail uses the project's frontmatter-baked models. With p
173
173
  Open **Agents** in the project's right sidebar. Two sub-tabs:
174
174
 
175
175
  - **Profiles** — full CRUD over `.specrails/profiles/*.json`. The live validator enforces the baseline trio and routing ordering — Save is disabled with an "N issues to resolve" hint while the profile is broken.
176
- - **Agents Catalog** — read-only viewer of upstream `sr-*` agents and your `custom-*` agents.
176
+ - **Agents Catalog** — read-only viewer of the runtime-defined baseline roles (`sr-architect`, `sr-developer`, `sr-reviewer`, shown with the definition the Core runtime actually executes) and your `custom-*` agents.
177
177
 
178
- The empty state offers **Migrate from current agents**: one click creates a `default` profile mirroring today's frontmatter.
178
+ The empty state offers **Migrate from current agents**: one click creates a `default` profile from the baseline roles and your existing custom agents — no role file has to exist for it to work.
179
179
 
180
180
  Each profile gets a per-profile analytics card showing usage for the last 7 / 30 / 90 days: jobs, success rate, avg tokens, avg duration.
181
181
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.59.2",
3
+ "version": "2.60.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,8 +19,8 @@ const bundled_core_1 = require("./bundled-core");
19
19
  const core_runtime_1 = require("./core-runtime");
20
20
  // Windows has no `which`; probe PATH via `where` instead.
21
21
  const WHICH_CMD = process.platform === 'win32' ? 'where' : 'which';
22
- /** Paired development target; older supported contracts remain compatible. */
23
- exports.EXPECTED_CORE_CONTRACT_SCHEMA_VERSION = '5.1';
22
+ /** Paired development target; older supported contracts (5.1) remain compatible. */
23
+ exports.EXPECTED_CORE_CONTRACT_SCHEMA_VERSION = '5.2';
24
24
  // These must mirror KNOWN_VERBS in cli/specrails-desktop.ts
25
25
  const DESKTOP_KNOWN_COMMANDS = new Set([
26
26
  'implement',
@@ -29,6 +29,10 @@ const DESKTOP_KNOWN_COMMANDS = new Set([
29
29
  // Commands older Cores still ship that Desktop no longer invokes. They are
30
30
  // tolerated (not drift) but never required: Batch was folded into implement.
31
31
  const DESKTOP_TOLERATED_LEGACY_COMMANDS = new Set(['batch-implement']);
32
+ // Checkpoints that contract 5.1 still declares but Desktop retired: role agents
33
+ // are runtime-defined since contract 5.2, so `agent_generation` is tolerated
34
+ // (not drift) and never required.
35
+ const DESKTOP_TOLERATED_LEGACY_CHECKPOINTS = new Set(['agent_generation']);
32
36
  function isRecord(value) {
33
37
  return typeof value === 'object' && value !== null && !Array.isArray(value);
34
38
  }
@@ -233,7 +237,7 @@ async function checkCoreCompat() {
233
237
  : (contract.checkpoints && typeof contract.checkpoints === 'object'
234
238
  ? Object.keys(contract.checkpoints)
235
239
  : []);
236
- const missingCheckpoints = contractCheckpoints.filter((c) => !desktopCheckpointKeys.includes(c));
240
+ const missingCheckpoints = contractCheckpoints.filter((c) => !desktopCheckpointKeys.includes(c) && !DESKTOP_TOLERATED_LEGACY_CHECKPOINTS.has(c));
237
241
  const deterministic = contract.lifecycle?.mode === 'deterministic' && contract.lifecycle.requiresEnrich === false;
238
242
  // Supporting legacy installation checkpoints is compatible with Core 5;
239
243
  // demanding them from a deterministic installer would recreate enrich.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkoutSubdirectory = checkoutSubdirectory;
3
+ exports.checkoutSubdirectory = void 0;
4
4
  exports.prepareCoreExecution = prepareCoreExecution;
5
5
  exports.coreVerificationContext = coreVerificationContext;
6
6
  exports.checkCoreCompletion = checkCoreCompletion;
@@ -10,6 +10,9 @@ const node_path_1 = require("node:path");
10
10
  const core_node_runtime_1 = require("./core-node-runtime");
11
11
  const core_update_state_1 = require("./core-update-state");
12
12
  const agent_runtime_loader_1 = require("./modules/agent-runtime/runtime/agent-runtime-loader");
13
+ const checkout_path_1 = require("./util/checkout-path");
14
+ var checkout_path_2 = require("./util/checkout-path");
15
+ Object.defineProperty(exports, "checkoutSubdirectory", { enumerable: true, get: function () { return checkout_path_2.checkoutSubdirectory; } });
13
16
  const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
14
17
  // Resolve existing ancestors too: the tickets file may not exist yet, and macOS
15
18
  // exposes temporary directories through both /var and /private/var.
@@ -26,22 +29,6 @@ function canonicalPath(path) {
26
29
  return (0, node_path_1.join)(canonicalPath(parent), (0, node_path_1.basename)(path));
27
30
  }
28
31
  }
29
- /** Where a directory sits inside its git checkout: '' for the checkout root or a directory outside git. */
30
- function checkoutSubdirectory(directory) {
31
- let start;
32
- try {
33
- start = (0, node_fs_1.realpathSync)(directory);
34
- }
35
- catch {
36
- return '';
37
- }
38
- for (let current = start;; current = (0, node_path_1.dirname)(current)) {
39
- if ((0, node_fs_1.existsSync)((0, node_path_1.join)(current, '.git')))
40
- return (0, node_path_1.relative)(current, start).split(node_path_1.sep).join('/');
41
- if ((0, node_path_1.dirname)(current) === current)
42
- return '';
43
- }
44
- }
45
32
  /**
46
33
  * A registered repository may be one package of a larger git checkout (a
47
34
  * monorepo app). An isolated worktree always mounts the whole checkout, so the
@@ -54,7 +41,7 @@ function registeredScope(checkout, sourcePath) {
54
41
  (0, node_fs_1.realpathSync)(sourcePath);
55
42
  const local = (0, node_path_1.relative)(checkout, sourcePath).split(node_path_1.sep).join('/');
56
43
  const contained = local && local !== '..' && !local.startsWith('../') && !(0, node_path_1.isAbsolute)(local);
57
- const prefix = checkoutSubdirectory(checkout) ? (contained ? local : '') : checkoutSubdirectory(sourcePath) || (contained ? local : '');
44
+ const prefix = (0, checkout_path_1.checkoutSubdirectory)(checkout) ? (contained ? local : '') : (0, checkout_path_1.checkoutSubdirectory)(sourcePath) || (contained ? local : '');
58
45
  if (!prefix)
59
46
  return undefined;
60
47
  if (!(0, node_fs_1.statSync)((0, node_path_1.join)(checkout, ...prefix.split('/'))).isDirectory())
@@ -33,6 +33,7 @@ exports.markOutboxDone = markOutboxDone;
33
33
  exports.markOutboxRetry = markOutboxRetry;
34
34
  exports.markOutboxDead = markOutboxDead;
35
35
  exports.resetInflight = resetInflight;
36
+ exports.reconcileSupersededTransitions = reconcileSupersededTransitions;
36
37
  exports.retryDeadOutbox = retryDeadOutbox;
37
38
  exports.listOutbox = listOutbox;
38
39
  exports.countOutboxByState = countOutboxByState;
@@ -328,12 +329,38 @@ function resetInflight(db) {
328
329
  .run(new Date().toISOString());
329
330
  return info.changes;
330
331
  }
331
- /** Re-queue a dead-lettered op for a manual retry. */
332
+ /**
333
+ * Preserve obsolete failed status intentions as terminal history rather than
334
+ * offering a retry that could revert a newer intention. Ordinary pending and
335
+ * inflight operations retain FIFO ownership. A pending row with a newer DONE
336
+ * transition is an old retry (normal FIFO cannot produce that ordering).
337
+ * Comments and field edits are independent operations and never superseded.
338
+ */
339
+ function reconcileSupersededTransitions(db) {
340
+ return db.prepare(`UPDATE jira_outbox AS candidate
341
+ SET state = 'superseded', next_attempt_at = NULL, updated_at = ?
342
+ WHERE candidate.op_type = 'transition'
343
+ AND candidate.state IN ('dead', 'pending')
344
+ AND EXISTS (
345
+ SELECT 1 FROM jira_outbox AS newer
346
+ WHERE newer.jira_issue_id = candidate.jira_issue_id
347
+ AND newer.op_type = 'transition'
348
+ AND newer.id > candidate.id
349
+ AND (candidate.state = 'dead' OR newer.state = 'done')
350
+ )`).run(new Date().toISOString()).changes;
351
+ }
352
+ /** Reconcile and re-queue atomically, so an obsolete intention is never revived. */
332
353
  function retryDeadOutbox(db, id) {
333
- const info = db
334
- .prepare("UPDATE jira_outbox SET state = 'pending', next_attempt_at = NULL, dead_reason = NULL, last_error = NULL, updated_at = ? WHERE id = ? AND state = 'dead'")
335
- .run(new Date().toISOString(), id);
336
- return info.changes > 0;
354
+ return db.transaction(() => {
355
+ reconcileSupersededTransitions(db);
356
+ const row = db.prepare('SELECT state FROM jira_outbox WHERE id = ?').get(id);
357
+ if (row?.state === 'superseded')
358
+ return 'superseded';
359
+ if (row?.state !== 'dead')
360
+ return null;
361
+ db.prepare("UPDATE jira_outbox SET state = 'pending', next_attempt_at = NULL, dead_reason = NULL, last_error = NULL, updated_at = ? WHERE id = ? AND state = 'dead'").run(new Date().toISOString(), id);
362
+ return 'pending';
363
+ })();
337
364
  }
338
365
  function listOutbox(db, opts = {}) {
339
366
  const limit = Math.min(opts.limit ?? 200, 1000);
@@ -344,7 +371,7 @@ function listOutbox(db, opts = {}) {
344
371
  }
345
372
  function countOutboxByState(db) {
346
373
  const rows = db.prepare('SELECT state, COUNT(*) AS n FROM jira_outbox GROUP BY state').all();
347
- const out = { pending: 0, inflight: 0, done: 0, dead: 0 };
374
+ const out = { pending: 0, inflight: 0, done: 0, dead: 0, superseded: 0 };
348
375
  for (const r of rows) {
349
376
  if (r.state in out)
350
377
  out[r.state] = r.n;
@@ -37,19 +37,21 @@ function issueStatusCategory(issue) {
37
37
  return k === 'new' || k === 'done' ? k : 'indeterminate';
38
38
  }
39
39
  /**
40
- * Map a Jira issue's status to a Specrails ticket status. When the connection
41
- * has an explicit `statusMap.on_review` and the issue's status NAME equals it
42
- * (case-insensitive), the inbound status is preserved as `on_review` — the
43
- * category fallback alone would flatten it to `in_progress` and silently revert
44
- * a ticket parked at on_review once its outbox transition drains (the frozen
45
- * guard only protects the pending window). Unconfigured ⇒ byte-identical
46
- * category mapping.
40
+ * Explicit status identities apply in both sync directions, even when a
41
+ * customer's category differs from the logical state's default. Preserve the
42
+ * historical on_review precedence for shared mappings; other ambiguous targets
43
+ * retain category fallback rather than choosing by configuration object order.
47
44
  */
48
45
  function mapStatus(issue, statusMap) {
49
46
  const name = (issue.fields.status?.name ?? '').toLowerCase();
50
- const onReviewTarget = statusMap?.on_review;
51
- if (onReviewTarget && name && name === onReviewTarget.toLowerCase())
47
+ const matches = (target) => Boolean(target
48
+ && (issue.fields.status?.id === target || (name && name === target.toLowerCase())));
49
+ if (matches(statusMap?.on_review))
52
50
  return 'on_review';
51
+ const states = ['todo', 'in_progress', 'done', 'cancelled'];
52
+ const mapped = states.filter((state) => matches(statusMap?.[state]));
53
+ if (mapped.length === 1)
54
+ return mapped[0];
53
55
  const cat = issueStatusCategory(issue);
54
56
  if (cat === 'new')
55
57
  return 'todo';