gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.64cd3ed

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/remote-questions-config.js +2 -2
  8. package/dist/resource-loader.js +34 -1
  9. package/dist/resources/extensions/async-jobs/index.js +10 -0
  10. package/dist/resources/extensions/browser-tools/index.js +3 -1
  11. package/dist/resources/extensions/browser-tools/package.json +3 -1
  12. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  13. package/dist/resources/extensions/cmux/index.js +55 -1
  14. package/dist/resources/extensions/context7/package.json +1 -1
  15. package/dist/resources/extensions/env-utils.js +29 -0
  16. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  17. package/dist/resources/extensions/github-sync/cli.js +284 -0
  18. package/dist/resources/extensions/github-sync/index.js +73 -0
  19. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  20. package/dist/resources/extensions/github-sync/sync.js +424 -0
  21. package/dist/resources/extensions/github-sync/templates.js +118 -0
  22. package/dist/resources/extensions/github-sync/types.js +7 -0
  23. package/dist/resources/extensions/google-search/package.json +3 -1
  24. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  26. package/dist/resources/extensions/gsd/auto-loop.js +650 -588
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  28. package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
  29. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  30. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  31. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  32. package/dist/resources/extensions/gsd/auto.js +143 -96
  33. package/dist/resources/extensions/gsd/captures.js +9 -1
  34. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  35. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  36. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  37. package/dist/resources/extensions/gsd/commands.js +24 -3
  38. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  39. package/dist/resources/extensions/gsd/detection.js +1 -2
  40. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  41. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  42. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  43. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  44. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  45. package/dist/resources/extensions/gsd/doctor.js +204 -12
  46. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  47. package/dist/resources/extensions/gsd/export.js +1 -1
  48. package/dist/resources/extensions/gsd/files.js +48 -9
  49. package/dist/resources/extensions/gsd/forensics.js +1 -1
  50. package/dist/resources/extensions/gsd/git-service.js +30 -12
  51. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  52. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  53. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  54. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  55. package/dist/resources/extensions/gsd/index.js +24 -20
  56. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  57. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  58. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  59. package/dist/resources/extensions/gsd/package.json +1 -1
  60. package/dist/resources/extensions/gsd/paths.js +3 -0
  61. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  62. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  63. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  64. package/dist/resources/extensions/gsd/preferences.js +22 -11
  65. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  66. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  67. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  68. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  69. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  70. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  72. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  73. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  74. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  75. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  76. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  77. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  78. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  79. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  80. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  81. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  82. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  83. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  84. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  85. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  86. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  87. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  88. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  89. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  90. package/dist/resources/extensions/gsd/state.js +42 -23
  91. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  92. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  93. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  94. package/dist/resources/extensions/gsd/worktree.js +35 -16
  95. package/dist/resources/extensions/mcp-client/index.js +14 -1
  96. package/dist/resources/extensions/remote-questions/status.js +4 -1
  97. package/dist/resources/extensions/remote-questions/store.js +4 -1
  98. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  99. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  100. package/dist/resources/extensions/subagent/index.js +12 -3
  101. package/dist/resources/extensions/subagent/isolation.js +2 -1
  102. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  103. package/dist/resources/extensions/universal-config/package.json +1 -1
  104. package/dist/welcome-screen.d.ts +12 -0
  105. package/dist/welcome-screen.js +53 -0
  106. package/package.json +1 -1
  107. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  108. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  109. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  110. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  112. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  115. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  117. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  119. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  121. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/index.js +1 -1
  123. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  124. package/packages/pi-coding-agent/package.json +1 -1
  125. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  126. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  127. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  128. package/packages/pi-coding-agent/src/index.ts +1 -0
  129. package/pkg/package.json +1 -1
  130. package/src/resources/extensions/async-jobs/index.ts +11 -0
  131. package/src/resources/extensions/browser-tools/index.ts +3 -0
  132. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  133. package/src/resources/extensions/cmux/index.ts +57 -1
  134. package/src/resources/extensions/env-utils.ts +31 -0
  135. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  136. package/src/resources/extensions/github-sync/cli.ts +364 -0
  137. package/src/resources/extensions/github-sync/index.ts +93 -0
  138. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  139. package/src/resources/extensions/github-sync/sync.ts +556 -0
  140. package/src/resources/extensions/github-sync/templates.ts +183 -0
  141. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  142. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  143. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  144. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  145. package/src/resources/extensions/github-sync/types.ts +47 -0
  146. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  147. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  148. package/src/resources/extensions/gsd/auto-loop.ts +553 -546
  149. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  150. package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
  151. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  152. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  153. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  154. package/src/resources/extensions/gsd/auto.ts +139 -101
  155. package/src/resources/extensions/gsd/captures.ts +10 -1
  156. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  157. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  158. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  159. package/src/resources/extensions/gsd/commands.ts +26 -4
  160. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  161. package/src/resources/extensions/gsd/detection.ts +2 -2
  162. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  163. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  164. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  165. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  166. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  167. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  168. package/src/resources/extensions/gsd/doctor.ts +199 -14
  169. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  170. package/src/resources/extensions/gsd/export.ts +1 -1
  171. package/src/resources/extensions/gsd/files.ts +51 -11
  172. package/src/resources/extensions/gsd/forensics.ts +1 -1
  173. package/src/resources/extensions/gsd/git-service.ts +44 -10
  174. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  175. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  176. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  177. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  178. package/src/resources/extensions/gsd/index.ts +24 -17
  179. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  180. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  181. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  182. package/src/resources/extensions/gsd/paths.ts +4 -0
  183. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  184. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  185. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  186. package/src/resources/extensions/gsd/preferences.ts +25 -11
  187. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  188. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  190. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  191. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  192. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  193. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  194. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  195. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  196. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  197. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  198. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  199. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  200. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  202. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  203. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  204. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  206. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  207. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  208. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  209. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  210. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  211. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  212. package/src/resources/extensions/gsd/state.ts +39 -21
  213. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  214. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  215. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  216. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  217. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  218. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  219. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  220. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  221. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  222. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  223. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  224. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  225. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  226. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  227. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  228. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  229. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  230. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  231. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  232. package/src/resources/extensions/gsd/types.ts +18 -1
  233. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  234. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  235. package/src/resources/extensions/gsd/worktree.ts +35 -15
  236. package/src/resources/extensions/mcp-client/index.ts +17 -1
  237. package/src/resources/extensions/remote-questions/status.ts +5 -1
  238. package/src/resources/extensions/remote-questions/store.ts +5 -1
  239. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  240. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  241. package/src/resources/extensions/subagent/index.ts +12 -3
  242. package/src/resources/extensions/subagent/isolation.ts +3 -1
  243. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  244. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  245. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  246. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  247. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  248. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  249. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  250. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  251. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  252. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  253. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  254. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  255. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -4,65 +4,18 @@
