gsd-pi 2.19.0 → 2.20.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 (249) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
  8. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  9. package/dist/resources/extensions/gsd/auto.ts +424 -30
  10. package/dist/resources/extensions/gsd/commands.ts +518 -36
  11. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  12. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  13. package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  14. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  15. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  16. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  17. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  20. package/dist/resources/extensions/gsd/export.ts +81 -2
  21. package/dist/resources/extensions/gsd/files.ts +39 -9
  22. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  23. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  24. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  25. package/dist/resources/extensions/gsd/history.ts +0 -1
  26. package/dist/resources/extensions/gsd/index.ts +277 -1
  27. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  28. package/dist/resources/extensions/gsd/metrics.ts +39 -3
  29. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  31. package/dist/resources/extensions/gsd/preferences.ts +125 -150
  32. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  33. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  35. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  36. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  37. package/dist/resources/extensions/gsd/quick.ts +156 -0
  38. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  39. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  40. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  41. package/dist/resources/extensions/gsd/state.ts +30 -0
  42. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  43. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  44. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  45. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  46. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  48. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  49. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  50. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  51. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  52. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  53. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  54. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  55. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  56. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  57. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  58. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  59. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  60. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  61. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  62. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  63. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  64. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  65. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  66. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  67. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  68. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  69. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  70. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  71. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  72. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  73. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  74. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  75. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  76. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  77. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  78. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  79. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  80. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  81. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  82. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  83. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  84. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  85. package/dist/resources/extensions/gsd/types.ts +29 -0
  86. package/dist/resources/extensions/gsd/undo.ts +0 -1
  87. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  88. package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
  89. package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  90. package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
  91. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  92. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  93. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  94. package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  95. package/dist/resources/extensions/remote-questions/format.ts +154 -8
  96. package/dist/resources/extensions/remote-questions/manager.ts +9 -7
  97. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  98. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  99. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  100. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  101. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  102. package/dist/resources/extensions/voice/index.ts +4 -3
  103. package/package.json +1 -1
  104. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  106. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  109. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  113. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  117. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  119. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  120. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  122. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  124. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  126. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  128. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  130. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  133. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  136. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  138. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  141. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  143. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  144. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  145. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  146. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  147. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  148. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  149. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  150. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  151. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  152. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  153. package/src/resources/extensions/google-search/index.ts +164 -47
  154. package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
  155. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  156. package/src/resources/extensions/gsd/auto.ts +424 -30
  157. package/src/resources/extensions/gsd/commands.ts +518 -36
  158. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  159. package/src/resources/extensions/gsd/context-store.ts +195 -0
  160. package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  161. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  162. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  163. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  164. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  165. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  166. package/src/resources/extensions/gsd/doctor.ts +283 -2
  167. package/src/resources/extensions/gsd/export.ts +81 -2
  168. package/src/resources/extensions/gsd/files.ts +39 -9
  169. package/src/resources/extensions/gsd/git-service.ts +6 -0
  170. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  171. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  172. package/src/resources/extensions/gsd/history.ts +0 -1
  173. package/src/resources/extensions/gsd/index.ts +277 -1
  174. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  175. package/src/resources/extensions/gsd/metrics.ts +39 -3
  176. package/src/resources/extensions/gsd/notifications.ts +0 -1
  177. package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  178. package/src/resources/extensions/gsd/preferences.ts +125 -150
  179. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  180. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  181. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  182. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  183. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  184. package/src/resources/extensions/gsd/quick.ts +156 -0
  185. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  186. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  187. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  188. package/src/resources/extensions/gsd/state.ts +30 -0
  189. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  190. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  191. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  193. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  194. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  195. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  196. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  197. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  198. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  199. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  200. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  201. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  202. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  203. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  204. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  205. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  206. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  207. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  208. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  209. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  211. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  212. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  213. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  214. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  216. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  217. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  218. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  219. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  220. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  221. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  222. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  223. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  224. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  225. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  226. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  227. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  228. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  229. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  230. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  231. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  232. package/src/resources/extensions/gsd/types.ts +29 -0
  233. package/src/resources/extensions/gsd/undo.ts +0 -1
  234. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  235. package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
  236. package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  237. package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
  238. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  239. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  240. package/src/resources/extensions/remote-questions/config.ts +4 -2
  241. package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  242. package/src/resources/extensions/remote-questions/format.ts +154 -8
  243. package/src/resources/extensions/remote-questions/manager.ts +9 -7
  244. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  245. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  246. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  247. package/src/resources/extensions/remote-questions/types.ts +2 -1
  248. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  249. package/src/resources/extensions/voice/index.ts +4 -3
