gsd-pi 2.34.0-dev.7d38042 → 2.34.0-dev.e6d9bed

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 (212) hide show
  1. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +5 -1
  2. package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  3. package/dist/resources/extensions/gsd/doctor-checks.js +113 -5
  4. package/dist/resources/extensions/gsd/doctor-proactive.js +22 -0
  5. package/dist/resources/extensions/gsd/doctor.js +36 -0
  6. package/dist/resources/extensions/gsd/guided-flow.js +4 -2
  7. package/dist/resources/extensions/gsd/preferences-validation.js +38 -0
  8. package/dist/resources/extensions/gsd/preferences.js +2 -0
  9. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  10. package/package.json +1 -1
  11. package/packages/pi-agent-core/dist/agent-loop.d.ts +14 -0
  12. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  13. package/packages/pi-agent-core/dist/agent-loop.js +24 -27
  14. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  15. package/packages/pi-agent-core/dist/agent.d.ts +1 -0
  16. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  17. package/packages/pi-agent-core/dist/agent.js +11 -22
  18. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  19. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  20. package/packages/pi-agent-core/dist/proxy.js +2 -8
  21. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  22. package/packages/pi-agent-core/src/agent-loop.ts +30 -27
  23. package/packages/pi-agent-core/src/agent.ts +12 -23
  24. package/packages/pi-agent-core/src/proxy.ts +2 -8
  25. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  26. package/packages/pi-ai/dist/providers/azure-openai-responses.js +5 -41
  27. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  28. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  29. package/packages/pi-ai/dist/providers/openai-completions.js +10 -73
  30. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  31. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  32. package/packages/pi-ai/dist/providers/openai-responses.js +8 -79
  33. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  34. package/packages/pi-ai/dist/providers/openai-shared.d.ts +65 -0
  35. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -0
  36. package/packages/pi-ai/dist/providers/openai-shared.js +146 -0
  37. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -0
  38. package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  39. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +7 -135
  40. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
  41. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  42. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +7 -135
  43. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  44. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts +46 -0
  45. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts.map +1 -0
  46. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js +160 -0
  47. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js.map +1 -0
  48. package/packages/pi-ai/src/providers/azure-openai-responses.ts +11 -45
  49. package/packages/pi-ai/src/providers/openai-completions.ts +16 -86
  50. package/packages/pi-ai/src/providers/openai-responses.ts +15 -95
  51. package/packages/pi-ai/src/providers/openai-shared.ts +193 -0
  52. package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +14 -162
  53. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +13 -161
  54. package/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts +201 -0
  55. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +16 -63
  56. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  57. package/packages/pi-coding-agent/dist/core/agent-session.js +104 -641
  58. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +0 -1
  60. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/auth-storage.js +4 -35
  62. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +5 -43
  65. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +11 -69
  68. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +40 -0
  70. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/compaction/utils.js +78 -0
  72. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +77 -0
  74. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -0
  75. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +331 -0
  76. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -0
  77. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +2 -2
  78. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
  80. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  81. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +15 -0
  82. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/extensions/runner.js +129 -243
  84. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +49 -42
  86. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/extensions/types.js +2 -21
  88. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts +39 -0
  90. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts.map +1 -0
  91. package/packages/pi-coding-agent/dist/core/lock-utils.js +89 -0
  92. package/packages/pi-coding-agent/dist/core/lock-utils.js.map +1 -0
  93. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +2 -0
  94. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/lsp/config.js +4 -1
  96. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/lsp/index.js +52 -107
  99. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +2 -21
  102. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +0 -1
  104. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/lsp/types.js +0 -28
  106. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/package-manager.js +2 -4
  109. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +2 -4
  111. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/resource-loader.js +33 -58
  113. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +87 -0
  115. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/core/retry-handler.js +295 -0
  117. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -1
  119. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -28
  121. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/skills.js +1 -3
  124. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  126. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/index.js +1 -1
  128. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +9 -26
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -13
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts +44 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts.map +1 -0
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js +61 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js.map +1 -0
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js +6 -9
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts +6 -0
  144. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts.map +1 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js +15 -0
  146. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js.map +1 -0
  147. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/print-mode.js +2 -30
  149. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +2 -28
  152. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts +19 -0
  154. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts.map +1 -0
  155. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js +45 -0
  156. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js.map +1 -0
  157. package/packages/pi-coding-agent/dist/utils/error.d.ts +5 -0
  158. package/packages/pi-coding-agent/dist/utils/error.d.ts.map +1 -0
  159. package/packages/pi-coding-agent/dist/utils/error.js +7 -0
  160. package/packages/pi-coding-agent/dist/utils/error.js.map +1 -0
  161. package/packages/pi-coding-agent/src/core/agent-session.ts +117 -745
  162. package/packages/pi-coding-agent/src/core/auth-storage.ts +4 -38
  163. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +7 -53
  164. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +14 -74
  165. package/packages/pi-coding-agent/src/core/compaction/utils.ts +100 -0
  166. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +424 -0
  167. package/packages/pi-coding-agent/src/core/extensions/index.ts +1 -21
  168. package/packages/pi-coding-agent/src/core/extensions/runner.ts +119 -243
  169. package/packages/pi-coding-agent/src/core/extensions/types.ts +50 -69
  170. package/packages/pi-coding-agent/src/core/lock-utils.ts +113 -0
  171. package/packages/pi-coding-agent/src/core/lsp/config.ts +4 -1
  172. package/packages/pi-coding-agent/src/core/lsp/index.ts +83 -152
  173. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +2 -22
  174. package/packages/pi-coding-agent/src/core/lsp/types.ts +0 -29
  175. package/packages/pi-coding-agent/src/core/package-manager.ts +1 -4
  176. package/packages/pi-coding-agent/src/core/resource-loader.ts +43 -67
  177. package/packages/pi-coding-agent/src/core/retry-handler.ts +359 -0
  178. package/packages/pi-coding-agent/src/core/session-manager.ts +3 -30
  179. package/packages/pi-coding-agent/src/core/skills.ts +1 -4
  180. package/packages/pi-coding-agent/src/index.ts +1 -7
  181. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +17 -29
  182. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -13
  183. package/packages/pi-coding-agent/src/modes/interactive/components/tree-render-utils.ts +81 -0
  184. package/packages/pi-coding-agent/src/modes/interactive/components/tree-selector.ts +14 -19
  185. package/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts +14 -0
  186. package/packages/pi-coding-agent/src/modes/print-mode.ts +2 -30
  187. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -28
  188. package/packages/pi-coding-agent/src/modes/shared/command-context-actions.ts +53 -0
  189. package/packages/pi-coding-agent/src/utils/error.ts +6 -0
  190. package/packages/pi-tui/dist/components/markdown.d.ts +5 -0
  191. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  192. package/packages/pi-tui/dist/components/markdown.js +25 -31
  193. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  194. package/packages/pi-tui/dist/keys.d.ts +0 -4
  195. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  196. package/packages/pi-tui/dist/keys.js +94 -162
  197. package/packages/pi-tui/dist/keys.js.map +1 -1
  198. package/packages/pi-tui/src/components/markdown.ts +25 -29
  199. package/packages/pi-tui/src/keys.ts +94 -173
  200. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +5 -1
  201. package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  202. package/src/resources/extensions/gsd/doctor-checks.ts +107 -5
  203. package/src/resources/extensions/gsd/doctor-proactive.ts +24 -0
  204. package/src/resources/extensions/gsd/doctor-types.ts +9 -1
  205. package/src/resources/extensions/gsd/doctor.ts +35 -0
  206. package/src/resources/extensions/gsd/guided-flow.ts +4 -2
  207. package/src/resources/extensions/gsd/preferences-validation.ts +38 -0
  208. package/src/resources/extensions/gsd/preferences.ts +2 -0
  209. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +98 -2
  210. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +59 -3
  211. package/src/resources/extensions/gsd/tests/preferences.test.ts +28 -0
  212. package/src/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
