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
@@ -22,11 +22,12 @@ import {
22
22
  } from "./paths.js";
23
23
  import { randomInt } from "node:crypto";
24
24
  import { join } from "node:path";
25
- import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
25
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
26
26
  import { nativeIsRepo, nativeInit, nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
27
27
  import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
28
28
  import { loadEffectiveGSDPreferences } from "./preferences.js";
29
29
  import { showConfirm } from "../shared/confirm-ui.js";
30
+ import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
30
31
 
31
32
  // ─── Auto-start after discuss ─────────────────────────────────────────────────
32
33
 
@@ -203,13 +204,16 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string)
203
204
  export function findMilestoneIds(basePath: string): string[] {
204
205
  const dir = milestonesDir(basePath);
205
206
  try {
206
- return readdirSync(dir, { withFileTypes: true })
207
+ const ids = readdirSync(dir, { withFileTypes: true })
207
208
  .filter((d) => d.isDirectory())
208
209
  .map((d) => {
209
210
  const match = d.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/);
210
211
  return match ? match[1] : d.name;
211
- })
212
- .sort(milestoneIdSort);
212
+ });
213
+
214
+ // Apply custom queue order if available, else fall back to numeric sort
215
+ const customOrder = loadQueueOrder(basePath);
216
+ return sortByQueueOrder(ids, customOrder);
213
217
  } catch {
214
218
  return [];
215
219
  }
@@ -305,6 +309,235 @@ export async function showQueue(
305
309
  return;
306
310
  }
307
311
 