4
4
  * Separates project-state detection and line rendering from the widget's
5
5
  * runtime integrations so the regressions can be tested directly.
6
6
  */
7
- import { existsSync, readdirSync } from "node:fs";
7
+ import { existsSync } from "node:fs";
8
+ import { detectProjectState } from "./detection.js";
8
9
  import { gsdRoot } from "./paths.js";
9
- import { join } from "node:path";
10
10
  export function detectHealthWidgetProjectState(basePath) {
11
- const root = gsdRoot(basePath);
12
- if (!existsSync(root))
11
+ if (!existsSync(gsdRoot(basePath)))
13
12
  return "none";
14
- // Lightweight milestone count avoids the full detectProjectState() scan
15
- // (CI markers, Makefile targets, etc.) that is unnecessary on the 60s refresh.
16
- try {
17
- const milestonesDir = join(root, "milestones");
18
- if (existsSync(milestonesDir)) {
19
- const entries = readdirSync(milestonesDir, { withFileTypes: true });
20
- if (entries.some(e => e.isDirectory()))
21
- return "active";
22
- }
23
- }
24
- catch { /* non-fatal */ }
25
- return "initialized";
13
+ const { state } = detectProjectState(basePath);
14
+ return state === "v2-gsd" ? "active" : "initialized";
26
15
  }
27
16
  function formatCost(n) {
28
17
  return n >= 1 ? `$${n.toFixed(2)}` : `${(n * 100).toFixed(1)}¢`;
29
18
  }
