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
@@ -788,9 +788,11 @@ export async function showSmartEntry(
788
788
  // ── Self-heal stale runtime records from crashed auto-mode sessions ──
789
789
  selfHealRuntimeRecords(basePath, ctx);
790
790
 
791
- // Check for crash from previous auto-mode session
791
+ // Check for crash from previous auto-mode session.
792
+ // Skip if the lock was written by the current process — acquireSessionLock()
793
+ // writes to the same file, so we'd always false-positive (#1398).
792
794
  const crashLock = readCrashLock(basePath);
793
- if (crashLock) {
795
+ if (crashLock && crashLock.pid !== process.pid) {
794
796
  clearLock(basePath);
795
797
 
796
798
  // Bootstrap crash with zero completed units = no work was lost.
@@ -586,5 +586,43 @@ export function validatePreferences(preferences: GSDPreferences): {
586
586
  }
587
587
  }
588
588
 
589
+ // ─── Auto Visualize ─────────────────────────────────────────────────
590
+ if (preferences.auto_visualize !== undefined) {
591
+ if (typeof preferences.auto_visualize === "boolean") {
592
+ validated.auto_visualize = preferences.auto_visualize;
593
+ } else {
594
+ errors.push("auto_visualize must be a boolean");
595
+ }
596
+ }
597
+
598
+ // ─── Auto Report ────────────────────────────────────────────────────
599
+ if (preferences.auto_report !== undefined) {
600
+ if (typeof preferences.auto_report === "boolean") {
601
+ validated.auto_report = preferences.auto_report;
602
+ } else {
603
+ errors.push("auto_report must be a boolean");
604
+ }
605
+ }
606
+
607
+ // ─── Compression Strategy ───────────────────────────────────────────
608
+ if (preferences.compression_strategy !== undefined) {
609
+ const validStrategies = new Set(["truncate", "compress"]);
610
+ if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
611
+ validated.compression_strategy = preferences.compression_strategy as GSDPreferences["compression_strategy"];
612
+ } else {
613
+ errors.push(`compression_strategy must be one of: truncate, compress`);
614
+ }
615
+ }
616
+
617
+ // ─── Context Selection ──────────────────────────────────────────────
618
+ if (preferences.context_selection !== undefined) {
619
+ const validModes = new Set(["full", "smart"]);
620
+ if (typeof preferences.context_selection === "string" && validModes.has(preferences.context_selection)) {
621
+ validated.context_selection = preferences.context_selection as GSDPreferences["context_selection"];
622
+ } else {
623
+ errors.push(`context_selection must be one of: full, smart`);
624
+ }
625
+ }
626
+
589
627
  return { preferences: validated, errors, warnings };
590
628
  }
@@ -252,6 +252,8 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
252
252
  search_provider: override.search_provider ?? base.search_provider,
253
253
  compression_strategy: override.compression_strategy ?? base.compression_strategy,
254
254
  context_selection: override.context_selection ?? base.context_selection,
255
+ auto_visualize: override.auto_visualize ?? base.auto_visualize,
256
+ auto_report: override.auto_report ?? base.auto_report,
255
257
  };
256
258
  }
257
259
 
@@ -2,9 +2,10 @@
2
2
  * doctor-git.test.ts — Integration tests for doctor git health checks.
3
3
  *
4
4
  * Creates real temp git repos with deliberate broken state, runs runGSDDoctor,
5
- * and asserts correct detection and fixing of all 4 git issue codes:
5
+ * and asserts correct detection and fixing of git issue codes:
6
6
  * orphaned_auto_worktree, stale_milestone_branch,
7
- * corrupt_merge_state, tracked_runtime_files
7
+ * corrupt_merge_state, tracked_runtime_files,
8
+ * integration_branch_missing, worktree_directory_orphaned
8
9
  */
9
10
 
10
11
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, realpathSync } from "node:fs";
@@ -299,6 +300,101 @@ async function main(): Promise<void> {
299
300
  console.log("\n=== none-mode skips stale branch (skipped on Windows) ===");
300
301
  }
301
302
 
