gsd-pi 2.34.0-dev.ed0bfbf → 2.35.0-dev.55dcc60

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 (255) hide show
  1. package/dist/resources/extensions/gsd/changelog.js +162 -0
  2. package/dist/resources/extensions/gsd/commands-bootstrap.js +1 -0
  3. package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
  4. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +5 -1
  5. package/dist/resources/extensions/gsd/commands.js +8 -1
  6. package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  7. package/dist/resources/extensions/gsd/doctor-checks.js +113 -5
  8. package/dist/resources/extensions/gsd/doctor-proactive.js +22 -0
  9. package/dist/resources/extensions/gsd/doctor.js +36 -0
  10. package/dist/resources/extensions/gsd/guided-flow.js +4 -2
  11. package/dist/resources/extensions/gsd/preferences-validation.js +38 -0
  12. package/dist/resources/extensions/gsd/preferences.js +2 -0
  13. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  14. package/package.json +1 -1
  15. package/packages/pi-agent-core/dist/agent-loop.d.ts +14 -0
  16. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  17. package/packages/pi-agent-core/dist/agent-loop.js +24 -27
  18. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  19. package/packages/pi-agent-core/dist/agent.d.ts +1 -0
  20. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  21. package/packages/pi-agent-core/dist/agent.js +11 -22
  22. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  23. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  24. package/packages/pi-agent-core/dist/proxy.js +2 -8
  25. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  26. package/packages/pi-agent-core/src/agent-loop.ts +30 -27
  27. package/packages/pi-agent-core/src/agent.ts +12 -23
  28. package/packages/pi-agent-core/src/proxy.ts +2 -8
  29. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  30. package/packages/pi-ai/dist/providers/azure-openai-responses.js +5 -41
  31. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  32. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-completions.js +10 -73
  34. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  35. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  36. package/packages/pi-ai/dist/providers/openai-responses.js +8 -79
  37. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  38. package/packages/pi-ai/dist/providers/openai-shared.d.ts +65 -0
  39. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -0
  40. package/packages/pi-ai/dist/providers/openai-shared.js +146 -0
  41. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -0
  42. package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  43. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +7 -135
  44. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
  45. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +7 -135
  47. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  48. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts +46 -0
  49. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts.map +1 -0
  50. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js +160 -0
  51. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js.map +1 -0
  52. package/packages/pi-ai/src/providers/azure-openai-responses.ts +11 -45
  53. package/packages/pi-ai/src/providers/openai-completions.ts +16 -86
  54. package/packages/pi-ai/src/providers/openai-responses.ts +15 -95
  55. package/packages/pi-ai/src/providers/openai-shared.ts +193 -0
  56. package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +14 -162
  57. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +13 -161
  58. package/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts +201 -0
  59. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +16 -63
  60. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/agent-session.js +104 -641
  62. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +0 -1
  64. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  65. package/packages/pi-coding-agent/dist/core/auth-storage.js +4 -35
  66. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  68. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +5 -43
  69. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +11 -69
  72. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +40 -0
  74. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  75. package/packages/pi-coding-agent/dist/core/compaction/utils.js +78 -0
  76. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +77 -0
  78. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -0
  79. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +331 -0
  80. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -0
  81. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +2 -2
  82. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
  84. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +15 -0
  86. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/extensions/runner.js +129 -243
  88. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +49 -42
  90. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/extensions/types.js +2 -21
  92. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts +39 -0
  94. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts.map +1 -0
  95. package/packages/pi-coding-agent/dist/core/lock-utils.js +89 -0
  96. package/packages/pi-coding-agent/dist/core/lock-utils.js.map +1 -0
  97. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +2 -0
  98. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/lsp/config.js +4 -1
  100. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/lsp/index.js +52 -107
  103. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +2 -21
  106. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +0 -1
  108. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/lsp/types.js +0 -28
  110. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/package-manager.js +2 -4
  113. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +2 -4
  115. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/resource-loader.js +33 -58
  117. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +87 -0
  119. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -0
  120. package/packages/pi-coding-agent/dist/core/retry-handler.js +295 -0
  121. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -0
  122. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -1
  123. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -28
  125. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +8 -0
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/settings-manager.js +76 -166
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/skills.js +1 -3
  132. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  134. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/index.js +1 -1
  136. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +9 -26
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -13
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts +44 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts.map +1 -0
  146. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js +61 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js.map +1 -0
  148. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js +6 -9
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +65 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +6 -16
  154. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts +12 -0
  156. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +175 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts +6 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts.map +1 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js +15 -0
  162. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js.map +1 -0
  163. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/print-mode.js +2 -30
  165. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +2 -28
  168. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts +19 -0
  170. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts.map +1 -0
  171. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js +45 -0
  172. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js.map +1 -0
  173. package/packages/pi-coding-agent/dist/utils/error.d.ts +5 -0
  174. package/packages/pi-coding-agent/dist/utils/error.d.ts.map +1 -0
  175. package/packages/pi-coding-agent/dist/utils/error.js +7 -0
  176. package/packages/pi-coding-agent/dist/utils/error.js.map +1 -0
  177. package/packages/pi-coding-agent/package.json +1 -1
  178. package/packages/pi-coding-agent/src/core/agent-session.ts +117 -745
  179. package/packages/pi-coding-agent/src/core/auth-storage.ts +4 -38
  180. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +7 -53
  181. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +14 -74
  182. package/packages/pi-coding-agent/src/core/compaction/utils.ts +100 -0
  183. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +424 -0
  184. package/packages/pi-coding-agent/src/core/extensions/index.ts +1 -21
  185. package/packages/pi-coding-agent/src/core/extensions/runner.ts +119 -243
  186. package/packages/pi-coding-agent/src/core/extensions/types.ts +50 -69
  187. package/packages/pi-coding-agent/src/core/lock-utils.ts +113 -0
  188. package/packages/pi-coding-agent/src/core/lsp/config.ts +4 -1
  189. package/packages/pi-coding-agent/src/core/lsp/index.ts +83 -152
  190. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +2 -22
  191. package/packages/pi-coding-agent/src/core/lsp/types.ts +0 -29
  192. package/packages/pi-coding-agent/src/core/package-manager.ts +1 -4
  193. package/packages/pi-coding-agent/src/core/resource-loader.ts +43 -67
  194. package/packages/pi-coding-agent/src/core/retry-handler.ts +359 -0
  195. package/packages/pi-coding-agent/src/core/session-manager.ts +3 -30
  196. package/packages/pi-coding-agent/src/core/settings-manager.ts +85 -164
  197. package/packages/pi-coding-agent/src/core/skills.ts +1 -4
  198. package/packages/pi-coding-agent/src/index.ts +1 -7
  199. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +17 -29
  200. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -13
  201. package/packages/pi-coding-agent/src/modes/interactive/components/tree-render-utils.ts +81 -0
  202. package/packages/pi-coding-agent/src/modes/interactive/components/tree-selector.ts +14 -19
  203. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +7 -18
  204. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +196 -0
  205. package/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts +14 -0
  206. package/packages/pi-coding-agent/src/modes/print-mode.ts +2 -30
  207. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -28
  208. package/packages/pi-coding-agent/src/modes/shared/command-context-actions.ts +53 -0
  209. package/packages/pi-coding-agent/src/utils/error.ts +6 -0
  210. package/packages/pi-tui/dist/components/markdown.d.ts +5 -0
  211. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  212. package/packages/pi-tui/dist/components/markdown.js +25 -31
  213. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  214. package/packages/pi-tui/dist/keys.d.ts +0 -4
  215. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  216. package/packages/pi-tui/dist/keys.js +94 -162
  217. package/packages/pi-tui/dist/keys.js.map +1 -1
  218. package/packages/pi-tui/src/components/markdown.ts +25 -29
  219. package/packages/pi-tui/src/keys.ts +94 -173
  220. package/pkg/dist/modes/interactive/theme/theme.d.ts +65 -0
  221. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  222. package/pkg/dist/modes/interactive/theme/theme.js +6 -16
  223. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  224. package/pkg/dist/modes/interactive/theme/themes.d.ts +12 -0
  225. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  226. package/pkg/dist/modes/interactive/theme/themes.js +175 -0
  227. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -0
  228. package/pkg/package.json +1 -1
  229. package/src/resources/extensions/gsd/changelog.ts +213 -0
  230. package/src/resources/extensions/gsd/commands-bootstrap.ts +1 -0
  231. package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
  232. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +5 -1
  233. package/src/resources/extensions/gsd/commands.ts +9 -1
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  235. package/src/resources/extensions/gsd/doctor-checks.ts +107 -5
  236. package/src/resources/extensions/gsd/doctor-proactive.ts +24 -0
  237. package/src/resources/extensions/gsd/doctor-types.ts +9 -1
  238. package/src/resources/extensions/gsd/doctor.ts +35 -0
  239. package/src/resources/extensions/gsd/guided-flow.ts +4 -2
  240. package/src/resources/extensions/gsd/preferences-validation.ts +38 -0
  241. package/src/resources/extensions/gsd/preferences.ts +2 -0
  242. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
  243. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +98 -2
  244. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +59 -3
  245. package/src/resources/extensions/gsd/tests/preferences.test.ts +28 -0
  246. package/src/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  247. package/packages/pi-coding-agent/dist/modes/interactive/theme/dark.json +0 -85
  248. package/packages/pi-coding-agent/dist/modes/interactive/theme/light.json +0 -84
  249. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.json +0 -335
  250. package/packages/pi-coding-agent/src/modes/interactive/theme/dark.json +0 -85
  251. package/packages/pi-coding-agent/src/modes/interactive/theme/light.json +0 -84
  252. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.json +0 -335
  253. package/pkg/dist/modes/interactive/theme/dark.json +0 -85
  254. package/pkg/dist/modes/interactive/theme/light.json +0 -84
  255. package/pkg/dist/modes/interactive/theme/theme-schema.json +0 -335
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shared file-locking utilities built on `proper-lockfile`.
3
+ *
4
+ * Centralises the synchronous retry-loop and async lock/release patterns
5
+ * that were previously duplicated across auth-storage, session-manager,
6
+ * settings-manager, and models-json-writer.
7
+ */
8
+
9
+ import lockfile from "proper-lockfile";
10
+
11
+ const DEFAULT_MAX_ATTEMPTS = 10;
12
+ const DEFAULT_DELAY_MS = 20;
13
+
14
+ /**
15
+ * Acquire a synchronous file lock with retry.
16
+ *
17
+ * Retries up to `maxAttempts` times when the lock is held by another process
18
+ * (ELOCKED), using a busy-wait between attempts.
19
+ *
20
+ * @returns A release function to unlock.
21
+ * @throws On non-ELOCKED errors or when all attempts are exhausted.
22
+ */
23
+ export function acquireLockSyncWithRetry(
24
+ lockPath: string,
25
+ maxAttempts: number = DEFAULT_MAX_ATTEMPTS,
26
+ delayMs: number = DEFAULT_DELAY_MS,
27
+ ): () => void {
28
+ let lastError: unknown;
29
+
30
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
31
+ try {
32
+ return lockfile.lockSync(lockPath, { realpath: false });
33
+ } catch (error) {
34
+ const code =
35
+ typeof error === "object" && error !== null && "code" in error
36
+ ? String((error as { code?: unknown }).code)
37
+ : undefined;
38
+ if (code !== "ELOCKED" || attempt === maxAttempts) {
39
+ throw error;
40
+ }
41
+ lastError = error;
42
+ const start = Date.now();
43
+ while (Date.now() - start < delayMs) {
44
+ // Busy-wait to avoid changing callers to async.
45
+ }
46
+ }
47
+ }
48
+
49
+ throw (lastError as Error) ?? new Error("Failed to acquire file lock");
50
+ }
51
+
52
+ /**
53
+ * Non-throwing variant of {@link acquireLockSyncWithRetry}.
54
+ *
55
+ * Returns `undefined` instead of throwing when the lock cannot be acquired,
56
+ * allowing callers to proceed without the lock rather than losing data.
57
+ */
58
+ export function tryAcquireLockSync(
59
+ lockPath: string,
60
+ maxAttempts: number = DEFAULT_MAX_ATTEMPTS,
61
+ delayMs: number = DEFAULT_DELAY_MS,
62
+ ): (() => void) | undefined {
63
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
64
+ try {
65
+ return lockfile.lockSync(lockPath, { realpath: false });
66
+ } catch (error) {
67
+ const code =
68
+ typeof error === "object" && error !== null && "code" in error
69
+ ? String((error as { code?: unknown }).code)
70
+ : undefined;
71
+ if (code !== "ELOCKED" || attempt === maxAttempts) {
72
+ // Non-fatal: proceed without lock rather than losing data
73
+ return undefined;
74
+ }
75
+ const start = Date.now();
76
+ while (Date.now() - start < delayMs) {
77
+ // Busy-wait to avoid changing callers to async.
78
+ }
79
+ }
80
+ }
81
+ return undefined;
82
+ }
83
+
84
+ export interface AsyncLockOptions {
85
+ /** Maximum staleness in ms before the lock is considered stale. */
86
+ staleMs?: number;
87
+ /** Called if the lock is compromised while held. */
88
+ onCompromised?: (err: Error) => void;
89
+ }
90
+
91
+ /**
92
+ * Acquire an async file lock with retries and optional staleness detection.
93
+ *
94
+ * Uses `proper-lockfile`'s async API with exponential-backoff retries.
95
+ *
96
+ * @returns A release function (async) to unlock.
97
+ */
98
+ export async function acquireLockAsync(
99
+ lockPath: string,
100
+ options?: AsyncLockOptions,
101
+ ): Promise<() => Promise<void>> {
102
+ return lockfile.lock(lockPath, {
103
+ retries: {
104
+ retries: 10,
105
+ factor: 2,
106
+ minTimeout: 100,
107
+ maxTimeout: 10000,
108
+ randomize: true,
109
+ },
110
+ stale: options?.staleMs,
111
+ onCompromised: options?.onCompromised,
112
+ });
113
+ }
@@ -176,7 +176,7 @@ const LOCAL_BIN_PATHS: Array<{ markers: string[]; binDir: string }> = [
176
176
  { markers: ["go.mod", "go.sum"], binDir: "bin" },
177
177
  ];