@@ -624,10 +624,14 @@ export function serializePreferencesToFrontmatter(prefs) {
624
624
  const orderedKeys = [
625
625
  "version", "mode", "always_use_skills", "prefer_skills", "avoid_skills",
626
626
  "skill_rules", "custom_instructions", "models", "skill_discovery",
627
- "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
627
+ "skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
628
628
  "budget_ceiling", "budget_enforcement", "context_pause_threshold",
629
629
  "notifications", "remote_questions", "git",
630
630
  "post_unit_hooks", "pre_dispatch_hooks",
631
+ "dynamic_routing", "token_profile", "phases", "parallel",
632
+ "auto_visualize", "auto_report",
633
+ "verification_commands", "verification_auto_fix", "verification_max_retries",
634
+ "search_provider", "compression_strategy", "context_selection",
631
635
  ];
632
636
  const seen = new Set();
633
637
  for (const key of orderedKeys) {
@@ -134,6 +134,10 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
134
134
  - `isolation`: `"worktree"`, `"branch"`, or `"none"` — controls auto-mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root but creates a milestone branch (useful for submodule-heavy repos); `"none"` works directly on the current branch with no worktree or milestone branch (ideal for step-mode with hot reloads). Default: `"worktree"`.
135
135
  - `manage_gitignore`: boolean — when `false`, GSD will not touch `.gitignore` at all. Useful when your project has a strictly managed `.gitignore` and you don't want GSD adding entries. Default: `true`.
136
136
  - `worktree_post_create`: string — script to run after a worktree is created (both auto-mode and manual `/worktree`). Receives `SOURCE_DIR` and `WORKTREE_DIR` as environment variables. Can be absolute or relative to project root. Runs with 30-second timeout. Failure is non-fatal (logged as warning). Default: none.
137
+ - `auto_pr`: boolean — automatically create a GitHub pull request after a milestone branch is merged. Requires `gh` CLI to be installed. Default: `false`.
138
+ - `pr_target_branch`: string — branch to target when `auto_pr` is enabled. Defaults to `main_branch` when omitted.
139
+ - **Deprecated:** `commit_docs` — no longer valid; `.gsd/` is always gitignored. Remove this setting.
140
+ - **Deprecated:** `merge_to_main` — no longer valid; milestone-level merge is always used. Remove this setting.
137
141
 
138
142
  - `unique_milestone_ids`: boolean — when `true`, generates milestone IDs in `M{seq}-{rand6}` format (e.g. `M001-eh88as`) instead of plain sequential `M001`. Prevents ID collisions in team workflows where multiple contributors create milestones concurrently. Both formats coexist — existing `M001`-style milestones remain valid. Default: `false`.
139
143
 
@@ -181,6 +185,12 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
181
185
 
182
186
  - `auto_report`: boolean — generate an HTML report snapshot after each milestone completion. Default: `true`.
183
187
 
188
+ - `search_provider`: `"brave"`, `"tavily"`, `"ollama"`, `"native"`, or `"auto"` — selects the search backend for research phases. `"native"` forces Anthropic's built-in web search only; provider values force that backend and disable native search; `"auto"` uses the default heuristic. Default: `"auto"`.
189
+
190
+ - `compression_strategy`: `"truncate"` or `"compress"` — controls how context that exceeds the budget is reduced. `"truncate"` (default) drops sections from the end. `"compress"` applies heuristic compression before truncating, preserving more content at the cost of some fidelity. Default: `"truncate"`.
191
+
192
+ - `context_selection`: `"full"` or `"smart"` — controls how files are inlined into context. `"full"` inlines entire files; `"smart"` uses semantic chunking to include only the most relevant sections. Default is derived from `token_profile`.
193
+
184
194
  - `parallel`: configures parallel orchestration for running multiple slices concurrently. Keys:
185
195
  - `enabled`: boolean — enable parallel execution. Default: `false`.
186
196
  - `max_workers`: number — maximum concurrent workers (1-4). Default: `2`.
@@ -1,13 +1,13 @@
1
- import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
- import { join, sep } from "node:path";
1
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, sep } from "node:path";
3
3
  import { loadFile, parseRoadmap } from "./files.js";
4
4
  import { resolveMilestoneFile, milestonesDir, gsdRoot, resolveGsdRootFile } from "./paths.js";
5
5
  import { deriveState, isMilestoneComplete } from "./state.js";
6
6
  import { saveFile } from "./files.js";
7
- import { listWorktrees, resolveGitDir } from "./worktree-manager.js";
7
+ import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
8
8
  import { abortAndReset } from "./git-self-heal.js";
9
- import { RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
10
- import { nativeIsRepo, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached } from "./native-git-bridge.js";
9
+ import { RUNTIME_EXCLUSION_PATHS, readIntegrationBranch } from "./git-service.js";
10
+ import { nativeIsRepo, nativeBranchExists, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached } from "./native-git-bridge.js";
11
11
  import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
12
12
  import { ensureGitignore } from "./gitignore.js";
13
13
  import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
@@ -200,6 +200,75 @@ export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix,
200
200
  catch {
201
201
  // git branch list failed — skip
202
202
  }
203
+ // ── Integration branch existence ──────────────────────────────────────
204
+ // For each active (non-complete) milestone, verify the stored integration
205
+ // branch still exists in git. A missing integration branch blocks merge-back
206
+ // and causes the next merge operation to fail silently.
207
+ try {
208
+ const state = await deriveState(basePath);
209
+ for (const milestone of state.registry) {
210
+ if (milestone.status === "complete")
211
+ continue;
212
+ const integrationBranch = readIntegrationBranch(basePath, milestone.id);
213
+ if (!integrationBranch)
214
+ continue; // No stored branch — skip (not yet set)
215
+ if (!nativeBranchExists(basePath, integrationBranch)) {
216
+ issues.push({
217
+ severity: "error",
218
+ code: "integration_branch_missing",
219
+ scope: "milestone",
220
+ unitId: milestone.id,
221
+ message: `Milestone ${milestone.id} recorded integration branch "${integrationBranch}" but that branch no longer exists in git. Merge-back will fail.`,
222
+ fixable: false,
223
+ });
224
+ }
225
+ }
226
+ }
227
+ catch {
228
+ // Non-fatal — integration branch check failed
229
+ }
230
+ // ── Orphaned worktree directories ────────────────────────────────────
231
+ // Worktree removal can fail after a branch delete, leaving a directory
232
+ // that is no longer registered with git. These orphaned dirs cause
233
+ // "already exists" errors when re-creating the same worktree name.
234
+ try {
235
+ const wtDir = worktreesDir(basePath);
236
+ if (existsSync(wtDir)) {
237
+ const registeredPaths = new Set(nativeWorktreeList(basePath).map(entry => entry.path));
238
+ for (const entry of readdirSync(wtDir)) {
239
+ const fullPath = join(wtDir, entry);
240
+ try {
241
+ if (!statSync(fullPath).isDirectory())
242
+ continue;
243
+ }
244
+ catch {
245
+ continue;
246
+ }
247
+ if (!registeredPaths.has(fullPath)) {
248
+ issues.push({
249
+ severity: "warning",
250
+ code: "worktree_directory_orphaned",
251
+ scope: "project",
252
+ unitId: entry,
253
+ message: `Worktree directory ${fullPath} exists on disk but is not registered with git. Run "git worktree prune" or doctor --fix to remove it.`,
254
+ fixable: true,
255
+ });
256
+ if (shouldFix("worktree_directory_orphaned")) {
257
+ try {
258
+ rmSync(fullPath, { recursive: true, force: true });
259
+ fixesApplied.push(`removed orphaned worktree directory ${fullPath}`);
260
+ }
261
+ catch {
262
+ fixesApplied.push(`failed to remove orphaned worktree directory ${fullPath}`);
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ catch {
270
+ // Non-fatal — orphaned worktree directory check failed
271
+ }
203
272
  }
204
273
  // ── Runtime Health Checks ──────────────────────────────────────────────────
205
274
  // Checks for stale crash locks, orphaned completed-units, stale hook state,
@@ -231,6 +300,45 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
231
300
  catch {
232
301
  // Non-fatal — crash lock check failed
233
302
  }
303
+ // ── Stranded lock directory ────────────────────────────────────────────
304
+ // proper-lockfile creates a `.gsd.lock/` directory as the OS-level lock
305
+ // mechanism. If the process was SIGKILLed or crashed hard, this directory
306
+ // can remain on disk without any live process holding it. The next session
307
+ // fails to acquire the lock until the directory is removed (#1245).
308
+ try {
309
+ const lockDir = join(dirname(root), `${basename(root)}.lock`);
310
+ if (existsSync(lockDir)) {
311
+ const statRes = statSync(lockDir);
312
+ if (statRes.isDirectory()) {
313
+ // Check if any live process actually holds this lock
314
+ const lock = readCrashLock(basePath);
315
+ const lockHolderAlive = lock ? isLockProcessAlive(lock) : false;
316
+ if (!lockHolderAlive) {
317
+ issues.push({
318
+ severity: "error",
319
+ code: "stranded_lock_directory",
320
+ scope: "project",
321
+ unitId: "project",
322
+ message: `Stranded lock directory "${lockDir}" exists but no live process holds the session lock. This blocks new auto-mode sessions from starting.`,
323
+ file: lockDir,
324
+ fixable: true,
325
+ });
326
+ if (shouldFix("stranded_lock_directory")) {
327
+ try {
328
+ rmSync(lockDir, { recursive: true, force: true });
329
+ fixesApplied.push(`removed stranded lock directory ${lockDir}`);
330
+ }
331
+ catch {
332
+ fixesApplied.push(`failed to remove stranded lock directory ${lockDir}`);
333
+ }
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }
339
+ catch {
340
+ // Non-fatal — stranded lock directory check failed
341
+ }
234
342
  // ── Stale parallel sessions ────────────────────────────────────────────
235
343
  try {
236
344
  const parallelStatuses = readAllSessionStatuses(basePath);
@@ -19,6 +19,9 @@ import { gsdRoot, resolveGsdRootFile } from "./paths.js";
19
19
  import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
20
20
  import { abortAndReset } from "./git-self-heal.js";
21
21
  import { rebuildState } from "./doctor.js";
22
+ import { deriveState } from "./state.js";
23
+ import { readIntegrationBranch } from "./git-service.js";
24
+ import { nativeBranchExists, nativeIsRepo } from "./native-git-bridge.js";
22
25
  /** In-memory health history for the current auto-mode session. */
23
26
  let healthHistory = [];
24
27
  /** Count of consecutive units with unresolved errors. */
@@ -156,6 +159,25 @@ export async function preDispatchHealthGate(basePath) {
156
159
  catch {
157
160
  // Non-fatal — dispatch continues without STATE.md if rebuild fails
158
161
  }
162
+ // ── Integration branch existence check ──
163
+ // If the active milestone's recorded integration branch no longer exists in
164
+ // git, the merge-back at the end of the milestone will fail. Block dispatch
165
+ // now to surface this before work is lost.
166
+ try {
167
+ if (nativeIsRepo(basePath)) {
168
+ const state = await deriveState(basePath);
169
+ if (state.activeMilestone) {
170
+ const integrationBranch = readIntegrationBranch(basePath, state.activeMilestone.id);
171
+ if (integrationBranch && !nativeBranchExists(basePath, integrationBranch)) {
172
+ issues.push(`Integration branch "${integrationBranch}" for milestone ${state.activeMilestone.id} no longer exists in git. ` +
173
+ `Restore the branch or update the integration branch before dispatching. Run /gsd doctor for details.`);
174
+ }
175
+ }
176
+ }
177
+ }
178
+ catch {
179
+ // Non-fatal — dispatch continues if state/branch check fails
180
+ }
159
181
  // If we had critical issues that couldn't be auto-healed, block dispatch
160
182
  if (issues.length > 0) {
161
183
  return {
@@ -8,6 +8,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
8
8
  import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
9
9
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
10
10
  import { checkEnvironmentHealth } from "./doctor-environment.js";
11
+ import { runProviderChecks } from "./doctor-providers.js";
11
12
  export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
12
13
  export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport } from "./doctor-environment.js";
13
14
  export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport } from "./progress-score.js";
@@ -373,6 +374,41 @@ export async function runGSDDoctor(basePath, options) {
373
374
  const requirementsContent = await loadFile(requirementsPath);
374
375
  issues.push(...auditRequirements(requirementsContent));
375
376
  const state = await deriveState(basePath);
377
+ // Provider / auth health checks — only relevant when there is active work to dispatch.
378
+ // Skipped for idle projects (no active milestone) to avoid noise in environments
379
+ // where CI/test runners have no API key configured.
380
+ if (state.activeMilestone) {
381
+ try {
382
+ const providerResults = runProviderChecks();
383
+ for (const result of providerResults) {
384
+ if (!result.required)
385
+ continue;
386
+ if (result.status === "error") {
387
+ issues.push({
388
+ severity: "warning",
389
+ code: "provider_key_missing",
390
+ scope: "project",
391
+ unitId: "project",
392
+ message: result.message + (result.detail ? ` — ${result.detail}` : ""),
393
+ fixable: false,
394
+ });
395
+ }
396
+ else if (result.status === "warning") {
397
+ issues.push({
398
+ severity: "warning",
399
+ code: "provider_key_backedoff",
400
+ scope: "project",
401
+ unitId: "project",
402
+ message: result.message + (result.detail ? ` — ${result.detail}` : ""),
403
+ fixable: false,
404
+ });
405
+ }
406
+ }
407
+ }
408
+ catch {
409
+ // Non-fatal — provider check failure should not block other checks
410
+ }
411
+ }
376
412
  for (const milestone of state.registry) {
377
413
  const milestoneId = milestone.id;
378
414
  const milestonePath = resolveMilestonePath(basePath, milestoneId);
@@ -661,9 +661,11 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
661
661
  untrackRuntimeFiles(basePath);
662
662
  // ── Self-heal stale runtime records from crashed auto-mode sessions ──
663
663
  selfHealRuntimeRecords(basePath, ctx);
664
- // Check for crash from previous auto-mode session
664
+ // Check for crash from previous auto-mode session.
665
+ // Skip if the lock was written by the current process — acquireSessionLock()
666
+ // writes to the same file, so we'd always false-positive (#1398).
665
667
  const crashLock = readCrashLock(basePath);
666
- if (crashLock) {
668
+ if (crashLock && crashLock.pid !== process.pid) {
667
669
  clearLock(basePath);
668
670
  // Bootstrap crash with zero completed units = no work was lost.
669
671
  // Auto-discard instead of prompting the user — this commonly happens
@@ -603,5 +603,43 @@ export function validatePreferences(preferences) {
603
603
  validated.git = git;
604
604
  }
605
605
  }
606
+ // ─── Auto Visualize ─────────────────────────────────────────────────
607
+ if (preferences.auto_visualize !== undefined) {
608
+ if (typeof preferences.auto_visualize === "boolean") {
609
+ validated.auto_visualize = preferences.auto_visualize;
610
+ }
611
+ else {
612
+ errors.push("auto_visualize must be a boolean");
613
+ }
614
+ }
615
+ // ─── Auto Report ────────────────────────────────────────────────────
616
+ if (preferences.auto_report !== undefined) {
617
+ if (typeof preferences.auto_report === "boolean") {
618
+ validated.auto_report = preferences.auto_report;
619
+ }
620
+ else {
621
+ errors.push("auto_report must be a boolean");
622
+ }
623
+ }
624
+ // ─── Compression Strategy ───────────────────────────────────────────
625
+ if (preferences.compression_strategy !== undefined) {
626
+ const validStrategies = new Set(["truncate", "compress"]);
627
+ if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
628
+ validated.compression_strategy = preferences.compression_strategy;
629
+ }
630
+ else {
631
+ errors.push(`compression_strategy must be one of: truncate, compress`);
632
+ }
633
+ }
634
+ // ─── Context Selection ──────────────────────────────────────────────
635
+ if (preferences.context_selection !== undefined) {
636
+ const validModes = new Set(["full", "smart"]);
637
+ if (typeof preferences.context_selection === "string" && validModes.has(preferences.context_selection)) {
638
+ validated.context_selection = preferences.context_selection;
639
+ }
640
+ else {
641
+ errors.push(`context_selection must be one of: full, smart`);
642
+ }
643
+ }
606
644
  return { preferences: validated, errors, warnings };
607
645
  }
@@ -186,6 +186,8 @@ function mergePreferences(base, override) {
186
186
  search_provider: override.search_provider ?? base.search_provider,
187
187
  compression_strategy: override.compression_strategy ?? base.compression_strategy,
188
188
  context_selection: override.context_selection ?? base.context_selection,
189
+ auto_visualize: override.auto_visualize ?? base.auto_visualize,
190
+ auto_report: override.auto_report ?? base.auto_report,
189
191
  };
190
192
  }
191
193
  function mergeStringLists(base, override) {
@@ -61,10 +61,10 @@ pi.on("tool_call", async (event, ctx) => {
61
61
 
62
62
  **tool_result** — Fired after tool executes. Can modify result. Handlers chain like middleware.
63
63
  ```typescript
64
- import { isBashToolResult } from "@mariozechner/pi-coding-agent";
64
+ import { isToolResultEventType } from "@mariozechner/pi-coding-agent";
65
65
 
66
66
  pi.on("tool_result", async (event, ctx) => {
67
- if (isBashToolResult(event)) {
67
+ if (isToolResultEventType("bash", event)) {
68
68
  // event.details is typed as BashToolDetails
69
69
  }
70
70
  // Return partial patch: { content, details, isError }
@@ -105,7 +105,7 @@ pi.on("model_select", async (event, ctx) => {
105
105
  Built-in type guards for tool events:
106
106
 
107
107
  ```typescript
108
- import { isToolCallEventType, isBashToolResult } from "@mariozechner/pi-coding-agent";
108
+ import { isToolCallEventType, isToolResultEventType } from "@mariozechner/pi-coding-agent";
109
109
 
110
110
  // Tool calls — narrows event.input type
111
111
  if (isToolCallEventType("bash", event)) { /* event.input: { command, timeout? } */ }
@@ -114,7 +114,7 @@ if (isToolCallEventType("write", event)) { /* event.input: { path, content } */
114
114
  if (isToolCallEventType("edit", event)) { /* event.input: { path, oldText, newText } */ }
115
115
 
116
116
  // Tool results — narrows event.details type
117
- if (isBashToolResult(event)) { /* event.details: BashToolDetails */ }
117
+ if (isToolResultEventType("bash", event)) { /* event.details: BashToolDetails */ }
118
118
  ```
119
119
 
120
120
  For custom tools, export your input type and use explicit type params:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-pi",
3
- "version": "2.34.0-dev.7d38042",
3
+ "version": "2.34.0-dev.e6d9bed",
4
4
  "description": "GSD — Get Shit Done coding agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -4,6 +4,20 @@
4
4
  */
5
5
  import { EventStream } from "@gsd/pi-ai";
6
6
  import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, StreamFn } from "./types.js";
7
+ export declare const ZERO_USAGE: {
8
+ readonly input: 0;
9
+ readonly output: 0;
10
+ readonly cacheRead: 0;
11
+ readonly cacheWrite: 0;
12
+ readonly totalTokens: 0;
13
+ readonly cost: {
14
+ readonly input: 0;
15
+ readonly output: 0;
16
+ readonly cacheRead: 0;
17
+ readonly cacheWrite: 0;
18
+ readonly total: 0;
19
+ };
20
+ };
7
21
  /**
8
22
  * Start an agent loop with a new prompt message.
9
23
  * The prompt is added to the context and events are emitted for it.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGN,WAAW,EAIX,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAIZ,QAAQ,EACR,MAAM,YAAY,CAAC;AA8BpB;;;GAGG;AACH,wBAAgB,SAAS,CACxB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CA8BzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CA+BzC"}
1
+ {"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGN,WAAW,EAIX,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACX,YAAY,EACZ,UAAU,EACV,eAAe,EACf,YAAY,EAIZ,QAAQ,EACR,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,UAAU;;;;;;;;;;;;;CAOb,CAAC;AA4CX;;;GAGG;AACH,wBAAgB,SAAS,CACxB,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CAwBzC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,QAAQ,GACjB,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,CAAC,CA0BzC"}
@@ -3,7 +3,7 @@
3
3
  * Transforms to Message[] only at the LLM call boundary.
4
4
  */
5
5
  import { EventStream, streamSimple, validateToolArguments, } from "@gsd/pi-ai";
6
- const ZERO_USAGE = {
6
+ export const ZERO_USAGE = {
7
7
  input: 0,
8
8
  output: 0,
9
9
  cacheRead: 0,
@@ -29,6 +29,23 @@ function createErrorMessage(error, config) {
29
29
  timestamp: Date.now(),
30
30
  };
31
31
  }
32
+ /**
33
+ * Emit a message_start + message_end pair for a single message.
34
+ */
35
+ function emitMessagePair(stream, message) {
36
+ stream.push({ type: "message_start", message });
37
+ stream.push({ type: "message_end", message });
38
+ }
39
+ /**
40
+ * Emit the standard error sequence when the outer agent loop catches an error.
41
+ * Pushes message_start/end, turn_end, agent_end, then closes the stream.
42
+ */
43
+ function emitErrorSequence(stream, errMsg, newMessages) {
44
+ emitMessagePair(stream, errMsg);
45
+ stream.push({ type: "turn_end", message: errMsg, toolResults: [] });
46
+ stream.push({ type: "agent_end", messages: [...newMessages, errMsg] });
47
+ stream.end([...newMessages, errMsg]);
48
+ }
32
49
  /**
33
50
  * Start an agent loop with a new prompt message.
34
51
  * The prompt is added to the context and events are emitted for it.
@@ -44,19 +61,13 @@ export function agentLoop(prompts, context, config, signal, streamFn) {
44
61
  stream.push({ type: "agent_start" });
45
62
  stream.push({ type: "turn_start" });
46
63
  for (const prompt of prompts) {
47
- stream.push({ type: "message_start", message: prompt });
48
- stream.push({ type: "message_end", message: prompt });
64
+ emitMessagePair(stream, prompt);
49
65
  }
50
66
  try {
51
67
  await runLoop(currentContext, newMessages, config, signal, stream, streamFn);
52
68
  }
53
69
  catch (error) {
54
- const errMsg = createErrorMessage(error, config);
55
- stream.push({ type: "message_start", message: errMsg });
56
- stream.push({ type: "message_end", message: errMsg });
57
- stream.push({ type: "turn_end", message: errMsg, toolResults: [] });
58
- stream.push({ type: "agent_end", messages: [...newMessages, errMsg] });
59
- stream.end([...newMessages, errMsg]);
70
+ emitErrorSequence(stream, createErrorMessage(error, config), newMessages);
60
71
  }
61
72
  })();
62
73
  return stream;
@@ -86,12 +97,7 @@ export function agentLoopContinue(context, config, signal, streamFn) {
86
97
  await runLoop(currentContext, newMessages, config, signal, stream, streamFn);
87
98
  }
88
99
  catch (error) {
89
- const errMsg = createErrorMessage(error, config);
90
- stream.push({ type: "message_start", message: errMsg });
91
- stream.push({ type: "message_end", message: errMsg });
92
- stream.push({ type: "turn_end", message: errMsg, toolResults: [] });
93
- stream.push({ type: "agent_end", messages: [...newMessages, errMsg] });
94
- stream.end([...newMessages, errMsg]);
100
+ emitErrorSequence(stream, createErrorMessage(error, config), newMessages);
95
101
  }
96
102
  })();
97
103
  return stream;
@@ -121,8 +127,7 @@ async function runLoop(currentContext, newMessages, config, signal, stream, stre
121
127
  // Process pending messages (inject before next assistant response)
122
128
  if (pendingMessages.length > 0) {
123
129
  for (const message of pendingMessages) {
124
- stream.push({ type: "message_start", message });
125
- stream.push({ type: "message_end", message });
130
+ emitMessagePair(stream, message);
126
131
  currentContext.messages.push(message);
127
132
  newMessages.push(message);
128
133
  }
@@ -144,14 +149,7 @@ async function runLoop(currentContext, newMessages, config, signal, stream, stre
144
149
  api: config.model.api,
145
150
  provider: config.model.provider,
146
151
  model: config.model.id,
147
- usage: {
148
- input: 0,
149
- output: 0,
150
- cacheRead: 0,
151
- cacheWrite: 0,
152
- totalTokens: 0,
153
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
154
- },
152
+ usage: ZERO_USAGE,
155
153
  stopReason: signal?.aborted ? "aborted" : "error",
156
154
  errorMessage: errorText,
157
155
  timestamp: Date.now(),
@@ -478,8 +476,7 @@ function emitToolCallOutcome(toolCall, result, isError, stream) {
478
476
  isError,
479
477
  timestamp: Date.now(),
480
478
  };
481
- stream.push({ type: "message_start", message: toolResultMessage });
482
- stream.push({ type: "message_end", message: toolResultMessage });
479
+ emitMessagePair(stream, toolResultMessage);
483
480
  return toolResultMessage;
484
481
  }
485
482
  function skipToolCall(toolCall, stream, options) {