303
+ // ─── Test: Integration branch missing ──────────────────────────────
304
+ if (process.platform !== "win32") {
305
+ console.log("\n=== integration_branch_missing ===");
306
+ {
307
+ const dir = createRepoWithActiveMilestone();
308
+ cleanups.push(dir);
309
+
310
+ // Write integration branch metadata for M001 pointing to a non-existent branch
311
+ const metaPath = join(dir, ".gsd", "milestones", "M001", "M001-META.json");
312
+ writeFileSync(metaPath, JSON.stringify({ integrationBranch: "feat/does-not-exist" }, null, 2));
313
+
314
+ const detect = await runGSDDoctor(dir);
315
+ const missingBranchIssues = detect.issues.filter(i => i.code === "integration_branch_missing");
316
+ assertTrue(missingBranchIssues.length > 0, "detects missing integration branch");
317
+ assertTrue(
318
+ missingBranchIssues[0]?.message.includes("feat/does-not-exist"),
319
+ "message includes the missing branch name",
320
+ );
321
+ assertEq(missingBranchIssues[0]?.fixable, false, "integration_branch_missing is not auto-fixable");
322
+ assertEq(missingBranchIssues[0]?.severity, "error", "severity is error");
323
+ }
324
+ } else {
325
+ console.log("\n=== integration_branch_missing (skipped on Windows) ===");
326
+ }
327
+
328
+ // ─── Test: Integration branch present — no false positive ──────────
329
+ if (process.platform !== "win32") {
330
+ console.log("\n=== integration_branch_missing (no false positive) ===");
331
+ {
332
+ const dir = createRepoWithActiveMilestone();
333
+ cleanups.push(dir);
334
+
335
+ // Write integration branch metadata for M001 pointing to "main" (which exists)
336
+ const metaPath = join(dir, ".gsd", "milestones", "M001", "M001-META.json");
337
+ writeFileSync(metaPath, JSON.stringify({ integrationBranch: "main" }, null, 2));
338
+
339
+ const detect = await runGSDDoctor(dir);
340
+ const missingBranchIssues = detect.issues.filter(i => i.code === "integration_branch_missing");
341
+ assertEq(missingBranchIssues.length, 0, "existing integration branch NOT flagged");
342
+ }
343
+ } else {
344
+ console.log("\n=== integration_branch_missing (no false positive — skipped on Windows) ===");
345
+ }
346
+
347
+ // ─── Test: Orphaned worktree directory ─────────────────────────────
348
+ if (process.platform !== "win32") {
349
+ console.log("\n=== worktree_directory_orphaned ===");
350
+ {
351
+ const dir = createRepoWithActiveMilestone();
352
+ cleanups.push(dir);
353
+
354
+ // Create a worktrees/ dir with an entry that is NOT in git worktree list
355
+ const orphanDir = join(dir, ".gsd", "worktrees", "orphan-feature");
356
+ mkdirSync(orphanDir, { recursive: true });
357
+ writeFileSync(join(orphanDir, "some-file.txt"), "leftover content\n");
358
+
359
+ const detect = await runGSDDoctor(dir);
360
+ const orphanDirIssues = detect.issues.filter(i => i.code === "worktree_directory_orphaned");
361
+ assertTrue(orphanDirIssues.length > 0, "detects orphaned worktree directory");
362
+ assertTrue(
363
+ orphanDirIssues[0]?.message.includes("orphan-feature"),
364
+ "message includes the orphaned directory name",
365
+ );
366
+ assertTrue(orphanDirIssues[0]?.fixable === true, "worktree_directory_orphaned is fixable");
367
+
368
+ const fixed = await runGSDDoctor(dir, { fix: true });
369
+ assertTrue(
370
+ fixed.fixesApplied.some(f => f.includes("removed orphaned worktree directory")),
371
+ "fix removes orphaned worktree directory",
372
+ );
373
+ assertTrue(!existsSync(orphanDir), "orphaned directory removed after fix");
374
+ }
375
+ } else {
376
+ console.log("\n=== worktree_directory_orphaned (skipped on Windows) ===");
377
+ }
378
+
379
+ // ─── Test: Registered worktree NOT flagged as orphaned ─────────────
380
+ if (process.platform !== "win32") {
381
+ console.log("\n=== worktree_directory_orphaned (registered worktree not flagged) ===");
382
+ {
383
+ const dir = createRepoWithActiveMilestone();
384
+ cleanups.push(dir);
385
+
386
+ // Create a real registered worktree under .gsd/worktrees/
387
+ mkdirSync(join(dir, ".gsd", "worktrees"), { recursive: true });
388
+ run("git worktree add -b worktree/feature-1 .gsd/worktrees/feature-1", dir);
389
+
390
+ const detect = await runGSDDoctor(dir);
391
+ const orphanDirIssues = detect.issues.filter(i => i.code === "worktree_directory_orphaned");
392
+ assertEq(orphanDirIssues.length, 0, "registered worktree NOT flagged as orphaned");
393
+ }
394
+ } else {
395
+ console.log("\n=== worktree_directory_orphaned (registered worktree not flagged — skipped on Windows) ===");
396
+ }
397
+
302
398
  // ─── Test 9: none-mode still detects corrupt merge state ───────────
303
399
  console.log("\n=== none-mode keeps corrupt merge state ===");
304
400
  {
@@ -2,9 +2,9 @@
2
2
  * doctor-runtime.test.ts — Tests for doctor runtime health checks.
3
3
  *
4
4
  * Tests detection and auto-fix of:
5
- * stale_crash_lock, orphaned_completed_units, stale_hook_state,
6
- * activity_log_bloat, state_file_missing, state_file_stale,
7
- * gitignore_missing_patterns
5
+ * stale_crash_lock, stranded_lock_directory, orphaned_completed_units,
6
+ * stale_hook_state, activity_log_bloat, state_file_missing,
7
+ * state_file_stale, gitignore_missing_patterns
8
8
  */
9
9
 
10
10
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync, realpathSync } from "node:fs";
@@ -290,6 +290,62 @@ node_modules/
290
290
  assertEq(content.length, 0, "all orphaned keys removed");