178
178
 
179
- function which(command: string): string | null {
179
+ export function which(command: string): string | null {
180
180
  // On Windows, prefer `where.exe` over `which` — MSYS/Git Bash's `which`
181
181
  // returns POSIX paths (/c/Users/...) that Node's spawn() can't execute.
182
182
  // `where.exe` returns native Windows paths (C:\Users\...).
@@ -319,3 +319,6 @@ export function getServersForFile(config: LspConfig, filePath: string): Array<[s
319
319
  });
320
320
  }
321
321
 
322
+ export function getServerForFile(config: LspConfig, filePath: string): [string, ServerConfig] | null {
323
+ return getServersForFile(config, filePath)[0] ?? null;
324
+ }
@@ -14,7 +14,7 @@ import {
14
14
  setIdleTimeout,
15
15
  WARMUP_TIMEOUT_MS,
16
16
  } from "./client.js";
17
- import { getServersForFile, type LspConfig, loadConfig, hasRootMarkers, resolveCommand } from "./config.js";
17
+ import { getServerForFile, getServersForFile, type LspConfig, loadConfig, hasRootMarkers, resolveCommand } from "./config.js";
18
18
  import { applyTextEdits, applyWorkspaceEdit } from "./edits.js";
19
19
  import { ToolAbortError, clampTimeout, throwIfAborted } from "./helpers.js";