@@ -1356,6 +1356,7 @@ export class AgentSession {
1356
1356
  this.agent.reset();
1357
1357
  // Update cwd to current process directory — auto-mode may have chdir'd
1358
1358
  // into a worktree since the original session was created.
1359
+ const previousCwd = this._cwd;
1359
1360
  this._cwd = process.cwd();
1360
1361
  this.sessionManager.newSession({ parentSession: options?.parentSession });
1361
1362
  this.agent.sessionId = this.sessionManager.getSessionId();
@@ -1365,6 +1366,17 @@ export class AgentSession {
1365
1366
 
1366
1367
  this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
1367
1368
 
1369
+ // Rebuild tools when cwd changed (e.g., auto-mode entered a worktree).
1370
+ // Tools capture cwd at creation time for path resolution — without
1371
+ // rebuilding, write/read/edit/bash resolve relative paths against
1372
+ // the original project root instead of the worktree (#633).
1373
+ if (this._cwd !== previousCwd) {
1374
+ this._buildRuntime({
1375
+ activeToolNames: this.getActiveToolNames(),
1376
+ includeAllExtensionTools: true,
1377
+ });
1378
+ }
1379
+
1368
1380
  // Run setup callback if provided (e.g., to append initial messages)
1369
1381
  if (options?.setup) {
1370
1382
  await options.setup(this.sessionManager);
@@ -2331,7 +2343,7 @@ export class AgentSession {
2331
2343
 
2332
2344
  const defaultActiveToolNames = this._baseToolsOverride
2333
2345
  ? Object.keys(this._baseToolsOverride)
2334
- : ["read", "bash", "edit", "write"];
2346
+ : ["read", "bash", "edit", "write", "lsp"];
2335
2347
  const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
2336
2348
  this._refreshToolRegistry({
2337
2349
  activeToolNames: baseActiveToolNames,
@@ -19,6 +19,7 @@ import * as _bundledPiTui from "@gsd/pi-tui";
19
19
  // These MUST be static so Bun bundles them into the compiled binary.
20
20
  // The virtualModules option then makes them available to extensions.
21
21
  import * as _bundledTypebox from "@sinclair/typebox";
22
+ import * as _bundledYaml from "yaml";
22
23
  import { getAgentDir, isBunBinary } from "../../config.js";
23
24
  // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
24
25
  // avoiding a circular dependency. Extensions can import from @gsd/pi-coding-agent.
@@ -46,6 +47,7 @@ const VIRTUAL_MODULES: Record<string, unknown> = {
46
47
  "@gsd/pi-ai": _bundledPiAi,
47
48
  "@gsd/pi-ai/oauth": _bundledPiAiOauth,
48
49
  "@gsd/pi-coding-agent": _bundledPiCodingAgent,
50
+ "yaml": _bundledYaml,
49
51
  // Aliases for external PI ecosystem packages that import from the original scope
50
52
  "@mariozechner/pi-agent-core": _bundledPiAgentCore,
51
53
  "@mariozechner/pi-tui": _bundledPiTui,
@@ -70,6 +72,9 @@ function getAliases(): Record<string, string> {
70
72
  const typeboxEntry = require.resolve("@sinclair/typebox");
71
73
  const typeboxRoot = typeboxEntry.replace(/[\\/]build[\\/]cjs[\\/]index\.js$/, "");
72
74
 
75
+ const yamlEntry = require.resolve("yaml");
76
+ const yamlRoot = yamlEntry.replace(/[\\/]dist[\\/]index\.js$/, "");
77
+
73
78
  const packagesRoot = path.resolve(__dirname, "../../../../");
74
79
  const resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {
75
80
  const workspacePath = path.join(packagesRoot, workspaceRelativePath);
@@ -86,6 +91,7 @@ function getAliases(): Record<string, string> {
86
91
  "@gsd/pi-ai": resolveWorkspaceOrImport("ai/dist/index.js", "@gsd/pi-ai"),
87
92
  "@gsd/pi-ai/oauth": resolveWorkspaceOrImport("ai/dist/oauth.js", "@gsd/pi-ai/oauth"),
88
93
  "@sinclair/typebox": typeboxRoot,
94
+ "yaml": yamlRoot,
89
95
  // Aliases for external PI ecosystem packages that import from the original scope
90
96
  "@mariozechner/pi-coding-agent": packageIndex,
91
97
  "@mariozechner/pi-agent-core": resolveWorkspaceOrImport("agent/dist/index.js", "@gsd/pi-agent-core"),
@@ -124,6 +124,18 @@ const CLIENT_CAPABILITIES = {
124
124
  properties: ["edit"],
125
125
  },
126
126
  },
127
+ callHierarchy: {
128
+ dynamicRegistration: false,
129
+ },
130
+ signatureHelp: {
131
+ dynamicRegistration: false,
132
+ signatureInformation: {
133
+ documentationFormat: ["markdown", "plaintext"],
134
+ parameterInformation: {
135
+ labelOffsetSupport: true,
136
+ },
137
+ },
138
+ },
127
139
  formatting: {
128
140
  dynamicRegistration: false,
129
141
  },
@@ -701,6 +713,20 @@ export async function refreshFile(client: LspClient, filePath: string, signal?:
701
713
  }
702
714
  }
703
715
 
716
+ /**
717
+ * Notify all LSP clients that have the file open that it changed on disk.
718
+ * Synchronous entry point — async refresh runs in background.
719
+ * Swallows errors so editing never fails because of LSP.
720
+ */
721
+ export function notifyFileChanged(filePath: string): void {
722
+ const uri = fileToUri(filePath);
723
+ for (const client of clients.values()) {
724
+ if (client.openFiles.has(uri)) {
725
+ refreshFile(client, filePath).catch(() => {});
726
+ }
727
+ }
728
+ }
729
+
704
730
  /**
705
731
  * Shutdown a specific client by key.
706
732
  */
@@ -15,10 +15,13 @@ import {
15
15
  WARMUP_TIMEOUT_MS,
16
16
  } from "./client.js";
17
17
  import { getServersForFile, type LspConfig, loadConfig } from "./config.js";
18
- import { applyWorkspaceEdit } from "./edits.js";
18
+ import { applyTextEdits, applyWorkspaceEdit } from "./edits.js";
19
19
  import { ToolAbortError, clampTimeout, throwIfAborted } from "./helpers.js";
20
20
  import { detectLspmux } from "./lspmux.js";
21
21
  import {
22
+ type CallHierarchyIncomingCall,
23
+ type CallHierarchyItem,
24
+ type CallHierarchyOutgoingCall,
22
25
  type CodeAction,
23
26
  type CodeActionContext,
24
27
  type Command,
@@ -32,7 +35,9 @@ import {
32
35
  type LspToolDetails,
33
36
  lspSchema,
34
37
  type ServerConfig,
38
+ type SignatureHelp,
35
39
  type SymbolInformation,
40
+ type TextEdit,
36
41
  type WorkspaceEdit,
37
42
  } from "./types.js";
38
43
  import {
@@ -42,12 +47,14 @@ import {
42
47
  extractHoverText,
43
48
  fileToUri,
44
49
  filterWorkspaceSymbols,
50
+ formatCallHierarchyItem,
45
51
  formatCodeAction,
46
52
  formatDiagnostic,
47
53
  formatDiagnosticsSummary,
48
54
  formatDocumentSymbol,
49
55
  formatGroupedDiagnosticMessages,
50
56
  formatLocation,
57
+ formatSignatureHelp,
51
58
  formatSymbolInformation,
52
59
  formatWorkspaceEdit,
53
60
  hasGlobPattern,
@@ -338,7 +345,7 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
338
345
  signal?: AbortSignal,
339
346
  _onUpdate?: AgentToolUpdateCallback<LspToolDetails>,
340
347
  ): Promise<AgentToolResult<LspToolDetails>> {
341
- const { action, file, line, symbol, occurrence, query, new_name, apply, timeout } = params;
348
+ const { action, file, line, symbol, occurrence, query, new_name, apply, tab_size, insert_spaces, timeout } = params;
342
349
  const timeoutSec = clampTimeout(timeout);
343
350
  const timeoutSignal = AbortSignal.timeout(timeoutSec * 1000);
344
351
  signal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
@@ -876,6 +883,154 @@ export function createLspTool(cwd: string): AgentTool<typeof lspSchema, LspToolD
876
883
  break;
877
884
  }
878
885
 
886
+ case "incoming_calls": {
887
+ const prepareResult = (await sendRequest(
888
+ client,
889
+ "textDocument/prepareCallHierarchy",
890
+ {
891
+ textDocument: { uri },
892
+ position,
893
+ },
894
+ signal,
895
+ )) as CallHierarchyItem[] | null;
896
+
897
+ if (!prepareResult || prepareResult.length === 0) {
898
+ output = "No call hierarchy item found at this position";
899
+ break;
900
+ }
901
+
902
+ const incomingResult = (await sendRequest(
903
+ client,
904
+ "callHierarchy/incomingCalls",
905
+ { item: prepareResult[0] },
906
+ signal,
907
+ )) as CallHierarchyIncomingCall[] | null;
908
+
909
+ if (!incomingResult || incomingResult.length === 0) {
910
+ output = `No incoming calls found for ${prepareResult[0].name}`;
911
+ break;
912
+ }
913
+
914
+ const incomingLines: string[] = [];
915
+ const limitedIncoming = incomingResult.slice(0, REFERENCE_CONTEXT_LIMIT);
916
+ for (const call of limitedIncoming) {
917
+ const header = formatCallHierarchyItem(call.from, cwd);
918
+ const filePath = uriToFile(call.from.uri);
919
+ const callLine = call.fromRanges[0]?.start.line ?? call.from.selectionRange.start.line;
920
+ const context = await readLocationContext(filePath, callLine + 1, LOCATION_CONTEXT_LINES);
921
+ if (context.length > 0) {
922
+ incomingLines.push(` ${header}\n${context.map(l => ` ${l}`).join("\n")}`);
923
+ } else {
924
+ incomingLines.push(` ${header}`);
925
+ }
926
+ }
927
+
928
+ const truncation = incomingResult.length > REFERENCE_CONTEXT_LIMIT
929
+ ? `\n ... ${incomingResult.length - REFERENCE_CONTEXT_LIMIT} additional caller(s) omitted`
930
+ : "";
931
+ output = `${incomingResult.length} caller(s) of ${prepareResult[0].name}:\n${incomingLines.join("\n")}${truncation}`;
932
+ break;
933
+ }
934
+
935
+ case "outgoing_calls": {
936
+ const prepareResult = (await sendRequest(
937
+ client,
938
+ "textDocument/prepareCallHierarchy",
939
+ {
940
+ textDocument: { uri },
941
+ position,
942
+ },
943
+ signal,
944
+ )) as CallHierarchyItem[] | null;
945
+
946
+ if (!prepareResult || prepareResult.length === 0) {
947
+ output = "No call hierarchy item found at this position";
948
+ break;
949
+ }
950
+
951
+ const outgoingResult = (await sendRequest(
952
+ client,
953
+ "callHierarchy/outgoingCalls",
954
+ { item: prepareResult[0] },
955
+ signal,
956
+ )) as CallHierarchyOutgoingCall[] | null;
957
+
958
+ if (!outgoingResult || outgoingResult.length === 0) {
959
+ output = `No outgoing calls found from ${prepareResult[0].name}`;
960
+ break;
961
+ }
962
+
963
+ const outgoingLines: string[] = [];
964
+ const limitedOutgoing = outgoingResult.slice(0, REFERENCE_CONTEXT_LIMIT);
965
+ for (const call of limitedOutgoing) {
966
+ const header = formatCallHierarchyItem(call.to, cwd);
967
+ const filePath = uriToFile(call.to.uri);
968
+ const callLine = call.to.selectionRange.start.line;
969
+ const context = await readLocationContext(filePath, callLine + 1, LOCATION_CONTEXT_LINES);
970
+ if (context.length > 0) {
971
+ outgoingLines.push(` ${header}\n${context.map(l => ` ${l}`).join("\n")}`);
972
+ } else {
973
+ outgoingLines.push(` ${header}`);
974
+ }
975
+ }
976
+
977
+ const outTruncation = outgoingResult.length > REFERENCE_CONTEXT_LIMIT
978
+ ? `\n ... ${outgoingResult.length - REFERENCE_CONTEXT_LIMIT} additional callee(s) omitted`
979
+ : "";
980
+ output = `${outgoingResult.length} callee(s) from ${prepareResult[0].name}:\n${outgoingLines.join("\n")}${outTruncation}`;
981
+ break;
982
+ }
983
+
984
+ case "format": {
985
+ if (!targetFile) {
986
+ output = "Error: file parameter required for format";
987
+ break;
988
+ }
989
+
990
+ const formatResult = (await sendRequest(
991
+ client,
992
+ "textDocument/formatting",
993
+ {
994
+ textDocument: { uri },
995
+ options: {
996
+ tabSize: tab_size ?? 4,
997
+ insertSpaces: insert_spaces ?? true,
998
+ },
999
+ },
1000
+ signal,
1001
+ )) as TextEdit[] | null;
1002
+
1003
+ if (!formatResult || formatResult.length === 0) {
1004
+ const relPath = path.relative(cwd, targetFile);
1005
+ output = `${relPath}: already formatted (no changes)`;
1006
+ break;
1007
+ }
1008
+
1009
+ await applyTextEdits(targetFile, formatResult);
1010
+ const relPath = path.relative(cwd, targetFile);
1011
+ output = `Formatted ${relPath}: ${formatResult.length} edit(s) applied`;
1012
+ break;
1013
+ }
1014
+
1015
+ case "signature": {
1016
+ const sigResult = (await sendRequest(
1017
+ client,
1018
+ "textDocument/signatureHelp",
1019
+ {
1020
+ textDocument: { uri },
1021
+ position,
1022
+ },
1023
+ signal,
1024
+ )) as SignatureHelp | null;
1025
+
1026
+ if (!sigResult || !sigResult.signatures || sigResult.signatures.length === 0) {
1027
+ output = "No signature information at this position";
1028
+ } else {
1029
+ output = formatSignatureHelp(sigResult);
1030
+ }
1031
+ break;
1032
+ }
1033
+
879
1034
  case "rename": {
880
1035
  if (!new_name) {
881
1036
  return {
@@ -8,8 +8,12 @@ Interacts with Language Server Protocol servers for code intelligence.
8
8
  - `references`: Find references → locations with 3-line source context (first 50), remaining location-only
9
9
  - `hover`: Get type info and documentation → type signature + docs
10
10
  - `symbols`: List symbols in file, or search workspace (with query, no file)
11
+ - `incoming_calls`: Find all callers of a function → call sites with context
12
+ - `outgoing_calls`: Find all functions called by a function → callees with context
11
13
  - `rename`: Rename symbol across codebase → preview or apply edits
12
14
  - `code_actions`: List available quick-fixes/refactors/import actions; apply one when `apply: true` and `query` matches title or index
15
+ - `format`: Format file using language server formatter → applies edits in-place
16
+ - `signature`: Get function signature and parameter info at cursor position
13
17
  - `status`: Show active language servers
14
18
  - `reload`: Restart the language server
15
19
  </operations>
@@ -22,6 +26,8 @@ Interacts with Language Server Protocol servers for code intelligence.
22
26
  - `query`: Symbol search query, code-action kind filter (list mode), or code-action selector (apply mode)
23
27
  - `new_name`: Required for rename
24
28
  - `apply`: Apply edits for rename/code_actions (default true for rename, list mode for code_actions unless explicitly true)
29
+ - `tab_size`: Tab size for formatting (default: 4)
30
+ - `insert_spaces`: Use spaces for formatting (default: true)
25
31
  - `timeout`: Request timeout in seconds (clamped to 5-60, default 20)
26
32
  </parameters>
27
33
 
@@ -29,6 +29,10 @@ export const lspSchema = Type.Object({
29
29
  "code_actions",
30
30
  "type_definition",
31
31
  "implementation",
32
+ "incoming_calls",
33
+ "outgoing_calls",
34
+ "format",
35
+ "signature",
32
36
  "status",
33
37
  "reload",
34
38
  ],
@@ -43,6 +47,8 @@ export const lspSchema = Type.Object({
43
47
  query: Type.Optional(Type.String({ description: "Search query or SSR pattern" })),
44
48
  new_name: Type.Optional(Type.String({ description: "New name for rename" })),
45
49
  apply: Type.Optional(Type.Boolean({ description: "Apply edits (default: true)" })),
50
+ tab_size: Type.Optional(Type.Number({ description: "Tab size for formatting (default: 4)" })),
51
+ insert_spaces: Type.Optional(Type.Boolean({ description: "Use spaces for formatting (default: true)" })),
46
52
  timeout: Type.Optional(Type.Number({ description: "Request timeout in seconds" })),
47
53
  });
48
54
 
@@ -419,3 +425,50 @@ export interface LspJsonRpcNotification {
419
425
  method: string;
420
426
  params?: unknown;
421
427
  }
428
+
429
+ // =============================================================================
430
+ // Call Hierarchy
431
+ // =============================================================================
432
+
433
+ export interface CallHierarchyItem {
434
+ name: string;
435
+ kind: SymbolKind;
436
+ tags?: number[];
437
+ detail?: string;
438
+ uri: string;
439
+ range: Range;
440
+ selectionRange: Range;
441
+ data?: unknown;
442
+ }
443
+
444
+ export interface CallHierarchyIncomingCall {
445
+ from: CallHierarchyItem;
446
+ fromRanges: Range[];
447
+ }
448
+
449
+ export interface CallHierarchyOutgoingCall {
450
+ to: CallHierarchyItem;
451
+ fromRanges: Range[];
452
+ }
453
+
454
+ // =============================================================================
455
+ // Signature Help
456
+ // =============================================================================
457
+
458
+ export interface ParameterInformation {
459
+ label: string | [number, number];
460
+ documentation?: string | MarkupContent;
461
+ }
462
+
463
+ export interface SignatureInformation {
464
+ label: string;
465
+ documentation?: string | MarkupContent;
466
+ parameters?: ParameterInformation[];
467
+ activeParameter?: number;
468
+ }
469
+
470
+ export interface SignatureHelp {
471
+ signatures: SignatureInformation[];
472
+ activeSignature?: number;
473
+ activeParameter?: number;
474
+ }
@@ -3,12 +3,15 @@ import path from "node:path";
3
3
  import { glob } from "glob";
4
4
  import { isEnoent } from "./helpers.js";
5
5
  import type {
6
+ CallHierarchyItem,
6
7
  CodeAction,
7
8
  Command,
8
9
  Diagnostic,
9
10
  DiagnosticSeverity,
10
11
  DocumentSymbol,
11
12
  Location,
13
+ MarkupContent,
14
+ SignatureHelp,
12
15
  SymbolInformation,
13
16
  SymbolKind,
14
17
  TextEdit,
@@ -680,3 +683,56 @@ export async function readLocationContext(filePath: string, line: number, contex
680
683
  throw error;
681
684
  }
682
685
  }
686
+
687
+ // =============================================================================
688
+ // Call Hierarchy Formatting
689
+ // =============================================================================
690
+
691
+ export function formatCallHierarchyItem(item: CallHierarchyItem, cwd: string): string {
692
+ const icon = symbolKindToIcon(item.kind);
693
+ const detail = item.detail ? ` ${item.detail}` : "";
694
+ const relPath = path.relative(cwd, uriToFile(item.uri));
695
+ const line = item.selectionRange.start.line + 1;
696
+ return `${icon} ${item.name}${detail} @ ${relPath}:${line}`;
697
+ }
698
+
699
+ // =============================================================================
700
+ // Signature Help Formatting
701
+ // =============================================================================
702
+
703
+ function extractDocText(doc: string | MarkupContent | undefined): string {
704
+ if (!doc) return "";
705
+ if (typeof doc === "string") return doc;
706
+ return doc.value;
707
+ }
708
+
709
+ export function formatSignatureHelp(result: SignatureHelp): string {
710
+ if (!result.signatures || result.signatures.length === 0) {
711
+ return "No signature information";
712
+ }
713
+
714
+ const activeIdx = result.activeSignature ?? 0;
715
+ const sig = result.signatures[activeIdx] ?? result.signatures[0];
716
+ const activeParam = result.activeParameter ?? sig.activeParameter;
717
+
718
+ const lines: string[] = [sig.label];
719
+
720
+ const sigDoc = extractDocText(sig.documentation);
721
+ if (sigDoc) {
722
+ lines.push("", sigDoc);
723
+ }
724
+
725
+ if (sig.parameters && sig.parameters.length > 0) {
726
+ lines.push("", "Parameters:");
727
+ for (let i = 0; i < sig.parameters.length; i++) {
728
+ const p = sig.parameters[i];
729
+ const label = typeof p.label === "string" ? p.label : sig.label.slice(p.label[0], p.label[1]);
730
+ const active = i === activeParam ? " <-- active" : "";
731
+ const doc = extractDocText(p.documentation);
732
+ const docSuffix = doc ? ` — ${doc}` : "";
733
+ lines.push(` ${label}${docSuffix}${active}`);
734
+ }
735
+ }
736
+
737
+ return lines.join("\n");
738
+ }
@@ -473,6 +473,16 @@ export class SettingsManager {
473
473
  this.errors.push({ scope, error: normalizedError });
474
474
  }
475
475
 
476
+ /**
477
+ * Check if project-level settings are active (loaded from a file).
478
+ * Used to scope model persistence to the project when possible,
479
+ * preventing model config bleed between concurrent instances (#650).
480
+ */
481
+ private hasProjectSettings(): boolean {
482
+ // Project settings are active if we loaded them and they weren't empty/errored
483
+ return !this.projectSettingsLoadError && Object.keys(this.projectSettings).length > 0;
484
+ }
485
+
476
486
  private clearModifiedScope(scope: SettingsScope): void {
477
487
  if (scope === "global") {
478
488
  this.modifiedFields.clear();
@@ -595,23 +605,43 @@ export class SettingsManager {
595
605
  }
596
606
 
597
607
  setDefaultProvider(provider: string): void {
598
- this.globalSettings.defaultProvider = provider;
599
- this.markModified("defaultProvider");
600
- this.save();
608
+ if (this.hasProjectSettings()) {
609
+ this.projectSettings.defaultProvider = provider;
610
+ this.markProjectModified("defaultProvider");
611
+ this.saveProjectSettings(this.projectSettings);
612
+ } else {
613
+ this.globalSettings.defaultProvider = provider;
614
+ this.markModified("defaultProvider");
615
+ this.save();
616
+ }
601
617
  }
602
618
 
603
619
  setDefaultModel(modelId: string): void {
604
- this.globalSettings.defaultModel = modelId;
605
- this.markModified("defaultModel");
606
- this.save();
620
+ if (this.hasProjectSettings()) {
621
+ this.projectSettings.defaultModel = modelId;
622
+ this.markProjectModified("defaultModel");
623
+ this.saveProjectSettings(this.projectSettings);
624
+ } else {
625
+ this.globalSettings.defaultModel = modelId;
626
+ this.markModified("defaultModel");
627
+ this.save();
628
+ }
607
629
  }
608
630
 
609
631
  setDefaultModelAndProvider(provider: string, modelId: string): void {
610
- this.globalSettings.defaultProvider = provider;
611
- this.globalSettings.defaultModel = modelId;
612
- this.markModified("defaultProvider");
613
- this.markModified("defaultModel");
614
- this.save();
632
+ if (this.hasProjectSettings()) {
633
+ this.projectSettings.defaultProvider = provider;
634
+ this.projectSettings.defaultModel = modelId;
635
+ this.markProjectModified("defaultProvider");
636
+ this.markProjectModified("defaultModel");
637
+ this.saveProjectSettings(this.projectSettings);
638
+ } else {
639
+ this.globalSettings.defaultProvider = provider;
640
+ this.globalSettings.defaultModel = modelId;
641
+ this.markModified("defaultProvider");
642
+ this.markModified("defaultModel");
643
+ this.save();
644
+ }
615
645
  }
616
646
 
617
647
  getSteeringMode(): "all" | "one-at-a-time" {
@@ -159,7 +159,13 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
159
159
  // LSP guideline
160
160
  if (hasLsp) {
161
161
  addGuideline(
162
- "Use lsp for go-to-definition, find-references, hover, rename, and diagnostics when working in typed codebases. Prefer lsp over grep for semantic navigation (finding call sites, implementations, type info). Falls back gracefully if no language server is available for the file type.",
162
+ `Use lsp as the primary tool for code navigation in typed codebases:
163
+ - Navigation: definition, type_definition, implementation, references, incoming_calls, outgoing_calls
164
+ - Understanding: hover (types + docs), signature (parameter info), symbols (file/workspace search)
165
+ - Refactoring: rename (project-wide), code_actions (quick-fixes, imports, refactors), format (formatter)
166
+ - Verification: diagnostics after edits to catch type errors immediately
167
+ - Never grep for a symbol definition when lsp can resolve it semantically
168
+ - Never shell out to a formatter when lsp format is available`,
163
169
  );
164
170
  }
165
171
 
@@ -11,6 +11,7 @@ import {
11
11
  restoreLineEndings,
12
12
  stripBom,
13
13
  } from "./edit-diff.js";
14
+ import { notifyFileChanged } from "../lsp/client.js";
14
15
  import { resolveToCwd } from "./path-utils.js";
15
16
 
16
17
  const editSchema = Type.Object({
@@ -187,6 +188,8 @@ export function createEditTool(cwd: string, options?: EditToolOptions): AgentToo
187
188
  const finalContent = bom + restoreLineEndings(newContent, originalEnding);
188
189
  await ops.writeFile(absolutePath, finalContent);
189
190
 
191
+ try { notifyFileChanged(absolutePath); } catch { /* best-effort */ }
192
+
190
193
  // Check if aborted after writing
191
194
  if (aborted) {
192
195
  return;
@@ -2,6 +2,7 @@ import type { AgentTool } from "@gsd/pi-agent-core";
2
2
  import { type Static, Type } from "@sinclair/typebox";
3
3
  import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
4
4
  import { dirname } from "path";
5
+ import { notifyFileChanged } from "../lsp/client.js";
5
6
  import { resolveToCwd } from "./path-utils.js";
6
7
 
7
8
  const writeSchema = Type.Object({
@@ -83,6 +84,8 @@ export function createWriteTool(cwd: string, options?: WriteToolOptions): AgentT
83
84
  // Write the file
84
85
  await ops.writeFile(absolutePath, content);
85
86
 
87
+ try { notifyFileChanged(absolutePath); } catch { /* best-effort */ }
88
+
86
89
  // Check if aborted after writing
87
90
  if (aborted) {
88
91
  return;