30
- function formatProgress(progress) {
31
- if (!progress)
32
- return null;
33
- const parts = [];
34
- parts.push(`M ${progress.milestones.done}/${progress.milestones.total}`);
35
- if (progress.slices)
36
- parts.push(`S ${progress.slices.done}/${progress.slices.total}`);
37
- if (progress.tasks)
38
- parts.push(`T ${progress.tasks.done}/${progress.tasks.total}`);
39
- return parts.length > 0 ? `Progress: ${parts.join(" · ")}` : null;
40
- }
41
- function formatEnvironmentSummary(errorCount, warningCount) {
42
- if (errorCount <= 0 && warningCount <= 0)
43
- return null;
44
- const parts = [];
45
- if (errorCount > 0)
46
- parts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
47
- if (warningCount > 0)
48
- parts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
49
- return `Env: ${parts.join(", ")}`;
50
- }
51
- function formatBudgetSummary(data) {
52
- if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
53
- const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
54
- return `Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`;
55
- }
56
- if (data.budgetSpent > 0) {
57
- return `Spent: ${formatCost(data.budgetSpent)}`;
58
- }
59
- return null;
60
- }
61
- function buildExecutionHeadline(data) {
62
- const status = data.executionStatus ?? "Active project";
63
- const target = data.executionTarget ?? data.blocker ?? "loading status…";
64
- return ` GSD ${status}${target ? ` - ${target}` : ""}`;
65
- }
66
19
  /**
67
20
  * Build compact health lines for the widget.
68
21
  * Returns a string array suitable for setWidget().
@@ -74,23 +27,32 @@ export function buildHealthLines(data) {
74
27
  if (data.projectState === "initialized") {
75
28
  return [" GSD Project initialized — run /gsd to continue setup"];
76
29
  }
77
- const lines = [buildExecutionHeadline(data)];
78
- const details = [];
79
- const progress = formatProgress(data.progress);
80
- if (progress)
81
- details.push(progress);
82
- if (data.providerIssue)
83
- details.push(data.providerIssue);
84
- const environment = formatEnvironmentSummary(data.environmentErrorCount, data.environmentWarningCount);
85
- if (environment)
86
- details.push(environment);
87
- const budget = formatBudgetSummary(data);
88
- if (budget)
89
- details.push(budget);
90
- if (data.eta)
91
- details.push(data.eta);
92
- if (details.length > 0) {
93
- lines.push(` ${details.join(" │ ")}`);
30
+ const parts = [];
31
+ const totalIssues = data.environmentErrorCount + data.environmentWarningCount + (data.providerIssue ? 1 : 0);
32
+ if (totalIssues === 0) {
33
+ parts.push("● System OK");
34
+ }
35
+ else if (data.environmentErrorCount > 0 || data.providerIssue?.includes("✗")) {
36
+ parts.push(`✗ ${totalIssues} issue${totalIssues > 1 ? "s" : ""}`);
37
+ }
38
+ else {
39
+ parts.push(`⚠ ${totalIssues} warning${totalIssues > 1 ? "s" : ""}`);
40
+ }
41
+ if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
42
+ const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
43
+ parts.push(`Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`);
44
+ }
45
+ else if (data.budgetSpent > 0) {
46
+ parts.push(`Spent: ${formatCost(data.budgetSpent)}`);
47
+ }
48
+ if (data.providerIssue) {
49
+ parts.push(data.providerIssue);
50
+ }
51
+ if (data.environmentErrorCount > 0) {
52
+ parts.push(`Env: ${data.environmentErrorCount} error${data.environmentErrorCount > 1 ? "s" : ""}`);
53
+ }
54
+ else if (data.environmentWarningCount > 0) {
55
+ parts.push(`Env: ${data.environmentWarningCount} warning${data.environmentWarningCount > 1 ? "s" : ""}`);
94
56
  }
95
- return lines;
57
+ return [` ${parts.join(" │ ")}`];
96
58
  }
@@ -11,12 +11,10 @@ import { runProviderChecks, summariseProviderIssues } from "./doctor-providers.j
11
11
  import { runEnvironmentChecks } from "./doctor-environment.js";
12
12
  import { loadEffectiveGSDPreferences } from "./preferences.js";
13
13
  import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
14
- import { describeNextUnit, estimateTimeRemaining, updateSliceProgressCache } from "./auto-dashboard.js";
15
14
  import { projectRoot } from "./commands.js";
16
- import { deriveState, invalidateStateCache } from "./state.js";
17
15
  import { buildHealthLines, detectHealthWidgetProjectState, } from "./health-widget-core.js";
18
16
  // ── Data loader ────────────────────────────────────────────────────────────────
19
- function loadBaseHealthWidgetData(basePath) {
17
+ function loadHealthWidgetData(basePath) {
20
18
  let budgetCeiling;
21
19
  let budgetSpent = 0;
22
20
  let providerIssue = null;
@@ -58,86 +56,6 @@ function loadBaseHealthWidgetData(basePath) {
58
56
  lastRefreshed: Date.now(),
59
57
  };
60
58
  }
61
- function compactText(text, max = 64) {
62
- const trimmed = text.replace(/\s+/g, " ").trim();
63
- if (trimmed.length <= max)
64
- return trimmed;
65
- return `${trimmed.slice(0, max - 1).trimEnd()}…`;
66
- }
67
- function summarizeExecutionStatus(state) {
68
- switch (state.phase) {
69
- case "blocked": return "Blocked";
70
- case "paused": return "Paused";
71
- case "complete": return "Complete";
72
- case "executing": return "Executing";
73
- case "planning": return "Planning";
74
- case "pre-planning": return "Pre-planning";
75
- case "summarizing": return "Summarizing";
76
- case "validating-milestone": return "Validating";
77
- case "completing-milestone": return "Completing";
78
- case "needs-discussion": return "Needs discussion";
79
- case "replanning-slice": return "Replanning";
80
- default: return "Active";
81
- }
82
- }
83
- function summarizeExecutionTarget(state) {
84
- switch (state.phase) {
85
- case "needs-discussion":
86
- return state.activeMilestone ? `Discuss ${state.activeMilestone.id}` : "Discuss milestone draft";
87
- case "pre-planning":
88
- return state.activeMilestone ? `Plan ${state.activeMilestone.id}` : "Research & plan milestone";
89
- case "planning":
90
- return state.activeSlice ? `Plan ${state.activeSlice.id}` : "Plan next slice";
91
- case "executing":
92
- return state.activeTask ? `Execute ${state.activeTask.id}` : "Execute next task";
93
- case "summarizing":
94
- return state.activeSlice ? `Complete ${state.activeSlice.id}` : "Complete current slice";
95
- case "validating-milestone":
96
- return state.activeMilestone ? `Validate ${state.activeMilestone.id}` : "Validate milestone";
97
- case "completing-milestone":
98
- return state.activeMilestone ? `Complete ${state.activeMilestone.id}` : "Complete milestone";
99
- case "replanning-slice":
100
- return state.activeSlice ? `Replan ${state.activeSlice.id}` : "Replan current slice";
101
- case "blocked":
102
- return `waiting on ${compactText(state.blockers[0] ?? state.nextAction, 56)}`;
103
- case "paused":
104
- return compactText(state.nextAction || "waiting to resume", 56);
105
- case "complete":
106
- return "All milestones complete";
107
- default:
108
- return compactText(describeNextUnit(state).label, 56);
109
- }
110
- }
111
- async function enrichHealthWidgetData(basePath, baseData) {
112
- if (baseData.projectState !== "active")
113
- return baseData;
114
- try {
115
- invalidateStateCache();
116
- const state = await deriveState(basePath);
117
- if (state.activeMilestone) {
118
- // Warm the slice-progress cache so estimateTimeRemaining() has data
119
- updateSliceProgressCache(basePath, state.activeMilestone.id, state.activeSlice?.id);
120
- }
121
- return {
122
- ...baseData,
123
- executionPhase: state.phase,
124
- executionStatus: summarizeExecutionStatus(state),
125
- executionTarget: summarizeExecutionTarget(state),
126
- nextAction: state.nextAction,
127
- blocker: state.blockers[0] ?? null,
128
- activeMilestoneId: state.activeMilestone?.id,
129
- activeSliceId: state.activeSlice?.id,
130
- activeTaskId: state.activeTask?.id,
131
- progress: state.progress,
132
- eta: state.phase === "blocked" || state.phase === "paused" || state.phase === "complete"
133
- ? null
134
- : estimateTimeRemaining(),
135
- };
136
- }
137
- catch {
138
- return baseData;
139
- }
140
- }
141
59
  // ── Widget init ────────────────────────────────────────────────────────────────
142
60
  const REFRESH_INTERVAL_MS = 60_000;
143
61
  /**
@@ -149,7 +67,7 @@ export function initHealthWidget(ctx) {
149
67
  return;
150
68
  const basePath = projectRoot();
151
69
  // String-array fallback — used in RPC mode (factory is a no-op there)
152
- const initialData = loadBaseHealthWidgetData(basePath);
70
+ const initialData = loadHealthWidgetData(basePath);
153
71
  ctx.ui.setWidget("gsd-health", buildHealthLines(initialData), { placement: "belowEditor" });
154
72
  // Factory-based widget for TUI mode — replaces the string-array above
155
73
  ctx.ui.setWidget("gsd-health", (_tui, _theme) => {
@@ -161,8 +79,7 @@ export function initHealthWidget(ctx) {
161
79
  return;
162
80
  refreshInFlight = true;
163
81
  try {
164
- const baseData = loadBaseHealthWidgetData(basePath);
165
- data = await enrichHealthWidgetData(basePath, baseData);
82
+ data = loadHealthWidgetData(basePath);
166
83
  cachedLines = undefined;
167
84
  _tui.requestRender();
168
85
  }
@@ -41,6 +41,7 @@ import { join } from "node:path";
41
41
  import { existsSync, readFileSync } from "node:fs";
42
42
  import { homedir } from "node:os";
43
43
  import { shortcutDesc } from "../shared/mod.js";
44
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
44
45
  import { Text } from "@gsd/pi-tui";
45
46
  import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
46
47
  import { toPosixPath } from "../shared/mod.js";
@@ -52,7 +53,7 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../cmux/index.js"
52
53
  // Pi core natively supports AGENTS.md (with CLAUDE.md fallback) per directory.
53
54
  function warnDeprecatedAgentInstructions() {
54
55
  const paths = [
55
- join(homedir(), ".gsd", "agent-instructions.md"),
56
+ join(gsdHome, "agent-instructions.md"),
56
57
  join(process.cwd(), ".gsd", "agent-instructions.md"),
57
58
  ];
58
59
  for (const p of paths) {
@@ -65,6 +66,24 @@ function warnDeprecatedAgentInstructions() {
65
66
  }
66
67
  // ── Depth verification state ──────────────────────────────────────────────
67
68
  let depthVerificationDone = false;
69
+ // ── DB lazy-open helper ───────────────────────────────────────────────────
70
+ // In manual sessions (no auto-mode), the DB is never opened by bootstrapAutoSession.
71
+ // This helper ensures the DB is lazily opened on first tool call that needs it.
72
+ async function ensureDbOpen() {
73
+ try {
74
+ const db = await import("./gsd-db.js");
75
+ if (db.isDbAvailable())
76
+ return true;
77
+ const dbPath = join(process.cwd(), ".gsd", "gsd.db");
78
+ if (existsSync(dbPath)) {
79
+ return db.openDatabase(dbPath);
80
+ }
81
+ return false;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
68
87
  // ── Queue phase tracking ──────────────────────────────────────────────────
69
88
  // When true, the LLM is in a queue flow writing CONTEXT.md files.
70
89
  // The write-gate applies during queue flows just like discussion flows.
@@ -227,13 +246,8 @@ export default function (pi) {
227
246
  when_context: Type.Optional(Type.String({ description: "When/context for the decision (e.g. milestone ID)" })),
228
247
  }),
229
248
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
230
- // Check DB availability
231
- let dbAvailable = false;
232
- try {
233
- const db = await import("./gsd-db.js");
234
- dbAvailable = db.isDbAvailable();
235
- }
236
- catch { /* dynamic import failed */ }
249
+ // Ensure DB is open (lazy-open on first tool call in manual sessions)
250
+ const dbAvailable = await ensureDbOpen();
237
251
  if (!dbAvailable) {
238
252
  return {
239
253
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot save decision." }],
@@ -289,12 +303,7 @@ export default function (pi) {
289
303
  supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
290
304
  }),
291
305
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
292
- let dbAvailable = false;
293
- try {
294
- const db = await import("./gsd-db.js");
295
- dbAvailable = db.isDbAvailable();
296
- }
297
- catch { /* dynamic import failed */ }
306
+ const dbAvailable = await ensureDbOpen();
298
307
  if (!dbAvailable) {
299
308
  return {
300
309
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot update requirement." }],
@@ -364,12 +373,7 @@ export default function (pi) {
364
373
  content: Type.String({ description: "The full markdown content of the artifact" }),
365
374
  }),
366
375
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
367
- let dbAvailable = false;
368
- try {
369
- const db = await import("./gsd-db.js");
370
- dbAvailable = db.isDbAvailable();
371
- }
372
- catch { /* dynamic import failed */ }
376
+ const dbAvailable = await ensureDbOpen();
373
377
  if (!dbAvailable) {
374
378
  return {
375
379
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot save artifact." }],
@@ -2,7 +2,7 @@
2
2
  // Pure functions that take file content (string) and return typed data.
3
3
  // Zero Pi dependencies — uses only exported helpers from files.ts.
4
4
  import { splitFrontmatter, parseFrontmatterMap, extractBoldField } from '../files.js';
5
- import { normalizeStringArray } from '../../shared/mod.js';
5
+ import { normalizeStringArray } from '../../shared/format-utils.js';
6
6
  // Re-export PlanningProjectMeta — not in types.ts yet, use string for project field
7
7
  // Actually PlanningProjectMeta isn't in types.ts — project is stored as string | null.
8
8
  // We'll keep parseOldProject returning a simple shape.
@@ -5,11 +5,13 @@
5
5
  * `~/.gsd/projects/<hash>/` state directory. After migration, a
6
6
  * symlink replaces the original directory so all paths remain valid.
7
7
  */
8
+ import { execFileSync } from "node:child_process";
8
9
  import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
9
10
  import { join } from "node:path";
10
11
  import { externalGsdRoot } from "./repo-identity.js";
11
12
  import { getErrorMessage } from "./error-utils.js";
12
13
  import { hasGitTrackedGsdFiles } from "./gitignore.js";
14
+ import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
13
15
  /**
14
16
  * Migrate a legacy in-project `.gsd/` directory to external storage.
15
17
  *
@@ -142,7 +144,22 @@ export function migrateToExternalState(basePath) {
142
144
  catch { /* best-effort restore */ }
143
145
  return { migrated: false, error: `Migration verification failed: ${getErrorMessage(verifyErr)}` };
144
146
  }
145
- // Remove .gsd.migrating only after symlink is verified
147
+ // Clean the git index — any .gsd/* files tracked before migration now
148
+ // sit behind the symlink and git can't follow it, causing them to show
149
+ // as deleted. Remove them from the index so the working tree stays clean.
150
+ // --ignore-unmatch makes this a no-op on fresh projects with no tracked .gsd/.
151
+ try {
152
+ execFileSync("git", ["rm", "-r", "--cached", "--ignore-unmatch", ".gsd"], {
153
+ cwd: basePath,
154
+ stdio: ["ignore", "pipe", "ignore"],
155
+ env: GIT_NO_PROMPT_ENV,
156
+ timeout: 10_000,
157
+ });
158
+ }
159
+ catch {
160
+ // Non-fatal — git may be unavailable or nothing was tracked
161
+ }
162
+ // Remove .gsd.migrating only after symlink is verified and index is clean
146
163
  rmSync(migratingPath, { recursive: true, force: true });
147
164
  return { migrated: true };
148
165
  }
@@ -518,6 +518,43 @@ export function nativeAddAll(basePath) {
518
518
  }
519
519
  gitFileExec(basePath, ["add", "-A"]);
520
520
  }