312
+ // ── Count pending milestones ────────────────────────────────────────
313
+ const pendingMilestones = state.registry.filter(
314
+ m => m.status === "pending" || m.status === "active",
315
+ );
316
+ const completeCount = state.registry.filter(m => m.status === "complete").length;
317
+
318
+ // ── If multiple pending milestones, show queue management hub ──────
319
+ if (pendingMilestones.length > 1) {
320
+ const choice = await showNextAction(ctx, {
321
+ title: "GSD — Queue Management",
322
+ summary: [
323
+ `${completeCount} complete, ${pendingMilestones.length} pending.`,
324
+ ],
325
+ actions: [
326
+ {
327
+ id: "reorder",
328
+ label: "Reorder queue",
329
+ description: `Change execution order of ${pendingMilestones.length} pending milestones.`,
330
+ recommended: true,
331
+ },
332
+ {
333
+ id: "add",
334
+ label: "Add new work",
335
+ description: "Queue new milestones via discussion.",
336
+ },
337
+ ],
338
+ notYetMessage: "Run /gsd queue when ready.",
339
+ });
340
+
341
+ if (choice === "reorder") {
342
+ await handleQueueReorder(ctx, basePath, state);
343
+ return;
344
+ }
345
+ if (choice === "not_yet") return;
346
+ // "add" falls through to existing queue-add logic below
347
+ }
348
+
349
+ // ── Existing queue-add flow ─────────────────────────────────────────
350
+ await showQueueAdd(ctx, pi, basePath, state);
351
+ }
352
+
353
+ async function handleQueueReorder(
354
+ ctx: ExtensionCommandContext,
355
+ basePath: string,
356
+ state: Awaited<ReturnType<typeof deriveState>>,
357
+ ): Promise<void> {
358
+ const { showQueueReorder: showReorderUI } = await import("./queue-reorder-ui.js");
359
+ const { invalidateStateCache } = await import("./state.js");
360
+
361
+ const completed = state.registry
362
+ .filter(m => m.status === "complete")
363
+ .map(m => ({ id: m.id, title: m.title, dependsOn: m.dependsOn }));
364
+
365
+ const pending = state.registry
366
+ .filter(m => m.status !== "complete")
367
+ .map(m => ({ id: m.id, title: m.title, dependsOn: m.dependsOn }));
368
+
369
+ const result = await showReorderUI(ctx, completed, pending);
370
+ if (!result) {
371
+ ctx.ui.notify("Queue reorder cancelled.", "info");
372
+ return;
373
+ }
374
+
375
+ // Save the new order
376
+ saveQueueOrder(basePath, result.order);
377
+ invalidateStateCache();
378
+
379
+ // Remove conflicting depends_on entries from CONTEXT.md files
380
+ if (result.depsToRemove.length > 0) {
381
+ removeDependsOnFromContextFiles(basePath, result.depsToRemove);
382
+ }
383
+
384
+ // Sync PROJECT.md milestone sequence table
385
+ syncProjectMdSequence(basePath, state.registry, result.order);
386
+
387
+ // Commit the change
388
+ const filesToAdd = [".gsd/QUEUE-ORDER.json", ".gsd/PROJECT.md"];
389
+ for (const r of result.depsToRemove) {
390
+ filesToAdd.push(`.gsd/milestones/${r.milestone}/${r.milestone}-CONTEXT.md`);
391
+ }
392
+ try {
393
+ nativeAddPaths(basePath, filesToAdd);
394
+ nativeCommit(basePath, "docs: reorder queue");
395
+ } catch {
396
+ // Commit may fail if nothing changed or git hooks block — non-fatal
397
+ }
398
+
399
+ const depInfo = result.depsToRemove.length > 0
400
+ ? ` (removed ${result.depsToRemove.length} depends_on)`
401
+ : "";
402
+ ctx.ui.notify(`Queue reordered: ${result.order.join(" → ")}${depInfo}`, "info");
403
+ }
404
+
405
+ /**
406
+ * Remove specific depends_on entries from milestone CONTEXT.md frontmatter.
407
+ */
408
+ function removeDependsOnFromContextFiles(
409
+ basePath: string,
410
+ depsToRemove: Array<{ milestone: string; dep: string }>,
411
+ ): void {
412
+ // Group removals by milestone
413
+ const byMilestone = new Map<string, string[]>();
414
+ for (const { milestone, dep } of depsToRemove) {
415
+ const existing = byMilestone.get(milestone) ?? [];
416
+ existing.push(dep);
417
+ byMilestone.set(milestone, existing);
418
+ }
419
+
420
+ for (const [mid, depsToRemoveForMid] of byMilestone) {
421
+ const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
422
+ if (!contextFile || !existsSync(contextFile)) continue;
423
+
424
+ const content = readFileSync(contextFile, "utf-8");
425
+
426
+ // Parse frontmatter
427
+ const trimmed = content.trimStart();
428
+ if (!trimmed.startsWith("---")) continue;
429
+ const afterFirst = trimmed.indexOf("\n");
430
+ if (afterFirst === -1) continue;
431
+ const rest = trimmed.slice(afterFirst + 1);
432
+ const endIdx = rest.indexOf("\n---");
433
+ if (endIdx === -1) continue;
434
+
435
+ const fmText = rest.slice(0, endIdx);
436
+ const body = rest.slice(endIdx + 4);
437
+
438
+ // Parse depends_on line(s)
439
+ const fmLines = fmText.split("\n");
440
+ const removeSet = new Set(depsToRemoveForMid.map(d => d.toUpperCase()));
441
+
442
+ // Handle inline format: depends_on: [M009, M010]
443
+ const inlineMatch = fmLines.findIndex(l => /^depends_on:\s*\[/.test(l));
444
+ if (inlineMatch >= 0) {
445
+ const line = fmLines[inlineMatch];
446
+ const inner = line.match(/\[([^\]]*)\]/);
447
+ if (inner) {
448
+ const remaining = inner[1]
449
+ .split(",")
450
+ .map(s => s.trim())
451
+ .filter(s => s && !removeSet.has(s.toUpperCase()));
452
+ if (remaining.length === 0) {
453
+ fmLines.splice(inlineMatch, 1);
454
+ } else {
455
+ fmLines[inlineMatch] = `depends_on: [${remaining.join(", ")}]`;
456
+ }
457
+ }
458
+ } else {
459
+ // Handle multi-line format
460
+ const keyIdx = fmLines.findIndex(l => /^depends_on:\s*$/.test(l));
461
+ if (keyIdx >= 0) {
462
+ let end = keyIdx + 1;
463
+ while (end < fmLines.length && /^\s+-\s/.test(fmLines[end])) {
464
+ const val = fmLines[end].replace(/^\s+-\s*/, "").trim().toUpperCase();
465
+ if (removeSet.has(val)) {
466
+ fmLines.splice(end, 1);
467
+ } else {
468
+ end++;
469
+ }
470
+ }
471
+ if (end === keyIdx + 1 || (end <= fmLines.length && !/^\s+-\s/.test(fmLines[keyIdx + 1] ?? ""))) {
472
+ fmLines.splice(keyIdx, 1);
473
+ }
474
+ }
475
+ }
476
+
477
+ // Rebuild file
478
+ const newFm = fmLines.filter(l => l !== undefined).join("\n");
479
+ const newContent = newFm.trim()
480
+ ? `---\n${newFm}\n---${body}`
481
+ : body.replace(/^\n+/, "");
482
+ writeFileSync(contextFile, newContent, "utf-8");
483
+ }
484
+ }
485
+
486
+ function syncProjectMdSequence(
487
+ basePath: string,
488
+ registry: Array<{ id: string; title: string; status: string }>,
489
+ newOrder: string[],
490
+ ): void {
491
+ const projectPath = resolveGsdRootFile(basePath, "PROJECT");
492
+ if (!projectPath || !existsSync(projectPath)) return;
493
+
494
+ const content = readFileSync(projectPath, "utf-8");
495
+ const lines = content.split("\n");
496
+
497
+ const headerIdx = lines.findIndex(l => /^##\s+Milestone Sequence/.test(l));
498
+ if (headerIdx < 0) return;
499
+
500
+ let tableStart = headerIdx + 1;
501
+ while (tableStart < lines.length && !lines[tableStart].startsWith("|")) tableStart++;
502
+ if (tableStart >= lines.length) return;
503
+
504
+ let tableEnd = tableStart + 1;
505
+ while (tableEnd < lines.length && lines[tableEnd].startsWith("|")) tableEnd++;
506
+
507
+ const registryMap = new Map(registry.map(m => [m.id, m]));
508
+ const completedSet = new Set(registry.filter(m => m.status === "complete").map(m => m.id));
509
+
510
+ const newRows: string[] = [];
511
+ for (const m of registry) {
512
+ if (m.status === "complete") {
513
+ newRows.push(`| ${m.id} | ${m.title} | ✅ Complete |`);
514
+ }
515
+ }
516
+ let isFirst = true;
517
+ for (const id of newOrder) {
518
+ if (completedSet.has(id)) continue;
519
+ const m = registryMap.get(id);
520
+ if (!m) continue;
521
+ const status = isFirst ? "📋 Next" : "📋 Queued";
522
+ newRows.push(`| ${m.id} | ${m.title} | ${status} |`);
523
+ isFirst = false;
524
+ }
525
+
526
+ const headerLine = lines[tableStart];
527
+ const separatorLine = lines[tableStart + 1];
528
+ const newTable = [headerLine, separatorLine, ...newRows];
529
+ lines.splice(tableStart, tableEnd - tableStart, ...newTable);
530
+ writeFileSync(projectPath, lines.join("\n"), "utf-8");
531
+ }
532
+
533
+ async function showQueueAdd(
534
+ ctx: ExtensionCommandContext,
535
+ pi: ExtensionAPI,
536
+ basePath: string,
537
+ state: Awaited<ReturnType<typeof deriveState>>,
538
+ ): Promise<void> {
539
+ const milestoneIds = findMilestoneIds(basePath);
540
+
308
541
  // ── Build existing milestones context for the prompt ────────────────
309
542
  const existingContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
310
543
 
@@ -712,7 +945,8 @@ export async function showSmartEntry(
712
945
  }
713
946
 
714
947
  // ── Ensure .gitignore has baseline patterns ──────────────────────────
715
- ensureGitignore(basePath);
948
+ const commitDocs = loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs;
949
+ ensureGitignore(basePath, { commitDocs });
716
950
  untrackRuntimeFiles(basePath);
717
951
 
718
952
  // ── No GSD project OR no milestone → Create first/next milestone ────
@@ -723,11 +957,14 @@ export async function showSmartEntry(
723
957
 
724
958
  // ── Create PREFERENCES.md template ────────────────────────────────
725
959
  ensurePreferences(basePath);
726
- try {
727
- nativeAddPaths(basePath, [".gsd", ".gitignore"]);
728
- nativeCommit(basePath, "chore: init gsd");
729
- } catch {
730
- // nothing to commit — that's fine
960
+ // Only commit .gsd/ init when commit_docs is not explicitly false
961
+ if (commitDocs !== false) {
962
+ try {
963
+ nativeAddPaths(basePath, [".gsd", ".gitignore"]);
964
+ nativeCommit(basePath, "chore: init gsd");
965
+ } catch {
966
+ // nothing to commit — that's fine
967
+ }
731
968
  }
732
969
  }
733
970
 
@@ -28,10 +28,11 @@ import { createBashTool, createWriteTool, createReadTool, createEditTool, isTool
28
28
  import { registerGSDCommand, loadToolApiKeys } from "./commands.js";
29
29
  import { registerExitCommand } from "./exit-command.js";
30
30
  import { registerWorktreeCommand, getWorktreeOriginalCwd, getActiveWorktreeName } from "./worktree-command.js";
31
+ import { getActiveAutoWorktreeContext } from "./auto-worktree.js";
31
32
  import { saveFile, formatContinue, loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
32
33
  import { loadPrompt } from "./prompt-loader.js";
33
34
  import { deriveState } from "./state.js";
34
- import { isAutoActive, isAutoPaused, handleAgentEnd, pauseAuto, getAutoDashboardData } from "./auto.js";
35
+ import { isAutoActive, isAutoPaused, handleAgentEnd, pauseAuto, getAutoDashboardData, markToolStart, markToolEnd } from "./auto.js";
35
36
  import { saveActivityLog } from "./activity-log.js";
36
37
  import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId } from "./guided-flow.js";
37
38
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
@@ -47,10 +48,11 @@ import {
47
48
  resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTaskFiles, resolveTasksDir,
48
49
  relSliceFile, relSlicePath, relTaskFile,
49
50
  buildSliceFileName, buildMilestoneFileName, gsdRoot, resolveMilestonePath,
51
+ resolveGsdRootFile,
50
52
  } from "./paths.js";
51
53
  import { Key } from "@gsd/pi-tui";
52
54
  import { join } from "node:path";
53
- import { existsSync } from "node:fs";
55
+ import { existsSync, readFileSync } from "node:fs";
54
56
  import { shortcutDesc } from "../shared/terminal.js";
55
57
  import { Text } from "@gsd/pi-tui";
56
58
  import { pauseAutoForProviderError } from "./provider-error-pause.js";
@@ -272,6 +274,20 @@ export default function (pi: ExtensionAPI) {
272
274
  }
273
275
  }
274
276
 
277
+ // Load project knowledge if available
278
+ let knowledgeBlock = "";
279
+ const knowledgePath = resolveGsdRootFile(process.cwd(), "KNOWLEDGE");
280
+ if (existsSync(knowledgePath)) {
281
+ try {
282
+ const content = readFileSync(knowledgePath, "utf-8").trim();
283
+ if (content) {
284
+ knowledgeBlock = `\n\n[PROJECT KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${content}`;
285
+ }
286
+ } catch {
287
+ // File read error — skip knowledge injection
288
+ }
289
+ }
290
+
275
291
  // Detect skills installed during this auto-mode session
276
292
  let newSkillsBlock = "";
277
293
  if (hasSkillSnapshot()) {
@@ -287,6 +303,7 @@ export default function (pi: ExtensionAPI) {
287
303
  let worktreeBlock = "";
288
304
  const worktreeName = getActiveWorktreeName();
289
305
  const worktreeMainCwd = getWorktreeOriginalCwd();
306
+ const autoWorktree = getActiveAutoWorktreeContext();
290
307
  if (worktreeName && worktreeMainCwd) {
291
308
  worktreeBlock = [
292
309
  "",
@@ -304,10 +321,27 @@ export default function (pi: ExtensionAPI) {
304
321
  "All file operations, bash commands, and GSD state resolve against the worktree path above.",
305
322
  "Use /worktree merge to merge changes back. Use /worktree return to switch back to the main tree.",
306
323
  ].join("\n");
324
+ } else if (autoWorktree) {
325
+ worktreeBlock = [
326
+ "",
327
+ "",
328
+ "[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]",
329
+ `IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`,
330
+ `The actual current working directory is: ${process.cwd()}`,
331
+ "",
332
+ "You are working inside a GSD auto-worktree.",
333
+ `- Milestone worktree: ${autoWorktree.worktreeName}`,
334
+ `- Worktree path (this is the real cwd): ${process.cwd()}`,
335
+ `- Main project: ${autoWorktree.originalBase}`,
336
+ `- Branch: ${autoWorktree.branch}`,
337
+ "",
338
+ "All file operations, bash commands, and GSD state resolve against the worktree path above.",
339
+ "Write every .gsd artifact in the worktree path above, never in the main project tree.",
340
+ ].join("\n");
307
341
  }
308
342
 
309
343
  return {
310
- systemPrompt: `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${newSkillsBlock}${worktreeBlock}`,
344
+ systemPrompt: `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${newSkillsBlock}${worktreeBlock}`,
311
345
  ...(injection
312
346
  ? {
313
347
  message: {
@@ -542,6 +576,16 @@ export default function (pi: ExtensionAPI) {
542
576
  const existing = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
543
577
  await saveFile(discussionPath, existing + newBlock);
544
578
  });
579
+
580
+ // ── tool_execution_start/end: track in-flight tools for idle detection ──
581
+ pi.on("tool_execution_start", async (event) => {
582
+ if (!isAutoActive()) return;
583
+ markToolStart(event.toolCallId);
584
+ });
585
+
586
+ pi.on("tool_execution_end", async (event) => {
587
+ markToolEnd(event.toolCallId);
588
+ });
545
589
  }
546
590
 
547
591
  async function buildGuidedExecuteContextInjection(prompt: string, basePath: string): Promise<string | null> {
@@ -303,6 +303,50 @@ export function formatCost(cost: number): string {
303
303
  return `$${n.toFixed(2)}`;
304
304
  }
305
305
 
306
+ // ─── Budget Prediction ────────────────────────────────────────────────────────
307
+
308
+ /**
309
+ * Calculate average cost per unit type from completed units.
310
+ * Returns a Map from unit type to average cost in USD.
311
+ */
312
+ export function getAverageCostPerUnitType(units: UnitMetrics[]): Map<string, number> {
313
+ const sums = new Map<string, { total: number; count: number }>();
314
+ for (const u of units) {
315
+ const entry = sums.get(u.type) ?? { total: 0, count: 0 };
316
+ entry.total += u.cost;
317
+ entry.count += 1;
318
+ sums.set(u.type, entry);
319
+ }
320
+ const avgs = new Map<string, number>();
321
+ for (const [type, { total, count }] of sums) {
322
+ avgs.set(type, total / count);
323
+ }
324
+ return avgs;
325
+ }
326
+
327
+ /**
328
+ * Estimate remaining cost given average costs and remaining unit counts.
329
+ * @param avgCosts - Average cost per unit type
330
+ * @param remainingUnits - Array of unit types still to dispatch
331
+ * @param fallbackAvg - Fallback average if unit type not seen before
332
+ * @returns Estimated remaining cost in USD
333
+ */
334
+ export function predictRemainingCost(
335
+ avgCosts: Map<string, number>,
336
+ remainingUnits: string[],
337
+ fallbackAvg?: number,
338
+ ): number {
339
+ // If no averages available, use overall average as fallback
340
+ const allAvgs = [...avgCosts.values()];
341
+ const overallAvg = fallbackAvg ?? (allAvgs.length > 0 ? allAvgs.reduce((a, b) => a + b, 0) / allAvgs.length : 0);
342
+
343
+ let total = 0;
344
+ for (const unitType of remainingUnits) {
345
+ total += avgCosts.get(unitType) ?? overallAvg;
346
+ }
347
+ return total;
348
+ }
349
+
306
350
  /**
307
351
  * Compute a projected remaining cost based on completed slice averages.
308
352
  *
@@ -17,6 +17,10 @@ const GIT_NO_PROMPT_ENV = {
17
17
  GIT_SVN_ID: "",
18
18
  };
19
19
 
20
+ // Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
21
+ // caller explicitly opts into the native helper.
22
+ const NATIVE_GSD_GIT_ENABLED = process.env.GSD_ENABLE_NATIVE_GSD_GIT === "1";
23
+
20
24
  // ─── Native Module Types ──────────────────────────────────────────────────
21
25
 
22
26
  interface GitDiffStat {
@@ -116,6 +120,7 @@ let loadAttempted = false;
116
120
  function loadNative(): typeof nativeModule {
117
121
  if (loadAttempted) return nativeModule;
118
122
  loadAttempted = true;
123
+ if (!NATIVE_GSD_GIT_ENABLED) return nativeModule;
119
124
 
120
125
  try {
121
126
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -6,6 +6,10 @@
6
6
 
7
7
  import type { Roadmap, BoundaryMapEntry, RoadmapSliceEntry, RiskLevel } from './types.js';
8
8
 
9
+ // Issue #453: auto-mode post-turn reconciliation must stay on the stable JS path
10
+ // unless the native parser is explicitly requested.
11
+ const NATIVE_GSD_PARSER_ENABLED = process.env.GSD_ENABLE_NATIVE_GSD_PARSER === "1";
12
+
9
13
  let nativeModule: {
10
14
  parseFrontmatter: (content: string) => { metadata: string; body: string };
11
15
  extractSection: (content: string, heading: string, level?: number) => { content: string; found: boolean };
@@ -29,6 +33,7 @@ let loadAttempted = false;
29
33
  function loadNative(): typeof nativeModule {
30
34
  if (loadAttempted) return nativeModule;
31
35
  loadAttempted = true;
36
+ if (!NATIVE_GSD_PARSER_ENABLED) return nativeModule;
32
37
 
33
38
  try {
34
39
  // Dynamic import to avoid hard dependency - fails gracefully if native module not built
@@ -15,6 +15,9 @@ import { nativeScanGsdTree, type GsdTreeEntry } from "./native-parser-bridge.js"
15
15
 
16
16
  // ─── Directory Listing Cache ──────────────────────────────────────────────────
17
17
 
18
+ /** Max entries before eviction. Prevents unbounded growth in long sessions (#611). */
19
+ const DIR_CACHE_MAX = 200;
20
+
18
21
  const dirEntryCache = new Map<string, Dirent[]>();
19
22
  const dirListCache = new Map<string, string[]>();
20
23
 
@@ -85,6 +88,7 @@ function cachedReaddirWithTypes(dirPath: string): Dirent[] {
85
88
  d.isSocket = () => false;
86
89
  return d;
87
90
  });
91
+ if (dirEntryCache.size >= DIR_CACHE_MAX) dirEntryCache.clear();
88
92
  dirEntryCache.set(dirPath, dirents);
89
93
  return dirents;
90
94
  }
@@ -92,6 +96,7 @@ function cachedReaddirWithTypes(dirPath: string): Dirent[] {
92
96
  }
93
97
 
94
98
  const entries = readdirSync(dirPath, { withFileTypes: true });
99
+ if (dirEntryCache.size >= DIR_CACHE_MAX) dirEntryCache.clear();
95
100
  dirEntryCache.set(dirPath, entries);
96
101
  return entries;
97
102
  }
@@ -107,6 +112,7 @@ function cachedReaddir(dirPath: string): string[] {
107
112
  const treeEntries = nativeTreeCache.get(key);
108
113
  if (treeEntries) {
109
114
  const names = treeEntries.map(e => e.name);
115
+ if (dirListCache.size >= DIR_CACHE_MAX) dirListCache.clear();
110
116
  dirListCache.set(dirPath, names);
111
117
  return names;
112
118
  }
@@ -114,6 +120,7 @@ function cachedReaddir(dirPath: string): string[] {
114
120
  }
115
121
 
116
122
  const entries = readdirSync(dirPath);
123
+ if (dirListCache.size >= DIR_CACHE_MAX) dirListCache.clear();
117
124
  dirListCache.set(dirPath, entries);
118
125
  return entries;
119
126
  }
@@ -248,6 +255,7 @@ export const GSD_ROOT_FILES = {
248
255
  STATE: "STATE.md",
249
256
  REQUIREMENTS: "REQUIREMENTS.md",
250
257
  OVERRIDES: "OVERRIDES.md",
258
+ KNOWLEDGE: "KNOWLEDGE.md",
251
259
  } as const;
252
260
 
253
261
  export type GSDRootFileKey = keyof typeof GSD_ROOT_FILES;
@@ -259,6 +267,7 @@ const LEGACY_GSD_ROOT_FILES: Record<GSDRootFileKey, string> = {
259
267
  STATE: "state.md",
260
268
  REQUIREMENTS: "requirements.md",
261
269
  OVERRIDES: "overrides.md",
270
+ KNOWLEDGE: "knowledge.md",
262
271
  };
263
272
 
264
273
  export function gsdRoot(basePath: string): string {