20
20
  import { detectLspmux } from "./lspmux.js";
@@ -144,15 +144,6 @@ function getLspServers(config: LspConfig): Array<[string, ServerConfig]> {
144
144
  return Object.entries(config.servers) as Array<[string, ServerConfig]>;
145
145
  }
146
146
 
147
- function getLspServersForFile(config: LspConfig, filePath: string): Array<[string, ServerConfig]> {
148
- return getServersForFile(config, filePath);
149
- }
150
-
151
- function getLspServerForFile(config: LspConfig, filePath: string): [string, ServerConfig] | null {
152
- const servers = getLspServersForFile(config, filePath);
153
- return servers.length > 0 ? servers[0] : null;
154
- }
155
-
156
147
  const DIAGNOSTIC_MESSAGE_LIMIT = 50;
157
148
  const SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS = 3000;
158
149
  const BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS = 400;
@@ -197,6 +188,73 @@ async function formatLocationWithContext(location: Location, cwd: string): Promi
197
188
  return `${header}\n${context.map(lineText => ` ${lineText}`).join("\n")}`;
198
189
  }
199
190
 
191
+ async function formatLocationResults(
192
+ result: Location | Location[] | LocationLink | LocationLink[] | null,
193
+ label: string,
194
+ cwd: string,
195
+ ): Promise<string> {
196
+ const locations = normalizeLocationResult(result);
197
+ if (locations.length === 0) {
198
+ return `No ${label} found`;
199
+ }
200
+ const lines = await Promise.all(locations.map(location => formatLocationWithContext(location, cwd)));
201
+ return `Found ${locations.length} ${label}(s):\n${lines.join("\n")}`;
202
+ }
203
+
204
+ async function formatCallHierarchyResults(
205
+ client: LspClient,
206
+ position: { line: number; character: number },
207
+ uri: string,
208
+ direction: "incoming" | "outgoing",
209
+ cwd: string,
210
+ signal?: AbortSignal,
211
+ ): Promise<string> {
212
+ const prepareResult = (await sendRequest(
213
+ client,
214
+ "textDocument/prepareCallHierarchy",
215
+ { textDocument: { uri }, position },
216
+ signal,
217
+ )) as CallHierarchyItem[] | null;
218
+
219
+ if (!prepareResult || prepareResult.length === 0) {
220
+ return "No call hierarchy item found at this position";
221
+ }
222
+
223
+ const method = direction === "incoming" ? "callHierarchy/incomingCalls" : "callHierarchy/outgoingCalls";
224
+ const callResult = (await sendRequest(client, method, { item: prepareResult[0] }, signal)) as
225
+ | CallHierarchyIncomingCall[]
226
+ | CallHierarchyOutgoingCall[]
227
+ | null;
228
+
229
+ if (!callResult || callResult.length === 0) {
230
+ const verb = direction === "incoming" ? "incoming calls" : "outgoing calls";
231
+ const prep = direction === "incoming" ? "for" : "from";
232
+ return `No ${verb} found ${prep} ${prepareResult[0].name}`;
233
+ }
234
+
235
+ const lines: string[] = [];
236
+ const limited = callResult.slice(0, REFERENCE_CONTEXT_LIMIT);
237
+ for (const call of limited) {
238
+ const item = "from" in call ? call.from : call.to;
239
+ const header = formatCallHierarchyItem(item, cwd);
240
+ const filePath = uriToFile(item.uri);
241
+ const callLine = ("from" in call ? call.fromRanges[0]?.start.line : undefined) ?? item.selectionRange.start.line;
242
+ const context = await readLocationContext(filePath, callLine + 1, LOCATION_CONTEXT_LINES);
243
+ if (context.length > 0) {
244
+ lines.push(` ${header}\n${context.map(l => ` ${l}`).join("\n")}`);
245
+ } else {
246
+ lines.push(` ${header}`);
247
+ }
248
+ }
249
+
250
+ const noun = direction === "incoming" ? "caller" : "callee";
251
+ const prep = direction === "incoming" ? "of" : "from";
252
+ const truncation = callResult.length > REFERENCE_CONTEXT_LIMIT
253
+ ? `\n ... ${callResult.length - REFERENCE_CONTEXT_LIMIT} additional ${noun}(s) omitted`
254
+ : "";
255
+ return `${callResult.length} ${noun}(s) ${prep} ${prepareResult[0].name}:\n${lines.join("\n")}${truncation}`;
256
+ }
257
+
200
258
  async function reloadServer(client: LspClient, serverName: string, signal?: AbortSignal): Promise<string> {
201
259
  let output = `Restarted ${serverName}`;
202
260
  const reloadMethods = ["rust-analyzer/reloadWorkspace", "workspace/didChangeConfiguration"];
@@ -647,7 +705,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
647
705
  }