521
+ /**
522
+ * Stage all files with pathspec exclusions (git add -A -- ':!pattern' ...).
523
+ * Excluded paths are never hashed by git, preventing hangs on large
524
+ * untracked artifact trees (57GB+, 11K+ files). See #1605.
525
+ *
526
+ * Falls back to plain `git add -A` when no exclusions are provided.
527
+ * Always uses the CLI path (not libgit2) because libgit2's add_all
528
+ * does not support pathspec exclusion syntax.
529
+ *
530
+ * When excluded paths are already covered by .gitignore, git may exit
531
+ * with code 1 and an "ignored by .gitignore" warning. This is harmless
532
+ * (the staging succeeds for all non-ignored files) and is suppressed.
533
+ */
534
+ export function nativeAddAllWithExclusions(basePath, exclusions) {
535
+ if (exclusions.length === 0) {
536
+ nativeAddAll(basePath);
537
+ return;
538
+ }
539
+ const pathspecs = exclusions.map(e => `:!${e}`);
540
+ try {
541
+ execFileSync("git", ["add", "-A", "--", ...pathspecs], {
542
+ cwd: basePath,
543
+ stdio: ["ignore", "pipe", "pipe"],
544
+ encoding: "utf-8",
545
+ env: GIT_NO_PROMPT_ENV,
546
+ });
547
+ }
548
+ catch (err) {
549
+ // git exits 1 when pathspec exclusions reference paths already covered
550
+ // by .gitignore. The staging itself succeeds — only suppress that case.
551
+ const stderr = err?.stderr ?? "";
552
+ if (stderr.includes("ignored by one of your .gitignore files")) {
553
+ return;
554
+ }
555
+ throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
556
+ }
557
+ }
521
558
  /**
522
559
  * Stage specific files.
523
560
  * Native: libgit2 index add.
@@ -5,7 +5,7 @@
5
5
  "type": "module",
6
6
  "pi": {
7
7
  "extensions": [
8
- "./index.ts"
8
+ "./index.js"
9
9
  ]
10
10
  }
11
11
  }
@@ -343,6 +343,9 @@ function probeGsdRoot(rawBasePath) {
343
343
  export function milestonesDir(basePath) {
344
344
  return join(gsdRoot(basePath), "milestones");
345
345
  }
346
+ export function resolveRuntimeFile(basePath) {
347
+ return join(gsdRoot(basePath), "RUNTIME.md");
348
+ }
346
349
  export function resolveGsdRootFile(basePath, key) {
347
350
  const root = gsdRoot(basePath);
348
351
  const canonical = join(root, GSD_ROOT_FILES[key]);
@@ -260,18 +260,6 @@ export function resolveInlineLevel() {
260
260
  case "quality": return "full";
261
261
  }
262
262
  }
263
- /**
264
- * Resolve the compression strategy from the active token profile.
265
- * budget/balanced -> "compress", quality -> "truncate".
266
- * Explicit preference always wins.
267
- */
268
- export function resolveCompressionStrategy() {
269
- const prefs = loadEffectiveGSDPreferences();
270
- if (prefs?.preferences.compression_strategy)
271
- return prefs.preferences.compression_strategy;
272
- const profile = resolveEffectiveProfile();
273
- return profile === "quality" ? "truncate" : "compress";
274
- }
275
263
  /**
276
264
  * Resolve the context selection mode from the active token profile.
277
265
  * budget -> "smart", balanced/quality -> "full".
@@ -62,10 +62,10 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
62
62
  "verification_auto_fix",
63
63
  "verification_max_retries",
64
64
  "search_provider",
65
- "compression_strategy",
66
65
  "context_selection",
67
66
  "widget_mode",
68
67
  "reactive_execution",
68
+ "github",
69
69
  ]);
70
70
  /** Canonical list of all dispatch unit types. */
