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
@@ -5,7 +5,7 @@ import { readFileSync } from "node:fs";
5
5
  import { readdirSync } from "node:fs";
6
6
  import { resolveMilestoneFile, milestonesDir } from "./paths.js";
7
7
  import { parseRoadmapSlices } from "./roadmap-slices.js";
8
- import { extractMilestoneSeq, milestoneIdSort } from "./guided-flow.js";
8
+ import { findMilestoneIds } from "./guided-flow.js";
9
9
 
10
10
  const SLICE_DISPATCH_TYPES = new Set([
11
11
  "research-slice",
@@ -43,24 +43,12 @@ export function getPriorSliceCompletionBlocker(base: string, _mainBranch: string
43
43
  const [targetMid, targetSid] = unitId.split("/");
44
44
  if (!targetMid || !targetSid) return null;
45
45
 
46
- const targetSeq = extractMilestoneSeq(targetMid);
47
- if (targetSeq === 0) return null;
48
-
49
- // Scan actual milestone directories instead of iterating by number
50
- let milestoneIds: string[];
51
- try {
52
- milestoneIds = readdirSync(milestonesDir(base), { withFileTypes: true })
53
- .filter(d => d.isDirectory())
54
- .map(d => {
55
- const match = d.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/);
56
- return match ? match[1] : null;
57
- })
58
- .filter((id): id is string => id !== null)
59
- .sort(milestoneIdSort)
60
- .filter(id => extractMilestoneSeq(id) <= targetSeq);
61
- } catch {
62
- return null;
63
- }
46
+ // Use findMilestoneIds to respect custom queue order.
47
+ // Only check milestones that come BEFORE the target in queue order.
48
+ const allIds = findMilestoneIds(base);
49
+ const targetIdx = allIds.indexOf(targetMid);
50
+ if (targetIdx < 0) return null;
51
+ const milestoneIds = allIds.slice(0, targetIdx + 1);
64
52
 
65
53
  for (const mid of milestoneIds) {
66
54
  // Read from disk (working tree) — always has the latest state
@@ -80,9 +80,9 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
80
80
 
81
81
  - `skill_rules`: situational rules with a human-readable `when` trigger and one or more of `use`, `prefer`, or `avoid`.
82
82
 
83
- - `custom_instructions`: extra durable instructions related to skill use.
83
+ - `custom_instructions`: extra durable instructions related to skill use. For operational project knowledge (recurring rules, gotchas, patterns), use `.gsd/KNOWLEDGE.md` instead — it's injected into every agent prompt automatically and agents can append to it during execution.
84
84
 
85
- - `models`: per-stage model selection for auto-mode. Keys: `research`, `planning`, `execution`, `completion`. Values can be:
85
+ - `models`: per-stage model selection for auto-mode. Keys: `research`, `planning`, `execution`, `execution_simple`, `completion`, `subagent`. Values can be:
86
86
  - Simple string: `"claude-sonnet-4-6"` — single model, no fallbacks
87
87
  - Provider-qualified string: `"bedrock/claude-sonnet-4-6"` — targets a specific provider when the same model ID exists across multiple providers
88
88
  - Object with fallbacks: `{ model: "claude-opus-4-6", fallbacks: ["glm-5", "minimax-m2.5"] }` — tries fallbacks in order if primary fails
@@ -108,9 +108,75 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
108
108
  - `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a worktree back to the integration branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `false`.
109
109
  - `commit_type`: string — override the conventional commit type prefix. Must be one of: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `style`. Default: inferred from diff content.
110
110
  - `main_branch`: string — the primary branch name for new git repos (e.g., `"main"`, `"master"`, `"trunk"`). Also used by `getMainBranch()` as the preferred branch when auto-detection is ambiguous. Default: `"main"`.
111
+ - `merge_strategy`: `"squash"` or `"merge"` — controls how worktree branches are merged back. `"squash"` combines all commits into one; `"merge"` preserves individual commits. Default: `"squash"`.
112
+ - `isolation`: `"worktree"` or `"branch"` — controls auto-mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root (useful for submodule-heavy repos). Default: `"worktree"`.
113
+ - `commit_docs`: boolean — when `false`, prevents GSD from committing `.gsd/` planning artifacts to git. The `.gsd/` folder is added to `.gitignore` and kept local-only. Useful for teams where only some members use GSD, or when company policy requires a clean repository. Default: `true`.
111
114
 
112
115
  - `unique_milestone_ids`: boolean — when `true`, generates milestone IDs in `M{seq}-{rand6}` format (e.g. `M001-eh88as`) instead of plain sequential `M001`. Prevents ID collisions in team workflows where multiple contributors create milestones concurrently. Both formats coexist — existing `M001`-style milestones remain valid. Default: `false`.
113
116
 
117
+ - `budget_ceiling`: number — maximum dollar amount to spend on auto-mode. When reached, behavior is controlled by `budget_enforcement`. Default: no limit.
118
+
119
+ - `budget_enforcement`: `"warn"`, `"pause"`, or `"halt"` — action taken when `budget_ceiling` is reached.
120
+ - `warn` — log a warning but continue execution.
121
+ - `pause` — pause auto-mode and wait for user confirmation.
122
+ - `halt` — stop auto-mode immediately.
123
+ - Default: `"pause"`.
124
+
125
+ - `context_pause_threshold`: number (0-100) — context window usage percentage at which auto-mode should pause to suggest checkpointing. Set to `0` to disable. Default: `0` (disabled).
126
+
127
+ - `token_profile`: `"budget"`, `"balanced"`, or `"quality"` — coordinates model selection, phase skipping, and context compression. `budget` skips research/reassessment and uses cheaper models; `balanced` (default) runs all phases; `quality` prefers higher-quality models. See token-optimization docs.
128
+
129
+ - `phases`: fine-grained control over which phases run. Usually set by `token_profile`, but can be overridden. Keys:
130
+ - `skip_research`: boolean — skip milestone-level research. Default: `false`.
131
+ - `skip_reassess`: boolean — skip roadmap reassessment after each slice. Default: `false`.
132
+ - `skip_slice_research`: boolean — skip per-slice research. Default: `false`.
133
+
134
+ - `remote_questions`: route interactive questions to Slack/Discord for headless auto-mode. Keys:
135
+ - `channel`: `"slack"` or `"discord"` — channel type.
136
+ - `channel_id`: string or number — channel ID.
137
+ - `timeout_minutes`: number — question timeout in minutes (clamped 1-30).
138
+ - `poll_interval_seconds`: number — poll interval in seconds (clamped 2-30).
139
+
140
+ - `notifications`: configures desktop notification behavior during auto-mode. Keys:
141
+ - `enabled`: boolean — master toggle for all notifications. Default: `true`.
142
+ - `on_complete`: boolean — notify when a unit completes. Default: `true`.
143
+ - `on_error`: boolean — notify on errors. Default: `true`.
144
+ - `on_budget`: boolean — notify when budget thresholds are reached. Default: `true`.
145
+ - `on_milestone`: boolean — notify when a milestone finishes. Default: `true`.
146
+ - `on_attention`: boolean — notify when manual attention is needed. Default: `true`.
147
+
148
+ - `uat_dispatch`: boolean — when `true`, enables UAT (User Acceptance Testing) dispatch mode. Default: `false`.
149
+
150
+ - `post_unit_hooks`: array — hooks that fire after a unit completes. Each entry has:
151
+ - `name`: string — unique hook identifier.
152
+ - `after`: string[] — unit types that trigger this hook (e.g., `["execute-task"]`).
153
+ - `prompt`: string — prompt sent to the LLM. Supports `{milestoneId}`, `{sliceId}`, `{taskId}` substitutions.
154
+ - `max_cycles`: number — max times this hook fires per trigger (default: 1, max: 10).
155
+ - `model`: string — optional model override.
156
+ - `artifact`: string — expected output file name (relative to task/slice dir). Hook is skipped if file already exists (idempotent).
157
+ - `retry_on`: string — if this file is produced instead of the artifact, re-run the trigger unit then re-run hooks.
158
+ - `agent`: string — agent definition file to use for hook execution.
159
+ - `enabled`: boolean — toggle without removing (default: `true`).
160
+
161
+ - `pre_dispatch_hooks`: array — hooks that fire before a unit is dispatched. Each entry has:
162
+ - `name`: string — unique hook identifier.
163
+ - `before`: string[] — unit types to intercept.
164
+ - `action`: `"modify"`, `"skip"`, or `"replace"` — what to do with the unit.
165
+ - `prepend`: string — text prepended to unit prompt (for `"modify"` action).
166
+ - `append`: string — text appended to unit prompt (for `"modify"` action).
167
+ - `prompt`: string — replacement prompt (for `"replace"` action; required when action is `"replace"`).
168
+ - `unit_type`: string — override unit type label (for `"replace"` action).
169
+ - `skip_if`: string — for `"skip"` action: only skip if this file exists (relative to unit dir).
170
+ - `model`: string — optional model override when this hook fires.
171
+ - `enabled`: boolean — toggle without removing (default: `true`).
172
+
173
+ **Action validation:**
174
+ - `"modify"` requires at least one of `prepend` or `append`.
175
+ - `"replace"` requires `prompt`.
176
+ - `"skip"` is valid with no additional fields.
177
+
178
+ **Known unit types for `before`/`after`:** `research-milestone`, `plan-milestone`, `research-slice`, `plan-slice`, `execute-task`, `complete-slice`, `replan-slice`, `reassess-roadmap`, `run-uat`.
179
+
114
180
  ---
115
181
 
116
182
  ## Best Practices
@@ -276,3 +342,137 @@ git:
276
342
  ```
277
343
 
278
344
  All git fields are optional. Omit any field to use the default behavior. Project-level preferences override global preferences on a per-field basis.
345
+
346
+ ---
347
+
348
+ ## Budget & Cost Control Example
349
+
350
+ ```yaml
351
+ ---
352
+ version: 1
353
+ budget_ceiling: 10.00
354
+ budget_enforcement: pause
355
+ context_pause_threshold: 80
356
+ ---
357
+ ```
358
+
359
+ Sets a $10 budget ceiling. Auto-mode pauses when the ceiling is reached. Context window pauses at 80% usage for checkpointing.
360
+
361
+ ---
362
+
363
+ ## Notifications Example
364
+
365
+ ```yaml
366
+ ---
367
+ version: 1
368
+ notifications:
369
+ enabled: true
370
+ on_complete: false
371
+ on_error: true
372
+ on_budget: true
373
+ on_milestone: true
374
+ on_attention: true
375
+ ---
376
+ ```
377
+
378
+ Disables per-unit completion notifications (noisy in long runs) while keeping error, budget, milestone, and attention notifications enabled.
379
+
380
+ ---
381
+
382
+ ## Post-Unit Hooks Example
383
+
384
+ ```yaml
385
+ ---
386
+ version: 1
387
+ post_unit_hooks:
388
+ - name: code-review
389
+ after:
390
+ - execute-task
391
+ prompt: "Review the code changes in {sliceId}/{taskId} for quality, security, and test coverage."
392
+ max_cycles: 1
393
+ artifact: REVIEW.md
394
+ ---
395
+ ```
396
+
397
+ Runs an automated code review after each task execution. Skips if `REVIEW.md` already exists (idempotent).
398
+
399
+ ---
400
+
401
+ ## Pre-Dispatch Hooks Examples
402
+
403
+ **Modify — inject instructions before every task:**
404
+
405
+ ```yaml
406
+ ---
407
+ version: 1
408
+ pre_dispatch_hooks:
409
+ - name: enforce-standards
410
+ before:
411
+ - execute-task
412
+ action: modify
413
+ prepend: "Follow our TypeScript coding standards and always run linting."
414
+ ---
415
+ ```
416
+
417
+ **Skip — skip per-slice research when a research file already exists:**
418
+
419
+ ```yaml
420
+ ---
421
+ version: 1
422
+ pre_dispatch_hooks:
423
+ - name: skip-existing-research
424
+ before:
425
+ - research-slice
426
+ action: skip
427
+ skip_if: RESEARCH.md
428
+ ---
429
+ ```
430
+
431
+ **Replace — substitute a custom prompt for task execution:**
432
+
433
+ ```yaml
434
+ ---
435
+ version: 1
436
+ pre_dispatch_hooks:
437
+ - name: tdd-execute
438
+ before:
439
+ - execute-task
440
+ action: replace
441
+ prompt: "Implement the task using strict TDD. Write failing tests first, then implement, then refactor."
442
+ model: claude-opus-4-6
443
+ ---
444
+ ```
445
+
446
+ ---
447
+
448
+ ## Token Profile & Phases Example
449
+
450
+ ```yaml
451
+ ---
452
+ version: 1
453
+ token_profile: budget
454
+ phases:
455
+ skip_research: true
456
+ skip_reassess: true
457
+ skip_slice_research: false
458
+ ---
459
+ ```
460
+
461
+ Uses the `budget` profile to minimize token usage, with explicit override to keep slice-level research enabled.
462
+
463
+ ---
464
+
465
+ ## Remote Questions Example
466
+
467
+ ```yaml
468
+ ---
469
+ version: 1
470
+ remote_questions:
471
+ channel: slack
472
+ channel_id: "C0123456789"
473
+ timeout_minutes: 15
474
+ poll_interval_seconds: 10
475
+ ---
476
+ ```
477
+
478
+ Routes interactive questions to a Slack channel for headless auto-mode sessions. Questions time out after 15 minutes if unanswered.
@@ -26,12 +26,16 @@ import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativePa
26
26
 
27
27
  const CACHE_MAX = 50;
28
28
 
29
- /** Fast composite key: length + first/last 100 chars. Unique enough for distinct markdown files. */
29
+ /** Fast composite key: length + first/mid/last 100 chars. The middle sample
30
+ * prevents collisions when only a few characters change in the interior of
31
+ * a file (e.g., a checkbox [ ] → [x] that doesn't alter length or endpoints). */
30
32
  function cacheKey(content: string): string {
31
33
  const len = content.length;
32
34
  const head = content.slice(0, 100);
35
+ const midStart = Math.max(0, Math.floor(len / 2) - 50);
36
+ const mid = len > 200 ? content.slice(midStart, midStart + 100) : '';
33
37
  const tail = len > 100 ? content.slice(-100) : '';
34
- return `${len}:${head}:${tail}`;
38
+ return `${len}:${head}:${mid}:${tail}`;
35
39
  }
36
40
 
37
41
  const _parseCache = new Map<string, unknown>();
@@ -845,7 +849,7 @@ export function parseContextDependsOn(content: string | null): string[] {
845
849
  const fm = parseFrontmatterMap(fmLines);
846
850
  const raw = fm['depends_on'];
847
851
  if (!Array.isArray(raw) || raw.length === 0) return [];
848
- return (raw as string[]).map(s => String(s).toUpperCase().trim()).filter(Boolean);
852
+ return (raw as string[]).map(s => String(s).trim()).filter(Boolean);
849
853
  }
850
854
 
851
855
  /**
@@ -947,6 +951,128 @@ export async function appendOverride(basePath: string, change: string, appliedAt
947
951
  }
948
952
  }
949
953
 
954
+ export async function appendKnowledge(
955
+ basePath: string,
956
+ type: "rule" | "pattern" | "lesson",
957
+ entry: string,
958
+ scope: string,
959
+ ): Promise<void> {
960
+ const knowledgePath = resolveGsdRootFile(basePath, "KNOWLEDGE");
961
+ const existing = await loadFile(knowledgePath);
962
+
963
+ if (existing) {
964
+ // Find the next ID for this type
965
+ const prefix = type === "rule" ? "K" : type === "pattern" ? "P" : "L";
966
+ const idPattern = new RegExp(`^\\| ${prefix}(\\d+)`, "gm");
967
+ let maxId = 0;
968
+ let match;
969
+ while ((match = idPattern.exec(existing)) !== null) {
970
+ const num = parseInt(match[1], 10);
971
+ if (num > maxId) maxId = num;
972
+ }
973
+ const nextId = `${prefix}${String(maxId + 1).padStart(3, "0")}`;
974
+
975
+ // Build the table row
976
+ let row: string;
977
+ if (type === "rule") {
978
+ row = `| ${nextId} | ${scope} | ${entry} | — | manual |`;
979
+ } else if (type === "pattern") {
980
+ row = `| ${nextId} | ${entry} | — | ${scope} |`;
981
+ } else {
982
+ row = `| ${nextId} | ${entry} | — | — | ${scope} |`;
983
+ }
984
+
985
+ // Find the right section and append after the table header
986
+ const sectionHeading = type === "rule" ? "## Rules" : type === "pattern" ? "## Patterns" : "## Lessons Learned";
987
+ const sectionIdx = existing.indexOf(sectionHeading);
988
+ if (sectionIdx !== -1) {
989
+ // Find the end of the table header row (the |---|...| line)
990
+ const afterHeading = existing.indexOf("\n", sectionIdx);
991
+ // Find the next section or end
992
+ const nextSection = existing.indexOf("\n## ", afterHeading + 1);
993
+ const insertPoint = nextSection !== -1 ? nextSection : existing.length;
994
+
995
+ // Insert row before the next section (or at end)
996
+ const before = existing.slice(0, insertPoint).trimEnd();
997
+ const after = existing.slice(insertPoint);
998
+ await saveFile(knowledgePath, before + "\n" + row + "\n" + after);
999
+ } else {
1000
+ // Section not found — append at end
1001
+ await saveFile(knowledgePath, existing.trimEnd() + "\n\n" + row + "\n");
1002
+ }
1003
+ } else {
1004
+ // Create file from scratch with template header
1005
+ const header = [
1006
+ "# Project Knowledge",
1007
+ "",
1008
+ "Append-only register of project-specific rules, patterns, and lessons learned.",
1009
+ "Agents read this before every unit. Add entries when you discover something worth remembering.",
1010
+ "",
1011
+ ].join("\n");
1012
+
1013
+ let content: string;
1014
+ if (type === "rule") {
1015
+ content = header + [
1016
+ "## Rules",
1017
+ "",
1018
+ "| # | Scope | Rule | Why | Added |",
1019
+ "|---|-------|------|-----|-------|",
1020
+ `| K001 | ${scope} | ${entry} | — | manual |`,
1021
+ "",
1022
+ "## Patterns",
1023
+ "",
1024
+ "| # | Pattern | Where | Notes |",
1025
+ "|---|---------|-------|-------|",
1026
+ "",
1027
+ "## Lessons Learned",
1028
+ "",
1029
+ "| # | What Happened | Root Cause | Fix | Scope |",
1030
+ "|---|--------------|------------|-----|-------|",
1031
+ "",
1032
+ ].join("\n");
1033
+ } else if (type === "pattern") {
1034
+ content = header + [
1035
+ "## Rules",
1036
+ "",
1037
+ "| # | Scope | Rule | Why | Added |",
1038
+ "|---|-------|------|-----|-------|",
1039
+ "",
1040
+ "## Patterns",
1041
+ "",
1042
+ "| # | Pattern | Where | Notes |",
1043
+ "|---|---------|-------|-------|",
1044
+ `| P001 | ${entry} | — | ${scope} |`,
1045
+ "",
1046
+ "## Lessons Learned",
1047
+ "",
1048
+ "| # | What Happened | Root Cause | Fix | Scope |",
1049
+ "|---|--------------|------------|-----|-------|",
1050
+ "",
1051
+ ].join("\n");
1052
+ } else {
1053
+ content = header + [
1054
+ "## Rules",
1055
+ "",
1056
+ "| # | Scope | Rule | Why | Added |",
1057
+ "|---|-------|------|-----|-------|",
1058
+ "",
1059
+ "## Patterns",
1060
+ "",
1061
+ "| # | Pattern | Where | Notes |",
1062
+ "|---|---------|-------|-------|",
1063
+ "",
1064
+ "## Lessons Learned",
1065
+ "",
1066
+ "| # | What Happened | Root Cause | Fix | Scope |",
1067
+ "|---|--------------|------------|-----|-------|",
1068
+ `| L001 | ${entry} | — | — | ${scope} |`,
1069
+ "",
1070
+ ].join("\n");
1071
+ }
1072
+ await saveFile(knowledgePath, content);
1073
+ }
1074
+ }
1075
+
950
1076
  export async function loadActiveOverrides(basePath: string): Promise<Override[]> {
951
1077
  const overridesPath = resolveGsdRootFile(basePath, "OVERRIDES");
952
1078
  const content = await loadFile(overridesPath);
@@ -47,6 +47,11 @@ export interface GitPreferences {
47
47
  * - "branch": works directly in the project root (for submodule-heavy repos)
48
48
  */
49
49
  isolation?: "worktree" | "branch";
50
+ /** When false, prevents GSD from committing .gsd/ planning artifacts to git.
51
+ * The .gsd/ folder is added to .gitignore and kept local-only.
52
+ * Default: true (planning docs are tracked in git).
53
+ */
54
+ commit_docs?: boolean;
50
55
  }
51
56
 
52
57
  export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
@@ -152,7 +157,7 @@ export function readIntegrationBranch(basePath: string, milestoneId: string): st
152
157
  *
153
158
  * The file is committed immediately so the metadata is persisted in git.
154
159
  */
155
- export function writeIntegrationBranch(basePath: string, milestoneId: string, branch: string): void {
160
+ export function writeIntegrationBranch(basePath: string, milestoneId: string, branch: string, options?: { commitDocs?: boolean }): void {
156
161
  // Don't record slice branches as the integration target
157
162
  if (SLICE_BRANCH_RE.test(branch)) return;
158
163
  // Validate
@@ -178,12 +183,15 @@ export function writeIntegrationBranch(basePath: string, milestoneId: string, br
178
183
  writeFileSync(metaFile, JSON.stringify(existing, null, 2) + "\n", "utf-8");
179
184
 
180
185
  // Commit immediately so the metadata is persisted in git.
181
- try {
182
- nativeAddPaths(basePath, [metaFile]);
183
- nativeCommit(basePath, `chore(${milestoneId}): record integration branch`, { allowEmpty: false });
184
- } catch {
185
- // Non-fatal file is on disk even if commit fails (e.g. nothing to commit
186
- // because the file was already tracked with identical content)
186
+ // Skip when commit_docs is explicitly false — .gsd/ is local-only.
187
+ if (options?.commitDocs !== false) {
188
+ try {
189
+ nativeAddPaths(basePath, [metaFile]);
190
+ nativeCommit(basePath, `chore(${milestoneId}): record integration branch`, { allowEmpty: false });
191
+ } catch {
192
+ // Non-fatal — file is on disk even if commit fails (e.g. nothing to commit
193
+ // because the file was already tracked with identical content)
194
+ }
187
195
  }
188
196
  }
189
197
 
@@ -284,7 +292,10 @@ export class GitServiceImpl {
284
292
  * @param extraExclusions Additional pathspec exclusions beyond RUNTIME_EXCLUSION_PATHS.
285
293
  */
286
294
  private smartStage(extraExclusions: readonly string[] = []): void {
287
- const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
295
+ // When commit_docs is false, exclude the entire .gsd/ directory from staging
296
+ const commitDocsDisabled = this.prefs.commit_docs === false;
297
+ const gsdExclusion = commitDocsDisabled ? [".gsd/"] : [];
298
+ const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...gsdExclusion, ...extraExclusions];
288
299
 
289
300
  // One-time cleanup: if runtime files are already tracked in the index
290
301
  // (from older versions where the fallback bug staged them), untrack them
@@ -78,15 +78,26 @@ const BASELINE_PATTERNS = [
78
78
  * Ensure basePath/.gitignore contains all baseline patterns.
79
79
  * Creates the file if missing; appends only missing lines if it exists.
80
80
  * Returns true if the file was created or modified, false if already complete.
81
+ *
82
+ * When `commitDocs` is false, the entire `.gsd/` directory is added to
83
+ * .gitignore instead of individual runtime patterns, keeping all GSD
84
+ * artifacts local-only.
81
85
  */
82
- export function ensureGitignore(basePath: string): boolean {
86
+ export function ensureGitignore(basePath: string, options?: { commitDocs?: boolean }): boolean {
83
87
  const gitignorePath = join(basePath, ".gitignore");
88
+ const commitDocs = options?.commitDocs !== false; // default true
84
89
 
85
90
  let existing = "";
86
91
  if (existsSync(gitignorePath)) {
87
92
  existing = readFileSync(gitignorePath, "utf-8");
88
93
  }
89
94
 
95
+ // When commit_docs is false, ensure blanket ".gsd/" is in .gitignore
96
+ // and skip the self-heal that would remove it.
97
+ if (!commitDocs) {
98
+ return ensureBlanketGsdIgnore(gitignorePath, existing);
99
+ }
100
+
90
101
  // Self-heal: remove blanket ".gsd/" lines from pre-v2.14.0 projects.
91
102
  // The blanket ignore prevented planning artifacts (.gsd/milestones/) from
92
103
  // being tracked in git, causing artifacts to vanish in worktrees and
@@ -203,7 +214,7 @@ See \`~/.gsd/agent/extensions/gsd/docs/preferences-reference.md\` for full field
203
214
  - \`models\`: Model preferences for specific task types
204
215
  - \`skill_discovery\`: Automatic skill detection preferences
205
216
  - \`auto_supervisor\`: Supervision and gating rules for autonomous modes
206
- - \`git\`: Git preferences — \`main_branch\` (default branch name for new repos, e.g., "main", "master", "trunk"), \`auto_push\`, \`snapshots\`, etc.
217
+ - \`git\`: Git preferences — \`main_branch\` (default branch name for new repos, e.g., "main", "master", "trunk"), \`auto_push\`, \`snapshots\`, \`commit_docs\` (set to \`false\` to keep .gsd/ local-only), etc.
207
218
 
208
219
  ## Examples
209
220
 
@@ -224,3 +235,31 @@ custom_instructions:
224
235
  return true;
225
236
  }
226
237
 
238
+ /**
239
+ * When commit_docs is false, ensure `.gsd/` is in .gitignore as a blanket
240
+ * pattern. This keeps all GSD artifacts local-only.
241
+ * Returns true if the file was modified, false if already complete.
242
+ */
243
+ function ensureBlanketGsdIgnore(gitignorePath: string, existing: string): boolean {
244
+ const existingLines = new Set(
245
+ existing
246
+ .split("\n")
247
+ .map((l) => l.trim())
248
+ .filter((l) => l && !l.startsWith("#")),
249
+ );
250
+
251
+ // Already has blanket .gsd/ ignore
252
+ if (existingLines.has(".gsd/") || existingLines.has(".gsd")) return false;
253
+
254
+ const block = [
255
+ "",
256
+ "# ── GSD (local-only, commit_docs: false) ──",
257
+ ".gsd/",
258
+ "",
259
+ ].join("\n");
260
+
261
+ const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
262
+ writeFileSync(gitignorePath, existing + prefix + block, "utf-8");
263
+ return true;
264
+ }
265
+