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
@@ -12,6 +12,7 @@ exports.parseLoginShellOutput = parseLoginShellOutput;
12
12
  exports.augmentPathFromLoginShell = augmentPathFromLoginShell;
13
13
  exports.parseLoginShellEnv = parseLoginShellEnv;
14
14
  exports.augmentEnvFromLoginShell = augmentEnvFromLoginShell;
15
+ exports.readEnvFromLoginShellSync = readEnvFromLoginShellSync;
15
16
  exports.augmentEnvFromLoginShellSync = augmentEnvFromLoginShellSync;
16
17
  exports.augmentAuthEnvFromLoginShell = augmentAuthEnvFromLoginShell;
17
18
  exports.getPathDiagnostic = getPathDiagnostic;
@@ -372,6 +373,17 @@ function parseLoginShellOutput(stdout) {
372
373
  return null;
373
374
  return stdout.slice(start, end);
374
375
  }
376
+ /** GUI launches may omit SHELL even when the account uses zsh/bash. */
377
+ function resolveLoginShell(env = process.env) {
378
+ if (env.SHELL)
379
+ return env.SHELL;
380
+ try {
381
+ return os_1.default.userInfo().shell || '/bin/sh';
382
+ }
383
+ catch {
384
+ return '/bin/sh';
385
+ }
386
+ }
375
387
  /**
376
388
  * Spawn the user's login shell once and merge any additional PATH segments
377
389
  * it exposes (Volta/nvm/fnm/asdf shims) into `process.env.PATH`. Async,
@@ -398,7 +410,7 @@ async function augmentPathFromLoginShell(opts = {}) {
398
410
  }
399
411
  const spawnFn = opts.spawnFn ?? child_process_1.spawn;
400
412
  const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
401
- const shell = process.env.SHELL || '/bin/sh';
413
+ const shell = resolveLoginShell();
402
414
  const command = `printf "${PATH_BEGIN}%s${PATH_END}" "$PATH"`;
403
415
  const status = await new Promise((resolve) => {
404
416
  let child;
@@ -474,7 +486,6 @@ exports.AUTH_ENV_VARS = [
474
486
  'GOOGLE_APPLICATION_CREDENTIALS',
475
487
  ];
476
488
  const LOGIN_SHELL_ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
477
- const loginShellEnvProbeCache = new Set();
478
489
  function normalizeLoginShellEnvNames(names) {
479
490
  const out = [];
480
491
  for (const raw of names) {
@@ -528,7 +539,7 @@ async function augmentEnvFromLoginShell(names, opts = {}) {
528
539
  return;
529
540
  const spawnFn = opts.spawnFn ?? child_process_1.spawn;
530
541
  const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
531
- const shell = process.env.SHELL || '/bin/sh';
542
+ const shell = resolveLoginShell();
532
543
  const command = buildLoginShellEnvCommand(wanted);
533
544
  await new Promise((resolve) => {
534
545
  let child;
@@ -569,42 +580,53 @@ async function augmentEnvFromLoginShell(names, opts = {}) {
569
580
  });
570
581
  });
571
582
  }
572
- /** Synchronous, cached variant for spawn-time env assembly. It probes each
573
- * missing name at most once per process to keep loops from paying the login
574
- * shell timeout on every step. */
575
- function augmentEnvFromLoginShellSync(names, opts = {}) {
583
+ /** Read missing configured names without mutating the caller's or global env.
584
+ * Callers own any cache so project credentials cannot leak across scopes. */
585
+ function readEnvFromLoginShellSync(names, opts = {}) {
576
586
  if (process.platform === 'win32')
577
- return;
587
+ return {};
578
588
  if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
579
- return;
580
- const wanted = normalizeLoginShellEnvNames(names).filter((name) => !process.env[name] && !loginShellEnvProbeCache.has(name));
589
+ return {};
590
+ const env = opts.env ?? process.env;
591
+ const wanted = normalizeLoginShellEnvNames(names).filter((name) => !env[name]);
581
592
  if (wanted.length === 0)
582
- return;
583
- for (const name of wanted)
584
- loginShellEnvProbeCache.add(name);
593
+ return {};
585
594
  const spawnSyncFn = opts.spawnSyncFn ?? child_process_1.spawnSync;
586
595
  const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
587
- const shell = process.env.SHELL || '/bin/sh';
596
+ const shell = resolveLoginShell(env);
588
597
  const command = buildLoginShellEnvCommand(wanted);
589
598
  let res;
590
599
  try {
591
600
  res = spawnSyncFn(shell, ['-l', '-i', '-c', command], {
601
+ env,
592
602
  encoding: 'utf8',
593
603
  stdio: ['ignore', 'pipe', 'pipe'],
594
604
  timeout: timeoutMs,
605
+ maxBuffer: 1024 * 1024,
595
606
  });
596
607
  }
597
608
  catch {
598
- return;
609
+ return {};
599
610
  }
600
- if (res.error)
601
- return;
611
+ if (res.error || res.status !== 0)
612
+ return {};
602
613
  const stdout = typeof res.stdout === 'string' ? res.stdout : res.stdout?.toString('utf8') ?? '';
603
614
  const recovered = parseLoginShellEnv(stdout);
615
+ const out = {};
604
616
  for (const key of wanted) {
605
617
  const val = recovered[key];
606
- if (val && !process.env[key])
607
- process.env[key] = val;
618
+ if (val)
619
+ out[key] = val;
620
+ }
621
+ return out;
622
+ }
623
+ /** Compatibility backfill for callers that intentionally augment process.env.
624
+ * Failed probes remain retryable; successful names are already present. */
625
+ function augmentEnvFromLoginShellSync(names, opts = {}) {
626
+ const recovered = readEnvFromLoginShellSync(names, opts);
627
+ for (const [key, value] of Object.entries(recovered)) {
628
+ if (value && !process.env[key])
629
+ process.env[key] = value;
608
630
  }
609
631
  }
610
632
  /** Startup backfill for the built-in provider auth variables. */
@@ -647,5 +669,4 @@ function __resetPathResolverForTest() {
647
669
  diagnostic = { pathSegments: [], pathSources: [], loginShellStatus: 'skipped' };
648
670
  warnedLoginShell = false;
649
671
  bundledRuntimesActive = false;
650
- loginShellEnvProbeCache.clear();
651
672
  }
@@ -4,21 +4,37 @@ exports.resolveWorktreeEnvPassthrough = resolveWorktreeEnvPassthrough;
4
4
  exports.applyWorktreeEnvPassthrough = applyWorktreeEnvPassthrough;
5
5
  const db_1 = require("./db");
6
6
  const path_resolver_1 = require("./path-resolver");
7
+ // Values stay in the owning project's memory, never process.env or SQLite.
8
+ // Both successful and missing lookups expire so profiles can recover/rotate.
9
+ const LOGIN_SHELL_CACHE_MS = 30_000;
10
+ const shellValues = new WeakMap();
11
+ function recoverProjectEnv(db, names) {
12
+ const missing = names.filter(name => !process.env[name]);
13
+ if (!missing.length) {
14
+ shellValues.delete(db);
15
+ return {};
16
+ }
17
+ const identity = JSON.stringify([names, missing, process.env.SHELL, process.env.HOME, process.env.ZDOTDIR]);
18
+ const cached = shellValues.get(db);
19
+ if (cached?.identity === identity && Date.now() < cached.expiresAt)
20
+ return cached.env;
21
+ const env = (0, path_resolver_1.readEnvFromLoginShellSync)(missing);
22
+ shellValues.set(db, { identity, expiresAt: Date.now() + LOGIN_SHELL_CACHE_MS, env });
23
+ return env;
24
+ }
7
25
  /** Resolve the per-project env passthrough overlay for a spawn.
8
26
  *
9
27
  * The project setting stores names only. Values are read from `sourceEnv` at
10
28
  * spawn time so credentials can rotate without touching SQLite. When the
11
- * source is `process.env`, missing configured names get one bounded login-shell
12
- * recovery attempt first; unresolved names are still ignored instead of
13
- * blocking the run.
29
+ * source is `process.env`, missing configured names get bounded login-shell
30
+ * recovery with a short, project-owned cache. Unresolved names remain absent.
14
31
  */
15
32
  function resolveWorktreeEnvPassthrough(db, sourceEnv = process.env) {
16
33
  const out = {};
17
34
  const names = (0, db_1.getProjectSettings)(db).worktreeEnvPassthrough;
18
- if (sourceEnv === process.env)
19
- (0, path_resolver_1.augmentEnvFromLoginShellSync)(names);
35
+ const recovered = sourceEnv === process.env ? recoverProjectEnv(db, names) : {};
20
36
  for (const name of names) {
21
- const value = sourceEnv[name];
37
+ const value = sourceEnv[name] || recovered[name] || sourceEnv[name];
22
38
  if (value !== undefined)
23
39
  out[name] = value;
24
40
  }
@@ -1,18 +1,20 @@
1
1
  "use strict";
2
- // Gemini headless subagent pre-acknowledgment.
2
+ // Gemini headless subagent pre-acknowledgment (custom agents only).
3
3
  //
4
4
  // gemini 0.46+ DISCOVERS `<project>/.gemini/agents/*.md` but only ENABLES a
5
5
  // project's custom subagents after an interactive "New Agents Discovered →
6
6
  // Acknowledge and Enable" prompt. That prompt never fires in headless
7
7
  // (`gemini -p`) spawns — which is how the desktop runs every rail — so
8
- // `invoke_agent sr-architect` returns "Subagent not found" and the implement
9
- // orchestrator silently falls back to a generic agent (the specialised
10
- // architect/developer/reviewer personas never run in isolation).
8
+ // `invoke_agent custom-<name>` returns "Subagent not found" and the role that
9
+ // delegates to it silently falls back to inline work. The baseline roles
10
+ // (architect/developer/reviewer) are defined by the specrails-core runtime and
11
+ // are not `.gemini/agents` files, so only the project's `custom-*` agents need
12
+ // this acknowledgment.
11
13
  //
12
14
  // specrails-core writes the acknowledgment file at install time; this is the
13
15
  // defence-in-depth copy the desktop runs right before a gemini rail spawn, so a
14
- // project installed with an older core (or whose agents changed since install)
15
- // is still trusted headless. The file gemini reads is
16
+ // project whose custom agents changed since install is still trusted headless.
17
+ // The file gemini reads is
16
18
  // `~/.gemini/acknowledgments/agents.json`, shaped
17
19
  // { [projectRoot]: { [agentName]: <sha256-hex of the agent .md file> } }
18
20
  // where the hash is sha256 of the FULL agent markdown file (verified empirically
@@ -29,10 +31,11 @@ function ackFilePath() {
29
31
  return (0, path_1.join)((0, os_1.homedir)(), '.gemini', 'acknowledgments', 'agents.json');
30
32
  }
31
33
  /**
32
- * Pre-acknowledge every `<projectPath>/.gemini/agents/*.md` so gemini loads them
33
- * in headless mode. No-op when the project has no `.gemini/agents` dir or no
34
- * agent files. The `projectPath` is the key gemini uses (the spawn cwd / repo
35
- * root), matching what `specrails-core` writes at install.
34
+ * Pre-acknowledge every `<projectPath>/.gemini/agents/*.md` (the project's
35
+ * custom agents) so gemini loads them in headless mode. No-op when the project
36
+ * has no `.gemini/agents` dir or no agent files. The `projectPath` is the key
37
+ * gemini uses (the spawn cwd / repo root), matching what `specrails-core`
38
+ * writes at install.
36
39
  */
37
40
  function acknowledgeGeminiProjectAgents(projectPath) {
38
41
  const agentsDir = (0, path_1.join)(projectPath, '.gemini', 'agents');
@@ -186,7 +186,6 @@ exports.CHECKPOINTS = [
186
186
  { key: 'agent_selection', name: 'Agent selection' },
187
187
  { key: 'codebase_analysis', name: 'Codebase analysis' },
188
188
  { key: 'vpc_discovery', name: 'VPC discovery' },
189
- { key: 'agent_generation', name: 'Agent generation' },
190
189
  { key: 'persona_synthesis', name: 'Persona synthesis' },
191
190
  { key: 'command_generation', name: 'Command generation' },
192
191
  ];
@@ -204,10 +203,7 @@ function checkFilesystem(projectPath, provider = 'claude') {
204
203
  (0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails-version'));
205
204
  const hasSetupTemplates = (0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails', 'setup-templates')) ||
206
205
  (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'setup-templates'));
207
- const hasRules = (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'rules')) &&
208
- hasFiles((0, path_1.join)(projectPath, dir, 'rules'), /\.md$/);
209
206
  const hasPersonas = artifactState.hasPersonas;
210
- const hasAgents = artifactState.hasAgents;
211
207
  const hasCommands = artifactState.hasCommands;
212
208
  const instructionCandidates = [(0, path_1.join)(projectPath, adapter.instructionsFilename)];
213
209
  // Older Core releases sometimes placed a top-level instructions filename
@@ -227,22 +223,15 @@ function checkFilesystem(projectPath, provider = 'claude') {
227
223
  base_install: hasBaseInstall,
228
224
  // Back-compat filesystem signals from older cores are translated to the
229
225
  // current integration-contract checkpoint keys.
230
- agent_selection: hasInstallConfig || hasAgentConfig || hasBacklogConfig || hasAgents || hasCommands,
226
+ agent_selection: hasInstallConfig || hasAgentConfig || hasBacklogConfig || hasCommands,
231
227
  codebase_analysis: hasBaseInstall && (hasInstructions || hasSetupTemplates),
232
228
  vpc_discovery: hasPersonas,
233
229
  persona_synthesis: hasPersonas,
234
- agent_generation: hasAgents,
235
- command_generation: hasCommands || (hasAgents && hasRules),
230
+ // Role agents are runtime-defined by specrails-core; only the workflow
231
+ // commands (and provider skills) prove a complete installation.
232
+ command_generation: hasCommands,
236
233
  };
237
234
  }
238
- function hasFiles(dir, pattern) {
239
- try {
240
- return (0, fs_1.readdirSync)(dir).some((f) => pattern.test(f));
241
- }
242
- catch {
243
- return false;
244
- }
245
- }
246
235
  // ─── Enrich.md content resolver (shared by start + resume enrich paths) ─────
247
236
  /**
248
237
  * Reads the enrich command's body from the project's specrails dir, falling
@@ -286,9 +275,6 @@ function detectCheckpointFromText(text) {
286
275
  if (/phase\s*3|configuration|agent\s*selection|backlog\s*provider/i.test(text)) {
287
276
  hits.push({ key: 'agent_selection', detail: 'Configuring agents...' });
288
277
  }
289
- if (/generating\s*all\s*files|writing.*agent|sr-architect|sr-developer|sr-reviewer/i.test(text)) {
290
- hits.push({ key: 'agent_generation', detail: 'Generating agents...' });
291
- }
292
278
  if (/command\s*selection|installing.*commands|\.claude\/commands\/(sr|specrails)/i.test(text)) {
293
279
  hits.push({ key: 'command_generation', detail: 'Configuring commands...' });
294
280
  }
@@ -301,7 +287,7 @@ function detectCheckpointFromText(text) {
301
287
  hits.push({ key: 'config_written' });
302
288
  }
303
289
  if (/installing\s*specrails|phase\s*2\s*&\s*3|placing\s*agents/i.test(text)) {
304
- hits.push({ key: 'agent_generation', detail: 'Installing specrails artefacts...' });
290
+ hits.push({ key: 'command_generation', detail: 'Installing specrails artefacts...' });
305
291
  }
306
292
  if (/writing\s*manifest|wrote\s+.*specrails-manifest/i.test(text)) {
307
293
  hits.push({ key: 'command_generation' });
@@ -315,16 +301,8 @@ function detectCheckpointFromText(text) {
315
301
  if (text.includes('/agents/personas/') && text.includes('.md')) {
316
302
  hits.push({ key: 'persona_synthesis', detail: 'Writing personas...' });
317
303
  }
318
- // Claude path: .claude/agents/sr-<name>.md
319
- if (/\/agents\/sr-[^/]+\.md/.test(text)) {
320
- hits.push({ key: 'agent_generation', detail: 'Writing agents...' });
321
- }
322
- // Provider-native rail skills. Kimi's upstream discovery contract scans only
323
- // direct children of `.kimi-code/skills`, so its roles are top-level
324
- // `.kimi-code/skills/sr-*/SKILL.md` entries. Codex may also use `rails/`.
325
- if (/\.(?:codex|kimi-code)\/skills\/(?:rails\/)?sr-[^/]+\/SKILL\.md/.test(text)) {
326
- hits.push({ key: 'agent_generation', detail: 'Writing agent skills...' });
327
- }
304
+ // Role agents (`sr-*`) are no longer installed files: Core defines them in
305
+ // its runtime, so role paths carry no checkpoint signal.
328
306
  if ((text.includes('/commands/sr/') || text.includes('/commands/specrails/')) && text.includes('.md')) {
329
307
  hits.push({ key: 'command_generation', detail: 'Writing commands...' });
330
308
  }
@@ -423,10 +401,11 @@ function computeSummary(projectPath, tier, provider = 'claude') {
423
401
  if (provider === 'codex' || provider === 'kimi') {
424
402
  // Codex/Kimi layout: every artefact ships as a SKILL under the provider's
425
403
  // `skills/` root.
426
- // - agents = rail personas (`skills/rails/sr-*/SKILL.md`) + orchestrator
427
- // skills at the root with an `sr-` prefix. Kimi roles MUST be
428
- // direct children (`skills/sr-*/SKILL.md`) because upstream
429
- // Kimi discovery is intentionally one level deep.
404
+ // - agents = custom roles only: codex `skills/rails/custom-*/SKILL.md`,
405
+ // kimi direct children `skills/custom-*/SKILL.md` (upstream
406
+ // Kimi discovery is intentionally one level deep). Baseline
407
+ // `sr-*` roles are runtime-defined and never counted; stale
408
+ // `sr-*` skills from older Cores are ignored.
430
409
  // - opsxCommands = `skills/openspec-*/SKILL.md`
431
410
  // - specrailsCommands = everything else under `skills/` (ported claude
432
411
  // slash commands like propose-spec, explore-spec, retry, doctor,
@@ -444,7 +423,7 @@ function computeSummary(projectPath, tier, provider = 'claude') {
444
423
  const personasDir = (0, path_1.join)(railsDir, entry);
445
424
  personas = (0, fs_1.readdirSync)(personasDir).filter((persona) => (0, fs_1.existsSync)((0, path_1.join)(personasDir, persona, 'SKILL.md'))).length;
446
425
  }
447
- else if ((0, fs_1.existsSync)((0, path_1.join)(railsDir, entry, 'SKILL.md'))) {
426
+ else if (/^custom-/.test(entry) && (0, fs_1.existsSync)((0, path_1.join)(railsDir, entry, 'SKILL.md'))) {
448
427
  agents++;
449
428
  }
450
429
  }
@@ -456,6 +435,8 @@ function computeSummary(projectPath, tier, provider = 'claude') {
456
435
  if (!(0, fs_1.existsSync)((0, path_1.join)(skillsDir, entry, 'SKILL.md')))
457
436
  continue;
458
437
  if (/^sr-/.test(entry))
438
+ continue;
439
+ else if (/^custom-/.test(entry))
459
440
  agents++;
460
441
  else if (/^openspec-/.test(entry))
461
442
  opsxCommands++;
@@ -473,7 +454,8 @@ function computeSummary(projectPath, tier, provider = 'claude') {
473
454
  }
474
455
  }
475
456
  else {
476
- // Claude / Gemini layout: agents at `<dir>/agents/sr-*.md`, slash commands
457
+ // Claude / Gemini layout: custom roles at `<dir>/agents/custom-*.md`
458
+ // (baseline roles are runtime-defined, stale `sr-*` files are ignored), slash commands
477
459
  // at `<dir>/commands/{specrails,opsx}/*.<ext>`. Gemini installs into
478
460
  // `.gemini/` and its commands are TOML (`.gemini/commands/specrails/*.toml`);
479
461
  // claude installs into `.claude/` with Markdown commands. Without the
@@ -484,7 +466,7 @@ function computeSummary(projectPath, tier, provider = 'claude') {
484
466
  const agentsDir = (0, path_1.join)(projectPath, dir, 'agents');
485
467
  if ((0, fs_1.existsSync)(agentsDir)) {
486
468
  const files = (0, fs_1.readdirSync)(agentsDir);
487
- agents = files.filter((f) => /^sr-.*\.md$/.test(f)).length;
469
+ agents = files.filter((f) => /^custom-.*\.md$/.test(f)).length;
488
470
  const personasDir = (0, path_1.join)(agentsDir, 'personas');
489
471
  if ((0, fs_1.existsSync)(personasDir)) {
490
472
  personas = (0, fs_1.readdirSync)(personasDir).filter((f) => f.endsWith('.md')).length;
@@ -515,6 +497,8 @@ function computeSummary(projectPath, tier, provider = 'claude') {
515
497
  }
516
498
  /**
517
499
  * Resolve completion against the selected provider's real filesystem layout.
500
+ * Completion is decided by the workflow commands Core places; role agents are
501
+ * runtime-defined and never required.
518
502
  * A legacy `.claude` fallback remains intentional only for Codex/Gemini:
519
503
  * older Core releases scaffolded Claude artefacts for those selections.
520
504
  * Kimi never had such a released legacy Core target; accepting a mixed
@@ -529,14 +513,13 @@ function setupArtifactState(projectPath, tier, provider) {
529
513
  : nativeSummary;
530
514
  const nativeHasCommands = nativeSummary.specrailsCommands + nativeSummary.opsxCommands > 0;
531
515
  const legacyHasCommands = legacySummary.specrailsCommands + legacySummary.opsxCommands > 0;
532
- const nativeComplete = nativeSummary.agents > 0 && nativeHasCommands;
533
- const legacyComplete = legacySummary.agents > 0 && legacyHasCommands;
516
+ const nativeComplete = nativeHasCommands;
517
+ const legacyComplete = legacyHasCommands;
534
518
  const selected = nativeComplete || !legacyComplete ? nativeSummary : legacySummary;
535
519
  return {
536
520
  // Preserve the actual selected provider for UI labels even when the
537
521
  // artefacts came from the legacy Claude compatibility tree.
538
522
  summary: { ...selected, provider },
539
- hasAgents: nativeSummary.agents > 0 || legacySummary.agents > 0,
540
523
  hasCommands: nativeHasCommands || legacyHasCommands,
541
524
  hasPersonas: nativeSummary.personas > 0 || legacySummary.personas > 0,
542
525
  complete: nativeComplete || legacyComplete,
@@ -1026,7 +1009,9 @@ class SetupManager {
1026
1009
  const validation = validateInstalledCore(root);
1027
1010
  const selectedProvider = provider ?? this._projectProviders.get(projectId) ?? 'claude';
1028
1011
  const summary = computeSummary(root, 'quick', selectedProvider);
1029
- if (!validation.ok || !installedVersion || summary.agents < 3 || summary.specrailsCommands < 1) {
1012
+ // Role agents are runtime-defined: a Core install is complete when its
1013
+ // workflow commands are in place, regardless of custom role count.
1014
+ if (!validation.ok || !installedVersion || summary.specrailsCommands < 1) {
1030
1015
  this._broadcast({ type: 'setup_error', projectId, error: 'Core 5 uses deterministic installation. Run project installation to repair its framework; enrichment is no longer supported.' });
1031
1016
  return true;
1032
1017
  }
@@ -1315,9 +1300,9 @@ class SetupManager {
1315
1300
  case 'tool-use': {
1316
1301
  this._broadcast({ type: 'setup_log', projectId, line: `[tool] ${ev.name}`, stream: 'stdout' });
1317
1302
  // Tool inputs commonly mention the paths being written — feed the
1318
- // input preview into the checkpoint detector so writes to
1319
- // .claude/agents/sr-*.md or .codex/skills/sr-*/SKILL.md advance
1320
- // the checkpoint state immediately.
1303
+ // input preview into the checkpoint detector so writes to workflow
1304
+ // commands (`commands/specrails/*.md`) or provider command skills
1305
+ // advance the checkpoint state immediately.
1321
1306
  const hits = detectCheckpointFromText(ev.inputPreview);
1322
1307
  for (const hit of hits) {
1323
1308
  this._advanceCheckpoint(projectId, hit.key, hit.detail);
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkoutSubdirectory = checkoutSubdirectory;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ /** Where a directory sits inside its Git checkout. Handles worktree .git files
7
+ * as well as directories; root and non-Git directories both return ''. */
8
+ function checkoutSubdirectory(directory) {
9
+ let start;
10
+ try {
11
+ start = (0, node_fs_1.realpathSync)(directory);
12
+ }
13
+ catch {
14
+ return '';
15
+ }
16
+ for (let current = start;; current = (0, node_path_1.dirname)(current)) {
17
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(current, '.git')))
18
+ return (0, node_path_1.relative)(current, start).split(node_path_1.sep).join('/');
19
+ if ((0, node_path_1.dirname)(current) === current)
20
+ return '';
21
+ }
22
+ }
@@ -3,7 +3,6 @@ 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.ensureFrameworkAgents = ensureFrameworkAgents;
7
6
  exports.ensureFrameworkCommandSubtrees = ensureFrameworkCommandSubtrees;
8
7
  exports.workspacePathFor = workspacePathFor;
9
8
  exports.ensureWorkspace = ensureWorkspace;
@@ -16,63 +15,6 @@ const framework_manager_1 = require("./framework-manager");
16
15
  const framework_migration_1 = require("./framework-migration");
17
16
  const artifact_registry_1 = require("./artifact-registry");
18
17
  const agent_mcp_config_1 = require("./agent-mcp-config");
19
- /**
20
- * Windows repair: ensure the workspace's `<providerDir>/agents` holds the
21
- * framework's `sr-*` agent definitions, copied from the REAL versioned framework
22
- * dir (`~/.specrails/framework/<version>/<providerDir>/agents`).
23
- *
24
- * Why: on Windows the `framework/current` JUNCTION can be untraversable by Node's
25
- * `fs` ("UNKNOWN: scandir" / "untrusted mount point"); the bundled core's
26
- * `assemble` sources the agents THROUGH `current`, so `linkAgentFiles` reads
27
- * nothing and the workspace ends up with NO `sr-*` agents — the implement
28
- * pipeline then has no sub-agents to delegate to (architect/developer/reviewer)
29
- * and silently runs everything inline. The versioned dir is a real, traversable
30
- * directory, so we read it directly. (The proper fix lives in core's `assemble`;
31
- * this repairs already-broken installs without waiting for a core republish, and
32
- * mirrors exactly what the fixed core does on Windows — copy from the version
33
- * dir.) `assemble` never re-runs on a rail spawn, so a workspace broken at setup
34
- * stays broken; this runs per rail spawn to self-heal it.
35
- *
36
- * NO-OP on POSIX (per-file symlinks already populate the workspace correctly;
37
- * byte-identical). Additive (never touches user `custom-*.md`) + idempotent.
38
- * Returns the number of agents copied.
39
- */
40
- function ensureFrameworkAgents(workspaceDir, providerDir, home) {
41
- (0, core_update_state_1.assertWorkspaceCoreReady)(workspaceDir, home);
42
- if (process.platform !== 'win32')
43
- return 0;
44
- const root = (0, framework_manager_1.frameworkRoot)(home);
45
- const version = (0, framework_manager_1.readCurrentFrameworkVersion)(home);
46
- if (!version)
47
- return 0;
48
- const src = path_1.default.join(root, version, providerDir, 'agents');
49
- let entries;
50
- try {
51
- entries = fs_1.default.readdirSync(src);
52
- }
53
- catch {
54
- return 0; // framework agents not materialized for this provider
55
- }
56
- const dest = path_1.default.join(workspaceDir, providerDir, 'agents');
57
- let copied = 0;
58
- for (const name of entries) {
59
- // Framework agents only; never clobber a user/plugin `custom-*.md`.
60
- if (!name.endsWith('.md') || name.startsWith('custom-'))
61
- continue;
62
- const destFile = path_1.default.join(dest, name);
63
- if (fs_1.default.existsSync(destFile))
64
- continue;
65
- try {
66
- fs_1.default.mkdirSync(dest, { recursive: true });
67
- fs_1.default.copyFileSync(path_1.default.join(src, name), destFile);
68
- copied += 1;
69
- }
70
- catch {
71
- /* best-effort per file — one failure must never abort the rail spawn */
72
- }
73
- }
74
- return copied;
75
- }
76
18
  /** Whole-directory framework links for the legacy provider layouts. */
77
19
  const DIR_LINKED_SUBTREES = ['commands', 'skills', 'rules'];
78
20
  /**
@@ -80,8 +22,9 @@ const DIR_LINKED_SUBTREES = ['commands', 'skills', 'rules'];
80
22
  *
81
23
  * - `rules/` and `specrails/` are whole-directory framework links.
82
24
  * - `skills/` is a real merged directory. Core links only its framework-owned
83
- * `specrails-*` workflow and `sr-*` role children so OpenSpec, custom, and
84
- * other user-owned direct-child skills can coexist.
25
+ * `specrails-*` workflow children (older Cores also linked `sr-*` role
26
+ * skills) so OpenSpec, custom, and other user-owned direct-child skills can
27
+ * coexist.
85
28
  *
86
29
  * The Windows repair must mirror that ownership boundary exactly. Replacing the
87
30
  * Kimi `skills/` root would destroy user state; merely seeing entries in that
@@ -172,21 +115,20 @@ function repairMissingCommands(src, dest) {
172
115
  }
173
116
  }
174
117
  /**
175
- * Windows repair for provider-owned framework links — the sibling of
176
- * `ensureFrameworkAgents`.
118
+ * Windows repair for provider-owned framework links.
177
119
  *
178
- * Why a SEPARATE repair: agents are linked per-file, so a broken `current`
179
- * junction leaves an EMPTY-but-real `agents/` dir that `ensureFrameworkAgents`
180
- * refills file-by-file. `commands`/`skills`/`rules` are instead a single
181
- * dir-symlink INTO `current/<provider>/<subtree>`; when the `current` junction is
182
- * untraversable by the sidecar (the documented Windows failure), that link is
183
- * unreadable and the workspace has NO `/specrails:*` commands at all — the claude
184
- * CLI then reports `Unknown command: /specrails:implement`. This replaces an
120
+ * Why: `commands`/`skills`/`rules` are a single dir-symlink INTO
121
+ * `current/<provider>/<subtree>`; when the `current` junction is untraversable
122
+ * by the sidecar (the documented Windows failure), that link is unreadable and
123
+ * the workspace has NO `/specrails:*` commands at all — the claude CLI then
124
+ * reports `Unknown command: /specrails:implement`. This replaces an
185
125
  * unreadable/missing link with a REAL recursively-copied directory read straight
186
- * from the versioned framework dir (never through `current`).
126
+ * from the versioned framework dir (never through `current`). Role agents need
127
+ * no repair: specrails-core defines them in its runtime, not as workspace files.
128
+ * `assemble` never re-runs on a rail spawn, so this runs per spawn to self-heal.
187
129
  *
188
130
  * Kimi needs an additional per-child repair because its `skills/` root is a real
189
- * merged directory. Only `specrails-*` and `sr-*` children are framework-owned;
131
+ * merged directory. Only `specrails-*` (and legacy `sr-*`) children are framework-owned;
190
132
  * `openspec-*`, `custom-*`, and unknown children are never copied, replaced, or
191
133
  * removed.
192
134
  *