648
706
 
649
707
  // File-specific actions
650
- const serverInfo = resolvedFile ? getLspServerForFile(config, resolvedFile) : null;
708
+ const serverInfo = resolvedFile ? getServerForFile(config, resolvedFile) : null;
651
709
  if (!serverInfo) {
652
710
  return {
653
711
  content: [{ type: "text", text: "No language server found for this action" }],
@@ -676,74 +734,35 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
676
734
 
677
735
  switch (action) {
678
736
  case "definition": {
679
- const result = (await sendRequest(
737
+ const result = await sendRequest(
680
738
  client,
681
739
  "textDocument/definition",
682
- {
683
- textDocument: { uri },
684
- position,
685
- },
740
+ { textDocument: { uri }, position },
686
741
  signal,
687
- )) as Location | Location[] | LocationLink | LocationLink[] | null;
688
-
689
- const locations = normalizeLocationResult(result);
690
-
691
- if (locations.length === 0) {
692
- output = "No definition found";
693
- } else {
694
- const lines = await Promise.all(
695
- locations.map(location => formatLocationWithContext(location, cwd)),
696
- );
697
- output = `Found ${locations.length} definition(s):\n${lines.join("\n")}`;
698
- }
742
+ );
743
+ output = await formatLocationResults(result as Location | Location[] | LocationLink | LocationLink[] | null, "definition", cwd);
699
744
  break;
700
745
  }
701
746
 
702
747
  case "type_definition": {
703
- const result = (await sendRequest(
748
+ const result = await sendRequest(
704
749
  client,
705
750
  "textDocument/typeDefinition",
706
- {
707
- textDocument: { uri },
708
- position,
709
- },
751
+ { textDocument: { uri }, position },
710
752
  signal,
711
- )) as Location | Location[] | LocationLink | LocationLink[] | null;
712
-
713
- const locations = normalizeLocationResult(result);
714
-
715
- if (locations.length === 0) {
716
- output = "No type definition found";
717
- } else {
718
- const lines = await Promise.all(
719
- locations.map(location => formatLocationWithContext(location, cwd)),
720
- );
721
- output = `Found ${locations.length} type definition(s):\n${lines.join("\n")}`;
722
- }
753
+ );
754
+ output = await formatLocationResults(result as Location | Location[] | LocationLink | LocationLink[] | null, "type definition", cwd);
723
755
  break;
724
756
  }
725
757
 
726
758
  case "implementation": {
727
- const result = (await sendRequest(
759
+ const result = await sendRequest(
728
760
  client,
729
761
  "textDocument/implementation",
730
- {
731
- textDocument: { uri },
732
- position,
733
- },
762
+ { textDocument: { uri }, position },
734
763
  signal,
735
- )) as Location | Location[] | LocationLink | LocationLink[] | null;
736
-
737
- const locations = normalizeLocationResult(result);
738
-
739
- if (locations.length === 0) {
740
- output = "No implementation found";
741
- } else {
742
- const lines = await Promise.all(
743
- locations.map(location => formatLocationWithContext(location, cwd)),
744
- );
745
- output = `Found ${locations.length} implementation(s):\n${lines.join("\n")}`;
746
- }
764
+ );
765
+ output = await formatLocationResults(result as Location | Location[] | LocationLink | LocationLink[] | null, "implementation", cwd);
747
766
  break;
748
767
  }
749
768
 
@@ -917,100 +936,12 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
917
936
  }
918
937
 
919
938
  case "incoming_calls": {
920
- const prepareResult = (await sendRequest(
921
- client,
922
- "textDocument/prepareCallHierarchy",
923
- {
924
- textDocument: { uri },
925
- position,
926
- },
927
- signal,
928
- )) as CallHierarchyItem[] | null;
929
-
930
- if (!prepareResult || prepareResult.length === 0) {
931
- output = "No call hierarchy item found at this position";
932
- break;
933
- }
934
-
935
- const incomingResult = (await sendRequest(
936
- client,
937
- "callHierarchy/incomingCalls",
938
- { item: prepareResult[0] },
939
- signal,
940
- )) as CallHierarchyIncomingCall[] | null;
941
-
942
- if (!incomingResult || incomingResult.length === 0) {
943
- output = `No incoming calls found for ${prepareResult[0].name}`;
944
- break;
945
- }
946
-
947
- const incomingLines: string[] = [];
948
- const limitedIncoming = incomingResult.slice(0, REFERENCE_CONTEXT_LIMIT);
949
- for (const call of limitedIncoming) {
950
- const header = formatCallHierarchyItem(call.from, cwd);
951
- const filePath = uriToFile(call.from.uri);
952
- const callLine = call.fromRanges[0]?.start.line ?? call.from.selectionRange.start.line;
953
- const context = await readLocationContext(filePath, callLine + 1, LOCATION_CONTEXT_LINES);
954
- if (context.length > 0) {
955
- incomingLines.push(` ${header}\n${context.map(l => ` ${l}`).join("\n")}`);
956
- } else {
957
- incomingLines.push(` ${header}`);
958
- }
959
- }
960
-
961
- const truncation = incomingResult.length > REFERENCE_CONTEXT_LIMIT
962
- ? `\n ... ${incomingResult.length - REFERENCE_CONTEXT_LIMIT} additional caller(s) omitted`
963
- : "";
964
- output = `${incomingResult.length} caller(s) of ${prepareResult[0].name}:\n${incomingLines.join("\n")}${truncation}`;
939
+ output = await formatCallHierarchyResults(client, position, uri, "incoming", cwd, signal);
965
940
  break;
966
941
  }
967
942
 
968
943
  case "outgoing_calls": {
969
- const prepareResult = (await sendRequest(
970
- client,
971
- "textDocument/prepareCallHierarchy",
972
- {
973
- textDocument: { uri },
974
- position,
975
- },
976
- signal,
977
- )) as CallHierarchyItem[] | null;
978
-
979
- if (!prepareResult || prepareResult.length === 0) {
980
- output = "No call hierarchy item found at this position";
981
- break;
982
- }
983
-
984
- const outgoingResult = (await sendRequest(
985
- client,
986
- "callHierarchy/outgoingCalls",
987
- { item: prepareResult[0] },
988
- signal,
989
- )) as CallHierarchyOutgoingCall[] | null;
990
-
991
- if (!outgoingResult || outgoingResult.length === 0) {
992
- output = `No outgoing calls found from ${prepareResult[0].name}`;
993
- break;
994
- }
995
-
996
- const outgoingLines: string[] = [];
997
- const limitedOutgoing = outgoingResult.slice(0, REFERENCE_CONTEXT_LIMIT);
998
- for (const call of limitedOutgoing) {
999
- const header = formatCallHierarchyItem(call.to, cwd);
1000
- const filePath = uriToFile(call.to.uri);
1001
- const callLine = call.to.selectionRange.start.line;
1002
- const context = await readLocationContext(filePath, callLine + 1, LOCATION_CONTEXT_LINES);
1003
- if (context.length > 0) {
1004
- outgoingLines.push(` ${header}\n${context.map(l => ` ${l}`).join("\n")}`);
1005
- } else {
1006
- outgoingLines.push(` ${header}`);
1007
- }
1008
- }
1009
-
1010
- const outTruncation = outgoingResult.length > REFERENCE_CONTEXT_LIMIT
1011
- ? `\n ... ${outgoingResult.length - REFERENCE_CONTEXT_LIMIT} additional callee(s) omitted`
1012
- : "";
1013
- output = `${outgoingResult.length} callee(s) from ${prepareResult[0].name}:\n${outgoingLines.join("\n")}${outTruncation}`;
944
+ output = await formatCallHierarchyResults(client, position, uri, "outgoing", cwd, signal);
1014
945
  break;
1015
946
  }
1016
947
 
@@ -1,8 +1,9 @@
1
- import { execSync, spawn } from "node:child_process";
1
+ import { spawn } from "node:child_process";
2
2
  import * as fsPromises from "node:fs/promises";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { LSP_LIVENESS_TIMEOUT_MS, LSP_STATE_CACHE_TTL_MS } from "../constants.js";
6
+ import { which } from "./config.js";
6
7
 
7
8
  /**
8
9
  * lspmux integration for LSP server multiplexing.
@@ -43,27 +44,6 @@ const DEFAULT_SUPPORTED_SERVERS = new Set([
43
44
  ]);
44
45
 
45
46
 
46
- // =============================================================================
47
- // Helpers
48
- // =============================================================================
49
-
50
- function which(command: string): string | null {
51
- try {
52
- // On Windows, prefer `where.exe` over `which` — MSYS/Git Bash's `which`
53
- // returns POSIX paths (/c/Users/...) that Node's spawn() can't execute (#1121).
54
- const isWindows = process.platform === "win32";
55
- const cmd = isWindows ? "where.exe" : "which";
56
- const result = isWindows
57
- ? execSync(`${cmd} ${command}`, { encoding: "utf-8" })
58
- : execSync(`which ${command}`, { encoding: "utf-8" });
59
- // `where.exe` may return multiple lines — take the first
60
- const resolved = result.trim().split(/\r?\n/)[0]?.trim();
61
- return resolved || null;
62
- } catch {
63
- return null;
64
- }
65
- }
66
-
67
47
  // =============================================================================
68
48
  // Config Path
69
49
  // =============================================================================
@@ -256,35 +256,6 @@ export type SymbolKind =
256
256
  | 25 // Operator
257
257
  | 26; // TypeParameter
258
258
 
259
- export const SYMBOL_KIND_NAMES: Record<SymbolKind, string> = {
260
- 1: "File",
261
- 2: "Module",
262
- 3: "Namespace",
263
- 4: "Package",
264
- 5: "Class",
265
- 6: "Method",
266
- 7: "Property",
267
- 8: "Field",
268
- 9: "Constructor",
269
- 10: "Enum",
270
- 11: "Interface",
271
- 12: "Function",
272
- 13: "Variable",
273
- 14: "Constant",
274
- 15: "String",
275
- 16: "Number",
276
- 17: "Boolean",
277
- 18: "Array",
278
- 19: "Object",
279
- 20: "Key",
280
- 21: "Null",
281
- 22: "EnumMember",
282
- 23: "Struct",
283
- 24: "Event",
284
- 25: "Operator",
285
- 26: "TypeParameter",
286
- };
287
-
288
259
  export interface DocumentSymbol {
289
260
  name: string;
290
261
  detail?: string;
@@ -7,6 +7,7 @@ import ignore from "ignore";
7
7
  import { minimatch } from "minimatch";
8
8
  import { CONFIG_DIR_NAME } from "../config.js";
9
9
  import { type GitSource, parseGitUrl } from "../utils/git.js";
10
+ import { toPosixPath } from "../utils/path-display.js";
10
11
  import type { PackageSource, SettingsManager } from "./settings-manager.js";
11
12
 
12
13
  const NETWORK_TIMEOUT_MS = 10000;
@@ -121,10 +122,6 @@ const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"];
121
122
 
122
123
  type IgnoreMatcher = ReturnType<typeof ignore>;
123
124
 
124
- function toPosixPath(p: string): string {
125
- return p.split(sep).join("/");
126
- }
127
-
128
125
  function prefixIgnorePattern(line: string, prefix: string): string | null {
129
126
  const trimmed = line.trim();
130
127
  if (!trimmed) return null;
@@ -4,7 +4,7 @@ import { join, resolve, sep } from "node:path";
4
4
  import chalk from "chalk";
5
5
  import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
6
6
  import { loadThemeFromPath, type Theme } from "../modes/interactive/theme/theme.js";
7
- import type { ResourceDiagnostic } from "./diagnostics.js";
7
+ import type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.js";
8
8
 
9
9
  export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.js";
10
10
 
@@ -422,12 +422,12 @@ export class DefaultResourceLoader implements ResourceLoader {
422
422
  this.agentsFiles = resolvedAgentsFiles.agentsFiles;
423
423
 
424
424
  const baseSystemPrompt = resolvePromptInput(
425
- this.systemPromptSource ?? this.discoverSystemPromptFile(),
425
+ this.systemPromptSource ?? this.discoverFileInSearchPaths("SYSTEM.md"),
426
426
  "system prompt",
427
427
  );
428
428
  this.systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(baseSystemPrompt) : baseSystemPrompt;
429
429
 
430
- const appendSource = this.appendSystemPromptSource ?? this.discoverAppendSystemPromptFile();
430
+ const appendSource = this.appendSystemPromptSource ?? this.discoverFileInSearchPaths("APPEND_SYSTEM.md");
431
431
  const resolvedAppend = resolvePromptInput(appendSource, "append system prompt");
432
432
  const baseAppend = resolvedAppend ? [resolvedAppend] : [];
433
433
  this.appendSystemPrompt = this.appendSystemPromptOverride
@@ -485,7 +485,13 @@ export class DefaultResourceLoader implements ResourceLoader {
485
485
  promptPaths,
486
486
  includeDefaults: false,
487
487
  });
488
- promptsResult = this.dedupePrompts(allPrompts);
488
+ const deduped = this.dedupeResources(allPrompts, {
489
+ getName: (p) => p.name,
490
+ getPath: (p) => p.filePath,
491
+ resourceType: "prompt",
492
+ namePrefix: "/",
493
+ });
494
+ promptsResult = { prompts: deduped.items, diagnostics: deduped.diagnostics };
489
495
  }
490
496
  const resolvedPrompts = this.promptsOverride ? this.promptsOverride(promptsResult) : promptsResult;
491
497
  this.prompts = resolvedPrompts.prompts;
@@ -508,8 +514,12 @@ export class DefaultResourceLoader implements ResourceLoader {
508
514
  themesResult = { themes: [], diagnostics: [] };
509
515
  } else {
510
516
  const loaded = this.loadThemes(themePaths, false);
511
- const deduped = this.dedupeThemes(loaded.themes);
512
- themesResult = { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };
517
+ const deduped = this.dedupeResources(loaded.themes, {
518
+ getName: (t) => t.name ?? "unnamed",
519
+ getPath: (t) => t.sourcePath,
520
+ resourceType: "theme",
521
+ });
522
+ themesResult = { themes: deduped.items, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };
513
523
  }
514
524
  const resolvedThemes = this.themesOverride ? this.themesOverride(themesResult) : themesResult;
515
525
  this.themes = resolvedThemes.themes;
@@ -686,84 +696,50 @@ export class DefaultResourceLoader implements ResourceLoader {
686
696
  return { extensions, errors };
687
697
  }
688
698
 
689
- private dedupePrompts(prompts: PromptTemplate[]): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {
690
- const seen = new Map<string, PromptTemplate>();
699
+ private dedupeResources<T>(
700
+ items: T[],
701
+ options: {
702
+ getName: (item: T) => string;
703
+ getPath: (item: T) => string | undefined;
704
+ resourceType: ResourceCollision["resourceType"];
705
+ namePrefix?: string;
706
+ },
707
+ ): { items: T[]; diagnostics: ResourceDiagnostic[] } {
708
+ const seen = new Map<string, T>();
691
709
  const diagnostics: ResourceDiagnostic[] = [];
710
+ const { getName, getPath, resourceType, namePrefix = "" } = options;
692
711
 
693
- for (const prompt of prompts) {
694
- const existing = seen.get(prompt.name);
695
- if (existing) {
696
- diagnostics.push({
697
- type: "collision",
698
- message: `name "/${prompt.name}" collision`,
699
- path: prompt.filePath,
700
- collision: {
701
- resourceType: "prompt",
702
- name: prompt.name,
703
- winnerPath: existing.filePath,
704
- loserPath: prompt.filePath,
705
- },
706
- });
707
- } else {
708
- seen.set(prompt.name, prompt);
709
- }
710
- }
711
-
712
- return { prompts: Array.from(seen.values()), diagnostics };
713
- }
714
-
715
- private dedupeThemes(themes: Theme[]): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {
716
- const seen = new Map<string, Theme>();
717
- const diagnostics: ResourceDiagnostic[] = [];
718
-
719
- for (const t of themes) {
720
- const name = t.name ?? "unnamed";
712
+ for (const item of items) {
713
+ const name = getName(item);
721
714
  const existing = seen.get(name);
722
715
  if (existing) {
723
716
  diagnostics.push({
724
717
  type: "collision",
725
- message: `name "${name}" collision`,
726
- path: t.sourcePath,
718
+ message: `name "${namePrefix}${name}" collision`,
719
+ path: getPath(item),
727
720
  collision: {
728
- resourceType: "theme",
721
+ resourceType,
729
722
  name,
730
- winnerPath: existing.sourcePath ?? "<builtin>",
731
- loserPath: t.sourcePath ?? "<builtin>",
723
+ winnerPath: getPath(existing) ?? "<builtin>",
724
+ loserPath: getPath(item) ?? "<builtin>",
732
725
  },
733
726
  });
734
727
  } else {
735
- seen.set(name, t);
728
+ seen.set(name, item);
736
729
  }
737
730
  }
738
731
 
739
- return { themes: Array.from(seen.values()), diagnostics };
732
+ return { items: Array.from(seen.values()), diagnostics };
740
733
  }
741
734
 
742
- private discoverSystemPromptFile(): string | undefined {
743
- const projectPath = join(this.cwd, CONFIG_DIR_NAME, "SYSTEM.md");
744
- if (existsSync(projectPath)) {
745
- return projectPath;
746
- }
747
-
748
- const globalPath = join(this.agentDir, "SYSTEM.md");
749
- if (existsSync(globalPath)) {
750
- return globalPath;
751
- }
752
-
753
- return undefined;
754
- }
755
-
756
- private discoverAppendSystemPromptFile(): string | undefined {
757
- const projectPath = join(this.cwd, CONFIG_DIR_NAME, "APPEND_SYSTEM.md");
758
- if (existsSync(projectPath)) {
759
- return projectPath;
760
- }
761
-
762
- const globalPath = join(this.agentDir, "APPEND_SYSTEM.md");
763
- if (existsSync(globalPath)) {
764
- return globalPath;
735
+ private discoverFileInSearchPaths(filename: string): string | undefined {
736
+ const searchDirs = [join(this.cwd, CONFIG_DIR_NAME), this.agentDir];
737
+ for (const dir of searchDirs) {
738
+ const filePath = join(dir, filename);
739
+ if (existsSync(filePath)) {
740
+ return filePath;
741
+ }
765
742
  }
766
-
767
743
  return undefined;
768
744
  }
769
745