gsd-pi 2.19.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
  8. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  9. package/dist/resources/extensions/gsd/auto.ts +424 -30
  10. package/dist/resources/extensions/gsd/commands.ts +518 -36
  11. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  12. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  13. package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  14. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  15. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  16. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  17. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  20. package/dist/resources/extensions/gsd/export.ts +81 -2
  21. package/dist/resources/extensions/gsd/files.ts +39 -9
  22. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  23. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  24. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  25. package/dist/resources/extensions/gsd/history.ts +0 -1
  26. package/dist/resources/extensions/gsd/index.ts +277 -1
  27. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  28. package/dist/resources/extensions/gsd/metrics.ts +39 -3
  29. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  31. package/dist/resources/extensions/gsd/preferences.ts +125 -150
  32. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  33. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  35. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  36. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  37. package/dist/resources/extensions/gsd/quick.ts +156 -0
  38. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  39. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  40. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  41. package/dist/resources/extensions/gsd/state.ts +30 -0
  42. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  43. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  44. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  45. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  46. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  48. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  49. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  50. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  51. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  52. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  53. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  54. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  55. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  56. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  57. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  58. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  59. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  60. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  61. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  62. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  63. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  64. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  65. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  66. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  67. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  68. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  69. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  70. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  71. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  72. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  73. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  74. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  75. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  76. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  77. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  78. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  79. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  80. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  81. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  82. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  83. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  84. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  85. package/dist/resources/extensions/gsd/types.ts +29 -0
  86. package/dist/resources/extensions/gsd/undo.ts +0 -1
  87. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  88. package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
  89. package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  90. package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
  91. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  92. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  93. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  94. package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  95. package/dist/resources/extensions/remote-questions/format.ts +154 -8
  96. package/dist/resources/extensions/remote-questions/manager.ts +9 -7
  97. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  98. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  99. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  100. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  101. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  102. package/dist/resources/extensions/voice/index.ts +4 -3
  103. package/package.json +1 -1
  104. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  106. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  109. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  113. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  117. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  119. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  120. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  122. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  124. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  126. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  128. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  130. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  133. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  136. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  138. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  141. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  143. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  144. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  145. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  146. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  147. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  148. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  149. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  150. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  151. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  152. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  153. package/src/resources/extensions/google-search/index.ts +164 -47
  154. package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
  155. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  156. package/src/resources/extensions/gsd/auto.ts +424 -30
  157. package/src/resources/extensions/gsd/commands.ts +518 -36
  158. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  159. package/src/resources/extensions/gsd/context-store.ts +195 -0
  160. package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  161. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  162. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  163. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  164. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  165. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  166. package/src/resources/extensions/gsd/doctor.ts +283 -2
  167. package/src/resources/extensions/gsd/export.ts +81 -2
  168. package/src/resources/extensions/gsd/files.ts +39 -9
  169. package/src/resources/extensions/gsd/git-service.ts +6 -0
  170. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  171. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  172. package/src/resources/extensions/gsd/history.ts +0 -1
  173. package/src/resources/extensions/gsd/index.ts +277 -1
  174. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  175. package/src/resources/extensions/gsd/metrics.ts +39 -3
  176. package/src/resources/extensions/gsd/notifications.ts +0 -1
  177. package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  178. package/src/resources/extensions/gsd/preferences.ts +125 -150
  179. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  180. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  181. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  182. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  183. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  184. package/src/resources/extensions/gsd/quick.ts +156 -0
  185. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  186. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  187. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  188. package/src/resources/extensions/gsd/state.ts +30 -0
  189. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  190. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  191. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  193. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  194. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  195. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  196. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  197. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  198. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  199. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  200. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  201. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  202. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  203. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  204. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  205. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  206. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  207. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  208. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  209. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  211. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  212. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  213. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  214. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  216. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  217. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  218. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  219. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  220. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  221. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  222. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  223. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  224. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  225. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  226. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  227. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  228. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  229. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  230. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  231. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  232. package/src/resources/extensions/gsd/types.ts +29 -0
  233. package/src/resources/extensions/gsd/undo.ts +0 -1
  234. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  235. package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
  236. package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  237. package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
  238. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  239. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  240. package/src/resources/extensions/remote-questions/config.ts +4 -2
  241. package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  242. package/src/resources/extensions/remote-questions/format.ts +154 -8
  243. package/src/resources/extensions/remote-questions/manager.ts +9 -7
  244. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  245. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  246. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  247. package/src/resources/extensions/remote-questions/types.ts +2 -1
  248. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  249. package/src/resources/extensions/voice/index.ts +4 -3
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Tests for model config isolation between concurrent instances (#650).
3
+ */
4
+
5
+ import { describe, it, beforeEach, afterEach } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+
11
+ // ─── Test helpers ─────────────────────────────────────────────────────────────
12
+
13
+ function makeTmpDir(suffix: string): string {
14
+ const dir = join(tmpdir(), `gsd-test-650-${suffix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
15
+ mkdirSync(dir, { recursive: true });
16
+ return dir;
17
+ }
18
+
19
+ // ─── Settings Manager Model Scoping ───────────────────────────────────────────
20
+
21
+ describe("model config isolation (#650)", () => {
22
+ let tmpGlobal: string;
23
+ let tmpProjectA: string;
24
+ let tmpProjectB: string;
25
+
26
+ beforeEach(() => {
27
+ tmpGlobal = makeTmpDir("global");
28
+ tmpProjectA = makeTmpDir("project-a");
29
+ tmpProjectB = makeTmpDir("project-b");
30
+ // Create .pi directories for project settings
31
+ mkdirSync(join(tmpProjectA, ".pi"), { recursive: true });
32
+ mkdirSync(join(tmpProjectB, ".pi"), { recursive: true });
33
+ });
34
+
35
+ afterEach(() => {
36
+ try { rmSync(tmpGlobal, { recursive: true, force: true }); } catch {}
37
+ try { rmSync(tmpProjectA, { recursive: true, force: true }); } catch {}
38
+ try { rmSync(tmpProjectB, { recursive: true, force: true }); } catch {}
39
+ });
40
+
41
+ it("project settings file isolates model from global", async () => {
42
+ // Write project settings for project A
43
+ const projectSettingsPath = join(tmpProjectA, ".pi", "settings.json");
44
+ writeFileSync(projectSettingsPath, JSON.stringify({
45
+ defaultProvider: "anthropic",
46
+ defaultModel: "claude-opus-4-6",
47
+ }));
48
+
49
+ // Write global settings with a different model
50
+ const globalSettingsPath = join(tmpGlobal, "settings.json");
51
+ writeFileSync(globalSettingsPath, JSON.stringify({
52
+ defaultProvider: "openai",
53
+ defaultModel: "gpt-5.4",
54
+ }));
55
+
56
+ // Verify project settings exist and have independent data
57
+ const projectData = JSON.parse(readFileSync(projectSettingsPath, "utf-8"));
58
+ const globalData = JSON.parse(readFileSync(globalSettingsPath, "utf-8"));
59
+
60
+ assert.equal(projectData.defaultModel, "claude-opus-4-6");
61
+ assert.equal(globalData.defaultModel, "gpt-5.4");
62
+ assert.notEqual(projectData.defaultModel, globalData.defaultModel,
63
+ "Project and global should have different models");
64
+ });
65
+
66
+ it("two projects have independent model configs", () => {
67
+ const settingsA = join(tmpProjectA, ".pi", "settings.json");
68
+ const settingsB = join(tmpProjectB, ".pi", "settings.json");
69
+
70
+ writeFileSync(settingsA, JSON.stringify({
71
+ defaultProvider: "anthropic",
72
+ defaultModel: "claude-opus-4-6",
73
+ }));
74
+ writeFileSync(settingsB, JSON.stringify({
75
+ defaultProvider: "openai-codex",
76
+ defaultModel: "gpt-5.4",
77
+ }));
78
+
79
+ const dataA = JSON.parse(readFileSync(settingsA, "utf-8"));
80
+ const dataB = JSON.parse(readFileSync(settingsB, "utf-8"));
81
+
82
+ assert.equal(dataA.defaultModel, "claude-opus-4-6");
83
+ assert.equal(dataB.defaultModel, "gpt-5.4");
84
+ assert.notEqual(dataA.defaultProvider, dataB.defaultProvider);
85
+ });
86
+
87
+ it("autoModeStartModel concept prevents model drift", () => {
88
+ // Simulate the auto-mode start model capture pattern
89
+ const autoModeStartModel = { provider: "anthropic", id: "claude-opus-4-6" };
90
+
91
+ // Simulate another instance writing to global settings
92
+ const globalSettings = { defaultProvider: "openai-codex", defaultModel: "gpt-5.4" };
93
+
94
+ // The captured model should be used, not the global settings
95
+ assert.notEqual(autoModeStartModel.id, globalSettings.defaultModel);
96
+ assert.equal(autoModeStartModel.id, "claude-opus-4-6",
97
+ "Captured model should be preserved regardless of global settings changes");
98
+ });
99
+ });
@@ -1661,4 +1661,44 @@ console.log('\n=== LLM round-trip: extra blank lines ===');
1661
1661
  assertTrue(consecutiveBlanks === null, 'blank-lines: formatted output has no 4+ consecutive newlines');
1662
1662
  }
1663
1663
 
1664
+ // ═══════════════════════════════════════════════════════════════════════════
1665
+ // parseRoadmap: boundary map with embedded code fences (#468)
1666
+ // ═══════════════════════════════════════════════════════════════════════════
1667
+
1668
+ console.log('\n=== parseRoadmap: boundary map with code fences (#468) ===');
1669
+ {
1670
+ const content = `# M001: Test
1671
+
1672
+ **Vision:** Test
1673
+
1674
+ ## Slices
1675
+
1676
+ - [ ] **S01: Core** \`risk:low\` \`depends:[]\`
1677
+ - [ ] **S02: API** \`risk:low\` \`depends:[S01]\`
1678
+
1679
+ ## Boundary Map
1680
+
1681
+ ### S01 → S02
1682
+
1683
+ Produces:
1684
+ types.ts — all types
1685
+ \`\`\`
1686
+ const x = 1;
1687
+ \`\`\`
1688
+
1689
+ Consumes: nothing
1690
+ `;
1691
+
1692
+ // This test ensures the boundary map parser does not hang or
1693
+ // catastrophically backtrack when content contains code fences.
1694
+ const start = Date.now();
1695
+ const r = parseRoadmap(content);
1696
+ const elapsed = Date.now() - start;
1697
+
1698
+ assertTrue(elapsed < 1000, `boundary map with code fences parsed in ${elapsed}ms (should be < 1s)`);
1699
+ assertEq(r.slices.length, 2, 'code-fence roadmap: slice count');
1700
+ // Boundary map should still parse (may not capture perfectly with code fences, but must not hang)
1701
+ assertTrue(r.boundaryMap.length >= 0, 'code-fence roadmap: boundary map parsed without hanging');
1702
+ }
1703
+
1664
1704
  report();
@@ -1,5 +1,4 @@
1
1
  // GSD Extension — Hook Engine Tests (Post-Unit, Pre-Dispatch, State Persistence)
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "node:fs";
5
4
  import { join } from "node:path";
@@ -18,6 +17,7 @@ import {
18
17
  clearPersistedHookState,
19
18
  getHookStatus,
20
19
  formatHookStatus,
20
+ triggerHookManually,
21
21
  } from "../post-unit-hooks.ts";
22
22
 
23
23
  const { assertEq, assertTrue, assertMatch, report } = createTestContext();
@@ -294,4 +294,44 @@ console.log("\n=== Hook status: no hooks ===");
294
294
  assertMatch(formatted, /No hooks configured/, "status message says no hooks");
295
295
  }
296
296
 
297
+ // ═══════════════════════════════════════════════════════════════════════════
298
+ // Phase 4: Manual Hook Trigger Tests
299
+ // ═══════════════════════════════════════════════════════════════════════════
300
+
301
+ console.log("\n=== triggerHookManually: hook not found ===");
302
+
303
+ {
304
+ resetHookState();
305
+ const base = createFixtureBase();
306
+ try {
307
+ const result = triggerHookManually("nonexistent-hook", "execute-task", "M001/S01/T01", base);
308
+ assertEq(result, null, "returns null when hook not found");
309
+ } finally {
310
+ rmSync(base, { recursive: true, force: true });
311
+ }
312
+ }
313
+
314
+ console.log("\n=== triggerHookManually: with configured hook ===");
315
+
316
+ {
317
+ resetHookState();
318
+ const base = createFixtureBase();
319
+ try {
320
+ // This test will work when preferences are configured
321
+ // For now, just verify the function exists and handles missing hooks
322
+ const result = triggerHookManually("code-review", "execute-task", "M001/S01/T01", base);
323
+ // Result depends on whether code-review hook is configured in preferences
324
+ // The function should either return null or a valid HookDispatchResult
325
+ assertTrue(result === null || typeof result === "object", "returns null or object");
326
+ if (result) {
327
+ assertEq(result.hookName, "code-review", "hook name in result");
328
+ assertEq(result.unitType, "hook/code-review", "unit type is hook-prefixed");
329
+ assertEq(result.unitId, "M001/S01/T01", "unit ID preserved");
330
+ assertTrue(typeof result.prompt === "string", "prompt is a string");
331
+ }
332
+ } finally {
333
+ rmSync(base, { recursive: true, force: true });
334
+ }
335
+ }
336
+
297
337
  report();
@@ -1,5 +1,4 @@
1
1
  // GSD Git Preferences Tests — validates git.isolation and git.merge_to_main handling
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { createTestContext } from "./test-helpers.ts";
5
4
  import { validatePreferences } from "../preferences.ts";
@@ -1,5 +1,4 @@
1
1
  // GSD Extension — Hook Preferences Parsing Tests (Post-Unit + Pre-Dispatch)
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { createTestContext } from "./test-helpers.ts";
5
4
  import type { PreDispatchHookConfig } from "../types.ts";
@@ -0,0 +1,110 @@
1
+ // GSD Workflow Mode Tests — validates mode defaults, overrides, and validation
2
+
3
+ import { createTestContext } from "./test-helpers.ts";
4
+ import { validatePreferences, applyModeDefaults } from "../preferences.ts";
5
+ import type { GSDPreferences } from "../preferences.ts";
6
+
7
+ const { assertEq, assertTrue, report } = createTestContext();
8
+
9
+ async function main(): Promise<void> {
10
+ console.log("\n=== mode: solo defaults ===");
11
+
12
+ {
13
+ const prefs: GSDPreferences = { mode: "solo" };
14
+ const result = applyModeDefaults("solo", prefs);
15
+ assertEq(result.git?.auto_push, true, "solo — auto_push defaults to true");
16
+ assertEq(result.git?.push_branches, false, "solo — push_branches defaults to false");
17
+ assertEq(result.git?.pre_merge_check, false, "solo — pre_merge_check defaults to false");
18
+ assertEq(result.git?.merge_strategy, "squash", "solo — merge_strategy defaults to squash");
19
+ assertEq(result.git?.isolation, "worktree", "solo — isolation defaults to worktree");
20
+ assertEq(result.git?.commit_docs, true, "solo — commit_docs defaults to true");
21
+ assertEq(result.unique_milestone_ids, false, "solo — unique_milestone_ids defaults to false");
22
+ }
23
+
24
+ console.log("\n=== mode: team defaults ===");
25
+
26
+ {
27
+ const prefs: GSDPreferences = { mode: "team" };
28
+ const result = applyModeDefaults("team", prefs);
29
+ assertEq(result.git?.auto_push, false, "team — auto_push defaults to false");
30
+ assertEq(result.git?.push_branches, true, "team — push_branches defaults to true");
31
+ assertEq(result.git?.pre_merge_check, true, "team — pre_merge_check defaults to true");
32
+ assertEq(result.git?.merge_strategy, "squash", "team — merge_strategy defaults to squash");
33
+ assertEq(result.git?.isolation, "worktree", "team — isolation defaults to worktree");
34
+ assertEq(result.git?.commit_docs, true, "team — commit_docs defaults to true");
35
+ assertEq(result.unique_milestone_ids, true, "team — unique_milestone_ids defaults to true");
36
+ }
37
+
38
+ console.log("\n=== explicit override wins over mode default ===");
39
+
40
+ {
41
+ const prefs: GSDPreferences = {
42
+ mode: "solo",
43
+ git: { auto_push: false },
44
+ };
45
+ const result = applyModeDefaults("solo", prefs);
46
+ assertEq(result.git?.auto_push, false, "solo + explicit auto_push=false — override wins");
47
+ assertEq(result.git?.push_branches, false, "solo + override — other defaults still apply");
48
+ assertEq(result.git?.merge_strategy, "squash", "solo + override — merge_strategy still defaults");
49
+ }
50
+
51
+ console.log("\n=== no mode set — no defaults injected ===");
52
+
53
+ {
54
+ const prefs: GSDPreferences = { git: { auto_push: true } };
55
+ const { preferences } = validatePreferences(prefs);
56
+ assertEq(preferences.mode, undefined, "no mode — mode is undefined");
57
+ assertEq(preferences.git?.push_branches, undefined, "no mode — push_branches not injected");
58
+ assertEq(preferences.unique_milestone_ids, undefined, "no mode — unique_milestone_ids not injected");
59
+ }
60
+
61
+ console.log("\n=== invalid mode value → validation error ===");
62
+
63
+ {
64
+ const { errors } = validatePreferences({ mode: "invalid" as any });
65
+ assertTrue(errors.length > 0, "invalid mode — produces error");
66
+ assertTrue(errors[0].includes("solo, team"), "invalid mode — error mentions valid values");
67
+ }
68
+
69
+ console.log("\n=== valid mode values pass validation ===");
70
+
71
+ {
72
+ const { errors: soloErrors, preferences: soloPrefs } = validatePreferences({ mode: "solo" });
73
+ assertEq(soloErrors.length, 0, "mode: solo — no errors");
74
+ assertEq(soloPrefs.mode, "solo", "mode: solo — value preserved");
75
+ }
76
+ {
77
+ const { errors: teamErrors, preferences: teamPrefs } = validatePreferences({ mode: "team" });
78
+ assertEq(teamErrors.length, 0, "mode: team — no errors");
79
+ assertEq(teamPrefs.mode, "team", "mode: team — value preserved");
80
+ }
81
+
82
+ console.log("\n=== deep merge: mode + explicit git.remote ===");
83
+
84
+ {
85
+ const prefs: GSDPreferences = {
86
+ mode: "team",
87
+ git: { remote: "upstream" },
88
+ };
89
+ const result = applyModeDefaults("team", prefs);
90
+ assertEq(result.git?.remote, "upstream", "team + git.remote — custom remote preserved");
91
+ assertEq(result.git?.auto_push, false, "team + git.remote — team auto_push default applied");
92
+ assertEq(result.git?.push_branches, true, "team + git.remote — team push_branches default applied");
93
+ }
94
+
95
+ console.log("\n=== mode + unique_milestone_ids explicit override ===");
96
+
97
+ {
98
+ const prefs: GSDPreferences = {
99
+ mode: "team",
100
+ unique_milestone_ids: false,
101
+ };
102
+ const result = applyModeDefaults("team", prefs);
103
+ assertEq(result.unique_milestone_ids, false, "team + explicit unique_milestone_ids=false — override wins");
104
+ assertEq(result.git?.push_branches, true, "team + override — other team defaults still apply");
105
+ }
106
+
107
+ report();
108
+ }
109
+
110
+ main();
@@ -1,5 +1,4 @@
1
1
  // GSD Extension — Model Preferences Parsing Tests
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import test from "node:test";
5
4
  import assert from "node:assert/strict";