71
71
  export const KNOWN_UNIT_TYPES = [
@@ -6,7 +6,7 @@
6
6
  * together with any errors and warnings.
7
7
  */
8
8
  import { VALID_BRANCH_NAME } from "./git-service.js";
9
- import { normalizeStringArray } from "../shared/mod.js";
9
+ import { normalizeStringArray } from "../shared/format-utils.js";
10
10
  import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_TYPES, SKILL_ACTIONS, } from "./preferences-types.js";
11
11
  const VALID_TOKEN_PROFILES = new Set(["budget", "balanced", "quality"]);
12
12
  export function validatePreferences(preferences) {
@@ -707,16 +707,6 @@ export function validatePreferences(preferences) {
707
707
  errors.push("auto_report must be a boolean");
708
708
  }
709
709
  }
710
- // ─── Compression Strategy ───────────────────────────────────────────
711
- if (preferences.compression_strategy !== undefined) {
712
- const validStrategies = new Set(["truncate", "compress"]);
713
- if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
714
- validated.compression_strategy = preferences.compression_strategy;
715
- }
716
- else {
717
- errors.push(`compression_strategy must be one of: truncate, compress`);
718
- }
719
- }
720
710
  // ─── Context Selection ──────────────────────────────────────────────
721
711
  if (preferences.context_selection !== undefined) {
722
712
  const validModes = new Set(["full", "smart"]);
@@ -727,5 +717,63 @@ export function validatePreferences(preferences) {
727
717
  errors.push(`context_selection must be one of: full, smart`);
728
718
  }
729
719
  }
720
+ // ─── GitHub Sync ────────────────────────────────────────────────────────
721
+ if (preferences.github !== undefined) {
722
+ if (typeof preferences.github === "object" && preferences.github !== null) {
723
+ const gh = preferences.github;
724
+ const validGh = {};
725
+ if (gh.enabled !== undefined) {
726
+ if (typeof gh.enabled === "boolean")
727
+ validGh.enabled = gh.enabled;
728
+ else
729
+ errors.push("github.enabled must be a boolean");
730
+ }
731
+ if (gh.repo !== undefined) {
732
+ if (typeof gh.repo === "string" && gh.repo.includes("/"))
733
+ validGh.repo = gh.repo;
734
+ else
735
+ errors.push('github.repo must be a string in "owner/repo" format');
736
+ }
737
+ if (gh.project !== undefined) {
738
+ const p = typeof gh.project === "number" ? gh.project : Number(gh.project);
739
+ if (Number.isFinite(p) && p > 0)
740
+ validGh.project = Math.floor(p);
741
+ else
742
+ errors.push("github.project must be a positive number");
743
+ }
744
+ if (gh.labels !== undefined) {
745
+ if (Array.isArray(gh.labels) && gh.labels.every((l) => typeof l === "string")) {
746
+ validGh.labels = gh.labels;
747
+ }
748
+ else {
749
+ errors.push("github.labels must be an array of strings");
750
+ }
751
+ }
752
+ if (gh.auto_link_commits !== undefined) {
753
+ if (typeof gh.auto_link_commits === "boolean")
754
+ validGh.auto_link_commits = gh.auto_link_commits;
755
+ else
756
+ errors.push("github.auto_link_commits must be a boolean");
757
+ }
758
+ if (gh.slice_prs !== undefined) {
759
+ if (typeof gh.slice_prs === "boolean")
760
+ validGh.slice_prs = gh.slice_prs;
761
+ else
762
+ errors.push("github.slice_prs must be a boolean");
763
+ }
764
+ const knownGhKeys = new Set(["enabled", "repo", "project", "labels", "auto_link_commits", "slice_prs"]);
765
+ for (const key of Object.keys(gh)) {
766
+ if (!knownGhKeys.has(key)) {
767
+ warnings.push(`unknown github key "${key}" — ignored`);
768
+ }
769
+ }
770
+ if (Object.keys(validGh).length > 0) {
771
+ validated.github = validGh;
772
+ }
773
+ }
774
+ else {
775
+ errors.push("github must be an object");
776
+ }
777
+ }
730
778
  return { preferences: validated, errors, warnings };
731
779
  }
@@ -14,7 +14,7 @@ import { homedir } from "node:os";
14
14
  import { join } from "node:path";
15
15
  import { gsdRoot } from "./paths.js";
16
16
  import { parse as parseYaml } from "yaml";
17
- import { normalizeStringArray } from "../shared/mod.js";
17
+ import { normalizeStringArray } from "../shared/format-utils.js";
18
18
  import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
19
19
  import { MODE_DEFAULTS, } from "./preferences-types.js";
20
20
  import { validatePreferences } from "./preferences-validation.js";
@@ -24,33 +24,42 @@ export { validatePreferences } from "./preferences-validation.js";
24
24
  // ─── Re-exports: skills ─────────────────────────────────────────────────────
25
25
  export { resolveAllSkillReferences, resolveSkillDiscoveryMode, resolveSkillStalenessDays, } from "./preferences-skills.js";
26
26
  // ─── Re-exports: models ─────────────────────────────────────────────────────
27
- export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveCompressionStrategy, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
27
+ export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
28
28
  // ─── Path Constants & Getters ───────────────────────────────────────────────
29
- const GLOBAL_PREFERENCES_PATH = join(homedir(), ".gsd", "preferences.md");
30
- const LEGACY_GLOBAL_PREFERENCES_PATH = join(homedir(), ".pi", "agent", "gsd-preferences.md");
29
+ function gsdHome() {
30
+ return process.env.GSD_HOME || join(homedir(), ".gsd");
31
+ }
32
+ function globalPreferencesPath() {
33
+ return join(gsdHome(), "preferences.md");
34
+ }
35
+ function legacyGlobalPreferencesPath() {
36
+ return join(homedir(), ".pi", "agent", "gsd-preferences.md");
37
+ }
31
38
  function projectPreferencesPath() {
32
39
  return join(gsdRoot(process.cwd()), "preferences.md");
33
40
  }
34
41
  // Bootstrap in gitignore.ts historically created PREFERENCES.md (uppercase) by mistake.
35
42
  // Check uppercase as a fallback so those files aren't silently ignored.
36
- const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(homedir(), ".gsd", "PREFERENCES.md");
43
+ function globalPreferencesPathUppercase() {
44
+ return join(gsdHome(), "PREFERENCES.md");
45
+ }
37
46
  function projectPreferencesPathUppercase() {
38
47
  return join(gsdRoot(process.cwd()), "PREFERENCES.md");
39
48
  }
40
49
  export function getGlobalGSDPreferencesPath() {
41
- return GLOBAL_PREFERENCES_PATH;
50
+ return globalPreferencesPath();
42
51
  }
43
52
  export function getLegacyGlobalGSDPreferencesPath() {
44
- return LEGACY_GLOBAL_PREFERENCES_PATH;
53
+ return legacyGlobalPreferencesPath();
45
54
  }
46
55
  export function getProjectGSDPreferencesPath() {
47
56
  return projectPreferencesPath();
48
57
  }
49
58
  // ─── Loading ────────────────────────────────────────────────────────────────
50
59
  export function loadGlobalGSDPreferences() {
51
- return loadPreferencesFile(GLOBAL_PREFERENCES_PATH, "global")
52
- ?? loadPreferencesFile(GLOBAL_PREFERENCES_PATH_UPPERCASE, "global")
53
- ?? loadPreferencesFile(LEGACY_GLOBAL_PREFERENCES_PATH, "global");
60
+ return loadPreferencesFile(globalPreferencesPath(), "global")
61
+ ?? loadPreferencesFile(globalPreferencesPathUppercase(), "global")
62
+ ?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
54
63
  }
55
64
  export function loadProjectGSDPreferences() {
56
65
  return loadPreferencesFile(projectPreferencesPath(), "project")
@@ -199,10 +208,12 @@ function mergePreferences(base, override) {
199
208
  verification_auto_fix: override.verification_auto_fix ?? base.verification_auto_fix,
200
209
  verification_max_retries: override.verification_max_retries ?? base.verification_max_retries,
201
210
  search_provider: override.search_provider ?? base.search_provider,
202
- compression_strategy: override.compression_strategy ?? base.compression_strategy,
203
211
  context_selection: override.context_selection ?? base.context_selection,
204
212
  auto_visualize: override.auto_visualize ?? base.auto_visualize,
205
213
  auto_report: override.auto_report ?? base.auto_report,
214
+ github: (base.github || override.github)
215
+ ? { ...(base.github ?? {}), ...(override.github ?? {}) }
216
+ : undefined,
206
217
  };
207
218
  }
208
219
  function mergeStringLists(base, override) {