gsd-pi 2.16.0 → 2.18.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 (225) hide show
  1. package/README.md +39 -0
  2. package/dist/onboarding.js +2 -2
  3. package/dist/remote-questions-config.d.ts +10 -0
  4. package/dist/remote-questions-config.js +36 -0
  5. package/dist/resources/extensions/gsd/activity-log.ts +37 -7
  6. package/dist/resources/extensions/gsd/auto-dashboard.ts +4 -0
  7. package/dist/resources/extensions/gsd/auto-dispatch.ts +9 -3
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +91 -42
  9. package/dist/resources/extensions/gsd/auto-recovery.ts +7 -2
  10. package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
  11. package/dist/resources/extensions/gsd/auto.ts +177 -25
  12. package/dist/resources/extensions/gsd/commands.ts +264 -23
  13. package/dist/resources/extensions/gsd/complexity.ts +236 -0
  14. package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
  15. package/dist/resources/extensions/gsd/docs/preferences-reference.md +202 -2
  16. package/dist/resources/extensions/gsd/files.ts +129 -3
  17. package/dist/resources/extensions/gsd/git-service.ts +19 -8
  18. package/dist/resources/extensions/gsd/gitignore.ts +41 -2
  19. package/dist/resources/extensions/gsd/guided-flow.ts +247 -10
  20. package/dist/resources/extensions/gsd/index.ts +47 -3
  21. package/dist/resources/extensions/gsd/metrics.ts +44 -0
  22. package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -0
  23. package/dist/resources/extensions/gsd/native-parser-bridge.ts +5 -0
  24. package/dist/resources/extensions/gsd/paths.ts +9 -0
  25. package/dist/resources/extensions/gsd/preferences.ts +181 -2
  26. package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
  27. package/dist/resources/extensions/gsd/prompts/system.md +2 -0
  28. package/dist/resources/extensions/gsd/queue-order.ts +231 -0
  29. package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
  30. package/dist/resources/extensions/gsd/routing-history.ts +290 -0
  31. package/dist/resources/extensions/gsd/state.ts +15 -3
  32. package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
  33. package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
  34. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
  35. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
  36. package/dist/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
  37. package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
  38. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
  39. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
  40. package/dist/resources/extensions/gsd/tests/git-service.test.ts +132 -0
  41. package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
  42. package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
  43. package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
  44. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
  45. package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
  46. package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
  48. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
  49. package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
  50. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
  51. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
  52. package/dist/resources/extensions/gsd/types.ts +28 -0
  53. package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
  54. package/dist/resources/extensions/gsd/worktree.ts +24 -2
  55. package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
  56. package/package.json +1 -1
  57. package/packages/pi-ai/dist/models.generated.d.ts +493 -13
  58. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  59. package/packages/pi-ai/dist/models.generated.js +422 -62
  60. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  61. package/packages/pi-ai/dist/providers/google-shared.d.ts +12 -0
  62. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  63. package/packages/pi-ai/dist/providers/google-shared.js +9 -22
  64. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  65. package/packages/pi-ai/dist/providers/google-shared.test.d.ts +2 -0
  66. package/packages/pi-ai/dist/providers/google-shared.test.d.ts.map +1 -0
  67. package/packages/pi-ai/dist/providers/google-shared.test.js +125 -0
  68. package/packages/pi-ai/dist/providers/google-shared.test.js.map +1 -0
  69. package/packages/pi-ai/src/models.generated.ts +422 -62
  70. package/packages/pi-ai/src/providers/google-shared.test.ts +137 -0
  71. package/packages/pi-ai/src/providers/google-shared.ts +10 -19
  72. package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
  73. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  74. package/packages/pi-coding-agent/dist/cli/args.js +21 -0
  75. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
  77. package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
  79. package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
  81. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
  83. package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
  85. package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
  87. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
  89. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
  91. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
  93. package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
  94. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
  95. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
  96. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
  99. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
  101. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
  103. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
  105. package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
  107. package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
  109. package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
  110. package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
  111. package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
  112. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
  113. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  115. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  118. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +7 -7
  120. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +209 -13
  122. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts +2 -0
  124. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +67 -0
  126. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -0
  127. package/packages/pi-coding-agent/dist/index.d.ts +5 -1
  128. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/index.js +4 -1
  130. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/main.js +17 -2
  133. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +32 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  151. package/packages/pi-coding-agent/src/cli/args.ts +21 -0
  152. package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
  153. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
  154. package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
  155. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
  156. package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
  157. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
  158. package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
  159. package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
  160. package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
  161. package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
  162. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  163. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +85 -0
  164. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +245 -17
  165. package/packages/pi-coding-agent/src/index.ts +5 -0
  166. package/packages/pi-coding-agent/src/main.ts +19 -2
  167. package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
  168. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
  169. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
  170. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
  171. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +13 -0
  172. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  173. package/pkg/dist/modes/interactive/theme/theme.js +10 -0
  174. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  175. package/src/resources/extensions/gsd/activity-log.ts +37 -7
  176. package/src/resources/extensions/gsd/auto-dashboard.ts +4 -0
  177. package/src/resources/extensions/gsd/auto-dispatch.ts +9 -3
  178. package/src/resources/extensions/gsd/auto-prompts.ts +91 -42
  179. package/src/resources/extensions/gsd/auto-recovery.ts +7 -2
  180. package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
  181. package/src/resources/extensions/gsd/auto.ts +177 -25
  182. package/src/resources/extensions/gsd/commands.ts +264 -23
  183. package/src/resources/extensions/gsd/complexity.ts +236 -0
  184. package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
  185. package/src/resources/extensions/gsd/docs/preferences-reference.md +202 -2
  186. package/src/resources/extensions/gsd/files.ts +129 -3
  187. package/src/resources/extensions/gsd/git-service.ts +19 -8
  188. package/src/resources/extensions/gsd/gitignore.ts +41 -2
  189. package/src/resources/extensions/gsd/guided-flow.ts +247 -10
  190. package/src/resources/extensions/gsd/index.ts +47 -3
  191. package/src/resources/extensions/gsd/metrics.ts +44 -0
  192. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -0
  193. package/src/resources/extensions/gsd/native-parser-bridge.ts +5 -0
  194. package/src/resources/extensions/gsd/paths.ts +9 -0
  195. package/src/resources/extensions/gsd/preferences.ts +181 -2
  196. package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
  197. package/src/resources/extensions/gsd/prompts/system.md +2 -0
  198. package/src/resources/extensions/gsd/queue-order.ts +231 -0
  199. package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
  200. package/src/resources/extensions/gsd/routing-history.ts +290 -0
  201. package/src/resources/extensions/gsd/state.ts +15 -3
  202. package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
  203. package/src/resources/extensions/gsd/templates/preferences.md +14 -0
  204. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
  205. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
  206. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
  207. package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
  208. package/src/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
  209. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/git-service.test.ts +132 -0
  211. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
  212. package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
  213. package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
  214. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
  215. package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
  216. package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
  217. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
  218. package/src/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
  219. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
  220. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
  221. package/src/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
  222. package/src/resources/extensions/gsd/types.ts +28 -0
  223. package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
  224. package/src/resources/extensions/gsd/worktree.ts +24 -2
  225. package/src/resources/extensions/shared/next-action-ui.ts +16 -1
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Token Profile — unit tests for M004/S01.
3
+ *
4
+ * Tests profile resolution, preference merging, phase skip defaults,
5
+ * subagent model routing, default-to-balanced behavior, and dispatch
6
+ * table guard clauses (source-level structural verification).
7
+ *
8
+ * Uses source-level checks (readFileSync + string matching) to avoid
9
+ * @gsd/pi-coding-agent import resolution issues in dev environments.
10
+ */
11
+
12
+ import test from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import { readFileSync } from "node:fs";
15
+ import { join, dirname } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+
20
+ // ─── Source files for structural checks ───────────────────────────────────
21
+
22
+ const dispatchSrc = readFileSync(join(__dirname, "..", "auto-dispatch.ts"), "utf-8");
23
+ const preferencesSrc = readFileSync(join(__dirname, "..", "preferences.ts"), "utf-8");
24
+ const typesSrc = readFileSync(join(__dirname, "..", "types.ts"), "utf-8");
25
+
26
+ // ═══════════════════════════════════════════════════════════════════════════
27
+ // Type Definitions
28
+ // ═══════════════════════════════════════════════════════════════════════════
29
+
30
+ test("types: TokenProfile type exported with budget/balanced/quality", () => {
31
+ assert.ok(typesSrc.includes("export type TokenProfile"), "TokenProfile should be exported");
32
+ assert.ok(typesSrc.includes("'budget'"), "should include budget");
33
+ assert.ok(typesSrc.includes("'balanced'"), "should include balanced");
34
+ assert.ok(typesSrc.includes("'quality'"), "should include quality");
35
+ });
36
+
37
+ test("types: InlineLevel type exported with full/standard/minimal", () => {
38
+ assert.ok(typesSrc.includes("export type InlineLevel"), "InlineLevel should be exported");
39
+ assert.ok(typesSrc.includes("'full'"), "should include full");
40
+ assert.ok(typesSrc.includes("'standard'"), "should include standard");
41
+ assert.ok(typesSrc.includes("'minimal'"), "should include minimal");
42
+ });
43
+
44
+ test("types: PhaseSkipPreferences interface exported", () => {
45
+ assert.ok(typesSrc.includes("export interface PhaseSkipPreferences"), "PhaseSkipPreferences should be exported");
46
+ assert.ok(typesSrc.includes("skip_research"), "should include skip_research");
47
+ assert.ok(typesSrc.includes("skip_reassess"), "should include skip_reassess");
48
+ assert.ok(typesSrc.includes("skip_slice_research"), "should include skip_slice_research");
49
+ });
50
+
51
+ // ═══════════════════════════════════════════════════════════════════════════
52
+ // GSDPreferences Interface
53
+ // ═══════════════════════════════════════════════════════════════════════════
54
+
55
+ test("preferences: GSDPreferences includes token_profile field", () => {
56
+ assert.ok(
57
+ preferencesSrc.includes("token_profile?: TokenProfile"),
58
+ "GSDPreferences should have token_profile field",
59
+ );
60
+ });
61
+
62
+ test("preferences: GSDPreferences includes phases field", () => {
63
+ assert.ok(
64
+ preferencesSrc.includes("phases?: PhaseSkipPreferences"),
65
+ "GSDPreferences should have phases field",
66
+ );
67
+ });
68
+
69
+ test("preferences: GSDModelConfig includes subagent field", () => {
70
+ // Check both v1 and v2 configs
71
+ const v1Match = preferencesSrc.match(/interface GSDModelConfig\s*\{[^}]*subagent/);
72
+ assert.ok(v1Match, "GSDModelConfig should have subagent field");
73
+ const v2Match = preferencesSrc.match(/interface GSDModelConfigV2\s*\{[^}]*subagent/);
74
+ assert.ok(v2Match, "GSDModelConfigV2 should have subagent field");
75
+ });
76
+
77
+ test("preferences: KNOWN_PREFERENCE_KEYS includes token_profile and phases", () => {
78
+ assert.ok(preferencesSrc.includes('"token_profile"'), "KNOWN_PREFERENCE_KEYS should include token_profile");
79
+ assert.ok(preferencesSrc.includes('"phases"'), "KNOWN_PREFERENCE_KEYS should include phases");
80
+ });
81
+
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ // Profile Resolution
84
+ // ═══════════════════════════════════════════════════════════════════════════
85
+
86
+ test("profile: resolveProfileDefaults exists and handles all 3 tiers", () => {
87
+ assert.ok(
88
+ preferencesSrc.includes("export function resolveProfileDefaults"),
89
+ "resolveProfileDefaults should be exported",
90
+ );
91
+ assert.ok(
92
+ preferencesSrc.includes('case "budget"') &&
93
+ preferencesSrc.includes('case "balanced"') &&
94
+ preferencesSrc.includes('case "quality"'),
95
+ "resolveProfileDefaults should handle all 3 tiers",
96
+ );
97
+ });
98
+
99
+ test("profile: budget profile sets phase skips to true", () => {
100
+ // Extract the budget case block
101
+ const budgetIdx = preferencesSrc.indexOf('case "budget":');
102
+ const balancedIdx = preferencesSrc.indexOf('case "balanced":');
103
+ const budgetBlock = preferencesSrc.slice(budgetIdx, balancedIdx);
104
+ assert.ok(budgetBlock.includes("skip_research: true"), "budget should skip research");
105
+ assert.ok(budgetBlock.includes("skip_reassess: true"), "budget should skip reassess");
106
+ assert.ok(budgetBlock.includes("skip_slice_research: true"), "budget should skip slice research");
107
+ });
108
+
109
+ test("profile: balanced profile skips only slice research", () => {
110
+ const balancedIdx = preferencesSrc.indexOf('case "balanced":');
111
+ const qualityIdx = preferencesSrc.indexOf('case "quality":');
112
+ const balancedBlock = preferencesSrc.slice(balancedIdx, qualityIdx);
113
+ assert.ok(balancedBlock.includes("skip_slice_research: true"), "balanced should skip slice research");
114
+ assert.ok(!balancedBlock.includes("skip_research: true"), "balanced should NOT skip milestone research");
115
+ assert.ok(!balancedBlock.includes("skip_reassess: true"), "balanced should NOT skip reassess");
116
+ });
117
+
118
+ test("profile: quality profile has empty phases (no skips)", () => {
119
+ const qualityIdx = preferencesSrc.indexOf('case "quality":');
120
+ const qualityEnd = preferencesSrc.indexOf("}", qualityIdx + 50);
121
+ // Look for the return block after case "quality":
122
+ const qualityReturn = preferencesSrc.slice(qualityIdx, qualityIdx + 200);
123
+ assert.ok(
124
+ qualityReturn.includes("phases: {}"),
125
+ "quality should have empty phases object (no skips)",
126
+ );
127
+ });
128
+
129
+ // ═══════════════════════════════════════════════════════════════════════════
130
+ // Default Behavior (D046)
131
+ // ═══════════════════════════════════════════════════════════════════════════
132
+
133
+ test("profile: resolveEffectiveProfile defaults to balanced (D046)", () => {
134
+ assert.ok(
135
+ preferencesSrc.includes("export function resolveEffectiveProfile"),
136
+ "resolveEffectiveProfile should be exported",
137
+ );
138
+ assert.ok(
139
+ preferencesSrc.includes('return "balanced"'),
140
+ "resolveEffectiveProfile should default to balanced",
141
+ );
142
+ });
143
+
144
+ // ═══════════════════════════════════════════════════════════════════════════
145
+ // Inline Level Mapping
146
+ // ═══════════════════════════════════════════════════════════════════════════
147
+
148
+ test("profile: resolveInlineLevel maps profile to inline level", () => {
149
+ assert.ok(
150
+ preferencesSrc.includes("export function resolveInlineLevel"),
151
+ "resolveInlineLevel should be exported",
152
+ );
153
+ assert.ok(preferencesSrc.includes('case "budget": return "minimal"'), "budget → minimal");
154
+ assert.ok(preferencesSrc.includes('case "balanced": return "standard"'), "balanced → standard");
155
+ assert.ok(preferencesSrc.includes('case "quality": return "full"'), "quality → full");
156
+ });
157
+
158
+ // ═══════════════════════════════════════════════════════════════════════════
159
+ // Validation
160
+ // ═══════════════════════════════════════════════════════════════════════════
161
+
162
+ test("validate: validatePreferences handles token_profile", () => {
163
+ assert.ok(
164
+ preferencesSrc.includes("preferences.token_profile") &&
165
+ preferencesSrc.includes("budget, balanced, quality"),
166
+ "validatePreferences should validate token_profile enum values",
167
+ );
168
+ });
169
+
170
+ test("validate: validatePreferences handles phases object", () => {
171
+ assert.ok(
172
+ preferencesSrc.includes("preferences.phases") &&
173
+ preferencesSrc.includes("skip_research") &&
174
+ preferencesSrc.includes("skip_reassess") &&
175
+ preferencesSrc.includes("skip_slice_research"),
176
+ "validatePreferences should validate phases fields",
177
+ );
178
+ });
179
+
180
+ test("validate: phases warns on unknown keys", () => {
181
+ assert.ok(
182
+ preferencesSrc.includes("knownPhaseKeys") &&
183
+ preferencesSrc.includes("unknown phases key"),
184
+ "validatePreferences should warn on unknown phase keys",
185
+ );
186
+ });
187
+
188
+ // ═══════════════════════════════════════════════════════════════════════════
189
+ // Merge
190
+ // ═══════════════════════════════════════════════════════════════════════════
191
+
192
+ test("merge: mergePreferences handles token_profile with nullish coalescing", () => {
193
+ assert.ok(
194
+ preferencesSrc.includes("token_profile: override.token_profile ?? base.token_profile"),
195
+ "mergePreferences should use nullish coalescing for token_profile",
196
+ );
197
+ });
198
+
199
+ test("merge: mergePreferences handles phases with spread", () => {
200
+ assert.ok(
201
+ preferencesSrc.includes("...(base.phases") && preferencesSrc.includes("...(override.phases"),
202
+ "mergePreferences should spread phases objects",
203
+ );
204
+ });
205
+
206
+ // ═══════════════════════════════════════════════════════════════════════════
207
+ // Subagent Model Routing
208
+ // ═══════════════════════════════════════════════════════════════════════════
209
+
210
+ test("subagent: budget profile sets subagent model", () => {
211
+ const budgetIdx = preferencesSrc.indexOf('case "budget":');
212
+ const balancedIdx = preferencesSrc.indexOf('case "balanced":');
213
+ const budgetBlock = preferencesSrc.slice(budgetIdx, balancedIdx);
214
+ assert.ok(budgetBlock.includes("subagent:"), "budget profile should set subagent model");
215
+ });
216
+
217
+ test("subagent: resolveModelWithFallbacksForUnit handles subagent unit types", () => {
218
+ assert.ok(
219
+ preferencesSrc.includes('"subagent"') && preferencesSrc.includes('startsWith("subagent/")'),
220
+ "resolveModelWithFallbacksForUnit should handle subagent and subagent/* unit types",
221
+ );
222
+ });
223
+
224
+ // ═══════════════════════════════════════════════════════════════════════════
225
+ // Dispatch Table — Phase Skip Guards
226
+ // ═══════════════════════════════════════════════════════════════════════════
227
+
228
+ test("dispatch: research-milestone rule has skip_research guard", () => {
229
+ // Find the research-milestone rule and check it has the guard
230
+ const ruleIdx = dispatchSrc.indexOf("research-milestone");
231
+ assert.ok(ruleIdx > -1, "should have research-milestone rule");
232
+ // The guard should appear near this rule
233
+ assert.ok(
234
+ dispatchSrc.includes("skip_research") && dispatchSrc.includes("research-milestone"),
235
+ "research-milestone dispatch rule should check phases.skip_research",
236
+ );
237
+ });
238
+
239
+ test("dispatch: research-slice rule has skip guards", () => {
240
+ const ruleIdx = dispatchSrc.indexOf("research-slice");
241
+ assert.ok(ruleIdx > -1, "should have research-slice rule");
242
+ const afterRule = dispatchSrc.slice(ruleIdx);
243
+ assert.ok(
244
+ afterRule.includes("skip_research") || afterRule.includes("skip_slice_research"),
245
+ "research-slice rule should check skip_research or skip_slice_research",
246
+ );
247
+ });
248
+
249
+ test("dispatch: reassess-roadmap rule has skip_reassess guard", () => {
250
+ assert.ok(
251
+ dispatchSrc.includes("skip_reassess") && dispatchSrc.includes("reassess-roadmap"),
252
+ "reassess-roadmap dispatch rule should check phases.skip_reassess",
253
+ );
254
+ });
255
+
256
+ test("dispatch: phase skip guards return null (not stop)", () => {
257
+ // Verify skip guards use return null pattern
258
+ const researchGuard = dispatchSrc.match(/skip_research\).*?return null/s);
259
+ assert.ok(researchGuard, "skip_research guard should return null (fall-through)");
260
+
261
+ const reassessGuard = dispatchSrc.match(/skip_reassess\).*?return null/s);
262
+ assert.ok(reassessGuard, "skip_reassess guard should return null (fall-through)");
263
+ });
@@ -238,6 +238,34 @@ export interface HookDispatchResult {
238
238
 
239
239
  export type BudgetEnforcementMode = 'warn' | 'pause' | 'halt';
240
240
 
241
+ export type TokenProfile = 'budget' | 'balanced' | 'quality';
242
+
243
+ export type InlineLevel = 'full' | 'standard' | 'minimal';
244
+
245
+ export type ComplexityTier = 'light' | 'standard' | 'heavy';
246
+
247
+ export interface ClassificationResult {
248
+ tier: ComplexityTier;
249
+ reason: string;
250
+ downgraded: boolean;
251
+ }
252
+
253
+ export interface TaskMetadata {
254
+ fileCount?: number;
255
+ dependencyCount?: number;
256
+ isNewFile?: boolean;
257
+ tags?: string[];
258
+ estimatedLines?: number;
259
+ codeBlockCount?: number;
260
+ complexityKeywords?: string[];
261
+ }
262
+
263
+ export interface PhaseSkipPreferences {
264
+ skip_research?: boolean;
265
+ skip_reassess?: boolean;
266
+ skip_slice_research?: boolean;
267
+ }
268
+
241
269
  export interface NotificationPreferences {
242
270
  enabled?: boolean; // default true
243
271
  on_complete?: boolean; // notify on each unit completion
@@ -94,7 +94,7 @@ export function worktreeBranchName(name: string): string {
94
94
  *
95
95
  * @param opts.branch — override the default `worktree/<name>` branch name
96
96
  */
97
- export function createWorktree(basePath: string, name: string, opts: { branch?: string } = {}): WorktreeInfo {
97
+ export function createWorktree(basePath: string, name: string, opts: { branch?: string; startPoint?: string } = {}): WorktreeInfo {
98
98
  // Validate name: alphanumeric, hyphens, underscores only
99
99
  if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
100
100
  throw new Error(`Invalid worktree name "${name}". Use only letters, numbers, hyphens, and underscores.`);
@@ -114,9 +114,12 @@ export function createWorktree(basePath: string, name: string, opts: { branch?:
114
114
  // Prune any stale worktree entries from a previous removal
115
115
  nativeWorktreePrune(basePath);
116
116
 
117
+ // Use the explicit start point (e.g. integration branch) if provided,
118
+ // otherwise fall back to the repo's detected main branch.
119
+ const startPoint = opts.startPoint ?? nativeDetectMainBranch(basePath);
120
+
117
121
  // Check if the branch already exists (leftover from a previous worktree)
118
122
  const branchAlreadyExists = nativeBranchExists(basePath, branch);
119
- const mainBranch = nativeDetectMainBranch(basePath);
120
123
 
121
124
  if (branchAlreadyExists) {
122
125
  // Check if the branch is actively used by an existing worktree.
@@ -130,11 +133,11 @@ export function createWorktree(basePath: string, name: string, opts: { branch?:
130
133
  );
131
134
  }
132
135
 
133
- // Reset the stale branch to current main, then attach worktree to it
134
- nativeBranchForceReset(basePath, branch, mainBranch);
136
+ // Reset the stale branch to the start point, then attach worktree to it
137
+ nativeBranchForceReset(basePath, branch, startPoint);
135
138
  nativeWorktreeAdd(basePath, wtPath, branch);
136
139
  } else {
137
- nativeWorktreeAdd(basePath, wtPath, branch, true, mainBranch);
140
+ nativeWorktreeAdd(basePath, wtPath, branch, true, startPoint);
138
141
  }
139
142
 
140
143
  return {
@@ -54,10 +54,10 @@ export function setActiveMilestoneId(basePath: string, milestoneId: string | nul
54
54
  * record when the user starts from a different branch (#300). Always a no-op
55
55
  * if on a GSD slice branch.
56
56
  */
57
- export function captureIntegrationBranch(basePath: string, milestoneId: string): void {
57
+ export function captureIntegrationBranch(basePath: string, milestoneId: string, options?: { commitDocs?: boolean }): void {
58
58
  const svc = getService(basePath);
59
59
  const current = svc.getCurrentBranch();
60
- writeIntegrationBranch(basePath, milestoneId, current);
60
+ writeIntegrationBranch(basePath, milestoneId, current, options);
61
61
  }
62
62
 
63
63
  // ─── Pure Utility Functions (unchanged) ────────────────────────────────────
@@ -76,6 +76,28 @@ export function detectWorktreeName(basePath: string): string | null {
76
76
  return name || null;
77
77
  }
78
78
 
79
+ /**
80
+ * Resolve the project root from a path that may be inside a worktree.
81
+ * If the path contains `/.gsd/worktrees/<name>/`, returns the portion
82
+ * before `/.gsd/`. Otherwise returns the input unchanged.
83
+ *
84
+ * Use this in commands that call `process.cwd()` to ensure they always
85
+ * operate against the real project root, not a worktree subdirectory.
86
+ */
87
+ export function resolveProjectRoot(basePath: string): string {
88
+ const normalizedPath = basePath.replaceAll("\\", "/");
89
+ const marker = "/.gsd/worktrees/";
90
+ const idx = normalizedPath.indexOf(marker);
91
+ if (idx === -1) return basePath;
92
+ // Return the original path up to the .gsd/ marker (un-normalized)
93
+ // Account for potential OS-specific separators
94
+ const sep = basePath.includes("\\") ? "\\" : "/";
95
+ const markerOs = `${sep}.gsd${sep}worktrees${sep}`;
96
+ const idxOs = basePath.indexOf(markerOs);
97
+ if (idxOs !== -1) return basePath.slice(0, idxOs);
98
+ return basePath.slice(0, idx);
99
+ }
100
+
79
101
  /**
80
102
  * Get the slice branch name, namespaced by worktree when inside one.
81
103
  *
@@ -118,7 +118,7 @@ export async function showNextAction(
118
118
  }
119
119
  });
120
120
 
121
- return ctx.ui.custom<string>((_tui: TUI, theme: Theme, _kb, done) => {
121
+ const result = await ctx.ui.custom<string>((_tui: TUI, theme: Theme, _kb, done) => {
122
122
  let cursorIdx = defaultIdx;
123
123
  let cachedLines: string[] | undefined;
124
124
 
@@ -194,4 +194,19 @@ export async function showNextAction(
194
194
 
195
195
  return { render, invalidate: () => { cachedLines = undefined; }, handleInput };
196
196
  });
197
+
198
+ // Fallback for RPC mode where ctx.ui.custom() returns undefined (#447).
199
+ // Fall back to ctx.ui.select() which IS implemented in RPC mode.
200
+ if (result === undefined || result === null) {
201
+ const labels = allActions.map(a => {
202
+ const tag = a.recommended ? " (recommended)" : "";
203
+ return `${a.label}${tag}: ${a.description}`;
204
+ });
205
+ const selected = await ctx.ui.select(opts.title, labels);
206
+ if (selected === undefined || selected === null) return "not_yet";
207
+ const idx = labels.indexOf(selected as string);
208
+ return idx >= 0 ? allActions[idx].id : "not_yet";
209
+ }
210
+
211
+ return result;
197
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-pi",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "description": "GSD — Get Shit Done coding agent",
5
5
  "license": "MIT",
6
6
  "repository": {