291
291
  }
292
292
 
293
+ // ─── Test: Stranded lock directory detection & fix ────────────────
294
+ // Skip on Windows: proper-lockfile uses advisory file locking on Windows,
295
+ // not the directory-based mechanism. The .gsd.lock/ directory pattern is
296
+ // a POSIX-specific lockfile implementation detail.
297
+ if (process.platform !== "win32") {
298
+ console.log("\n=== stranded_lock_directory ===");
299
+ {
300
+ const dir = createMinimalProject();
301
+ cleanups.push(dir);
302
+
303
+ // Create the proper-lockfile lock directory without a live lock holder.
304
+ // The lock dir sits at <parent of .gsd>/.gsd.lock (i.e., <basePath>/.gsd.lock).
305
+ const lockDir = join(dir, ".gsd.lock");
306
+ mkdirSync(lockDir, { recursive: true });
307
+
308
+ const detect = await runGSDDoctor(dir);
309
+ const strandedIssues = detect.issues.filter(i => i.code === "stranded_lock_directory");
310
+ assertTrue(strandedIssues.length > 0, "detects stranded lock directory");
311
+ assertTrue(strandedIssues[0]?.message.includes("lock directory"), "message describes stranded lock directory");
312
+ assertTrue(strandedIssues[0]?.fixable === true, "stranded lock dir is fixable");
313
+
314
+ const fixed = await runGSDDoctor(dir, { fix: true });
315
+ assertTrue(
316
+ fixed.fixesApplied.some(f => f.includes("removed stranded lock directory")),
317
+ "fix removes stranded lock directory",
318
+ );
319
+ assertTrue(!existsSync(lockDir), "lock directory removed after fix");
320
+ }
321
+
322
+ // ─── Test: Stranded lock dir with live lock holder — NOT flagged ───
323
+ console.log("\n=== stranded_lock_directory (live holder not flagged) ===");
324
+ {
325
+ const dir = createMinimalProject();
326
+ cleanups.push(dir);
327
+
328
+ // Create lock dir + auto.lock with PID 1 (init/launchd — always alive, never our own PID)
329
+ const lockDir = join(dir, ".gsd.lock");
330
+ mkdirSync(lockDir, { recursive: true });
331
+ const liveLockData = {
332
+ pid: 1,
333
+ startedAt: new Date().toISOString(),
334
+ unitType: "execute-task",
335
+ unitId: "M001/S01/T01",
336
+ unitStartedAt: new Date().toISOString(),
337
+ completedUnits: 1,
338
+ };
339
+ writeFileSync(join(dir, ".gsd", "auto.lock"), JSON.stringify(liveLockData, null, 2));
340
+
341
+ const detect = await runGSDDoctor(dir);
342
+ const strandedIssues = detect.issues.filter(i => i.code === "stranded_lock_directory");
343
+ assertEq(strandedIssues.length, 0, "live lock holder: stranded_lock_directory NOT detected");
344
+ }
345
+ } else {
346
+ console.log("\n=== stranded_lock_directory (skipped on Windows) ===");
347
+ }
348
+
293
349
  } finally {
294
350
  for (const dir of cleanups) {
295
351
  try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
@@ -175,6 +175,34 @@ test("git fields comprehensive validation", () => {
175
175
  assert.equal(preferences.git?.isolation, "branch");
176
176
  });
177
177
 
178
+ test("auto_visualize, auto_report, compression_strategy, context_selection validate correctly", () => {
179
+ const { preferences, errors } = validatePreferences({
180
+ auto_visualize: true,
181
+ auto_report: false,
182
+ compression_strategy: "compress",
183
+ context_selection: "smart",
184
+ });
185
+ assert.equal(errors.length, 0);
186
+ assert.equal(preferences.auto_visualize, true);
187
+ assert.equal(preferences.auto_report, false);
188
+ assert.equal(preferences.compression_strategy, "compress");
189
+ assert.equal(preferences.context_selection, "smart");
190
+ });
191
+
192
+ test("auto_visualize, auto_report, compression_strategy, context_selection reject invalid values", () => {
193
+ const { errors: e1 } = validatePreferences({ auto_visualize: "yes" as never });
194
+ assert.ok(e1.some(e => e.includes("auto_visualize")));
195
+
196
+ const { errors: e2 } = validatePreferences({ auto_report: 1 as never });
197
+ assert.ok(e2.some(e => e.includes("auto_report")));
198
+
199
+ const { errors: e3 } = validatePreferences({ compression_strategy: "shrink" as never });
200
+ assert.ok(e3.some(e => e.includes("compression_strategy")));
201
+
202
+ const { errors: e4 } = validatePreferences({ context_selection: "partial" as never });
203
+ assert.ok(e4.some(e => e.includes("context_selection")));
204
+ });
205
+
178
206
  test("all wizard fields together produce no errors", () => {
179
207
  const { errors, warnings } = validatePreferences({
180
208
  version: 1,
@@ -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: